@warlock.js/cascade 5.19.1 → 5.21.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/CHANGELOG.md +36 -1
- package/cjs/index.cjs +2356 -1039
- package/cjs/index.cjs.map +1 -1
- package/esm/context/database-transaction-context.d.mts +10 -0
- package/esm/context/database-transaction-context.d.mts.map +1 -1
- package/esm/context/database-transaction-context.mjs +17 -0
- package/esm/context/database-transaction-context.mjs.map +1 -1
- package/esm/contracts/query-builder.contract.d.mts +30 -3
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.d.mts +33 -1
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs +75 -5
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/database-dirty-tracker.d.mts +5 -0
- package/esm/database-dirty-tracker.d.mts.map +1 -1
- package/esm/database-dirty-tracker.mjs +16 -0
- package/esm/database-dirty-tracker.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +58 -19
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/index.d.mts +2 -3
- package/esm/drivers/postgres/index.mjs +1 -2
- package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
- package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +196 -58
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.mjs +8 -6
- package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
- package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
- package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
- package/esm/events/model-events.d.mts +1 -1
- package/esm/events/model-events.d.mts.map +1 -1
- package/esm/events/model-events.mjs.map +1 -1
- package/esm/index.d.mts +6 -6
- package/esm/index.mjs +5 -5
- package/esm/migration/column-builder.d.mts.map +1 -1
- package/esm/migration/column-builder.mjs +2 -2
- package/esm/migration/column-builder.mjs.map +1 -1
- package/esm/migration/index.d.mts +2 -2
- package/esm/migration/index.mjs +1 -1
- package/esm/migration/migration-order.mjs +5 -2
- package/esm/migration/migration-order.mjs.map +1 -1
- package/esm/migration/migration-runner.d.mts +50 -5
- package/esm/migration/migration-runner.d.mts.map +1 -1
- package/esm/migration/migration-runner.mjs +370 -147
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/migration.d.mts +36 -3
- package/esm/migration/migration.d.mts.map +1 -1
- package/esm/migration/migration.mjs +62 -5
- package/esm/migration/migration.mjs.map +1 -1
- package/esm/model/methods/accessor-methods.mjs +1 -3
- package/esm/model/methods/accessor-methods.mjs.map +1 -1
- package/esm/model/methods/delete-methods.mjs +6 -2
- package/esm/model/methods/delete-methods.mjs.map +1 -1
- package/esm/model/methods/hydration-methods.mjs +16 -2
- package/esm/model/methods/hydration-methods.mjs.map +1 -1
- package/esm/model/methods/instance-event-methods.mjs +2 -1
- package/esm/model/methods/instance-event-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs +53 -16
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +86 -15
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/static-event-methods.mjs +16 -1
- package/esm/model/methods/static-event-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.d.mts.map +1 -1
- package/esm/model/methods/write-methods.mjs +50 -50
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +16 -1
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +39 -5
- package/esm/model/model.mjs.map +1 -1
- package/esm/model/register-model.d.mts +6 -1
- package/esm/model/register-model.d.mts.map +1 -1
- package/esm/model/register-model.mjs +16 -1
- package/esm/model/register-model.mjs.map +1 -1
- package/esm/query-builder/query-builder.d.mts +21 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +97 -20
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/relations/key-conventions.mjs +13 -3
- package/esm/relations/key-conventions.mjs.map +1 -1
- package/esm/relations/pivot-operations.mjs +1 -1
- package/esm/relations/relation-loader.d.mts +15 -5
- package/esm/relations/relation-loader.d.mts.map +1 -1
- package/esm/relations/relation-loader.mjs +51 -16
- package/esm/relations/relation-loader.mjs.map +1 -1
- package/esm/remover/database-remover.d.mts.map +1 -1
- package/esm/remover/database-remover.mjs +21 -7
- package/esm/remover/database-remover.mjs.map +1 -1
- package/esm/restorer/database-restorer.d.mts +12 -2
- package/esm/restorer/database-restorer.d.mts.map +1 -1
- package/esm/restorer/database-restorer.mjs +43 -13
- package/esm/restorer/database-restorer.mjs.map +1 -1
- package/esm/sync/model-events.mjs +63 -3
- package/esm/sync/model-events.mjs.map +1 -1
- package/esm/sync/model-sync-operation.d.mts.map +1 -1
- package/esm/sync/model-sync-operation.mjs +3 -2
- package/esm/sync/model-sync-operation.mjs.map +1 -1
- package/esm/sync/sync-manager.d.mts +3 -42
- package/esm/sync/sync-manager.d.mts.map +1 -1
- package/esm/sync/sync-manager.mjs +37 -126
- package/esm/sync/sync-manager.mjs.map +1 -1
- package/esm/transactions/after-commit.d.mts +24 -0
- package/esm/transactions/after-commit.d.mts.map +1 -0
- package/esm/transactions/after-commit.mjs +47 -0
- package/esm/transactions/after-commit.mjs.map +1 -0
- package/esm/utils/connect-to-database.d.mts.map +1 -1
- package/esm/utils/connect-to-database.mjs +4 -4
- package/esm/utils/connect-to-database.mjs.map +1 -1
- package/esm/utils/database-writer.utils.d.mts +1 -1
- package/esm/utils/database-writer.utils.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.mjs +1 -0
- package/esm/utils/database-writer.utils.mjs.map +1 -1
- package/esm/utils/define-model.d.mts.map +1 -1
- package/esm/utils/define-model.mjs +2 -10
- package/esm/utils/define-model.mjs.map +1 -1
- package/esm/utils/is-valid-date-value.mjs +3 -2
- package/esm/utils/is-valid-date-value.mjs.map +1 -1
- package/esm/utils/once-connected.d.mts.map +1 -1
- package/esm/utils/once-connected.mjs +12 -2
- package/esm/utils/once-connected.mjs.map +1 -1
- package/esm/validation/mutators/embed-mutator.mjs +19 -11
- package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
- package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
- package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
- package/esm/validation/rules/database-model-rule.mjs +6 -5
- package/esm/validation/rules/database-model-rule.mjs.map +1 -1
- package/esm/validation/rules/exists-rule.mjs +6 -4
- package/esm/validation/rules/exists-rule.mjs.map +1 -1
- package/esm/validation/rules/unique-rule.mjs +5 -2
- package/esm/validation/rules/unique-rule.mjs.map +1 -1
- package/esm/validation/validators/embed-validator.d.mts.map +1 -1
- package/esm/validation/validators/embed-validator.mjs +1 -1
- package/esm/validation/validators/embed-validator.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts +27 -0
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +84 -17
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +2783 -2766
- package/llms.txt +15 -15
- package/package.json +6 -6
- package/skills/aggregate-data/SKILL.md +165 -165
- package/skills/alter-migration/SKILL.md +531 -531
- package/skills/configure-delete-strategy/SKILL.md +130 -130
- package/skills/define-model/SKILL.md +205 -205
- package/skills/define-relations/SKILL.md +171 -171
- package/skills/index.md +23 -0
- package/skills/manage-data-sources/SKILL.md +140 -140
- package/skills/manage-transactions/SKILL.md +208 -191
- package/skills/paginate-results/SKILL.md +122 -122
- package/skills/perform-atomic-ops/SKILL.md +156 -156
- package/skills/query-data/SKILL.md +213 -213
- package/skills/run-cascade-cli/SKILL.md +125 -125
- package/skills/search-by-vector/SKILL.md +127 -127
- package/skills/subscribe-to-model-events/SKILL.md +148 -148
- package/skills/track-changes/SKILL.md +127 -127
- package/skills/write-migration/SKILL.md +206 -206
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
- package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
- package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-migration-driver.mjs","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/postgres-migration-driver.ts"],"sourcesContent":["/**\r\n * PostgreSQL Migration Driver\r\n *\r\n * Implements the MigrationDriverContract for PostgreSQL DDL operations.\r\n * Provides methods for creating/dropping tables, columns, indexes,\r\n * and constraints.\r\n *\r\n * @module cascade/drivers/postgres\r\n */\r\n\r\nimport { databaseTransactionContext } from \"../../context/database-transaction-context\";\r\nimport type {\r\n ColumnDefinition,\r\n ColumnType,\r\n ForeignKeyDefinition,\r\n FullTextIndexOptions,\r\n GeoIndexOptions,\r\n IndexDefinition,\r\n MigrationDriverContract,\r\n TableIndexInformation,\r\n VectorIndexOptions,\r\n} from \"../../contracts/migration-driver.contract\";\r\nimport type { MigrationDefaults, UuidStrategy } from \"../../types\";\r\nimport type { PostgresDriver } from \"./postgres-driver\";\r\n\r\n/**\r\n * PostgreSQL Migration Driver.\r\n *\r\n * Handles database schema operations for PostgreSQL including:\r\n * - Table creation and deletion\r\n * - Column management\r\n * - Index creation (B-tree, GIN, GiST, etc.)\r\n * - Constraint management (foreign keys, unique, etc.)\r\n *\r\n * @example\r\n * ```typescript\r\n * const migrationDriver = driver.migrationDriver();\r\n *\r\n * // Create a table\r\n * await migrationDriver.createTable('users');\r\n *\r\n * // Add columns\r\n * await migrationDriver.addColumn('users', {\r\n * name: 'email',\r\n * type: 'string',\r\n * length: 255,\r\n * nullable: false\r\n * });\r\n *\r\n * // Create unique index\r\n * await migrationDriver.createUniqueIndex('users', ['email']);\r\n * ```\r\n */\r\nexport class PostgresMigrationDriver implements MigrationDriverContract {\r\n /**\r\n * Active transaction client (if any).\r\n */\r\n private get transactionClient(): unknown {\r\n return databaseTransactionContext.getSession();\r\n }\r\n\r\n /**\r\n * Create a new migration driver.\r\n *\r\n * @param driver - The PostgreSQL driver instance\r\n */\r\n public constructor(public readonly driver: PostgresDriver) {}\r\n\r\n // ============================================================================\r\n // TABLE OPERATIONS\r\n // ============================================================================\r\n\r\n /**\r\n * Create a new table with a default id column.\r\n *\r\n * @param table - Table name\r\n */\r\n public async createTable(table: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n // Create empty table - columns are added via addColumn operations\r\n await this.execute(`CREATE TABLE ${quotedTable} ()`);\r\n }\r\n\r\n /**\r\n * Create table if it doesn't exist.\r\n *\r\n * @param table - Table name\r\n */\r\n public async createTableIfNotExists(table: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n // Create empty table if not exists - columns are added via addColumn operations\r\n await this.execute(`CREATE TABLE IF NOT EXISTS ${quotedTable} ()`);\r\n }\r\n\r\n /**\r\n * Drop an existing table.\r\n *\r\n * @param table - Table name\r\n */\r\n public async dropTable(table: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n await this.execute(`DROP TABLE ${quotedTable} CASCADE`);\r\n }\r\n\r\n /**\r\n * Drop table if it exists.\r\n *\r\n * @param table - Table name\r\n */\r\n public async dropTableIfExists(table: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n await this.execute(`DROP TABLE IF EXISTS ${quotedTable} CASCADE`);\r\n }\r\n\r\n /**\r\n * Rename a table.\r\n *\r\n * @param from - Current table name\r\n * @param to - New table name\r\n */\r\n public async renameTable(from: string, to: string): Promise<void> {\r\n const quotedFrom = this.driver.dialect.quoteIdentifier(from);\r\n const quotedTo = this.driver.dialect.quoteIdentifier(to);\r\n await this.execute(`ALTER TABLE ${quotedFrom} RENAME TO ${quotedTo}`);\r\n }\r\n\r\n /**\r\n * Truncate a table — remove all rows efficiently.\r\n *\r\n * @param table - Table name\r\n */\r\n public async truncateTable(table: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n await this.execute(`TRUNCATE TABLE ${quotedTable}`);\r\n }\r\n\r\n /**\r\n * Check if a table exists.\r\n *\r\n * @param table - Table name\r\n * @returns Whether the table exists\r\n */\r\n public async tableExists(table: string): Promise<boolean> {\r\n const result = await this.driver.query<{ exists: boolean }>(\r\n `SELECT EXISTS (\r\n SELECT FROM information_schema.tables\r\n WHERE table_schema = 'public'\r\n AND table_name = $1\r\n )`,\r\n [table],\r\n );\r\n return result.rows[0]?.exists ?? false;\r\n }\r\n\r\n /**\r\n * List all columns in a table.\r\n *\r\n * @param table - Table name\r\n * @returns Array of column definitions\r\n */\r\n public async listColumns(table: string): Promise<ColumnDefinition[]> {\r\n const result = await this.driver.query<{\r\n column_name: string;\r\n data_type: string;\r\n character_maximum_length: number | null;\r\n numeric_precision: number | null;\r\n numeric_scale: number | null;\r\n is_nullable: string;\r\n column_default: string | null;\r\n }>(\r\n `SELECT\r\n column_name,\r\n data_type,\r\n character_maximum_length,\r\n numeric_precision,\r\n numeric_scale,\r\n is_nullable,\r\n column_default\r\n FROM information_schema.columns\r\n WHERE table_schema = 'public'\r\n AND table_name = $1\r\n ORDER BY ordinal_position`,\r\n [table],\r\n );\r\n\r\n return result.rows.map((row) => ({\r\n name: row.column_name,\r\n type: this.mapPostgresTypeToColumnType(row.data_type),\r\n length: row.character_maximum_length ?? undefined,\r\n precision: row.numeric_precision ?? undefined,\r\n scale: row.numeric_scale ?? undefined,\r\n nullable: row.is_nullable === \"YES\",\r\n defaultValue: row.column_default ?? undefined,\r\n }));\r\n }\r\n\r\n /**\r\n * List all tables in the current database.\r\n *\r\n * @returns Array of table names\r\n */\r\n public async listTables(): Promise<string[]> {\r\n const result = await this.driver.query<{ table_name: string }>(\r\n `SELECT table_name\r\n FROM information_schema.tables\r\n WHERE table_schema = 'public'\r\n ORDER BY table_name`,\r\n );\r\n\r\n return result.rows.map((row) => row.table_name);\r\n }\r\n\r\n /**\r\n * Ensure the migrations tracking table exists.\r\n *\r\n * Creates the table with proper schema if it doesn't exist.\r\n *\r\n * @param tableName - Name of the migrations table\r\n */\r\n public async ensureMigrationsTable(tableName: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(tableName);\r\n\r\n await this.execute(`\r\n CREATE TABLE IF NOT EXISTS ${quotedTable} (\r\n \"id\" SERIAL PRIMARY KEY,\r\n \"name\" VARCHAR(255) NOT NULL UNIQUE,\r\n \"batch\" INTEGER NOT NULL,\r\n \"executedAt\" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\r\n \"createdAt\" TIMESTAMP WITH TIME ZONE\r\n )\r\n `);\r\n }\r\n\r\n // ============================================================================\r\n // COLUMN OPERATIONS\r\n // ============================================================================\r\n\r\n /**\r\n * Add a column to an existing table.\r\n *\r\n * @param table - Table name\r\n * @param column - Column definition\r\n */\r\n public async addColumn(table: string, column: ColumnDefinition): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumn = this.driver.dialect.quoteIdentifier(column.name);\r\n\r\n // For auto-increment integers, use SERIAL/BIGSERIAL instead of INTEGER/BIGINT\r\n let sqlType: string;\r\n if (column.autoIncrement) {\r\n if (column.type === \"bigInteger\") {\r\n sqlType = \"BIGSERIAL\";\r\n } else {\r\n sqlType = \"SERIAL\";\r\n }\r\n } else {\r\n sqlType = this.driver.dialect.getSqlType(column.type, {\r\n length: column.length,\r\n precision: column.precision,\r\n scale: column.scale,\r\n dimensions: column.dimensions,\r\n });\r\n }\r\n\r\n let sql = `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedColumn} ${sqlType}`;\r\n\r\n // Handle generated columns\r\n if (column.generated) {\r\n sql += ` GENERATED ALWAYS AS (${column.generated.expression})`;\r\n if (column.generated.stored) {\r\n sql += \" STORED\";\r\n }\r\n // PostgreSQL only supports STORED, not VIRTUAL\r\n // If virtual is requested, it's simply ignored (PostgreSQL doesn't support it)\r\n } else {\r\n // SERIAL/BIGSERIAL are always NOT NULL, so skip for those\r\n if (!column.autoIncrement && column.nullable === false) {\r\n sql += \" NOT NULL\";\r\n }\r\n\r\n if (column.defaultValue !== undefined) {\r\n // Check for special CURRENT_TIMESTAMP marker\r\n if (\r\n typeof column.defaultValue === \"object\" &&\r\n column.defaultValue !== null &&\r\n (column.defaultValue as any).__type === \"CURRENT_TIMESTAMP\"\r\n ) {\r\n sql += \" DEFAULT NOW()\";\r\n } else if (column.isRawDefault === false) {\r\n // Explicit string literal - escape it properly\r\n const escaped = String(column.defaultValue).replace(/'/g, \"''\");\r\n sql += ` DEFAULT '${escaped}'`;\r\n } else if (typeof column.defaultValue === \"boolean\") {\r\n // Boolean values\r\n sql += ` DEFAULT ${column.defaultValue ? \"TRUE\" : \"FALSE\"}`;\r\n } else if (typeof column.defaultValue === \"number\") {\r\n // Numeric values\r\n sql += ` DEFAULT ${column.defaultValue}`;\r\n } else {\r\n // Raw SQL expression (default behavior when isRawDefault is true or undefined)\r\n sql += ` DEFAULT ${column.defaultValue}`;\r\n }\r\n }\r\n\r\n // Handle primary key\r\n if (column.primary) {\r\n sql += \" PRIMARY KEY\";\r\n }\r\n\r\n // Handle unique constraint\r\n if (column.unique) {\r\n sql += \" UNIQUE\";\r\n }\r\n }\r\n\r\n await this.execute(sql);\r\n }\r\n\r\n /**\r\n * Drop a column from a table.\r\n *\r\n * @param table - Table name\r\n * @param column - Column name\r\n */\r\n public async dropColumn(table: string, column: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\r\n await this.execute(`ALTER TABLE ${quotedTable} DROP COLUMN ${quotedColumn}`);\r\n }\r\n\r\n /**\r\n * Drop multiple columns from a table.\r\n *\r\n * @param table - Table name\r\n * @param columns - Column names\r\n */\r\n public async dropColumns(table: string, columns: string[]): Promise<void> {\r\n for (const column of columns) {\r\n await this.dropColumn(table, column);\r\n }\r\n }\r\n\r\n /**\r\n * Rename a column.\r\n *\r\n * @param table - Table name\r\n * @param from - Current column name\r\n * @param to - New column name\r\n */\r\n public async renameColumn(table: string, from: string, to: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedFrom = this.driver.dialect.quoteIdentifier(from);\r\n const quotedTo = this.driver.dialect.quoteIdentifier(to);\r\n await this.execute(`ALTER TABLE ${quotedTable} RENAME COLUMN ${quotedFrom} TO ${quotedTo}`);\r\n }\r\n\r\n /**\r\n * Modify an existing column.\r\n *\r\n * @param table - Table name\r\n * @param column - New column definition\r\n */\r\n public async modifyColumn(table: string, column: ColumnDefinition): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumn = this.driver.dialect.quoteIdentifier(column.name);\r\n const sqlType = this.driver.dialect.getSqlType(column.type, {\r\n length: column.length,\r\n precision: column.precision,\r\n scale: column.scale,\r\n dimensions: column.dimensions,\r\n });\r\n\r\n // PostgreSQL requires separate ALTER statements for type and nullability\r\n await this.execute(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} TYPE ${sqlType}`);\r\n\r\n if (column.nullable === false) {\r\n await this.execute(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} SET NOT NULL`);\r\n } else if (column.nullable === true) {\r\n await this.execute(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} DROP NOT NULL`);\r\n }\r\n\r\n if (column.defaultValue !== undefined) {\r\n let defaultVal: string;\r\n\r\n // Check for special CURRENT_TIMESTAMP marker\r\n if (\r\n typeof column.defaultValue === \"object\" &&\r\n column.defaultValue !== null &&\r\n (column.defaultValue as any).__type === \"CURRENT_TIMESTAMP\"\r\n ) {\r\n defaultVal = \"NOW()\";\r\n } else if (typeof column.defaultValue === \"string\") {\r\n defaultVal = `'${column.defaultValue}'`;\r\n } else {\r\n defaultVal = String(column.defaultValue);\r\n }\r\n\r\n await this.execute(\r\n `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} SET DEFAULT ${defaultVal}`,\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Create standard timestamp columns (created_at, updated_at).\r\n *\r\n * PostgreSQL implementation creates TIMESTAMPTZ columns with NOW() defaults.\r\n *\r\n * @param table - Table name\r\n */\r\n public async createTimestampColumns(table: string): Promise<void> {\r\n await this.addColumn(table, {\r\n name: \"created_at\",\r\n type: \"timestamp\",\r\n nullable: false,\r\n defaultValue: \"NOW()\",\r\n isRawDefault: true,\r\n });\r\n\r\n await this.addColumn(table, {\r\n name: \"updated_at\",\r\n type: \"timestamp\",\r\n nullable: false,\r\n defaultValue: \"NOW()\",\r\n isRawDefault: true,\r\n });\r\n }\r\n\r\n // ============================================================================\r\n // INDEX OPERATIONS\r\n // ============================================================================\r\n\r\n /**\r\n * Create an index on one or more columns.\r\n *\r\n * Supports:\r\n * - Regular column indexes\r\n * - Expression-based indexes (e.g., `lower(email)`)\r\n * - Covering indexes (INCLUDE clause)\r\n * - Concurrent index creation (CONCURRENTLY keyword)\r\n *\r\n * @param table - Table name\r\n * @param index - Index definition\r\n */\r\n public async createIndex(table: string, index: IndexDefinition): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const indexName = index.name ?? `idx_${table}_${index.columns.join(\"_\")}`;\r\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\r\n const uniqueKeyword = index.unique ? \"UNIQUE \" : \"\";\r\n const concurrentlyKeyword = index.concurrently ? \"CONCURRENTLY \" : \"\";\r\n\r\n let columnsPart: string;\r\n\r\n // Handle expression-based indexes\r\n if (index.expressions && index.expressions.length > 0) {\r\n // Wrap each expression in parentheses\r\n columnsPart = index.expressions.map((expr) => `(${expr})`).join(\", \");\r\n } else {\r\n // Regular column indexes\r\n const columns = index.columns.map((col, i) => {\r\n const quotedCol = this.driver.dialect.quoteIdentifier(col);\r\n const direction = index.directions?.[i]?.toUpperCase() ?? \"\";\r\n return direction ? `${quotedCol} ${direction}` : quotedCol;\r\n });\r\n columnsPart = columns.join(\", \");\r\n }\r\n\r\n let sql = `CREATE ${uniqueKeyword}INDEX ${concurrentlyKeyword}${quotedIndexName} ON ${quotedTable} (${columnsPart})`;\r\n\r\n // Add INCLUDE clause for covering indexes\r\n if (index.include && index.include.length > 0) {\r\n const includeCols = index.include\r\n .map((col) => this.driver.dialect.quoteIdentifier(col))\r\n .join(\", \");\r\n sql += ` INCLUDE (${includeCols})`;\r\n }\r\n\r\n // Add partial index condition\r\n if (index.where && Object.keys(index.where).length > 0) {\r\n const conditions = Object.entries(index.where)\r\n .map(([key, value]) => {\r\n const quotedKey = this.driver.dialect.quoteIdentifier(key);\r\n return typeof value === \"string\"\r\n ? `${quotedKey} = '${value}'`\r\n : `${quotedKey} = ${value}`;\r\n })\r\n .join(\" AND \");\r\n sql += ` WHERE ${conditions}`;\r\n }\r\n\r\n await this.execute(sql);\r\n }\r\n\r\n /**\r\n * Drop an index.\r\n *\r\n * @param table - Table name\r\n * @param indexNameOrColumns - Index name or columns\r\n */\r\n public async dropIndex(table: string, indexNameOrColumns: string | string[]): Promise<void> {\r\n let indexName: string;\r\n\r\n if (typeof indexNameOrColumns === \"string\") {\r\n indexName = indexNameOrColumns;\r\n } else {\r\n indexName = `idx_${table}_${indexNameOrColumns.join(\"_\")}`;\r\n }\r\n\r\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\r\n await this.execute(`DROP INDEX IF EXISTS ${quotedIndexName}`);\r\n }\r\n\r\n /**\r\n * Create a unique index.\r\n *\r\n * @param table - Table name\r\n * @param columns - Columns to include\r\n * @param name - Optional index name\r\n */\r\n public async createUniqueIndex(table: string, columns: string[], name?: string): Promise<void> {\r\n await this.createIndex(table, { columns, unique: true, name });\r\n }\r\n\r\n /**\r\n * Drop a unique index.\r\n *\r\n * @param table - Table name\r\n * @param columns - Columns in the index\r\n */\r\n public async dropUniqueIndex(table: string, columns: string[]): Promise<void> {\r\n await this.dropIndex(table, columns);\r\n }\r\n\r\n // ============================================================================\r\n // SPECIALIZED INDEXES\r\n // ============================================================================\r\n\r\n /**\r\n * Create a full-text search index using GIN.\r\n *\r\n * @param table - Table name\r\n * @param columns - Columns to index\r\n * @param options - Full-text options\r\n */\r\n public async createFullTextIndex(\r\n table: string,\r\n columns: string[],\r\n options?: FullTextIndexOptions,\r\n ): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const indexName = options?.name ?? `idx_${table}_fulltext_${columns.join(\"_\")}`;\r\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\r\n const language = options?.language ?? \"english\";\r\n\r\n const tsvectors = columns.map((col) => {\r\n const weight = options?.weights?.[col] ?? \"A\";\r\n return `setweight(to_tsvector('${language}', COALESCE(${this.driver.dialect.quoteIdentifier(col)}, '')), '${weight}')`;\r\n });\r\n\r\n await this.execute(\r\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} USING GIN ((${tsvectors.join(\" || \")}))`,\r\n );\r\n }\r\n\r\n /**\r\n * Drop a full-text search index.\r\n *\r\n * @param table - Table name\r\n * @param name - Index name\r\n */\r\n public async dropFullTextIndex(table: string, name: string): Promise<void> {\r\n await this.dropIndex(table, name);\r\n }\r\n\r\n /**\r\n * Create a geo-spatial index using GiST.\r\n *\r\n * @param table - Table name\r\n * @param column - Geo column\r\n * @param options - Geo index options\r\n */\r\n public async createGeoIndex(\r\n table: string,\r\n column: string,\r\n options?: GeoIndexOptions,\r\n ): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\r\n const indexName = options?.name ?? `idx_${table}_geo_${column}`;\r\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\r\n\r\n await this.execute(\r\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} USING GIST (${quotedColumn})`,\r\n );\r\n }\r\n\r\n /**\r\n * Drop a geo-spatial index.\r\n *\r\n * @param table - Table name\r\n * @param column - Geo column\r\n */\r\n public async dropGeoIndex(table: string, column: string): Promise<void> {\r\n await this.dropIndex(table, `idx_${table}_geo_${column}`);\r\n }\r\n\r\n /**\r\n * Create a vector search index (requires pgvector extension).\r\n *\r\n * @param table - Table name\r\n * @param column - Vector column\r\n * @param options - Vector index options\r\n */\r\n public async createVectorIndex(\r\n table: string,\r\n column: string,\r\n options: VectorIndexOptions,\r\n ): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\r\n const indexName = options.name ?? `idx_${table}_vector_${column}`;\r\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\r\n\r\n // Map similarity to pgvector operator class\r\n const opClass =\r\n options.similarity === \"euclidean\"\r\n ? \"vector_l2_ops\"\r\n : options.similarity === \"dotProduct\"\r\n ? \"vector_ip_ops\"\r\n : \"vector_cosine_ops\";\r\n\r\n const lists = options.lists ?? 100;\r\n\r\n await this.execute(\r\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} USING ivfflat (${quotedColumn} ${opClass}) WITH (lists = ${lists})`,\r\n );\r\n }\r\n\r\n /**\r\n * Drop a vector search index.\r\n *\r\n * @param table - Table name\r\n * @param column - Vector column\r\n */\r\n public async dropVectorIndex(table: string, column: string): Promise<void> {\r\n await this.dropIndex(table, `idx_${table}_vector_${column}`);\r\n }\r\n\r\n /**\r\n * Create a TTL index (not natively supported in PostgreSQL).\r\n *\r\n * Note: PostgreSQL doesn't have native TTL indexes like MongoDB.\r\n * This creates a partial index and requires a scheduled job for cleanup.\r\n *\r\n * @param table - Table name\r\n * @param column - Date column\r\n * @param expireAfterSeconds - Expiration time in seconds\r\n */\r\n public async createTTLIndex(\r\n table: string,\r\n column: string,\r\n expireAfterSeconds: number,\r\n ): Promise<void> {\r\n // Create a partial index for expired rows (for efficient cleanup queries)\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\r\n const indexName = `idx_${table}_ttl_${column}`;\r\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\r\n\r\n await this.execute(\r\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} (${quotedColumn}) WHERE ${quotedColumn} < NOW() - INTERVAL '${expireAfterSeconds} seconds'`,\r\n );\r\n\r\n // Note: User must set up a scheduled job (pg_cron, etc.) to:\r\n // DELETE FROM table WHERE column < NOW() - INTERVAL 'X seconds'\r\n }\r\n\r\n /**\r\n * Drop a TTL index.\r\n *\r\n * @param table - Table name\r\n * @param column - Column with TTL index\r\n */\r\n public async dropTTLIndex(table: string, column: string): Promise<void> {\r\n await this.dropIndex(table, `idx_${table}_ttl_${column}`);\r\n }\r\n\r\n /**\r\n * List all indexes on a table.\r\n *\r\n * @param table - Table name\r\n * @returns Array of index metadata\r\n */\r\n public async listIndexes(table: string): Promise<TableIndexInformation[]> {\r\n const result = await this.driver.query<{ indexname: string; indexdef: string }>(\r\n `SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' AND tablename = $1`,\r\n [table],\r\n );\r\n\r\n return result.rows.map((row) => {\r\n const isUnique = row.indexdef.includes(\"UNIQUE\");\r\n const isPrimary = row.indexname.endsWith(\"_pkey\");\r\n const columnsMatch = row.indexdef.match(/\\(([^)]+)\\)/);\r\n // Capture group 1 is present whenever the pattern matched, but that is a\r\n // property of the regex literal rather than of this expression. `?? \"\"`\r\n // yields the empty column list the `: []` branch already gives.\r\n const columns = (columnsMatch?.[1] ?? \"\")\r\n .split(\",\")\r\n .map((c) => c.trim().replace(/\"/g, \"\"))\r\n .filter(Boolean);\r\n\r\n let type = \"btree\";\r\n if (row.indexdef.includes(\"USING GIN\")) type = \"gin\";\r\n else if (row.indexdef.includes(\"USING GIST\")) type = \"gist\";\r\n else if (row.indexdef.includes(\"USING HASH\")) type = \"hash\";\r\n else if (row.indexdef.includes(\"USING ivfflat\")) type = \"ivfflat\";\r\n\r\n return {\r\n name: row.indexname,\r\n columns,\r\n type,\r\n unique: isUnique || isPrimary,\r\n partial: row.indexdef.includes(\"WHERE\"),\r\n options: { primary: isPrimary, definition: row.indexdef },\r\n };\r\n });\r\n }\r\n\r\n // ============================================================================\r\n // EXTENSIONS\r\n // ============================================================================\r\n\r\n /**\r\n * Check if a PostgreSQL extension is available on the database server.\r\n *\r\n * @param extension - Extension name (e.g., \"vector\")\r\n */\r\n public async isExtensionAvailable(extension: string): Promise<boolean> {\r\n try {\r\n const result = await this.driver.query<{ name: string }>(\r\n `SELECT name FROM pg_available_extensions WHERE name = $1`,\r\n [extension],\r\n );\r\n\r\n return result?.rows?.length > 0;\r\n } catch {\r\n // If we can't check (e.g., permissions issue or pg_available_extensions not accessible),\r\n // we return true to avoid false positives. Execution will just proceed and fail naturally if it's missing.\r\n return true;\r\n }\r\n }\r\n\r\n /**\r\n * Get the official documentation or installation URL for a PostgreSQL extension.\r\n *\r\n * @param extension - Extension name\r\n */\r\n public getExtensionDocsUrl(extension: string): string | undefined {\r\n const guideUrls: Record<string, string> = {\r\n vector: \"https://github.com/pgvector/pgvector#installation\",\r\n postgis: \"https://postgis.net/documentation/getting_started/\",\r\n pg_trgm: \"https://www.postgresql.org/docs/current/pgtrgm.html\",\r\n uuid_ossp: \"https://www.postgresql.org/docs/current/uuid-ossp.html\",\r\n };\r\n\r\n return guideUrls[extension] ?? `https://www.postgresql.org/docs/current/${extension}.html`;\r\n }\r\n\r\n // ============================================================================\r\n // CONSTRAINTS\r\n // ============================================================================\r\n\r\n /**\r\n * Add a foreign key constraint.\r\n *\r\n * @param table - Table name\r\n * @param foreignKey - Foreign key definition\r\n */\r\n public async addForeignKey(table: string, foreignKey: ForeignKeyDefinition): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumn = this.driver.dialect.quoteIdentifier(foreignKey.column);\r\n const quotedRefTable = this.driver.dialect.quoteIdentifier(foreignKey.referencesTable);\r\n const quotedRefColumn = this.driver.dialect.quoteIdentifier(foreignKey.referencesColumn);\r\n\r\n const constraintName =\r\n foreignKey.name ?? `fk_${table}_${foreignKey.column}_${foreignKey.referencesTable}`;\r\n const quotedConstraint = this.driver.dialect.quoteIdentifier(constraintName);\r\n\r\n let sql = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${quotedConstraint} FOREIGN KEY (${quotedColumn}) REFERENCES ${quotedRefTable} (${quotedRefColumn})`;\r\n\r\n if (foreignKey.onDelete) {\r\n sql += ` ON DELETE ${this.mapForeignKeyAction(foreignKey.onDelete)}`;\r\n }\r\n\r\n if (foreignKey.onUpdate) {\r\n sql += ` ON UPDATE ${this.mapForeignKeyAction(foreignKey.onUpdate)}`;\r\n }\r\n\r\n await this.execute(sql);\r\n }\r\n\r\n /**\r\n * Drop a foreign key constraint.\r\n *\r\n * @param table - Table name\r\n * @param name - Constraint name\r\n */\r\n public async dropForeignKey(table: string, name: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedConstraint = this.driver.dialect.quoteIdentifier(name);\r\n await this.execute(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quotedConstraint}`);\r\n }\r\n\r\n /**\r\n * Add a primary key constraint.\r\n *\r\n * @param table - Table name\r\n * @param columns - Primary key columns\r\n */\r\n public async addPrimaryKey(table: string, columns: string[]): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedColumns = columns.map((c) => this.driver.dialect.quoteIdentifier(c)).join(\", \");\r\n const constraintName = `pk_${table}`;\r\n const quotedConstraint = this.driver.dialect.quoteIdentifier(constraintName);\r\n\r\n await this.execute(\r\n `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${quotedConstraint} PRIMARY KEY (${quotedColumns})`,\r\n );\r\n }\r\n\r\n /**\r\n * Drop the primary key constraint.\r\n *\r\n * @param table - Table name\r\n */\r\n public async dropPrimaryKey(table: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const constraintName = `pk_${table}`;\r\n const quotedConstraint = this.driver.dialect.quoteIdentifier(constraintName);\r\n\r\n await this.execute(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quotedConstraint}`);\r\n }\r\n\r\n /**\r\n * Add a CHECK constraint.\r\n *\r\n * @param table - Table name\r\n * @param name - Constraint name\r\n * @param expression - SQL CHECK expression\r\n */\r\n public async addCheck(table: string, name: string, expression: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedName = this.driver.dialect.quoteIdentifier(name);\r\n const sql = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${quotedName} CHECK (${expression})`;\r\n await this.execute(sql);\r\n }\r\n\r\n /**\r\n * Drop a CHECK constraint.\r\n *\r\n * @param table - Table name\r\n * @param name - Constraint name\r\n */\r\n public async dropCheck(table: string, name: string): Promise<void> {\r\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\r\n const quotedName = this.driver.dialect.quoteIdentifier(name);\r\n await this.execute(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quotedName}`);\r\n }\r\n\r\n // ============================================================================\r\n // SCHEMA VALIDATION (NOT APPLICABLE FOR PostgreSQL)\r\n // ============================================================================\r\n\r\n /**\r\n * Set schema validation (no-op for PostgreSQL).\r\n *\r\n * PostgreSQL uses column constraints instead.\r\n */\r\n public async setSchemaValidation(_table: string, _schema: object): Promise<void> {\r\n // No-op: PostgreSQL doesn't have MongoDB-style schema validation\r\n // Use CHECK constraints instead\r\n }\r\n\r\n /**\r\n * Remove schema validation (no-op for PostgreSQL).\r\n */\r\n public async removeSchemaValidation(_table: string): Promise<void> {\r\n // No-op\r\n }\r\n\r\n // ============================================================================\r\n // TRANSACTIONS\r\n // ============================================================================\r\n\r\n /**\r\n * Begin a transaction.\r\n */\r\n public async beginTransaction(): Promise<void> {\r\n await this.execute(\"BEGIN\");\r\n }\r\n\r\n /**\r\n * Commit the current transaction.\r\n */\r\n public async commit(): Promise<void> {\r\n await this.execute(\"COMMIT\");\r\n }\r\n\r\n /**\r\n * Rollback the current transaction.\r\n */\r\n public async rollback(): Promise<void> {\r\n await this.execute(\"ROLLBACK\");\r\n }\r\n\r\n /**\r\n * Whether transactions are supported.\r\n */\r\n public supportsTransactions(): boolean {\r\n return true;\r\n }\r\n\r\n /**\r\n * Get the default transactional behavior for PostgreSQL.\r\n *\r\n * PostgreSQL supports transactional DDL operations, so migrations\r\n * are wrapped in transactions by default for atomicity and safety.\r\n *\r\n * @returns true (PostgreSQL DDL is transactional)\r\n */\r\n public getDefaultTransactional(): boolean {\r\n return true;\r\n }\r\n\r\n // ============================================================================\r\n // DEFAULTS\r\n // ============================================================================\r\n\r\n /**\r\n * Get the default UUID generation expression for PostgreSQL.\r\n *\r\n * Resolution order:\r\n * 1. `migrationDefaults.uuidExpression` → raw expression (escape hatch)\r\n * 2. `migrationDefaults.uuidStrategy` → mapped to PG function\r\n * 3. Fallback → `gen_random_uuid()` (v4, PG 13+)\r\n *\r\n * @param migrationDefaults - Optional overrides from DataSource config\r\n * @returns PostgreSQL SQL expression for UUID generation\r\n *\r\n * @example\r\n * ```typescript\r\n * driver.getUuidDefault(); // \"gen_random_uuid()\"\r\n * driver.getUuidDefault({ uuidStrategy: \"v7\" }); // \"uuidv7()\"\r\n * driver.getUuidDefault({ uuidExpression: \"uuid_generate_v1mc()\" }); // \"uuid_generate_v1mc()\"\r\n * ```\r\n */\r\n public getUuidDefault(migrationDefaults?: MigrationDefaults): string {\r\n // Escape hatch: raw expression takes highest precedence\r\n if (migrationDefaults?.uuidExpression) {\r\n return migrationDefaults.uuidExpression;\r\n }\r\n\r\n const strategy = migrationDefaults?.uuidStrategy ?? \"v4\";\r\n\r\n const strategyMap: Record<UuidStrategy, string> = {\r\n v4: \"gen_random_uuid()\",\r\n v7: \"uuidv7()\",\r\n };\r\n\r\n return strategyMap[strategy];\r\n }\r\n\r\n // ============================================================================\r\n // RAW ACCESS\r\n // ============================================================================\r\n\r\n /**\r\n * Execute raw operations with direct driver access.\r\n *\r\n * @param callback - Callback receiving the driver\r\n */\r\n public async raw<T>(callback: (connection: unknown) => Promise<T>): Promise<T> {\r\n return callback(this.driver);\r\n }\r\n\r\n // ============================================================================\r\n // HELPERS\r\n // ============================================================================\r\n\r\n /**\r\n * Execute a SQL statement.\r\n *\r\n * @param sql - SQL to execute\r\n * @param params - Query parameters\r\n */\r\n private async execute(sql: string, params: unknown[] = []): Promise<void> {\r\n await this.driver.query(sql, params);\r\n }\r\n\r\n /**\r\n * Map foreign key action to PostgreSQL syntax.\r\n */\r\n private mapForeignKeyAction(action: \"cascade\" | \"restrict\" | \"setNull\" | \"noAction\"): string {\r\n const mapping: Record<string, string> = {\r\n cascade: \"CASCADE\",\r\n restrict: \"RESTRICT\",\r\n setNull: \"SET NULL\",\r\n noAction: \"NO ACTION\",\r\n };\r\n return mapping[action] ?? \"NO ACTION\";\r\n }\r\n\r\n /**\r\n * Map PostgreSQL data type to ColumnType.\r\n */\r\n private mapPostgresTypeToColumnType(pgType: string): ColumnType {\r\n const typeMap: Record<string, ColumnType> = {\r\n \"character varying\": \"string\",\r\n varchar: \"string\",\r\n character: \"char\",\r\n char: \"char\",\r\n text: \"text\",\r\n integer: \"integer\",\r\n int: \"integer\",\r\n smallint: \"smallInteger\",\r\n bigint: \"bigInteger\",\r\n real: \"float\",\r\n \"double precision\": \"double\",\r\n numeric: \"decimal\",\r\n decimal: \"decimal\",\r\n boolean: \"boolean\",\r\n date: \"date\",\r\n timestamp: \"dateTime\",\r\n \"timestamp without time zone\": \"dateTime\",\r\n \"timestamp with time zone\": \"timestamp\",\r\n time: \"time\",\r\n \"time without time zone\": \"time\",\r\n json: \"json\",\r\n jsonb: \"json\",\r\n bytea: \"binary\",\r\n uuid: \"uuid\",\r\n inet: \"ipAddress\",\r\n macaddr: \"macAddress\",\r\n point: \"point\",\r\n polygon: \"polygon\",\r\n line: \"lineString\",\r\n geometry: \"geometry\",\r\n // PostgreSQL array types\r\n \"integer[]\": \"arrayInt\",\r\n \"int[]\": \"arrayInt\",\r\n \"bigint[]\": \"arrayBigInt\",\r\n \"real[]\": \"arrayFloat\",\r\n \"decimal[]\": \"arrayDecimal\",\r\n \"numeric[]\": \"arrayDecimal\",\r\n \"boolean[]\": \"arrayBoolean\",\r\n \"text[]\": \"arrayText\",\r\n \"date[]\": \"arrayDate\",\r\n \"timestamp with time zone[]\": \"arrayTimestamp\",\r\n \"timestamptz[]\": \"arrayTimestamp\",\r\n \"uuid[]\": \"arrayUuid\",\r\n \"jsonb[]\": \"arrayJson\",\r\n \"json[]\": \"arrayJson\",\r\n };\r\n\r\n return typeMap[pgType.toLowerCase()] ?? \"string\";\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"postgres-migration-driver.mjs","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/postgres-migration-driver.ts"],"sourcesContent":["/**\n * PostgreSQL Migration Driver\n *\n * Implements the MigrationDriverContract for PostgreSQL DDL operations.\n * Provides methods for creating/dropping tables, columns, indexes,\n * and constraints.\n *\n * @module cascade/drivers/postgres\n */\n\nimport { databaseTransactionContext } from \"../../context/database-transaction-context\";\nimport type {\n ColumnDefinition,\n ColumnType,\n ForeignKeyDefinition,\n FullTextIndexOptions,\n GeoIndexOptions,\n IndexDefinition,\n MigrationDriverContract,\n TableIndexInformation,\n VectorIndexOptions,\n} from \"../../contracts/migration-driver.contract\";\nimport type { MigrationDefaults, UuidStrategy } from \"../../types\";\nimport type { PostgresDriver } from \"./postgres-driver\";\n\n/**\n * PostgreSQL Migration Driver.\n *\n * Handles database schema operations for PostgreSQL including:\n * - Table creation and deletion\n * - Column management\n * - Index creation (B-tree, GIN, GiST, etc.)\n * - Constraint management (foreign keys, unique, etc.)\n *\n * @example\n * ```typescript\n * const migrationDriver = driver.migrationDriver();\n *\n * // Create a table\n * await migrationDriver.createTable('users');\n *\n * // Add columns\n * await migrationDriver.addColumn('users', {\n * name: 'email',\n * type: 'string',\n * length: 255,\n * nullable: false\n * });\n *\n * // Create unique index\n * await migrationDriver.createUniqueIndex('users', ['email']);\n * ```\n */\nexport class PostgresMigrationDriver implements MigrationDriverContract {\n /**\n * Active transaction client (if any).\n */\n private get transactionClient(): unknown {\n return databaseTransactionContext.getSession();\n }\n\n /**\n * Create a new migration driver.\n *\n * @param driver - The PostgreSQL driver instance\n */\n public constructor(public readonly driver: PostgresDriver) {}\n\n // ============================================================================\n // TABLE OPERATIONS\n // ============================================================================\n\n /**\n * Create a new table with a default id column.\n *\n * @param table - Table name\n */\n public async createTable(table: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n // Create empty table - columns are added via addColumn operations\n await this.execute(`CREATE TABLE ${quotedTable} ()`);\n }\n\n /**\n * Create table if it doesn't exist.\n *\n * @param table - Table name\n */\n public async createTableIfNotExists(table: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n // Create empty table if not exists - columns are added via addColumn operations\n await this.execute(`CREATE TABLE IF NOT EXISTS ${quotedTable} ()`);\n }\n\n /**\n * Drop an existing table.\n *\n * @param table - Table name\n */\n public async dropTable(table: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n await this.execute(`DROP TABLE ${quotedTable} CASCADE`);\n }\n\n /**\n * Drop table if it exists.\n *\n * @param table - Table name\n */\n public async dropTableIfExists(table: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n await this.execute(`DROP TABLE IF EXISTS ${quotedTable} CASCADE`);\n }\n\n /**\n * Rename a table.\n *\n * @param from - Current table name\n * @param to - New table name\n */\n public async renameTable(from: string, to: string): Promise<void> {\n const quotedFrom = this.driver.dialect.quoteIdentifier(from);\n const quotedTo = this.driver.dialect.quoteIdentifier(to);\n await this.execute(`ALTER TABLE ${quotedFrom} RENAME TO ${quotedTo}`);\n }\n\n /**\n * Truncate a table — remove all rows efficiently.\n *\n * @param table - Table name\n */\n public async truncateTable(table: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n await this.execute(`TRUNCATE TABLE ${quotedTable}`);\n }\n\n /**\n * Check if a table exists.\n *\n * @param table - Table name\n * @returns Whether the table exists\n */\n public async tableExists(table: string): Promise<boolean> {\n const result = await this.driver.query<{ exists: boolean }>(\n `SELECT EXISTS (\n SELECT FROM information_schema.tables\n WHERE table_schema = current_schema()\n AND table_name = $1\n )`,\n [table],\n );\n return result.rows[0]?.exists ?? false;\n }\n\n /**\n * List all columns in a table.\n *\n * @param table - Table name\n * @returns Array of column definitions\n */\n public async listColumns(table: string): Promise<ColumnDefinition[]> {\n const result = await this.driver.query<{\n column_name: string;\n data_type: string;\n character_maximum_length: number | null;\n numeric_precision: number | null;\n numeric_scale: number | null;\n is_nullable: string;\n column_default: string | null;\n }>(\n `SELECT\n column_name,\n data_type,\n character_maximum_length,\n numeric_precision,\n numeric_scale,\n is_nullable,\n column_default\n FROM information_schema.columns\n WHERE table_schema = current_schema()\n AND table_name = $1\n ORDER BY ordinal_position`,\n [table],\n );\n\n return result.rows.map((row) => ({\n name: row.column_name,\n type: this.mapPostgresTypeToColumnType(row.data_type),\n length: row.character_maximum_length ?? undefined,\n precision: row.numeric_precision ?? undefined,\n scale: row.numeric_scale ?? undefined,\n nullable: row.is_nullable === \"YES\",\n defaultValue: row.column_default ?? undefined,\n }));\n }\n\n /**\n * List all tables in the current database.\n *\n * @returns Array of table names\n */\n public async listTables(): Promise<string[]> {\n const result = await this.driver.query<{ table_name: string }>(\n `SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = current_schema()\n ORDER BY table_name`,\n );\n\n return result.rows.map((row) => row.table_name);\n }\n\n /**\n * Ensure the migrations tracking table exists.\n *\n * Creates the table with proper schema if it doesn't exist.\n *\n * @param tableName - Name of the migrations table\n */\n public async ensureMigrationsTable(tableName: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(tableName);\n\n await this.execute(`\n CREATE TABLE IF NOT EXISTS ${quotedTable} (\n \"id\" SERIAL PRIMARY KEY,\n \"name\" VARCHAR(255) NOT NULL UNIQUE,\n \"batch\" INTEGER NOT NULL,\n \"executedAt\" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n \"createdAt\" TIMESTAMP WITH TIME ZONE\n )\n `);\n }\n\n // ============================================================================\n // COLUMN OPERATIONS\n // ============================================================================\n\n /**\n * Add a column to an existing table.\n *\n * @param table - Table name\n * @param column - Column definition\n */\n public async addColumn(table: string, column: ColumnDefinition): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumn = this.driver.dialect.quoteIdentifier(column.name);\n\n // For auto-increment integers, use SERIAL/BIGSERIAL instead of INTEGER/BIGINT\n let sqlType: string;\n if (column.autoIncrement) {\n if (column.type === \"bigInteger\") {\n sqlType = \"BIGSERIAL\";\n } else {\n sqlType = \"SERIAL\";\n }\n } else {\n sqlType = this.driver.dialect.getSqlType(column.type, {\n length: column.length,\n precision: column.precision,\n scale: column.scale,\n dimensions: column.dimensions,\n });\n }\n\n let sql = `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedColumn} ${sqlType}`;\n\n // Handle generated columns\n if (column.generated) {\n sql += ` GENERATED ALWAYS AS (${column.generated.expression})`;\n if (column.generated.stored) {\n sql += \" STORED\";\n }\n // PostgreSQL only supports STORED, not VIRTUAL\n // If virtual is requested, it's simply ignored (PostgreSQL doesn't support it)\n } else {\n // SERIAL/BIGSERIAL are always NOT NULL, so skip for those\n if (!column.autoIncrement && column.nullable === false) {\n sql += \" NOT NULL\";\n }\n\n if (column.defaultValue !== undefined) {\n // Check for special CURRENT_TIMESTAMP marker\n if (\n typeof column.defaultValue === \"object\" &&\n column.defaultValue !== null &&\n (column.defaultValue as any).__type === \"CURRENT_TIMESTAMP\"\n ) {\n sql += \" DEFAULT NOW()\";\n } else if (column.isRawDefault === false) {\n // Explicit string literal - escape it properly\n const escaped = String(column.defaultValue).replace(/'/g, \"''\");\n sql += ` DEFAULT '${escaped}'`;\n } else if (typeof column.defaultValue === \"boolean\") {\n // Boolean values\n sql += ` DEFAULT ${column.defaultValue ? \"TRUE\" : \"FALSE\"}`;\n } else if (typeof column.defaultValue === \"number\") {\n // Numeric values\n sql += ` DEFAULT ${column.defaultValue}`;\n } else {\n // Raw SQL expression (default behavior when isRawDefault is true or undefined)\n sql += ` DEFAULT ${column.defaultValue}`;\n }\n }\n\n // Handle primary key\n if (column.primary) {\n sql += \" PRIMARY KEY\";\n }\n\n // Handle unique constraint\n if (column.unique) {\n sql += \" UNIQUE\";\n }\n }\n\n await this.execute(sql);\n }\n\n /**\n * Drop a column from a table.\n *\n * @param table - Table name\n * @param column - Column name\n */\n public async dropColumn(table: string, column: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\n await this.execute(`ALTER TABLE ${quotedTable} DROP COLUMN ${quotedColumn}`);\n }\n\n /**\n * Drop multiple columns from a table.\n *\n * @param table - Table name\n * @param columns - Column names\n */\n public async dropColumns(table: string, columns: string[]): Promise<void> {\n for (const column of columns) {\n await this.dropColumn(table, column);\n }\n }\n\n /**\n * Rename a column.\n *\n * @param table - Table name\n * @param from - Current column name\n * @param to - New column name\n */\n public async renameColumn(table: string, from: string, to: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedFrom = this.driver.dialect.quoteIdentifier(from);\n const quotedTo = this.driver.dialect.quoteIdentifier(to);\n await this.execute(`ALTER TABLE ${quotedTable} RENAME COLUMN ${quotedFrom} TO ${quotedTo}`);\n }\n\n /**\n * Modify an existing column.\n *\n * @param table - Table name\n * @param column - New column definition\n */\n public async modifyColumn(table: string, column: ColumnDefinition): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumn = this.driver.dialect.quoteIdentifier(column.name);\n const sqlType = this.driver.dialect.getSqlType(column.type, {\n length: column.length,\n precision: column.precision,\n scale: column.scale,\n dimensions: column.dimensions,\n });\n\n // PostgreSQL requires separate ALTER statements for type and nullability\n await this.execute(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} TYPE ${sqlType}`);\n\n if (column.nullable === false) {\n await this.execute(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} SET NOT NULL`);\n } else if (column.nullable === true) {\n await this.execute(`ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} DROP NOT NULL`);\n }\n\n if (column.defaultValue !== undefined) {\n let defaultVal: string;\n\n // Check for special CURRENT_TIMESTAMP marker\n if (\n typeof column.defaultValue === \"object\" &&\n column.defaultValue !== null &&\n (column.defaultValue as any).__type === \"CURRENT_TIMESTAMP\"\n ) {\n defaultVal = \"NOW()\";\n } else if (typeof column.defaultValue === \"string\") {\n defaultVal = `'${column.defaultValue}'`;\n } else {\n defaultVal = String(column.defaultValue);\n }\n\n await this.execute(\n `ALTER TABLE ${quotedTable} ALTER COLUMN ${quotedColumn} SET DEFAULT ${defaultVal}`,\n );\n }\n }\n\n /**\n * Create standard timestamp columns (created_at, updated_at).\n *\n * PostgreSQL implementation creates TIMESTAMPTZ columns with NOW() defaults.\n *\n * @param table - Table name\n */\n public async createTimestampColumns(table: string): Promise<void> {\n await this.addColumn(table, {\n name: \"created_at\",\n type: \"timestamp\",\n nullable: false,\n defaultValue: \"NOW()\",\n isRawDefault: true,\n });\n\n await this.addColumn(table, {\n name: \"updated_at\",\n type: \"timestamp\",\n nullable: false,\n defaultValue: \"NOW()\",\n isRawDefault: true,\n });\n }\n\n // ============================================================================\n // INDEX OPERATIONS\n // ============================================================================\n\n /**\n * Create an index on one or more columns.\n *\n * Supports:\n * - Regular column indexes\n * - Expression-based indexes (e.g., `lower(email)`)\n * - Covering indexes (INCLUDE clause)\n * - Concurrent index creation (CONCURRENTLY keyword)\n *\n * @param table - Table name\n * @param index - Index definition\n */\n public async createIndex(table: string, index: IndexDefinition): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const indexName = index.name ?? `idx_${table}_${index.columns.join(\"_\")}`;\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\n const uniqueKeyword = index.unique ? \"UNIQUE \" : \"\";\n const concurrentlyKeyword = index.concurrently ? \"CONCURRENTLY \" : \"\";\n\n let columnsPart: string;\n\n // Handle expression-based indexes\n if (index.expressions && index.expressions.length > 0) {\n // Wrap each expression in parentheses\n columnsPart = index.expressions.map((expr) => `(${expr})`).join(\", \");\n } else {\n // Regular column indexes\n const columns = index.columns.map((col, i) => {\n const quotedCol = this.driver.dialect.quoteIdentifier(col);\n const direction = index.directions?.[i]?.toUpperCase() ?? \"\";\n return direction ? `${quotedCol} ${direction}` : quotedCol;\n });\n columnsPart = columns.join(\", \");\n }\n\n let sql = `CREATE ${uniqueKeyword}INDEX ${concurrentlyKeyword}${quotedIndexName} ON ${quotedTable} (${columnsPart})`;\n\n // Add INCLUDE clause for covering indexes\n if (index.include && index.include.length > 0) {\n const includeCols = index.include\n .map((col) => this.driver.dialect.quoteIdentifier(col))\n .join(\", \");\n sql += ` INCLUDE (${includeCols})`;\n }\n\n // Add partial index condition\n if (index.where && Object.keys(index.where).length > 0) {\n const conditions = Object.entries(index.where)\n .map(([key, value]) => {\n const quotedKey = this.driver.dialect.quoteIdentifier(key);\n return typeof value === \"string\"\n ? `${quotedKey} = '${value}'`\n : `${quotedKey} = ${value}`;\n })\n .join(\" AND \");\n sql += ` WHERE ${conditions}`;\n }\n\n await this.execute(sql);\n }\n\n /**\n * Drop an index.\n *\n * @param table - Table name\n * @param indexNameOrColumns - Index name or columns\n */\n public async dropIndex(table: string, indexNameOrColumns: string | string[]): Promise<void> {\n let indexName: string;\n\n if (typeof indexNameOrColumns === \"string\") {\n indexName = indexNameOrColumns;\n } else {\n indexName = `idx_${table}_${indexNameOrColumns.join(\"_\")}`;\n }\n\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\n await this.execute(`DROP INDEX IF EXISTS ${quotedIndexName}`);\n }\n\n /**\n * Create a unique index.\n *\n * @param table - Table name\n * @param columns - Columns to include\n * @param name - Optional index name\n */\n public async createUniqueIndex(table: string, columns: string[], name?: string): Promise<void> {\n await this.createIndex(table, { columns, unique: true, name });\n }\n\n /**\n * Drop a unique index.\n *\n * @param table - Table name\n * @param columns - Columns in the index\n */\n public async dropUniqueIndex(table: string, columns: string[]): Promise<void> {\n await this.dropIndex(table, columns);\n }\n\n // ============================================================================\n // SPECIALIZED INDEXES\n // ============================================================================\n\n /**\n * Create a full-text search index using GIN.\n *\n * @param table - Table name\n * @param columns - Columns to index\n * @param options - Full-text options\n */\n public async createFullTextIndex(\n table: string,\n columns: string[],\n options?: FullTextIndexOptions,\n ): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const indexName = options?.name ?? `idx_${table}_fulltext_${columns.join(\"_\")}`;\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\n const language = options?.language ?? \"english\";\n\n const tsvectors = columns.map((col) => {\n const weight = options?.weights?.[col] ?? \"A\";\n return `setweight(to_tsvector('${language}', COALESCE(${this.driver.dialect.quoteIdentifier(col)}, '')), '${weight}')`;\n });\n\n await this.execute(\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} USING GIN ((${tsvectors.join(\" || \")}))`,\n );\n }\n\n /**\n * Drop a full-text search index.\n *\n * @param table - Table name\n * @param name - Index name\n */\n public async dropFullTextIndex(table: string, name: string): Promise<void> {\n await this.dropIndex(table, name);\n }\n\n /**\n * Create a geo-spatial index using GiST.\n *\n * @param table - Table name\n * @param column - Geo column\n * @param options - Geo index options\n */\n public async createGeoIndex(\n table: string,\n column: string,\n options?: GeoIndexOptions,\n ): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\n const indexName = options?.name ?? `idx_${table}_geo_${column}`;\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\n\n await this.execute(\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} USING GIST (${quotedColumn})`,\n );\n }\n\n /**\n * Drop a geo-spatial index.\n *\n * @param table - Table name\n * @param column - Geo column\n */\n public async dropGeoIndex(table: string, column: string): Promise<void> {\n await this.dropIndex(table, `idx_${table}_geo_${column}`);\n }\n\n /**\n * Create a vector search index (requires pgvector extension).\n *\n * @param table - Table name\n * @param column - Vector column\n * @param options - Vector index options\n */\n public async createVectorIndex(\n table: string,\n column: string,\n options: VectorIndexOptions,\n ): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\n const indexName = options.name ?? `idx_${table}_vector_${column}`;\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\n\n // Map similarity to pgvector operator class\n const opClass =\n options.similarity === \"euclidean\"\n ? \"vector_l2_ops\"\n : options.similarity === \"dotProduct\"\n ? \"vector_ip_ops\"\n : \"vector_cosine_ops\";\n\n const lists = options.lists ?? 100;\n\n await this.execute(\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} USING ivfflat (${quotedColumn} ${opClass}) WITH (lists = ${lists})`,\n );\n }\n\n /**\n * Drop a vector search index.\n *\n * @param table - Table name\n * @param column - Vector column\n */\n public async dropVectorIndex(table: string, column: string): Promise<void> {\n await this.dropIndex(table, `idx_${table}_vector_${column}`);\n }\n\n /**\n * Create a TTL index (not natively supported in PostgreSQL).\n *\n * Note: PostgreSQL doesn't have native TTL indexes like MongoDB.\n * This creates a plain index (a `NOW()` predicate is not IMMUTABLE, so a\n * partial index cannot be built) and requires a scheduled job for cleanup.\n *\n * @param table - Table name\n * @param column - Date column\n * @param expireAfterSeconds - Expiration time in seconds\n */\n public async createTTLIndex(\n table: string,\n column: string,\n expireAfterSeconds: number,\n ): Promise<void> {\n // Plain index that keeps the purge `DELETE ... WHERE column < ...` efficient\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumn = this.driver.dialect.quoteIdentifier(column);\n const indexName = `idx_${table}_ttl_${column}`;\n const quotedIndexName = this.driver.dialect.quoteIdentifier(indexName);\n\n await this.execute(\n `CREATE INDEX ${quotedIndexName} ON ${quotedTable} (${quotedColumn})`,\n );\n\n // Note: User must set up a scheduled job (pg_cron, etc.) to:\n // DELETE FROM table WHERE column < NOW() - INTERVAL 'X seconds'\n }\n\n /**\n * Drop a TTL index.\n *\n * @param table - Table name\n * @param column - Column with TTL index\n */\n public async dropTTLIndex(table: string, column: string): Promise<void> {\n await this.dropIndex(table, `idx_${table}_ttl_${column}`);\n }\n\n /**\n * List all indexes on a table.\n *\n * @param table - Table name\n * @returns Array of index metadata\n */\n public async listIndexes(table: string): Promise<TableIndexInformation[]> {\n const result = await this.driver.query<{ indexname: string; indexdef: string }>(\n `SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = current_schema() AND tablename = $1`,\n [table],\n );\n\n return result.rows.map((row) => {\n const isUnique = row.indexdef.includes(\"UNIQUE\");\n const isPrimary = row.indexname.endsWith(\"_pkey\");\n const columnsMatch = row.indexdef.match(/\\(([^)]+)\\)/);\n // Capture group 1 is present whenever the pattern matched, but that is a\n // property of the regex literal rather than of this expression. `?? \"\"`\n // yields the empty column list the `: []` branch already gives.\n const columns = (columnsMatch?.[1] ?? \"\")\n .split(\",\")\n .map((c) => c.trim().replace(/\"/g, \"\"))\n .filter(Boolean);\n\n let type = \"btree\";\n if (row.indexdef.includes(\"USING GIN\")) type = \"gin\";\n else if (row.indexdef.includes(\"USING GIST\")) type = \"gist\";\n else if (row.indexdef.includes(\"USING HASH\")) type = \"hash\";\n else if (row.indexdef.includes(\"USING ivfflat\")) type = \"ivfflat\";\n\n return {\n name: row.indexname,\n columns,\n type,\n unique: isUnique || isPrimary,\n partial: row.indexdef.includes(\"WHERE\"),\n options: { primary: isPrimary, definition: row.indexdef },\n };\n });\n }\n\n // ============================================================================\n // EXTENSIONS\n // ============================================================================\n\n /**\n * Check if a PostgreSQL extension is available on the database server.\n *\n * @param extension - Extension name (e.g., \"vector\")\n */\n public async isExtensionAvailable(extension: string): Promise<boolean> {\n try {\n const result = await this.driver.query<{ name: string }>(\n `SELECT name FROM pg_available_extensions WHERE name = $1`,\n [extension],\n );\n\n return result?.rows?.length > 0;\n } catch {\n // If we can't check (e.g., permissions issue or pg_available_extensions not accessible),\n // we return true to avoid false positives. Execution will just proceed and fail naturally if it's missing.\n return true;\n }\n }\n\n /**\n * Get the official documentation or installation URL for a PostgreSQL extension.\n *\n * @param extension - Extension name\n */\n public getExtensionDocsUrl(extension: string): string | undefined {\n const guideUrls: Record<string, string> = {\n vector: \"https://github.com/pgvector/pgvector#installation\",\n postgis: \"https://postgis.net/documentation/getting_started/\",\n pg_trgm: \"https://www.postgresql.org/docs/current/pgtrgm.html\",\n uuid_ossp: \"https://www.postgresql.org/docs/current/uuid-ossp.html\",\n };\n\n return guideUrls[extension] ?? `https://www.postgresql.org/docs/current/${extension}.html`;\n }\n\n // ============================================================================\n // CONSTRAINTS\n // ============================================================================\n\n /**\n * Add a foreign key constraint.\n *\n * @param table - Table name\n * @param foreignKey - Foreign key definition\n */\n public async addForeignKey(table: string, foreignKey: ForeignKeyDefinition): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumn = this.driver.dialect.quoteIdentifier(foreignKey.column);\n const quotedRefTable = this.driver.dialect.quoteIdentifier(foreignKey.referencesTable);\n const quotedRefColumn = this.driver.dialect.quoteIdentifier(foreignKey.referencesColumn);\n\n const constraintName =\n foreignKey.name ?? `fk_${table}_${foreignKey.column}_${foreignKey.referencesTable}`;\n const quotedConstraint = this.driver.dialect.quoteIdentifier(constraintName);\n\n let sql = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${quotedConstraint} FOREIGN KEY (${quotedColumn}) REFERENCES ${quotedRefTable} (${quotedRefColumn})`;\n\n if (foreignKey.onDelete) {\n sql += ` ON DELETE ${this.mapForeignKeyAction(foreignKey.onDelete)}`;\n }\n\n if (foreignKey.onUpdate) {\n sql += ` ON UPDATE ${this.mapForeignKeyAction(foreignKey.onUpdate)}`;\n }\n\n await this.execute(sql);\n }\n\n /**\n * Drop a foreign key constraint.\n *\n * @param table - Table name\n * @param name - Constraint name\n */\n public async dropForeignKey(table: string, name: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedConstraint = this.driver.dialect.quoteIdentifier(name);\n await this.execute(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quotedConstraint}`);\n }\n\n /**\n * Add a primary key constraint.\n *\n * @param table - Table name\n * @param columns - Primary key columns\n */\n public async addPrimaryKey(table: string, columns: string[]): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedColumns = columns.map((c) => this.driver.dialect.quoteIdentifier(c)).join(\", \");\n const constraintName = `pk_${table}`;\n const quotedConstraint = this.driver.dialect.quoteIdentifier(constraintName);\n\n await this.execute(\n `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${quotedConstraint} PRIMARY KEY (${quotedColumns})`,\n );\n }\n\n /**\n * Drop the primary key constraint.\n *\n * @param table - Table name\n */\n public async dropPrimaryKey(table: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const constraintName = `pk_${table}`;\n const quotedConstraint = this.driver.dialect.quoteIdentifier(constraintName);\n\n await this.execute(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quotedConstraint}`);\n }\n\n /**\n * Add a CHECK constraint.\n *\n * @param table - Table name\n * @param name - Constraint name\n * @param expression - SQL CHECK expression\n */\n public async addCheck(table: string, name: string, expression: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedName = this.driver.dialect.quoteIdentifier(name);\n const sql = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${quotedName} CHECK (${expression})`;\n await this.execute(sql);\n }\n\n /**\n * Drop a CHECK constraint.\n *\n * @param table - Table name\n * @param name - Constraint name\n */\n public async dropCheck(table: string, name: string): Promise<void> {\n const quotedTable = this.driver.dialect.quoteIdentifier(table);\n const quotedName = this.driver.dialect.quoteIdentifier(name);\n await this.execute(`ALTER TABLE ${quotedTable} DROP CONSTRAINT ${quotedName}`);\n }\n\n // ============================================================================\n // SCHEMA VALIDATION (NOT APPLICABLE FOR PostgreSQL)\n // ============================================================================\n\n /**\n * Set schema validation (no-op for PostgreSQL).\n *\n * PostgreSQL uses column constraints instead.\n */\n public async setSchemaValidation(_table: string, _schema: object): Promise<void> {\n // No-op: PostgreSQL doesn't have MongoDB-style schema validation\n // Use CHECK constraints instead\n }\n\n /**\n * Remove schema validation (no-op for PostgreSQL).\n */\n public async removeSchemaValidation(_table: string): Promise<void> {\n // No-op\n }\n\n // ============================================================================\n // TRANSACTIONS\n // ============================================================================\n\n /**\n * Begin a transaction.\n */\n public async beginTransaction(): Promise<void> {\n await this.execute(\"BEGIN\");\n }\n\n /**\n * Commit the current transaction.\n */\n public async commit(): Promise<void> {\n await this.execute(\"COMMIT\");\n }\n\n /**\n * Rollback the current transaction.\n */\n public async rollback(): Promise<void> {\n await this.execute(\"ROLLBACK\");\n }\n\n /**\n * Whether transactions are supported.\n */\n public supportsTransactions(): boolean {\n return true;\n }\n\n /**\n * Get the default transactional behavior for PostgreSQL.\n *\n * PostgreSQL supports transactional DDL operations, so migrations\n * are wrapped in transactions by default for atomicity and safety.\n *\n * @returns true (PostgreSQL DDL is transactional)\n */\n public getDefaultTransactional(): boolean {\n return true;\n }\n\n // ============================================================================\n // DEFAULTS\n // ============================================================================\n\n /**\n * Get the default UUID generation expression for PostgreSQL.\n *\n * Resolution order:\n * 1. `migrationDefaults.uuidExpression` → raw expression (escape hatch)\n * 2. `migrationDefaults.uuidStrategy` → mapped to PG function\n * 3. Fallback → `gen_random_uuid()` (v4, PG 13+)\n *\n * @param migrationDefaults - Optional overrides from DataSource config\n * @returns PostgreSQL SQL expression for UUID generation\n *\n * @example\n * ```typescript\n * driver.getUuidDefault(); // \"gen_random_uuid()\"\n * driver.getUuidDefault({ uuidStrategy: \"v7\" }); // \"uuidv7()\"\n * driver.getUuidDefault({ uuidExpression: \"uuid_generate_v1mc()\" }); // \"uuid_generate_v1mc()\"\n * ```\n */\n public getUuidDefault(migrationDefaults?: MigrationDefaults): string {\n // Escape hatch: raw expression takes highest precedence\n if (migrationDefaults?.uuidExpression) {\n return migrationDefaults.uuidExpression;\n }\n\n const strategy = migrationDefaults?.uuidStrategy ?? \"v4\";\n\n const strategyMap: Record<UuidStrategy, string> = {\n v4: \"gen_random_uuid()\",\n v7: \"uuidv7()\",\n };\n\n return strategyMap[strategy];\n }\n\n // ============================================================================\n // RAW ACCESS\n // ============================================================================\n\n /**\n * Execute raw operations with direct driver access.\n *\n * @param callback - Callback receiving the driver\n */\n public async raw<T>(callback: (connection: unknown) => Promise<T>): Promise<T> {\n return callback(this.driver);\n }\n\n // ============================================================================\n // HELPERS\n // ============================================================================\n\n /**\n * Execute a SQL statement.\n *\n * @param sql - SQL to execute\n * @param params - Query parameters\n */\n private async execute(sql: string, params: unknown[] = []): Promise<void> {\n await this.driver.query(sql, params);\n\n // DDL changes what the driver's connect-time caches (native-array columns,\n // unique keys) describe, so refresh them.\n if (/^\\s*(CREATE|ALTER|DROP)\\b/i.test(sql)) {\n await this.driver.invalidateSchemaCaches();\n }\n }\n\n /**\n * Map foreign key action to PostgreSQL syntax.\n */\n private mapForeignKeyAction(action: \"cascade\" | \"restrict\" | \"setNull\" | \"noAction\"): string {\n const mapping: Record<string, string> = {\n cascade: \"CASCADE\",\n restrict: \"RESTRICT\",\n setNull: \"SET NULL\",\n noAction: \"NO ACTION\",\n };\n return mapping[action] ?? \"NO ACTION\";\n }\n\n /**\n * Map PostgreSQL data type to ColumnType.\n */\n private mapPostgresTypeToColumnType(pgType: string): ColumnType {\n const typeMap: Record<string, ColumnType> = {\n \"character varying\": \"string\",\n varchar: \"string\",\n character: \"char\",\n char: \"char\",\n text: \"text\",\n integer: \"integer\",\n int: \"integer\",\n smallint: \"smallInteger\",\n bigint: \"bigInteger\",\n real: \"float\",\n \"double precision\": \"double\",\n numeric: \"decimal\",\n decimal: \"decimal\",\n boolean: \"boolean\",\n date: \"date\",\n timestamp: \"dateTime\",\n \"timestamp without time zone\": \"dateTime\",\n \"timestamp with time zone\": \"timestamp\",\n time: \"time\",\n \"time without time zone\": \"time\",\n json: \"json\",\n jsonb: \"json\",\n bytea: \"binary\",\n uuid: \"uuid\",\n inet: \"ipAddress\",\n macaddr: \"macAddress\",\n point: \"point\",\n polygon: \"polygon\",\n line: \"lineString\",\n geometry: \"geometry\",\n // PostgreSQL array types\n \"integer[]\": \"arrayInt\",\n \"int[]\": \"arrayInt\",\n \"bigint[]\": \"arrayBigInt\",\n \"real[]\": \"arrayFloat\",\n \"decimal[]\": \"arrayDecimal\",\n \"numeric[]\": \"arrayDecimal\",\n \"boolean[]\": \"arrayBoolean\",\n \"text[]\": \"arrayText\",\n \"date[]\": \"arrayDate\",\n \"timestamp with time zone[]\": \"arrayTimestamp\",\n \"timestamptz[]\": \"arrayTimestamp\",\n \"uuid[]\": \"arrayUuid\",\n \"jsonb[]\": \"arrayJson\",\n \"json[]\": \"arrayJson\",\n };\n\n return typeMap[pgType.toLowerCase()] ?? \"string\";\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChunkCallback, CursorPaginationOptions, CursorPaginationResult, DriverQuery, GroupByInput, LeanDocument, PaginationOptions, PaginationResult, QueryBuilderContract, RawExpression, UnwindOptions } from "../../contracts/query-builder.contract.mjs";
|
|
1
|
+
import { ChunkCallback, CursorPaginationOptions, CursorPaginationResult, DriverQuery, GroupByInput, LeanDocument, PaginationOptions, PaginationResult, QueryBuilderContract, RawExpression, UnwindOptions, WhereObject } from "../../contracts/query-builder.contract.mjs";
|
|
2
2
|
import { DataSource } from "../../data-source/data-source.mjs";
|
|
3
3
|
import { ModelRef } from "../../model/register-model.mjs";
|
|
4
4
|
import { Op, QueryBuilder } from "../../query-builder/query-builder.mjs";
|
|
@@ -200,6 +200,12 @@ declare class PostgresQueryBuilder<T = unknown> extends QueryBuilder<T> implemen
|
|
|
200
200
|
onFetched(callback: (records: unknown[], context: unknown) => void | Promise<void>): () => void;
|
|
201
201
|
/** Apply pending global scopes to the operations list. */
|
|
202
202
|
private applyPendingScopes;
|
|
203
|
+
/** Quote raw-SQL column references through the Postgres dialect. */
|
|
204
|
+
protected quoteColumn(field: string): string;
|
|
205
|
+
/** Full-text search across the whole row, plus optional equality filters. */
|
|
206
|
+
textSearch(query: string, filters?: WhereObject): this;
|
|
207
|
+
/** Full-text search (OR). */
|
|
208
|
+
orWhereFullText(fields: string | string[], query: string): this;
|
|
203
209
|
/** Array field contains a value (or object with key). */
|
|
204
210
|
whereArrayContains(field: string, value: unknown, key?: string): this;
|
|
205
211
|
/** Array field does NOT contain a value (or object with key). */
|
|
@@ -245,16 +251,21 @@ declare class PostgresQueryBuilder<T = unknown> extends QueryBuilder<T> implemen
|
|
|
245
251
|
firstOrNew<TResult = T>(defaults: GenericObject): Promise<TResult>;
|
|
246
252
|
/** Find by primary key. */
|
|
247
253
|
find<TResult = T>(id: number | string): Promise<TResult | null>;
|
|
248
|
-
/**
|
|
254
|
+
/**
|
|
255
|
+
* Collect the operations that decide WHICH rows match (filters, joins,
|
|
256
|
+
* grouping, distinct), after rewriting scopes and has/whereHas.
|
|
257
|
+
*/
|
|
258
|
+
private collectMatchOps;
|
|
259
|
+
/** Count matching rows (grouped / distinct queries count their result rows). */
|
|
249
260
|
count(): Promise<number>;
|
|
250
261
|
/** SUM a numeric field. */
|
|
251
262
|
sum(field: string): Promise<number>;
|
|
252
263
|
/** AVG of a numeric field. */
|
|
253
|
-
avg(field: string): Promise<number>;
|
|
254
|
-
/** MIN of a
|
|
255
|
-
min(field: string): Promise<
|
|
256
|
-
/** MAX of a
|
|
257
|
-
max(field: string): Promise<
|
|
264
|
+
avg(field: string): Promise<number | null>;
|
|
265
|
+
/** MIN of a field; null for an empty set. Numeric columns are parsed, other types returned as-is. */
|
|
266
|
+
min<V = number>(field: string): Promise<V | null>;
|
|
267
|
+
/** MAX of a field; null for an empty set. Numeric columns are parsed, other types returned as-is. */
|
|
268
|
+
max<V = number>(field: string): Promise<V | null>;
|
|
258
269
|
/** Get distinct values for a field. */
|
|
259
270
|
distinct<TResult = unknown>(field: string): Promise<TResult[]>;
|
|
260
271
|
/** Get array of all values for a single field. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-query-builder.d.mts","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/postgres-query-builder.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"postgres-query-builder.d.mts","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/postgres-query-builder.ts"],"mappings":"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"}
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { dataSourceRegistry } from "../../data-source/data-source-registry.mjs";
|
|
2
2
|
import { UnsupportedQueryOperationError } from "../../errors/unsupported-query-operation.error.mjs";
|
|
3
|
-
import { resolveModelClass, tryResolveModelClass } from "../../model/register-model.mjs";
|
|
4
3
|
import { inferBelongsToForeignKey, inferHasForeignKey, inferPivotKey, inferPivotTable } from "../../relations/key-conventions.mjs";
|
|
4
|
+
import { resolveModelClass, tryResolveModelClass } from "../../model/register-model.mjs";
|
|
5
5
|
import { RelationLoader, attachLoadedRelation } from "../../relations/relation-loader.mjs";
|
|
6
6
|
import { isAggregateExpression } from "../../expressions/aggregate-expressions.mjs";
|
|
7
7
|
import { assertLeanCompatible, stripHiddenFromLeanRecords } from "../../query-builder/lean-records.mjs";
|
|
8
8
|
import { QueryBuilder } from "../../query-builder/query-builder.mjs";
|
|
9
9
|
import { PostgresQueryParser } from "./postgres-query-parser.mjs";
|
|
10
|
+
import { log } from "@warlock.js/logger";
|
|
10
11
|
|
|
11
12
|
//#region ../cascade/src/drivers/postgres/postgres-query-builder.ts
|
|
12
13
|
/**
|
|
13
14
|
* Cast an Op[] to PostgresParserOperation[] — the shapes are compatible since
|
|
14
15
|
* both have `type: string` and `data: Record<string, unknown>`.
|
|
15
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* Normalise a MIN/MAX result: null stays null, numeric strings (pg returns
|
|
19
|
+
* NUMERIC/BIGINT as text) are parsed, anything else (dates, text) is untouched.
|
|
20
|
+
*/
|
|
21
|
+
function parseAggregateValue(value) {
|
|
22
|
+
if (value === null || value === void 0) return null;
|
|
23
|
+
if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) return parseFloat(value);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
16
26
|
function toParserOps(ops) {
|
|
17
27
|
return ops;
|
|
18
28
|
}
|
|
@@ -142,6 +152,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
142
152
|
return this;
|
|
143
153
|
}
|
|
144
154
|
groupBy(fields, aggregates) {
|
|
155
|
+
this.hydrateCallback = void 0;
|
|
145
156
|
if (!aggregates) return super.groupBy(fields);
|
|
146
157
|
const fieldList = Array.isArray(fields) ? fields : [fields];
|
|
147
158
|
this.addOperation("select", { fields: fieldList });
|
|
@@ -171,6 +182,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
171
182
|
* });
|
|
172
183
|
*/
|
|
173
184
|
groupByDate(column, unit, aggregates) {
|
|
185
|
+
this.hydrateCallback = void 0;
|
|
174
186
|
const bucketSql = this.driver.dialect.dateTruncSql(column, unit);
|
|
175
187
|
this.addOperation("selectRaw", {
|
|
176
188
|
expression: `${bucketSql} AS ${this.driver.dialect.quoteIdentifier(column)}`,
|
|
@@ -295,13 +307,64 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
295
307
|
if (timing === "before") beforeOps.push(...temp.operations);
|
|
296
308
|
else afterOps.push(...temp.operations);
|
|
297
309
|
}
|
|
310
|
+
const HAS_OP_TYPES = new Set([
|
|
311
|
+
"has",
|
|
312
|
+
"whereHas",
|
|
313
|
+
"orWhereHas",
|
|
314
|
+
"doesntHave",
|
|
315
|
+
"whereDoesntHave"
|
|
316
|
+
]);
|
|
317
|
+
const isFilter = (op) => /^(or)?where/i.test(op.type) && !HAS_OP_TYPES.has(op.type);
|
|
318
|
+
const scopeFilters = [...beforeOps, ...afterOps].filter(isFilter);
|
|
319
|
+
if (scopeFilters.length === 0) {
|
|
320
|
+
this.operations = [
|
|
321
|
+
...beforeOps,
|
|
322
|
+
...this.operations,
|
|
323
|
+
...afterOps
|
|
324
|
+
];
|
|
325
|
+
this.scopesApplied = true;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const userFilters = this.operations.filter(isFilter);
|
|
329
|
+
const userRest = this.operations.filter((op) => !isFilter(op));
|
|
298
330
|
this.operations = [
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
331
|
+
{
|
|
332
|
+
type: "where",
|
|
333
|
+
data: { nested: scopeFilters }
|
|
334
|
+
},
|
|
335
|
+
...beforeOps.filter((op) => !isFilter(op)),
|
|
336
|
+
...userFilters.length > 0 ? [{
|
|
337
|
+
type: "where",
|
|
338
|
+
data: { nested: userFilters }
|
|
339
|
+
}] : [],
|
|
340
|
+
...userRest,
|
|
341
|
+
...afterOps.filter((op) => !isFilter(op))
|
|
302
342
|
];
|
|
303
343
|
this.scopesApplied = true;
|
|
304
344
|
}
|
|
345
|
+
/** Quote raw-SQL column references through the Postgres dialect. */
|
|
346
|
+
quoteColumn(field) {
|
|
347
|
+
return this.driver.dialect.quoteIdentifier(field);
|
|
348
|
+
}
|
|
349
|
+
/** Full-text search across the whole row, plus optional equality filters. */
|
|
350
|
+
textSearch(query, filters) {
|
|
351
|
+
const table = this.driver.dialect.quoteIdentifier(this.table);
|
|
352
|
+
this.addOperation("whereRaw", {
|
|
353
|
+
expression: `to_tsvector('english', ${table}::text) @@ plainto_tsquery('english', ?)`,
|
|
354
|
+
bindings: [query]
|
|
355
|
+
});
|
|
356
|
+
if (filters) for (const [key, value] of Object.entries(filters)) this.where(key, value);
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
/** Full-text search (OR). */
|
|
360
|
+
orWhereFullText(fields, query) {
|
|
361
|
+
const vectors = (Array.isArray(fields) ? fields : [fields]).map((f) => `to_tsvector('english', ${this.driver.dialect.quoteIdentifier(f)})`).join(" || ");
|
|
362
|
+
this.addOperation("orWhereRaw", {
|
|
363
|
+
expression: `(${vectors}) @@ plainto_tsquery('english', ?)`,
|
|
364
|
+
bindings: [query]
|
|
365
|
+
});
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
305
368
|
/** Array field contains a value (or object with key). */
|
|
306
369
|
whereArrayContains(field, value, key) {
|
|
307
370
|
const quotedField = this.driver.dialect.quoteIdentifier(field);
|
|
@@ -457,8 +520,15 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
457
520
|
this.operations = [];
|
|
458
521
|
return records;
|
|
459
522
|
} catch (error) {
|
|
460
|
-
|
|
461
|
-
|
|
523
|
+
log.error({
|
|
524
|
+
module: "database.postgres",
|
|
525
|
+
action: "query-builder.error",
|
|
526
|
+
message: `${query} | Params: ${bindings.length}`,
|
|
527
|
+
context: {
|
|
528
|
+
sql: query,
|
|
529
|
+
paramCount: bindings.length
|
|
530
|
+
}
|
|
531
|
+
});
|
|
462
532
|
throw error;
|
|
463
533
|
}
|
|
464
534
|
}
|
|
@@ -498,17 +568,46 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
498
568
|
async find(id) {
|
|
499
569
|
return this.where("id", id).first();
|
|
500
570
|
}
|
|
501
|
-
/**
|
|
502
|
-
|
|
571
|
+
/**
|
|
572
|
+
* Collect the operations that decide WHICH rows match (filters, joins,
|
|
573
|
+
* grouping, distinct), after rewriting scopes and has/whereHas.
|
|
574
|
+
*/
|
|
575
|
+
collectMatchOps() {
|
|
503
576
|
this.applyPendingScopes();
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
577
|
+
this.applyHasRelations();
|
|
578
|
+
return this.operations.filter((op) => /where|join|has|groupby|having|distinct|select/i.test(op.type));
|
|
579
|
+
}
|
|
580
|
+
/** Count matching rows (grouped / distinct queries count their result rows). */
|
|
581
|
+
async count() {
|
|
582
|
+
const matchOps = this.collectMatchOps();
|
|
583
|
+
const isShaped = matchOps.some((op) => /groupby|distinct/i.test(op.type));
|
|
584
|
+
let query;
|
|
585
|
+
let bindings;
|
|
586
|
+
if (isShaped) {
|
|
587
|
+
const hasSelect = matchOps.some((op) => /select/i.test(op.type));
|
|
588
|
+
const isDistinct = matchOps.some((op) => op.type === "distinct");
|
|
589
|
+
const innerOps = toParserOps(hasSelect || isDistinct ? matchOps : [...matchOps, {
|
|
590
|
+
type: "selectRaw",
|
|
591
|
+
data: { expression: "1" }
|
|
592
|
+
}]);
|
|
593
|
+
const inner = new PostgresQueryParser({
|
|
594
|
+
table: this.table,
|
|
595
|
+
operations: innerOps
|
|
596
|
+
}).parse();
|
|
597
|
+
query = `SELECT COUNT(*) AS "count" FROM (${inner.query}) AS "__count"`;
|
|
598
|
+
bindings = inner.bindings ?? [];
|
|
599
|
+
} else {
|
|
600
|
+
const countOps = toParserOps([...matchOps.filter((op) => !/select/i.test(op.type)), {
|
|
601
|
+
type: "selectRaw",
|
|
602
|
+
data: { expression: "COUNT(*) AS \"count\"" }
|
|
603
|
+
}]);
|
|
604
|
+
const parsed = new PostgresQueryParser({
|
|
605
|
+
table: this.table,
|
|
606
|
+
operations: countOps
|
|
607
|
+
}).parse();
|
|
608
|
+
query = parsed.query ?? "";
|
|
609
|
+
bindings = parsed.bindings ?? [];
|
|
610
|
+
}
|
|
512
611
|
const result = await this.driver.query(query, bindings);
|
|
513
612
|
return parseInt(result.rows[0]?.count ?? "0", 10);
|
|
514
613
|
}
|
|
@@ -516,29 +615,27 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
516
615
|
async sum(field) {
|
|
517
616
|
this.applyPendingScopes();
|
|
518
617
|
this.hydrateCallback = void 0;
|
|
519
|
-
const result = await this.selectRaw(`SUM(${field}) as sum`).first();
|
|
618
|
+
const result = await this.selectRaw(`SUM(${this.driver.dialect.quoteIdentifier(field)}) as sum`).first();
|
|
520
619
|
return parseFloat(result?.sum ?? "0");
|
|
521
620
|
}
|
|
522
621
|
/** AVG of a numeric field. */
|
|
523
622
|
async avg(field) {
|
|
524
623
|
this.applyPendingScopes();
|
|
525
624
|
this.hydrateCallback = void 0;
|
|
526
|
-
const result = await this.selectRaw(`AVG(${field}) as avg`).first();
|
|
527
|
-
return
|
|
625
|
+
const result = await this.selectRaw(`AVG(${this.driver.dialect.quoteIdentifier(field)}) as avg`).first();
|
|
626
|
+
return result?.avg == null ? null : parseFloat(result.avg);
|
|
528
627
|
}
|
|
529
|
-
/** MIN of a
|
|
628
|
+
/** MIN of a field; null for an empty set. Numeric columns are parsed, other types returned as-is. */
|
|
530
629
|
async min(field) {
|
|
531
630
|
this.applyPendingScopes();
|
|
532
631
|
this.hydrateCallback = void 0;
|
|
533
|
-
|
|
534
|
-
return parseFloat(result?.min ?? "0");
|
|
632
|
+
return parseAggregateValue((await this.selectRaw(`MIN(${this.driver.dialect.quoteIdentifier(field)}) as min`).first())?.min);
|
|
535
633
|
}
|
|
536
|
-
/** MAX of a
|
|
634
|
+
/** MAX of a field; null for an empty set. Numeric columns are parsed, other types returned as-is. */
|
|
537
635
|
async max(field) {
|
|
538
636
|
this.applyPendingScopes();
|
|
539
637
|
this.hydrateCallback = void 0;
|
|
540
|
-
|
|
541
|
-
return parseFloat(result?.max ?? "0");
|
|
638
|
+
return parseAggregateValue((await this.selectRaw(`MAX(${this.driver.dialect.quoteIdentifier(field)}) as max`).first())?.max);
|
|
542
639
|
}
|
|
543
640
|
/** Get distinct values for a field. */
|
|
544
641
|
async distinct(field) {
|
|
@@ -558,7 +655,24 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
558
655
|
}
|
|
559
656
|
/** Check whether any matching rows exist. */
|
|
560
657
|
async exists() {
|
|
561
|
-
|
|
658
|
+
const matchOps = this.collectMatchOps();
|
|
659
|
+
if (matchOps.some((op) => /groupby|distinct/i.test(op.type))) return await this.count() > 0;
|
|
660
|
+
const probeOps = toParserOps([
|
|
661
|
+
...matchOps.filter((op) => !/select/i.test(op.type)),
|
|
662
|
+
{
|
|
663
|
+
type: "selectRaw",
|
|
664
|
+
data: { expression: "1" }
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
type: "limit",
|
|
668
|
+
data: { value: 1 }
|
|
669
|
+
}
|
|
670
|
+
]);
|
|
671
|
+
const { query = "", bindings = [] } = new PostgresQueryParser({
|
|
672
|
+
table: this.table,
|
|
673
|
+
operations: probeOps
|
|
674
|
+
}).parse();
|
|
675
|
+
return (await this.driver.query(query, bindings)).rows.length > 0;
|
|
562
676
|
}
|
|
563
677
|
/** Check whether NO matching rows exist. */
|
|
564
678
|
async notExists() {
|
|
@@ -567,11 +681,11 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
567
681
|
/** COUNT DISTINCT a field. */
|
|
568
682
|
async countDistinct(field) {
|
|
569
683
|
this.hydrateCallback = void 0;
|
|
570
|
-
const result = await this.selectRaw(`COUNT(DISTINCT ${field}) as count`).first();
|
|
684
|
+
const result = await this.selectRaw(`COUNT(DISTINCT ${this.driver.dialect.quoteIdentifier(field)}) as count`).first();
|
|
571
685
|
return parseInt(result?.count ?? "0", 10);
|
|
572
686
|
}
|
|
573
687
|
/** Get latest records ordered by a column. */
|
|
574
|
-
async latest(column =
|
|
688
|
+
async latest(column = this.defaultCreatedAtColumn()) {
|
|
575
689
|
return this.orderBy(column, "desc").get();
|
|
576
690
|
}
|
|
577
691
|
/** Increment a numeric field. Returns new value. */
|
|
@@ -579,7 +693,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
579
693
|
this.applyPendingScopes();
|
|
580
694
|
const { sql: filterSql, params: filterParams } = this.buildFilter();
|
|
581
695
|
const amountPlaceholder = `$${filterParams.length + 1}`;
|
|
582
|
-
const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + ${amountPlaceholder}
|
|
696
|
+
const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + ${amountPlaceholder} WHERE ctid IN (SELECT ctid FROM ${this.driver.dialect.quoteIdentifier(this.table)} ${filterSql} LIMIT 1 FOR UPDATE SKIP LOCKED) RETURNING ${this.driver.dialect.quoteIdentifier(field)}`;
|
|
583
697
|
return (await this.driver.query(updateSql, [...filterParams, amount])).rows[0]?.[field] ?? 0;
|
|
584
698
|
}
|
|
585
699
|
/** Decrement a numeric field. Returns new value. */
|
|
@@ -701,7 +815,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
701
815
|
this.applyPendingScopes();
|
|
702
816
|
const { sql: filterSql, params } = this.buildFilter();
|
|
703
817
|
const quotedTable = this.driver.dialect.quoteIdentifier(this.table);
|
|
704
|
-
const deleteSql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${filterSql} LIMIT 1)`;
|
|
818
|
+
const deleteSql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${filterSql} LIMIT 1 FOR UPDATE SKIP LOCKED)`;
|
|
705
819
|
return (await this.driver.query(deleteSql, params)).rowCount ?? 0;
|
|
706
820
|
}
|
|
707
821
|
/**
|
|
@@ -1197,7 +1311,8 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
|
|
|
1197
1311
|
* Used by DELETE / UPDATE / increment paths.
|
|
1198
1312
|
*/
|
|
1199
1313
|
buildFilter() {
|
|
1200
|
-
|
|
1314
|
+
this.applyHasRelations();
|
|
1315
|
+
const whereOps = this.operations.filter((op) => op.type === "has" || op.type.includes("where") || op.type.includes("Where"));
|
|
1201
1316
|
if (whereOps.length === 0) return {
|
|
1202
1317
|
sql: "",
|
|
1203
1318
|
params: []
|