ANN: squeal-postgresql-qq

I would like to announce a new package: squeal-postgresql-qq

This is a QuasiQuoter which allows you to write regular SQL statements and have them translated into the corresponding squeal-postgresql statement.

The motivation is that Squeal is an absolutely amazing way to write type safe queries. But I don’t use it so often that I can ever memorize all the little bits of how it contorts SQL syntax into Haskell. Sometimes I know exactly the statement I want to write, but get frustrated relearning how to make it happen. This lets me simply write the SQL statement in SQL language that I know[1] and the QuasiQuoter will do the translation for me!

It is still missing a lot of features (where “features” means coverage of the Postgres SQL syntax), but on the other hand it can be useful in a decent number of straightforward scenarios where you just want to write some basic sql that typechecks.

I plan to continue fleshing out its coverage over time. If you use it and find something you would like that is unsupported, please submit an issue. I will prioritize real-world examples when adding features.

Thanks!

[1] Ha. Well. Allegedly know. Turns out there is a lot about SQL I don’t know.

Examples

Some examples (take from the docs)

For the examples, let’s assume you have a database like this:

type UsersConstraints = '[ "pk_users" ::: 'PrimaryKey '["id"] ]
type UsersColumns =
  '[          "id" :::   'Def :=> 'NotNull 'PGtext
   ,        "name" ::: 'NoDef :=> 'NotNull 'PGtext
   , "employee_id" ::: 'NoDef :=> 'NotNull 'PGuuid
   ,         "bio" ::: 'NoDef :=> 'Null    'PGtext
   ]
type EmailsConstraints =
  '[ "pk_emails"  ::: 'PrimaryKey '["id"]
   , "fk_user_id" ::: 'ForeignKey '["user_id"] "public" "users" '["id"]
   ]
type EmailsColumns =
  '[      "id" :::   'Def :=> 'NotNull 'PGint4
   , "user_id" ::: 'NoDef :=> 'NotNull 'PGtext
   ,   "email" ::: 'NoDef :=> 'Null 'PGtext
   ]
type Schema =
  '[  "users" ::: 'Table (UsersConstraints :=> UsersColumns)
   , "emails" ::: 'Table (EmailsConstraints :=> EmailsColumns)
   ]

Insert Example

mkStatement :: Int32 -> Text -> Maybe Text -> Statement DB () ()
mkStatement emailId uid email =
  [ssql|
    insert into
      emails (id, user_id, email)
      values (inline("emailId"), inline(uid), inline_param(email))
  |]

Notice the quotes around @“emailId”@. This is because postgres SQL parsing mandates the unquoted idents be converted to lower case (as a way of being “case insensitive”), which would result in the quasiquoter injecting the lower case @emailid@ variable, which is not in scope. The solution is to double quote the SQL ident so that its casing is preserved.

Select Example

mkStatement
  :: Text
  -> Statement
       DB
       ()
       ( Field "id" Text
       , ( Field "name" Text
         , ( Field "email" (Maybe Text)
           , ()
           )
         )
       )
mkStatement targetEmail =
  [ssql|
    select users.id, users.name, emails.email
    from users
    left outer join emails
    on emails.user_id = users.id
    where emails.email = inline("targetEmail")
  |]
7 Likes