@dbsp/adapter-pgsql 2.0.0 → 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 { DialectCapabilities, ModelIR, ColumnListInput, ParamIntent, JsonAggOrderByEntry, IndexIR, 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';
@@ -780,912 +780,168 @@ declare function generateCreateIndex(tableName: string, idx: IndexIR, schemaName
780
780
  declare function canGenerateCreateIndex(tableName: string, idx: IndexIR, schemaName?: string | undefined, naming?: NamingPlugin): boolean;
781
781
 
782
782
  /**
783
- * Schema Comparison Engine (DDL-PROV Block 1)
783
+ * PostgreSQL Schema Introspection (ADAPTER-006)
784
784
  *
785
- * Compares two ModelIRs (schema definition vs database state)
786
- * and produces a structured diff of changes needed.
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
787
791
  *
788
- * @module schema-diff
792
+ * @module introspection
789
793
  */
790
794
 
791
- 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';
792
- interface SchemaChange {
793
- readonly kind: ChangeKind;
794
- readonly table: string;
795
- readonly column?: string;
796
- readonly destructive: boolean;
797
- readonly details: string;
798
- /** Additional metadata for SQL generation */
799
- readonly meta?: Readonly<Record<string, unknown>>;
800
- }
801
- interface DiffSummary {
802
- readonly tables: {
803
- readonly added: number;
804
- readonly dropped: number;
805
- };
806
- readonly columns: {
807
- readonly added: number;
808
- readonly dropped: number;
809
- readonly altered: number;
810
- };
811
- readonly indexes: {
812
- readonly added: number;
813
- readonly dropped: number;
814
- };
815
- readonly constraints: {
816
- readonly added: number;
817
- readonly dropped: number;
818
- readonly altered: number;
819
- };
820
- }
821
- interface SchemaDiff {
822
- readonly changes: readonly SchemaChange[];
823
- readonly hasDestructive: boolean;
824
- readonly summary: DiffSummary;
825
- }
826
- interface CompareSchemataOptions {
827
- /**
828
- * Database naming convention.
829
- * When set, schema model names (camelCase) are converted to DB format
830
- * (e.g. snake_case) before comparison with the introspected model.
831
- */
832
- dbCasing?: DbCasing;
833
- /** Dialect capabilities — comparisons for unsupported features will be skipped */
834
- readonly dialectCapabilities?: DialectCapabilities;
835
- /**
836
- * When `true`, extensions present in the live DB but absent from the model
837
- * schema are silently ignored — no `drop_extension` change is emitted for them.
838
- * Only extensions explicitly declared in the model are managed (created if missing).
839
- *
840
- * Use this when the database image pre-installs extensions that the application
841
- * schema does not own (e.g. pgvector, pg_search bundled in a custom Postgres image).
842
- * Default: `false` (full-sync behaviour — unmanaged DB extensions produce a
843
- * `drop_extension` entry).
844
- */
845
- readonly ignoreUnmanagedExtensions?: boolean;
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;
846
799
  }
847
800
  /**
848
- * Compare two ModelIRs and produce a structured diff.
849
- *
850
- * @param schema - The desired schema (from definition)
851
- * @param db - The current database state (from introspection)
852
- * @param options - Optional comparison settings (e.g. dbCasing)
853
- * @returns SchemaDiff with all changes needed to bring DB in sync with schema
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.
854
804
  */
855
- declare function compareSchemata(schema: ModelIR, db: ModelIR, options?: CompareSchemataOptions): SchemaDiff;
856
-
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 */
857
812
  /**
858
- * Migration SQL Generator (DDL-PROV Block 1)
859
- *
860
- * Generates ordered SQL statements from a SchemaDiff.
861
- * Statements are topologically sorted: DROP constraints → DROP objects → CREATE objects → ADD constraints.
862
- *
863
- * @module migration-sql
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).
864
816
  */
865
-
866
- interface MigrationSQLOptions {
867
- /**
868
- * Schema namespace (default: none — unqualified).
869
- * Required when emitted migration SQL would otherwise mix non-default
870
- * target-scoped custom types/enums with unqualified table SQL.
871
- */
872
- readonly schemaName?: string;
873
- /** Whether to include destructive changes (drops) */
874
- readonly includeDestructive?: boolean;
875
- /** Automatically create indexes on FK columns for new tables (default: true) */
876
- readonly fkAutoIndex?: boolean;
877
- /** Dialect capabilities — migration SQL for unsupported features will be filtered */
878
- readonly dialectCapabilities?: DialectCapabilities;
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[];
879
823
  }
880
824
  /**
881
- * Generate ordered SQL statements from a SchemaDiff.
825
+ * Introspect a database through a pool.
882
826
  *
883
- * Topological order:
884
- * 0. DROP FK/CHECK constraints (must drop before referenced tables)
885
- * 1. DROP indexes
886
- * 2. DROP columns
887
- * 3. DROP primary keys
888
- * 4. DROP tables, DROP ENUMs
889
- * 5. CREATE ENUMs (must exist before tables that use them)
890
- * 6. CREATE tables
891
- * 7. ADD columns
892
- * 8. ALTER columns (type, nullable, default)
893
- * 9. ADD primary keys / column UNIQUE constraints
894
- * 10. ADD FK constraints (must add after referenced tables exist)
895
- * 11. ALTER FK (drop + re-add)
896
- * 12. CREATE indexes
897
- * 13. ADD CHECK constraints
898
- * 14. ALTER ENUM ADD VALUE (must be last — has transaction visibility caveats in PG)
899
- * 15. COMMENT ON TABLE / COLUMN (very last)
900
- */
901
- declare function generateMigrationSQL(diff: SchemaDiff, options?: MigrationSQLOptions): readonly string[];
902
- /**
903
- * Generate ordered DOWN SQL statements from a SchemaDiff.
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.
904
834
  *
905
- * Reverses the topological order used in UP migrations:
906
- * phases run in descending order (11, 10, 9, ..., 0).
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.
907
840
  *
908
- * Irreversible changes (drops that lose data) produce SQL WARNING comments.
841
+ * So: hold a client, use `new PgsqlAdapter(client, { borrowedClient: true })`
842
+ * and call `.introspect()` on it.
909
843
  */
910
- declare function generateDownSQL(diff: SchemaDiff, options?: MigrationSQLOptions): readonly string[];
844
+ declare function introspect(pool: Pool, options?: IntrospectionOptions): Promise<IntrospectedModelIR>;
911
845
 
912
846
  /**
913
- * Migration File Format v2 — UP + DOWN sections.
914
- *
915
- * File format:
916
- * -- dbsp:destructive: true|false
917
- * <UP statements>;
918
- * -- DOWN
919
- * <DOWN statements>;
847
+ * PgsqlAdapter - Implements the Adapter interface for PostgreSQL using native pg driver.
920
848
  *
921
- * The separator `-- DOWN` must be on its own line (SC-25).
849
+ * This adapter wraps a pg Pool instance and provides the unified
850
+ * adapter interface for the db-semantic-planner ORM.
922
851
  *
923
- * @module ddl/migration-file
852
+ * @module pgsql-adapter
924
853
  */
925
854
 
926
- /**
927
- * Result of parsing a migration file's UP/DOWN sections.
928
- */
929
- interface ParsedMigrationFile {
930
- readonly upStatements: readonly string[];
931
- readonly downStatements: readonly string[];
932
- readonly hasDown: boolean;
933
- readonly destructive?: boolean | undefined;
855
+ declare class PgsqlRawSqlTransactionControlError extends Error {
856
+ readonly dbspRawSqlTransactionControl = true;
857
+ constructor(cause: unknown);
934
858
  }
935
- /**
936
- * Generate a migration file content with UP and DOWN sections.
937
- */
938
- declare function generateMigrationFile(diff: SchemaDiff, options?: MigrationSQLOptions & {
939
- name?: string;
940
- }): string;
941
- /**
942
- * Parse a migration file into UP and DOWN sections.
943
- * Separator: `^\s*-- DOWN\s*$` (strict regex, own line only — SC-25)
944
- */
945
- declare function parseMigrationFile(content: string): ParsedMigrationFile;
946
- /**
947
- * Check if SQL statements contain destructive operations.
948
- * Destructive: DROP TABLE, DROP COLUMN, lossy ALTER COLUMN TYPE
949
- */
950
- declare function isDestructiveDown(downStatements: readonly string[]): boolean;
951
-
952
- /**
953
- * Migration Tracker — `_dbsp_migrations` table CRUD.
954
- *
955
- * Manages the tracking table that records which migrations
956
- * have been applied to a database.
957
- */
958
-
959
- interface MigrationRecord {
960
- /** Migration filename (e.g., "0001_create_users.sql") */
961
- readonly name: string;
962
- /** SHA-256 checksum of the migration file content */
963
- readonly checksum: string;
964
- /** When the migration was applied */
965
- readonly appliedAt: Date;
966
- /** Schema version at time of this migration */
967
- readonly schemaVersion: number;
968
- /** Whether this migration contains destructive changes */
969
- readonly destructive: boolean;
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);
970
866
  }
971
867
  /**
972
- * Execute a callback under an advisory lock using a dedicated client.
973
- * The lock is held for the duration of the callback.
974
- * The client is released (and lock freed) after the callback completes.
975
- */
976
- declare function withMigrationLock<T>(pool: Pool, fn: (client: PoolClient) => Promise<T>): Promise<T>;
977
- /**
978
- * Ensure the migrations tracking table exists.
979
- * Auto-migrates existing tables that lack `schema_version`/`destructive` columns,
980
- * and backfills `schema_version` by `applied_at` order for rows still at 0.
981
- */
982
- declare function ensureMigrationsTable(pool: Pool): Promise<void>;
983
- /**
984
- * Get all applied migrations, ordered by name.
985
- */
986
- declare function getAppliedMigrations(pool: Pool): Promise<readonly MigrationRecord[]>;
987
- /**
988
- * Record a migration as applied.
989
- */
990
- declare function recordMigration(pool: Pool, name: string, checksum: string, schemaVersion: number, destructive: boolean): Promise<void>;
991
- /**
992
- * Check if a specific migration has been applied.
993
- */
994
- declare function isMigrationApplied(pool: Pool, name: string): Promise<boolean>;
995
- /**
996
- * Get the next schema version number (max + 1, or 1 if no migrations).
997
- */
998
- declare function getNextSchemaVersion(pool: Pool): Promise<number>;
999
- /**
1000
- * Remove a migration record (for rollback).
868
+ * Options for PgsqlAdapter.
1001
869
  */
1002
- declare function removeMigrationRecord(pool: Pool, name: string): Promise<void>;
1003
-
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
+ }
1004
922
  /**
1005
- * Type Mapping - Maps ModelIR ColumnType to PostgreSQL data types
923
+ * Adapter implementation for PostgreSQL using native pg driver.
1006
924
  *
1007
- * Supports both manual schemas and introspected schemas (preserving originalDbType).
1008
- * Handles auto-increment via SERIAL/BIGSERIAL types.
1009
- *
1010
- * @module ddl/type-mapping
1011
- */
1012
-
1013
- /**
1014
- * Map ColumnType to PostgreSQL data type string.
1015
- *
1016
- * Uses originalDbType if available (from introspection), otherwise
1017
- * falls back to reasonable PostgreSQL defaults.
1018
- *
1019
- * @param col - Column definition from ModelIR
1020
- * @returns PostgreSQL type string (e.g., 'VARCHAR(255)', 'SERIAL', 'JSONB')
1021
- */
1022
- declare function mapColumnType(col: ColumnIR, targetSchema?: string): string;
1023
- /**
1024
- * Map OnDeleteAction to PostgreSQL syntax.
1025
- */
1026
- declare function mapOnDeleteAction(action?: string): string;
1027
-
1028
- /**
1029
- * EXPLAIN Statement Compiler
1030
- *
1031
- * Generates PostgreSQL EXPLAIN statements with various options.
1032
- * Supports:
1033
- * - ANALYZE (execute and show actual run times)
1034
- * - FORMAT (text, json, xml, yaml)
1035
- * - VERBOSE, COSTS, BUFFERS, TIMING, SETTINGS
1036
- */
1037
-
1038
- /**
1039
- * Output format for EXPLAIN results.
1040
- */
1041
- type ExplainFormat = 'text' | 'json' | 'xml' | 'yaml';
1042
- /**
1043
- * Options for EXPLAIN statement.
1044
- */
1045
- interface ExplainOptions {
1046
- /** Execute the query and show actual run times */
1047
- analyze?: boolean;
1048
- /** Show more detailed output */
1049
- verbose?: boolean;
1050
- /** Show cost estimates (default: true) */
1051
- costs?: boolean;
1052
- /** Show buffer usage (requires analyze) */
1053
- buffers?: boolean;
1054
- /** Show actual timing (requires analyze) */
1055
- timing?: boolean;
1056
- /** Show non-default settings */
1057
- settings?: boolean;
1058
- /** Output format */
1059
- format?: ExplainFormat;
1060
- }
1061
- /**
1062
- * Build an EXPLAIN statement wrapping a query.
1063
- *
1064
- * @param query - The query to explain (SelectStmt, InsertStmt, etc.)
1065
- * @param options - EXPLAIN options
1066
- * @returns ExplainStmt AST node
925
+ * @typeParam DB - Database schema type
1067
926
  *
1068
927
  * @example
1069
928
  * ```typescript
1070
- * const selectAst = { SelectStmt: { ... } };
1071
- * const explainAst = buildExplain(selectAst, { analyze: true, format: 'json' });
1072
- * // Produces: EXPLAIN (ANALYZE, FORMAT JSON) SELECT ...
1073
- * ```
1074
- */
1075
- declare function buildExplain(query: Node, options?: ExplainOptions): Node;
1076
- /**
1077
- * Build EXPLAIN ANALYZE with JSON format (common pattern).
1078
- *
1079
- * @param query - The query to explain
1080
- * @returns ExplainStmt with ANALYZE and JSON format
1081
- */
1082
- declare function buildExplainAnalyzeJson(query: Node): Node;
1083
- /**
1084
- * Build simple EXPLAIN (plan only, no execution).
1085
- *
1086
- * @param query - The query to explain
1087
- * @returns ExplainStmt with default options
1088
- */
1089
- declare function buildExplainPlan(query: Node): Node;
1090
- /**
1091
- * Build verbose EXPLAIN with costs and buffers.
1092
- *
1093
- * @param query - The query to explain
1094
- * @returns ExplainStmt with verbose options
1095
- */
1096
- declare function buildExplainVerbose(query: Node): Node;
1097
- /**
1098
- * Parse EXPLAIN JSON output to get execution statistics.
1099
- *
1100
- * @param jsonOutput - The JSON string from EXPLAIN (ANALYZE, FORMAT JSON)
1101
- * @returns Parsed plan with execution statistics
1102
- */
1103
- declare function parseExplainJson(jsonOutput: string): ExplainPlan[];
1104
- /**
1105
- * Parsed EXPLAIN plan structure (simplified).
1106
- */
1107
- interface ExplainPlan {
1108
- Plan: {
1109
- 'Node Type': string;
1110
- 'Relation Name'?: string;
1111
- Alias?: string;
1112
- 'Startup Cost'?: number;
1113
- 'Total Cost'?: number;
1114
- 'Plan Rows'?: number;
1115
- 'Plan Width'?: number;
1116
- 'Actual Startup Time'?: number;
1117
- 'Actual Total Time'?: number;
1118
- 'Actual Rows'?: number;
1119
- 'Actual Loops'?: number;
1120
- Plans?: ExplainPlan['Plan'][];
1121
- };
1122
- 'Planning Time'?: number;
1123
- 'Execution Time'?: number;
1124
- Triggers?: unknown[];
1125
- }
1126
- /**
1127
- * Extract total execution time from EXPLAIN ANALYZE JSON output.
1128
- *
1129
- * @param plans - Parsed EXPLAIN plans
1130
- * @returns Total execution time in milliseconds
1131
- */
1132
- declare function getTotalExecutionTime(plans: ExplainPlan[]): number;
1133
- /**
1134
- * Extract row counts from EXPLAIN ANALYZE JSON output.
1135
- *
1136
- * @param plans - Parsed EXPLAIN plans
1137
- * @returns Object with estimated and actual row counts
1138
- */
1139
- declare function getRowEstimates(plans: ExplainPlan[]): {
1140
- estimated: number;
1141
- actual: number;
1142
- };
1143
-
1144
- /**
1145
- * ParadeDB Extension Wrappers
1146
- *
1147
- * Type-safe query builders for ParadeDB BM25 full-text search.
1148
- * All functions return ExpressionRef instances that can be used in:
1149
- * - SELECT: .column(score('id').as('score'))
1150
- * - WHERE: .where(bm25Search('symbols', query, { name: 3.0, doc: 1.0 }))
1151
- * - ORDER BY: .orderBy(score('id'), 'desc')
1152
- *
1153
- * @remarks
1154
- * ParadeDB functions accept both named and positional arguments.
1155
- * This module uses named args via namedArg() for parse(), which produces:
1156
- * paradedb.parse(field => 'field_name', query_string => $1)
1157
- * Named parameter syntax is supported via the NamedArgExpressionIntent (EXT-NAMED-PARAMS).
1158
- */
1159
-
1160
- /**
1161
- * BM25 relevance score for a row.
1162
- *
1163
- * Use in SELECT and ORDER BY to retrieve and sort by full-text relevance.
1164
- * Requires a BM25 index on the table.
1165
- *
1166
- * @param keyField - The key field of the BM25 index (typically the primary key, e.g. 'id')
1167
- * @returns ExpressionRef that compiles to: paradedb.score("keyField")
1168
- *
1169
- * @example
1170
- * orm.select('symbols').column(score('id').as('score')).orderBy(score('id'), 'desc')
1171
- * // → paradedb.score("id") AS "score"
1172
- */
1173
- declare function score(keyField: string): ExpressionRef;
1174
- /**
1175
- * Parse a single-field BM25 query expression.
1176
- *
1177
- * Compiles to: paradedb.parse(field => 'field_name', query_string => $N)
1178
- *
1179
- * @param field - Column name to search in (must be indexed in the BM25 index)
1180
- * @param query - Query string value (will be bound as a parameter)
1181
- * @returns ExpressionRef for use with boost() or booleanSearch()
1182
- *
1183
- * @example
1184
- * parse('name', 'hello world')
1185
- * // → paradedb.parse(field => 'name', query_string => $1)
1186
- */
1187
- declare function parse(field: string, query: ExpressionRef | unknown): ExpressionRef;
1188
- /**
1189
- * Apply a boost multiplier to a BM25 sub-expression.
1190
- *
1191
- * Compiles to: paradedb.boost(factor, expr)
1192
- *
1193
- * @param factor - Boost multiplier (e.g. 3.0 for 3x weight)
1194
- * @param expr - Expression to boost (typically a parse() call)
1195
- * @returns ExpressionRef for use with booleanSearch()
1196
- *
1197
- * @example
1198
- * boost(3.0, parse('name', 'hello'))
1199
- * // → paradedb.boost(3.0, paradedb.parse('name', $1))
1200
- */
1201
- declare function boost(factor: number, expr: ExpressionRef): ExpressionRef;
1202
- /**
1203
- * Combine multiple BM25 sub-expressions with boolean OR logic.
1204
- *
1205
- * Compiles to: paradedb.boolean(expr1, expr2, ...)
1206
- *
1207
- * @param exprs - One or more sub-expressions (typically boost() calls)
1208
- * @returns ExpressionRef for use on the right side of the @@@ operator
1209
- *
1210
- * @example
1211
- * booleanSearch([boost(3.0, parse('name', q)), boost(1.0, parse('doc', q))])
1212
- * // → paradedb.boolean(paradedb.boost(3.0, ...), paradedb.boost(1.0, ...))
1213
- */
1214
- /**
1215
- * Combine multiple BM25 sub-expressions with boolean OR logic.
1216
- *
1217
- * Compiles to: paradedb.boolean(should => ARRAY[expr1, expr2, ...])
1218
- *
1219
- * @param exprs - One or more sub-expressions (typically boost() calls)
1220
- * @returns ExpressionRef for use on the right side of the @@@ operator
1221
- *
1222
- * @example
1223
- * booleanSearch([boost(3.0, parse('name', q)), boost(1.0, parse('doc', q))])
1224
- * // → paradedb.boolean(should => ARRAY[paradedb.boost(3.0, ...), paradedb.boost(1.0, ...)])
1225
- */
1226
- declare function booleanSearch(exprs: ExpressionRef[]): ExpressionRef;
1227
- /**
1228
- * Full BM25 multi-field search with per-field boost weights.
1229
- *
1230
- * Produces: table @@@ paradedb.boolean(boost1, boost2, ...)
1231
- *
1232
- * Each field in `fieldBoosts` generates a `paradedb.boost(weight, paradedb.parse(field, $N))`
1233
- * sub-expression. The same query string is used for all fields (single parameter binding).
1234
- *
1235
- * @param table - Table alias for the left side of the @@@ operator
1236
- * @param query - Query string (bound as a single $N parameter, shared across all fields)
1237
- * @param fieldBoosts - Map of column name → boost weight
1238
- * @returns ExpressionRef for use in .where()
1239
- *
1240
- * @example
1241
- * bm25Search('s', searchTerm, {
1242
- * name_searchable: 3.0,
1243
- * name: 1.0,
1244
- * signature: 1.5,
1245
- * doc_searchable: 1.0,
1246
- * })
1247
- * // → s @@@ paradedb.boolean(
1248
- * // paradedb.boost(3.0, paradedb.parse('name_searchable', $1)),
1249
- * // paradedb.boost(1.0, paradedb.parse('name', $1)),
1250
- * // paradedb.boost(1.5, paradedb.parse('signature', $1)),
1251
- * // paradedb.boost(1.0, paradedb.parse('doc_searchable', $1))
1252
- * // )
1253
- *
1254
- * @remarks
1255
- * The query parameter is shared: all parse() calls reference the same $N slot.
1256
- * If you need different query strings per field, compose parse()/boost()/booleanSearch() manually.
929
+ * import { Pool } from 'pg';
930
+ * import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';
1257
931
  *
1258
- * @remarks
1259
- * ParadeDB's boolean() function accepts both positional args and the named
1260
- * `should => ARRAY[...]` syntax. This wrapper uses positional args.
1261
- * Named parameter syntax is deferred to EXT-NAMED-PARAMS.
1262
- */
1263
- declare function bm25Search(table: string, query: unknown, fieldBoosts: Record<string, number>): ExpressionRef;
1264
-
1265
- /**
1266
- * PostgreSQL built-in function helpers.
1267
- *
1268
- * Thin wrappers around core expression primitives for common PostgreSQL functions.
1269
- * Same pattern as pgvector.ts and paradedb.ts.
1270
- */
1271
-
1272
- /**
1273
- * Generate a series of values: generate_series(start, stop[, step])
1274
- *
1275
- * Returns a set of values from start to stop (inclusive), with an optional step.
1276
- * Commonly used with CTE for batch operations.
1277
- *
1278
- * @example generateSeries(1, 100) → generate_series(1, 100)
1279
- * @example generateSeries(0, 50, 5) → generate_series(0, 50, 5)
1280
- */
1281
- declare function generateSeries(start: number, stop: number, step?: number): ExpressionRef;
1282
- /**
1283
- * Get next value from a sequence: nextval('sequence_name')
1284
- *
1285
- * @example nextval('order_id_seq') → nextval('order_id_seq')
1286
- */
1287
- declare function nextval(sequenceName: string): ExpressionRef;
1288
-
1289
- /**
1290
- * pgvector Extension Wrappers
1291
- *
1292
- * Type-safe query builders for pgvector distance operators.
1293
- * All functions return ExpressionRef instances that can be used in:
1294
- * - SELECT: .column(cosineDistance('vector', qv).as('score'))
1295
- * - WHERE: .where(cosineDistance('vector', qv).gte(0.5))
1296
- * - ORDER BY: .orderBy(rawDistance('vector', qv), 'asc')
1297
- */
1298
-
1299
- /**
1300
- * Cosine similarity: 1 - (col <=> vector)
1301
- *
1302
- * Score in [0, 1], higher = more similar.
1303
- * Use in SELECT to get a similarity score.
1304
- *
1305
- * @example
1306
- * orm.select('embeddings').column(cosineDistance('vector', qv).as('score'))
1307
- */
1308
- declare function cosineDistance(column: string, vector: number[]): ExpressionRef;
1309
- /**
1310
- * Raw cosine distance: col <=> vector
1311
- *
1312
- * Lower = closer. Index-friendly — use in ORDER BY for ANN search.
1313
- * Do NOT use in SELECT as a similarity score (lower = closer is counterintuitive).
1314
- *
1315
- * @example
1316
- * orm.select('embeddings').orderBy(rawDistance('vector', qv), 'asc')
1317
- */
1318
- declare function rawDistance(column: string, vector: number[]): ExpressionRef;
1319
- /**
1320
- * L2 (Euclidean) distance: col <-> vector
1321
- *
1322
- * @example
1323
- * orm.select('embeddings').orderBy(l2Distance('vector', qv), 'asc')
1324
- */
1325
- declare function l2Distance(column: string, vector: number[]): ExpressionRef;
1326
- /**
1327
- * Inner product distance: col <#> vector (negative inner product)
1328
- *
1329
- * For maximum inner product search: ORDER BY innerProduct('vector', qv) ASC.
1330
- *
1331
- * @example
1332
- * orm.select('embeddings').orderBy(innerProduct('vector', qv), 'asc')
1333
- */
1334
- declare function innerProduct(column: string, vector: number[]): ExpressionRef;
1335
- /**
1336
- * Get the number of dimensions of a vector column: vector_dims(col)
1337
- *
1338
- * Returns an integer — the dimension count of the stored vector.
1339
- * Useful for sanity-checking that embeddings match the expected model dimension.
1340
- *
1341
- * @example
1342
- * orm.from(embeddings).columns([vectorDims('vector').as('dim')]).first()
1343
- * // → SELECT vector_dims("t0"."vector") AS "dim" FROM "embeddings" AS "t0"
1344
- */
1345
- declare function vectorDims(column: string): ExpressionRef;
1346
-
1347
- /**
1348
- * PostgreSQL Schema Introspection (ADAPTER-006)
1349
- *
1350
- * Queries information_schema/pg_catalog to build ModelIR
1351
- * from an existing database. Supports:
1352
- * - Table/column/PK discovery
1353
- * - FK → bidirectional relation inference
1354
- * - Hierarchy detection (adjacency + edge-table)
1355
- * - Include/exclude filtering
1356
- *
1357
- * @module introspection
1358
- */
1359
-
1360
- /** Options for database introspection */
1361
- interface IntrospectionOptions {
1362
- /** Tables to exclude (glob patterns: * matches any chars) */
1363
- readonly exclude?: readonly string[];
1364
- /** Tables to include (default: all). Applied before exclude. */
1365
- readonly include?: readonly string[];
1366
- /** Schema name to introspect (default: 'public') */
1367
- readonly schema?: string;
1368
- }
1369
- /** Hierarchy pattern detected during introspection */
1370
- /**
1371
- * Hierarchy pattern detected during introspection.
1372
- * Alias of {@link HierarchyIR} from \@dbsp/types — kept here for
1373
- * public-API backwards compatibility (re-exported from \@dbsp/adapter-pgsql).
1374
- */
1375
- type DetectedHierarchy = HierarchyIR;
1376
- /** Extended ModelIR with hierarchy metadata */
1377
- interface IntrospectedModelIR extends ModelIR {
1378
- readonly hierarchies: readonly DetectedHierarchy[];
1379
- readonly introspectedAt: Date;
1380
- readonly warnings: readonly string[];
1381
- }
1382
- declare function introspect(pool: Pool, options?: IntrospectionOptions): Promise<IntrospectedModelIR>;
1383
-
1384
- /**
1385
- * Mutation Compiler
1386
- *
1387
- * Compiles INSERT, UPDATE, and DELETE statements from plan decisions.
1388
- * Supports:
1389
- * - INSERT with values/from subquery
1390
- * - INSERT with RETURNING
1391
- * - UPDATE with SET and WHERE
1392
- * - DELETE with WHERE
1393
- * - RETURNING clause for all mutations
1394
- */
1395
-
1396
- /**
1397
- * Configuration for INSERT compilation
1398
- */
1399
- interface InsertConfig {
1400
- /** Table to insert into */
1401
- table: string;
1402
- /** Columns to insert */
1403
- columns: string[];
1404
- /** Values for each column (array of rows) */
1405
- values: unknown[][];
1406
- /** Columns to return (RETURNING clause) */
1407
- returning?: string[];
1408
- /** Alias-aware RETURNING projection items */
1409
- returningItems?: readonly MutationReturningItem[];
1410
- /** Subquery for INSERT ... SELECT */
1411
- selectQuery?: Node;
1412
- /** Column database types for type-cast emission (e.g. range types) */
1413
- columnTypes?: Record<string, string>;
1414
- }
1415
- /**
1416
- * Configuration for UPDATE compilation
1417
- */
1418
- interface UpdateConfig {
1419
- /** Table to update */
1420
- table: string;
1421
- /** Column-value pairs to set */
1422
- set: {
1423
- column: string;
1424
- value: unknown;
1425
- }[];
1426
- /** WHERE conditions */
1427
- where?: Decision[];
1428
- /** Columns to return (RETURNING clause) */
1429
- returning?: string[];
1430
- /** Alias-aware RETURNING projection items */
1431
- returningItems?: readonly MutationReturningItem[];
1432
- /** Column database types for type-cast emission (e.g. range types) */
1433
- columnTypes?: Record<string, string>;
1434
- }
1435
- /**
1436
- * Configuration for DELETE compilation
1437
- */
1438
- interface DeleteConfig {
1439
- /** Table to delete from */
1440
- table: string;
1441
- /** WHERE conditions */
1442
- where?: Decision[];
1443
- /** Columns to return (RETURNING clause) */
1444
- returning?: string[];
1445
- /** Alias-aware RETURNING projection items */
1446
- returningItems?: readonly MutationReturningItem[];
1447
- }
1448
- /**
1449
- * Compile an INSERT statement from configuration.
1450
- */
1451
- declare function compileInsert(config: InsertConfig, ctx: CompilerContext, state: CompilerState): Node;
1452
- /**
1453
- * Compile an UPDATE statement from configuration.
1454
- */
1455
- declare function compileUpdate(config: UpdateConfig, ctx: CompilerContext, state: CompilerState): Node;
1456
- declare function compileDelete(config: DeleteConfig, ctx: CompilerContext, state: CompilerState): Node;
1457
- /**
1458
- * Compile a mutation decision to AST.
1459
- * Determines mutation type from decision.type and delegates.
1460
- */
1461
- declare function compileMutation(decision: Decision, ctx: CompilerContext, state: CompilerState): Node;
1462
-
1463
- /**
1464
- * Upsert (INSERT ... ON CONFLICT) Compiler
1465
- *
1466
- * Compiles UPSERT statements with ON CONFLICT handling.
1467
- * Supports:
1468
- * - ON CONFLICT DO NOTHING
1469
- * - ON CONFLICT DO UPDATE SET ...
1470
- * - Conflict target (columns or constraint name)
1471
- * - WHERE clause for conflict resolution
1472
- */
1473
-
1474
- /**
1475
- * Conflict resolution strategy
1476
- */
1477
- type ConflictAction = 'nothing' | 'update';
1478
- /**
1479
- * Conflict target specification
1480
- */
1481
- interface ConflictTarget {
1482
- /** Column names that form the unique constraint */
1483
- columns?: string[];
1484
- /** Named constraint */
1485
- constraint?: string;
1486
- /** WHERE clause for partial index */
1487
- where?: Decision[];
1488
- }
1489
- /**
1490
- * Configuration for UPSERT compilation
1491
- */
1492
- interface UpsertConfig {
1493
- /** Table to upsert into */
1494
- table: string;
1495
- /** Columns to insert */
1496
- columns: string[];
1497
- /** Values for each column (array of rows) */
1498
- values: unknown[][];
1499
- /** Conflict target (unique columns or constraint) */
1500
- conflictTarget: ConflictTarget;
1501
- /** What to do on conflict */
1502
- conflictAction: ConflictAction;
1503
- /** Columns to update on conflict (for 'update' action) */
1504
- updateColumns?: string[];
1505
- /** Optional WHERE clause for ON CONFLICT DO UPDATE */
1506
- actionWhere?: Decision[];
1507
- /** Optional direct WHERE intent for ON CONFLICT DO UPDATE */
1508
- actionWhereIntent?: WhereIntent;
1509
- /** Compile the direct action WHERE intent using the caller's WHERE compiler */
1510
- compileActionWhere?: (where: WhereIntent, state: CompilerState) => Node;
1511
- /** Use EXCLUDED.column for update values (default: true) */
1512
- useExcluded?: boolean;
1513
- /** Columns to return (RETURNING clause) */
1514
- returning?: string[];
1515
- /** Alias-aware RETURNING projection items */
1516
- returningItems?: readonly MutationReturningItem[];
1517
- /** Optional column type hints for unnest casting (schema-driven) */
1518
- columnTypes?: Record<string, string>;
1519
- /**
1520
- * Raw SQL expressions for specific update columns.
1521
- * These are injected verbatim into the ON CONFLICT DO UPDATE SET clause.
1522
- * Keys are logical column names (before naming plugin), values are raw SQL fragments.
1523
- *
1524
- * @warning SECURITY: fragments are inserted without parameterization.
1525
- * Only use with hardcoded expressions. Never with user input.
1526
- *
1527
- * @example { last_parsed: 'now()', count: 'excluded.count + 1' }
1528
- */
1529
- updateExpressions?: Record<string, string>;
1530
- }
1531
- /**
1532
- * Build ON CONFLICT clause for INSERT statement.
1533
- */
1534
- declare function buildOnConflictClause(config: UpsertConfig, ctx: CompilerContext, state: CompilerState): OnConflictClause;
1535
- /**
1536
- * Compile a complete UPSERT statement.
1537
- */
1538
- declare function compileUpsert(config: UpsertConfig, ctx: CompilerContext, state: CompilerState): Node;
1539
- /**
1540
- * Build EXCLUDED.column reference.
1541
- * EXCLUDED is a special table alias in ON CONFLICT ... DO UPDATE
1542
- * that refers to the row that would have been inserted.
1543
- */
1544
- declare function excludedRef(column: string, naming: {
1545
- toDatabase: (s: string) => string;
1546
- }): Node;
1547
- /**
1548
- * Build conditional update using COALESCE.
1549
- *
1550
- * Produces: COALESCE(EXCLUDED.col, table.col)
1551
- * This keeps existing value if new value is NULL.
1552
- */
1553
- declare function conditionalUpdate(column: string, table: string, ctx: CompilerContext): Node;
1554
-
1555
- /**
1556
- * @module naming
1557
- * Utilities for resolving database names to logical model names.
1558
- *
1559
- * The ModelIR.getTable() method expects logical (camelCase) names,
1560
- * but the adapter often works with database (snake_case) names.
1561
- * This module bridges that gap.
1562
- */
1563
-
1564
- /**
1565
- * Resolve a database table name to the corresponding logical model name.
1566
- *
1567
- * Converts the DB name using the naming convention, then looks it up in the model.
1568
- * Falls back to exact match if conversion doesn't find a match.
1569
- *
1570
- * @param model - The model IR to search in
1571
- * @param dbName - Database table name (e.g. "post_comments")
1572
- * @param convention - Naming convention used by the adapter
1573
- * @returns The logical table name if found, undefined otherwise
1574
- *
1575
- * @example
1576
- * ```typescript
1577
- * // With camelCase convention:
1578
- * resolveLogicalName(model, "post_comments", "camelCase") // → "postComments"
1579
- * resolveLogicalName(model, "posts", "camelCase") // → "posts"
1580
- * resolveLogicalName(model, "unknown", "camelCase") // → undefined
1581
- * ```
1582
- */
1583
- declare function resolveLogicalName(model: ModelIR, dbName: string, casing: DbCasing): string | undefined;
1584
-
1585
- /**
1586
- * ParamRef validation and helpers for PostgreSQL AST
1587
- *
1588
- * ParamRef nodes represent parameterized query placeholders ($1, $2, etc.)
1589
- * This module provides validation and creation helpers for safe AST construction.
1590
- */
1591
-
1592
- /**
1593
- * Validation result for ParamRef nodes
1594
- */
1595
- interface ParamRefValidationResult {
1596
- valid: boolean;
1597
- errors: string[];
1598
- }
1599
- /**
1600
- * Validates a ParamRef node
1601
- *
1602
- * Rules:
1603
- * - `number` must be a positive integer (1-based indexing)
1604
- * - `number` must not exceed reasonable bounds (e.g., 65535)
1605
- */
1606
- declare function validateParamRef(paramRef: ParamRef): ParamRefValidationResult;
1607
- /**
1608
- * Creates a validated ParamRef node
1609
- * @throws Error if validation fails
1610
- */
1611
- declare function createParamRef(number: number, location?: number): Node;
1612
- /**
1613
- * Creates a TypeCast node wrapping a ParamRef
1614
- * Example: $1::integer, $2::text[]
1615
- */
1616
- declare function createTypeCastParamRef(paramNumber: number, typeName: string, isArray?: boolean, location?: number): Node;
1617
- /**
1618
- * Creates an A_Expr node for equality comparison with ParamRef
1619
- * Example: col = $1
1620
- */
1621
- declare function createEqualityExpr(columnName: string, paramNumber: number, tableName?: string, location?: number): Node;
1622
- /**
1623
- * Creates a FuncCall node for ANY() with ParamRef
1624
- * Example: col = ANY($1) for array parameter matching
1625
- */
1626
- declare function createAnyExpr(columnName: string, paramNumber: number, tableName?: string, location?: number): Node;
1627
- /**
1628
- * Collects all ParamRef nodes from an AST, validating each
1629
- * Returns validation results for all found ParamRefs
1630
- */
1631
- declare function collectAndValidateParamRefs(node: unknown): {
1632
- paramRefs: Array<{
1633
- paramRef: ParamRef;
1634
- path: string;
1635
- }>;
1636
- validationResults: ParamRefValidationResult[];
1637
- allValid: boolean;
1638
- };
1639
-
1640
- /**
1641
- * PgsqlAdapter - Implements the Adapter interface for PostgreSQL using native pg driver.
1642
- *
1643
- * This adapter wraps a pg Pool instance and provides the unified
1644
- * adapter interface for the db-semantic-planner ORM.
1645
- *
1646
- * @module pgsql-adapter
1647
- */
1648
-
1649
- /**
1650
- * Options for PgsqlAdapter.
1651
- */
1652
- interface PgsqlAdapterOptions {
1653
- /** Schema name for multi-tenant queries */
1654
- readonly schemaName?: string;
1655
- /**
1656
- * DB column casing convention (intuitive semantics).
1657
- * - `'snake_case'`: DB columns are snake_case → transform to camelCase for JS
1658
- * - `'camelCase'`: DB columns are camelCase → no transformation
1659
- * - `'preserve'`: No transformation
1660
- */
1661
- readonly dbCasing?: DbCasing;
1662
- /** Optional model for WHERE compilation */
1663
- readonly model?: ModelIR;
1664
- /** Optional logger for debug/error messages */
1665
- readonly logger?: AdapterLogger;
1666
- /** Default primary key column name for convention fallbacks (default: 'id') */
1667
- readonly defaultPkColumnName?: string;
1668
- /** Convention for deriving FK column names: (tableName, pkName) => fkColumnName */
1669
- readonly deriveFkColumnName?: FkColumnDerivation;
1670
- }
1671
- /**
1672
- * Adapter implementation for PostgreSQL using native pg driver.
1673
- *
1674
- * @typeParam DB - Database schema type
1675
- *
1676
- * @example
1677
- * ```typescript
1678
- * import { Pool } from 'pg';
1679
- * import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';
1680
- *
1681
- * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
1682
- * const adapter = createPgsqlAdapter(pool);
1683
- * const orm = createOrm({ model, adapter });
1684
- * ```
932
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
933
+ * const adapter = createPgsqlAdapter(pool);
934
+ * const orm = createOrm({ model, adapter });
935
+ * ```
1685
936
  */
1686
937
  declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1687
938
  private readonly pool;
1688
939
  private readonly client;
940
+ private readonly borrowedClient;
941
+ private readonly managedTransactions;
942
+ private readonly adapterManagedTransaction;
943
+ private readonly scopeToken;
944
+ private readonly scopeState;
1689
945
  private readonly schemaName;
1690
946
  private readonly _dbCasing;
1691
947
  private readonly naming;
@@ -1697,10 +953,19 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1697
953
  /**
1698
954
  * Create a new PgsqlAdapter.
1699
955
  *
1700
- * @param pool - pg.Pool instance, PoolClient (transactions), or undefined (compile-only mode)
1701
- * @param options - Optional configuration
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
1702
964
  */
1703
- constructor(pool?: Pool | PoolClient | undefined, options?: PgsqlAdapterOptions);
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);
1704
969
  /**
1705
970
  * Shared compilation dependencies — built lazily from adapter fields.
1706
971
  * Passed to compiler sub-modules instead of `this`.
@@ -1730,9 +995,9 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1730
995
  */
