Generate squeal types from a running database.
Starting with v0.2.0.0, overloaded PostgreSQL functions now use disambiguated labels to ensure type-safe calling:
-- Old (v1.x): Only one overload could be represented, using simple name
type Functions = '[ "my_func" ::: Function ... ]
-- New (v2.0): All representable overloads use disambiguated labels
type Functions = '[ "my_func__int4" ::: Function '[Null PGint4] :=> ...
, "my_func__int8" ::: Function '[Null PGint8] :=> ... ]Migration Guide:
-
Search for simple function names in your codebase that may have been overloaded:
# Find usages of function labels in your code grep -r '#"my_func"' src/
-
Replace with disambiguated labels:
- Before:
#"my_func"→ After:#"my_func__int4"or#"my_func__int8"(as appropriate)
- Before:
-
Compatibility aliases: If only ONE overload of a function is representable (others have pseudotype arguments), a compatibility alias is emitted:
-- Both labels work when only one overload is representable: type Functions = '[ "legacy_func" ::: Function ... -- compatibility alias , "legacy_func__int8" ::: Function ... -- disambiguated label ]
In this case, existing code using the simple name will continue to work.
-
Pseudotype functions (using
anyelement,anyarray, etc.) are not representable and are omitted with a comment—this behavior is unchanged.
Squeal is a lovely way to interact with a database, but setting up the initial schema is a struggle. By default, it assumes you will be managing and migrating your database with Squeal, and if you are starting from scratch, that works great, but if you're managing it some other way, or even just want to test out Squeal on an existing database, it's tedious to have to set up the database types and keep them up to date.
-
clone the repo and change into the directory
-
Install the executable (recommended):
cabal install exe:squealgen --installdir=$HOME/.local/bin --overwrite-policy=alwaysIf you prefer the generated script (dev convenience), you can also run:
make prefix=$HOME/.local install -
If my database is
cooldb, my Haskell module isSchema(fileSchema.hs), and I want to generate from thepublicschema, I would runsquealgen cooldb Schema public > ~/myproject/src/Schema.hs.Notes:
DBNAMEis passed topsql -d, so it can be a database name or a libpq connection string/URL.MODULENAMEis the Haskell module name (not a file path).IMPORTS(optional) is inserted into the generated module; a convenient pattern is"... $(cat extra_imports.txt)".PSQLCMDcan be set to use a non-defaultpsqlbinary.
SCHEMAis treated as a comma-separatedsearch_pathfragment, so you can passpublic,extif you also needexton the path (e.g. for extension-owned types).
You could integrate this in various ways: perhaps just as an initial scaffold, or perhaps integrated as part of your build process. A true madman could integrate this into a TH call, but I suspect this would be slow and prone to failing (for instance, better never compile any code if you don't have access to the right version of psql or a way of spinning up an empty database.)
I highly recommend having a scripted way to bring up a temporary database and run all migrations first. I use Jonathan Fischoff's tmp-postgres library and recommend it if you're running migrations through Haskell.
My workflow looks like this:
make testwatchsquealgen is generated from squealgen.sql via ./mksquealgen.sh.
Treat squealgen.sql as the source of truth and do not edit squealgen directly.
./check_squealgen_drift.sh is run by make test and CI to enforce that the checked-in ./squealgen script matches squealgen.sql.
Validation contract:
- Local validation (
make test): enforcesquealgendrift parity, regenerate fixture modules, then runcabal test. - CI validation (
make ci): enforce drift parity, regenerate fixture modules, then runcabal testwith reduced falsify cases (--falsify-tests 25) to keep runtime bounded.
SCHEMA is treated as a comma-separated search_path fragment.
The generator targets only the first schema in the fragment for emitted types, but sets the full search_path safely (quoted identifiers).
Extension story:
- If the schema references extension-owned types, squealgen emits opaque
UnsafePGTypealiases (e.g.type PGltree = UnsafePGType "ltree") only when needed. - When any extension-owned types are present, generated output includes a comment block listing detected required extensions.
- Users are responsible for installing extensions via migrations/DDL; CI enforces this via the
test/Extensionsltree fixture.
Function-overload compatibility notes:
- Generated output always includes deterministic disambiguated overloaded labels (
name__argtokens). - When an overloaded base name has exactly one representable signature, a compatibility alias using the legacy simple name (
name) is also emitted. - When two or more representable overloads remain, no legacy alias is emitted; callers must use the disambiguated labels.
- PostgreSQL client/server tools on your
PATH:psql,initdb,pg_ctl,createdb(used by tests and vendoredvendor/pg_tmp). On Ubuntu, these are often under/usr/lib/postgresql/<version>/bin(e.g./usr/lib/postgresql/16/bin); ifpg_configis available:export PATH="$(pg_config --bindir):$PATH". - make
- cabal-install
inotifywait(frominotify-tools) if you want to usemake testwatch.
- Remove string-hacking, generate in a more principled way.
- Improve function-label ergonomics while preserving overload safety and readability.
- Investigate richer type-level trigger/check representations while preserving current metadata fallback behavior.
┌─────────────────────────────────────────────────────────────────────────────┐
│ squealgen flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌────────────────┐ ┌──────────────────────────┐ │
│ │ PostgreSQL │ │ squealgen.sql │ │ Generated Schema.hs │ │
│ │ Database │───▶│ (psql script) │───▶│ (Squeal types) │ │
│ │ │ │ │ │ │ │
│ │ - tables │ │ - CTE queries │ │ - type DB │ │
│ │ - views │ │ - type mapping │ │ - type Schema │ │
│ │ - enums │ │ - emit logic │ │ - type Tables/Views/... │ │
│ │ - functions │ │ │ │ - function definitions │ │
│ └──────────────┘ └────────────────┘ └──────────────────────────┘ │
│ │
│ Input: DBNAME, MODULENAME, SCHEMA │
│ Output: Haskell module with Squeal type definitions │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The generator queries PostgreSQL system catalogs (pg_catalog, information_schema) to extract schema metadata, then emits Haskell type definitions compatible with Squeal's type-level DSL.
The generated output includes a Triggers type that provides metadata about PostgreSQL triggers defined on tables in the schema:
-- Example generated output:
-- triggers
-- Trigger contract: Triggers is generated metadata and is not composed into Schema.
type Triggers =
'[ "users_insert_trigger" ::: 'TriggerMetadata
'["table" ::: "users", "event" ::: "INSERT", "timing" ::: "BEFORE"]
]Important: The Triggers type is metadata-only and is NOT composed into the Schema type. It cannot be used in Squeal queries. Its purpose is to document what triggers exist in the database for developer reference. Squeal does not provide type-level trigger support.
| PostgreSQL Type | Squeal Type |
|---|---|
boolean |
PGbool |
int2 / smallint |
PGint2 |
int4 / integer |
PGint4 |
int8 / bigint |
PGint8 |
float4 / real |
PGfloat4 |
float8 / double precision |
PGfloat8 |
numeric |
PGnumeric |
text |
PGtext |
varchar |
PGtext or (PGvarchar n) |
char |
PGchar or (PGvarchar n) |
bytea |
PGbytea |
date |
PGdate |
time |
PGtime |
timestamp |
PGtimestamp |
timestamptz |
PGtimestamptz |
interval |
PGinterval |
uuid |
PGuuid |
inet |
PGinet |
json |
PGjson |
jsonb |
PGjsonb |
oid |
PGoid |
array[] |
(PGvararray ...) |
enum |
'PGenum '["label1", "label2", ...] |
composite |
'PGcomposite '[...] |
domain |
Alias to base type |
Extension types (ltree, hstore, etc.) are emitted as UnsafePGType "typename" aliases.
Run ./mksquealgen.sh to regenerate the squealgen script from squealgen.sql, then commit both files. The CI enforces that these stay in sync.
PostgreSQL binaries may not be on your PATH. On Ubuntu, try:
export PATH="/usr/lib/postgresql/$(ls /usr/lib/postgresql | tail -1)/bin:$PATH"Or use pg_config:
export PATH="$(pg_config --bindir):$PATH"- Ensure you're using compatible versions of
squeal-postgresqland GHC. - Check for pseudotype arguments/returns in functions - these are omitted with a comment.
- Extension types require
UnsafePGType- ensure extensions are installed in the database.
Functions with pseudotype arguments (e.g., anyelement) or returns are not representable in Squeal's type system. Check the generated output for comments like:
-- Omitted function signatures:
-- my_func(anyelement): pseudotype argument is not representableThe schema argument is required. Provide a valid schema name:
squealgen mydb MySchema public > Schema.hsUse comma-separated search_path for extensions:
squealgen mydb MySchema public,extensions > Schema.hsTypes are generated only for the first schema (public), but extension-owned types referenced by it will emit UnsafePGType aliases.