Fork me on GitHub
#honeysql
<
2024-06-12
>
igrishaev17:06:38

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

igrishaev17:06:45

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?

seancorfield18:06:38

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

seancorfield18:06:44

(this has come up before)

seancorfield18:06:27

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).

seancorfield18:06:53

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

seancorfield18:06:02

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?

igrishaev18:06:51

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

igrishaev18:06:00

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

seancorfield18:06:00

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

seancorfield18:06:09

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?

igrishaev19:06:45

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

igrishaev19:06:41

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;

igrishaev19:06:39

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.

igrishaev19:06:43

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.

seancorfield19:06:55

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

seancorfield19:06:27

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

seancorfield19:06:55

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?

igrishaev19:06:25

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"

igrishaev19:06:26

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]])

seancorfield20:06:33

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

seancorfield20:06:09

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.

igrishaev17:06:01

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

seancorfield17:06:06

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

igrishaev17:06:47

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

igrishaev17:06:19

but maybe I'm mixing something...

seancorfield17:06:08

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).

seancorfield17:06:34

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

igrishaev17:06:36

ok, sorry for confusion