2828 */
2929import type { Kysely } from "kysely" ;
3030import { sql } from "kysely" ;
31- import type { SchemaSnapshot , TableDescriptor , ColumnDescriptor , ColumnDefault , IndexDescriptor , FkDescriptor , FkAction , ViewDescriptor } from "../types.js" ;
31+ import type { SchemaSnapshot , TableDescriptor , ColumnDescriptor , ColumnDefault , IndexDescriptor , FkDescriptor , FkAction , ViewDescriptor , CheckDescriptor } from "../types.js" ;
3232import type { SqlType } from "../sql-type.js" ;
3333import { MIGRATIONS_TABLE } from "../apply/ledger.js" ;
3434
@@ -52,7 +52,7 @@ export async function introspectPostgres(db: Kysely<Record<string, unknown>>): P
5252 columns,
5353 indexes : await readPgIndexes ( k , schema , name ) ,
5454 foreignKeys : await readPgForeignKeys ( k , schema , name ) ,
55- checks : [ ] , // CHECK introspection is out of scope; expected-side derives them
55+ checks : await readPgChecks ( k , schema , name ) ,
5656 primaryKey,
5757 } ) ;
5858 }
@@ -437,3 +437,31 @@ function pgRuleToAction(rule: string): FkAction {
437437 if ( r === "RESTRICT" ) return "restrict" ;
438438 return "no-action" ;
439439}
440+
441+ /**
442+ * Read CHECK constraints for a table from pg_constraint. pg-mem does not support
443+ * pg_constraint, so this catches and returns [] there (same accepted gap as
444+ * readPgForeignKeys/readPgIndexes); real-DB coverage is the MIGRATE_TS_PG_URL-gated
445+ * integration test. `pg_get_constraintdef` returns `CHECK (<expr>)`; the wrapper is
446+ * stripped to the expression, compared via normalizeCheckExpr at diff time.
447+ */
448+ async function readPgChecks ( k : RawKysely , schema : string , table : string ) : Promise < CheckDescriptor [ ] > {
449+ try {
450+ const rows = await sql < { name : string ; def : string } > `
451+ SELECT con.conname AS name, pg_get_constraintdef(con.oid) AS def
452+ FROM pg_constraint con
453+ JOIN pg_class rel ON rel.oid = con.conrelid
454+ JOIN pg_namespace ns ON ns.oid = rel.relnamespace
455+ WHERE con.contype = 'c' AND rel.relname = ${ table } AND ns.nspname = ${ schema }
456+ ` . execute ( k ) ;
457+ return rows . rows . map ( ( r ) => ( { name : r . name , expression : stripCheckWrapper ( r . def ) } ) ) ;
458+ } catch {
459+ return [ ] ; // pg-mem: pg_constraint unsupported
460+ }
461+ }
462+
463+ /** `CHECK (<expr>)` → `<expr>` (balanced outer wrapper); returns input unchanged if no wrapper. */
464+ function stripCheckWrapper ( def : string ) : string {
465+ const m = / ^ \s * C H E C K \s * \( ( .* ) \) \s * $ / is. exec ( def ) ;
466+ return m ? m [ 1 ] ! . trim ( ) : def . trim ( ) ;
467+ }
0 commit comments