@dbsp/adapter-pgsql 1.11.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { ExpressionRef, IndexInfo, TruncateOptions, VacuumOptions, AlterColumnOptions, CreateIndexOptions, DropIndexOptions, ModelIR as ModelIR$1 } from '@dbsp/core';
1
+ import { IndexInfo, TruncateOptions, VacuumOptions, AlterColumnOptions, CreateIndexOptions, DropIndexOptions, ExpressionRef, ModelIR as ModelIR$1 } from '@dbsp/core';
2
2
  export { normalizeSQL } from '@dbsp/core';
3
3
  import * as _dbsp_types from '@dbsp/types';
4
- import { ColumnListInput, ParamIntent, JsonAggOrderByEntry, DialectCapabilities, ModelIR, DbCasing, ColumnIR, HierarchyIR, MutationReturningItem, WhereIntent, Adapter, AdapterLogger, AdapterCapabilities, PlanReport, CompiledNqlQuery, CompileOptions, CompiledQuery, CompileResultWithIncludes, SubqueryIncludeInfo, ExpressionIntent, InsertIntent, InsertFromIntent, UpdateIntent, BatchUpdateIntent, DeleteIntent, UpsertIntent, UpsertFromIntent, RecursivePlanReport, CteQueryIntent, SetOperationIntent, DumpMeta, Dump, AdapterStreamOptions, CompileOnlyAdapter, QueryIntent } from '@dbsp/types';
4
+ import { DialectCapabilities, ModelIR, ColumnListInput, ParamIntent, JsonAggOrderByEntry, IndexIR, HierarchyIR, Adapter, DbCasing, AdapterLogger, AdapterCapabilities, PlanReport, CompiledNqlQuery, CompileOptions, CompiledQuery, CompileResultWithIncludes, SubqueryIncludeInfo, ExpressionIntent, InsertIntent, InsertFromIntent, UpdateIntent, BatchUpdateIntent, DeleteIntent, UpsertIntent, UpsertFromIntent, RecursivePlanReport, CteQueryIntent, SetOperationIntent, DumpMeta, Dump, AdapterStreamOptions, CompileOnlyAdapter, ColumnIR, MutationReturningItem, WhereIntent, QueryIntent } from '@dbsp/types';
5
5
  import * as _pgsql_types from '@pgsql/types';
6
6
  import { Node, OnConflictClause, ParamRef } from '@pgsql/types';
7
7
  import { Pool, PoolClient } from 'pg';
@@ -99,6 +99,253 @@ declare function getNamingPluginForDbCasing(casing: 'snake_case' | 'camelCase' |
99
99
 
100
100
  type BindingNameRegistry = ReadonlySet<string>;
101
101
 
102
+ /**
103
+ * Immutable context passed to all handlers during compilation.
104
+ */
105
+ interface CompilerContext {
106
+ /** Naming convention transformer */
107
+ readonly naming: NamingPlugin;
108
+ /** Schema name for table qualification (optional) */
109
+ readonly schema?: string;
110
+ /** Dialect capabilities for adapter-layer SQL surface gates */
111
+ readonly dialectCapabilities?: DialectCapabilities;
112
+ /** Root table name for the query */
113
+ readonly rootTable: string;
114
+ /** Current table alias (for JOINs) */
115
+ readonly currentAlias?: string;
116
+ /** Final relation path/name → SQL join alias map for relation-aware expression contexts */
117
+ readonly aliases?: ReadonlyMap<string, string>;
118
+ /** Maximum recursive depth (default: 100) */
119
+ readonly maxRecursiveDepth: number;
120
+ /** Optional callback for raw SQL audit trail */
121
+ readonly onRawSQL?: (sql: string) => void;
122
+ /** Default primary key column name for convention fallbacks (default: 'id') */
123
+ readonly defaultPkColumnName?: string;
124
+ /** Convention for deriving FK column names: (tableName, pkName) => fkColumnName */
125
+ readonly deriveFkColumnName?: FkColumnDerivation;
126
+ /** Alias of the outer (parent) query — used for FieldRef scope:'outer' resolution in EXISTS subqueries */
127
+ readonly outerAlias?: string;
128
+ /** Query-local CTE/binding names that must not be schema-qualified. */
129
+ readonly bindingNames?: BindingNameRegistry;
130
+ /**
131
+ * Optional callback to compile a QueryIntent into an AST Node (SubLink subselect).
132
+ * Set by PlanCompiler when compiling selectCustomExpression — enables SubqueryExpressionIntent
133
+ * to embed a fully compiled sub-SELECT into the parent SELECT column list.
134
+ *
135
+ * @param query - The inner QueryIntent to compile
136
+ * @param paramOffset - Current outer paramIndex; inner $N are renumbered by this offset
137
+ * @returns The compiled SelectStmt AST node and the inner parameters
138
+ */
139
+ readonly compileSubquery?: (query: _dbsp_types.QueryIntent, paramOffset: number) => {
140
+ ast: Node;
141
+ parameters: readonly unknown[];
142
+ };
143
+ /**
144
+ * Optional recursive compiler for NQL-origin SELECT expression values nested
145
+ * inside handler arguments, such as coalesce(upper(name), :fallback) or
146
+ * (price + :a) * :b.
147
+ */
148
+ readonly compileNqlSelectExpression?: (value: unknown, ctx: CompilerContext, state: CompilerState) => Node;
149
+ /**
150
+ * Optional callback to compile a custom fn() FILTER (WHERE ...) condition.
151
+ * Set by PlanCompiler to keep WHERE-dispatcher dependencies out of expression
152
+ * handlers while still applying FILTER in every expression position.
153
+ */
154
+ readonly compileCustomFnFilter?: (filterIntent: _dbsp_types.WhereIntent, ctx: CompilerContext, state: CompilerState) => Node | undefined;
155
+ /**
156
+ * Optional ModelIR for type-aware parameter casting.
157
+ * When provided, WHERE comparisons emit `$N::type` to eliminate
158
+ * PostgreSQL type inference ambiguity for nullable columns.
159
+ */
160
+ readonly model?: ModelIR;
161
+ }
162
+ /**
163
+ * Mutable state maintained during compilation.
164
+ */
165
+ interface CompilerState {
166
+ /** Collected parameters in order */
167
+ parameters: unknown[];
168
+ /** Current parameter index (1-based for PostgreSQL) */
169
+ paramIndex: number;
170
+ /** Registered CTEs for the query */
171
+ ctes: Map<string, Node>;
172
+ /** Table aliases in use */
173
+ aliases: Map<string, string>;
174
+ /** JOIN clauses accumulated */
175
+ joins: Node[];
176
+ }
177
+ /**
178
+ * Base decision interface matching core's PlanDecision structure.
179
+ */
180
+ interface Decision {
181
+ readonly type: string;
182
+ readonly table?: string;
183
+ readonly column?: string;
184
+ readonly alias?: string;
185
+ readonly operator?: string;
186
+ readonly value?: unknown;
187
+ readonly paramIndex?: number;
188
+ readonly dataType?: string;
189
+ readonly direction?: 'ASC' | 'DESC';
190
+ readonly nulls?: 'FIRST' | 'LAST';
191
+ readonly joinType?: 'inner' | 'left';
192
+ readonly sourceColumn?: ColumnListInput;
193
+ readonly targetColumn?: ColumnListInput;
194
+ readonly targetTable?: string;
195
+ readonly function?: string;
196
+ /** Apply DISTINCT to a SELECT-list aggregate (e.g. COUNT(DISTINCT col)). */
197
+ readonly distinct?: boolean;
198
+ readonly args?: readonly unknown[];
199
+ readonly conditions?: readonly Decision[];
200
+ readonly columns?: readonly string[];
201
+ readonly values?: readonly unknown[];
202
+ readonly set?: readonly {
203
+ column: string;
204
+ value: unknown;
205
+ }[];
206
+ readonly limit?: number | ParamIntent | {
207
+ paramIndex: number;
208
+ };
209
+ readonly offset?: number | ParamIntent | {
210
+ paramIndex: number;
211
+ };
212
+ readonly strategy?: 'join' | 'lateral' | 'json_agg' | 'cte';
213
+ readonly relation?: string;
214
+ readonly relationName?: string;
215
+ readonly relationPath?: string;
216
+ readonly hydrationPrefix?: string;
217
+ readonly include?: readonly Decision[];
218
+ readonly relationType?: 'belongsTo' | 'hasMany' | 'hasOne';
219
+ readonly foreignKey?: ColumnListInput;
220
+ readonly parentKey?: ColumnListInput;
221
+ readonly orderByFallback?: boolean;
222
+ readonly children?: readonly Decision[];
223
+ readonly partition?: readonly string[];
224
+ readonly orderBy?: readonly {
225
+ column: string;
226
+ direction?: 'ASC' | 'DESC';
227
+ }[] | readonly JsonAggOrderByEntry[];
228
+ readonly frame?: string;
229
+ readonly maxDepth?: number;
230
+ readonly pathColumn?: string;
231
+ readonly cycleDetection?: boolean;
232
+ readonly selectColumn?: string;
233
+ readonly aggregate?: string;
234
+ /**
235
+ * Apply DISTINCT to a scalar subquery's aggregate (e.g. AVG(DISTINCT price)).
236
+ * Deliberately NOT named `distinct` — `assertNoDroppedDecisionModifiers`
237
+ * (subquery-emission.ts) treats a top-level `distinct === true` on ANY
238
+ * subquery decision as an unsupported query-level DISTINCT modifier and
239
+ * throws. This field is scoped to the aggregate projection only, so it
240
+ * must not collide with that generic guard.
241
+ */
242
+ readonly aggregateDistinct?: boolean;
243
+ readonly subqueryOperator?: string;
244
+ readonly traversal?: string;
245
+ readonly traversals?: readonly {
246
+ traversal: string;
247
+ targetColumn?: string;
248
+ }[];
249
+ readonly isRecursive?: boolean;
250
+ readonly fkColumn?: string;
251
+ readonly pkColumn?: string;
252
+ readonly expandRelation?: string;
253
+ readonly relationColumns?: readonly string[];
254
+ readonly columnAliases?: Readonly<Record<string, string>>;
255
+ readonly jsonPath?: readonly unknown[];
256
+ readonly jsonMode?: 'json' | 'text';
257
+ readonly _compiledFilterWhere?: _pgsql_types.Node;
258
+ readonly filterWhere?: _pgsql_types.Node;
259
+ readonly expressionIntent?: unknown;
260
+ readonly escape?: string;
261
+ /**
262
+ * Provenance: the ORIGINAL QueryIntent before lowering.
263
+ * Set by every lowering site (convertIn, convertSubquery, normalizeToDecision,
264
+ * dispatchWhere, mapInSubqueryCondition) so that `buildPredicateSubquerySelect`
265
+ * (subquery-emission.ts) can validate the true caller intent rather than the
266
+ * stripped-down lowered decision fields.
267
+ *
268
+ * Required for IN / scalar / inSubquery / notInSubquery decisions.
269
+ * Optional on other decision types.
270
+ */
271
+ readonly subqueryIntent?: _dbsp_types.QueryIntent;
272
+ }
273
+ /**
274
+ * Handler for WHERE clause conditions.
275
+ * Transforms condition decisions into PostgreSQL AST expressions.
276
+ */
277
+ interface WhereHandler {
278
+ /** Operator(s) this handler supports */
279
+ readonly operators: readonly string[];
280
+ /**
281
+ * Compile a WHERE condition to AST.
282
+ * @param decision The condition decision
283
+ * @param ctx Immutable compiler context
284
+ * @param state Mutable compiler state
285
+ * @param dispatch Callback to compile nested conditions
286
+ * @returns PostgreSQL AST node for the condition
287
+ */
288
+ compile(decision: Decision, ctx: CompilerContext, state: CompilerState, dispatch: WhereDispatcher): Node;
289
+ }
290
+ /**
291
+ * Dispatcher for recursive WHERE compilation.
292
+ */
293
+ type WhereDispatcher = (decision: Decision, ctx: CompilerContext, state: CompilerState) => Node;
294
+ /**
295
+ * Handler for SELECT expressions.
296
+ * Transforms expression decisions into PostgreSQL AST nodes.
297
+ */
298
+ interface ExpressionHandler {
299
+ /** Expression type(s) this handler supports */
300
+ readonly types: readonly string[];
301
+ /**
302
+ * Safe to use when a function name comes from NQL text.
303
+ *
304
+ * Raw/escape-hatch handlers must not set this. NQL-origin function names use
305
+ * this opt-in surface only, then fall back to generic FuncCall emission.
306
+ */
307
+ readonly nqlSafe?: boolean;
308
+ /**
309
+ * Compile an expression to AST.
310
+ * @param decision The expression decision
311
+ * @param ctx Immutable compiler context
312
+ * @param state Mutable compiler state
313
+ * @returns PostgreSQL AST node for the expression
314
+ */
315
+ compile(decision: Decision, ctx: CompilerContext, state: CompilerState): Node;
316
+ }
317
+ /**
318
+ * Handler for include/relation strategies.
319
+ * Transforms include decisions into PostgreSQL constructs (JOIN, LATERAL, json_agg, CTE).
320
+ */
321
+ interface IncludeHandler {
322
+ /** Strategy this handler implements */
323
+ readonly strategy: 'join' | 'lateral' | 'json_agg' | 'cte';
324
+ /**
325
+ * Compile an include to AST.
326
+ * @param decision The include decision
327
+ * @param ctx Immutable compiler context
328
+ * @param state Mutable compiler state
329
+ * @returns Object with modifications to apply
330
+ */
331
+ compile(decision: Decision, ctx: CompilerContext, state: CompilerState): IncludeResult;
332
+ }
333
+ /**
334
+ * Result of include compilation.
335
+ */
336
+ interface IncludeResult {
337
+ /** Additional target list items (SELECT columns) */
338
+ targets?: Node[];
339
+ /** JOIN to add to FROM clause */
340
+ join?: Node;
341
+ /** Additional JOINs for cascaded includes (e.g., flat deep nesting) */
342
+ additionalJoins?: Node[];
343
+ /** CTE to add to WITH clause */
344
+ cte?: Node;
345
+ /** Subquery for LATERAL */
346
+ lateral?: Node;
347
+ }
348
+
102
349
  type PlanExpressionOrderBy = readonly {
103
350
  field: string;
104
351
  direction?: 'asc' | 'desc';
@@ -459,11 +706,9 @@ declare class PlanCompiler {
459
706
  private compileCaseValue;
460
707
  /**
461
708
  * Compile a custom ExpressionIntent (customFn, customOp, ref, cast, unary,
462
- * array, function, subquery, …) to an AST node, applying the customFn FILTER
463
- * clause. Shared by the `selectCustomExpression` target path and CASE
464
- * THEN/ELSE values so both render the full expression surface identically
465
- * (every expression kind + FILTER), rather than one path silently binding
466
- * expressions as parameters or dropping FILTER.
709
+ * array, function, subquery, …) to an AST node through the shared expression
710
+ * compiler. Shared by the `selectCustomExpression` target path and CASE
711
+ * THEN/ELSE values so both render the full expression surface identically.
467
712
  */
468
713
  private compileCustomExpressionNode;
469
714
  private compileInsert;
@@ -494,7 +739,11 @@ declare function compilePlan(plan: SimplifiedPlanReport, options?: CompilerOptio
494
739
  interface GenerateDDLOptions {
495
740
  /** Include DROP TABLE IF EXISTS statements before CREATE TABLE */
496
741
  readonly includeDropStatements?: boolean;
497
- /** Database schema name (e.g., 'public', 'tenant_123') */
742
+ /**
743
+ * Database schema name (e.g., 'public', 'tenant_123').
744
+ * Required when emitted DDL would otherwise mix non-default target-scoped
745
+ * custom types/enums with unqualified table SQL.
746
+ */
498
747
  readonly schemaName?: string;
499
748
  /**
500
749
  * Automatically create indexes on foreign key columns.
@@ -520,1302 +769,326 @@ interface GenerateDDLOptions {
520
769
  * @returns Array of DDL statements in dependency order
521
770
  */
522
771
  declare function generateDDL(schema: ModelIR, options?: GenerateDDLOptions): string[];
523
-
772
+ declare function generateCreateIndex(tableName: string, idx: IndexIR, schemaName: string | undefined, naming: NamingPlugin): string;
524
773
  /**
525
- * Schema Comparison Engine (DDL-PROV Block 1)
526
- *
527
- * Compares two ModelIRs (schema definition vs database state)
528
- * and produces a structured diff of changes needed.
774
+ * Returns whether the PostgreSQL DDL generator can emit this IndexIR.
529
775
  *
530
- * @module schema-diff
776
+ * Keep this as the single representability predicate for generated schema
777
+ * omission and destructive-drop classification: both sides must agree on the
778
+ * exact validation surface used by generateCreateIndex().
531
779
  */
532
-
533
- type ChangeKind = 'create_table' | 'drop_table' | 'add_column' | 'drop_column' | 'alter_column_type' | 'alter_column_nullable' | 'alter_column_default' | 'alter_column_unique' | 'add_primary_key' | 'drop_primary_key' | 'add_foreign_key' | 'drop_foreign_key' | 'alter_foreign_key' | 'validate_constraint' | 'create_index' | 'drop_index' | 'add_check_constraint' | 'drop_check_constraint' | 'create_enum' | 'alter_enum_add_value' | 'drop_enum' | 'alter_column_collation' | 'alter_column_identity' | 'add_comment' | 'drop_comment' | 'create_extension' | 'drop_extension' | 'create_sequence' | 'alter_sequence' | 'drop_sequence' | 'enable_rls' | 'disable_rls' | 'create_policy' | 'drop_policy';
534
- interface SchemaChange {
535
- readonly kind: ChangeKind;
536
- readonly table: string;
537
- readonly column?: string;
538
- readonly destructive: boolean;
539
- readonly details: string;
540
- /** Additional metadata for SQL generation */
541
- readonly meta?: Readonly<Record<string, unknown>>;
542
- }
543
- interface DiffSummary {
544
- readonly tables: {
545
- readonly added: number;
546
- readonly dropped: number;
547
- };
548
- readonly columns: {
549
- readonly added: number;
550
- readonly dropped: number;
551
- readonly altered: number;
552
- };
553
- readonly indexes: {
554
- readonly added: number;
555
- readonly dropped: number;
556
- };
557
- readonly constraints: {
558
- readonly added: number;
559
- readonly dropped: number;
560
- readonly altered: number;
561
- };
562
- }
563
- interface SchemaDiff {
564
- readonly changes: readonly SchemaChange[];
565
- readonly hasDestructive: boolean;
566
- readonly summary: DiffSummary;
567
- }
568
- interface CompareSchemataOptions {
569
- /**
570
- * Database naming convention.
571
- * When set, schema model names (camelCase) are converted to DB format
572
- * (e.g. snake_case) before comparison with the introspected model.
573
- */
574
- dbCasing?: DbCasing;
575
- /** Dialect capabilities — comparisons for unsupported features will be skipped */
576
- readonly dialectCapabilities?: DialectCapabilities;
577
- /**
578
- * When `true`, extensions present in the live DB but absent from the model
579
- * schema are silently ignored — no `drop_extension` change is emitted for them.
580
- * Only extensions explicitly declared in the model are managed (created if missing).
581
- *
582
- * Use this when the database image pre-installs extensions that the application
583
- * schema does not own (e.g. pgvector, pg_search bundled in a custom Postgres image).
584
- * Default: `false` (full-sync behaviour — unmanaged DB extensions produce a
585
- * `drop_extension` entry).
586
- */
587
- readonly ignoreUnmanagedExtensions?: boolean;
588
- }
589
- /**
590
- * Compare two ModelIRs and produce a structured diff.
591
- *
592
- * @param schema - The desired schema (from definition)
593
- * @param db - The current database state (from introspection)
594
- * @param options - Optional comparison settings (e.g. dbCasing)
595
- * @returns SchemaDiff with all changes needed to bring DB in sync with schema
596
- */
597
- declare function compareSchemata(schema: ModelIR, db: ModelIR, options?: CompareSchemataOptions): SchemaDiff;
780
+ declare function canGenerateCreateIndex(tableName: string, idx: IndexIR, schemaName?: string | undefined, naming?: NamingPlugin): boolean;
598
781
 
599
782
  /**
600
- * Migration SQL Generator (DDL-PROV Block 1)
783
+ * PostgreSQL Schema Introspection (ADAPTER-006)
601
784
  *
602
- * Generates ordered SQL statements from a SchemaDiff.
603
- * Statements are topologically sorted: DROP constraints → DROP objects → CREATE objects → ADD constraints.
785
+ * Queries information_schema/pg_catalog to build ModelIR
786
+ * from an existing database. Supports:
787
+ * - Table/column/PK discovery
788
+ * - FK → bidirectional relation inference
789
+ * - Hierarchy detection (adjacency + edge-table)
790
+ * - Include/exclude filtering
604
791
  *
605
- * @module migration-sql
792
+ * @module introspection
606
793
  */
607
794
 
608
- interface MigrationSQLOptions {
609
- /** Schema namespace (default: none — unqualified) */
610
- readonly schemaName?: string;
611
- /** Whether to include destructive changes (drops) */
612
- readonly includeDestructive?: boolean;
613
- /** Automatically create indexes on FK columns for new tables (default: true) */
614
- readonly fkAutoIndex?: boolean;
615
- /** Dialect capabilities — migration SQL for unsupported features will be filtered */
616
- readonly dialectCapabilities?: DialectCapabilities;
795
+ /** The minimum every schema-level operation needs: which schema. */
796
+ interface SchemaScopeOptions {
797
+ /** Schema name to operate on (default: 'public') */
798
+ readonly schema?: string;
617
799
  }
618
800
  /**
619
- * Generate ordered SQL statements from a SchemaDiff.
620
- *
621
- * Topological order:
622
- * 0. DROP FK/CHECK constraints (must drop before referenced tables)
623
- * 1. DROP indexes
624
- * 2. DROP columns
625
- * 3. DROP primary keys
626
- * 4. DROP tables, DROP ENUMs
627
- * 5. CREATE ENUMs (must exist before tables that use them)
628
- * 6. CREATE tables
629
- * 7. ADD columns
630
- * 8. ALTER columns (type, nullable, default)
631
- * 9. ADD primary keys / column UNIQUE constraints
632
- * 10. ADD FK constraints (must add after referenced tables exist)
633
- * 11. ALTER FK (drop + re-add)
634
- * 12. CREATE indexes
635
- * 13. ADD CHECK constraints
636
- * 14. ALTER ENUM ADD VALUE (must be last — has transaction visibility caveats in PG)
637
- * 15. COMMENT ON TABLE / COLUMN (very last)
801
+ * Introspection additionally chooses WHICH TABLES to read. It is a read-only
802
+ * path, so narrowing it is safe — that is why the table filters live here and
803
+ * nowhere else.
638
804
  */
639
- declare function generateMigrationSQL(diff: SchemaDiff, options?: MigrationSQLOptions): readonly string[];
805
+ interface IntrospectionOptions extends SchemaScopeOptions {
806
+ /** Tables to exclude (glob patterns: * matches any chars) */
807
+ readonly exclude?: readonly string[];
808
+ /** Tables to include (default: all). Applied before exclude. */
809
+ readonly include?: readonly string[];
810
+ }
811
+ /** Hierarchy pattern detected during introspection */
640
812
  /**
641
- * Generate ordered DOWN SQL statements from a SchemaDiff.
642
- *
643
- * Reverses the topological order used in UP migrations:
644
- * phases run in descending order (11, 10, 9, ..., 0).
645
- *
646
- * Irreversible changes (drops that lose data) produce SQL WARNING comments.
813
+ * Hierarchy pattern detected during introspection.
814
+ * Alias of {@link HierarchyIR} from \@dbsp/types — kept here for
815
+ * public-API backwards compatibility (re-exported from \@dbsp/adapter-pgsql).
647
816
  */
648
- declare function generateDownSQL(diff: SchemaDiff, options?: MigrationSQLOptions): readonly string[];
649
-
817
+ type DetectedHierarchy = HierarchyIR;
818
+ /** Extended ModelIR with hierarchy metadata */
819
+ interface IntrospectedModelIR extends ModelIR {
820
+ readonly hierarchies: readonly DetectedHierarchy[];
821
+ readonly introspectedAt: Date;
822
+ readonly warnings: readonly string[];
823
+ }
650
824
  /**
651
- * Migration File Format v2 — UP + DOWN sections.
825
+ * Introspect a database through a pool.
652
826
  *
653
- * File format:
654
- * -- dbsp:destructive: true|false
655
- * <UP statements>;
656
- * -- DOWN
657
- * <DOWN statements>;
827
+ * This does NOT accept a checked-out `PoolClient`, and that is deliberate. A
828
+ * client may be sitting inside a transaction that belongs to its owner, and a
829
+ * catalog query that fails there aborts *their* transaction. Protecting that
830
+ * needs a savepoint, and knowing whether to take one needs the caller to say
831
+ * whose transaction it is — which is what `PgsqlAdapter`'s `borrowedClient`
832
+ * declaration is for. Guessing it from the object's shape is the exact defect
833
+ * this adapter was rewritten to remove.
658
834
  *
659
- * The separator `-- DOWN` must be on its own line (SC-25).
835
+ * Saying so in a comment is not enough: `CatalogQueryExecutor` is structural, so
836
+ * a `PoolClient` — which has a `query()` — satisfies it, and the prose would have
837
+ * been the only thing standing in the way. It is branded instead, and only the
838
+ * adapter's own protected executor carries the brand. A client cannot be passed
839
+ * here at all.
660
840
  *
661
- * @module ddl/migration-file
662
- */
663
-
664
- /**
665
- * Result of parsing a migration file's UP/DOWN sections.
666
- */
667
- interface ParsedMigrationFile {
668
- readonly upStatements: readonly string[];
669
- readonly downStatements: readonly string[];
670
- readonly hasDown: boolean;
671
- readonly destructive?: boolean | undefined;
672
- }
673
- /**
674
- * Generate a migration file content with UP and DOWN sections.
675
- */
676
- declare function generateMigrationFile(diff: SchemaDiff, options?: MigrationSQLOptions & {
677
- name?: string;
678
- }): string;
679
- /**
680
- * Parse a migration file into UP and DOWN sections.
681
- * Separator: `^\s*-- DOWN\s*$` (strict regex, own line only — SC-25)
682
- */
683
- declare function parseMigrationFile(content: string): ParsedMigrationFile;
684
- /**
685
- * Check if SQL statements contain destructive operations.
686
- * Destructive: DROP TABLE, DROP COLUMN, lossy ALTER COLUMN TYPE
841
+ * So: hold a client, use `new PgsqlAdapter(client, { borrowedClient: true })`
842
+ * and call `.introspect()` on it.
687
843
  */
688
- declare function isDestructiveDown(downStatements: readonly string[]): boolean;
844
+ declare function introspect(pool: Pool, options?: IntrospectionOptions): Promise<IntrospectedModelIR>;
689
845
 
690
846
  /**
691
- * Migration Tracker — `_dbsp_migrations` table CRUD.
847
+ * PgsqlAdapter - Implements the Adapter interface for PostgreSQL using native pg driver.
692
848
  *
693
- * Manages the tracking table that records which migrations
694
- * have been applied to a database.
849
+ * This adapter wraps a pg Pool instance and provides the unified
850
+ * adapter interface for the db-semantic-planner ORM.
851
+ *
852
+ * @module pgsql-adapter
695
853
  */
696
854
 
697
- interface MigrationRecord {
698
- /** Migration filename (e.g., "0001_create_users.sql") */
699
- readonly name: string;
700
- /** SHA-256 checksum of the migration file content */
701
- readonly checksum: string;
702
- /** When the migration was applied */
703
- readonly appliedAt: Date;
704
- /** Schema version at time of this migration */
705
- readonly schemaVersion: number;
706
- /** Whether this migration contains destructive changes */
707
- readonly destructive: boolean;
855
+ declare class PgsqlRawSqlTransactionControlError extends Error {
856
+ readonly dbspRawSqlTransactionControl = true;
857
+ constructor(cause: unknown);
858
+ }
859
+ declare class PgsqlTransactionAbortedCommitError extends Error {
860
+ readonly dbspTransactionAbortedCommit = true;
861
+ constructor(cause: unknown);
862
+ }
863
+ declare class PgsqlTransactionAbortedError extends Error {
864
+ readonly dbspTransactionAborted = true;
865
+ constructor(cause: unknown);
708
866
  }
709
867
  /**
710
- * Acquire a session-level advisory lock for migration operations.
711
- *
712
- * @deprecated Use {@link withMigrationLock} instead — pool.query may release
713
- * the connection (and the lock) before the migration completes.
868
+ * Options for PgsqlAdapter.
714
869
  */
715
- declare function acquireMigrationLock(pool: Pool): Promise<void>;
870
+ interface PgsqlAdapterOptions {
871
+ /** Schema name for multi-tenant queries */
872
+ readonly schemaName?: string;
873
+ /**
874
+ * DB column casing convention (intuitive semantics).
875
+ * - `'snake_case'`: DB columns are snake_case → transform to camelCase for JS
876
+ * - `'camelCase'`: DB columns are camelCase → no transformation
877
+ * - `'preserve'`: No transformation
878
+ */
879
+ readonly dbCasing?: DbCasing;
880
+ /** Optional model for WHERE compilation */
881
+ readonly model?: ModelIR;
882
+ /** Optional logger for debug/error messages */
883
+ readonly logger?: AdapterLogger;
884
+ /** Default primary key column name for convention fallbacks (default: 'id') */
885
+ readonly defaultPkColumnName?: string;
886
+ /** Convention for deriving FK column names: (tableName, pkName) => fkColumnName */
887
+ readonly deriveFkColumnName?: FkColumnDerivation;
888
+ }
889
+ interface PgsqlPoolAdapterOptions extends PgsqlAdapterOptions {
890
+ readonly borrowedClient?: false;
891
+ }
892
+ interface PgsqlBorrowedClientAdapterOptions extends PgsqlAdapterOptions {
893
+ /** This connection belongs to the caller. dbsp never releases it. */
894
+ readonly borrowedClient: true;
895
+ /**
896
+ * Let dbsp run transactions on your connection, through a savepoint.
897
+ *
898
+ * When your connection is already inside a transaction, dbsp creates a
899
+ * savepoint and rolls back dbsp's changes after that savepoint if the callback
900
+ * fails. `RELEASE SAVEPOINT` does not commit; it merges the work into your
901
+ * surrounding transaction, so a callback that succeeded is still undone if you
902
+ * later roll back. Deferred constraints or triggers can still make your outer
903
+ * `COMMIT` fail after dbsp has returned. `SET LOCAL` changes inside the callback
904
+ * remain in effect for the rest of your transaction after the savepoint is
905
+ * released. `ON COMMIT DROP` and `ON COMMIT DELETE ROWS` fire at your transaction
906
+ * boundary, not at the savepoint. Sequences are not transactional:
907
+ * `nextval`/`setval` are not reclaimed by a savepoint rollback. Session-level
908
+ * advisory locks ignore rollback; transaction-level advisory locks taken by a
909
+ * successful callback last until your transaction ends.
910
+ *
911
+ * Transaction control through raw SQL inside a scope dbsp is managing is
912
+ * unsupported. `COMMIT`, `ROLLBACK`, and `PREPARE TRANSACTION` end the
913
+ * transaction dbsp is working inside; dbsp detects that and fails loudly, but
914
+ * the data is already whatever your statement made it. Raw savepoint control
915
+ * (`SAVEPOINT`, `RELEASE SAVEPOINT`, `ROLLBACK TO SAVEPOINT`) can alter the
916
+ * savepoint stack before dbsp sees the command tag; dbsp poisons the scope, but
917
+ * it cannot make that command un-run. Manage your transaction outside dbsp's
918
+ * calls.
919
+ */
920
+ readonly managedTransactions?: true;
921
+ }
716
922
  /**
717
- * Release the session-level advisory lock for migration operations.
923
+ * Adapter implementation for PostgreSQL using native pg driver.
718
924
  *
719
- * @deprecated Use {@link withMigrationLock} instead.
720
- */
721
- declare function releaseMigrationLock(pool: Pool): Promise<void>;
722
- /**
723
- * Execute a callback under an advisory lock using a dedicated client.
724
- * The lock is held for the duration of the callback.
725
- * The client is released (and lock freed) after the callback completes.
726
- */
727
- declare function withMigrationLock<T>(pool: Pool, fn: (client: PoolClient) => Promise<T>): Promise<T>;
728
- /**
729
- * Ensure the migrations tracking table exists.
730
- * Auto-migrates existing tables that lack `schema_version`/`destructive` columns,
731
- * and backfills `schema_version` by `applied_at` order for rows still at 0.
732
- */
733
- declare function ensureMigrationsTable(pool: Pool): Promise<void>;
734
- /**
735
- * Get all applied migrations, ordered by name.
736
- */
737
- declare function getAppliedMigrations(pool: Pool): Promise<readonly MigrationRecord[]>;
738
- /**
739
- * Record a migration as applied.
740
- */
741
- declare function recordMigration(pool: Pool, name: string, checksum: string, schemaVersion: number, destructive: boolean): Promise<void>;
742
- /**
743
- * Check if a specific migration has been applied.
744
- */
745
- declare function isMigrationApplied(pool: Pool, name: string): Promise<boolean>;
746
- /**
747
- * Get the next schema version number (max + 1, or 1 if no migrations).
748
- */
749
- declare function getNextSchemaVersion(pool: Pool): Promise<number>;
750
- /**
751
- * Remove a migration record (for rollback).
752
- */
753
- declare function removeMigrationRecord(pool: Pool, name: string): Promise<void>;
754
-
755
- /**
756
- * Type Mapping - Maps ModelIR ColumnType to PostgreSQL data types
925
+ * @typeParam DB - Database schema type
757
926
  *
758
- * Supports both manual schemas and introspected schemas (preserving originalDbType).
759
- * Handles auto-increment via SERIAL/BIGSERIAL types.
927
+ * @example
928
+ * ```typescript
929
+ * import { Pool } from 'pg';
930
+ * import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';
760
931
  *
761
- * @module ddl/type-mapping
762
- */
763
-
764
- /**
765
- * Map ColumnType to PostgreSQL data type string.
766
- *
767
- * Uses originalDbType if available (from introspection), otherwise
768
- * falls back to reasonable PostgreSQL defaults.
769
- *
770
- * @param col - Column definition from ModelIR
771
- * @returns PostgreSQL type string (e.g., 'VARCHAR(255)', 'SERIAL', 'JSONB')
772
- */
773
- declare function mapColumnType(col: ColumnIR): string;
774
- /**
775
- * Map OnDeleteAction to PostgreSQL syntax.
776
- */
777
- declare function mapOnDeleteAction(action?: string): string;
778
-
779
- /**
780
- * EXPLAIN Statement Compiler
781
- *
782
- * Generates PostgreSQL EXPLAIN statements with various options.
783
- * Supports:
784
- * - ANALYZE (execute and show actual run times)
785
- * - FORMAT (text, json, xml, yaml)
786
- * - VERBOSE, COSTS, BUFFERS, TIMING, SETTINGS
787
- */
788
-
789
- /**
790
- * Output format for EXPLAIN results.
791
- */
792
- type ExplainFormat = 'text' | 'json' | 'xml' | 'yaml';
793
- /**
794
- * Options for EXPLAIN statement.
795
- */
796
- interface ExplainOptions {
797
- /** Execute the query and show actual run times */
798
- analyze?: boolean;
799
- /** Show more detailed output */
800
- verbose?: boolean;
801
- /** Show cost estimates (default: true) */
802
- costs?: boolean;
803
- /** Show buffer usage (requires analyze) */
804
- buffers?: boolean;
805
- /** Show actual timing (requires analyze) */
806
- timing?: boolean;
807
- /** Show non-default settings */
808
- settings?: boolean;
809
- /** Output format */
810
- format?: ExplainFormat;
811
- }
812
- /**
813
- * Build an EXPLAIN statement wrapping a query.
814
- *
815
- * @param query - The query to explain (SelectStmt, InsertStmt, etc.)
816
- * @param options - EXPLAIN options
817
- * @returns ExplainStmt AST node
818
- *
819
- * @example
820
- * ```typescript
821
- * const selectAst = { SelectStmt: { ... } };
822
- * const explainAst = buildExplain(selectAst, { analyze: true, format: 'json' });
823
- * // Produces: EXPLAIN (ANALYZE, FORMAT JSON) SELECT ...
932
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
933
+ * const adapter = createPgsqlAdapter(pool);
934
+ * const orm = createOrm({ model, adapter });
824
935
  * ```
825
936
  */
826
- declare function buildExplain(query: Node, options?: ExplainOptions): Node;
827
- /**
828
- * Build EXPLAIN ANALYZE with JSON format (common pattern).
829
- *
830
- * @param query - The query to explain
831
- * @returns ExplainStmt with ANALYZE and JSON format
832
- */
833
- declare function buildExplainAnalyzeJson(query: Node): Node;
834
- /**
835
- * Build simple EXPLAIN (plan only, no execution).
836
- *
837
- * @param query - The query to explain
838
- * @returns ExplainStmt with default options
839
- */
840
- declare function buildExplainPlan(query: Node): Node;
841
- /**
842
- * Build verbose EXPLAIN with costs and buffers.
843
- *
844
- * @param query - The query to explain
845
- * @returns ExplainStmt with verbose options
846
- */
847
- declare function buildExplainVerbose(query: Node): Node;
848
- /**
849
- * Parse EXPLAIN JSON output to get execution statistics.
850
- *
851
- * @param jsonOutput - The JSON string from EXPLAIN (ANALYZE, FORMAT JSON)
852
- * @returns Parsed plan with execution statistics
853
- */
854
- declare function parseExplainJson(jsonOutput: string): ExplainPlan[];
855
- /**
856
- * Parsed EXPLAIN plan structure (simplified).
857
- */
858
- interface ExplainPlan {
859
- Plan: {
860
- 'Node Type': string;
861
- 'Relation Name'?: string;
862
- Alias?: string;
863
- 'Startup Cost'?: number;
864
- 'Total Cost'?: number;
865
- 'Plan Rows'?: number;
866
- 'Plan Width'?: number;
867
- 'Actual Startup Time'?: number;
868
- 'Actual Total Time'?: number;
869
- 'Actual Rows'?: number;
870
- 'Actual Loops'?: number;
871
- Plans?: ExplainPlan['Plan'][];
872
- };
873
- 'Planning Time'?: number;
874
- 'Execution Time'?: number;
875
- Triggers?: unknown[];
876
- }
877
- /**
878
- * Extract total execution time from EXPLAIN ANALYZE JSON output.
879
- *
880
- * @param plans - Parsed EXPLAIN plans
881
- * @returns Total execution time in milliseconds
882
- */
883
- declare function getTotalExecutionTime(plans: ExplainPlan[]): number;
884
- /**
885
- * Extract row counts from EXPLAIN ANALYZE JSON output.
886
- *
887
- * @param plans - Parsed EXPLAIN plans
888
- * @returns Object with estimated and actual row counts
889
- */
890
- declare function getRowEstimates(plans: ExplainPlan[]): {
891
- estimated: number;
892
- actual: number;
893
- };
894
-
895
- /**
896
- * ParadeDB Extension Wrappers
897
- *
898
- * Type-safe query builders for ParadeDB BM25 full-text search.
899
- * All functions return ExpressionRef instances that can be used in:
900
- * - SELECT: .column(score('id').as('score'))
901
- * - WHERE: .where(bm25Search('symbols', query, { name: 3.0, doc: 1.0 }))
902
- * - ORDER BY: .orderBy(score('id'), 'desc')
903
- *
904
- * @remarks
905
- * ParadeDB functions accept both named and positional arguments.
906
- * This module uses named args via namedArg() for parse(), which produces:
907
- * paradedb.parse(field => 'field_name', query_string => $1)
908
- * Named parameter syntax is supported via the NamedArgExpressionIntent (EXT-NAMED-PARAMS).
909
- */
910
-
911
- /**
912
- * BM25 relevance score for a row.
913
- *
914
- * Use in SELECT and ORDER BY to retrieve and sort by full-text relevance.
915
- * Requires a BM25 index on the table.
916
- *
917
- * @param keyField - The key field of the BM25 index (typically the primary key, e.g. 'id')
918
- * @returns ExpressionRef that compiles to: paradedb.score("keyField")
919
- *
920
- * @example
921
- * orm.select('symbols').column(score('id').as('score')).orderBy(score('id'), 'desc')
922
- * // → paradedb.score("id") AS "score"
923
- */
924
- declare function score(keyField: string): ExpressionRef;
925
- /**
926
- * Parse a single-field BM25 query expression.
927
- *
928
- * Compiles to: paradedb.parse(field => 'field_name', query_string => $N)
929
- *
930
- * @param field - Column name to search in (must be indexed in the BM25 index)
931
- * @param query - Query string value (will be bound as a parameter)
932
- * @returns ExpressionRef for use with boost() or booleanSearch()
933
- *
934
- * @example
935
- * parse('name', 'hello world')
936
- * // → paradedb.parse(field => 'name', query_string => $1)
937
- */
938
- declare function parse(field: string, query: ExpressionRef | unknown): ExpressionRef;
939
- /**
940
- * Apply a boost multiplier to a BM25 sub-expression.
941
- *
942
- * Compiles to: paradedb.boost(factor, expr)
943
- *
944
- * @param factor - Boost multiplier (e.g. 3.0 for 3x weight)
945
- * @param expr - Expression to boost (typically a parse() call)
946
- * @returns ExpressionRef for use with booleanSearch()
947
- *
948
- * @example
949
- * boost(3.0, parse('name', 'hello'))
950
- * // → paradedb.boost(3.0, paradedb.parse('name', $1))
951
- */
952
- declare function boost(factor: number, expr: ExpressionRef): ExpressionRef;
953
- /**
954
- * Combine multiple BM25 sub-expressions with boolean OR logic.
955
- *
956
- * Compiles to: paradedb.boolean(expr1, expr2, ...)
957
- *
958
- * @param exprs - One or more sub-expressions (typically boost() calls)
959
- * @returns ExpressionRef for use on the right side of the @@@ operator
960
- *
961
- * @example
962
- * booleanSearch([boost(3.0, parse('name', q)), boost(1.0, parse('doc', q))])
963
- * // → paradedb.boolean(paradedb.boost(3.0, ...), paradedb.boost(1.0, ...))
964
- */
965
- /**
966
- * Combine multiple BM25 sub-expressions with boolean OR logic.
967
- *
968
- * Compiles to: paradedb.boolean(should => ARRAY[expr1, expr2, ...])
969
- *
970
- * @param exprs - One or more sub-expressions (typically boost() calls)
971
- * @returns ExpressionRef for use on the right side of the @@@ operator
972
- *
973
- * @example
974
- * booleanSearch([boost(3.0, parse('name', q)), boost(1.0, parse('doc', q))])
975
- * // → paradedb.boolean(should => ARRAY[paradedb.boost(3.0, ...), paradedb.boost(1.0, ...)])
976
- */
977
- declare function booleanSearch(exprs: ExpressionRef[]): ExpressionRef;
978
- /**
979
- * Full BM25 multi-field search with per-field boost weights.
980
- *
981
- * Produces: table @@@ paradedb.boolean(boost1, boost2, ...)
982
- *
983
- * Each field in `fieldBoosts` generates a `paradedb.boost(weight, paradedb.parse(field, $N))`
984
- * sub-expression. The same query string is used for all fields (single parameter binding).
985
- *
986
- * @param table - Table alias for the left side of the @@@ operator
987
- * @param query - Query string (bound as a single $N parameter, shared across all fields)
988
- * @param fieldBoosts - Map of column name → boost weight
989
- * @returns ExpressionRef for use in .where()
990
- *
991
- * @example
992
- * bm25Search('s', searchTerm, {
993
- * name_searchable: 3.0,
994
- * name: 1.0,
995
- * signature: 1.5,
996
- * doc_searchable: 1.0,
997
- * })
998
- * // → s @@@ paradedb.boolean(
999
- * // paradedb.boost(3.0, paradedb.parse('name_searchable', $1)),
1000
- * // paradedb.boost(1.0, paradedb.parse('name', $1)),
1001
- * // paradedb.boost(1.5, paradedb.parse('signature', $1)),
1002
- * // paradedb.boost(1.0, paradedb.parse('doc_searchable', $1))
1003
- * // )
1004
- *
1005
- * @remarks
1006
- * The query parameter is shared: all parse() calls reference the same $N slot.
1007
- * If you need different query strings per field, compose parse()/boost()/booleanSearch() manually.
1008
- *
1009
- * @remarks
1010
- * ParadeDB's boolean() function accepts both positional args and the named
1011
- * `should => ARRAY[...]` syntax. This wrapper uses positional args.
1012
- * Named parameter syntax is deferred to EXT-NAMED-PARAMS.
1013
- */
1014
- declare function bm25Search(table: string, query: unknown, fieldBoosts: Record<string, number>): ExpressionRef;
1015
-
1016
- /**
1017
- * PostgreSQL built-in function helpers.
1018
- *
1019
- * Thin wrappers around core expression primitives for common PostgreSQL functions.
1020
- * Same pattern as pgvector.ts and paradedb.ts.
1021
- */
1022
-
1023
- /**
1024
- * Generate a series of values: generate_series(start, stop[, step])
1025
- *
1026
- * Returns a set of values from start to stop (inclusive), with an optional step.
1027
- * Commonly used with CTE for batch operations.
1028
- *
1029
- * @example generateSeries(1, 100) → generate_series(1, 100)
1030
- * @example generateSeries(0, 50, 5) → generate_series(0, 50, 5)
1031
- */
1032
- declare function generateSeries(start: number, stop: number, step?: number): ExpressionRef;
1033
- /**
1034
- * Get next value from a sequence: nextval('sequence_name')
1035
- *
1036
- * @example nextval('order_id_seq') → nextval('order_id_seq')
1037
- */
1038
- declare function nextval(sequenceName: string): ExpressionRef;
1039
-
1040
- /**
1041
- * pgvector Extension Wrappers
1042
- *
1043
- * Type-safe query builders for pgvector distance operators.
1044
- * All functions return ExpressionRef instances that can be used in:
1045
- * - SELECT: .column(cosineDistance('vector', qv).as('score'))
1046
- * - WHERE: .where(cosineDistance('vector', qv).gte(0.5))
1047
- * - ORDER BY: .orderBy(rawDistance('vector', qv), 'asc')
1048
- */
1049
-
1050
- /**
1051
- * Cosine similarity: 1 - (col <=> vector)
1052
- *
1053
- * Score in [0, 1], higher = more similar.
1054
- * Use in SELECT to get a similarity score.
1055
- *
1056
- * @example
1057
- * orm.select('embeddings').column(cosineDistance('vector', qv).as('score'))
1058
- */
1059
- declare function cosineDistance(column: string, vector: number[]): ExpressionRef;
1060
- /**
1061
- * Raw cosine distance: col <=> vector
1062
- *
1063
- * Lower = closer. Index-friendly — use in ORDER BY for ANN search.
1064
- * Do NOT use in SELECT as a similarity score (lower = closer is counterintuitive).
1065
- *
1066
- * @example
1067
- * orm.select('embeddings').orderBy(rawDistance('vector', qv), 'asc')
1068
- */
1069
- declare function rawDistance(column: string, vector: number[]): ExpressionRef;
1070
- /**
1071
- * L2 (Euclidean) distance: col <-> vector
1072
- *
1073
- * @example
1074
- * orm.select('embeddings').orderBy(l2Distance('vector', qv), 'asc')
1075
- */
1076
- declare function l2Distance(column: string, vector: number[]): ExpressionRef;
1077
- /**
1078
- * Inner product distance: col <#> vector (negative inner product)
1079
- *
1080
- * For maximum inner product search: ORDER BY innerProduct('vector', qv) ASC.
1081
- *
1082
- * @example
1083
- * orm.select('embeddings').orderBy(innerProduct('vector', qv), 'asc')
1084
- */
1085
- declare function innerProduct(column: string, vector: number[]): ExpressionRef;
1086
- /**
1087
- * Get the number of dimensions of a vector column: vector_dims(col)
1088
- *
1089
- * Returns an integer — the dimension count of the stored vector.
1090
- * Useful for sanity-checking that embeddings match the expected model dimension.
1091
- *
1092
- * @example
1093
- * orm.from(embeddings).columns([vectorDims('vector').as('dim')]).first()
1094
- * // → SELECT vector_dims("t0"."vector") AS "dim" FROM "embeddings" AS "t0"
1095
- */
1096
- declare function vectorDims(column: string): ExpressionRef;
1097
-
1098
- /**
1099
- * Immutable context passed to all handlers during compilation.
1100
- */
1101
- interface CompilerContext {
1102
- /** Naming convention transformer */
1103
- readonly naming: NamingPlugin;
1104
- /** Schema name for table qualification (optional) */
1105
- readonly schema?: string;
1106
- /** Dialect capabilities for adapter-layer SQL surface gates */
1107
- readonly dialectCapabilities?: DialectCapabilities;
1108
- /** Root table name for the query */
1109
- readonly rootTable: string;
1110
- /** Current table alias (for JOINs) */
1111
- readonly currentAlias?: string;
1112
- /** Final relation path/name → SQL join alias map for relation-aware expression contexts */
1113
- readonly aliases?: ReadonlyMap<string, string>;
1114
- /** Maximum recursive depth (default: 100) */
1115
- readonly maxRecursiveDepth: number;
1116
- /** Optional callback for raw SQL audit trail */
1117
- readonly onRawSQL?: (sql: string) => void;
1118
- /** Default primary key column name for convention fallbacks (default: 'id') */
1119
- readonly defaultPkColumnName?: string;
1120
- /** Convention for deriving FK column names: (tableName, pkName) => fkColumnName */
1121
- readonly deriveFkColumnName?: FkColumnDerivation;
1122
- /** Alias of the outer (parent) query — used for FieldRef scope:'outer' resolution in EXISTS subqueries */
1123
- readonly outerAlias?: string;
1124
- /** Query-local CTE/binding names that must not be schema-qualified. */
1125
- readonly bindingNames?: BindingNameRegistry;
937
+ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
938
+ private readonly pool;
939
+ private readonly client;
940
+ private readonly borrowedClient;
941
+ private readonly managedTransactions;
942
+ private readonly adapterManagedTransaction;
943
+ private readonly scopeToken;
944
+ private readonly scopeState;
945
+ private readonly schemaName;
946
+ private readonly _dbCasing;
947
+ private readonly naming;
948
+ private readonly model;
949
+ private readonly logger;
950
+ private readonly _capabilities;
951
+ private readonly defaultPk;
952
+ private readonly deriveFk;
1126
953
  /**
1127
- * Optional callback to compile a QueryIntent into an AST Node (SubLink subselect).
1128
- * Set by PlanCompiler when compiling selectCustomExpression — enables SubqueryExpressionIntent
1129
- * to embed a fully compiled sub-SELECT into the parent SELECT column list.
954
+ * Create a new PgsqlAdapter.
1130
955
  *
1131
- * @param query - The inner QueryIntent to compile
1132
- * @param paramOffset - Current outer paramIndex; inner $N are renumbered by this offset
1133
- * @returns The compiled SelectStmt AST node and the inner parameters
956
+ * Ownership of the connection is **declared**, never inferred. Handing over a
957
+ * `PoolClient` means nothing on its own — it says the object has a `release()`
958
+ * method, not that a transaction is open or that the caller owns the lifecycle.
959
+ * Pass `borrowedClient: true` to say so.
960
+ *
961
+ * @param pool - a pg.Pool, a caller-owned pg.PoolClient (with `borrowedClient: true`),
962
+ * or nothing at all for compile-only mode
963
+ * @param options - configuration; declares connection ownership
1134
964
  */
1135
- readonly compileSubquery?: (query: _dbsp_types.QueryIntent, paramOffset: number) => {
1136
- ast: Node;
1137
- parameters: readonly unknown[];
1138
- };
965
+ constructor(pool?: Pool | undefined, options?: PgsqlPoolAdapterOptions);
966
+ constructor(pool: Pool, options?: PgsqlPoolAdapterOptions);
967
+ constructor(client: PoolClient, options: PgsqlBorrowedClientAdapterOptions);
968
+ constructor(pool: undefined, options?: PgsqlAdapterOptions);
1139
969
  /**
1140
- * Optional recursive compiler for NQL-origin SELECT expression values nested
1141
- * inside handler arguments, such as coalesce(upper(name), :fallback) or
1142
- * (price + :a) * :b.
970
+ * Shared compilation dependencies — built lazily from adapter fields.
971
+ * Passed to compiler sub-modules instead of `this`.
1143
972
  */
1144
- readonly compileNqlSelectExpression?: (value: unknown, ctx: CompilerContext, state: CompilerState) => Node;
1145
973
  /**
1146
- * Optional ModelIR for type-aware parameter casting.
1147
- * When provided, WHERE comparisons emit `$N::type` to eliminate
1148
- * PostgreSQL type inference ambiguity for nullable columns.
974
+ * Return a new PgsqlAdapterOptions that merges current config with overrides.
975
+ * Ensures that all configuration fields (logger, defaultPkColumnName,
976
+ * deriveFkColumnName, etc.) are propagated to scoped/transactional adapters.
1149
977
  */
1150
- readonly model?: ModelIR;
1151
- }
1152
- /**
1153
- * Mutable state maintained during compilation.
1154
- */
1155
- interface CompilerState {
1156
- /** Collected parameters in order */
1157
- parameters: unknown[];
1158
- /** Current parameter index (1-based for PostgreSQL) */
1159
- paramIndex: number;
1160
- /** Registered CTEs for the query */
1161
- ctes: Map<string, Node>;
1162
- /** Table aliases in use */
1163
- aliases: Map<string, string>;
1164
- /** JOIN clauses accumulated */
1165
- joins: Node[];
1166
- }
1167
- /**
1168
- * Base decision interface matching core's PlanDecision structure.
1169
- */
1170
- interface Decision {
1171
- readonly type: string;
1172
- readonly table?: string;
1173
- readonly column?: string;
1174
- readonly alias?: string;
1175
- readonly operator?: string;
1176
- readonly value?: unknown;
1177
- readonly paramIndex?: number;
1178
- readonly dataType?: string;
1179
- readonly direction?: 'ASC' | 'DESC';
1180
- readonly nulls?: 'FIRST' | 'LAST';
1181
- readonly joinType?: 'inner' | 'left';
1182
- readonly sourceColumn?: ColumnListInput;
1183
- readonly targetColumn?: ColumnListInput;
1184
- readonly targetTable?: string;
1185
- readonly function?: string;
1186
- /** Apply DISTINCT to a SELECT-list aggregate (e.g. COUNT(DISTINCT col)). */
1187
- readonly distinct?: boolean;
1188
- readonly args?: readonly unknown[];
1189
- readonly conditions?: readonly Decision[];
1190
- readonly columns?: readonly string[];
1191
- readonly values?: readonly unknown[];
1192
- readonly set?: readonly {
1193
- column: string;
1194
- value: unknown;
1195
- }[];
1196
- readonly limit?: number | ParamIntent | {
1197
- paramIndex: number;
1198
- };
1199
- readonly offset?: number | ParamIntent | {
1200
- paramIndex: number;
1201
- };
1202
- readonly strategy?: 'join' | 'lateral' | 'json_agg' | 'cte';
1203
- readonly relation?: string;
1204
- readonly relationName?: string;
1205
- readonly relationPath?: string;
1206
- readonly hydrationPrefix?: string;
1207
- readonly include?: readonly Decision[];
1208
- readonly relationType?: 'belongsTo' | 'hasMany' | 'hasOne';
1209
- readonly foreignKey?: ColumnListInput;
1210
- readonly parentKey?: ColumnListInput;
1211
- readonly orderByFallback?: boolean;
1212
- readonly children?: readonly Decision[];
1213
- readonly partition?: readonly string[];
1214
- readonly orderBy?: readonly {
1215
- column: string;
1216
- direction?: 'ASC' | 'DESC';
1217
- }[] | readonly JsonAggOrderByEntry[];
1218
- readonly frame?: string;
1219
- readonly maxDepth?: number;
1220
- readonly pathColumn?: string;
1221
- readonly cycleDetection?: boolean;
1222
- readonly selectColumn?: string;
1223
- readonly aggregate?: string;
978
+ private cloneOptions;
979
+ private buildCompileDeps;
980
+ private requireNqlCompileModel;
981
+ private assertNqlBindingNamesDisjointFromTables;
982
+ private compileNqlMutation;
983
+ private compileNqlBundleLeaf;
984
+ private compileNqlBundle;
1224
985
  /**
1225
- * Apply DISTINCT to a scalar subquery's aggregate (e.g. AVG(DISTINCT price)).
1226
- * Deliberately NOT named `distinct` — `assertNoDroppedDecisionModifiers`
1227
- * (subquery-emission.ts) treats a top-level `distinct === true` on ANY
1228
- * subquery decision as an unsupported query-level DISTINCT modifier and
1229
- * throws. This field is scoped to the aggregate projection only, so it
1230
- * must not collide with that generic guard.
986
+ * Returns the pool/client executor, or throws if in compile-only mode.
1231
987
  */
1232
- readonly aggregateDistinct?: boolean;
1233
- readonly subqueryOperator?: string;
1234
- readonly traversal?: string;
1235
- readonly traversals?: readonly {
1236
- traversal: string;
1237
- targetColumn?: string;
1238
- }[];
1239
- readonly isRecursive?: boolean;
1240
- readonly fkColumn?: string;
1241
- readonly pkColumn?: string;
1242
- readonly expandRelation?: string;
1243
- readonly relationColumns?: readonly string[];
1244
- readonly columnAliases?: Readonly<Record<string, string>>;
1245
- readonly jsonPath?: readonly unknown[];
1246
- readonly jsonMode?: 'json' | 'text';
1247
- readonly _compiledFilterWhere?: _pgsql_types.Node;
1248
- readonly filterWhere?: _pgsql_types.Node;
1249
- readonly expressionIntent?: unknown;
1250
- readonly escape?: string;
988
+ private requireConnection;
989
+ /** Adapter capabilities for feature detection */
990
+ get capabilities(): AdapterCapabilities;
991
+ /** PostgreSQL dialect capabilities for planner strategy selection */
992
+ get dialectCapabilities(): DialectCapabilities;
1251
993
  /**
1252
- * Provenance: the ORIGINAL QueryIntent before lowering.
1253
- * Set by every lowering site (convertIn, convertSubquery, normalizeToDecision,
1254
- * dispatchWhere, mapInSubqueryCondition) so that `buildPredicateSubquerySelect`
1255
- * (subquery-emission.ts) can validate the true caller intent rather than the
1256
- * stripped-down lowered decision fields.
994
+ * DB column casing convention used by this adapter.
995
+ */
996
+ get dbCasing(): DbCasing;
997
+ /**
998
+ * Get the underlying pg Pool or borrowed PoolClient instance.
999
+ */
1000
+ getPoolInstance(): Pool | PoolClient;
1001
+ /**
1002
+ * Compile a plan to executable SQL.
1257
1003
  *
1258
- * Required for IN / scalar / inSubquery / notInSubquery decisions.
1259
- * Optional on other decision types.
1004
+ * When passed a direct `CompiledNqlQuery`, the adapter trusts that the bundle
1005
+ * has already been semantically validated by the NQL compiler. This method
1006
+ * still performs adapter-owned SQL safety checks for emitted binding names
1007
+ * before CTE emission.
1260
1008
  */
1261
- readonly subqueryIntent?: _dbsp_types.QueryIntent;
1262
- }
1263
- /**
1264
- * Handler for WHERE clause conditions.
1265
- * Transforms condition decisions into PostgreSQL AST expressions.
1266
- */
1267
- interface WhereHandler {
1268
- /** Operator(s) this handler supports */
1269
- readonly operators: readonly string[];
1009
+ compile<T = unknown>(plan: PlanReport | CompiledNqlQuery, options?: CompileOptions): CompiledQuery<T>;
1270
1010
  /**
1271
- * Compile a WHERE condition to AST.
1272
- * @param decision The condition decision
1273
- * @param ctx Immutable compiler context
1274
- * @param state Mutable compiler state
1275
- * @param dispatch Callback to compile nested conditions
1276
- * @returns PostgreSQL AST node for the condition
1011
+ * Compile a plan with includes, returning subquery include metadata (DX-033).
1277
1012
  */
1278
- compile(decision: Decision, ctx: CompilerContext, state: CompilerState, dispatch: WhereDispatcher): Node;
1279
- }
1280
- /**
1281
- * Dispatcher for recursive WHERE compilation.
1282
- */
1283
- type WhereDispatcher = (decision: Decision, ctx: CompilerContext, state: CompilerState) => Node;
1284
- /**
1285
- * Handler for SELECT expressions.
1286
- * Transforms expression decisions into PostgreSQL AST nodes.
1287
- */
1288
- interface ExpressionHandler {
1289
- /** Expression type(s) this handler supports */
1290
- readonly types: readonly string[];
1013
+ compileWithIncludes<T = unknown>(plan: PlanReport, options?: CompileOptions): CompileResultWithIncludes<T>;
1291
1014
  /**
1292
- * Safe to use when a function name comes from NQL text.
1015
+ * Compile a subquery include query for given parent IDs (DX-033).
1016
+ * Generates: SELECT * FROM targetTable WHERE foreignKey IN ($1, $2, ...)
1293
1017
  *
1294
- * Raw/escape-hatch handlers must not set this. NQL-origin function names use
1295
- * this opt-in surface only, then fall back to generic FuncCall emission.
1018
+ * @param info - Subquery include metadata
1019
+ * @param parentIds - Parent record IDs to fetch related records for
1020
+ * @param options - Compile options
1021
+ * @returns Compiled query for fetching related records
1296
1022
  */
1297
- readonly nqlSafe?: boolean;
1023
+ compileSubqueryInclude(info: SubqueryIncludeInfo, parentIds: readonly unknown[], options?: CompileOptions): CompiledQuery;
1298
1024
  /**
1299
- * Compile an expression to AST.
1300
- * @param decision The expression decision
1301
- * @param ctx Immutable compiler context
1302
- * @param state Mutable compiler state
1303
- * @returns PostgreSQL AST node for the expression
1025
+ * Compile a FROM-less SELECT expression to SQL.
1026
+ *
1027
+ * Produces: SELECT <expr>
1028
+ * Example: SELECT nextval('my_seq')
1029
+ *
1030
+ * @param expr - ExpressionIntent to evaluate
1031
+ * @returns Compiled SQL and parameters
1304
1032
  */
1305
- compile(decision: Decision, ctx: CompilerContext, state: CompilerState): Node;
1306
- }
1307
- /**
1308
- * Handler for include/relation strategies.
1309
- * Transforms include decisions into PostgreSQL constructs (JOIN, LATERAL, json_agg, CTE).
1310
- */
1311
- interface IncludeHandler {
1312
- /** Strategy this handler implements */
1313
- readonly strategy: 'join' | 'lateral' | 'json_agg' | 'cte';
1033
+ compileSelectExpression(expr: ExpressionIntent): CompiledQuery;
1314
1034
  /**
1315
- * Compile an include to AST.
1316
- * @param decision The include decision
1317
- * @param ctx Immutable compiler context
1318
- * @param state Mutable compiler state
1319
- * @returns Object with modifications to apply
1035
+ * Compile an insert intent to executable SQL.
1036
+ *
1037
+ * Strategy switch (per CompileOptions):
1038
+ * - rows <= batchThreshold (default 50): VALUES ($1,$2),($3,$4),...
1039
+ * - rows > batchThreshold OR batchThreshold === 0: SELECT unnest($1::type[]),...
1320
1040
  */
1321
- compile(decision: Decision, ctx: CompilerContext, state: CompilerState): IncludeResult;
1322
- }
1323
- /**
1324
- * Result of include compilation.
1325
- */
1326
- interface IncludeResult {
1327
- /** Additional target list items (SELECT columns) */
1328
- targets?: Node[];
1329
- /** JOIN to add to FROM clause */
1330
- join?: Node;
1331
- /** Additional JOINs for cascaded includes (e.g., flat deep nesting) */
1332
- additionalJoins?: Node[];
1333
- /** CTE to add to WITH clause */
1334
- cte?: Node;
1335
- /** Subquery for LATERAL */
1336
- lateral?: Node;
1337
- }
1338
-
1339
- /**
1340
- * PostgreSQL Schema Introspection (ADAPTER-006)
1341
- *
1342
- * Queries information_schema/pg_catalog to build ModelIR
1343
- * from an existing database. Supports:
1344
- * - Table/column/PK discovery
1345
- * - FK → bidirectional relation inference
1346
- * - Hierarchy detection (adjacency + edge-table)
1347
- * - Include/exclude filtering
1348
- *
1349
- * @module introspection
1350
- */
1351
-
1352
- /** Options for database introspection */
1353
- interface IntrospectionOptions {
1354
- /** Tables to exclude (glob patterns: * matches any chars) */
1355
- readonly exclude?: readonly string[];
1356
- /** Tables to include (default: all). Applied before exclude. */
1357
- readonly include?: readonly string[];
1358
- /** Schema name to introspect (default: 'public') */
1359
- readonly schema?: string;
1360
- }
1361
- /** Hierarchy pattern detected during introspection */
1362
- /**
1363
- * Hierarchy pattern detected during introspection.
1364
- * Alias of {@link HierarchyIR} from \@dbsp/types — kept here for
1365
- * public-API backwards compatibility (re-exported from \@dbsp/adapter-pgsql).
1366
- */
1367
- type DetectedHierarchy = HierarchyIR;
1368
- /** Extended ModelIR with hierarchy metadata */
1369
- interface IntrospectedModelIR extends ModelIR {
1370
- readonly hierarchies: readonly DetectedHierarchy[];
1371
- readonly introspectedAt: Date;
1372
- readonly warnings: readonly string[];
1373
- }
1374
- declare function introspect(pool: Pool, options?: IntrospectionOptions): Promise<IntrospectedModelIR>;
1375
-
1376
- /**
1377
- * Mutation Compiler
1378
- *
1379
- * Compiles INSERT, UPDATE, and DELETE statements from plan decisions.
1380
- * Supports:
1381
- * - INSERT with values/from subquery
1382
- * - INSERT with RETURNING
1383
- * - UPDATE with SET and WHERE
1384
- * - DELETE with WHERE
1385
- * - RETURNING clause for all mutations
1386
- */
1387
-
1388
- /**
1389
- * Configuration for INSERT compilation
1390
- */
1391
- interface InsertConfig {
1392
- /** Table to insert into */
1393
- table: string;
1394
- /** Columns to insert */
1395
- columns: string[];
1396
- /** Values for each column (array of rows) */
1397
- values: unknown[][];
1398
- /** Columns to return (RETURNING clause) */
1399
- returning?: string[];
1400
- /** Alias-aware RETURNING projection items */
1401
- returningItems?: readonly MutationReturningItem[];
1402
- /** Subquery for INSERT ... SELECT */
1403
- selectQuery?: Node;
1404
- /** Column database types for type-cast emission (e.g. range types) */
1405
- columnTypes?: Record<string, string>;
1406
- }
1407
- /**
1408
- * Configuration for UPDATE compilation
1409
- */
1410
- interface UpdateConfig {
1411
- /** Table to update */
1412
- table: string;
1413
- /** Column-value pairs to set */
1414
- set: {
1415
- column: string;
1416
- value: unknown;
1417
- }[];
1418
- /** WHERE conditions */
1419
- where?: Decision[];
1420
- /** Columns to return (RETURNING clause) */
1421
- returning?: string[];
1422
- /** Alias-aware RETURNING projection items */
1423
- returningItems?: readonly MutationReturningItem[];
1424
- /** Column database types for type-cast emission (e.g. range types) */
1425
- columnTypes?: Record<string, string>;
1426
- }
1427
- /**
1428
- * Configuration for DELETE compilation
1429
- */
1430
- interface DeleteConfig {
1431
- /** Table to delete from */
1432
- table: string;
1433
- /** WHERE conditions */
1434
- where?: Decision[];
1435
- /** Columns to return (RETURNING clause) */
1436
- returning?: string[];
1437
- /** Alias-aware RETURNING projection items */
1438
- returningItems?: readonly MutationReturningItem[];
1439
- }
1440
- /**
1441
- * Compile an INSERT statement from configuration.
1442
- */
1443
- declare function compileInsert(config: InsertConfig, ctx: CompilerContext, state: CompilerState): Node;
1444
- /**
1445
- * Compile an UPDATE statement from configuration.
1446
- */
1447
- declare function compileUpdate(config: UpdateConfig, ctx: CompilerContext, state: CompilerState): Node;
1448
- declare function compileDelete(config: DeleteConfig, ctx: CompilerContext, state: CompilerState): Node;
1449
- /**
1450
- * Compile a mutation decision to AST.
1451
- * Determines mutation type from decision.type and delegates.
1452
- */
1453
- declare function compileMutation(decision: Decision, ctx: CompilerContext, state: CompilerState): Node;
1454
-
1455
- /**
1456
- * Upsert (INSERT ... ON CONFLICT) Compiler
1457
- *
1458
- * Compiles UPSERT statements with ON CONFLICT handling.
1459
- * Supports:
1460
- * - ON CONFLICT DO NOTHING
1461
- * - ON CONFLICT DO UPDATE SET ...
1462
- * - Conflict target (columns or constraint name)
1463
- * - WHERE clause for conflict resolution
1464
- */
1465
-
1466
- /**
1467
- * Conflict resolution strategy
1468
- */
1469
- type ConflictAction = 'nothing' | 'update';
1470
- /**
1471
- * Conflict target specification
1472
- */
1473
- interface ConflictTarget {
1474
- /** Column names that form the unique constraint */
1475
- columns?: string[];
1476
- /** Named constraint */
1477
- constraint?: string;
1478
- /** WHERE clause for partial index */
1479
- where?: Decision[];
1480
- }
1481
- /**
1482
- * Configuration for UPSERT compilation
1483
- */
1484
- interface UpsertConfig {
1485
- /** Table to upsert into */
1486
- table: string;
1487
- /** Columns to insert */
1488
- columns: string[];
1489
- /** Values for each column (array of rows) */
1490
- values: unknown[][];
1491
- /** Conflict target (unique columns or constraint) */
1492
- conflictTarget: ConflictTarget;
1493
- /** What to do on conflict */
1494
- conflictAction: ConflictAction;
1495
- /** Columns to update on conflict (for 'update' action) */
1496
- updateColumns?: string[];
1497
- /** Optional WHERE clause for ON CONFLICT DO UPDATE */
1498
- actionWhere?: Decision[];
1499
- /** Optional direct WHERE intent for ON CONFLICT DO UPDATE */
1500
- actionWhereIntent?: WhereIntent;
1501
- /** Compile the direct action WHERE intent using the caller's WHERE compiler */
1502
- compileActionWhere?: (where: WhereIntent, state: CompilerState) => Node;
1503
- /** Use EXCLUDED.column for update values (default: true) */
1504
- useExcluded?: boolean;
1505
- /** Columns to return (RETURNING clause) */
1506
- returning?: string[];
1507
- /** Alias-aware RETURNING projection items */
1508
- returningItems?: readonly MutationReturningItem[];
1509
- /** Optional column type hints for unnest casting (schema-driven) */
1510
- columnTypes?: Record<string, string>;
1041
+ compileInsert(intent: InsertIntent, options?: CompileOptions): CompiledQuery;
1511
1042
  /**
1512
- * Raw SQL expressions for specific update columns.
1513
- * These are injected verbatim into the ON CONFLICT DO UPDATE SET clause.
1514
- * Keys are logical column names (before naming plugin), values are raw SQL fragments.
1515
- *
1516
- * @warning SECURITY: fragments are inserted without parameterization.
1517
- * Only use with hardcoded expressions. Never with user input.
1518
- *
1519
- * @example { last_parsed: 'now()', count: 'excluded.count + 1' }
1043
+ * Compile an insert-from intent to executable SQL (NQL-ALIGN).
1044
+ * INSERT INTO target (cols) SELECT cols FROM source WHERE ... LIMIT ... RETURNING ...
1520
1045
  */
1521
- updateExpressions?: Record<string, string>;
1522
- }
1523
- /**
1524
- * Build ON CONFLICT clause for INSERT statement.
1525
- */
1526
- declare function buildOnConflictClause(config: UpsertConfig, ctx: CompilerContext, state: CompilerState): OnConflictClause;
1527
- /**
1528
- * Compile a complete UPSERT statement.
1529
- */
1530
- declare function compileUpsert(config: UpsertConfig, ctx: CompilerContext, state: CompilerState): Node;
1531
- /**
1532
- * Build EXCLUDED.column reference.
1533
- * EXCLUDED is a special table alias in ON CONFLICT ... DO UPDATE
1534
- * that refers to the row that would have been inserted.
1535
- */
1536
- declare function excludedRef(column: string, naming: {
1537
- toDatabase: (s: string) => string;
1538
- }): Node;
1539
- /**
1540
- * Build conditional update using COALESCE.
1541
- *
1542
- * Produces: COALESCE(EXCLUDED.col, table.col)
1543
- * This keeps existing value if new value is NULL.
1544
- */
1545
- declare function conditionalUpdate(column: string, table: string, ctx: CompilerContext): Node;
1546
-
1547
- /**
1548
- * @module naming
1549
- * Utilities for resolving database names to logical model names.
1550
- *
1551
- * The ModelIR.getTable() method expects logical (camelCase) names,
1552
- * but the adapter often works with database (snake_case) names.
1553
- * This module bridges that gap.
1554
- */
1555
-
1556
- /**
1557
- * Resolve a database table name to the corresponding logical model name.
1558
- *
1559
- * Converts the DB name using the naming convention, then looks it up in the model.
1560
- * Falls back to exact match if conversion doesn't find a match.
1561
- *
1562
- * @param model - The model IR to search in
1563
- * @param dbName - Database table name (e.g. "post_comments")
1564
- * @param convention - Naming convention used by the adapter
1565
- * @returns The logical table name if found, undefined otherwise
1566
- *
1567
- * @example
1568
- * ```typescript
1569
- * // With camelCase convention:
1570
- * resolveLogicalName(model, "post_comments", "camelCase") // → "postComments"
1571
- * resolveLogicalName(model, "posts", "camelCase") // → "posts"
1572
- * resolveLogicalName(model, "unknown", "camelCase") // → undefined
1573
- * ```
1574
- */
1575
- declare function resolveLogicalName(model: ModelIR, dbName: string, casing: DbCasing): string | undefined;
1576
-
1577
- /**
1578
- * ParamRef validation and helpers for PostgreSQL AST
1579
- *
1580
- * ParamRef nodes represent parameterized query placeholders ($1, $2, etc.)
1581
- * This module provides validation and creation helpers for safe AST construction.
1582
- */
1583
-
1584
- /**
1585
- * Validation result for ParamRef nodes
1586
- */
1587
- interface ParamRefValidationResult {
1588
- valid: boolean;
1589
- errors: string[];
1590
- }
1591
- /**
1592
- * Validates a ParamRef node
1593
- *
1594
- * Rules:
1595
- * - `number` must be a positive integer (1-based indexing)
1596
- * - `number` must not exceed reasonable bounds (e.g., 65535)
1597
- */
1598
- declare function validateParamRef(paramRef: ParamRef): ParamRefValidationResult;
1599
- /**
1600
- * Creates a validated ParamRef node
1601
- * @throws Error if validation fails
1602
- */
1603
- declare function createParamRef(number: number, location?: number): Node;
1604
- /**
1605
- * Creates a TypeCast node wrapping a ParamRef
1606
- * Example: $1::integer, $2::text[]
1607
- */
1608
- declare function createTypeCastParamRef(paramNumber: number, typeName: string, isArray?: boolean, location?: number): Node;
1609
- /**
1610
- * Creates an A_Expr node for equality comparison with ParamRef
1611
- * Example: col = $1
1612
- */
1613
- declare function createEqualityExpr(columnName: string, paramNumber: number, tableName?: string, location?: number): Node;
1614
- /**
1615
- * Creates a FuncCall node for ANY() with ParamRef
1616
- * Example: col = ANY($1) for array parameter matching
1617
- */
1618
- declare function createAnyExpr(columnName: string, paramNumber: number, tableName?: string, location?: number): Node;
1619
- /**
1620
- * Collects all ParamRef nodes from an AST, validating each
1621
- * Returns validation results for all found ParamRefs
1622
- */
1623
- declare function collectAndValidateParamRefs(node: unknown): {
1624
- paramRefs: Array<{
1625
- paramRef: ParamRef;
1626
- path: string;
1627
- }>;
1628
- validationResults: ParamRefValidationResult[];
1629
- allValid: boolean;
1630
- };
1631
-
1632
- /**
1633
- * PgsqlAdapter - Implements the Adapter interface for PostgreSQL using native pg driver.
1634
- *
1635
- * This adapter wraps a pg Pool instance and provides the unified
1636
- * adapter interface for the db-semantic-planner ORM.
1637
- *
1638
- * @module pgsql-adapter
1639
- */
1640
-
1641
- /**
1642
- * Options for PgsqlAdapter.
1643
- */
1644
- interface PgsqlAdapterOptions {
1645
- /** Schema name for multi-tenant queries */
1646
- readonly schemaName?: string;
1046
+ compileInsertFrom(intent: InsertFromIntent, options?: CompileOptions): CompiledQuery;
1647
1047
  /**
1648
- * DB column casing convention (intuitive semantics).
1649
- * - `'snake_case'`: DB columns are snake_case → transform to camelCase for JS
1650
- * - `'camelCase'`: DB columns are camelCase → no transformation
1651
- * - `'preserve'`: No transformation
1048
+ * Compile an update intent to executable SQL.
1652
1049
  */
1653
- readonly dbCasing?: DbCasing;
1654
- /** Optional model for WHERE compilation */
1655
- readonly model?: ModelIR;
1656
- /** Optional logger for debug/error messages */
1657
- readonly logger?: AdapterLogger;
1658
- /** Default primary key column name for convention fallbacks (default: 'id') */
1659
- readonly defaultPkColumnName?: string;
1660
- /** Convention for deriving FK column names: (tableName, pkName) => fkColumnName */
1661
- readonly deriveFkColumnName?: FkColumnDerivation;
1662
- }
1663
- /**
1664
- * Adapter implementation for PostgreSQL using native pg driver.
1665
- *
1666
- * @typeParam DB - Database schema type
1667
- *
1668
- * @example
1669
- * ```typescript
1670
- * import { Pool } from 'pg';
1671
- * import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';
1672
- *
1673
- * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
1674
- * const adapter = createPgsqlAdapter(pool);
1675
- * const orm = createOrm({ model, adapter });
1676
- * ```
1677
- */
1678
- declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1679
- private readonly pool;
1680
- private readonly client;
1681
- private readonly schemaName;
1682
- private readonly _dbCasing;
1683
- private readonly naming;
1684
- private readonly model;
1685
- private readonly logger;
1686
- private readonly _capabilities;
1687
- private readonly defaultPk;
1688
- private readonly deriveFk;
1050
+ compileUpdate(intent: UpdateIntent, options?: CompileOptions): CompiledQuery;
1689
1051
  /**
1690
- * Create a new PgsqlAdapter.
1052
+ * Compile a batch update intent to executable SQL using unnest FROM strategy (BATCH-001).
1691
1053
  *
1692
- * @param pool - pg.Pool instance, PoolClient (transactions), or undefined (compile-only mode)
1693
- * @param options - Optional configuration
1694
- */
1695
- constructor(pool?: Pool | PoolClient | undefined, options?: PgsqlAdapterOptions);
1696
- /**
1697
- * Shared compilation dependencies — built lazily from adapter fields.
1698
- * Passed to compiler sub-modules instead of `this`.
1054
+ * Generates:
1055
+ * UPDATE "table" SET "update_col" = t."update_col" [, "scalar_col" = $N]
1056
+ * FROM unnest(CAST($1 AS type[]), CAST($2 AS type[])) AS t("match_col", "update_col")
1057
+ * WHERE "table"."match_col" = t."match_col"
1058
+ * [RETURNING ...]
1699
1059
  */
1060
+ compileBatchUpdate(intent: BatchUpdateIntent, options?: CompileOptions): CompiledQuery;
1700
1061
  /**
1701
- * Return a new PgsqlAdapterOptions that merges current config with overrides.
1702
- * Ensures that all configuration fields (logger, defaultPkColumnName,
1703
- * deriveFkColumnName, etc.) are propagated to scoped/transactional adapters.
1062
+ * Compile a delete intent to executable SQL.
1704
1063
  */
1705
- private cloneOptions;
1706
- private buildCompileDeps;
1707
- private requireNqlCompileModel;
1708
- private assertNqlBindingNamesDisjointFromTables;
1709
- private compileNqlMutation;
1710
- private compileNqlBundleLeaf;
1711
- private compileNqlBundle;
1064
+ compileDelete(intent: DeleteIntent, options?: CompileOptions): CompiledQuery;
1712
1065
  /**
1713
- * Returns the pool/client executor, or throws if in compile-only mode.
1066
+ * Compile an upsert intent to executable SQL (DX-026).
1714
1067
  */
1715
- private requireConnection;
1716
- /** Adapter capabilities for feature detection */
1717
- get capabilities(): AdapterCapabilities;
1718
- /** PostgreSQL dialect capabilities for planner strategy selection */
1719
- get dialectCapabilities(): DialectCapabilities;
1068
+ compileUpsert(intent: UpsertIntent, options?: CompileOptions): CompiledQuery;
1720
1069
  /**
1721
- * DB column casing convention used by this adapter.
1070
+ * Compile an upsert-from intent to executable SQL (NQL-BIND).
1071
+ * INSERT INTO target SELECT ... FROM source ON CONFLICT (cols) DO UPDATE SET ...
1722
1072
  */
1723
- get dbCasing(): DbCasing;
1073
+ compileUpsertFrom(intent: UpsertFromIntent, options?: CompileOptions): CompiledQuery;
1724
1074
  /**
1725
- * Get the underlying pg Pool instance.
1075
+ * Compile a recursive CTE plan to executable SQL.
1076
+ * Supports adjacency-list and edge-table traversal modes.
1726
1077
  */
1727
- getPoolInstance(): Pool;
1078
+ compileRecursive(report: RecursivePlanReport, model: ModelIR, options?: CompileOptions): CompiledQuery;
1728
1079
  /**
1729
- * Compile a plan to executable SQL.
1080
+ * Compile a CTE query backed by unnest() arrays (BATCH-001 Block 5).
1730
1081
  *
1731
- * When passed a direct `CompiledNqlQuery`, the adapter trusts that the bundle
1732
- * has already been semantically validated by the NQL compiler. This method
1733
- * still performs adapter-owned SQL safety checks for emitted binding names
1734
- * before CTE emission.
1082
+ * Strategy: compile CTE nodes to SQL fragments, compile outer query
1083
+ * independently (parameters starting at $1), then renumber outer params
1084
+ * to start after CTE params and prepend WITH clause.
1735
1085
  */
1736
- compile<T = unknown>(plan: PlanReport | CompiledNqlQuery, options?: CompileOptions): CompiledQuery<T>;
1086
+ compileCteQuery(intent: CteQueryIntent, options?: CompileOptions): CompiledQuery;
1737
1087
  /**
1738
- * Compile a plan with includes, returning subquery include metadata (DX-033).
1088
+ * Compile a set operation (UNION / INTERSECT / EXCEPT) to SQL.
1739
1089
  */
1740
- compileWithIncludes<T = unknown>(plan: PlanReport, options?: CompileOptions): CompileResultWithIncludes<T>;
1741
- /**
1742
- * Compile a subquery include query for given parent IDs (DX-033).
1743
- * Generates: SELECT * FROM targetTable WHERE foreignKey IN ($1, $2, ...)
1744
- *
1745
- * @param info - Subquery include metadata
1746
- * @param parentIds - Parent record IDs to fetch related records for
1747
- * @param options - Compile options
1748
- * @returns Compiled query for fetching related records
1749
- */
1750
- compileSubqueryInclude(info: SubqueryIncludeInfo, parentIds: readonly unknown[], options?: CompileOptions): CompiledQuery;
1751
- /**
1752
- * Compile a FROM-less SELECT expression to SQL.
1753
- *
1754
- * Produces: SELECT <expr>
1755
- * Example: SELECT nextval('my_seq')
1756
- *
1757
- * @param expr - ExpressionIntent to evaluate
1758
- * @returns Compiled SQL and parameters
1759
- */
1760
- compileSelectExpression(expr: ExpressionIntent): CompiledQuery;
1761
- /**
1762
- * Compile an insert intent to executable SQL.
1763
- *
1764
- * Strategy switch (per CompileOptions):
1765
- * - rows <= batchThreshold (default 50): VALUES ($1,$2),($3,$4),...
1766
- * - rows > batchThreshold OR batchThreshold === 0: SELECT unnest($1::type[]),...
1767
- */
1768
- compileInsert(intent: InsertIntent, options?: CompileOptions): CompiledQuery;
1769
- /**
1770
- * Compile an insert-from intent to executable SQL (NQL-ALIGN).
1771
- * INSERT INTO target (cols) SELECT cols FROM source WHERE ... LIMIT ... RETURNING ...
1772
- */
1773
- compileInsertFrom(intent: InsertFromIntent, options?: CompileOptions): CompiledQuery;
1774
- /**
1775
- * Compile an update intent to executable SQL.
1776
- */
1777
- compileUpdate(intent: UpdateIntent, options?: CompileOptions): CompiledQuery;
1778
- /**
1779
- * Compile a batch update intent to executable SQL using unnest FROM strategy (BATCH-001).
1780
- *
1781
- * Generates:
1782
- * UPDATE "table" SET "update_col" = t."update_col" [, "scalar_col" = $N]
1783
- * FROM unnest(CAST($1 AS type[]), CAST($2 AS type[])) AS t("match_col", "update_col")
1784
- * WHERE "table"."match_col" = t."match_col"
1785
- * [RETURNING ...]
1786
- */
1787
- compileBatchUpdate(intent: BatchUpdateIntent, options?: CompileOptions): CompiledQuery;
1788
- /**
1789
- * Compile a delete intent to executable SQL.
1790
- */
1791
- compileDelete(intent: DeleteIntent, options?: CompileOptions): CompiledQuery;
1792
- /**
1793
- * Compile an upsert intent to executable SQL (DX-026).
1794
- */
1795
- compileUpsert(intent: UpsertIntent, options?: CompileOptions): CompiledQuery;
1796
- /**
1797
- * Compile an upsert-from intent to executable SQL (NQL-BIND).
1798
- * INSERT INTO target SELECT ... FROM source ON CONFLICT (cols) DO UPDATE SET ...
1799
- */
1800
- compileUpsertFrom(intent: UpsertFromIntent, options?: CompileOptions): CompiledQuery;
1801
- /**
1802
- * Compile a recursive CTE plan to executable SQL.
1803
- * Supports adjacency-list and edge-table traversal modes.
1804
- */
1805
- compileRecursive(report: RecursivePlanReport, model: ModelIR, options?: CompileOptions): CompiledQuery;
1806
- /**
1807
- * Compile a CTE query backed by unnest() arrays (BATCH-001 Block 5).
1808
- *
1809
- * Strategy: compile CTE nodes to SQL fragments, compile outer query
1810
- * independently (parameters starting at $1), then renumber outer params
1811
- * to start after CTE params and prepend WITH clause.
1812
- */
1813
- compileCteQuery(intent: CteQueryIntent, options?: CompileOptions): CompiledQuery;
1814
- /**
1815
- * Compile a set operation (UNION / INTERSECT / EXCEPT) to SQL.
1816
- */
1817
- compileSetOperation(intent: SetOperationIntent, model: ModelIR, options?: CompileOptions): CompiledQuery;
1818
- private compileSetOperationWithBindings;
1090
+ compileSetOperation(intent: SetOperationIntent, model: ModelIR, options?: CompileOptions): CompiledQuery;
1091
+ private compileSetOperationWithBindings;
1819
1092
  /**
1820
1093
  * Create a dump for observability.
1821
1094
  */
@@ -1854,6 +1127,9 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1854
1127
  * Internal: Stream with an existing client using cursors.
1855
1128
  */
1856
1129
  private streamWithClient;
1130
+ private streamWithManagedClient;
1131
+ private streamWithManagedClientSavepointScope;
1132
+ private streamWithClientTransaction;
1857
1133
  /**
1858
1134
  * Introspect the database schema and return a ModelIR.
1859
1135
  *
@@ -1870,8 +1146,83 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1870
1146
  introspect(options?: IntrospectionOptions): Promise<IntrospectedModelIR>;
1871
1147
  /**
1872
1148
  * Execute a callback within a database transaction.
1149
+ *
1150
+ * ## What this guarantees
1151
+ *
1152
+ * The callback's work commits together or not at all; a statement issued inside
1153
+ * the transaction never executes after its boundary, even if you forget to
1154
+ * `await` it; a nested `transaction()` is a real savepoint; and the connection
1155
+ * never goes back to the pool with a transaction still open on it.
1156
+ *
1157
+ * ## What it cannot guarantee, and you should know before you reach for raw SQL
1158
+ *
1159
+ * **Raw SQL that ends the transaction ends it.** `COMMIT`, `ROLLBACK` and
1160
+ * `PREPARE TRANSACTION` issued through `executeRaw` — or through several commands
1161
+ * in one call — reach PostgreSQL and take effect *before* dbsp is told what they
1162
+ * were: the command tag arrives after the statement has run. dbsp detects it, kills
1163
+ * the scope so nothing else escapes, and throws — but **it cannot un-run what your
1164
+ * statement already did**, and `transaction()` rejecting does not mean nothing was
1165
+ * committed.
1166
+ *
1167
+ * The same holds for session state raw SQL creates: a sequence that advanced stays
1168
+ * advanced, an advisory lock stays held, a `PREPARE` or a `SET` you issued survives
1169
+ * on a pooled connection. dbsp cleans up only what dbsp created.
1170
+ *
1171
+ * This is the contract of an escape hatch, not an oversight — see #327. If you need
1172
+ * transaction control, own the transaction: take a client, `BEGIN` on it yourself,
1173
+ * and hand dbsp a `borrowedClient` **without** `managedTransactions`. dbsp will then
1174
+ * contain its own statements inside *your* transaction instead of the other way round.
1873
1175
  */
1874
1176
  transaction<T>(fn: (adapter: Adapter<DB>) => Promise<T>): Promise<T>;
1177
+ /**
1178
+ * Execute scratch PostgreSQL work in a scope that always rolls back on success.
1179
+ *
1180
+ * This is intentionally PostgreSQL-adapter-specific. It is used for catalog
1181
+ * shaped scratch DDL such as CHECK expression canonicalisation, where the
1182
+ * caller needs PostgreSQL's rendering but must not keep the scratch objects.
1183
+ * Rollback here is cleanup of dbsp-created work, not a sandbox for arbitrary
1184
+ * session effects.
1185
+ */
1186
+ withScratchScope<T>(fn: (adapter: PgsqlAdapter<DB>) => Promise<T>): Promise<T>;
1187
+ private createManagedClientAdapter;
1188
+ private createChildTransactionObserver;
1189
+ private observeChildTransaction;
1190
+ private markChildTransactionObserved;
1191
+ private refreshScopeChildrenFailure;
1192
+ private transactionWithManagedClient;
1193
+ private transactionWithManagedClientSavepointScope;
1194
+ private transactionWithClientTransaction;
1195
+ private releaseClient;
1196
+ private rollbackAndReleaseSavepoint;
1197
+ private rollbackSavepoint;
1198
+ private releaseSavepoint;
1199
+ private rollbackTransactionIfOpen;
1200
+ private classifyTransactionStateError;
1201
+ private probeTransactionState;
1202
+ private classifySavepointReleaseFailure;
1203
+ private rollbackSavepointAfterReleaseFailure;
1204
+ private enterTransactionScope;
1205
+ private enterSavepointScope;
1206
+ private pushClientScope;
1207
+ private currentClientScope;
1208
+ private assertCanUseClient;
1209
+ private assertUsableScopeAncestors;
1210
+ private findClientScope;
1211
+ private assertScopeNotPoisoned;
1212
+ private throwIfScopePoisoned;
1213
+ private scopePoisonOutranksError;
1214
+ private poisonScopeState;
1215
+ private poisonClientScope;
1216
+ private poisonClientScopeStack;
1217
+ private runWithScopeStatementLock;
1218
+ private closeScope;
1219
+ private closeScopeAndAssertChildren;
1220
+ private drainScopeStatements;
1221
+ private drainScopeChildren;
1222
+ private assertScopeChildrenSettled;
1223
+ private drainScopeWork;
1224
+ private executeScopeBoundaryStatement;
1225
+ private assertCommitSucceeded;
1875
1226
  /**
1876
1227
  * Create a schema-scoped adapter for multi-tenant queries.
1877
1228
  */
@@ -1880,6 +1231,15 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1880
1231
  * Execute raw SQL directly.
1881
1232
  *
1882
1233
  * ⚠️ WARNING: Use parameter placeholders ($1, $2, etc.) for all values.
1234
+ *
1235
+ * Transaction control through raw SQL inside a scope dbsp is managing is
1236
+ * unsupported. `COMMIT`, `ROLLBACK`, and `PREPARE TRANSACTION` end the
1237
+ * transaction dbsp is working inside; dbsp detects that and fails loudly, but
1238
+ * the data is already whatever your statement made it. Raw savepoint control
1239
+ * (`SAVEPOINT`, `RELEASE SAVEPOINT`, `ROLLBACK TO SAVEPOINT`) can alter the
1240
+ * savepoint stack before dbsp sees the command tag; dbsp poisons the scope, but
1241
+ * it cannot make that command un-run. Manage your transaction outside dbsp's
1242
+ * calls.
1883
1243
  */
1884
1244
  executeRaw<T = unknown>(sql: string, parameters?: readonly unknown[]): Promise<T[]>;
1885
1245
  /**
@@ -1901,17 +1261,35 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1901
1261
  */
1902
1262
  executeDDL(sql: string): Promise<void>;
1903
1263
  /**
1904
- * Whether this adapter instance is scoped inside a transaction.
1905
- * Guards unsafe DDL operations (VACUUM, CREATE INDEX CONCURRENTLY).
1264
+ * Whether a transaction is open on this adapter's connection.
1265
+ * This is true for dbsp-managed scopes and for borrowed pg clients whose
1266
+ * ReadyForQuery status says the caller has an open transaction.
1906
1267
  *
1907
1268
  * @since DDL-TABLE-001
1908
1269
  */
1909
1270
  get inTransaction(): boolean;
1271
+ private adapterManagedScopeIsLive;
1272
+ private executeQueryProtectingOpenTransaction;
1273
+ private executeConnectionStatement;
1274
+ private executeConnectionStatementUnlocked;
1275
+ private executeConnectionStatementInSavepoint;
1276
+ private issueConnectionQuery;
1277
+ private assertNoMultiCommandRawCall;
1278
+ private assertNoTransactionControlCommand;
1279
+ private assertPrepareDidNotEndTransaction;
1280
+ /**
1281
+ * Resolve the explicit schema for a catalog read: an explicit argument, else
1282
+ * the adapter's configured schema, else `undefined` (resolve in-query). NOT a
1283
+ * hard-coded 'public' — an unresolved schema is handled by the SQL, which
1284
+ * finds the table's schema search_path-aware, in the SAME session, so a
1285
+ * non-public search_path and a pooled connection both stay correct.
1286
+ */
1287
+ private explicitSchema;
1910
1288
  /**
1911
1289
  * List all indexes on a table by querying pg_indexes.
1912
1290
  *
1913
1291
  * @param table - Table name
1914
- * @param schema - Schema name (defaults to adapter schema or 'public')
1292
+ * @param schema - Schema name (defaults to the search_path-resolved schema)
1915
1293
  */
1916
1294
  listIndexes(table: string, schema?: string, options?: {
1917
1295
  namePattern?: string;
@@ -1921,93 +1299,1047 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1921
1299
  *
1922
1300
  * @param name - Index name
1923
1301
  * @param table - Table name
1924
- * @param schema - Schema name (defaults to adapter schema or 'public')
1302
+ * @param schema - Schema name (defaults to the search_path-resolved schema)
1925
1303
  */
1926
1304
  indexExists(name: string, table: string, schema?: string): Promise<boolean>;
1927
1305
  /**
1928
- * Return the total storage size of a table in bytes.
1306
+ * Return the total storage size of a table in bytes (includes indexes and TOAST).
1307
+ *
1308
+ * The table name is a SQL identifier — it is double-quoted, not parameterized,
1309
+ * because PostgreSQL does not allow parameterized table names in FROM clauses.
1310
+ * With no known schema the table is left unqualified so ::regclass resolves it
1311
+ * through search_path (the same table an unqualified reference would hit).
1929
1312
  *
1930
1313
  * @param table - Table name
1931
- * @param schema - Schema name (defaults to adapter schema or 'public')
1314
+ * @param schema - Schema name (defaults to the search_path-resolved schema)
1315
+ */
1316
+ storageSize(table: string, schema?: string): Promise<number>;
1317
+ /**
1318
+ * Generate SQL for TRUNCATE TABLE.
1319
+ * Implements TableDDLGeneratorAdapter.generateTruncate.
1320
+ */
1321
+ generateTruncate(table: string, schema?: string, options?: TruncateOptions): string;
1322
+ /**
1323
+ * Generate SQL for VACUUM.
1324
+ * Implements TableDDLGeneratorAdapter.generateVacuum.
1325
+ */
1326
+ generateVacuum(table: string, schema?: string, options?: VacuumOptions): string;
1327
+ /**
1328
+ * Generate SQL for ALTER TABLE ... ALTER COLUMN.
1329
+ * Implements TableDDLGeneratorAdapter.generateAlterColumn.
1330
+ */
1331
+ generateAlterColumn(table: string, column: string, options: AlterColumnOptions, schema?: string): string;
1332
+ /**
1333
+ * Generate SQL for CREATE INDEX.
1334
+ * Implements TableDDLGeneratorAdapter.generateCreateIndex.
1335
+ */
1336
+ generateCreateIndex(table: string, options: CreateIndexOptions, schema?: string): string;
1337
+ /**
1338
+ * Generate SQL for DROP INDEX.
1339
+ * Implements TableDDLGeneratorAdapter.generateDropIndex.
1340
+ */
1341
+ generateDropIndex(name: string, options?: DropIndexOptions): string;
1342
+ /**
1343
+ * Validate an identifier (table name, column name, schema name).
1344
+ */
1345
+ validateIdentifier(value: string, type: string): void;
1346
+ }
1347
+ /**
1348
+ * Create a PgsqlAdapter from a pg Pool instance.
1349
+ *
1350
+ * @param pool - pg Pool instance
1351
+ * @param options - Optional configuration
1352
+ * @returns A new PgsqlAdapter instance
1353
+ *
1354
+ * @example
1355
+ * ```typescript
1356
+ * import { Pool } from 'pg';
1357
+ * import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';
1358
+ *
1359
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
1360
+ * const adapter = createPgsqlAdapter(pool);
1361
+ *
1362
+ * // With naming convention
1363
+ * const adapter = createPgsqlAdapter(pool, { dbCasing: 'snake_case' });
1364
+ * ```
1365
+ */
1366
+ declare function createPgsqlAdapter<DB = unknown>(pool: Pool, options?: PgsqlPoolAdapterOptions): PgsqlAdapter<DB>;
1367
+ declare function createPgsqlAdapter<DB = unknown>(client: PoolClient, options: PgsqlBorrowedClientAdapterOptions): PgsqlAdapter<DB>;
1368
+ /**
1369
+ * Creates a compile-only PgsqlAdapter for SQL generation without a database connection.
1370
+ *
1371
+ * All compilation methods (compile, compileInsert, etc.), createDump(), and generateDDL()
1372
+ * work normally. Execution methods (execute, stream, transaction, etc.) throw an error.
1373
+ *
1374
+ * @example
1375
+ * ```typescript
1376
+ * import { createPgsqlCompileOnlyAdapter } from '@dbsp/adapter-pgsql';
1377
+ * import { createOrm } from '@dbsp/core';
1378
+ *
1379
+ * const adapter = createPgsqlCompileOnlyAdapter();
1380
+ * const orm = createOrm({ model, adapter });
1381
+ * const dump = await orm.select('users').dump();
1382
+ * console.log(dump.sql);
1383
+ * ```
1384
+ */
1385
+ declare function createPgsqlCompileOnlyAdapter<DB = unknown>(options?: PgsqlAdapterOptions): CompileOnlyAdapter;
1386
+
1387
+ /**
1388
+ * Schema Comparison Engine (DDL-PROV Block 1)
1389
+ *
1390
+ * Compares two ModelIRs (schema definition vs database state)
1391
+ * and produces a structured diff of changes needed.
1392
+ *
1393
+ * @module schema-diff
1394
+ */
1395
+
1396
+ type ChangeKind = 'create_table' | 'drop_table' | 'add_column' | 'drop_column' | 'alter_column_type' | 'alter_column_nullable' | 'alter_column_default' | 'alter_column_unique' | 'add_primary_key' | 'drop_primary_key' | 'add_foreign_key' | 'drop_foreign_key' | 'alter_foreign_key' | 'validate_constraint' | 'create_index' | 'drop_index' | 'add_check_constraint' | 'drop_check_constraint' | 'create_enum' | 'alter_enum_add_value' | 'drop_enum' | 'alter_column_collation' | 'alter_column_identity' | 'add_comment' | 'drop_comment' | 'create_extension' | 'drop_extension' | 'create_sequence' | 'alter_sequence' | 'drop_sequence' | 'enable_rls' | 'disable_rls' | 'create_policy' | 'drop_policy';
1397
+ interface SchemaChange {
1398
+ readonly kind: ChangeKind;
1399
+ readonly table: string;
1400
+ readonly column?: string;
1401
+ readonly destructive: boolean;
1402
+ readonly details: string;
1403
+ /** Additional metadata for SQL generation */
1404
+ readonly meta?: Readonly<Record<string, unknown>>;
1405
+ }
1406
+ interface DiffSummary {
1407
+ readonly tables: {
1408
+ readonly added: number;
1409
+ readonly dropped: number;
1410
+ };
1411
+ readonly columns: {
1412
+ readonly added: number;
1413
+ readonly dropped: number;
1414
+ readonly altered: number;
1415
+ };
1416
+ readonly indexes: {
1417
+ readonly added: number;
1418
+ readonly dropped: number;
1419
+ };
1420
+ readonly constraints: {
1421
+ readonly added: number;
1422
+ readonly dropped: number;
1423
+ readonly altered: number;
1424
+ };
1425
+ }
1426
+ interface SchemaDiff {
1427
+ readonly changes: readonly SchemaChange[];
1428
+ readonly hasDestructive: boolean;
1429
+ readonly summary: DiffSummary;
1430
+ }
1431
+ interface CompareSchemataOptions {
1432
+ /**
1433
+ * Database naming convention.
1434
+ * When set, schema model names (camelCase) are converted to DB format
1435
+ * (e.g. snake_case) before comparison with the introspected model.
1436
+ */
1437
+ dbCasing?: DbCasing;
1438
+ /** Dialect capabilities — comparisons for unsupported features will be skipped */
1439
+ readonly dialectCapabilities?: DialectCapabilities;
1440
+ /**
1441
+ * When `true`, extensions present in the live DB but absent from the model
1442
+ * schema are silently ignored — no `drop_extension` change is emitted for them.
1443
+ * Only extensions explicitly declared in the model are managed (created if missing).
1444
+ *
1445
+ * Use this when the database image pre-installs extensions that the application
1446
+ * schema does not own (e.g. pgvector, pg_search bundled in a custom Postgres image).
1447
+ * Default: `false` (full-sync behaviour — unmanaged DB extensions produce a
1448
+ * `drop_extension` entry).
1449
+ */
1450
+ readonly ignoreUnmanagedExtensions?: boolean;
1451
+ /**
1452
+ * Strict compile-only mode for callers that require convergence guarantees.
1453
+ *
1454
+ * `compareSchemata()` is intentionally pure and cannot ask PostgreSQL to
1455
+ * canonicalise raw-SQL expression surfaces. By default it keeps the historic
1456
+ * best-effort raw string comparison for CHECK expressions, partial-index
1457
+ * predicates, and index expressions. Set this flag to throw when either model
1458
+ * contains one of those surfaces so a caller cannot accidentally rely on a
1459
+ * compile-only diff for a convergence-sensitive check.
1460
+ *
1461
+ * Live PostgreSQL callers should use `comparePgsqlDatabaseSchema()`, which
1462
+ * canonicalises CHECK constraint expressions before calling this function.
1463
+ * Partial-index predicates and index expressions are not canonicalised by the
1464
+ * live helper and are rejected there when this strict flag is set.
1465
+ */
1466
+ readonly requireExpressionCanonicalization?: boolean;
1467
+ }
1468
+ declare class ExpressionCanonicalizationUnavailableError extends Error {
1469
+ readonly surfaces: readonly string[];
1470
+ constructor(surfaces: readonly string[]);
1471
+ }
1472
+ /**
1473
+ * Compare two ModelIRs and produce a structured diff.
1474
+ *
1475
+ * @param schema - The desired schema (from definition)
1476
+ * @param db - The current database state (from introspection)
1477
+ * @param options - Optional comparison settings (e.g. dbCasing)
1478
+ * @returns SchemaDiff with all changes needed to bring DB in sync with schema
1479
+ */
1480
+ declare function compareSchemata(schema: ModelIR, db: ModelIR, options?: CompareSchemataOptions): SchemaDiff;
1481
+
1482
+ interface ComparePgsqlDatabaseSchemaOptions extends CompareSchemataOptions, SchemaScopeOptions {
1483
+ /**
1484
+ * Whether to canonicalise PostgreSQL CHECK constraint expressions before
1485
+ * comparing. Defaults to `true`. Set to `false` only for compatibility with
1486
+ * legacy raw-string live diffs.
1487
+ *
1488
+ * Live canonicalisation creates temporary scratch tables and missing desired
1489
+ * enum types inside an adapter scratch scope whose successful cleanup is
1490
+ * rollback. The database role needs permission to create temporary tables,
1491
+ * and enum-dependent checks may need permission to create the pending enum
1492
+ * type. If PostgreSQL refuses that scratch DDL, non-strict mode warns and
1493
+ * falls back to best-effort raw string comparison for the affected CHECK
1494
+ * constraints.
1495
+ */
1496
+ readonly canonicalizeExpressions?: boolean;
1497
+ /**
1498
+ * Receives live CHECK canonicalisation warnings. Defaults to console.warn.
1932
1499
  */
1500
+ readonly onWarning?: (message: string) => void;
1501
+ /**
1502
+ * Diff that the caller just applied before this live re-diff. Used only when
1503
+ * CHECK expressions are compared by raw text to fail loudly if the exact same
1504
+ * expression-surface drift appears again after re-introspection.
1505
+ */
1506
+ readonly previouslyAppliedDiff?: SchemaDiff;
1507
+ }
1508
+ declare class NonConvergentSchemaDiffError extends Error {
1509
+ readonly table: string;
1510
+ readonly constraint: string;
1511
+ readonly desiredExpression: string;
1512
+ readonly databaseExpression: string;
1513
+ constructor(table: string, constraint: string, desiredExpression: string, databaseExpression: string);
1514
+ }
1515
+ /** An enum value this diff adds, reported as a candidate cause — never asserted. */
1516
+ interface AddedEnumValue {
1517
+ readonly enumName: string;
1518
+ readonly value: string;
1519
+ }
1520
+ declare class CheckConstraintNewEnumValueError extends Error {
1521
+ readonly table: string;
1522
+ readonly constraint: string;
1523
+ readonly addedEnumValues: readonly AddedEnumValue[];
1524
+ constructor(table: string, constraint: string, addedEnumValues: readonly AddedEnumValue[]);
1525
+ }
1526
+ /**
1527
+ * Live PostgreSQL schema diff: introspect, canonicalise desired CHECK
1528
+ * constraint expressions through PostgreSQL, then call the pure synchronous
1529
+ * schema comparator.
1530
+ *
1531
+ * If CHECK canonicalisation falls back while the same diff adds a plausibly
1532
+ * referenced enum value, the diff is refused. dbsp currently applies each
1533
+ * migration in one transaction, and PostgreSQL forbids using a newly added enum
1534
+ * value in that same transaction; emitting the CHECK would produce a migration
1535
+ * that cannot run. Apply the enum addition by itself first, then add or update
1536
+ * the CHECK constraint in a later migration.
1537
+ *
1538
+ * Partial-index predicates and index expressions are intentionally not
1539
+ * canonicalised here; non-strict diffs compare them by raw string, and strict
1540
+ * diffs reject them.
1541
+ */
1542
+ declare function comparePgsqlDatabaseSchema(adapter: PgsqlAdapter, desired: ModelIR, options?: ComparePgsqlDatabaseSchemaOptions): Promise<SchemaDiff>;
1543
+ declare function assertNoRepeatedExpressionSurfaceDrift(previouslyAppliedDiff: SchemaDiff, currentDiff: SchemaDiff, rawCheckExpressionSurfaces?: ReadonlySet<string>): void;
1544
+
1545
+ /**
1546
+ * Migration SQL Generator (DDL-PROV Block 1)
1547
+ *
1548
+ * Generates ordered SQL statements from a SchemaDiff.
1549
+ * Statements are topologically sorted: DROP constraints → DROP objects → CREATE objects → ADD constraints.
1550
+ *
1551
+ * @module migration-sql
1552
+ */
1553
+
1554
+ interface MigrationSQLOptions {
1555
+ /**
1556
+ * Schema namespace (default: none — unqualified).
1557
+ * Required when emitted migration SQL would otherwise mix non-default
1558
+ * target-scoped custom types/enums with unqualified table SQL.
1559
+ */
1560
+ readonly schemaName?: string;
1561
+ /** Whether to include destructive changes (drops) */
1562
+ readonly includeDestructive?: boolean;
1563
+ /** Automatically create indexes on FK columns for new tables (default: true) */
1564
+ readonly fkAutoIndex?: boolean;
1565
+ /** Dialect capabilities — migration SQL for unsupported features will be filtered */
1566
+ readonly dialectCapabilities?: DialectCapabilities;
1567
+ }
1568
+ /**
1569
+ * Generate ordered SQL statements from a SchemaDiff.
1570
+ *
1571
+ * Topological order:
1572
+ * 0. DROP FK/CHECK constraints (must drop before referenced tables)
1573
+ * 1. DROP indexes
1574
+ * 2. DROP columns
1575
+ * 3. DROP primary keys
1576
+ * 4. DROP tables, DROP ENUMs
1577
+ * 5. CREATE ENUMs (must exist before tables that use them)
1578
+ * 6. CREATE tables
1579
+ * 7. ADD columns
1580
+ * 8. ALTER columns (type, nullable, default)
1581
+ * 9. ADD primary keys / column UNIQUE constraints
1582
+ * 10. ADD FK constraints (must add after referenced tables exist)
1583
+ * 11. ALTER FK (drop + re-add)
1584
+ * 12. CREATE indexes
1585
+ * 13. ADD CHECK constraints
1586
+ * 14. ALTER ENUM ADD VALUE (must be last — has transaction visibility caveats in PG)
1587
+ * 15. COMMENT ON TABLE / COLUMN (very last)
1588
+ */
1589
+ declare function generateMigrationSQL(diff: SchemaDiff, options?: MigrationSQLOptions): readonly string[];
1590
+ /**
1591
+ * Generate ordered DOWN SQL statements from a SchemaDiff.
1592
+ *
1593
+ * Reverses the topological order used in UP migrations:
1594
+ * phases run in descending order (11, 10, 9, ..., 0).
1595
+ *
1596
+ * Irreversible changes (drops that lose data) produce SQL WARNING comments.
1597
+ */
1598
+ declare function generateDownSQL(diff: SchemaDiff, options?: MigrationSQLOptions): readonly string[];
1599
+
1600
+ /**
1601
+ * Migration File Format v2 — UP + DOWN sections.
1602
+ *
1603
+ * File format:
1604
+ * -- dbsp:destructive: true|false
1605
+ * <UP statements>;
1606
+ * -- DOWN
1607
+ * <DOWN statements>;
1608
+ *
1609
+ * The separator `-- DOWN` must be on its own line (SC-25).
1610
+ *
1611
+ * @module ddl/migration-file
1612
+ */
1613
+
1614
+ /**
1615
+ * Result of parsing a migration file's UP/DOWN sections.
1616
+ */
1617
+ interface ParsedMigrationFile {
1618
+ readonly upStatements: readonly string[];
1619
+ readonly downStatements: readonly string[];
1620
+ readonly hasDown: boolean;
1621
+ readonly destructive?: boolean | undefined;
1622
+ }
1623
+ /**
1624
+ * Generate a migration file content with UP and DOWN sections.
1625
+ */
1626
+ declare function generateMigrationFile(diff: SchemaDiff, options?: MigrationSQLOptions & {
1627
+ name?: string;
1628
+ }): string;
1629
+ /**
1630
+ * Parse a migration file into UP and DOWN sections.
1631
+ * Separator: `^\s*-- DOWN\s*$` (strict regex, own line only — SC-25)
1632
+ */
1633
+ declare function parseMigrationFile(content: string): ParsedMigrationFile;
1634
+ /**
1635
+ * Check if SQL statements contain destructive operations.
1636
+ * Destructive: DROP TABLE, DROP COLUMN, lossy ALTER COLUMN TYPE
1637
+ */
1638
+ declare function isDestructiveDown(downStatements: readonly string[]): boolean;
1639
+
1640
+ /**
1641
+ * Migration Tracker — `_dbsp_migrations` table CRUD.
1642
+ *
1643
+ * Manages the tracking table that records which migrations
1644
+ * have been applied to a database.
1645
+ */
1646
+
1647
+ interface MigrationRecord {
1648
+ /** Migration filename (e.g., "0001_create_users.sql") */
1649
+ readonly name: string;
1650
+ /** SHA-256 checksum of the migration file content */
1651
+ readonly checksum: string;
1652
+ /** When the migration was applied */
1653
+ readonly appliedAt: Date;
1654
+ /** Schema version at time of this migration */
1655
+ readonly schemaVersion: number;
1656
+ /** Whether this migration contains destructive changes */
1657
+ readonly destructive: boolean;
1658
+ }
1659
+ /**
1660
+ * Execute a callback under an advisory lock using a dedicated client.
1661
+ * The lock is held for the duration of the callback.
1662
+ * The client is released (and lock freed) after the callback completes.
1663
+ */
1664
+ declare function withMigrationLock<T>(pool: Pool, fn: (client: PoolClient) => Promise<T>): Promise<T>;
1665
+ /**
1666
+ * Ensure the migrations tracking table exists.
1667
+ * Auto-migrates existing tables that lack `schema_version`/`destructive` columns,
1668
+ * and backfills `schema_version` by `applied_at` order for rows still at 0.
1669
+ */
1670
+ declare function ensureMigrationsTable(pool: Pool): Promise<void>;
1671
+ /**
1672
+ * Get all applied migrations, ordered by name.
1673
+ */
1674
+ declare function getAppliedMigrations(pool: Pool): Promise<readonly MigrationRecord[]>;
1675
+ /**
1676
+ * Record a migration as applied.
1677
+ */
1678
+ declare function recordMigration(pool: Pool, name: string, checksum: string, schemaVersion: number, destructive: boolean): Promise<void>;
1679
+ /**
1680
+ * Check if a specific migration has been applied.
1681
+ */
1682
+ declare function isMigrationApplied(pool: Pool, name: string): Promise<boolean>;
1683
+ /**
1684
+ * Get the next schema version number (max + 1, or 1 if no migrations).
1685
+ */
1686
+ declare function getNextSchemaVersion(pool: Pool): Promise<number>;
1687
+ /**
1688
+ * Remove a migration record (for rollback).
1689
+ */
1690
+ declare function removeMigrationRecord(pool: Pool, name: string): Promise<void>;
1691
+
1692
+ /**
1693
+ * Type Mapping - Maps ModelIR ColumnType to PostgreSQL data types
1694
+ *
1695
+ * Supports both manual schemas and introspected schemas (preserving originalDbType).
1696
+ * Handles auto-increment via SERIAL/BIGSERIAL types.
1697
+ *
1698
+ * @module ddl/type-mapping
1699
+ */
1700
+
1701
+ /**
1702
+ * Map ColumnType to PostgreSQL data type string.
1703
+ *
1704
+ * Uses originalDbType if available (from introspection), otherwise
1705
+ * falls back to reasonable PostgreSQL defaults.
1706
+ *
1707
+ * @param col - Column definition from ModelIR
1708
+ * @returns PostgreSQL type string (e.g., 'VARCHAR(255)', 'SERIAL', 'JSONB')
1709
+ */
1710
+ declare function mapColumnType(col: ColumnIR, targetSchema?: string): string;
1711
+ /**
1712
+ * Map OnDeleteAction to PostgreSQL syntax.
1713
+ */
1714
+ declare function mapOnDeleteAction(action?: string): string;
1715
+
1716
+ /**
1717
+ * EXPLAIN Statement Compiler
1718
+ *
1719
+ * Generates PostgreSQL EXPLAIN statements with various options.
1720
+ * Supports:
1721
+ * - ANALYZE (execute and show actual run times)
1722
+ * - FORMAT (text, json, xml, yaml)
1723
+ * - VERBOSE, COSTS, BUFFERS, TIMING, SETTINGS
1724
+ */
1725
+
1726
+ /**
1727
+ * Output format for EXPLAIN results.
1728
+ */
1729
+ type ExplainFormat = 'text' | 'json' | 'xml' | 'yaml';
1730
+ /**
1731
+ * Options for EXPLAIN statement.
1732
+ */
1733
+ interface ExplainOptions {
1734
+ /** Execute the query and show actual run times */
1735
+ analyze?: boolean;
1736
+ /** Show more detailed output */
1737
+ verbose?: boolean;
1738
+ /** Show cost estimates (default: true) */
1739
+ costs?: boolean;
1740
+ /** Show buffer usage (requires analyze) */
1741
+ buffers?: boolean;
1742
+ /** Show actual timing (requires analyze) */
1743
+ timing?: boolean;
1744
+ /** Show non-default settings */
1745
+ settings?: boolean;
1746
+ /** Output format */
1747
+ format?: ExplainFormat;
1748
+ }
1749
+ /**
1750
+ * Build an EXPLAIN statement wrapping a query.
1751
+ *
1752
+ * @param query - The query to explain (SelectStmt, InsertStmt, etc.)
1753
+ * @param options - EXPLAIN options
1754
+ * @returns ExplainStmt AST node
1755
+ *
1756
+ * @example
1757
+ * ```typescript
1758
+ * const selectAst = { SelectStmt: { ... } };
1759
+ * const explainAst = buildExplain(selectAst, { analyze: true, format: 'json' });
1760
+ * // Produces: EXPLAIN (ANALYZE, FORMAT JSON) SELECT ...
1761
+ * ```
1762
+ */
1763
+ declare function buildExplain(query: Node, options?: ExplainOptions): Node;
1764
+ /**
1765
+ * Build EXPLAIN ANALYZE with JSON format (common pattern).
1766
+ *
1767
+ * @param query - The query to explain
1768
+ * @returns ExplainStmt with ANALYZE and JSON format
1769
+ */
1770
+ declare function buildExplainAnalyzeJson(query: Node): Node;
1771
+ /**
1772
+ * Build simple EXPLAIN (plan only, no execution).
1773
+ *
1774
+ * @param query - The query to explain
1775
+ * @returns ExplainStmt with default options
1776
+ */
1777
+ declare function buildExplainPlan(query: Node): Node;
1778
+ /**
1779
+ * Build verbose EXPLAIN with costs and buffers.
1780
+ *
1781
+ * @param query - The query to explain
1782
+ * @returns ExplainStmt with verbose options
1783
+ */
1784
+ declare function buildExplainVerbose(query: Node): Node;
1785
+ /**
1786
+ * Parse EXPLAIN JSON output to get execution statistics.
1787
+ *
1788
+ * @param jsonOutput - The JSON string from EXPLAIN (ANALYZE, FORMAT JSON)
1789
+ * @returns Parsed plan with execution statistics
1790
+ */
1791
+ declare function parseExplainJson(jsonOutput: string): ExplainPlan[];
1792
+ /**
1793
+ * Parsed EXPLAIN plan structure (simplified).
1794
+ */
1795
+ interface ExplainPlan {
1796
+ Plan: {
1797
+ 'Node Type': string;
1798
+ 'Relation Name'?: string;
1799
+ Alias?: string;
1800
+ 'Startup Cost'?: number;
1801
+ 'Total Cost'?: number;
1802
+ 'Plan Rows'?: number;
1803
+ 'Plan Width'?: number;
1804
+ 'Actual Startup Time'?: number;
1805
+ 'Actual Total Time'?: number;
1806
+ 'Actual Rows'?: number;
1807
+ 'Actual Loops'?: number;
1808
+ Plans?: ExplainPlan['Plan'][];
1809
+ };
1810
+ 'Planning Time'?: number;
1811
+ 'Execution Time'?: number;
1812
+ Triggers?: unknown[];
1813
+ }
1814
+ /**
1815
+ * Extract total execution time from EXPLAIN ANALYZE JSON output.
1816
+ *
1817
+ * @param plans - Parsed EXPLAIN plans
1818
+ * @returns Total execution time in milliseconds
1819
+ */
1820
+ declare function getTotalExecutionTime(plans: ExplainPlan[]): number;
1821
+ /**
1822
+ * Extract row counts from EXPLAIN ANALYZE JSON output.
1823
+ *
1824
+ * @param plans - Parsed EXPLAIN plans
1825
+ * @returns Object with estimated and actual row counts
1826
+ */
1827
+ declare function getRowEstimates(plans: ExplainPlan[]): {
1828
+ estimated: number;
1829
+ actual: number;
1830
+ };
1831
+
1832
+ interface CheckConstraintCanonicalizationWarning {
1833
+ readonly table: string;
1834
+ readonly constraint: string;
1835
+ readonly message: string;
1836
+ readonly cause: unknown;
1837
+ }
1838
+ interface CanonicalizeCheckConstraintsOptions {
1839
+ /** Database schema that owns the target tables and target-scoped enum types. */
1840
+ readonly schemaName?: string;
1841
+ /** Naming convention used when matching desired table names to DB table names. */
1842
+ readonly dbCasing?: DbCasing;
1843
+ /** Called when PostgreSQL CHECK canonicalisation fails and raw comparison is used. */
1844
+ readonly onWarning?: (warning: CheckConstraintCanonicalizationWarning) => void;
1845
+ /** Throw instead of falling back to raw comparison when canonicalisation fails. */
1846
+ readonly requireCanonicalization?: boolean;
1847
+ }
1848
+ type PgsqlCanonicalizationScope = Pick<Adapter, 'executeRaw' | 'transaction'>;
1849
+ declare class CheckConstraintCanonicalizationError extends Error {
1850
+ readonly table: string;
1851
+ readonly constraints: readonly string[];
1852
+ readonly cause: unknown;
1853
+ constructor(table: string, constraints: readonly string[], cause: unknown);
1854
+ }
1855
+ /**
1856
+ * Canonicalise PostgreSQL CHECK constraint expressions in a desired model.
1857
+ *
1858
+ * CHECK constraints are canonicalised by creating a transaction-local temp table
1859
+ * with the desired table's column definitions (borrowing live database type
1860
+ * detail for existing columns when the desired model omits it), adding the
1861
+ * authored CHECK constraints to that temp table, and reading PostgreSQL's
1862
+ * `pg_get_constraintdef()` rendering. The caller must provide a rollback-only
1863
+ * scratch scope; that rollback is cleanup for dbsp-created scratch objects, not
1864
+ * a sandbox for arbitrary session effects.
1865
+ * Missing desired enum types are also created inside that scratch scope before
1866
+ * scratch tables.
1867
+ * Scratch columns include only names and types; defaults, identity, uniqueness,
1868
+ * nullability, and other table-shape clauses are deliberately omitted.
1869
+ *
1870
+ * The returned expression is the full `CHECK (...)` clause. Bare authored
1871
+ * predicates are accepted and become full CHECK clauses.
1872
+ *
1873
+ * PostgreSQL does not allow an enum value added by `ALTER TYPE ... ADD VALUE`
1874
+ * to be used in the same transaction that added it. Because dbsp currently
1875
+ * emits and applies each migration in one transaction, the live diff layer
1876
+ * deliberately refuses CHECK constraints that fall back while the same diff adds
1877
+ * a plausibly referenced enum value. Splitting that into multiple transaction
1878
+ * phases is a separate migration-runner feature.
1879
+ *
1880
+ * This does not canonicalise partial-index predicates or index expressions, so
1881
+ * those surfaces may still compare by raw text in non-strict diffs.
1882
+ */
1883
+ declare function canonicalizeCheckConstraints(adapter: PgsqlCanonicalizationScope, desired: ModelIR, dbModel: ModelIR, options?: CanonicalizeCheckConstraintsOptions): Promise<ModelIR>;
1884
+
1885
+ /**
1886
+ * ParadeDB Extension Wrappers
1887
+ *
1888
+ * Type-safe query builders for ParadeDB BM25 full-text search.
1889
+ * All functions return ExpressionRef instances that can be used in:
1890
+ * - SELECT: .column(score('id').as('score'))
1891
+ * - WHERE: .where(bm25Search('symbols', query, { name: 3.0, doc: 1.0 }))
1892
+ * - ORDER BY: .orderBy(score('id'), 'desc')
1893
+ *
1894
+ * @remarks
1895
+ * ParadeDB functions accept both named and positional arguments.
1896
+ * This module uses named args via namedArg() for parse(), which produces:
1897
+ * paradedb.parse(field => 'field_name', query_string => $1)
1898
+ * Named parameter syntax is supported via the NamedArgExpressionIntent (EXT-NAMED-PARAMS).
1899
+ */
1900
+
1901
+ /**
1902
+ * BM25 relevance score for a row.
1903
+ *
1904
+ * Use in SELECT and ORDER BY to retrieve and sort by full-text relevance.
1905
+ * Requires a BM25 index on the table.
1906
+ *
1907
+ * @param keyField - The key field of the BM25 index (typically the primary key, e.g. 'id')
1908
+ * @returns ExpressionRef that compiles to: paradedb.score("keyField")
1909
+ *
1910
+ * @example
1911
+ * orm.select('symbols').column(score('id').as('score')).orderBy(score('id'), 'desc')
1912
+ * // → paradedb.score("id") AS "score"
1913
+ */
1914
+ declare function score(keyField: string): ExpressionRef;
1915
+ /**
1916
+ * Parse a single-field BM25 query expression.
1917
+ *
1918
+ * Compiles to: paradedb.parse(field => 'field_name', query_string => $N)
1919
+ *
1920
+ * @param field - Column name to search in (must be indexed in the BM25 index)
1921
+ * @param query - Query string value (will be bound as a parameter)
1922
+ * @returns ExpressionRef for use with boost() or booleanSearch()
1923
+ *
1924
+ * @example
1925
+ * parse('name', 'hello world')
1926
+ * // → paradedb.parse(field => 'name', query_string => $1)
1927
+ */
1928
+ declare function parse(field: string, query: ExpressionRef | unknown): ExpressionRef;
1929
+ /**
1930
+ * Apply a boost multiplier to a BM25 sub-expression.
1931
+ *
1932
+ * Compiles to: paradedb.boost(factor, expr)
1933
+ *
1934
+ * @param factor - Boost multiplier (e.g. 3.0 for 3x weight)
1935
+ * @param expr - Expression to boost (typically a parse() call)
1936
+ * @returns ExpressionRef for use with booleanSearch()
1937
+ *
1938
+ * @example
1939
+ * boost(3.0, parse('name', 'hello'))
1940
+ * // → paradedb.boost(3.0, paradedb.parse('name', $1))
1941
+ */
1942
+ declare function boost(factor: number, expr: ExpressionRef): ExpressionRef;
1943
+ /**
1944
+ * Combine multiple BM25 sub-expressions with boolean OR logic.
1945
+ *
1946
+ * Compiles to: paradedb.boolean(expr1, expr2, ...)
1947
+ *
1948
+ * @param exprs - One or more sub-expressions (typically boost() calls)
1949
+ * @returns ExpressionRef for use on the right side of the @@@ operator
1950
+ *
1951
+ * @example
1952
+ * booleanSearch([boost(3.0, parse('name', q)), boost(1.0, parse('doc', q))])
1953
+ * // → paradedb.boolean(paradedb.boost(3.0, ...), paradedb.boost(1.0, ...))
1954
+ */
1955
+ /**
1956
+ * Combine multiple BM25 sub-expressions with boolean OR logic.
1957
+ *
1958
+ * Compiles to: paradedb.boolean(should => ARRAY[expr1, expr2, ...])
1959
+ *
1960
+ * @param exprs - One or more sub-expressions (typically boost() calls)
1961
+ * @returns ExpressionRef for use on the right side of the @@@ operator
1962
+ *
1963
+ * @example
1964
+ * booleanSearch([boost(3.0, parse('name', q)), boost(1.0, parse('doc', q))])
1965
+ * // → paradedb.boolean(should => ARRAY[paradedb.boost(3.0, ...), paradedb.boost(1.0, ...)])
1966
+ */
1967
+ declare function booleanSearch(exprs: ExpressionRef[]): ExpressionRef;
1968
+ /**
1969
+ * Full BM25 multi-field search with per-field boost weights.
1970
+ *
1971
+ * Produces: table @@@ paradedb.boolean(boost1, boost2, ...)
1972
+ *
1973
+ * Each field in `fieldBoosts` generates a `paradedb.boost(weight, paradedb.parse(field, $N))`
1974
+ * sub-expression. The same query string is used for all fields (single parameter binding).
1975
+ *
1976
+ * @param table - Table alias for the left side of the @@@ operator
1977
+ * @param query - Query string (bound as a single $N parameter, shared across all fields)
1978
+ * @param fieldBoosts - Map of column name → boost weight
1979
+ * @returns ExpressionRef for use in .where()
1980
+ *
1981
+ * @example
1982
+ * bm25Search('s', searchTerm, {
1983
+ * name_searchable: 3.0,
1984
+ * name: 1.0,
1985
+ * signature: 1.5,
1986
+ * doc_searchable: 1.0,
1987
+ * })
1988
+ * // → s @@@ paradedb.boolean(
1989
+ * // paradedb.boost(3.0, paradedb.parse('name_searchable', $1)),
1990
+ * // paradedb.boost(1.0, paradedb.parse('name', $1)),
1991
+ * // paradedb.boost(1.5, paradedb.parse('signature', $1)),
1992
+ * // paradedb.boost(1.0, paradedb.parse('doc_searchable', $1))
1993
+ * // )
1994
+ *
1995
+ * @remarks
1996
+ * The query parameter is shared: all parse() calls reference the same $N slot.
1997
+ * If you need different query strings per field, compose parse()/boost()/booleanSearch() manually.
1998
+ *
1999
+ * @remarks
2000
+ * ParadeDB's boolean() function accepts both positional args and the named
2001
+ * `should => ARRAY[...]` syntax. This wrapper uses positional args.
2002
+ * Named parameter syntax is deferred to EXT-NAMED-PARAMS.
2003
+ */
2004
+ declare function bm25Search(table: string, query: unknown, fieldBoosts: Record<string, number>): ExpressionRef;
2005
+
2006
+ /**
2007
+ * PostgreSQL built-in function helpers.
2008
+ *
2009
+ * Thin wrappers around core expression primitives for common PostgreSQL functions.
2010
+ * Same pattern as pgvector.ts and paradedb.ts.
2011
+ */
2012
+
2013
+ /**
2014
+ * Generate a series of values: generate_series(start, stop[, step])
2015
+ *
2016
+ * Returns a set of values from start to stop (inclusive), with an optional step.
2017
+ * Commonly used with CTE for batch operations.
2018
+ *
2019
+ * @example generateSeries(1, 100) → generate_series(1, 100)
2020
+ * @example generateSeries(0, 50, 5) → generate_series(0, 50, 5)
2021
+ */
2022
+ declare function generateSeries(start: number, stop: number, step?: number): ExpressionRef;
2023
+ /**
2024
+ * Get next value from a sequence: nextval('sequence_name')
2025
+ *
2026
+ * @example nextval('order_id_seq') → nextval('order_id_seq')
2027
+ */
2028
+ declare function nextval(sequenceName: string): ExpressionRef;
2029
+
2030
+ /**
2031
+ * pgvector Extension Wrappers
2032
+ *
2033
+ * Type-safe query builders for pgvector distance operators.
2034
+ * All functions return ExpressionRef instances that can be used in:
2035
+ * - SELECT: .column(cosineDistance('vector', qv).as('score'))
2036
+ * - WHERE: .where(cosineDistance('vector', qv).gte(0.5))
2037
+ * - ORDER BY: .orderBy(rawDistance('vector', qv), 'asc')
2038
+ */
2039
+
2040
+ /**
2041
+ * Cosine similarity: 1 - (col <=> vector)
2042
+ *
2043
+ * Score in [0, 1], higher = more similar.
2044
+ * Use in SELECT to get a similarity score.
2045
+ *
2046
+ * @example
2047
+ * orm.select('embeddings').column(cosineDistance('vector', qv).as('score'))
2048
+ */
2049
+ declare function cosineDistance(column: string, vector: number[]): ExpressionRef;
2050
+ /**
2051
+ * Raw cosine distance: col <=> vector
2052
+ *
2053
+ * Lower = closer. Index-friendly — use in ORDER BY for ANN search.
2054
+ * Do NOT use in SELECT as a similarity score (lower = closer is counterintuitive).
2055
+ *
2056
+ * @example
2057
+ * orm.select('embeddings').orderBy(rawDistance('vector', qv), 'asc')
2058
+ */
2059
+ declare function rawDistance(column: string, vector: number[]): ExpressionRef;
2060
+ /**
2061
+ * L2 (Euclidean) distance: col <-> vector
2062
+ *
2063
+ * @example
2064
+ * orm.select('embeddings').orderBy(l2Distance('vector', qv), 'asc')
2065
+ */
2066
+ declare function l2Distance(column: string, vector: number[]): ExpressionRef;
2067
+ /**
2068
+ * Inner product distance: col <#> vector (negative inner product)
2069
+ *
2070
+ * For maximum inner product search: ORDER BY innerProduct('vector', qv) ASC.
2071
+ *
2072
+ * @example
2073
+ * orm.select('embeddings').orderBy(innerProduct('vector', qv), 'asc')
2074
+ */
2075
+ declare function innerProduct(column: string, vector: number[]): ExpressionRef;
2076
+ /**
2077
+ * Get the number of dimensions of a vector column: vector_dims(col)
2078
+ *
2079
+ * Returns an integer — the dimension count of the stored vector.
2080
+ * Useful for sanity-checking that embeddings match the expected model dimension.
2081
+ *
2082
+ * @example
2083
+ * orm.from(embeddings).columns([vectorDims('vector').as('dim')]).first()
2084
+ * // → SELECT vector_dims("t0"."vector") AS "dim" FROM "embeddings" AS "t0"
2085
+ */
2086
+ declare function vectorDims(column: string): ExpressionRef;
2087
+
2088
+ /**
2089
+ * Mutation Compiler
2090
+ *
2091
+ * Compiles INSERT, UPDATE, and DELETE statements from plan decisions.
2092
+ * Supports:
2093
+ * - INSERT with values/from subquery
2094
+ * - INSERT with RETURNING
2095
+ * - UPDATE with SET and WHERE
2096
+ * - DELETE with WHERE
2097
+ * - RETURNING clause for all mutations
2098
+ */
2099
+
2100
+ /**
2101
+ * Configuration for INSERT compilation
2102
+ */
2103
+ interface InsertConfig {
2104
+ /** Table to insert into */
2105
+ table: string;
2106
+ /** Columns to insert */
2107
+ columns: string[];
2108
+ /** Values for each column (array of rows) */
2109
+ values: unknown[][];
2110
+ /** Columns to return (RETURNING clause) */
2111
+ returning?: string[];
2112
+ /** Alias-aware RETURNING projection items */
2113
+ returningItems?: readonly MutationReturningItem[];
2114
+ /** Subquery for INSERT ... SELECT */
2115
+ selectQuery?: Node;
2116
+ /** Column database types for type-cast emission (e.g. range types) */
2117
+ columnTypes?: Record<string, string>;
2118
+ }
2119
+ /**
2120
+ * Configuration for UPDATE compilation
2121
+ */
2122
+ interface UpdateConfig {
2123
+ /** Table to update */
2124
+ table: string;
2125
+ /** Column-value pairs to set */
2126
+ set: {
2127
+ column: string;
2128
+ value: unknown;
2129
+ }[];
2130
+ /** WHERE conditions */
2131
+ where?: Decision[];
2132
+ /** Columns to return (RETURNING clause) */
2133
+ returning?: string[];
2134
+ /** Alias-aware RETURNING projection items */
2135
+ returningItems?: readonly MutationReturningItem[];
2136
+ /** Column database types for type-cast emission (e.g. range types) */
2137
+ columnTypes?: Record<string, string>;
2138
+ }
2139
+ /**
2140
+ * Configuration for DELETE compilation
2141
+ */
2142
+ interface DeleteConfig {
2143
+ /** Table to delete from */
2144
+ table: string;
2145
+ /** WHERE conditions */
2146
+ where?: Decision[];
2147
+ /** Columns to return (RETURNING clause) */
2148
+ returning?: string[];
2149
+ /** Alias-aware RETURNING projection items */
2150
+ returningItems?: readonly MutationReturningItem[];
2151
+ }
2152
+ /**
2153
+ * Compile an INSERT statement from configuration.
2154
+ */
2155
+ declare function compileInsert(config: InsertConfig, ctx: CompilerContext, state: CompilerState): Node;
2156
+ /**
2157
+ * Compile an UPDATE statement from configuration.
2158
+ */
2159
+ declare function compileUpdate(config: UpdateConfig, ctx: CompilerContext, state: CompilerState): Node;
2160
+ declare function compileDelete(config: DeleteConfig, ctx: CompilerContext, state: CompilerState): Node;
2161
+ /**
2162
+ * Compile a mutation decision to AST.
2163
+ * Determines mutation type from decision.type and delegates.
2164
+ */
2165
+ declare function compileMutation(decision: Decision, ctx: CompilerContext, state: CompilerState): Node;
2166
+
2167
+ /**
2168
+ * Upsert (INSERT ... ON CONFLICT) Compiler
2169
+ *
2170
+ * Compiles UPSERT statements with ON CONFLICT handling.
2171
+ * Supports:
2172
+ * - ON CONFLICT DO NOTHING
2173
+ * - ON CONFLICT DO UPDATE SET ...
2174
+ * - Conflict target (columns or constraint name)
2175
+ * - WHERE clause for conflict resolution
2176
+ */
2177
+
2178
+ /**
2179
+ * Conflict resolution strategy
2180
+ */
2181
+ type ConflictAction = 'nothing' | 'update';
2182
+ /**
2183
+ * Conflict target specification
2184
+ */
2185
+ interface ConflictTarget {
2186
+ /** Column names that form the unique constraint */
2187
+ columns?: string[];
2188
+ /** Named constraint */
2189
+ constraint?: string;
2190
+ /** WHERE clause for partial index */
2191
+ where?: Decision[];
2192
+ }
2193
+ /**
2194
+ * Configuration for UPSERT compilation
2195
+ */
2196
+ interface UpsertConfig {
2197
+ /** Table to upsert into */
2198
+ table: string;
2199
+ /** Columns to insert */
2200
+ columns: string[];
2201
+ /** Values for each column (array of rows) */
2202
+ values: unknown[][];
2203
+ /** Conflict target (unique columns or constraint) */
2204
+ conflictTarget: ConflictTarget;
2205
+ /** What to do on conflict */
2206
+ conflictAction: ConflictAction;
2207
+ /** Columns to update on conflict (for 'update' action) */
2208
+ updateColumns?: string[];
2209
+ /** Optional WHERE clause for ON CONFLICT DO UPDATE */
2210
+ actionWhere?: Decision[];
2211
+ /** Optional direct WHERE intent for ON CONFLICT DO UPDATE */
2212
+ actionWhereIntent?: WhereIntent;
2213
+ /** Compile the direct action WHERE intent using the caller's WHERE compiler */
2214
+ compileActionWhere?: (where: WhereIntent, state: CompilerState) => Node;
2215
+ /** Use EXCLUDED.column for update values (default: true) */
2216
+ useExcluded?: boolean;
2217
+ /** Columns to return (RETURNING clause) */
2218
+ returning?: string[];
2219
+ /** Alias-aware RETURNING projection items */
2220
+ returningItems?: readonly MutationReturningItem[];
2221
+ /** Optional column type hints for unnest casting (schema-driven) */
2222
+ columnTypes?: Record<string, string>;
1933
2223
  /**
1934
- * Return the total storage size of a table in bytes (includes indexes and TOAST).
2224
+ * Raw SQL expressions for specific update columns.
2225
+ * These are injected verbatim into the ON CONFLICT DO UPDATE SET clause.
2226
+ * Keys are logical column names (before naming plugin), values are raw SQL fragments.
1935
2227
  *
1936
- * The table name is a SQL identifier — it is double-quoted, not parameterized,
1937
- * because PostgreSQL does not allow parameterized table names in FROM clauses.
2228
+ * @warning SECURITY: fragments are inserted without parameterization.
2229
+ * Only use with hardcoded expressions. Never with user input.
1938
2230
  *
1939
- * @param table - Table name
1940
- * @param schema - Schema name (defaults to adapter schema or 'public')
1941
- */
1942
- storageSize(table: string, schema?: string): Promise<number>;
1943
- /**
1944
- * Generate SQL for TRUNCATE TABLE.
1945
- * Implements TableDDLGeneratorAdapter.generateTruncate.
1946
- */
1947
- generateTruncate(table: string, schema?: string, options?: TruncateOptions): string;
1948
- /**
1949
- * Generate SQL for VACUUM.
1950
- * Implements TableDDLGeneratorAdapter.generateVacuum.
1951
- */
1952
- generateVacuum(table: string, schema?: string, options?: VacuumOptions): string;
1953
- /**
1954
- * Generate SQL for ALTER TABLE ... ALTER COLUMN.
1955
- * Implements TableDDLGeneratorAdapter.generateAlterColumn.
1956
- */
1957
- generateAlterColumn(table: string, column: string, options: AlterColumnOptions, schema?: string): string;
1958
- /**
1959
- * Generate SQL for CREATE INDEX.
1960
- * Implements TableDDLGeneratorAdapter.generateCreateIndex.
1961
- */
1962
- generateCreateIndex(table: string, options: CreateIndexOptions, schema?: string): string;
1963
- /**
1964
- * Generate SQL for DROP INDEX.
1965
- * Implements TableDDLGeneratorAdapter.generateDropIndex.
1966
- */
1967
- generateDropIndex(name: string, options?: DropIndexOptions): string;
1968
- /**
1969
- * Validate an identifier (table name, column name, schema name).
2231
+ * @example { last_parsed: 'now()', count: 'excluded.count + 1' }
1970
2232
  */
1971
- validateIdentifier(value: string, type: string): void;
2233
+ updateExpressions?: Record<string, string>;
1972
2234
  }
1973
2235
  /**
1974
- * Create a PgsqlAdapter from a pg Pool instance.
1975
- *
1976
- * @param pool - pg Pool instance
1977
- * @param options - Optional configuration
1978
- * @returns A new PgsqlAdapter instance
1979
- *
1980
- * @example
1981
- * ```typescript
1982
- * import { Pool } from 'pg';
1983
- * import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';
2236
+ * Build ON CONFLICT clause for INSERT statement.
2237
+ */
2238
+ declare function buildOnConflictClause(config: UpsertConfig, ctx: CompilerContext, state: CompilerState): OnConflictClause;
2239
+ /**
2240
+ * Compile a complete UPSERT statement.
2241
+ */
2242
+ declare function compileUpsert(config: UpsertConfig, ctx: CompilerContext, state: CompilerState): Node;
2243
+ /**
2244
+ * Build EXCLUDED.column reference.
2245
+ * EXCLUDED is a special table alias in ON CONFLICT ... DO UPDATE
2246
+ * that refers to the row that would have been inserted.
2247
+ */
2248
+ declare function excludedRef(column: string, naming: {
2249
+ toDatabase: (s: string) => string;
2250
+ }): Node;
2251
+ /**
2252
+ * Build conditional update using COALESCE.
1984
2253
  *
1985
- * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
1986
- * const adapter = createPgsqlAdapter(pool);
2254
+ * Produces: COALESCE(EXCLUDED.col, table.col)
2255
+ * This keeps existing value if new value is NULL.
2256
+ */
2257
+ declare function conditionalUpdate(column: string, table: string, ctx: CompilerContext): Node;
2258
+
2259
+ /**
2260
+ * @module naming
2261
+ * Utilities for resolving database names to logical model names.
1987
2262
  *
1988
- * // With naming convention
1989
- * const adapter = createPgsqlAdapter(pool, { dbCasing: 'snake_case' });
1990
- * ```
2263
+ * The ModelIR.getTable() method expects logical (camelCase) names,
2264
+ * but the adapter often works with database (snake_case) names.
2265
+ * This module bridges that gap.
1991
2266
  */
1992
- declare function createPgsqlAdapter<DB = unknown>(pool: Pool, options?: PgsqlAdapterOptions): PgsqlAdapter<DB>;
2267
+
1993
2268
  /**
1994
- * Creates a compile-only PgsqlAdapter for SQL generation without a database connection.
2269
+ * Resolve a database table name to the corresponding logical model name.
1995
2270
  *
1996
- * All compilation methods (compile, compileInsert, etc.), createDump(), and generateDDL()
1997
- * work normally. Execution methods (execute, stream, transaction, etc.) throw an error.
2271
+ * Converts the DB name using the naming convention, then looks it up in the model.
2272
+ * Falls back to exact match if conversion doesn't find a match.
2273
+ *
2274
+ * @param model - The model IR to search in
2275
+ * @param dbName - Database table name (e.g. "post_comments")
2276
+ * @param convention - Naming convention used by the adapter
2277
+ * @returns The logical table name if found, undefined otherwise
1998
2278
  *
1999
2279
  * @example
2000
2280
  * ```typescript
2001
- * import { createPgsqlCompileOnlyAdapter } from '@dbsp/adapter-pgsql';
2002
- * import { createOrm } from '@dbsp/core';
2003
- *
2004
- * const adapter = createPgsqlCompileOnlyAdapter();
2005
- * const orm = createOrm({ model, adapter });
2006
- * const dump = await orm.select('users').dump();
2007
- * console.log(dump.sql);
2281
+ * // With camelCase convention:
2282
+ * resolveLogicalName(model, "post_comments", "camelCase") // → "postComments"
2283
+ * resolveLogicalName(model, "posts", "camelCase") // → "posts"
2284
+ * resolveLogicalName(model, "unknown", "camelCase") // → undefined
2008
2285
  * ```
2009
2286
  */
2010
- declare function createPgsqlCompileOnlyAdapter<DB = unknown>(options?: PgsqlAdapterOptions): CompileOnlyAdapter;
2287
+ declare function resolveLogicalName(model: ModelIR, dbName: string, casing: DbCasing): string | undefined;
2288
+
2289
+ /**
2290
+ * ParamRef validation and helpers for PostgreSQL AST
2291
+ *
2292
+ * ParamRef nodes represent parameterized query placeholders ($1, $2, etc.)
2293
+ * This module provides validation and creation helpers for safe AST construction.
2294
+ */
2295
+
2296
+ /**
2297
+ * Validation result for ParamRef nodes
2298
+ */
2299
+ interface ParamRefValidationResult {
2300
+ valid: boolean;
2301
+ errors: string[];
2302
+ }
2303
+ /**
2304
+ * Validates a ParamRef node
2305
+ *
2306
+ * Rules:
2307
+ * - `number` must be a positive integer (1-based indexing)
2308
+ * - `number` must not exceed reasonable bounds (e.g., 65535)
2309
+ */
2310
+ declare function validateParamRef(paramRef: ParamRef): ParamRefValidationResult;
2311
+ /**
2312
+ * Creates a validated ParamRef node
2313
+ * @throws Error if validation fails
2314
+ */
2315
+ declare function createParamRef(number: number, location?: number): Node;
2316
+ /**
2317
+ * Creates a TypeCast node wrapping a ParamRef
2318
+ * Example: $1::integer, $2::text[]
2319
+ */
2320
+ declare function createTypeCastParamRef(paramNumber: number, typeName: string, isArray?: boolean, location?: number): Node;
2321
+ /**
2322
+ * Creates an A_Expr node for equality comparison with ParamRef
2323
+ * Example: col = $1
2324
+ */
2325
+ declare function createEqualityExpr(columnName: string, paramNumber: number, tableName?: string, location?: number): Node;
2326
+ /**
2327
+ * Creates a FuncCall node for ANY() with ParamRef
2328
+ * Example: col = ANY($1) for array parameter matching
2329
+ */
2330
+ declare function createAnyExpr(columnName: string, paramNumber: number, tableName?: string, location?: number): Node;
2331
+ /**
2332
+ * Collects all ParamRef nodes from an AST, validating each
2333
+ * Returns validation results for all found ParamRefs
2334
+ */
2335
+ declare function collectAndValidateParamRefs(node: unknown): {
2336
+ paramRefs: Array<{
2337
+ paramRef: ParamRef;
2338
+ path: string;
2339
+ }>;
2340
+ validationResults: ParamRefValidationResult[];
2341
+ allValid: boolean;
2342
+ };
2011
2343
 
2012
2344
  /**
2013
2345
  * Redact sensitive values in a query dump's `params` array before logging.
@@ -2319,5 +2651,15 @@ declare function validateIdentifiers(identifiers: Record<string, 'table' | 'colu
2319
2651
  * NOT for use in SQL - use validateIdentifier + AST helpers for that.
2320
2652
  */
2321
2653
  declare function sanitizeForDisplay(value: string): string;
2654
+ /**
2655
+ * Validate a raw SQL expression used in DDL contexts (defaults, policy USING/CHECK).
2656
+ * Rejects injection vectors: semicolons, line-comment markers, block-comment markers.
2657
+ *
2658
+ * @security Called before any ModelIR-sourced string is interpolated into DDL.
2659
+ * @param sql The raw SQL expression string to validate.
2660
+ * @param context Human-readable context label for the error message.
2661
+ * @throws Error if the expression contains forbidden characters.
2662
+ */
2663
+ declare function validateSqlExpression(sql: string, context: string): void;
2322
2664
 
2323
- export { type BatchValuesJoinDecision, CamelCaseNamingPlugin, type ChangeKind, type CompareSchemataOptions, type CompiledResult, type CompilerContext, type CompilerOptions, type CompilerState, type ConflictAction, type ConflictTarget, type CursorHoldOption, type CursorOptions, type CursorScrollOption, DEFAULT_PK_COLUMN, DEFAULT_REDACTION_PATTERNS, type Decision, type DeleteConfig, type DetectedHierarchy, type DiffSummary, type ExplainFormat, type ExplainOptions, type ExplainPlan, type ExpressionHandler, type FetchDirection, type FetchOptions, type FkColumnDerivation, type GenerateDDLOptions, IdentityNamingPlugin, type IncludeHandler, type IncludeResult, type InsertConfig, type IntrospectedModelIR, type IntrospectionOptions, InvalidIdentifierError, type JoinDecision, type LeafCompileFn, type MigrationRecord, type MigrationSQLOptions, type NamingPlugin, type ParamRefValidationResult, type ParsedMigrationFile, PgsqlAdapter, type PgsqlAdapterOptions, PlanCompiler, type PlanDecision, type PrecompiledJoinDecision, type RedactionConfig, type RedactionPattern, type SchemaChange, type SchemaDiff, type SetOperationResult, type SimplifiedPlanReport, type StreamConfig, type UpdateConfig, type UpsertConfig, type WhereDispatcher, type WhereHandler, acquireMigrationLock, bm25Search, booleanSearch, boost, buildCloseCursor, buildDeclareCursor, buildExplain, buildExplainAnalyzeJson, buildExplainPlan, buildExplainVerbose, buildFetch, buildFetchAll, buildFetchFirst, buildFetchForward, buildFetchNext, buildOnConflictClause, buildStreamingStatements, camelCaseNaming, collectAndValidateParamRefs, compareSchemata, compileDelete, compileInsert, compileMutation, compilePlan, compileSetOperation, compileUpdate, compileUpsert, conditionalUpdate, cosineDistance, createAnyExpr, createEqualityExpr, createLeafCompileFn, createParamRef, createPgsqlAdapter, createPgsqlCompileOnlyAdapter, createTypeCastParamRef, defaultFkDerivation, ensureMigrationsTable, excludedRef, generateCursorName, generateDDL, generateDownSQL, generateMigrationFile, generateMigrationSQL, generateSeries, getAppliedMigrations, getNamingPluginForDbCasing, getNextSchemaVersion, getRowEstimates, getTotalExecutionTime, identityNaming, innerProduct, introspect, isBatchValuesJoinDecision, isDestructiveDown, isJoinDecision, isMigrationApplied, isPrecompiledJoinDecision, isReservedKeyword, l2Distance, mapColumnType, mapOnDeleteAction, nextval, parse, parseExplainJson, parseMigrationFile, rawDistance, recordMigration, redactParams, releaseMigrationLock, removeMigrationRecord, resolveLogicalName, sanitizeForDisplay, score, validateIdentifier, validateIdentifiers, validateParamRef, validateQualifiedIdentifier, vectorDims, withMigrationLock };
2665
+ export { type AddedEnumValue, type BatchValuesJoinDecision, CamelCaseNamingPlugin, type CanonicalizeCheckConstraintsOptions, type ChangeKind, CheckConstraintCanonicalizationError, type CheckConstraintCanonicalizationWarning, CheckConstraintNewEnumValueError, type ComparePgsqlDatabaseSchemaOptions, type CompareSchemataOptions, type CompiledResult, type CompilerContext, type CompilerOptions, type CompilerState, type ConflictAction, type ConflictTarget, type CursorHoldOption, type CursorOptions, type CursorScrollOption, DEFAULT_PK_COLUMN, DEFAULT_REDACTION_PATTERNS, type Decision, type DeleteConfig, type DetectedHierarchy, type DiffSummary, type ExplainFormat, type ExplainOptions, type ExplainPlan, ExpressionCanonicalizationUnavailableError, type ExpressionHandler, type FetchDirection, type FetchOptions, type FkColumnDerivation, type GenerateDDLOptions, IdentityNamingPlugin, type IncludeHandler, type IncludeResult, type InsertConfig, type IntrospectedModelIR, type IntrospectionOptions, InvalidIdentifierError, type JoinDecision, type LeafCompileFn, type MigrationRecord, type MigrationSQLOptions, type NamingPlugin, NonConvergentSchemaDiffError, type ParamRefValidationResult, type ParsedMigrationFile, PgsqlAdapter, type PgsqlAdapterOptions, type PgsqlBorrowedClientAdapterOptions, type PgsqlPoolAdapterOptions, PgsqlRawSqlTransactionControlError, PgsqlTransactionAbortedCommitError, PgsqlTransactionAbortedError, PlanCompiler, type PlanDecision, type PrecompiledJoinDecision, type RedactionConfig, type RedactionPattern, type SchemaChange, type SchemaDiff, type SchemaScopeOptions, type SetOperationResult, type SimplifiedPlanReport, type StreamConfig, type UpdateConfig, type UpsertConfig, type WhereDispatcher, type WhereHandler, assertNoRepeatedExpressionSurfaceDrift, bm25Search, booleanSearch, boost, buildCloseCursor, buildDeclareCursor, buildExplain, buildExplainAnalyzeJson, buildExplainPlan, buildExplainVerbose, buildFetch, buildFetchAll, buildFetchFirst, buildFetchForward, buildFetchNext, buildOnConflictClause, buildStreamingStatements, camelCaseNaming, canGenerateCreateIndex, canonicalizeCheckConstraints, collectAndValidateParamRefs, comparePgsqlDatabaseSchema, compareSchemata, compileDelete, compileInsert, compileMutation, compilePlan, compileSetOperation, compileUpdate, compileUpsert, conditionalUpdate, cosineDistance, createAnyExpr, createEqualityExpr, createLeafCompileFn, createParamRef, createPgsqlAdapter, createPgsqlCompileOnlyAdapter, createTypeCastParamRef, defaultFkDerivation, ensureMigrationsTable, excludedRef, generateCreateIndex, generateCursorName, generateDDL, generateDownSQL, generateMigrationFile, generateMigrationSQL, generateSeries, getAppliedMigrations, getNamingPluginForDbCasing, getNextSchemaVersion, getRowEstimates, getTotalExecutionTime, identityNaming, innerProduct, introspect, isBatchValuesJoinDecision, isDestructiveDown, isJoinDecision, isMigrationApplied, isPrecompiledJoinDecision, isReservedKeyword, l2Distance, mapColumnType, mapOnDeleteAction, nextval, parse, parseExplainJson, parseMigrationFile, rawDistance, recordMigration, redactParams, removeMigrationRecord, resolveLogicalName, sanitizeForDisplay, score, validateIdentifier, validateIdentifiers, validateParamRef, validateQualifiedIdentifier, validateSqlExpression, vectorDims, withMigrationLock };