lpsmith / postgresql-simple

Mid-level client library for accessing PostgreSQL from Haskell
Other
206 stars 71 forks source link

Support named arguments inside QuasiQuoter #258

Closed chshersh closed 5 years ago

chshersh commented 6 years ago

I'm trying to support classical usage of after :: Maybe Id parameter. Instead of having two queries for Nothing and Just I want to write only single query like this:

SELECT col1, col2
  FROM my_table
 WHERE (after IS NULL OR id > after)

Unfortunately, this doesn't work with quasiquoter because if I use ? twice I need to supply more than argument.

Workaround: just duplicate query parameter. But it would be better to have named parameters inside quasi-quoter, like this:

[sql|
SELECT col1, col2
  FROM my_table
 WHERE user_id = ?
   AND (?after IS NULL OR id > ?after)
|] (userId, after)
lpsmith commented 5 years ago

Yes, I've been wanting to do something along these lines. However, you can't implement your idea exactly as written, rather it would need to be something closer to:

[sql|
SELECT col1, col2
  FROM my_table
 WHERE user_id = ?
   AND (?after IS NULL OR id > ?after)
|] (Only userId)

This is because we don't have programmatic access to the variable name without wrapping a larger expression in template haskell. However, we don't need that in this case.

chshersh commented 5 years ago

sqlite-simple has very nice support for named arguments. Queries looks like this:

executeNamed_ [sql|
        INSERT INTO file_upload_history
            (file_name, uploader_id, timestamp, data_type, remarks)
        VALUES
            (:file, :uploader, :time, :type, :remarks)
        |] [ ":file"     := duFileName
           , ":uploader" := adminId
           , ":time"     := curTime
           , ":type"     := duDataType
           , ":remarks"  := duRemarks
           ]

You can't mix named and non-named parameters in single query. But that's even better.

bergey commented 5 years ago

I'd like to implement a named-parameter quasiquoter along these lines. I don't think I can make it work for either of the syntaxes above, because a QuasiQuoter can only emit a single Expression, and cannot modify the parameters tuple. @lpsmith do you have some clever way to support named params and backwards compatibility with the sql QQ, or do you agree with the below?

If the QQ emits a pair, call sites can look like:

uncurry (query conn) [sqlNamed|SELECT * FROM table WHERE column = ?foo|]

We can still mix positional and named, if people really want that, like:

uncurry (query conn) 
    ([sqlNamed|SELECT * FROM table WHERE column = ?foo AND c2 = ? |] (Only bar))
-- desugars to
uncurry (query conn) ((\(Only a) -> 
    ("SELECT * FROM table WHERE column = ? AND c2 = ?", (foo, a))) (Only bar))

Is this what folks want? Any better ideas for making usage more like the current library?

chshersh commented 5 years ago

Looks like the library is maintained under the following fork:

I can't transfer the issue there, so closing it here.

chshersh commented 5 years ago

Just in case somebody is looking for named parameters in postgresql-simple and found this issue. We've created the postgresql-simple-named library which adds support for named parameters on top of postgresql-simple: