honeysql

igrishaev 2024-06-12T17:29:38.157079Z

What is the simplest way to render the following expression with a single question mark?

field ? something
When I try [:? :field ...] it produces double ?? which is not an option in my case

igrishaev 2024-06-12T17:40:45.569719Z

I've figured out with this:

(sql/register-op! (keyword ""))

[(keyword "") field [:raw "?"] fields]
which produces
field  ?  ...
with extra spaces around the question mark. Is there a better solution?

seancorfield 2024-06-12T18:17:38.242499Z

JDBC expects ?? and turns it back to ? as part of parameter substitution.

seancorfield 2024-06-12T18:17:44.881649Z

(this has come up before)

seancorfield 2024-06-12T18:20:27.976929Z

The honey.sql.pg-ops ns defines ? as a symbol that you can use instead of :? if you want (that ns exists mostly for the ops that cannot be represented as keyword literals in Clojure).

seancorfield 2024-06-12T18:20:53.616399Z

So, the answer to your question is: yes, the better solution is just to use :? 🙂

seancorfield 2024-06-12T18:22:02.857109Z

Can you explain why ?? is not an option for you? Are you using HoneySQL to generate SQL that you are using somewhere other than JDBC?

igrishaev 2024-06-12T18:27:51.597059Z

yeah I remember that issue with ?? and ? which I rose by mistake. I'll take a look at the pg-ops, thank you! I'm building a complex JSONpath expression. It's an internal sublanguage in Postgres to query JSONb blobs. It has @@ , @? and ? operatos

igrishaev 2024-06-12T18:30:00.211079Z

But no, the ps-ops namespace didn't work. It still generates ??

igrishaev 2024-06-12T18:31:02.787149Z

Just for the reference: https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-SQLJSON-PATH

seancorfield 2024-06-12T18:50:00.209769Z

> It still generates ?? Yes, of course, because JDBC requires ?? -- ? must be escaped.

seancorfield 2024-06-12T18:51:09.312009Z

Again, can you explain why -- for JDBC -- you think you need a single ? here and not the correctly-escaped ?? version? Are you getting an error from JDBC with ?? or are you trying to use the generated SQL in a non-JDBC context?

igrishaev 2024-06-12T19:27:45.058779Z

Because JSONpath is a special language that doesn't accept JDBC parameters. Let me find an example...

igrishaev 2024-06-12T19:29:41.040929Z

select id from my_table
where
   entity @@ '(
               $.attrs."foo-bar-3-test-name" == "Aaa"
            && !($.attrs."foo-aaa-1-hello-name" == "XXX")
            && exists($.attrs."asset-class"."some-class-code" ? ((@ == "17" || @ == "22")))
            && (
                (   $.attrs."foo-first-name" == "Hello"
                 && $.attrs."fo-last-name" == "Some Value")
                ||
                (   $.attrs."bar-first-name" == "Hello"
                 && $.attrs."bar-last-name" == "Some Values")))'
limit 10;

igrishaev 2024-06-12T19:33:39.257929Z

The expression after @@ is JSONpath. It's a string which is processed by Postgres internally when querying JSONb values. This expression doesn't capture JDBC parameters. It may accept a JSON map of its own parameters though. The thing is, my query is pretty simple but the JSONpath expression is quite complex, and I've got to build it out from a so-called "DSL" which was used for another datastore. I HoneySQL because it can build JSONpath pretty well. The only issue I have is this ? operator which, in JSONpath, means checking a certain path. But with the workaround that I shared above, it works.

igrishaev 2024-06-12T19:36:43.799459Z

Briefly, first I build a data structure for JSONpath, then bake it into a string, and then build the final query passing that path inline.

seancorfield 2024-06-12T19:38:55.024089Z

You're producing the JSONpath separately and then dropping it :inline into the SQL?

seancorfield 2024-06-12T19:39:27.130579Z

(I'm actually a bit shocked you're able to use HoneySQL to generate even close to that!)

seancorfield 2024-06-12T19:40:55.834119Z

If you can explain a bit more, with code, about how exactly you're doing this, I may be able to come up with a solution... maybe a public but undocumented dynamic var that you could use to turn off the ? escaping in the few places where it happens?

igrishaev 2024-06-12T19:48:25.352379Z

Well, there is nothing special here, really. I'm receiving a DSL like

[:and [:= :some.attr.name "test"] 
      [:wildcard :some.other.field "hello"]
      [:or [:gte :some.attr.age 42]
           [:in :attr.status ["created" "pending"]]]]
I parse it with Malli so I have a tagged data strructure. then I traverse this structure using case. For each tag, I generate a peace of HoneySQL expression, for example:
[:&& [:= [:. :some :attr :name] [:raw (json/encode "test")]]
      [:|| ...]
      ]
The thing is, AND becomes &&, and OR becomes || I registered both them as operators, and I also had to write my own expression for regex_like
$.foo.bar like_regex "pattern" flag "iq"

igrishaev 2024-06-12T19:50:26.824119Z

Here is an example of generating

... exists($.foo.bar ? (@.id == 1 || @.id == 2, ...))
code fragment
:predicate-in
      (let [{:keys [attr value]}
            content

            field
            (-> attr
                attr->path
                path->$attr)

            fields
            (into [:||]
                  (for [v value]
                    [:== (keyword "@") [:raw (util/to-json v)]]))]

        [:exists [(keyword "") field [:raw "?"] fields]])

seancorfield 2024-06-12T20:34:33.574459Z

And how do you combine this into the main SQL part, in HoneySQL?

seancorfield 2024-06-12T20:36:09.507919Z

It sounds like you call into HoneySQL once for the JSONpath part and then a second time to generate the SQL with the SQL from the JSONpath part as an inlined string? That's what I'm trying to understand.

igrishaev 2024-06-13T06:55:54.358889Z

Exactly, this is a simplified code fragment:

(let [json-path-map
      (build-from-dsl ...)
      
      [jsonpath]
      (honey.sql/format json-path-map)

      top-map
      {:select ...
       :from ...
       :where [pg/atat :entity [:inline jsonpath]]}]

  (honey.sql/format top-map))

seancorfield 2024-06-13T07:26:08.466279Z

OK, so either an option or a dyn var to disable the escaping would work for you? Can you create an issue on GH and I'll look at that for the next release (which will be fairly soon, I expect, since I just made a new release tonight)

igrishaev 2024-06-13T11:06:07.690339Z

Sure, here it is: https://github.com/seancorfield/honeysql/issues/533 Thank you for that amazing lib, it's helped me more times than I can remember!

p-himik 2024-06-13T11:38:45.293769Z

Shouldn't that be considered abusing HoneySQL? I mean, jsonpath is not SQL. Nor is xpath or regex or anything else that PostgreSQL supports and that has to be provided as a plain string.

igrishaev 2024-06-13T14:44:03.708139Z

Yeah I agree it’s some sort of abusing. That’s why I’m not forcing this.

p-himik 2024-06-13T14:55:16.841109Z

Right, it's more of a question for @seancorfield. :) Even though he's the maintainer, I'm still a bit worried about scope creep and all the negatives it entails.

seancorfield 2024-06-13T16:28:49.132679Z

user=> (require '[honey.sql :as sql] 'honey.sql.pg-ops)
nil
user=> (sql/format {:select [[[:? 13 42]]]})
["SELECT ? ?? ?" 13 42]
user=> (sql/format {:select [[[:? 13 42]]]} {:inline true})
["SELECT 13 ?? 42"]
user=> (binding [sql/*escape-?* false] (sql/format {:select [[[:? 13 42]]]} {:inline true}))
["SELECT 13 ? 42"]
user=> 
It won't be documented beyond the brief mention in the change log.

seancorfield 2024-06-13T16:29:29.526619Z

It'll be a while before I cut a new release, so you'll have to use a git dep for now (or the SNAPSHOT that should be deployed to Clojars in a few minutes).

igrishaev 2024-06-13T18:46:42.595579Z

Thank you a lot!

igrishaev 2024-06-15T17:33:01.637409Z

By the way, there is a ? operator for Postgres as well, not only for JSON path. For example, to ensure that the lefthand JSON includes the righthand one:

select '{"foo": 123}'::jsonb ? 'foo';
so that feature with muting ?? may be considered not only as a hack but a feature too

seancorfield 2024-06-15T17:36:06.955679Z

Right, but PG JDBC accepts ?? there and still treats it as the ? operator.

igrishaev 2024-06-15T17:36:47.768819Z

ah you're right. It failed before with JSONpath because it was inside 'quotes'

igrishaev 2024-06-15T17:37:19.210889Z

but maybe I'm mixing something...

seancorfield 2024-06-15T17:38:08.954929Z

For SQL, ?? works in JDBC (for PG). For JSONpath, yes, that's a "string" so you need ? there instead of ?? I suspect (which is what triggered all of this).

seancorfield 2024-06-15T17:38:34.963309Z

Anyway, you have an (undocumented) option to suppress ?? escaping so you can experiment 🙂

igrishaev 2024-06-15T17:38:36.944119Z

ok, sorry for confusion