Skip to content

Empty list as param generates incorrect sql query at runtime. #5

Open

Description

@NightBlues

If we pass empty list, this code
https://github.com/tizoc/ppx_pgsql/blob/master/lib/runtime/ppx_pgsql_runtime.ml#L49
generates () that postgres can't understand. For example:

SELECT COUNT(*) FROM users
WHERE id IN (1,2,3)

is correct, but

SELECT COUNT(*) FROM users
WHERE id IN ()

gives syntax error.

Activity

  1. NightBlues commented on Apr 9, 2019

    @NightBlues
    Author

    I can make a patch, but I dont see easy way to fix it - if we substitute an empty array here we have to specify types explicitly because we generate prepared statement instead of real query and Postgres can't infer it, but we don't know params types at this point.

  2. tizoc commented on Apr 9, 2019

    @tizoc
    Owner

    I can't think of any good solution. Other than what you mention, the alternative is to replace the whole expression <VALUE-EXPRESSION> IN () for false and <VALUE-EXPRESSION> NOT IN () for true, but doing that requires parsing SQL, which makes everything quite a bit more complicated than it is already. And thats not even a good translation because it doesn't behave the same for NULL values as the original expression.

  3. tizoc commented on Apr 9, 2019

    @tizoc
    Owner

    Thinking a bit more about it, the IN () case can be replaced by IN (NULL) while retaining the expected behaviour, right? It is NOT IN () that gets weird.

    @NightBlues thoughts?

  4. NightBlues commented on Apr 10, 2019

    @NightBlues
    Author

    According to postgres documentation NULL is like a blackhole - everything touched by it becomes NULL too:)
    1 IN (NULL) gives NULL, while 1 IN (2,3) gives false, NULL IN (NULL) also gives NULL because NULL is not equal to itself.
    Seems to be appropriate solution, despite looking like a hack.

  5. tizoc commented on Apr 15, 2019

    @tizoc
    Owner

    @NightBlues I have an untested alternative that may solve every case in a clean way, while at the same time avoiding the need of having to create a separate prepared statement for each list length (something that is needed with the current implementation). I'm not sure how well it will work in practice because PGOCaml doesn't define arrays for every type IIRC.

    The basic idea is:

    SELECT COUNT(*) FROM users
    WHERE id IN $@ids::int[]

    gets translated into

    SELECT COUNT(*) FROM users
    WHERE id IN (SELECT unnest($1::int[]))

    This requires the user to be explicit and cast the value to the required type. Anyway, I have to think about this more, just wanted to share the idea I had.

  6. tizoc commented on Feb 4, 2020

    @tizoc
    Owner

    @NightBlues by now you probably have this solved already, but I found another solution that seems to work fine:

    -- Equivalent to IN (...)
    SELECT COUNT(*) FROM users
    WHERE id = ANY($user_ids_array::int[])
    
    -- Equivalent to NOT IN (...)
    SELECT COUNT(*) FROM users
    WHERE id <> ALL($user_ids_array::int[])

    This will work with empty arrays too, and has the advantage of requiring a single prepared statement for every list length, while the IN/NOT IN version will create a new prepared statement for each list length.

    I will update the documentation with a comment mentioning this (along with other gotchas like nullable columns in views, and you COALESCE trick for outer joins)

  7. NightBlues commented on Feb 5, 2020

    @NightBlues
    Author

    Modern postgres has very powerfull features, for example sometimes I use conversions to json and back with json operators. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions