postgresql-syntax
PostgreSQL AST parsing and rendering
https://github.com/nikita-volkov/postgresql-syntax
| LTS Haskell 24.59: | 0.4.5.0 |
| Stackage Nightly 2026-09-18: | 0.5.0.3 |
| Latest on Hackage: | 0.5.0.3 |
MIT licensed and maintained by Nikita Volkov
This version can be pinned in stack with:
postgresql-syntax-0.5.0.3@sha256:f7324840fc0d36787010d8100531eb2caab4a65cef6b30081fe9c4a8d6ed0e55,12974Module documentation for 0.5.0.3
Depends on 1 package(full list with versions):
Used by 2 packages in nightly-2026-09-18(full list with versions):
Changes
v0.5.0.3
Fixes
- Fixed subquery operators requiring whitespace around them: unspaced forms like
a=ANY(b)used to be a parse error and now parse, matching Postgres (#36).
v0.5.0.2
Non-breaking
PostgresqlSyntax.Algebra’sLeftRecursion base ext itemclass (internal-library-only, not part of the publicPostgresqlSyntaxAPI) is now a single-methodExtends base ext- drops the unreaditemtype parameter:extension/applyExtension/foldExtensionsare gone in favor of oneparseExtensionsmethod;nonRecursiveParser→parseBase;parseLeftRecursive→parseMaybeExtended;leftRecursionProperties→extendedByProperties.JoinedTableExtensionis deleted (#33).TableRef.hsandFuncApplicationParams.hsnow have explicit export lists, exporting only their types and instances (#30).
Fixes
- Fixed the parsed tree shape of
UNION/INTERSECT/EXCEPTchains to match Postgres’s actual associativity and precedence (gram.y’s%left UNION EXCEPT/%left INTERSECT, INTERSECT binding tighter):a EXCEPT b EXCEPT cnow nests left instead of right, anda INTERSECT b UNION cnow roots atUNIONinstead ofINTERSECT. Rendered text is unaffected; onlySimpleSelect’s parsed/canonical tree shape for such chains changes (#30). - Fixed a further associativity bug in the same fold: a run of two or more consecutive
INTERSECTs (e.g.a INTERSECT b INTERSECT c) nested right instead of left, contradictinggram.y’s%left INTERSECT. Parsed/canonical tree shape changes for such chains; rendered text is unaffected (#34). - Fixed
InsertRestmisparsing parenthesizedVALUES/SELECTas a column list (#35).
v0.5.0.1
Fixes
- Added doc-files to the .cabal-file. README.md, LICENSE and CHANGELOG.md.
v0.5.0.0
Breaking
IsAstclass methodsparser/toTextBuildernow take aSettingsparameter to control parse/render options (e.g. nullability?markers). Migration:parse x->parse mempty x,toText x->toText mempty x,parseWithPosError x->parseWithPosError mempty x. New public modulePostgresqlSyntax.Settingsprovides theSettingstype and thenullabilityMarkersconstructor.- Nullability
?markers inTypenameare now opt-in vianullabilityMarkers True. In standard mode (mempty) they are not recognized - both flags parse asFalseand a literal?in their position is a parse error. Markers no longer accept a preceding space:int ?->int?. Extended mode gives up one spelling of real Postgres:x::jsonb? 'b'(unspaced jsonb key-existence operator); usex::jsonb ? 'b'orjsonb_exists(x, 'b'). - Removed
PostgresqlSyntax.ParsingandPostgresqlSyntax.Rendering. Every type’s parser and renderer are now theparser/toTextBuildermethods of itsIsAstinstance (exported fromPostgresqlSyntax), and the top-level entry points moved to thePostgresqlSyntaxmodule:Parsing.run/.runWithPosErrorbecamePostgresqlSyntax.parse/.parseWithPosError, generalized to work over anyIsAsttype rather than taking an explicit parser argument. PostgresqlSyntax.KeywordSetandPostgresqlSyntax.Validationare no longer part of the public API surface.- Several former type aliases are now distinct ADTs/newtypes instead of bare
Either/Maybe/primitive aliases:SelectStmt,SelectClause,ExplicitRow, and the primitive-wrapper newtypesSconst,Bconst,Xconst,Iconst,Fconst,Op,OptVarying,Timezone,IntervalSecond,OptOrdinality. Code that pattern-matched these asEither/Maybe/Text/Booldirectly needs to match on the new constructors instead. - Removed the
SuffixQualOpAExprconstructor ofAExpr. It modelled the postfix operator production (a_expr qual_Op), which Postgres removed in version 14 -x OPERATOR(pg_catalog.+#)is a syntax error in every supported server version, so the parser no longer accepts it and the renderer can no longer emit it. Code pattern-matching or constructingSuffixQualOpAExprneeds to drop those cases. This also removes a family of round-trip failures: the rendering<operand> <operator>left the operator without a right-hand side, so reparsing swallowed whatever keyword followed (PRECEDING,FOLLOWING,ROWS, an implicit column alias, or - in the postfix case specifically - the?of theTypenamenullability extension) as its operand. Only the postfix-triggered?swallow is fixed by this;Op "?"colliding with the nullability extension as a binary operator is a separate, still-open issue.
Fixes
- Fix the
AExpr/BExprrenderers (and related expression renderers) being precedence-naive: operators were concatenated without parenthesization, so a non-canonical but valid AST (e.g.(NOT x) - y) could render to SQL that re-parses to a different tree (NOT (x - y)). Renderers now parenthesize based on operator precedence, making render -> parse round-trip safe for the full value space of each type, not just parser-canonical trees.
v0.4.5.0
Non-breaking
- Derive
Datafor every AST type inPostgresqlSyntax.Ast(#6). Enables generic (SYB-style) traversals and transformations over the syntax tree, e.g. for expression normalization. - Add
parseWithSourcePosError, likeparseWithPosErrorbut pairing each error with aText.Megaparsec.SourcePosinstead of a rawIntbyte offset, so callers (e.g.hasql-th, see nikita-volkov/hasql-th#35) can report line/column positions without recomputing them from the input.
v0.4.4.0
Fixes
- Fix parsing time growing exponentially with the nesting depth of an expression (#8).
Inputs as small as
((((((((((((a + b))))))))))))previously did not finish parsing; they now parse in well under a millisecond. Three grammar alternatives each parsed the content of a parenthesised group before discovering they did not apply, tripling the work per level of nesting; they are now left-factored so each group is parsed once. Parsing time is linear in input size, with a quadratic term in nesting depth alone (1000 characters of pure nesting take about a second; realistic input is unaffected).
Non-breaking
- Redundant parentheses around a sub-select now parse to a canonical shape.
((select 1))producesWithParensSelectWithParens; the equivalentNoParensSelectWithParensof aSelectNoParenscarrying nothing but that same parenthesised select is no longer produced. The two rendered identically, so this only affects which of two equivalent trees you get back. Trees that carry a set operation, sort clause, limit or locking clause around the parenthesised select are unaffected.
v0.4.3.2
Fixes
- Fix keyword error reporting under megaparsec >=9.8 (#20)
- Fix OFFSET/aExpr round-trip failure for
OPERATOR(...)prefix (#11, #22) - Fix hedgehog generator for
type_function_nameto use its own keyword set