equipped 5.3.1 → 5.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/cache/adapters/in-memory/index.cjs +155 -155
  3. package/dist/cjs/cache/adapters/in-memory/index.cjs.map +1 -1
  4. package/dist/cjs/cache/adapters/in-memory/index.min.cjs +3 -3
  5. package/dist/cjs/cache/adapters/in-memory/index.min.cjs.map +1 -1
  6. package/dist/cjs/dbs/adapters/mongodb/index.cjs.map +1 -1
  7. package/dist/cjs/dbs/adapters/mongodb/index.min.cjs +2 -2
  8. package/dist/cjs/dbs/adapters/mongodb/index.min.cjs.map +1 -1
  9. package/dist/cjs/dbs/index.cjs +181 -179
  10. package/dist/cjs/dbs/index.cjs.map +1 -1
  11. package/dist/cjs/dbs/index.min.cjs +3 -3
  12. package/dist/cjs/dbs/index.min.cjs.map +1 -1
  13. package/dist/cjs/errors/index.cjs +11 -0
  14. package/dist/cjs/errors/index.cjs.map +1 -1
  15. package/dist/cjs/errors/index.min.cjs +1 -1
  16. package/dist/cjs/errors/index.min.cjs.map +1 -1
  17. package/dist/cjs/events/adapters/kafka/index.cjs.map +1 -1
  18. package/dist/cjs/events/adapters/kafka/index.min.cjs +2 -2
  19. package/dist/cjs/events/adapters/kafka/index.min.cjs.map +1 -1
  20. package/dist/cjs/events/adapters/rabbitmq/index.cjs +179 -179
  21. package/dist/cjs/events/adapters/rabbitmq/index.cjs.map +1 -1
  22. package/dist/cjs/events/adapters/rabbitmq/index.min.cjs +3 -3
  23. package/dist/cjs/events/adapters/rabbitmq/index.min.cjs.map +1 -1
  24. package/dist/cjs/orm/adapters/mongodb/index.cjs +144 -144
  25. package/dist/cjs/orm/adapters/mongodb/index.cjs.map +1 -1
  26. package/dist/cjs/orm/adapters/mongodb/index.min.cjs +1 -1
  27. package/dist/cjs/orm/adapters/mongodb/index.min.cjs.map +1 -1
  28. package/dist/cjs/orm/adapters/postgresql/index.cjs +152 -215
  29. package/dist/cjs/orm/adapters/postgresql/index.cjs.map +1 -1
  30. package/dist/cjs/orm/adapters/postgresql/index.min.cjs +11 -11
  31. package/dist/cjs/orm/adapters/postgresql/index.min.cjs.map +1 -1
  32. package/dist/cjs/server/adapters/express/index.cjs +292 -225
  33. package/dist/cjs/server/adapters/express/index.cjs.map +1 -1
  34. package/dist/cjs/server/adapters/express/index.min.cjs +7 -7
  35. package/dist/cjs/server/adapters/express/index.min.cjs.map +1 -1
  36. package/dist/cjs/server/adapters/fastify/index.cjs +277 -210
  37. package/dist/cjs/server/adapters/fastify/index.cjs.map +1 -1
  38. package/dist/cjs/server/adapters/fastify/index.min.cjs +7 -7
  39. package/dist/cjs/server/adapters/fastify/index.min.cjs.map +1 -1
  40. package/dist/cjs/server/index.cjs +82 -15
  41. package/dist/cjs/server/index.cjs.map +1 -1
  42. package/dist/cjs/server/index.min.cjs +7 -7
  43. package/dist/cjs/server/index.min.cjs.map +1 -1
  44. package/dist/cjs/utilities/index.cjs +0 -5
  45. package/dist/cjs/utilities/index.cjs.map +1 -1
  46. package/dist/cjs/utilities/index.min.cjs +2 -2
  47. package/dist/cjs/utilities/index.min.cjs.map +1 -1
  48. package/dist/cjs/validations/index.cjs +1 -0
  49. package/dist/cjs/validations/index.cjs.map +1 -1
  50. package/dist/cjs/validations/index.min.cjs +1 -1
  51. package/dist/cjs/validations/index.min.cjs.map +1 -1
  52. package/dist/esm/cache/adapters/in-memory/index.min.mjs +1 -1
  53. package/dist/esm/cache/adapters/in-memory/index.min.mjs.map +1 -1
  54. package/dist/esm/cache/adapters/in-memory/index.mjs +6 -3
  55. package/dist/esm/cache/adapters/in-memory/index.mjs.map +1 -1
  56. package/dist/esm/cache/adapters/redis/index.min.mjs +1 -1
  57. package/dist/esm/cache/adapters/redis/index.mjs +5 -3
  58. package/dist/esm/cache/index.min.mjs +1 -1
  59. package/dist/esm/cache/index.mjs +2 -2
  60. package/dist/esm/{chunk-2GGMLKRP.min.mjs → chunk-4GZ43EQ2.min.mjs} +2 -2
  61. package/dist/esm/{chunk-IRYCN6PI.min.mjs → chunk-5FEVQT6K.min.mjs} +2 -2
  62. package/dist/esm/{chunk-LKS5ZE45.mjs → chunk-74GY55PM.mjs} +2 -2
  63. package/dist/esm/{chunk-A4P7P6BU.mjs → chunk-7N4WUZ7P.mjs} +167 -347
  64. package/dist/esm/chunk-7N4WUZ7P.mjs.map +1 -0
  65. package/dist/esm/chunk-A332TTED.mjs +135 -0
  66. package/dist/esm/chunk-A332TTED.mjs.map +1 -0
  67. package/dist/esm/{chunk-7CP7ZE2O.mjs → chunk-BONYW6LU.mjs} +9 -5
  68. package/dist/esm/{chunk-7CP7ZE2O.mjs.map → chunk-BONYW6LU.mjs.map} +1 -1
  69. package/dist/esm/{chunk-HXFNN6RI.mjs → chunk-C3G4W4ZV.mjs} +3 -3
  70. package/dist/esm/chunk-CU2HU5UE.min.mjs +2 -0
  71. package/dist/esm/chunk-CU2HU5UE.min.mjs.map +1 -0
  72. package/dist/esm/{chunk-676VIABN.min.mjs → chunk-LJZOS62I.min.mjs} +2 -2
  73. package/dist/esm/{chunk-7SQ5JQYR.min.mjs → chunk-MCX2O5IT.min.mjs} +2 -2
  74. package/dist/esm/{chunk-BWUFPMVG.mjs → chunk-MNKDFIVK.mjs} +2 -2
  75. package/dist/esm/chunk-N3OD4QL2.min.mjs +32 -0
  76. package/dist/esm/chunk-N3OD4QL2.min.mjs.map +1 -0
  77. package/dist/esm/chunk-NEF3UYFA.mjs +144 -0
  78. package/dist/esm/chunk-NEF3UYFA.mjs.map +1 -0
  79. package/dist/esm/{chunk-TSVHZZE4.mjs → chunk-RFJ5URMP.mjs} +5 -5
  80. package/dist/esm/chunk-RX4XPABH.min.mjs +2 -0
  81. package/dist/esm/{chunk-MQXTTWPS.min.mjs.map → chunk-RX4XPABH.min.mjs.map} +1 -1
  82. package/dist/esm/chunk-SPOVKWV3.min.mjs +2 -0
  83. package/dist/esm/chunk-SPOVKWV3.min.mjs.map +1 -0
  84. package/dist/esm/{chunk-3TP7NINX.mjs → chunk-XESM7IWM.mjs} +2 -2
  85. package/dist/esm/{chunk-5PZACGPI.mjs → chunk-XJKBHQWO.mjs} +7 -5
  86. package/dist/esm/{chunk-5PZACGPI.mjs.map → chunk-XJKBHQWO.mjs.map} +1 -1
  87. package/dist/esm/chunk-YC6V4HLT.min.mjs +2 -0
  88. package/dist/esm/{chunk-7NF7PV6S.min.mjs.map → chunk-YC6V4HLT.min.mjs.map} +1 -1
  89. package/dist/esm/{chunk-P4PRHHIY.min.mjs → chunk-YWQTFKNY.min.mjs} +2 -2
  90. package/dist/esm/dbs/adapters/mongodb/index.min.mjs +1 -1
  91. package/dist/esm/dbs/adapters/mongodb/index.min.mjs.map +1 -1
  92. package/dist/esm/dbs/adapters/mongodb/index.mjs +8 -5
  93. package/dist/esm/dbs/adapters/mongodb/index.mjs.map +1 -1
  94. package/dist/esm/dbs/index.min.mjs +1 -1
  95. package/dist/esm/dbs/index.mjs +5 -3
  96. package/dist/esm/errors/index.min.mjs +1 -1
  97. package/dist/esm/errors/index.mjs +3 -1
  98. package/dist/esm/events/adapters/kafka/index.min.mjs +1 -1
  99. package/dist/esm/events/adapters/kafka/index.mjs +4 -2
  100. package/dist/esm/events/adapters/rabbitmq/index.min.mjs +1 -1
  101. package/dist/esm/events/adapters/rabbitmq/index.min.mjs.map +1 -1
  102. package/dist/esm/events/adapters/rabbitmq/index.mjs +7 -3
  103. package/dist/esm/events/adapters/rabbitmq/index.mjs.map +1 -1
  104. package/dist/esm/index.min.mjs +1 -1
  105. package/dist/esm/index.mjs +1 -1
  106. package/dist/esm/instance/index.min.mjs +1 -1
  107. package/dist/esm/instance/index.mjs +1 -1
  108. package/dist/esm/introspection-TO27QT2X.min.mjs +2 -0
  109. package/dist/esm/{introspection-YJF3QAUK.mjs → introspection-ZUQYVEFC.mjs} +3 -3
  110. package/dist/esm/jobs/adapters/redis/index.min.mjs +1 -1
  111. package/dist/esm/jobs/adapters/redis/index.min.mjs.map +1 -1
  112. package/dist/esm/jobs/adapters/redis/index.mjs +10 -6
  113. package/dist/esm/jobs/adapters/redis/index.mjs.map +1 -1
  114. package/dist/esm/orm/adapters/in-memory/index.min.mjs +1 -1
  115. package/dist/esm/orm/adapters/in-memory/index.mjs +4 -3
  116. package/dist/esm/orm/adapters/json/index.min.mjs +1 -1
  117. package/dist/esm/orm/adapters/json/index.min.mjs.map +1 -1
  118. package/dist/esm/orm/adapters/json/index.mjs +6 -5
  119. package/dist/esm/orm/adapters/json/index.mjs.map +1 -1
  120. package/dist/esm/orm/adapters/mongodb/index.min.mjs +1 -1
  121. package/dist/esm/orm/adapters/mongodb/index.min.mjs.map +1 -1
  122. package/dist/esm/orm/adapters/mongodb/index.mjs +6 -4
  123. package/dist/esm/orm/adapters/mongodb/index.mjs.map +1 -1
  124. package/dist/esm/orm/adapters/postgresql/index.min.mjs +2 -2
  125. package/dist/esm/orm/adapters/postgresql/index.min.mjs.map +1 -1
  126. package/dist/esm/orm/adapters/postgresql/index.mjs +8 -5
  127. package/dist/esm/orm/adapters/postgresql/index.mjs.map +1 -1
  128. package/dist/esm/orm/index.min.mjs +1 -1
  129. package/dist/esm/orm/index.mjs +3 -3
  130. package/dist/esm/server/adapters/express/index.min.mjs +1 -1
  131. package/dist/esm/server/adapters/express/index.min.mjs.map +1 -1
  132. package/dist/esm/server/adapters/express/index.mjs +7 -3
  133. package/dist/esm/server/adapters/express/index.mjs.map +1 -1
  134. package/dist/esm/server/adapters/fastify/index.min.mjs +1 -1
  135. package/dist/esm/server/adapters/fastify/index.min.mjs.map +1 -1
  136. package/dist/esm/server/adapters/fastify/index.mjs +7 -3
  137. package/dist/esm/server/adapters/fastify/index.mjs.map +1 -1
  138. package/dist/esm/server/index.min.mjs +1 -1
  139. package/dist/esm/server/index.mjs +1 -1
  140. package/dist/esm/utilities/index.min.mjs +1 -1
  141. package/dist/esm/utilities/index.mjs +5 -4
  142. package/dist/esm/validations/index.min.mjs +1 -1
  143. package/dist/esm/validations/index.mjs +1 -1
  144. package/dist/types/{base-BDTBLZ9a.d.ts → base-BnwwLupM.d.ts} +3 -3
  145. package/dist/types/cache/adapters/in-memory/index.js +6 -3
  146. package/dist/types/cache/adapters/redis/index.js +5 -3
  147. package/dist/types/cache/index.js +2 -2
  148. package/dist/types/chunk-7MXHWHRV.js +134 -0
  149. package/dist/types/{chunk-LPB5LS7A.js → chunk-BKXNLF6X.js} +1 -1
  150. package/dist/types/{chunk-J3TGB5YQ.js → chunk-DZFKDZDZ.js} +8 -4
  151. package/dist/types/{chunk-YLJNBL6J.js → chunk-GQRSK2KP.js} +6 -4
  152. package/dist/types/chunk-PPD4WDG6.js +143 -0
  153. package/dist/types/{chunk-55CTVK7R.js → chunk-SZAA5GF6.js} +1 -1
  154. package/dist/types/{chunk-4DH3POYM.js → chunk-UGLZIYAR.js} +4 -4
  155. package/dist/types/{chunk-5Z26ZURE.js → chunk-UR7O43FC.js} +1 -1
  156. package/dist/types/{chunk-IZDJDGOV.js → chunk-X5ZDIXCZ.js} +2 -2
  157. package/dist/types/{chunk-TT5EORDK.js → chunk-YGIIUOQM.js} +166 -346
  158. package/dist/types/dbs/adapters/mongodb/index.js +8 -5
  159. package/dist/types/dbs/index.js +5 -3
  160. package/dist/types/errors/index.d.ts +8 -3
  161. package/dist/types/errors/index.js +3 -1
  162. package/dist/types/events/adapters/kafka/index.js +4 -2
  163. package/dist/types/events/adapters/rabbitmq/index.js +7 -3
  164. package/dist/types/index.js +1 -1
  165. package/dist/types/instance/index.js +1 -1
  166. package/dist/types/{introspection-EHGM4RMO.js → introspection-ZQMYTW6H.js} +2 -2
  167. package/dist/types/jobs/adapters/redis/index.js +10 -6
  168. package/dist/types/orm/adapters/in-memory/index.js +4 -3
  169. package/dist/types/orm/adapters/json/index.js +6 -5
  170. package/dist/types/orm/adapters/mongodb/index.js +6 -4
  171. package/dist/types/orm/adapters/postgresql/index.js +8 -5
  172. package/dist/types/orm/index.js +3 -3
  173. package/dist/types/{requestError-DdaL8hc3.d.ts → requestError-BmOhSuFb.d.ts} +1 -0
  174. package/dist/types/server/adapters/express/index.d.ts +2 -2
  175. package/dist/types/server/adapters/express/index.js +7 -3
  176. package/dist/types/server/adapters/fastify/index.d.ts +3 -3
  177. package/dist/types/server/adapters/fastify/index.js +7 -3
  178. package/dist/types/server/index.d.ts +4 -4
  179. package/dist/types/server/index.js +1 -1
  180. package/dist/types/utilities/index.d.ts +1 -2
  181. package/dist/types/utilities/index.js +5 -4
  182. package/dist/types/{validationError-oigtHqBi.d.ts → validationError-C2tVK4s7.d.ts} +1 -1
  183. package/dist/types/validations/index.d.ts +4 -4
  184. package/dist/types/validations/index.js +1 -1
  185. package/package.json +1 -1
  186. package/dist/esm/chunk-7NF7PV6S.min.mjs +0 -2
  187. package/dist/esm/chunk-A4P7P6BU.mjs.map +0 -1
  188. package/dist/esm/chunk-IN5YKIJ7.min.mjs +0 -32
  189. package/dist/esm/chunk-IN5YKIJ7.min.mjs.map +0 -1
  190. package/dist/esm/chunk-MQXTTWPS.min.mjs +0 -2
  191. package/dist/esm/introspection-THKMXV5V.min.mjs +0 -2
  192. /package/dist/esm/{chunk-2GGMLKRP.min.mjs.map → chunk-4GZ43EQ2.min.mjs.map} +0 -0
  193. /package/dist/esm/{chunk-IRYCN6PI.min.mjs.map → chunk-5FEVQT6K.min.mjs.map} +0 -0
  194. /package/dist/esm/{chunk-LKS5ZE45.mjs.map → chunk-74GY55PM.mjs.map} +0 -0
  195. /package/dist/esm/{chunk-HXFNN6RI.mjs.map → chunk-C3G4W4ZV.mjs.map} +0 -0
  196. /package/dist/esm/{chunk-676VIABN.min.mjs.map → chunk-LJZOS62I.min.mjs.map} +0 -0
  197. /package/dist/esm/{chunk-7SQ5JQYR.min.mjs.map → chunk-MCX2O5IT.min.mjs.map} +0 -0
  198. /package/dist/esm/{chunk-BWUFPMVG.mjs.map → chunk-MNKDFIVK.mjs.map} +0 -0
  199. /package/dist/esm/{chunk-TSVHZZE4.mjs.map → chunk-RFJ5URMP.mjs.map} +0 -0
  200. /package/dist/esm/{chunk-3TP7NINX.mjs.map → chunk-XESM7IWM.mjs.map} +0 -0
  201. /package/dist/esm/{chunk-P4PRHHIY.min.mjs.map → chunk-YWQTFKNY.min.mjs.map} +0 -0
  202. /package/dist/esm/{introspection-THKMXV5V.min.mjs.map → introspection-TO27QT2X.min.mjs.map} +0 -0
  203. /package/dist/esm/{introspection-YJF3QAUK.mjs.map → introspection-ZUQYVEFC.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"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"]}
1
+ {"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"]}