1731
996
  get dbCasing(): DbCasing;
1732
997
  /**
1733
- * Get the underlying pg Pool instance.
998
+ * Get the underlying pg Pool or borrowed PoolClient instance.
1734
999
  */
1735
- getPoolInstance(): Pool;
1000
+ getPoolInstance(): Pool | PoolClient;
1736
1001
  /**
1737
1002
  * Compile a plan to executable SQL.
1738
1003
  *
@@ -1862,6 +1127,9 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1862
1127
  * Internal: Stream with an existing client using cursors.
1863
1128
  */
1864
1129
  private streamWithClient;
1130
+ private streamWithManagedClient;
1131
+ private streamWithManagedClientSavepointScope;
1132
+ private streamWithClientTransaction;
1865
1133
  /**
1866
1134
  * Introspect the database schema and return a ModelIR.
1867
1135
  *
@@ -1878,8 +1146,83 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1878
1146
  introspect(options?: IntrospectionOptions): Promise<IntrospectedModelIR>;
1879
1147
  /**
1880
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.
1881
1175
  */
1882
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;
1883
1226
  /**
1884
1227
  * Create a schema-scoped adapter for multi-tenant queries.
1885
1228
  */
@@ -1888,6 +1231,15 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1888
1231
  * Execute raw SQL directly.
