squeal-postgresql-qq: QuasiQuoter transforming raw sql into Squeal expressions.

[ library, mit, unclassified ] [ Propose Tags ] [ Report a vulnerability ]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.1.0, 0.1.1.1
Change log changelog.md
Dependencies aeson (>=2.1.2.1 && <2.3), base (>=4.18.3.0 && <4.22), bytestring (>=0.11.3.0 && <0.13), generics-sop (>=0.5.1.3 && <0.6), postgresql-syntax (>=0.4.1 && <0.5), squeal-postgresql (>=0.9.1.3 && <0.10), template-haskell (>=2.20.0.0 && <2.24), text (>=1.2.5.0 && <2.2), time (>=1.9.3 && <1.15), uuid (>=1.3.15 && <1.4) [details]
License MIT
Copyright 2025 Rick Owens
Author Rick Owens
Maintainer rick@owensmurray.com
Home page https://github.com/owensmurray/squeal-postgresql-qq
Uploaded by rickowens at 2025-07-19T15:28:57Z
Distributions
Downloads 10 total (10 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for squeal-postgresql-qq-0.1.1.1

[back to package description]

squeal-postgresql-qq

This library provides a Template Haskell quasiquoter parsing SQL as the quoted language and producing corresponding squeal-postgresql expressing. The goal is to provide an easier way to use the squeal-postgresql library, by eliminating (or at least reducing) the need for the user to learn the squeal "DSL" and allowing her to write regular SQL instead.

Stability

I would give this package a 5 out of 10 for stability where 0 is completely unstable and experimental and 10 is maybe like the aeson package.

I think I've got a very large and usable segment of SQL supported but of course there are unsupported features that are kind of important such as common table expressions.

I don't foresee backwards incompatable changes being a problem because, after all, the "interface" is mostly the SQL language, which is stable. Most work will be about supporting new corners of SQL.

In terms of maintenance, I intend to be responsive to any bugs and to keep up to date with the latest dependencies and GHC versions. In other words, this is a maintained package, even if I experience a lull in adding new supported SQL features.

Production usage

I would feel relatively comfortable using this in production. The risk regarding stability/maintenance is pretty low.

If you have queries that are supported, great! They'll continue to be supported. If you have a query that is not supported, you can always fall back to crafting squeal expressions manually. (File an issue! I'll prioritize real-world usage.)

If you have a supported SQL statement that you find you have to modify in a way that makes it unsupported, you can always tell GHC to -ddump-splices and use the quasi-quoter generated squeal as a starting point for your modifications.

How to use this library.

See the haddocks.

Most important features not currently implemented

  • Prepared statement parameters
    • See the haddock documentation for how to get haskell values into your sql statements.
  • ON CONFLICT clause for INSERT statements.

Other features not currently implemented

This is a list of known unsupported SQL features. If you need one of these, please open an issue!

This list was generated by an LLM, and may not be complete.

General Query Structure

  • TABLESAMPLE clause
  • ONLY keyword for table inheritance
  • WINDOW clause and window functions (OVER)
  • INTO clause (SELECT ... INTO ...)
  • ORDER BY USING
  • FOR READ ONLY locking clause
  • FOR UPDATE/SHARE OF with qualified table names.
  • WHERE CURRENT OF for cursors
  • Aliasing a JOIN clause directly (e.g., (SELECT * FROM t1 JOIN t2 ON ...) AS myalias)
  • NATURAL JOIN
  • USING join qualification (e.g., JOIN ... USING (col))
  • LIMIT with comma offset (e.g. LIMIT 10, 20)
  • LIMIT ALL
  • FETCH clause
  • Advanced GROUP BY features (GROUPING SETS, CUBE, ROLLUP)
  • Multi-row VALUES clause (e.g. VALUES (1, 'a'), (2, 'b'))

Common Table Expressions (WITH clauses)

  • Recursive WITH clauses (WITH RECURSIVE ...)
  • MATERIALIZED / NOT MATERIALIZED hints
  • Column lists for CTEs are only partially supported (e.g., not for top-level SELECT statements)
  • Data-modifying statements (INSERT, UPDATE, DELETE) within a WITH clause

Data Manipulation (INSERT/UPDATE/DELETE)

  • INSERT ... DEFAULT VALUES
  • INSERT INTO table (columns) SELECT ... (must omit column list)
  • OVERRIDING clause for identity columns in INSERT
  • Column indirection in INSERT target lists (e.g., INSERT INTO tbl (col[1]) ...)
  • Complex relation expressions in UPDATE or DELETE targets
  • Column indirection in UPDATE SET clauses (e.g., UPDATE tbl SET col[1] = ...)
  • UPDATE with multiple-column SET (e.g. SET (a,b) = (1,2))

Expressions and Functions

  • LIKE with ESCAPE
  • OPERATOR() syntax
  • Parameter indirection (e.g., $1[i])
  • Indirection on parenthesized expressions (e.g., (expr)[i])
  • Aggregate FILTER clause
  • WITHIN GROUP clause for aggregates
  • DISTINCT in function arguments
  • ORDER BY in function arguments
  • Function name indirection (e.g., schema.func)
  • Named or colon-syntax function arguments (e.g. my_func(arg_name => 'val'))

Types and Casting

  • SETOF type modifier
  • BIT and BIT VARYING types
  • INTERVAL with qualifiers
  • Types with precision/scale (TIMESTAMP, TIME, FLOAT, NUMERIC, etc.)
  • Qualified type names (e.g., schema.my_type)
  • CURRENT_TIMESTAMP with precision
  • Multidimensional arrays with explicit bounds

Supported features.

This is the output from the test suite, which gives a pretty good indication of the supported SQL language features.

queries
  select * from users [✔]
  select * from public.users [✔]
  SELECT * FROM "users" AS "users" [✔]
  select * from users where name = 'bob' [✔]
  select users.name from users [✔]
  select name from users [✔]
  select count(*) from users group by () [✔]
  select name, id from users [✔]
  select id, name from users [✔]
  select users.id, employee_id from users [✔]
  select users.* from users [✔]
  select users.* from other.users [✔]
  select * from users limit 3 [✔]
  select * from users limit inline(lim) [✔]
  select * from users offset inline(off) [✔]
  select * from users offset 1 [✔]
  select users.id, employee_id as emp_id from users [✔]
  select users.id as user_id, employee_id from users [✔]
  select users.id from users left outer join emails on emails.user_id = users.id [✔]
  select users.id, users.name, emails.email from users left outer join emails on emails.user_id = users.id where emails.email = inline("targetEmail") [✔]
  select 'text_val' [✔]
  select 1 [✔]
  select 1 AS num, 'text_val' AS txt [✔]
  group by
    select name from users group by name [✔]
    select employee_id, count(id) from users group by employee_id [✔]
    select employee_id, name, count(id) from users group by employee_id, name [✔]
  common table expressions
    with users_cte as (select * from users) select * from users_cte [✔]
    with users_cte as (select * from users), emails_cte as (select * from emails) select users_cte.*, emails_cte.email from users_cte join emails_cte on users_cte.id = emails_cte.user_id [✔]
