{"version":3,"sources":["../../../../../src/orm/adapters/postgresql/index.ts","../../../../../src/orm/adapters/postgresql/query.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\n\nimport { Pool, type PoolClient } from 'pg'\nimport Cursor from 'pg-cursor'\nimport { v } from 'valleyed'\n\nimport {\n\tbuildAggregateQuery,\n\tbuildCountQuery,\n\tbuildCreateQuery,\n\tbuildDeleteQuery,\n\tbuildPkUpdateQuery,\n\tbuildSelectQuery,\n\tbuildUpdateQuery,\n\tbuildUpsertQuery,\n\textractUpsertConflictColumn,\n} from './query'\nimport { EquippedError } from '../../../errors'\nimport { configurable } from '../../../utilities'\nimport type { FieldTypeName } from '../../adapter'\nimport type { FilterGroup } from '../../filter'\nimport type { DiscoveredSchema, ForeignKeyAction } from '../../migrations/introspection-types'\nimport type {\n\tAddFieldChange,\n\tAddForeignKeyChange,\n\tAddIndexChange,\n\tCreateTableChange,\n\tDropFieldChange,\n\tDropForeignKeyChange,\n\tDropIndexChange,\n\tDropTableChange,\n\tModifyFieldChange,\n\tRenameFieldChange,\n\tRenameTableChange,\n} from '../../migrations/types'\nimport { OrmAdapter, type AggregateSpec } from '../../orm-adapter'\nimport type { IterationQueryOptions, QueryOptions } from '../../query-options'\nimport type { AnySchema } from '../../schema'\nimport { flattenOps, type AnyUpdateOp } from '../../updates'\n\nexport type PostgresqlRepoConfig = {\n\tschema?: string\n\ttable: string\n}\n\nexport type PostgresqlConnectionConfig = {\n\thost: string\n\tport: number\n\tusername: string\n\tpassword: string\n\tdatabase: string\n\tssl?: boolean\n}\n\nconst postgresqlConnectionPipe = () =>\n\tv.object({\n\t\thost: v.string(),\n\t\tport: v.number(),\n\t\tusername: v.string(),\n\t\tpassword: v.string(),\n\t\tdatabase: v.string(),\n\t\tssl: v.defaults(v.boolean(), false),\n\t})\n\nconst TRACKER_TABLE = 'equipped_migrations'\nconst ADVISORY_LOCK_KEY = 3_735_928_559\nconst DEFAULT_CURSOR_BATCH_SIZE = 1000\n\ntype PostgresCursor<T extends Record<string, unknown>> = {\n\tread: (rowCount: number) => Promise<T[]>\n\tclose: () => Promise<void>\n}\n\nconst FIELD_TYPE_TO_PG: Record<string, string> = {\n\tstring: 'text',\n\tnumber: 'double precision',\n\tboolean: 'boolean',\n\tdate: 'timestamptz',\n\tnull: 'text',\n\tobject: 'jsonb',\n\tarray: 'jsonb',\n}\n\nconst PG_TO_FIELD_TYPE: Record<string, FieldTypeName> = {\n\ttext: 'string',\n\t'character varying': 'string',\n\tvarchar: 'string',\n\tchar: 'string',\n\tcharacter: 'string',\n\t'double precision': 'number',\n\treal: 'number',\n\tinteger: 'number',\n\tsmallint: 'number',\n\tnumeric: 'number',\n\tdecimal: 'number',\n\tboolean: 'boolean',\n\t'timestamp with time zone': 'date',\n\t'timestamp without time zone': 'date',\n\ttimestamptz: 'date',\n\ttimestamp: 'date',\n\tdate: 'date',\n\tjsonb: 'object',\n\tjson: 'object',\n}\n\nexport class PostgresAdapter extends configurable(postgresqlConnectionPipe, OrmAdapter) {\n\treadonly schemaConfigPipe = v.object({\n\t\tschema: v.optional(v.string()),\n\t\ttable: v.string(),\n\t})\n\n\treadonly supportedFieldTypes = ['string', 'number', 'boolean', 'null', 'object', 'array', 'date'] as const\n\treadonly queryableOps = [\n\t\t'eq',\n\t\t'ne',\n\t\t'gt',\n\t\t'gte',\n\t\t'lt',\n\t\t'lte',\n\t\t'in',\n\t\t'notIn',\n\t\t'like',\n\t\t'exists',\n\t\t'notExists',\n\t\t'contains',\n\t\t'notContains',\n\t] as const\n\treadonly updateOps = ['set', 'inc', 'mul', 'min', 'max', 'unset', 'push', 'pull', 'patch'] as const\n\treadonly aggregateOps = ['count', 'countDistinct', 'sum', 'avg', 'min', 'max'] as const\n\n\treadonly pool: Pool\n\t#sessionStore = new AsyncLocalStorage<PoolClient | undefined>()\n\n\tprotected constructor(config: typeof PostgresAdapter.Config) {\n\t\tsuper(config)\n\t\tthis.pool = new Pool({\n\t\t\thost: config.host,\n\t\t\tport: config.port,\n\t\t\tuser: config.username,\n\t\t\tpassword: config.password,\n\t\t\tdatabase: config.database,\n\t\t\tssl: config.ssl ? { rejectUnauthorized: false } : false,\n\t\t})\n\t}\n\n\t#getClient() {\n\t\treturn this.#sessionStore.getStore() ?? this.pool\n\t}\n\n\t#resolveTableName(config: PostgresqlRepoConfig) {\n\t\treturn config.schema && config.schema !== 'public' ? `${config.schema}.${config.table}` : config.table\n\t}\n\n\tasync connect() {}\n\n\tasync disconnect() {\n\t\ttry {\n\t\t\tawait this.pool.end()\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError('Failed to disconnect PostgreSQL pool', { adapter: 'postgresql' }, error)\n\t\t}\n\t}\n\n\tasync findByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst pkName = schema.pkField.name\n\t\t\tconst result = await this.#getClient().query(`SELECT * FROM \"${tableName}\" WHERE \"${pkName}\" = $1`, [pk])\n\t\t\treturn result.rows[0] ?? null\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError('PostgreSQL findByPk failed', { adapter: 'postgresql', operation: 'findByPk', table: c.table }, error)\n\t\t}\n\t}\n\n\tasync createMany(_schema: AnySchema, config: unknown, data: Record<string, unknown>[]) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst client = this.#getClient()\n\t\t\tconst results: Record<string, unknown>[] = []\n\t\t\tfor (const doc of data) {\n\t\t\t\tconst { sql, params } = buildCreateQuery(tableName, doc)\n\t\t\t\tconst result = await client.query(sql, params)\n\t\t\t\tresults.push(result.rows[0])\n\t\t\t}\n\t\t\treturn results\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError(\n\t\t\t\t'PostgreSQL createMany failed',\n\t\t\t\t{ adapter: 'postgresql', operation: 'createMany', table: c.table },\n\t\t\t\terror,\n\t\t\t)\n\t\t}\n\t}\n\n\tasync updateByPk(schema: AnySchema, config: unknown, pk: unknown, ops: AnyUpdateOp[]) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst pkName = schema.pkField.name\n\t\t\tconst data = flattenOps(ops)\n\t\t\tif (Object.keys(data).length === 0) {\n\t\t\t\tconst result = await this.#getClient().query(`SELECT * FROM \"${tableName}\" WHERE \"${pkName}\" = $1`, [pk])\n\t\t\t\treturn result.rows[0] ?? null\n\t\t\t}\n\t\t\tconst { sql, params } = buildPkUpdateQuery(tableName, pkName, pk, data)\n\t\t\tconst result = await this.#getClient().query(sql, params)\n\t\t\treturn result.rows[0] ?? null\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError(\n\t\t\t\t'PostgreSQL updateByPk failed',\n\t\t\t\t{ adapter: 'postgresql', operation: 'updateByPk', table: c.table },\n\t\t\t\terror,\n\t\t\t)\n\t\t}\n\t}\n\n\tasync deleteByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst pkName = schema.pkField.name\n\t\t\tconst result = await this.#getClient().query(`DELETE FROM \"${tableName}\" WHERE \"${pkName}\" = $1 RETURNING *`, [pk])\n\t\t\treturn result.rows[0] ?? null\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError(\n\t\t\t\t'PostgreSQL deleteByPk failed',\n\t\t\t\t{ adapter: 'postgresql', operation: 'deleteByPk', table: c.table },\n\t\t\t\terror,\n\t\t\t)\n\t\t}\n\t}\n\n\tasync raw(_schema: AnySchema, config: unknown, command: string, params: unknown[] = []) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst result = await this.#getClient().query(command, params)\n\t\t\treturn result\n\t\t} catch (error) {\n\t\t\tif (error instanceof EquippedError) throw error\n\t\t\tthrow new EquippedError('PostgreSQL raw failed', { adapter: 'postgresql', operation: 'raw', table: c.table }, error)\n\t\t}\n\t}\n\n\tasync findMany(schema: AnySchema, config: unknown, filter: FilterGroup, options?: QueryOptions) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst { sql, params } = buildSelectQuery(filter, options, tableName, schema.pkField.name)\n\t\t\tconst result = await this.#getClient().query(sql, params)\n\t\t\treturn result.rows\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError('PostgreSQL findMany failed', { adapter: 'postgresql', operation: 'findMany', table: c.table }, error)\n\t\t}\n\t}\n\n\tasync count(schema: AnySchema, config: unknown, filter: FilterGroup) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst { sql, params } = buildCountQuery(filter, tableName, schema.pkField.name)\n\t\t\tconst result = await this.#getClient().query(sql, params)\n\t\t\treturn Number(result.rows[0]?.count ?? 0)\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError('PostgreSQL count failed', { adapter: 'postgresql', operation: 'count', table: c.table }, error)\n\t\t}\n\t}\n\n\tasync *iterateMany(schema: AnySchema, config: unknown, filter: FilterGroup, options?: IterationQueryOptions) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\tlet client: PoolClient | undefined\n\t\tlet releaseClient = false\n\t\tlet cursor: PostgresCursor<Record<string, unknown>> | undefined\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst { sql, params } = buildSelectQuery(filter, options, tableName, schema.pkField.name)\n\t\t\tconst sessionClient = this.#sessionStore.getStore()\n\t\t\tif (sessionClient) {\n\t\t\t\tclient = sessionClient\n\t\t\t} else {\n\t\t\t\tclient = await this.pool.connect()\n\t\t\t\treleaseClient = true\n\t\t\t}\n\t\t\tcursor = client.query(new Cursor<Record<string, unknown>>(sql, params) as any) as PostgresCursor<Record<string, unknown>>\n\t\t\tconst batchSize = options?.batchSize ?? DEFAULT_CURSOR_BATCH_SIZE\n\n\t\t\twhile (true) {\n\t\t\t\tconst rows = await cursor.read(batchSize)\n\t\t\t\tif (rows.length === 0) break\n\t\t\t\tfor (const row of rows) yield row\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError('PostgreSQL iterateMany failed', { adapter: 'postgresql', operation: 'iterateMany', table: c.table }, error)\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait cursor?.close()\n\t\t\t} finally {\n\t\t\t\tif (releaseClient) client?.release()\n\t\t\t}\n\t\t}\n\t}\n\n\tasync updateMany(schema: AnySchema, config: unknown, filter: FilterGroup, data: Record<string, unknown>) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst { sql, params } = buildUpdateQuery(filter, tableName, schema.pkField.name, data)\n\t\t\tconst result = await this.#getClient().query(sql, params)\n\t\t\treturn result.rows\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError(\n\t\t\t\t'PostgreSQL updateMany failed',\n\t\t\t\t{ adapter: 'postgresql', operation: 'updateMany', table: c.table },\n\t\t\t\terror,\n\t\t\t)\n\t\t}\n\t}\n\n\tasync deleteMany(schema: AnySchema, config: unknown, filter: FilterGroup) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst { sql, params } = buildDeleteQuery(filter, tableName, schema.pkField.name)\n\t\t\tconst result = await this.#getClient().query(sql, params)\n\t\t\treturn result.rows\n\t\t} catch (error) {\n\t\t\tthrow new EquippedError(\n\t\t\t\t'PostgreSQL deleteMany failed',\n\t\t\t\t{ adapter: 'postgresql', operation: 'deleteMany', table: c.table },\n\t\t\t\terror,\n\t\t\t)\n\t\t}\n\t}\n\n\tasync upsertOne(schema: AnySchema, config: unknown, filter: FilterGroup, create: Record<string, unknown>, ops: AnyUpdateOp[]) {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst conflictColumn = extractUpsertConflictColumn(filter, schema.name)\n\t\t\tconst data = ops.length > 0 ? flattenOps(ops) : {}\n\t\t\tconst { sql, params } = buildUpsertQuery(tableName, conflictColumn, schema.pkField.name, create, data)\n\t\t\tconst result = await this.#getClient().query(sql, params)\n\t\t\treturn result.rows[0]\n\t\t} catch (error) {\n\t\t\tif (error instanceof EquippedError) throw error\n\t\t\tthrow new EquippedError('PostgreSQL upsertOne failed', { adapter: 'postgresql', operation: 'upsertOne', table: c.table }, error)\n\t\t}\n\t}\n\n\tasync aggregate(schema: AnySchema, config: unknown, spec: AggregateSpec): Promise<Array<Record<string, unknown>>> {\n\t\tconst c = config as PostgresqlRepoConfig\n\t\ttry {\n\t\t\tconst tableName = this.#resolveTableName(c)\n\t\t\tconst { sql, params } = buildAggregateQuery(spec, tableName, schema.pkField.name)\n\t\t\tconst result = await this.#getClient().query(sql, params)\n\t\t\treturn result.rows.map((row: Record<string, unknown>) => {\n\t\t\t\tconst mapped: Record<string, unknown> = {}\n\t\t\t\tfor (const agg of spec.aggregates) {\n\t\t\t\t\tconst val = row[agg.alias]\n\t\t\t\t\tif (agg.fn === 'min' || agg.fn === 'max') {\n\t\t\t\t\t\tmapped[agg.alias] = val\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmapped[agg.alias] = typeof val === 'string' ? Number(val) : Number(val ?? 0)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const field of spec.groupBy) {\n\t\t\t\t\tmapped[field] = row[field]\n\t\t\t\t}\n\t\t\t\treturn mapped\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tif (error instanceof EquippedError) throw error\n\t\t\tthrow new EquippedError('PostgreSQL aggregate failed', { adapter: 'postgresql', operation: 'aggregate', table: c.table }, error)\n\t\t}\n\t}\n\n\tasync loadMigrations(): Promise<{ id: string; appliedAt: number }[]> {\n\t\tconst client = this.#getClient()\n\t\tawait client.query(`\n\t\t\tCREATE TABLE IF NOT EXISTS \"${TRACKER_TABLE}\" (\n\t\t\t\tid text PRIMARY KEY,\n\t\t\t\tapplied_at bigint NOT NULL\n\t\t\t)\n\t\t`)\n\t\tconst result = await client.query(`SELECT id, applied_at FROM \"${TRACKER_TABLE}\"`)\n\t\treturn result.rows.map((row: any) => ({ id: row.id, appliedAt: Number(row.applied_at) }))\n\t}\n\n\tasync recordMigration(id: string, appliedAt: number): Promise<void> {\n\t\tconst client = this.#getClient()\n\t\tawait client.query(`INSERT INTO \"${TRACKER_TABLE}\" (id, applied_at) VALUES ($1, $2)`, [id, appliedAt])\n\t}\n\n\tasync acquireMigrationLock<T>(fn: () => Promise<T>): Promise<T> {\n\t\tconst client = this.#getClient()\n\t\tawait client.query(`SELECT pg_advisory_lock($1)`, [ADVISORY_LOCK_KEY])\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\tawait client.query(`SELECT pg_advisory_unlock($1)`, [ADVISORY_LOCK_KEY])\n\t\t}\n\t}\n\n\tasync applyCreateTable(change: CreateTableChange<any>): Promise<void> {\n\t\tconst pkType = FIELD_TYPE_TO_PG[change.pk.type] ?? 'text'\n\t\tconst cols = [`\"${change.pk.name}\" ${pkType} PRIMARY KEY`]\n\t\tfor (const f of change.fields) {\n\t\t\tcols.push(this.#renderColumnDef(f))\n\t\t}\n\t\tawait this.#getClient().query(`CREATE TABLE \"${change.name}\" (${cols.join(', ')})`)\n\t}\n\n\tasync applyDropTable(change: DropTableChange): Promise<void> {\n\t\tawait this.#getClient().query(`DROP TABLE \"${change.name}\"`)\n\t}\n\n\tasync applyAddField(change: AddFieldChange<any>): Promise<void> {\n\t\tconst colDef = this.#renderColumnDef(change.field)\n\t\tawait this.#getClient().query(`ALTER TABLE \"${change.table}\" ADD COLUMN ${colDef}`)\n\t}\n\n\tasync applyDropField(change: DropFieldChange): Promise<void> {\n\t\tawait this.#getClient().query(`ALTER TABLE \"${change.table}\" DROP COLUMN \"${change.name}\"`)\n\t}\n\n\tasync applyModifyField(change: ModifyFieldChange<any>): Promise<void> {\n\t\tconst client = this.#getClient()\n\t\tconst table = `\"${change.table}\"`\n\t\tconst col = `\"${change.name}\"`\n\t\tconst newType = FIELD_TYPE_TO_PG[change.to.type] ?? 'text'\n\t\tawait client.query(`ALTER TABLE ${table} ALTER COLUMN ${col} TYPE ${newType} USING ${col}::${newType}`)\n\t\tif (change.to.nullable) {\n\t\t\tawait client.query(`ALTER TABLE ${table} ALTER COLUMN ${col} DROP NOT NULL`)\n\t\t} else {\n\t\t\tawait client.query(`ALTER TABLE ${table} ALTER COLUMN ${col} SET NOT NULL`)\n\t\t}\n\t\tif (change.to.default !== undefined) {\n\t\t\tawait client.query(`ALTER TABLE ${table} ALTER COLUMN ${col} SET DEFAULT ${this.#renderDefault(change.to.default)}`)\n\t\t} else {\n\t\t\tawait client.query(`ALTER TABLE ${table} ALTER COLUMN ${col} DROP DEFAULT`)\n\t\t}\n\t\tif (change.to.name !== change.name) {\n\t\t\tawait client.query(`ALTER TABLE ${table} RENAME COLUMN ${col} TO \"${change.to.name}\"`)\n\t\t}\n\t}\n\n\tasync applyRenameTable(change: RenameTableChange): Promise<void> {\n\t\tawait this.#getClient().query(`ALTER TABLE \"${change.from}\" RENAME TO \"${change.to}\"`)\n\t}\n\n\tasync applyRenameField(change: RenameFieldChange): Promise<void> {\n\t\tawait this.#getClient().query(`ALTER TABLE \"${change.table}\" RENAME COLUMN \"${change.from}\" TO \"${change.to}\"`)\n\t}\n\n\tasync applyAddIndex(change: AddIndexChange): Promise<void> {\n\t\tconst name = change.name ?? this.#deriveIndexName(change.table, change.on as string[], change.unique)\n\t\tconst unique = change.unique ? 'UNIQUE ' : ''\n\t\tconst cols = (change.on as string[]).map((c) => `\"${c}\"`).join(', ')\n\t\tawait this.#getClient().query(`CREATE ${unique}INDEX \"${name}\" ON \"${change.table}\" (${cols})`)\n\t}\n\n\tasync applyDropIndex(change: DropIndexChange): Promise<void> {\n\t\tawait this.#getClient().query(`DROP INDEX \"${change.name}\"`)\n\t}\n\n\tasync applyAddForeignKey(change: AddForeignKeyChange): Promise<void> {\n\t\tconst name = change.name ?? `fk_${change.table}_${change.on}`\n\t\tlet sql = `ALTER TABLE \"${change.table}\" ADD CONSTRAINT \"${name}\" FOREIGN KEY (\"${change.on}\") REFERENCES \"${change.references.table}\" (\"${change.references.column}\")`\n\t\tif (change.onDelete) sql += ` ON DELETE ${this.#renderFkAction(change.onDelete)}`\n\t\tif (change.onUpdate) sql += ` ON UPDATE ${this.#renderFkAction(change.onUpdate)}`\n\t\tawait this.#getClient().query(sql)\n\t}\n\n\tasync applyDropForeignKey(change: DropForeignKeyChange): Promise<void> {\n\t\tawait this.#getClient().query(`ALTER TABLE \"${change.table}\" DROP CONSTRAINT \"${change.name}\"`)\n\t}\n\n\tasync introspect(): Promise<DiscoveredSchema[]> {\n\t\tconst { OrmIntrospectionError } = await import('../../errors/introspection')\n\t\tconst client = this.#getClient()\n\n\t\tconst tablesResult = await client.query(`\n\t\t\tSELECT table_name FROM information_schema.tables\n\t\t\tWHERE table_schema = 'public' AND table_type = 'BASE TABLE' AND table_name != $1\n\t\t`, [TRACKER_TABLE])\n\n\t\tconst schemas: DiscoveredSchema[] = []\n\n\t\tfor (const tableRow of tablesResult.rows) {\n\t\t\tconst tableName = tableRow.table_name\n\n\t\t\tconst colsResult = await client.query(`\n\t\t\t\tSELECT column_name, data_type, is_nullable, column_default\n\t\t\t\tFROM information_schema.columns\n\t\t\t\tWHERE table_schema = 'public' AND table_name = $1\n\t\t\t\tORDER BY ordinal_position\n\t\t\t`, [tableName])\n\n\t\t\tconst pkResult = await client.query(`\n\t\t\t\tSELECT kcu.column_name\n\t\t\t\tFROM information_schema.table_constraints tc\n\t\t\t\tJOIN information_schema.key_column_usage kcu\n\t\t\t\t\tON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema\n\t\t\t\tWHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'\n\t\t\t`, [tableName])\n\n\t\t\tconst pkColName = pkResult.rows[0]?.column_name as string | undefined\n\n\t\t\tconst uniqueResult = await client.query(`\n\t\t\t\tSELECT kcu.column_name\n\t\t\t\tFROM information_schema.table_constraints tc\n\t\t\t\tJOIN information_schema.key_column_usage kcu\n\t\t\t\t\tON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema\n\t\t\t\tWHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'UNIQUE'\n\t\t\t`, [tableName])\n\t\t\tconst uniqueCols = new Set(uniqueResult.rows.map((r: any) => r.column_name))\n\n\t\t\tlet pk: { name: string; type: FieldTypeName } | undefined\n\t\t\tconst fields: { name: string; type: FieldTypeName; nullable: boolean; default?: string | number | boolean | null; unique?: boolean }[] = []\n\n\t\t\tfor (const col of colsResult.rows) {\n\t\t\t\tconst pgType = (col.data_type as string).toLowerCase()\n\t\t\t\tconst fieldType = PG_TO_FIELD_TYPE[pgType]\n\t\t\t\tif (!fieldType) {\n\t\t\t\t\tthrow new OrmIntrospectionError({ adapter: 'postgresql', table: tableName, cause: `Unsupported column type '${pgType}' on column '${col.column_name}'` })\n\t\t\t\t}\n\n\t\t\t\tif (col.column_name === pkColName) {\n\t\t\t\t\tpk = { name: col.column_name, type: fieldType }\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconst nullable = col.is_nullable === 'YES'\n\t\t\t\tconst raw = col.column_default as string | null\n\t\t\t\tconst def = this.#parseDefault(raw)\n\n\t\t\t\tfields.push({\n\t\t\t\t\tname: col.column_name,\n\t\t\t\t\ttype: fieldType,\n\t\t\t\t\tnullable,\n\t\t\t\t\t...(def !== undefined ? { default: def } : {}),\n\t\t\t\t\t...(uniqueCols.has(col.column_name) ? { unique: true } : {}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tconst idxResult = await client.query(`\n\t\t\t\tSELECT indexname, indexdef\n\t\t\t\tFROM pg_indexes\n\t\t\t\tWHERE schemaname = 'public' AND tablename = $1\n\t\t\t`, [tableName])\n\n\t\t\tconst indexes: { name: string; on: readonly string[]; unique: boolean }[] = []\n\t\t\tfor (const idx of idxResult.rows) {\n\t\t\t\tconst idxName = idx.indexname as string\n\t\t\t\tconst idxDef = idx.indexdef as string\n\t\t\t\tif (idxDef.includes('PRIMARY KEY') || idxName.endsWith('_pkey')) continue\n\t\t\t\tconst unique = idxDef.toUpperCase().includes('UNIQUE')\n\t\t\t\tconst colMatch = idxDef.match(/\\(([^)]+)\\)/)\n\t\t\t\tconst cols = colMatch ? colMatch[1].split(',').map((c: string) => c.trim().replace(/\"/g, '')) : []\n\t\t\t\tindexes.push({ name: idxName, on: cols, unique })\n\t\t\t}\n\n\t\t\tconst fkResult = await client.query(`\n\t\t\t\tSELECT\n\t\t\t\t\ttc.constraint_name,\n\t\t\t\t\tkcu.column_name,\n\t\t\t\t\tccu.table_name AS foreign_table_name,\n\t\t\t\t\tccu.column_name AS foreign_column_name,\n\t\t\t\t\trc.delete_rule,\n\t\t\t\t\trc.update_rule\n\t\t\t\tFROM information_schema.table_constraints tc\n\t\t\t\tJOIN information_schema.key_column_usage kcu\n\t\t\t\t\tON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema\n\t\t\t\tJOIN information_schema.constraint_column_usage ccu\n\t\t\t\t\tON tc.constraint_name = ccu.constraint_name AND tc.table_schema = ccu.table_schema\n\t\t\t\tJOIN information_schema.referential_constraints rc\n\t\t\t\t\tON tc.constraint_name = rc.constraint_name AND tc.table_schema = rc.constraint_schema\n\t\t\t\tWHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'FOREIGN KEY'\n\t\t\t`, [tableName])\n\n\t\t\tconst foreignKeys: { name: string; on: string; references: { table: string; column: string }; onDelete?: ForeignKeyAction; onUpdate?: ForeignKeyAction }[] = []\n\t\t\tfor (const fk of fkResult.rows) {\n\t\t\t\tforeignKeys.push({\n\t\t\t\t\tname: fk.constraint_name,\n\t\t\t\t\ton: fk.column_name,\n\t\t\t\t\treferences: { table: fk.foreign_table_name, column: fk.foreign_column_name },\n\t\t\t\t\t...(fk.delete_rule && fk.delete_rule !== 'NO ACTION' ? { onDelete: this.#parseFkAction(fk.delete_rule) } : {}),\n\t\t\t\t\t...(fk.update_rule && fk.update_rule !== 'NO ACTION' ? { onUpdate: this.#parseFkAction(fk.update_rule) } : {}),\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tschemas.push({ name: tableName, pk, fields, indexes, foreignKeys })\n\t\t}\n\n\t\treturn schemas\n\t}\n\n\t#renderColumnDef(f: { name: string; type: string; nullable?: boolean; default?: string | number | boolean | null; unique?: boolean }): string {\n\t\tconst pgType = FIELD_TYPE_TO_PG[f.type] ?? 'text'\n\t\tlet def = `\"${f.name}\" ${pgType}`\n\t\tif (!f.nullable) def += ' NOT NULL'\n\t\tif (f.default !== undefined) def += ` DEFAULT ${this.#renderDefault(f.default)}`\n\t\tif (f.unique) def += ' UNIQUE'\n\t\treturn def\n\t}\n\n\t#renderDefault(val: string | number | boolean | null | undefined): string {\n\t\tif (val === null) return 'NULL'\n\t\tif (typeof val === 'string') return `'${val.replace(/'/g, \"''\")}'`\n\t\tif (typeof val === 'boolean') return val ? 'TRUE' : 'FALSE'\n\t\treturn String(val)\n\t}\n\n\t#deriveIndexName(table: string, cols: string[], unique?: boolean): string {\n\t\tconst base = `idx_${table}_${cols.join('_')}`\n\t\treturn unique ? `${base}_unique` : base\n\t}\n\n\t#renderFkAction(action: string): string {\n\t\tconst map: Record<string, string> = { cascade: 'CASCADE', restrict: 'RESTRICT', setnull: 'SET NULL', noaction: 'NO ACTION' }\n\t\treturn map[action.toLowerCase()] ?? 'NO ACTION'\n\t}\n\n\t#parseFkAction(rule: string): ForeignKeyAction {\n\t\tconst map: Record<string, ForeignKeyAction> = { CASCADE: 'cascade', RESTRICT: 'restrict', 'SET NULL': 'setNull' }\n\t\treturn map[rule.toUpperCase()] ?? 'noAction'\n\t}\n\n\t#parseDefault(raw: string | null): string | number | boolean | null | undefined {\n\t\tif (raw === null || raw === undefined) return undefined\n\t\tif (raw === 'NULL' || raw === 'NULL::text') return null\n\t\tif (raw === 'true' || raw === 'TRUE') return true\n\t\tif (raw === 'false' || raw === 'FALSE') return false\n\t\tconst numMatch = raw.match(/^(-?\\d+(?:\\.\\d+)?)$/)\n\t\tif (numMatch) return Number(numMatch[1])\n\t\tconst strMatch = raw.match(/^'(.*)'(?:::.*)?$/)\n\t\tif (strMatch) return strMatch[1].replace(/''/g, \"'\")\n\t\treturn undefined\n\t}\n\n\tasync session<T>(fn: () => Promise<T>): Promise<T> {\n\t\tif (this.#sessionStore.getStore()) return fn()\n\t\tlet client: PoolClient | undefined\n\t\ttry {\n\t\t\tclient = await this.pool.connect()\n\t\t\tawait client.query('BEGIN')\n\t\t\tconst result = await this.#sessionStore.run(client, fn)\n\t\t\tawait client.query('COMMIT')\n\t\t\treturn result\n\t\t} catch (error) {\n\t\t\tif (client) {\n\t\t\t\ttry {\n\t\t\t\t\tawait client.query('ROLLBACK')\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore rollback errors and report original failure\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (error instanceof EquippedError) throw error\n\t\t\tthrow new EquippedError('PostgreSQL session failed', { adapter: 'postgresql', operation: 'session' }, error)\n\t\t} finally {\n\t\t\tclient?.release()\n\t\t}\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf, vi, beforeEach, afterEach } = import.meta.vitest\n\n\tconst testConnectionConfig = {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'test',\n\t\tpassword: 'test',\n\t\tdatabase: 'testdb',\n\t\tssl: false,\n\t} as const\n\n\tdescribe('PostgresAdapter: class-via-configurable shape', () => {\n\t\tlet onSpy: ReturnType<typeof vi.spyOn>\n\n\t\tbeforeEach(async () => {\n\t\t\tconst { Instance } = await import('../../../instance')\n\t\t\tonSpy = vi.spyOn(Instance, 'on').mockImplementation(() => {})\n\t\t})\n\n\t\tafterEach(() => {\n\t\t\tonSpy.mockRestore()\n\t\t})\n\n\t\ttest('PostgresAdapter.create returns instance with correct capability declarations', () => {\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\n\t\t\texpect(adapter.supportedFieldTypes).toEqual(['string', 'number', 'boolean', 'null', 'object', 'array', 'date'])\n\t\t\texpect(adapter.queryableOps).toEqual([\n\t\t\t\t'eq',\n\t\t\t\t'ne',\n\t\t\t\t'gt',\n\t\t\t\t'gte',\n\t\t\t\t'lt',\n\t\t\t\t'lte',\n\t\t\t\t'in',\n\t\t\t\t'notIn',\n\t\t\t\t'like',\n\t\t\t\t'exists',\n\t\t\t\t'notExists',\n\t\t\t\t'contains',\n\t\t\t\t'notContains',\n\t\t\t])\n\t\t\texpect(adapter.updateOps).toEqual(['set', 'inc', 'mul', 'min', 'max', 'unset', 'push', 'pull', 'patch'])\n\t\t})\n\n\t\ttest('PostgresAdapter.create validates connection config', () => {\n\t\t\texpect(() => PostgresAdapter.create({ host: 123, port: 'bad' } as any)).toThrow()\n\t\t})\n\n\t\ttest('pool is exposed as a readonly instance field', () => {\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\n\t\t\texpect(adapter.pool).toBeInstanceOf(Pool)\n\t\t})\n\n\t\ttest('schemaConfigPipe is declared as readonly with table-name shape', () => {\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\n\t\t\texpect(adapter.schemaConfigPipe).toBeDefined()\n\t\t})\n\n\t\ttest('auto-wires Instance hooks for connect and disconnect', () => {\n\t\t\tPostgresAdapter.create(testConnectionConfig)\n\n\t\t\texpect(onSpy).toHaveBeenCalledWith('start', expect.any(Function), expect.objectContaining({ class: PostgresAdapter }))\n\t\t\texpect(onSpy).toHaveBeenCalledWith('close', expect.any(Function), expect.objectContaining({ class: PostgresAdapter }))\n\t\t})\n\n\t\ttest('adapter.use returns OrmUse-shaped object', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst schema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst use = adapter.use(schema, { table: 'test' })\n\n\t\t\texpect(use.findMany).toBeTypeOf('function')\n\t\t\texpect(use.iterateMany).toBeTypeOf('function')\n\t\t\texpect(use.findOne).toBeTypeOf('function')\n\t\t\texpect(use.count).toBeTypeOf('function')\n\t\t\texpect(use.createOne).toBeTypeOf('function')\n\t\t\texpect(use.createMany).toBeTypeOf('function')\n\t\t\texpect(use.updateMany).toBeTypeOf('function')\n\t\t\texpect(use.updateOne).toBeTypeOf('function')\n\t\t\texpect(use.upsertOne).toBeTypeOf('function')\n\t\t\texpect(use.deleteOne).toBeTypeOf('function')\n\t\t\texpect(use.deleteMany).toBeTypeOf('function')\n\t\t\texpect(use.raw).toBeTypeOf('function')\n\t\t})\n\n\t\ttest('type-level: capability declarations use as const', () => {\n\t\t\tconst _adapter = PostgresAdapter.create(testConnectionConfig)\n\n\t\t\ttype Ops = typeof _adapter.updateOps\n\t\t\texpectTypeOf<Ops>().toEqualTypeOf<readonly ['set', 'inc', 'mul', 'min', 'max', 'unset', 'push', 'pull', 'patch']>()\n\n\t\t\ttype Types = typeof _adapter.supportedFieldTypes\n\t\t\texpectTypeOf<Types>().toEqualTypeOf<readonly ['string', 'number', 'boolean', 'null', 'object', 'array', 'date']>()\n\n\t\t\ttype QOps = typeof _adapter.queryableOps\n\t\t\texpectTypeOf<QOps>().toEqualTypeOf<\n\t\t\t\treadonly ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'like', 'exists', 'notExists', 'contains', 'notContains']\n\t\t\t>()\n\t\t})\n\n\t\ttest('type-level: Repo.from with PostgresAdapter enables all builder methods', async () => {\n\t\t\tconst { Repo } = await import('../../repo/repo')\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve(() => ({ table: 'test' }))\n\t\t\t\t.build()\n\t\t\tconst _TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\n\t\t\tconst _one = repo.on(_TestSchema).one()\n\t\t\tconst _all = repo.on(_TestSchema).all()\n\t\t\tconst _ref = repo.on(_TestSchema)\n\t\t\texpectTypeOf(_one.create).toBeFunction()\n\t\t\texpectTypeOf(_one.find).toBeFunction()\n\t\t\texpectTypeOf(_one.update).toBeFunction()\n\t\t\texpectTypeOf(_one.delete).toBeFunction()\n\t\t\texpectTypeOf(_one.upsert).toBeFunction()\n\t\t\texpectTypeOf(_all.create).toBeFunction()\n\t\t\texpectTypeOf(_all.find).toBeFunction()\n\t\t\texpectTypeOf(_all.count).toBeFunction()\n\t\t\texpectTypeOf(_all.update).toBeFunction()\n\t\t\texpectTypeOf(_all.delete).toBeFunction()\n\t\t\texpectTypeOf(_ref.raw).toBeFunction()\n\t\t\texpectTypeOf(repo.session).toBeFunction()\n\t\t})\n\n\t\ttest('type-level: raw arg-tuple infers (command: string, params?: unknown[]) from PostgresAdapter', async () => {\n\t\t\tconst { Repo } = await import('../../repo/repo')\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve(() => ({ table: 'test' }))\n\t\t\t\t.build()\n\t\t\tconst _TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst _ref = repo.on(_TestSchema)\n\n\t\t\texpectTypeOf(_ref.raw).parameters.toEqualTypeOf<[command: string, params?: unknown[]]>()\n\t\t})\n\n\t\ttest('type-level: per-call <T> override narrows PostgresAdapter raw return type', async () => {\n\t\t\tconst { Repo } = await import('../../repo/repo')\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve(() => ({ table: 'test' }))\n\t\t\t\t.build()\n\t\t\tconst _TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst _ref = repo.on(_TestSchema)\n\n\t\t\texpectTypeOf(_ref.raw<{ id: string }[]>).returns.toEqualTypeOf<Promise<{ id: string }[]>>()\n\t\t})\n\n\t\ttest('count forwards COUNT query to pool.query and returns a number', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\t\t\tconst { FilterGroup } = await import('../../filter')\n\n\t\t\tconst schema = Schema.from('pg_count')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tlet capturedSql: unknown\n\t\t\tlet capturedParams: unknown\n\t\t\t;(adapter.pool as any).query = async (sql: unknown, params: unknown) => {\n\t\t\t\tcapturedSql = sql\n\t\t\t\tcapturedParams = params\n\t\t\t\treturn { rows: [{ count: '4' }] }\n\t\t\t}\n\n\t\t\tconst result = await adapter.count(schema, { table: 'users' }, FilterGroup.create().eq('name', 'Alice'))\n\t\t\texpect(capturedSql).toBe('SELECT COUNT(*) as count FROM \"users\" WHERE \"name\" = $1')\n\t\t\texpect(capturedParams).toEqual(['Alice'])\n\t\t\texpect(result).toBe(4)\n\t\t})\n\n\t\ttest('raw forwards (command, params) to pool.query at runtime', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst schema = Schema.from('pg_raw')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tlet capturedCommand: unknown\n\t\t\tlet capturedParams: unknown\n\t\t\tconst mockResult = { rows: [{ id: '1' }], rowCount: 1 }\n\t\t\t;(adapter.pool as any).query = async (cmd: unknown, params: unknown) => {\n\t\t\t\tcapturedCommand = cmd\n\t\t\t\tcapturedParams = params\n\t\t\t\treturn mockResult\n\t\t\t}\n\t\t\tconst result = await adapter.use(schema, { table: 'users' }).raw('SELECT * FROM users WHERE id = $1', ['abc'])\n\t\t\texpect(capturedCommand).toBe('SELECT * FROM users WHERE id = $1')\n\t\t\texpect(capturedParams).toEqual(['abc'])\n\t\t\texpect(result).toBe(mockResult)\n\t\t})\n\n\t\ttest('raw defaults params to [] when omitted', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst schema = Schema.from('pg_raw2')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tlet capturedParams: unknown\n\t\t\t;(adapter.pool as any).query = async (_cmd: unknown, params: unknown) => {\n\t\t\t\tcapturedParams = params\n\t\t\t\treturn { rows: [] }\n\t\t\t}\n\t\t\tawait adapter.use(schema, { table: 'users' }).raw('SELECT 1')\n\t\t\texpect(capturedParams).toEqual([])\n\t\t})\n\n\t\ttest('iterateMany opens a dedicated cursor client, reads in batchSize chunks, and releases on completion', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\t\t\tconst { FilterGroup } = await import('../../filter')\n\t\t\tconst { OrderBy } = await import('../../query-options')\n\n\t\t\tconst schema = Schema.from('pg_iter')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tlet capturedCursor: Cursor<Record<string, unknown>> | undefined\n\t\t\tconst release = vi.fn()\n\t\t\tconst cursor = {\n\t\t\t\tread: vi.fn()\n\t\t\t\t\t.mockResolvedValueOnce([{ id: 'u3', age: 40 }])\n\t\t\t\t\t.mockResolvedValueOnce([{ id: 'u1', age: 30 }])\n\t\t\t\t\t.mockResolvedValueOnce([]),\n\t\t\t\tclose: vi.fn().mockResolvedValue(undefined),\n\t\t\t}\n\t\t\tconst client = {\n\t\t\t\tquery: vi.fn((query: Cursor<Record<string, unknown>>) => {\n\t\t\t\t\tcapturedCursor = query\n\t\t\t\t\treturn cursor\n\t\t\t\t}),\n\t\t\t\trelease,\n\t\t\t}\n\t\t\t;(adapter.pool as any).connect = vi.fn(async () => client)\n\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tfor await (const row of adapter.use(schema, { table: 'people' }).iterateMany(FilterGroup.create().gt('age', 19), {\n\t\t\t\torderBy: [new OrderBy('age', 'desc')],\n\t\t\t\toffset: 1,\n\t\t\t\tlimit: 2,\n\t\t\t\tbatchSize: 1,\n\t\t\t})) {\n\t\t\t\trows.push(row)\n\t\t\t}\n\n\t\t\texpect((adapter.pool as any).connect).toHaveBeenCalledTimes(1)\n\t\t\texpect(capturedCursor).toBeInstanceOf(Cursor)\n\t\t\tconst capturedCursorConfig = capturedCursor as unknown as { text: string; values: unknown[] }\n\t\t\texpect(capturedCursorConfig.text).toBe('SELECT * FROM \"people\" WHERE \"age\" > $1 ORDER BY \"age\" DESC LIMIT $2 OFFSET $3')\n\t\t\texpect(capturedCursorConfig.values).toEqual(['19', '2', '1'])\n\t\t\texpect(cursor.read).toHaveBeenNthCalledWith(1, 1)\n\t\t\texpect(cursor.read).toHaveBeenNthCalledWith(2, 1)\n\t\t\texpect(cursor.read).toHaveBeenNthCalledWith(3, 1)\n\t\t\texpect(cursor.close).toHaveBeenCalledTimes(1)\n\t\t\texpect(release).toHaveBeenCalledTimes(1)\n\t\t\texpect(rows).toEqual([{ id: 'u3', age: 40 }, { id: 'u1', age: 30 }])\n\t\t})\n\n\t\ttest('iterateMany closes cursor and releases dedicated client on early break', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\t\t\tconst { FilterGroup } = await import('../../filter')\n\n\t\t\tconst schema = Schema.from('pg_iter_break')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst release = vi.fn()\n\t\t\tconst cursor = {\n\t\t\t\tread: vi.fn().mockResolvedValueOnce([{ id: 'u1' }, { id: 'u2' }]),\n\t\t\t\tclose: vi.fn().mockResolvedValue(undefined),\n\t\t\t}\n\t\t\t;(adapter.pool as any).connect = vi.fn(async () => ({\n\t\t\t\tquery: vi.fn(() => cursor),\n\t\t\t\trelease,\n\t\t\t}))\n\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tfor await (const row of adapter.use(schema, { table: 'people' }).iterateMany(FilterGroup.create())) {\n\t\t\t\trows.push(row)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\texpect(rows).toEqual([{ id: 'u1' }])\n\t\t\texpect(cursor.close).toHaveBeenCalledTimes(1)\n\t\t\texpect(release).toHaveBeenCalledTimes(1)\n\t\t})\n\n\t\ttest('iterateMany closes cursor and releases dedicated client when the consumer throws', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\t\t\tconst { FilterGroup } = await import('../../filter')\n\n\t\t\tconst schema = Schema.from('pg_iter_throw')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst release = vi.fn()\n\t\t\tconst cursor = {\n\t\t\t\tread: vi.fn().mockResolvedValueOnce([{ id: 'u1' }]),\n\t\t\t\tclose: vi.fn().mockResolvedValue(undefined),\n\t\t\t}\n\t\t\t;(adapter.pool as any).connect = vi.fn(async () => ({\n\t\t\t\tquery: vi.fn(() => cursor),\n\t\t\t\trelease,\n\t\t\t}))\n\t\t\tconst consumerError = new Error('consumer failed')\n\n\t\t\tawait expect(async () => {\n\t\t\t\tfor await (const _row of adapter.use(schema, { table: 'people' }).iterateMany(FilterGroup.create())) {\n\t\t\t\t\tthrow consumerError\n\t\t\t\t}\n\t\t\t}).rejects.toThrow(consumerError)\n\n\t\t\texpect(cursor.close).toHaveBeenCalledTimes(1)\n\t\t\texpect(release).toHaveBeenCalledTimes(1)\n\t\t})\n\n\t\ttest('iterateMany inside a session uses the active client and leaves release ownership to session', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\t\t\tconst { FilterGroup } = await import('../../filter')\n\n\t\t\tconst schema = Schema.from('pg_iter_session')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst events: string[] = []\n\t\t\tconst release = vi.fn(() => events.push('release'))\n\t\t\tconst cursor = {\n\t\t\t\tread: vi.fn()\n\t\t\t\t\t.mockResolvedValueOnce([{ id: 'u1' }])\n\t\t\t\t\t.mockResolvedValueOnce([]),\n\t\t\t\tclose: vi.fn(async () => events.push('cursor.close')),\n\t\t\t}\n\t\t\tconst client = {\n\t\t\t\tquery: vi.fn((query: string | Cursor<Record<string, unknown>>) => {\n\t\t\t\t\tif (typeof query === 'string') {\n\t\t\t\t\t\tevents.push(query)\n\t\t\t\t\t\treturn { rows: [] }\n\t\t\t\t\t}\n\t\t\t\t\tevents.push('cursor.open')\n\t\t\t\t\treturn cursor\n\t\t\t\t}),\n\t\t\t\trelease,\n\t\t\t}\n\t\t\t;(adapter.pool as any).connect = vi.fn(async () => client)\n\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tawait adapter.session(async () => {\n\t\t\t\tfor await (const row of adapter.use(schema, { table: 'people' }).iterateMany(FilterGroup.create(), { batchSize: 1 })) {\n\t\t\t\t\trows.push(row)\n\t\t\t\t}\n\t\t\t\tevents.push('session.body.done')\n\t\t\t})\n\n\t\t\texpect((adapter.pool as any).connect).toHaveBeenCalledTimes(1)\n\t\t\texpect(rows).toEqual([{ id: 'u1' }])\n\t\t\texpect(events).toEqual(['BEGIN', 'cursor.open', 'cursor.close', 'session.body.done', 'COMMIT', 'release'])\n\t\t\texpect(release).toHaveBeenCalledTimes(1)\n\t\t})\n\n\t\ttest('session method is exposed on the adapter', () => {\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\texpect(adapter.session).toBeDefined()\n\t\t\texpect(adapter.session).toBeTypeOf('function')\n\t\t})\n\n\t\ttest('declares aggregateOps with all six canonical ops', () => {\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\texpect(adapter.aggregateOps).toEqual(['count', 'countDistinct', 'sum', 'avg', 'min', 'max'])\n\t\t})\n\n\t\ttest('type-level: aggregateOps is a readonly tuple of all six ops', () => {\n\t\t\tconst _adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\ttype AggOps = typeof _adapter.aggregateOps\n\t\t\texpectTypeOf<AggOps>().toEqualTypeOf<\n\t\t\t\treadonly ['count', 'countDistinct', 'sum', 'avg', 'min', 'max']\n\t\t\t>()\n\t\t})\n\n\t\ttest('aggregate method forwards spec to pool.query and returns mapped rows', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst schema = Schema.from('pg_agg')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('amount', v.number())\n\t\t\t\t.field('region', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tlet capturedSql: unknown\n\t\t\tlet capturedParams: unknown\n\t\t\t;(adapter.pool as any).query = async (sql: unknown, params: unknown) => {\n\t\t\t\tcapturedSql = sql\n\t\t\t\tcapturedParams = params\n\t\t\t\treturn { rows: [{ total: '5' }] }\n\t\t\t}\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(capturedSql).toBe('SELECT COUNT(*) AS \"total\" FROM \"orders\"')\n\t\t\texpect(capturedParams).toEqual([])\n\t\t\texpect(result).toEqual([{ total: 5 }])\n\t\t})\n\n\t\ttest('aggregate converts PG bigint/numeric strings to numbers for count/sum/avg', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst schema = Schema.from('pg_agg_types')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('amount', v.number())\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\t;(adapter.pool as any).query = async () => ({\n\t\t\t\trows: [{ cnt: '10', revenue: '250.50', avgAmt: '25.05' }],\n\t\t\t})\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'count', alias: 'cnt' },\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'revenue' },\n\t\t\t\t\t{ fn: 'avg', field: 'amount', alias: 'avgAmt' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result[0].cnt).toBe(10)\n\t\t\texpect(result[0].revenue).toBe(250.5)\n\t\t\texpect(result[0].avgAmt).toBe(25.05)\n\t\t})\n\n\t\ttest('aggregate preserves original type for min/max', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst schema = Schema.from('pg_agg_minmax')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\t;(adapter.pool as any).query = async () => ({\n\t\t\t\trows: [{ lowest: 'Alice', highest: 'Zara' }],\n\t\t\t})\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'users' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'min', field: 'name', alias: 'lowest' },\n\t\t\t\t\t{ fn: 'max', field: 'name', alias: 'highest' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result[0].lowest).toBe('Alice')\n\t\t\texpect(result[0].highest).toBe('Zara')\n\t\t})\n\n\t\ttest('aggregate includes groupBy fields in result rows', async () => {\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst schema = Schema.from('pg_agg_group')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('region', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\t;(adapter.pool as any).query = async () => ({\n\t\t\t\trows: [\n\t\t\t\t\t{ cnt: '3', region: 'US' },\n\t\t\t\t\t{ cnt: '2', region: 'EU' },\n\t\t\t\t],\n\t\t\t})\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t})\n\t\t\texpect(result).toEqual([\n\t\t\t\t{ cnt: 3, region: 'US' },\n\t\t\t\t{ cnt: 2, region: 'EU' },\n\t\t\t])\n\t\t})\n\n\t\ttest('type-level: Repo.from with PostgresAdapter enables aggregate method', async () => {\n\t\t\tconst { Repo } = await import('../../repo/repo')\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tconst adapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve(() => ({ table: 'test' }))\n\t\t\t\t.build()\n\t\t\tconst _TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\n\t\t\tconst _ref = repo.on(_TestSchema)\n\t\t\texpectTypeOf(_ref.aggregate).toBeFunction()\n\t\t})\n\t})\n\n\tdescribe('PostgresAdapter: migration methods', () => {\n\t\tlet onSpy: ReturnType<typeof vi.spyOn>\n\t\tlet adapter: PostgresAdapter\n\t\tlet queries: { sql: string; params?: unknown[] }[]\n\n\t\tbeforeEach(async () => {\n\t\t\tconst { Instance } = await import('../../../instance')\n\t\t\tonSpy = vi.spyOn(Instance, 'on').mockImplementation(() => {})\n\t\t\tadapter = PostgresAdapter.create(testConnectionConfig)\n\t\t\tqueries = []\n\t\t\t;(adapter.pool as any).query = async (sql: string, params?: unknown[]) => {\n\t\t\t\tqueries.push({ sql, params })\n\t\t\t\treturn { rows: [] }\n\t\t\t}\n\t\t})\n\n\t\tafterEach(() => {\n\t\t\tonSpy.mockRestore()\n\t\t})\n\n\t\ttest('loadMigrations creates tracker table then selects rows', async () => {\n\t\t\t;(adapter.pool as any).query = async (sql: string, params?: unknown[]) => {\n\t\t\t\tqueries.push({ sql, params })\n\t\t\t\tif (sql.includes('SELECT id')) {\n\t\t\t\t\treturn { rows: [{ id: '0001', applied_at: '1700000000000' }] }\n\t\t\t\t}\n\t\t\t\treturn { rows: [] }\n\t\t\t}\n\t\t\tconst result = await adapter.loadMigrations()\n\t\t\texpect(queries).toHaveLength(2)\n\t\t\texpect(queries[0].sql).toContain('CREATE TABLE IF NOT EXISTS')\n\t\t\texpect(queries[0].sql).toContain('equipped_migrations')\n\t\t\texpect(queries[1].sql).toContain('SELECT id, applied_at')\n\t\t\texpect(result).toEqual([{ id: '0001', appliedAt: 1700000000000 }])\n\t\t})\n\n\t\ttest('recordMigration inserts into tracker table', async () => {\n\t\t\tawait adapter.recordMigration('0001-init', 1700000000000)\n\t\t\texpect(queries).toHaveLength(1)\n\t\t\texpect(queries[0].sql).toContain('INSERT INTO')\n\t\t\texpect(queries[0].sql).toContain('equipped_migrations')\n\t\t\texpect(queries[0].params).toEqual(['0001-init', 1700000000000])\n\t\t})\n\n\t\ttest('acquireMigrationLock wraps fn with pg_advisory_lock/unlock', async () => {\n\t\t\tconst result = await adapter.acquireMigrationLock(async () => {\n\t\t\t\tqueries.push({ sql: 'USER_FN' })\n\t\t\t\treturn 42\n\t\t\t})\n\t\t\texpect(result).toBe(42)\n\t\t\texpect(queries[0].sql).toContain('pg_advisory_lock')\n\t\t\texpect(queries[1].sql).toBe('USER_FN')\n\t\t\texpect(queries[2].sql).toContain('pg_advisory_unlock')\n\t\t})\n\n\t\ttest('acquireMigrationLock unlocks even on error', async () => {\n\t\t\tawait expect(adapter.acquireMigrationLock(async () => {\n\t\t\t\tthrow new Error('boom')\n\t\t\t})).rejects.toThrow('boom')\n\t\t\texpect(queries[0].sql).toContain('pg_advisory_lock')\n\t\t\texpect(queries[1].sql).toContain('pg_advisory_unlock')\n\t\t})\n\n\t\ttest('applyCreateTable generates correct DDL', async () => {\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [\n\t\t\t\t\t{ name: 'email', type: 'string', unique: true },\n\t\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t\t\t{ name: 'active', type: 'boolean', default: true },\n\t\t\t\t],\n\t\t\t})\n\t\t\texpect(queries).toHaveLength(1)\n\t\t\tconst sql = queries[0].sql\n\t\t\texpect(sql).toContain('CREATE TABLE \"users\"')\n\t\t\texpect(sql).toContain('\"id\" text PRIMARY KEY')\n\t\t\texpect(sql).toContain('\"email\" text NOT NULL UNIQUE')\n\t\t\texpect(sql).toContain('\"age\" double precision')\n\t\t\texpect(sql).not.toContain('\"age\" double precision NOT NULL')\n\t\t\texpect(sql).toContain('\"active\" boolean NOT NULL DEFAULT TRUE')\n\t\t})\n\n\t\ttest('applyDropTable generates DROP TABLE', async () => {\n\t\t\tawait adapter.applyDropTable({ kind: 'dropTable', name: 'users' })\n\t\t\texpect(queries[0].sql).toBe('DROP TABLE \"users\"')\n\t\t})\n\n\t\ttest('applyAddField generates ALTER TABLE ADD COLUMN', async () => {\n\t\t\tawait adapter.applyAddField({\n\t\t\t\tkind: 'addField',\n\t\t\t\ttable: 'users',\n\t\t\t\tfield: { name: 'bio', type: 'string', nullable: true, default: 'none' },\n\t\t\t})\n\t\t\texpect(queries[0].sql).toContain('ALTER TABLE \"users\" ADD COLUMN')\n\t\t\texpect(queries[0].sql).toContain('\"bio\" text')\n\t\t\texpect(queries[0].sql).not.toContain('NOT NULL')\n\t\t\texpect(queries[0].sql).toContain(\"DEFAULT 'none'\")\n\t\t})\n\n\t\ttest('applyDropField generates ALTER TABLE DROP COLUMN', async () => {\n\t\t\tawait adapter.applyDropField({ kind: 'dropField', table: 'users', name: 'bio' })\n\t\t\texpect(queries[0].sql).toBe('ALTER TABLE \"users\" DROP COLUMN \"bio\"')\n\t\t})\n\n\t\ttest('applyModifyField generates ALTER COLUMN statements for type, nullable, default', async () => {\n\t\t\tawait adapter.applyModifyField({\n\t\t\t\tkind: 'modifyField',\n\t\t\t\ttable: 'users',\n\t\t\t\tname: 'age',\n\t\t\t\tto: { name: 'age', type: 'string', nullable: true, default: null },\n\t\t\t})\n\t\t\texpect(queries[0].sql).toContain('ALTER COLUMN \"age\" TYPE text')\n\t\t\texpect(queries[1].sql).toContain('DROP NOT NULL')\n\t\t\texpect(queries[2].sql).toContain('SET DEFAULT NULL')\n\t\t})\n\n\t\ttest('applyModifyField renames column when to.name differs', async () => {\n\t\t\tawait adapter.applyModifyField({\n\t\t\t\tkind: 'modifyField',\n\t\t\t\ttable: 'users',\n\t\t\t\tname: 'age',\n\t\t\t\tto: { name: 'years', type: 'number' },\n\t\t\t})\n\t\t\tconst renameSql = queries.find((q) => q.sql.includes('RENAME COLUMN'))\n\t\t\texpect(renameSql).toBeDefined()\n\t\t\texpect(renameSql!.sql).toContain('RENAME COLUMN \"age\" TO \"years\"')\n\t\t})\n\n\t\ttest('applyRenameTable generates ALTER TABLE RENAME', async () => {\n\t\t\tawait adapter.applyRenameTable({ kind: 'renameTable', from: 'users', to: 'accounts' })\n\t\t\texpect(queries[0].sql).toBe('ALTER TABLE \"users\" RENAME TO \"accounts\"')\n\t\t})\n\n\t\ttest('applyRenameField generates ALTER TABLE RENAME COLUMN', async () => {\n\t\t\tawait adapter.applyRenameField({ kind: 'renameField', table: 'users', from: 'name', to: 'fullName' })\n\t\t\texpect(queries[0].sql).toBe('ALTER TABLE \"users\" RENAME COLUMN \"name\" TO \"fullName\"')\n\t\t})\n\n\t\ttest('applyAddIndex generates CREATE INDEX with auto-derived name', async () => {\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'] })\n\t\t\texpect(queries[0].sql).toBe('CREATE INDEX \"idx_users_email\" ON \"users\" (\"email\")')\n\t\t})\n\n\t\ttest('applyAddIndex generates CREATE UNIQUE INDEX with explicit name', async () => {\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true, name: 'my_idx' })\n\t\t\texpect(queries[0].sql).toBe('CREATE UNIQUE INDEX \"my_idx\" ON \"users\" (\"email\")')\n\t\t})\n\n\t\ttest('applyAddIndex auto-derives unique suffix when unique and no name', async () => {\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'posts', on: ['author', 'title'], unique: true })\n\t\t\texpect(queries[0].sql).toBe('CREATE UNIQUE INDEX \"idx_posts_author_title_unique\" ON \"posts\" (\"author\", \"title\")')\n\t\t})\n\n\t\ttest('applyDropIndex generates DROP INDEX', async () => {\n\t\t\tawait adapter.applyDropIndex({ kind: 'dropIndex', name: 'idx_users_email' })\n\t\t\texpect(queries[0].sql).toBe('DROP INDEX \"idx_users_email\"')\n\t\t})\n\n\t\ttest('applyAddForeignKey generates ALTER TABLE ADD CONSTRAINT with auto-derived name', async () => {\n\t\t\tawait adapter.applyAddForeignKey({\n\t\t\t\tkind: 'addForeignKey',\n\t\t\t\ttable: 'posts',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t\tonUpdate: 'setNull',\n\t\t\t})\n\t\t\tconst sql = queries[0].sql\n\t\t\texpect(sql).toContain('ALTER TABLE \"posts\" ADD CONSTRAINT \"fk_posts_authorId\"')\n\t\t\texpect(sql).toContain('FOREIGN KEY (\"authorId\") REFERENCES \"users\" (\"id\")')\n\t\t\texpect(sql).toContain('ON DELETE CASCADE')\n\t\t\texpect(sql).toContain('ON UPDATE SET NULL')\n\t\t})\n\n\t\ttest('applyAddForeignKey uses explicit name when provided', async () => {\n\t\t\tawait adapter.applyAddForeignKey({\n\t\t\t\tkind: 'addForeignKey',\n\t\t\t\ttable: 'posts',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tname: 'custom_fk',\n\t\t\t})\n\t\t\texpect(queries[0].sql).toContain('ADD CONSTRAINT \"custom_fk\"')\n\t\t})\n\n\t\ttest('applyDropForeignKey generates ALTER TABLE DROP CONSTRAINT', async () => {\n\t\t\tawait adapter.applyDropForeignKey({ kind: 'dropForeignKey', table: 'posts', name: 'fk_posts_authorId' })\n\t\t\texpect(queries[0].sql).toBe('ALTER TABLE \"posts\" DROP CONSTRAINT \"fk_posts_authorId\"')\n\t\t})\n\n\t\ttest('introspect queries information_schema and builds DiscoveredSchema[]', async () => {\n\t\t\t;(adapter.pool as any).query = async (sql: string, params?: unknown[]) => {\n\t\t\t\tqueries.push({ sql, params })\n\t\t\t\tif (sql.includes('information_schema.tables')) {\n\t\t\t\t\treturn { rows: [{ table_name: 'users' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes('information_schema.columns')) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\trows: [\n\t\t\t\t\t\t\t{ column_name: 'id', data_type: 'text', is_nullable: 'NO', column_default: null },\n\t\t\t\t\t\t\t{ column_name: 'email', data_type: 'character varying', is_nullable: 'NO', column_default: null },\n\t\t\t\t\t\t\t{ column_name: 'age', data_type: 'integer', is_nullable: 'YES', column_default: '25' },\n\t\t\t\t\t\t],\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (sql.includes('PRIMARY KEY')) {\n\t\t\t\t\treturn { rows: [{ column_name: 'id' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes(\"constraint_type = 'UNIQUE'\")) {\n\t\t\t\t\treturn { rows: [{ column_name: 'email' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes('pg_indexes')) {\n\t\t\t\t\treturn { rows: [{ indexname: 'users_email_idx', indexdef: 'CREATE UNIQUE INDEX users_email_idx ON public.users USING btree (email)' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes(\"constraint_type = 'FOREIGN KEY'\")) {\n\t\t\t\t\treturn { rows: [] }\n\t\t\t\t}\n\t\t\t\treturn { rows: [] }\n\t\t\t}\n\t\t\tconst schemas = await adapter.introspect()\n\t\t\texpect(schemas).toHaveLength(1)\n\t\t\tconst s = schemas[0]\n\t\t\texpect(s.name).toBe('users')\n\t\t\texpect(s.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(s.fields).toEqual([\n\t\t\t\t{ name: 'email', type: 'string', nullable: false, unique: true },\n\t\t\t\t{ name: 'age', type: 'number', nullable: true, default: 25 },\n\t\t\t])\n\t\t\texpect(s.indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\t\t\texpect(s.foreignKeys).toEqual([])\n\t\t})\n\n\t\ttest('introspect throws OrmIntrospectionError on unsupported PG type', async () => {\n\t\t\tconst { OrmIntrospectionError } = await import('../../errors/introspection')\n\t\t\t;(adapter.pool as any).query = async (sql: string) => {\n\t\t\t\tif (sql.includes('information_schema.tables')) {\n\t\t\t\t\treturn { rows: [{ table_name: 'files' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes('information_schema.columns')) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\trows: [\n\t\t\t\t\t\t\t{ column_name: 'id', data_type: 'text', is_nullable: 'NO', column_default: null },\n\t\t\t\t\t\t\t{ column_name: 'data', data_type: 'bytea', is_nullable: 'YES', column_default: null },\n\t\t\t\t\t\t],\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (sql.includes('PRIMARY KEY')) {\n\t\t\t\t\treturn { rows: [{ column_name: 'id' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes(\"constraint_type = 'UNIQUE'\")) {\n\t\t\t\t\treturn { rows: [] }\n\t\t\t\t}\n\t\t\t\treturn { rows: [] }\n\t\t\t}\n\t\t\tawait expect(adapter.introspect()).rejects.toThrow(OrmIntrospectionError)\n\t\t\ttry {\n\t\t\t\tawait adapter.introspect()\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err.adapter).toBe('postgresql')\n\t\t\t\texpect(err.table).toBe('files')\n\t\t\t\texpect(err.cause).toContain('bytea')\n\t\t\t}\n\t\t})\n\n\t\ttest('introspect parses foreign keys with cascade actions', async () => {\n\t\t\t;(adapter.pool as any).query = async (sql: string) => {\n\t\t\t\tif (sql.includes('information_schema.tables')) {\n\t\t\t\t\treturn { rows: [{ table_name: 'posts' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes('information_schema.columns')) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\trows: [\n\t\t\t\t\t\t\t{ column_name: 'id', data_type: 'text', is_nullable: 'NO', column_default: null },\n\t\t\t\t\t\t\t{ column_name: 'author_id', data_type: 'text', is_nullable: 'NO', column_default: null },\n\t\t\t\t\t\t],\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (sql.includes('PRIMARY KEY')) {\n\t\t\t\t\treturn { rows: [{ column_name: 'id' }] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes(\"constraint_type = 'UNIQUE'\")) {\n\t\t\t\t\treturn { rows: [] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes('pg_indexes')) {\n\t\t\t\t\treturn { rows: [] }\n\t\t\t\t}\n\t\t\t\tif (sql.includes(\"constraint_type = 'FOREIGN KEY'\")) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\trows: [{\n\t\t\t\t\t\t\tconstraint_name: 'fk_posts_author_id',\n\t\t\t\t\t\t\tcolumn_name: 'author_id',\n\t\t\t\t\t\t\tforeign_table_name: 'users',\n\t\t\t\t\t\t\tforeign_column_name: 'id',\n\t\t\t\t\t\t\tdelete_rule: 'CASCADE',\n\t\t\t\t\t\t\tupdate_rule: 'SET NULL',\n\t\t\t\t\t\t}],\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { rows: [] }\n\t\t\t}\n\t\t\tconst schemas = await adapter.introspect()\n\t\t\texpect(schemas[0].foreignKeys).toEqual([{\n\t\t\t\tname: 'fk_posts_author_id',\n\t\t\t\ton: 'author_id',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t\tonUpdate: 'setNull',\n\t\t\t}])\n\t\t})\n\n\t\ttest('introspect excludes tracker table from results', async () => {\n\t\t\t;(adapter.pool as any).query = async (sql: string, params?: unknown[]) => {\n\t\t\t\tif (sql.includes('information_schema.tables')) {\n\t\t\t\t\texpect(params).toContain('equipped_migrations')\n\t\t\t\t\treturn { rows: [] }\n\t\t\t\t}\n\t\t\t\treturn { rows: [] }\n\t\t\t}\n\t\t\tconst schemas = await adapter.introspect()\n\t\t\texpect(schemas).toEqual([])\n\t\t})\n\n\t\ttest('type-level: PostgresAdapter declares all migration methods', () => {\n\t\t\texpectTypeOf(adapter.loadMigrations).toBeFunction()\n\t\t\texpectTypeOf(adapter.recordMigration).toBeFunction()\n\t\t\texpectTypeOf(adapter.acquireMigrationLock).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyCreateTable).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyDropTable).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyAddField).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyDropField).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyModifyField).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyRenameTable).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyRenameField).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyAddIndex).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyDropIndex).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyAddForeignKey).toBeFunction()\n\t\t\texpectTypeOf(adapter.applyDropForeignKey).toBeFunction()\n\t\t\texpectTypeOf(adapter.introspect).toBeFunction()\n\t\t})\n\n\t\ttest('type-level: Migrator.from rejects withoutLock for PostgresAdapter', async () => {\n\t\t\tconst { Repo } = await import('../../repo/repo')\n\t\t\tconst { Migrator } = await import('../../migrations/migrator')\n\t\t\tconst repo = Repo.from(adapter).resolve(() => ({ table: 'test' })).build()\n\t\t\tconst step = Migrator.from(repo, adapter).migrations([])\n\t\t\texpectTypeOf(step).toHaveProperty('build')\n\t\t\texpectTypeOf(step).not.toHaveProperty('withoutLock')\n\t\t})\n\n\t\ttest('applyCreateTable maps all field types to correct PG types', async () => {\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'all_types',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [\n\t\t\t\t\t{ name: 'f_num', type: 'number' },\n\t\t\t\t\t{ name: 'f_bool', type: 'boolean' },\n\t\t\t\t\t{ name: 'f_date', type: 'date' },\n\t\t\t\t\t{ name: 'f_null', type: 'null' },\n\t\t\t\t\t{ name: 'f_obj', type: 'object' },\n\t\t\t\t\t{ name: 'f_arr', type: 'array' },\n\t\t\t\t],\n\t\t\t})\n\t\t\tconst sql = queries[0].sql\n\t\t\texpect(sql).toContain('\"f_num\" double precision NOT NULL')\n\t\t\texpect(sql).toContain('\"f_bool\" boolean NOT NULL')\n\t\t\texpect(sql).toContain('\"f_date\" timestamptz NOT NULL')\n\t\t\texpect(sql).toContain('\"f_null\" text NOT NULL')\n\t\t\texpect(sql).toContain('\"f_obj\" jsonb NOT NULL')\n\t\t\texpect(sql).toContain('\"f_arr\" jsonb NOT NULL')\n\t\t})\n\n\t\ttest('applyModifyField sets NOT NULL when nullable is falsy', async () => {\n\t\t\tawait adapter.applyModifyField({\n\t\t\t\tkind: 'modifyField',\n\t\t\t\ttable: 'users',\n\t\t\t\tname: 'email',\n\t\t\t\tto: { name: 'email', type: 'string' },\n\t\t\t})\n\t\t\texpect(queries[1].sql).toContain('SET NOT NULL')\n\t\t})\n\n\t\ttest('applyModifyField drops default when to.default is undefined', async () => {\n\t\t\tawait adapter.applyModifyField({\n\t\t\t\tkind: 'modifyField',\n\t\t\t\ttable: 'users',\n\t\t\t\tname: 'age',\n\t\t\t\tto: { name: 'age', type: 'number' },\n\t\t\t})\n\t\t\texpect(queries[2].sql).toContain('DROP DEFAULT')\n\t\t})\n\n\t\ttest('applyAddField with string default escapes single quotes', async () => {\n\t\t\tawait adapter.applyAddField({\n\t\t\t\tkind: 'addField',\n\t\t\t\ttable: 'users',\n\t\t\t\tfield: { name: 'bio', type: 'string', default: \"it's\" },\n\t\t\t})\n\t\t\texpect(queries[0].sql).toContain(\"DEFAULT 'it''s'\")\n\t\t})\n\n\t\ttest('end-to-end: Migrator.from(repo, adapter) wires up correctly', async () => {\n\t\t\tconst { Repo } = await import('../../repo/repo')\n\t\t\tconst { Migrator } = await import('../../migrations/migrator')\n\t\t\tconst { Schema } = await import('../../schema')\n\n\t\t\tSchema.from('users').pk('id', v.string(), () => 'x').field('email', v.string()).build()\n\t\t\tconst repo = Repo.from(adapter).resolve(() => ({ table: 'test' })).build()\n\n\t\t\tlet loadCalls = 0\n\t\t\tconst mockClient = {\n\t\t\t\tquery: async (sql: string, _params?: unknown[]) => {\n\t\t\t\t\tif (sql.includes('pg_advisory_lock') || sql.includes('pg_advisory_unlock')) return { rows: [] }\n\t\t\t\t\tif (sql.includes('CREATE TABLE IF NOT EXISTS')) return { rows: [] }\n\t\t\t\t\tif (sql.includes('SELECT id, applied_at')) { loadCalls++; return { rows: [] } }\n\t\t\t\t\tif (sql.includes('INSERT INTO')) return { rows: [] }\n\t\t\t\t\tif (sql.includes('CREATE TABLE')) return { rows: [] }\n\t\t\t\t\tif (sql === 'BEGIN' || sql === 'COMMIT' || sql === 'ROLLBACK') return { rows: [] }\n\t\t\t\t\treturn { rows: [] }\n\t\t\t\t},\n\t\t\t\trelease: () => {},\n\t\t\t}\n\t\t\t;(adapter.pool as any).query = mockClient.query\n\t\t\t;(adapter.pool as any).connect = async () => mockClient\n\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([\n\t\t\t\t{ id: '0001', changes: [{ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'email', type: 'string' }] }] },\n\t\t\t]).build()\n\n\t\t\tconst result = await migrator.up()\n\t\t\texpect(result.ran).toEqual(['0001'])\n\t\t\texpect(loadCalls).toBe(1)\n\t\t})\n\t})\n}\n","import type { AggregateOpName } from '../../adapter'\nimport { OrmValidationError } from '../../errors'\nimport { Filter, FilterGroup, type FilterChild } from '../../filter'\nimport type { AggregateSpec } from '../../orm-adapter'\nimport type { QueryOptions } from '../../query-options'\nimport { IncOp, MaxOp, MinOp, MulOp, PatchOp, PullOp, PushOp, UnsetOp } from '../../updates'\n\nfunction mapField(field: string, primaryKey: string): string {\n\tif (field === 'id' && primaryKey !== 'id') return primaryKey\n\treturn field\n}\n\nfunction compileOps(f: Filter, field: string, nextParam: (v: unknown) => string): string {\n\tswitch (f.op) {\n\t\tcase 'eq':\n\t\t\treturn f.value === null ? `${field} IS NULL` : `${field} = ${nextParam(f.value)}`\n\t\tcase 'ne':\n\t\t\treturn f.value === null ? `${field} IS NOT NULL` : `${field} != ${nextParam(f.value)}`\n\t\tcase 'gt':\n\t\t\treturn `${field} > ${nextParam(f.value)}`\n\t\tcase 'gte':\n\t\t\treturn `${field} >= ${nextParam(f.value)}`\n\t\tcase 'lt':\n\t\t\treturn `${field} < ${nextParam(f.value)}`\n\t\tcase 'lte':\n\t\t\treturn `${field} <= ${nextParam(f.value)}`\n\t\tcase 'in':\n\t\t\treturn `${field} = ANY(${nextParam(f.value)})`\n\t\tcase 'notIn':\n\t\t\treturn `NOT (${field} = ANY(${nextParam(f.value)}))`\n\t\tcase 'like':\n\t\t\treturn `${field} ILIKE ${nextParam(`%${f.value}%`)}`\n\t\tcase 'exists':\n\t\t\treturn `${field} IS NOT NULL`\n\t\tcase 'notExists':\n\t\t\treturn `${field} IS NULL`\n\t\tcase 'contains':\n\t\t\treturn `${field} @> ${nextParam(JSON.stringify(f.value))}::jsonb`\n\t\tcase 'notContains':\n\t\t\treturn `NOT (${field} @> ${nextParam(JSON.stringify(f.value))}::jsonb)`\n\t\tdefault:\n\t\t\treturn `${field} = ${nextParam(f.value)}`\n\t}\n}\n\nfunction compileFilter(f: Filter, primaryKey: string, nextParam: (v: unknown) => string): string {\n\treturn compileOps(f, `\"${mapField(f.field, primaryKey)}\"`, nextParam)\n}\n\nfunction compileChildren(group: FilterGroup, compileLeaf: (f: Filter) => string): string[] {\n\treturn group.children.map((c) => compileChild(c, compileLeaf)).filter((c): c is string => c !== null)\n}\n\nfunction compileGroup(group: FilterGroup, compileLeaf: (f: Filter) => string): string | null {\n\tconst parts = compileChildren(group, compileLeaf)\n\tif (parts.length === 0) return null\n\tif (parts.length === 1) return parts[0]\n\treturn `(${parts.join(group.op === 'or' ? ' OR ' : ' AND ')})`\n}\n\nfunction compileChild(child: FilterChild, compileLeaf: (f: Filter) => string): string | null {\n\tif (child instanceof Filter) return compileLeaf(child)\n\tif (child instanceof FilterGroup) return compileGroup(child, compileLeaf)\n\treturn null\n}\n\nfunction compilePgFilter(\n\tgroup: FilterGroup,\n\tprimaryKey: string,\n\tstartIndex = 1,\n): { whereClause: string; params: unknown[]; nextParamIndex: number } {\n\tconst params: unknown[] = []\n\tlet paramIndex = startIndex\n\n\tfunction nextParam(value: unknown): string {\n\t\tparams.push(value)\n\t\treturn `$${paramIndex++}`\n\t}\n\n\tconst leaf = (f: Filter) => compileFilter(f, primaryKey, nextParam)\n\tconst whereParts = compileChildren(group, leaf)\n\n\tconst whereClause = whereParts.length > 0 ? `WHERE ${whereParts.join(' AND ')}` : ''\n\treturn { whereClause, params, nextParamIndex: paramIndex }\n}\n\nfunction buildSetParts(\n\tdata: Record<string, unknown>,\n\tstartIndex: number,\n): { setParts: string[]; params: unknown[]; nextParamIndex: number } {\n\tconst params: unknown[] = []\n\tlet paramIndex = startIndex\n\n\tconst setParts = Object.entries(data).map(([key, value]) => {\n\t\tconst col = `\"${key}\"`\n\n\t\tif (value instanceof IncOp) {\n\t\t\tparams.push(value.value)\n\t\t\treturn `${col} = ${col} + $${paramIndex++}`\n\t\t}\n\t\tif (value instanceof MulOp) {\n\t\t\tparams.push(value.value)\n\t\t\treturn `${col} = ${col} * $${paramIndex++}`\n\t\t}\n\t\tif (value instanceof MinOp) {\n\t\t\tparams.push(value.value)\n\t\t\treturn `${col} = LEAST(${col}, $${paramIndex++})`\n\t\t}\n\t\tif (value instanceof MaxOp) {\n\t\t\tparams.push(value.value)\n\t\t\treturn `${col} = GREATEST(${col}, $${paramIndex++})`\n\t\t}\n\t\tif (value instanceof UnsetOp) {\n\t\t\treturn `${col} = NULL`\n\t\t}\n\t\tif (value instanceof PushOp) {\n\t\t\tparams.push(JSON.stringify(value.value))\n\t\t\treturn `${col} = ${col} || jsonb_build_array($${paramIndex++}::jsonb)`\n\t\t}\n\t\tif (value instanceof PullOp) {\n\t\t\tparams.push(JSON.stringify(value.value))\n\t\t\treturn `${col} = (SELECT COALESCE(jsonb_agg(e), '[]'::jsonb) FROM jsonb_array_elements(${col}) e WHERE e <> $${paramIndex++}::jsonb)`\n\t\t}\n\t\tif (value instanceof PatchOp) {\n\t\t\tparams.push(JSON.stringify(value.value))\n\t\t\treturn `${col} = ${col} || $${paramIndex++}::jsonb`\n\t\t}\n\n\t\tparams.push(value)\n\t\treturn `${col} = $${paramIndex++}`\n\t})\n\n\treturn { setParts, params, nextParamIndex: paramIndex }\n}\n\nexport function buildSelectQuery(\n\tgroup: FilterGroup,\n\toptions: QueryOptions | undefined,\n\ttableName: string,\n\tprimaryKey: string,\n): { sql: string; params: unknown[] } {\n\tconst { whereClause, params, nextParamIndex } = compilePgFilter(group, primaryKey)\n\tlet i = nextParamIndex\n\n\tconst orderParts = (options?.orderBy ?? []).map((o) => `\"${mapField(o.field, primaryKey)}\" ${o.direction.toUpperCase()}`)\n\tconst orderClause = orderParts.length > 0 ? `ORDER BY ${orderParts.join(', ')}` : ''\n\n\tlet limitClause = ''\n\tif (options?.limit != null) {\n\t\tparams.push(options.limit)\n\t\tlimitClause = `LIMIT $${i++}`\n\t}\n\tlet offsetClause = ''\n\tif (options?.offset != null) {\n\t\tparams.push(options.offset)\n\t\toffsetClause = `OFFSET $${i++}`\n\t}\n\tconst selectClause = options?.select?.length ? options.select.map((f) => `\"${mapField(f, primaryKey)}\"`).join(', ') : '*'\n\n\tconst sql = `SELECT ${selectClause} FROM \"${tableName}\" ${whereClause} ${orderClause} ${limitClause} ${offsetClause}`\n\t\t.trim()\n\t\t.replace(/\\s+/g, ' ')\n\treturn { sql, params }\n}\n\nexport function buildCountQuery(group: FilterGroup, tableName: string, primaryKey: string): { sql: string; params: unknown[] } {\n\tconst { whereClause, params } = compilePgFilter(group, primaryKey)\n\tconst sql = `SELECT COUNT(*) as count FROM \"${tableName}\" ${whereClause}`.trim().replace(/\\s+/g, ' ')\n\treturn { sql, params }\n}\n\nexport function buildCreateQuery(tableName: string, data: Record<string, unknown>): { sql: string; params: unknown[] } {\n\tconst keys = Object.keys(data)\n\tconst params = Object.values(data)\n\tconst placeholders = keys.map((_, i) => `$${i + 1}`)\n\tconst columns = keys.map((k) => `\"${k}\"`).join(', ')\n\tconst sql = `INSERT INTO \"${tableName}\" (${columns}) VALUES (${placeholders.join(', ')}) RETURNING *`\n\treturn { sql, params }\n}\n\nexport function buildUpdateQuery(\n\tgroup: FilterGroup,\n\ttableName: string,\n\tprimaryKey: string,\n\tdata: Record<string, unknown>,\n): { sql: string; params: unknown[] } {\n\tconst { setParts, params: setParams, nextParamIndex } = buildSetParts(data, 1)\n\tconst { whereClause, params: whereParams } = compilePgFilter(group, primaryKey, nextParamIndex)\n\tconst params = [...setParams, ...whereParams]\n\tconst sql = `UPDATE \"${tableName}\" SET ${setParts.join(', ')} ${whereClause} RETURNING *`.trim().replace(/\\s+/g, ' ')\n\treturn { sql, params }\n}\n\nexport function buildPkUpdateQuery(\n\ttableName: string,\n\tprimaryKey: string,\n\tpk: unknown,\n\tdata: Record<string, unknown>,\n): { sql: string; params: unknown[] } {\n\tconst { setParts, params: setParams, nextParamIndex } = buildSetParts(data, 1)\n\tsetParams.push(pk)\n\tconst sql = `UPDATE \"${tableName}\" SET ${setParts.join(', ')} WHERE \"${primaryKey}\" = $${nextParamIndex} RETURNING *`.replace(\n\t\t/\\s+/g,\n\t\t' ',\n\t)\n\treturn { sql, params: setParams }\n}\n\nexport function buildDeleteQuery(group: FilterGroup, tableName: string, primaryKey: string): { sql: string; params: unknown[] } {\n\tconst { whereClause, params } = compilePgFilter(group, primaryKey)\n\tconst sql = `DELETE FROM \"${tableName}\" ${whereClause} RETURNING *`.trim().replace(/\\s+/g, ' ')\n\treturn { sql, params }\n}\n\nexport function buildUpsertQuery(\n\ttableName: string,\n\tconflictColumn: string,\n\tprimaryKey: string,\n\tcreate: Record<string, unknown>,\n\tdata: Record<string, unknown>,\n): { sql: string; params: unknown[] } {\n\tconst columns = Object.keys(create)\n\tconst createParams = Object.values(create)\n\tconst placeholders = columns.map((_, i) => `$${i + 1}`)\n\tconst pgConflictCol = mapField(conflictColumn, primaryKey)\n\n\tlet setClause: string\n\tconst allParams = [...createParams]\n\n\tif (Object.keys(data).length > 0) {\n\t\tconst { setParts, params: setParams } = buildSetParts(data, columns.length + 1)\n\t\tsetClause = setParts.join(', ')\n\t\tallParams.push(...setParams)\n\t} else {\n\t\tsetClause = `\"${pgConflictCol}\" = EXCLUDED.\"${pgConflictCol}\"`\n\t}\n\n\tconst sql =\n\t\t`INSERT INTO \"${tableName}\" (${columns.map((c) => `\"${c}\"`).join(', ')}) VALUES (${placeholders.join(', ')}) ON CONFLICT (\"${pgConflictCol}\") DO UPDATE SET ${setClause} RETURNING *`.replace(\n\t\t\t/\\s+/g,\n\t\t\t' ',\n\t\t)\n\treturn { sql, params: allParams }\n}\n\nexport function extractUpsertConflictColumn(filter: FilterGroup, schemaName: string): string {\n\tif (filter.children.length === 1 && filter.children[0] instanceof Filter && filter.children[0].op === 'eq') {\n\t\treturn filter.children[0].field\n\t}\n\n\tlet description: string\n\tif (filter.children.length === 0) {\n\t\tdescription = 'empty filter'\n\t} else if (filter.children.length === 1) {\n\t\tconst child = filter.children[0]\n\t\tconst opDesc = child instanceof Filter ? child.op : 'group'\n\t\tdescription = `single non-eq filter (op: ${opDesc})`\n\t} else {\n\t\tdescription = `${filter.children.length} filter clauses`\n\t}\n\n\tthrow new OrmValidationError('upsert-filter-incompatible', schemaName, 'upsertOne', [\n\t\t{\n\t\t\tcause: `PostgreSQL upsert requires a single eq filter on a UNIQUE-indexed column; received ${description}`,\n\t\t},\n\t])\n}\n\nfunction aggFnToSql(fn: AggregateOpName, field: string | undefined, primaryKey: string): string {\n\tif (fn === 'count') return 'COUNT(*)'\n\tconst col = `\"${mapField(field!, primaryKey)}\"`\n\tswitch (fn) {\n\t\tcase 'countDistinct':\n\t\t\treturn `COUNT(DISTINCT ${col})`\n\t\tcase 'sum':\n\t\t\treturn `SUM(${col})`\n\t\tcase 'avg':\n\t\t\treturn `AVG(${col})`\n\t\tcase 'min':\n\t\t\treturn `MIN(${col})`\n\t\tcase 'max':\n\t\t\treturn `MAX(${col})`\n\t}\n}\n\nfunction resolveHavingField(\n\tfield: string,\n\tprimaryKey: string,\n\taliasToExpr: Map<string, string>,\n\tgroupBySet: Set<string>,\n): string {\n\tif (aliasToExpr.has(field)) return aliasToExpr.get(field)!\n\tif (groupBySet.has(field)) return `\"${mapField(field, primaryKey)}\"`\n\treturn `\"${field}\"`\n}\n\nexport function buildAggregateQuery(\n\tspec: AggregateSpec,\n\ttableName: string,\n\tprimaryKey: string,\n): { sql: string; params: unknown[] } {\n\tconst params: unknown[] = []\n\tlet paramIndex = 1\n\n\tfunction nextParam(value: unknown): string {\n\t\tparams.push(value)\n\t\treturn `$${paramIndex++}`\n\t}\n\n\tconst selectParts: string[] = []\n\tconst aliasToExpr = new Map<string, string>()\n\n\tfor (const agg of spec.aggregates) {\n\t\tconst expr = aggFnToSql(agg.fn, agg.field, primaryKey)\n\t\taliasToExpr.set(agg.alias, expr)\n\t\tselectParts.push(`${expr} AS \"${agg.alias}\"`)\n\t}\n\n\tconst groupByFields: string[] = []\n\tfor (const f of spec.groupBy) {\n\t\tconst col = `\"${mapField(f, primaryKey)}\"`\n\t\tgroupByFields.push(col)\n\t\tselectParts.push(`${col} AS \"${f}\"`)\n\t}\n\n\tlet whereClause = ''\n\tif (spec.where) {\n\t\tconst whereLeaf = (f: Filter) => compileFilter(f, primaryKey, nextParam)\n\t\tconst whereParts = compileChildren(spec.where, whereLeaf)\n\t\tif (whereParts.length > 0) whereClause = `WHERE ${whereParts.join(' AND ')}`\n\t}\n\n\tconst groupByClause = groupByFields.length > 0 ? `GROUP BY ${groupByFields.join(', ')}` : ''\n\n\tlet havingClause = ''\n\tif (spec.having) {\n\t\tconst groupBySet = new Set(spec.groupBy)\n\t\tconst havingLeaf = (f: Filter) =>\n\t\t\tcompileOps(f, resolveHavingField(f.field, primaryKey, aliasToExpr, groupBySet), nextParam)\n\t\tconst havingParts = compileChildren(spec.having, havingLeaf)\n\t\tif (havingParts.length > 0) havingClause = `HAVING ${havingParts.join(' AND ')}`\n\t}\n\n\tconst sql = `SELECT ${selectParts.join(', ')} FROM \"${tableName}\" ${whereClause} ${groupByClause} ${havingClause}`\n\t\t.trim()\n\t\t.replace(/\\s+/g, ' ')\n\treturn { sql, params }\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { FilterGroup } = await import('../../filter')\n\tconst { OrderBy } = await import('../../query-options')\n\tconst { IncOp, MulOp, MinOp, MaxOp, UnsetOp, PushOp, PullOp, PatchOp, SetOp, flattenOps } = await import('../../updates')\n\tconst { OrmValidationError } = await import('../../errors')\n\n\tdescribe('compilePgFilter', () => {\n\t\ttest('empty filter group compiles to empty WHERE clause', () => {\n\t\t\tconst group = FilterGroup.create()\n\t\t\tconst { sql } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toBe('SELECT * FROM \"users\"')\n\t\t})\n\n\t\ttest('single eq filter compiles to WHERE field = $1', () => {\n\t\t\tconst group = FilterGroup.create().eq('name', 'Alice')\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toBe('SELECT * FROM \"users\" WHERE \"name\" = $1')\n\t\t\texpect(params).toEqual(['Alice'])\n\t\t})\n\n\t\ttest('eq with null compiles to IS NULL', () => {\n\t\t\tconst group = FilterGroup.create().eq('name', null)\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toBe('SELECT * FROM \"users\" WHERE \"name\" IS NULL')\n\t\t\texpect(params).toEqual([])\n\t\t})\n\n\t\ttest('ne compiles to != (or IS NOT NULL for null)', () => {\n\t\t\tconst group = FilterGroup.create().ne('age', 5)\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toBe('SELECT * FROM \"users\" WHERE \"age\" != $1')\n\t\t\texpect(params).toEqual([5])\n\n\t\t\tconst nullGroup = FilterGroup.create().ne('age', null)\n\t\t\tconst { sql: nullSql } = buildSelectQuery(nullGroup, undefined, 'users', 'id')\n\t\t\texpect(nullSql).toBe('SELECT * FROM \"users\" WHERE \"age\" IS NOT NULL')\n\t\t})\n\n\t\ttest('gt, gte, lt, lte compile to >, >=, <, <=', () => {\n\t\t\texpect(buildSelectQuery(FilterGroup.create().gt('age', 10), undefined, 't', 'id').sql).toContain('\"age\" > $1')\n\t\t\texpect(buildSelectQuery(FilterGroup.create().gte('age', 20), undefined, 't', 'id').sql).toContain('\"age\" >= $1')\n\t\t\texpect(buildSelectQuery(FilterGroup.create().lt('age', 30), undefined, 't', 'id').sql).toContain('\"age\" < $1')\n\t\t\texpect(buildSelectQuery(FilterGroup.create().lte('age', 40), undefined, 't', 'id').sql).toContain('\"age\" <= $1')\n\t\t})\n\n\t\ttest('in compiles to = ANY($N)', () => {\n\t\t\tconst group = FilterGroup.create().in('status', ['a', 'b'])\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toContain('\"status\" = ANY($1)')\n\t\t\texpect(params).toEqual([['a', 'b']])\n\t\t})\n\n\t\ttest('notIn compiles to NOT (field = ANY($N)) — canonical name notIn', () => {\n\t\t\tconst group = FilterGroup.create().notIn('status', ['x', 'y'])\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toContain('NOT (\"status\" = ANY($1))')\n\t\t\texpect(params).toEqual([['x', 'y']])\n\t\t})\n\n\t\ttest('like compiles to ILIKE with % wrapping', () => {\n\t\t\tconst group = FilterGroup.create().like('name', 'ali')\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toContain('\"name\" ILIKE $1')\n\t\t\texpect(params).toEqual(['%ali%'])\n\t\t})\n\n\t\ttest('exists compiles to IS NOT NULL (own op, not boolean form)', () => {\n\t\t\tconst group = FilterGroup.create().exists('val')\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toContain('\"val\" IS NOT NULL')\n\t\t\texpect(params).toEqual([])\n\t\t})\n\n\t\ttest('notExists is its own op — compiles to IS NULL', () => {\n\t\t\tconst group = FilterGroup.create().notExists('val')\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toContain('\"val\" IS NULL')\n\t\t\texpect(params).toEqual([])\n\t\t})\n\n\t\ttest('contains compiles to @> jsonb', () => {\n\t\t\tconst group = FilterGroup.create().contains('tags', ['a', 'b'])\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toContain('\"tags\" @> $1::jsonb')\n\t\t\texpect(params).toEqual([JSON.stringify(['a', 'b'])])\n\t\t})\n\n\t\ttest('notContains compiles to NOT (@> jsonb)', () => {\n\t\t\tconst group = FilterGroup.create().notContains('tags', ['x'])\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toContain('NOT (\"tags\" @> $1::jsonb)')\n\t\t\texpect(params).toEqual([JSON.stringify(['x'])])\n\t\t})\n\n\t\ttest('multiple clauses produce AND-joined WHERE', () => {\n\t\t\tconst group = FilterGroup.create().eq('a', 1).gt('b', 2)\n\t\t\tconst { sql } = buildSelectQuery(group, undefined, 't', 'id')\n\t\t\texpect(sql).toContain('WHERE \"a\" = $1 AND \"b\" > $2')\n\t\t})\n\n\t\ttest('nested and/or groups compile correctly', () => {\n\t\t\tconst group = FilterGroup.create().and([\n\t\t\t\t(q) => q.eq('a', 1),\n\t\t\t\t(q) => q.or([(g) => g.eq('b', 2), (g) => g.eq('c', 3)]),\n\t\t\t])\n\t\t\tconst { sql, params } = buildSelectQuery(group, undefined, 't', 'id')\n\t\t\texpect(sql).toContain('(\"a\" = $1 AND (\"b\" = $2 OR \"c\" = $3))')\n\t\t\texpect(params).toEqual([1, 2, 3])\n\t\t})\n\n\t\ttest('maps id field to primaryKey when primaryKey differs', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'abc')\n\t\t\tconst { sql } = buildSelectQuery(group, undefined, 'users', 'user_id')\n\t\t\texpect(sql).toContain('\"user_id\" = $1')\n\t\t})\n\t})\n\n\tdescribe('buildSelectQuery', () => {\n\t\ttest('options: sort, limit, offset, select', () => {\n\t\t\tconst group = FilterGroup.create().eq('active', true)\n\t\t\tconst options = {\n\t\t\t\torderBy: [new OrderBy('name', 'asc'), new OrderBy('age', 'desc')],\n\t\t\t\tlimit: 10,\n\t\t\t\toffset: 5,\n\t\t\t\tselect: ['name', 'age'] as const,\n\t\t\t}\n\t\t\tconst { sql, params } = buildSelectQuery(group, options, 'users', 'id')\n\t\t\texpect(sql).toContain('SELECT \"name\", \"age\"')\n\t\t\texpect(sql).toContain('ORDER BY \"name\" ASC, \"age\" DESC')\n\t\t\texpect(sql).toContain('LIMIT $2')\n\t\t\texpect(sql).toContain('OFFSET $3')\n\t\t\texpect(params).toEqual([true, 10, 5])\n\t\t})\n\n\t\ttest('undefined options returns basic SELECT *', () => {\n\t\t\tconst group = FilterGroup.create().eq('x', 1)\n\t\t\tconst { sql } = buildSelectQuery(group, undefined, 'users', 'id')\n\t\t\texpect(sql).toBe('SELECT * FROM \"users\" WHERE \"x\" = $1')\n\t\t})\n\t})\n\n\tdescribe('buildCreateQuery', () => {\n\t\ttest('produces INSERT with RETURNING *', () => {\n\t\t\tconst { sql, params } = buildCreateQuery('users', { name: 'Alice', age: 30 })\n\t\t\texpect(sql).toBe('INSERT INTO \"users\" (\"name\", \"age\") VALUES ($1, $2) RETURNING *')\n\t\t\texpect(params).toEqual(['Alice', 30])\n\t\t})\n\t})\n\n\tdescribe('buildUpdateQuery', () => {\n\t\ttest('plain values produce SET col = $N', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql, params } = buildUpdateQuery(group, 'users', 'id', { name: 'Bob', age: 25 })\n\t\t\texpect(sql).toBe('UPDATE \"users\" SET \"name\" = $1, \"age\" = $2 WHERE \"id\" = $3 RETURNING *')\n\t\t\texpect(params).toEqual(['Bob', 25, 'u1'])\n\t\t})\n\n\t\ttest('IncOp produces col = col + $N', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql, params } = buildUpdateQuery(group, 'users', 'id', { count: new IncOp('count', 5) })\n\t\t\texpect(sql).toContain('\"count\" = \"count\" + $1')\n\t\t\texpect(params[0]).toBe(5)\n\t\t})\n\n\t\ttest('MulOp produces col = col * $N', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql } = buildUpdateQuery(group, 'users', 'id', { score: new MulOp('score', 2) })\n\t\t\texpect(sql).toContain('\"score\" = \"score\" * $1')\n\t\t})\n\n\t\ttest('MinOp/MaxOp produce LEAST/GREATEST', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql } = buildUpdateQuery(group, 'users', 'id', {\n\t\t\t\tlo: new MinOp('lo', 1),\n\t\t\t\thi: new MaxOp('hi', 99),\n\t\t\t})\n\t\t\texpect(sql).toContain('LEAST(\"lo\", $1)')\n\t\t\texpect(sql).toContain('GREATEST(\"hi\", $2)')\n\t\t})\n\n\t\ttest('UnsetOp produces col = NULL', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql } = buildUpdateQuery(group, 'users', 'id', { old: new UnsetOp('old') })\n\t\t\texpect(sql).toContain('\"old\" = NULL')\n\t\t})\n\n\t\ttest('PushOp produces jsonb array append', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql } = buildUpdateQuery(group, 'users', 'id', { tags: new PushOp('tags', 'new') })\n\t\t\texpect(sql).toContain('jsonb_build_array($1::jsonb)')\n\t\t})\n\n\t\ttest('PullOp produces jsonb array removal', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql } = buildUpdateQuery(group, 'users', 'id', { tags: new PullOp('tags', 'old') })\n\t\t\texpect(sql).toContain('jsonb_array_elements')\n\t\t})\n\n\t\ttest('PatchOp produces jsonb merge', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql } = buildUpdateQuery(group, 'users', 'id', { meta: new PatchOp('meta', { a: 1 }) })\n\t\t\texpect(sql).toContain('|| $1::jsonb')\n\t\t})\n\t})\n\n\tdescribe('buildPkUpdateQuery', () => {\n\t\ttest('builds UPDATE with PK WHERE clause', () => {\n\t\t\tconst { sql, params } = buildPkUpdateQuery('users', 'id', 'u1', { name: 'Bob' })\n\t\t\texpect(sql).toBe('UPDATE \"users\" SET \"name\" = $1 WHERE \"id\" = $2 RETURNING *')\n\t\t\texpect(params).toEqual(['Bob', 'u1'])\n\t\t})\n\n\t\ttest('handles ops in PK update', () => {\n\t\t\tconst { sql, params } = buildPkUpdateQuery('users', 'id', 'u1', { count: new IncOp('count', 3) })\n\t\t\texpect(sql).toContain('\"count\" = \"count\" + $1')\n\t\t\texpect(sql).toContain('WHERE \"id\" = $2')\n\t\t\texpect(params).toEqual([3, 'u1'])\n\t\t})\n\t})\n\n\tdescribe('buildDeleteQuery', () => {\n\t\ttest('produces DELETE with RETURNING *', () => {\n\t\t\tconst group = FilterGroup.create().eq('id', 'u1')\n\t\t\tconst { sql, params } = buildDeleteQuery(group, 'users', 'id')\n\t\t\texpect(sql).toBe('DELETE FROM \"users\" WHERE \"id\" = $1 RETURNING *')\n\t\t\texpect(params).toEqual(['u1'])\n\t\t})\n\t})\n\n\tdescribe('buildUpsertQuery', () => {\n\t\ttest('produces INSERT ON CONFLICT with SET parts from data', () => {\n\t\t\tconst data = flattenOps([new SetOp({ name: 'Updated' })])\n\t\t\tconst { sql, params } = buildUpsertQuery('users', 'email', 'id', { id: 'u1', email: 'a@b.com', name: 'Alice' }, data)\n\t\t\texpect(sql).toContain('INSERT INTO \"users\"')\n\t\t\texpect(sql).toContain('ON CONFLICT (\"email\") DO UPDATE SET')\n\t\t\texpect(sql).toContain('\"name\" = $4')\n\t\t\texpect(sql).toContain('RETURNING *')\n\t\t\texpect(params).toEqual(['u1', 'a@b.com', 'Alice', 'Updated'])\n\t\t})\n\n\t\ttest('empty data produces no-op SET on conflict column', () => {\n\t\t\tconst { sql, params } = buildUpsertQuery('users', 'email', 'id', { id: 'u1', email: 'a@b.com' }, {})\n\t\t\texpect(sql).toContain('ON CONFLICT (\"email\") DO UPDATE SET \"email\" = EXCLUDED.\"email\"')\n\t\t\texpect(params).toEqual(['u1', 'a@b.com'])\n\t\t})\n\n\t\ttest('maps conflict column via mapField when primaryKey differs', () => {\n\t\t\tconst { sql } = buildUpsertQuery('users', 'id', 'user_id', { user_id: 'u1', name: 'A' }, {})\n\t\t\texpect(sql).toContain('ON CONFLICT (\"user_id\")')\n\t\t})\n\n\t\ttest('handles ops in upsert SET clause', () => {\n\t\t\tconst data = flattenOps([new IncOp('views', 1)])\n\t\t\tconst { sql, params } = buildUpsertQuery('posts', 'slug', 'id', { id: 'p1', slug: 'hello', views: 0 }, data)\n\t\t\texpect(sql).toContain('\"views\" = \"views\" + $4')\n\t\t\texpect(params).toEqual(['p1', 'hello', 0, 1])\n\t\t})\n\t})\n\n\tdescribe('extractUpsertConflictColumn', () => {\n\t\ttest('extracts field from single eq filter', () => {\n\t\t\tconst filter = FilterGroup.create().eq('email', 'a@b.com')\n\t\t\texpect(extractUpsertConflictColumn(filter, 'users')).toBe('email')\n\t\t})\n\n\t\ttest('throws upsert-filter-incompatible on empty filter', () => {\n\t\t\tconst filter = FilterGroup.create()\n\t\t\texpect(() => extractUpsertConflictColumn(filter, 'users')).toThrow(OrmValidationError)\n\t\t\ttry {\n\t\t\t\textractUpsertConflictColumn(filter, 'users')\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('upsert-filter-incompatible')\n\t\t\t\texpect(err.schema).toBe('users')\n\t\t\t\texpect(err.operation).toBe('upsertOne')\n\t\t\t\texpect(err.failures[0].cause).toContain('empty filter')\n\t\t\t}\n\t\t})\n\n\t\ttest('throws upsert-filter-incompatible on multiple filters', () => {\n\t\t\tconst filter = FilterGroup.create().eq('a', 1).eq('b', 2)\n\t\t\texpect(() => extractUpsertConflictColumn(filter, 'users')).toThrow(OrmValidationError)\n\t\t\ttry {\n\t\t\t\textractUpsertConflictColumn(filter, 'users')\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('upsert-filter-incompatible')\n\t\t\t\texpect(err.failures[0].cause).toContain('2 filter clauses')\n\t\t\t}\n\t\t})\n\n\t\ttest('throws upsert-filter-incompatible on non-eq filter', () => {\n\t\t\tconst filter = FilterGroup.create().gt('age', 10)\n\t\t\texpect(() => extractUpsertConflictColumn(filter, 'users')).toThrow(OrmValidationError)\n\t\t\ttry {\n\t\t\t\textractUpsertConflictColumn(filter, 'users')\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('upsert-filter-incompatible')\n\t\t\t\texpect(err.failures[0].cause).toContain('non-eq filter')\n\t\t\t}\n\t\t})\n\n\t\ttest('throws with clear message naming received filter shape', () => {\n\t\t\tconst filter = FilterGroup.create().in('status', ['a', 'b'])\n\t\t\ttry {\n\t\t\t\textractUpsertConflictColumn(filter, 'test')\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('upsert-filter-incompatible')\n\t\t\t\texpect(err.failures[0].cause).toContain('single eq filter')\n\t\t\t\texpect(err.failures[0].cause).toContain('UNIQUE-indexed column')\n\t\t\t}\n\t\t})\n\t})\n\n\tdescribe('buildAggregateQuery', () => {\n\t\ttest('bare count produces SELECT COUNT(*) AS alias', () => {\n\t\t\tconst { sql, params } = buildAggregateQuery(\n\t\t\t\t{ aggregates: [{ fn: 'count', alias: 'total' }], groupBy: [] },\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe('SELECT COUNT(*) AS \"total\" FROM \"orders\"')\n\t\t\texpect(params).toEqual([])\n\t\t})\n\n\t\ttest('multi-aggregator produces all functions', () => {\n\t\t\tconst { sql, params } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [\n\t\t\t\t\t\t{ fn: 'count', alias: 'cnt' },\n\t\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'revenue' },\n\t\t\t\t\t\t{ fn: 'avg', field: 'amount', alias: 'avgAmt' },\n\t\t\t\t\t\t{ fn: 'min', field: 'amount', alias: 'lo' },\n\t\t\t\t\t\t{ fn: 'max', field: 'amount', alias: 'hi' },\n\t\t\t\t\t],\n\t\t\t\t\tgroupBy: [],\n\t\t\t\t},\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe(\n\t\t\t\t'SELECT COUNT(*) AS \"cnt\", SUM(\"amount\") AS \"revenue\", AVG(\"amount\") AS \"avgAmt\", MIN(\"amount\") AS \"lo\", MAX(\"amount\") AS \"hi\" FROM \"orders\"',\n\t\t\t)\n\t\t\texpect(params).toEqual([])\n\t\t})\n\n\t\ttest('countDistinct produces COUNT(DISTINCT col)', () => {\n\t\t\tconst { sql } = buildAggregateQuery(\n\t\t\t\t{ aggregates: [{ fn: 'countDistinct', field: 'category', alias: 'unique' }], groupBy: [] },\n\t\t\t\t'items',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe('SELECT COUNT(DISTINCT \"category\") AS \"unique\" FROM \"items\"')\n\t\t})\n\n\t\ttest('single-column groupBy produces GROUP BY clause', () => {\n\t\t\tconst { sql } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\t\tgroupBy: ['region'],\n\t\t\t\t},\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe('SELECT COUNT(*) AS \"cnt\", \"region\" AS \"region\" FROM \"orders\" GROUP BY \"region\"')\n\t\t})\n\n\t\ttest('multi-column groupBy produces all GROUP BY columns', () => {\n\t\t\tconst { sql } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\t\tgroupBy: ['region', 'product'],\n\t\t\t\t},\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe(\n\t\t\t\t'SELECT COUNT(*) AS \"cnt\", \"region\" AS \"region\", \"product\" AS \"product\" FROM \"orders\" GROUP BY \"region\", \"product\"',\n\t\t\t)\n\t\t})\n\n\t\ttest('where-only produces WHERE clause with params', () => {\n\t\t\tconst where = FilterGroup.create().eq('status', 'active')\n\t\t\tconst { sql, params } = buildAggregateQuery(\n\t\t\t\t{ aggregates: [{ fn: 'count', alias: 'total' }], groupBy: [], where },\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe('SELECT COUNT(*) AS \"total\" FROM \"orders\" WHERE \"status\" = $1')\n\t\t\texpect(params).toEqual(['active'])\n\t\t})\n\n\t\ttest('having-only filters on aggregate alias via repeated expression', () => {\n\t\t\tconst having = FilterGroup.create().gt('total', 10)\n\t\t\tconst { sql, params } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\t\tgroupBy: ['region'],\n\t\t\t\t\thaving,\n\t\t\t\t},\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe(\n\t\t\t\t'SELECT COUNT(*) AS \"total\", \"region\" AS \"region\" FROM \"orders\" GROUP BY \"region\" HAVING COUNT(*) > $1',\n\t\t\t)\n\t\t\texpect(params).toEqual([10])\n\t\t})\n\n\t\ttest('where + having both produce correct param ordering', () => {\n\t\t\tconst where = FilterGroup.create().eq('status', 'active')\n\t\t\tconst having = FilterGroup.create().gte('revenue', 1000)\n\t\t\tconst { sql, params } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'sum', field: 'amount', alias: 'revenue' }],\n\t\t\t\t\tgroupBy: ['region'],\n\t\t\t\t\twhere,\n\t\t\t\t\thaving,\n\t\t\t\t},\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe(\n\t\t\t\t'SELECT SUM(\"amount\") AS \"revenue\", \"region\" AS \"region\" FROM \"orders\" WHERE \"status\" = $1 GROUP BY \"region\" HAVING SUM(\"amount\") >= $2',\n\t\t\t)\n\t\t\texpect(params).toEqual(['active', 1000])\n\t\t})\n\n\t\ttest('having on groupBy field uses column name directly', () => {\n\t\t\tconst having = FilterGroup.create().eq('region', 'US')\n\t\t\tconst { sql, params } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\t\tgroupBy: ['region'],\n\t\t\t\t\thaving,\n\t\t\t\t},\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toBe(\n\t\t\t\t'SELECT COUNT(*) AS \"cnt\", \"region\" AS \"region\" FROM \"orders\" GROUP BY \"region\" HAVING \"region\" = $1',\n\t\t\t)\n\t\t\texpect(params).toEqual(['US'])\n\t\t})\n\n\t\ttest('field-name mapping: id maps to primaryKey when primaryKey differs', () => {\n\t\t\tconst { sql } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'countDistinct', field: 'id', alias: 'uniqueIds' }],\n\t\t\t\t\tgroupBy: [],\n\t\t\t\t},\n\t\t\t\t'users',\n\t\t\t\t'user_id',\n\t\t\t)\n\t\t\texpect(sql).toBe('SELECT COUNT(DISTINCT \"user_id\") AS \"uniqueIds\" FROM \"users\"')\n\t\t})\n\n\t\ttest('groupBy with id field maps to primaryKey', () => {\n\t\t\tconst { sql } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\t\tgroupBy: ['id'],\n\t\t\t\t},\n\t\t\t\t'users',\n\t\t\t\t'user_id',\n\t\t\t)\n\t\t\texpect(sql).toContain('GROUP BY \"user_id\"')\n\t\t\texpect(sql).toContain('\"user_id\" AS \"id\"')\n\t\t})\n\n\t\ttest('complex having with AND/OR groups', () => {\n\t\t\tconst having = FilterGroup.create().and([\n\t\t\t\t(q) => q.gt('cnt', 5),\n\t\t\t\t(q) => q.or([(g) => g.eq('region', 'US'), (g) => g.eq('region', 'EU')]),\n\t\t\t])\n\t\t\tconst { sql, params } = buildAggregateQuery(\n\t\t\t\t{\n\t\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\t\tgroupBy: ['region'],\n\t\t\t\t\thaving,\n\t\t\t\t},\n\t\t\t\t'orders',\n\t\t\t\t'id',\n\t\t\t)\n\t\t\texpect(sql).toContain('HAVING (COUNT(*) > $1 AND (\"region\" = $2 OR \"region\" = $3))')\n\t\t\texpect(params).toEqual([5, 'US', 'EU'])\n\t\t})\n\t})\n}\n"],"mappings":"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","names":["AsyncLocalStorage","Pool","Cursor","v","mapField","field","primaryKey","compileOps","f","nextParam","compileFilter","compileChildren","group","compileLeaf","c","compileChild","compileGroup","parts","child","Filter","FilterGroup","compilePgFilter","startIndex","params","paramIndex","value","whereParts","buildSetParts","data","key","col","IncOp","MulOp","MinOp","MaxOp","UnsetOp","PushOp","PullOp","PatchOp","buildSelectQuery","options","tableName","whereClause","nextParamIndex","orderParts","o","orderClause","limitClause","offsetClause","buildCountQuery","buildCreateQuery","keys","placeholders","_","i","columns","k","buildUpdateQuery","setParts","setParams","whereParams","buildPkUpdateQuery","pk","buildDeleteQuery","buildUpsertQuery","conflictColumn","create","createParams","pgConflictCol","setClause","allParams","extractUpsertConflictColumn","filter","schemaName","description","OrmValidationError","aggFnToSql","fn","resolveHavingField","aliasToExpr","groupBySet","buildAggregateQuery","spec","selectParts","agg","expr","groupByFields","whereLeaf","groupByClause","havingClause","havingLeaf","havingParts","postgresqlConnectionPipe","v","TRACKER_TABLE","ADVISORY_LOCK_KEY","DEFAULT_CURSOR_BATCH_SIZE","FIELD_TYPE_TO_PG","PG_TO_FIELD_TYPE","PostgresAdapter","configurable","OrmAdapter","#sessionStore","AsyncLocalStorage","config","Pool","#getClient","#resolveTableName","error","EquippedError","schema","pk","c","tableName","pkName","_schema","data","client","results","doc","sql","params","buildCreateQuery","result","ops","flattenOps","buildPkUpdateQuery","command","filter","options","buildSelectQuery","buildCountQuery","releaseClient","cursor","sessionClient","Cursor","batchSize","rows","row","buildUpdateQuery","buildDeleteQuery","create","conflictColumn","extractUpsertConflictColumn","buildUpsertQuery","spec","buildAggregateQuery","mapped","agg","val","field","id","appliedAt","fn","change","pkType","cols","f","#renderColumnDef","colDef","table","col","newType","#renderDefault","name","#deriveIndexName","unique","#renderFkAction","OrmIntrospectionError","tablesResult","schemas","tableRow","colsResult","pkColName","uniqueResult","uniqueCols","r","fields","pgType","fieldType","nullable","raw","def","#parseDefault","idxResult","indexes","idx","idxName","idxDef","colMatch","fkResult","foreignKeys","fk","#parseFkAction","base","action","rule","numMatch","strMatch"]}