1889
1232
  *
1890
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.
1891
1243
  */
1892
1244
  executeRaw<T = unknown>(sql: string, parameters?: readonly unknown[]): Promise<T[]>;
1893
1245
  /**
@@ -1909,12 +1261,22 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1909
1261
  */
1910
1262
  executeDDL(sql: string): Promise<void>;
1911
1263
  /**
1912
- * Whether this adapter instance is scoped inside a transaction.
1913
- * 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.
1914
1267
  *
1915
1268
  * @since DDL-TABLE-001
1916
1269
  */
1917
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;
1918
1280
  /**
1919
1281
  * Resolve the explicit schema for a catalog read: an explicit argument, else
1920
1282
  * the adapter's configured schema, else `undefined` (resolve in-query). NOT a
@@ -1941,85 +1303,1043 @@ declare class PgsqlAdapter<DB = unknown> implements Adapter<DB> {
1941
1303
  */
1942
1304
  indexExists(name: string, table: string, schema?: string): Promise<boolean>;
1943
1305
  /**
1944
- * Return the total storage size of a table in bytes (includes indexes and TOAST).
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).
1312
+ *
1313
+ * @param table - Table name
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.
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>;
2223
+ /**
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.
1945
2227
  *
1946
- * The table name is a SQL identifier — it is double-quoted, not parameterized,
1947
- * because PostgreSQL does not allow parameterized table names in FROM clauses.
1948
- * With no known schema the table is left unqualified so ::regclass resolves it
1949
- * through search_path (the same table an unqualified reference would hit).
2228
+ * @warning SECURITY: fragments are inserted without parameterization.
2229
+ * Only use with hardcoded expressions. Never with user input.
1950
2230
  *
1951
- * @param table - Table name
1952
- * @param schema - Schema name (defaults to the search_path-resolved schema)
1953
- */
1954
- storageSize(table: string, schema?: string): Promise<number>;
1955
- /**
1956
- * Generate SQL for TRUNCATE TABLE.
1957
- * Implements TableDDLGeneratorAdapter.generateTruncate.
1958
- */
1959
- generateTruncate(table: string, schema?: string, options?: TruncateOptions): string;
1960
- /**
1961
- * Generate SQL for VACUUM.
1962
- * Implements TableDDLGeneratorAdapter.generateVacuum.
1963
- */
1964
- generateVacuum(table: string, schema?: string, options?: VacuumOptions): string;
1965
- /**
1966
- * Generate SQL for ALTER TABLE ... ALTER COLUMN.
1967
- * Implements TableDDLGeneratorAdapter.generateAlterColumn.
1968
- */
1969
- generateAlterColumn(table: string, column: string, options: AlterColumnOptions, schema?: string): string;
1970
- /**
1971
- * Generate SQL for CREATE INDEX.
1972
- * Implements TableDDLGeneratorAdapter.generateCreateIndex.
1973
- */
1974
- generateCreateIndex(table: string, options: CreateIndexOptions, schema?: string): string;
1975
- /**
1976
- * Generate SQL for DROP INDEX.
1977
- * Implements TableDDLGeneratorAdapter.generateDropIndex.
1978
- */
1979
- generateDropIndex(name: string, options?: DropIndexOptions): string;
1980
- /**
1981
- * Validate an identifier (table name, column name, schema name).
2231
+ * @example { last_parsed: 'now()', count: 'excluded.count + 1' }
1982
2232
  */
1983
- validateIdentifier(value: string, type: string): void;
2233
+ updateExpressions?: Record<string, string>;
1984
2234
  }
1985
2235
  /**
1986
- * Create a PgsqlAdapter from a pg Pool instance.
1987
- *
1988
- * @param pool - pg Pool instance
1989
- * @param options - Optional configuration
1990
- * @returns A new PgsqlAdapter instance
1991
- *
1992
- * @example
1993
- * ```typescript
1994
- * import { Pool } from 'pg';
1995
- * 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.
1996
2253
  *
1997
- * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
1998
- * 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.
1999
2262
  *
2000
- * // With naming convention
2001
- * const adapter = createPgsqlAdapter(pool, { dbCasing: 'snake_case' });
2002
- * ```
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.
2003
2266
  */
2004
- declare function createPgsqlAdapter<DB = unknown>(pool: Pool, options?: PgsqlAdapterOptions): PgsqlAdapter<DB>;
2267
+
2005
2268
  /**
2006
- * Creates a compile-only PgsqlAdapter for SQL generation without a database connection.
2269
+ * Resolve a database table name to the corresponding logical model name.
2007
2270
  *
2008
- * All compilation methods (compile, compileInsert, etc.), createDump(), and generateDDL()
2009
- * 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
2010
2278
  *
2011
2279
  * @example
2012
2280
  * ```typescript
2013
- * import { createPgsqlCompileOnlyAdapter } from '@dbsp/adapter-pgsql';
2014
- * import { createOrm } from '@dbsp/core';
2015
- *
2016
- * const adapter = createPgsqlCompileOnlyAdapter();
2017
- * const orm = createOrm({ model, adapter });
2018
- * const dump = await orm.select('users').dump();
2019
- * 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
2020
2285
  * ```
2021
2286
  */
2022
- 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
+ };
2023
2343
 
2024
2344
  /**
2025
2345
  * Redact sensitive values in a query dump's `params` array before logging.
@@ -2342,4 +2662,4 @@ declare function sanitizeForDisplay(value: string): string;
2342
2662
  */
2343
2663
  declare function validateSqlExpression(sql: string, context: string): void;
2344
2664
 
2345
- 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, bm25Search, booleanSearch, boost, buildCloseCursor, buildDeclareCursor, buildExplain, buildExplainAnalyzeJson, buildExplainPlan, buildExplainVerbose, buildFetch, buildFetchAll, buildFetchFirst, buildFetchForward, buildFetchNext, buildOnConflictClause, buildStreamingStatements, camelCaseNaming, canGenerateCreateIndex, collectAndValidateParamRefs, 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 };
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 };