inserts
  insert into emails (id, user_id, email) values (1, 'user-1', 'foo@bar') [✔]
  insert into emails (id, user_id, email) values (1, 'user-1', $1) [✔]
  insert into emails (id, user_id, email) values (1, $2, $1) [✔]
  insert into emails (id, user_id, email) values (inline(i), inline(uid), inline_param(e)) [✔]
  default keyword
    insert into emails (id, user_id, email) values (default, 'foo', 'bar') [✔]
    insert into emails (id, user_id, email) values (deFault, 'foo', 'bar') [✔]
    insert into emails (id, user_id, email) values (DEFAULT, 'foo', 'bar') [✔]
  null keyword
    insert into emails (id, user_id, email) values (DEFAULT, 'foo', null) [✔]
    insert into emails (id, user_id, email) values (DEFAULT, 'foo', NULL) [✔]
    insert into emails (id, user_id, email) values (DEFAULT, 'foo', NuLL) [✔]
  insert ... select ...
    insert into emails select id, user_id, email from emails where id = 1 [✔]
    insert into emails select id, user_id, email from emails where id = $1 [✔]
    insert into users_copy select id, name, bio from users where users.id = 'uid1' [✔]
  returning clause
    insert into emails (id, user_id, email) values (1, 'user-1', 'foo@bar') returning id [✔]
    insert into emails (id, user_id, email) values (1, 'user-1', 'foo@bar') returning * [✔]
  with common table expressions
    with new_user (id, name, bio) as (values ('id_new', 'new_name', 'new_bio')) insert into users_copy select * from new_user [✔]
deletes
  delete from users where true [✔]
  delete from emails where id = 1 [✔]
  delete from emails where email = inline(e) [✔]
  delete from users where id = 'some-id' returning id [✔]
  with common table expressions
    with to_delete as (select id from users where name = 'Alice') delete from users where id in (select to_delete.id from to_delete) [✔]
    with to_delete as (select id from users where name = 'Alice') delete from users using to_delete where users.id = to_delete.id [✔]
updates
  update users set name = 'new name' where id = 'some-id' [✔]
  update users set name = 'new name', bio = 'new bio' where id = 'some-id' [✔]
  update users set name = inline(n) where id = 'some-id' [✔]
  update users set name = 'new name' where id = 'some-id' returning id [✔]
  with common table expressions
    with to_update as (select id from users where name = 'Alice') update users set name = 'Alicia' from to_update where users.id = to_update.id [✔]
scalar expressions
  select users.id != 'no-such-user' as neq from users [✔]
  select * from users where users.id <> 'no-such-user' [✔]
  select * from emails where emails.id > 0 [✔]
  select * from emails where emails.id >= 0 [✔]
  select * from emails where emails.id < 10 [✔]
  select * from emails where emails.id <= 10 [✔]
  select emails.id + 1 as plus_one from emails [✔]
  select emails.id - 1 as minus_one from emails [✔]
  select emails.id * 2 as times_two from emails [✔]
  select * from users where users.id = 'a' and users.name = 'b' [✔]
  select * from users where users.id = 'a' or users.name = 'b' [✔]
  select * from users where users.name like 'A%' [✔]
  select * from users where users.name ilike 'a%' [✔]
  select * from users where not (users.name = 'no-one') [✔]
  select -emails.id as neg_id from emails [✔]
  select * from users where users.bio is null [✔]
  select * from users where users.bio is not null [✔]
  function calls
    select coalesce(users.bio, 'no bio') as bio from users [✔]
    select lower(users.name) as lower_name from users [✔]
    select char_length(users.name) as name_len from users [✔]
    select character_length(users.name) as name_len_alias from users [✔]
    select "upper"(users.name) as upper_name from users [✔]
    select now() as current_time [✔]
    select current_date as today [✔]
    haskell variables in expressions [✔]
  select (emails.id + 1) * 2 as calc from emails [✔]
  select * from users where users.name in ('Alice', 'Bob') [✔]
  select * from users where users.name not in ('Alice', 'Bob') [✔]
  select * from emails where emails.id between 0 and 10 [✔]
  select * from emails where emails.id not between 0 and 10 [✔]
  select (e.id :: text) as casted_id from emails as e [✔]
  select * from users for update [✔]
  select * from jsonb_test [✔]
  select * from json_test [✔]
  select distinct name from users [✔]
  select distinct * from users [✔]
  select distinct on (employee_id) employee_id, name from users [✔]
  select distinct on (employee_id, name) employee_id, name, id from users [✔]
  order by
    select * from users order by name [✔]
    select * from users order by name asc [✔]
    select * from users order by name desc [✔]
  having clause
    select employee_id, count(id) from users group by employee_id having count(id) > 1 [✔]