Opened 3 years ago

Closed 3 years ago

#32921 closed Bug (invalid)

Incorrect SQL generation for JSON has_key__in

Reported by: Shaheed Haque Owned by: nobody
Component: Database layer (models, ORM) Version: 3.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I have a Django 3.2.4 model with a JSONField (hosted on Postgres 12) called "snapshot" which contains employees like this:

    {
        "employee": {
            "6": {...},
            "99": {...}
        }
    }

I would like to perform a .filter() on the presence of certain employee keys, so I tried this:

    qs = company.payrun_set.filter(snapshot__employee__has_key__in=['6', '8'])

but this produces incorrect results. When I examine qs.query (which I know does not always produce an exact rendition), I see the following incorrect SQL:

    ...("paiyroll_payrun"."snapshot" #> ['employee', 'has_key']) IN ("6", "8")

Notice how has_key is treated as a literal key value. I believe the correct query can be formed like this:

    qs = company.payrun_set.extra(where=[''' "paiyroll_payrun"."snapshot" #> '{employee}' ?| array['6', '8'] '''])

and the corresponding SQL fragment should then look like this (again from examining qs.query):

   ..."paiyroll_payrun"."snapshot"#>'{employee}' ?| array['6', '8']

Basically, has_key followed by in should be converted into the "?| array[]".

I'm not an expert, so would love to be corrected if I have misunderstood.

Thanks, Shaheed

Change History (2)

comment:1 by Shaheed Haque, 3 years ago

My fix was incomplete: it turns out that an empty "array[]" gives rise to an error:

django.db.utils.ProgrammingError: cannot determine type of empty array
LINE 1: ...021-06-12'::date AND (snapshot #> '{employee}' ?| array[])) ...
                                                             ^
HINT:  Explicitly cast to the desired type, for example ARRAY[]::integer[].

which I fixed by casting to an array of strings using ::varchar[] (since JSON keys are always strings) like this:

qs = company.payrun_set.extra(where=[''' "paiyroll_payrun"."snapshot" #> '{employee}' ?| array[...is empty...]::varchar[] '''])

In a similar vein, I'm not clear if the values in the array should/could be explicitly converted to/masked as strings?

Last edited 3 years ago by Shaheed Haque (previous) (diff)

comment:2 by Mariusz Felisiak, 3 years ago

Resolution: invalid
Status: newclosed

has_key cannot be chained with other lookups, if you do this it will be interpreted as a key lookup for JSONField as you observed. You should use has_key_any. If you're having trouble understanding how Django works, see TicketClosingReasons/UseSupportChannels for ways to get help.

Note: See TracTickets for help on using tickets.
Back to Top