uql-orm 0.66.0 → 0.67.1

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 (193) hide show
  1. package/dist/browser/querier/httpQuerier.js +1 -8
  2. package/dist/browser/uql-browser.min.js.map +5 -5
  3. package/dist/bunSql/bunSql.util.d.ts +2 -6
  4. package/dist/bunSql/bunSql.util.js +2 -6
  5. package/dist/bunSql/bunSqlQuerier.d.ts +2 -5
  6. package/dist/bunSql/bunSqlQuerier.js +2 -5
  7. package/dist/cockroachdb/cockroachDialect.d.ts +4 -13
  8. package/dist/cockroachdb/cockroachDialect.js +4 -13
  9. package/dist/context/context.browser.js +2 -10
  10. package/dist/context/context.d.ts +4 -17
  11. package/dist/context/context.js +4 -17
  12. package/dist/dialect/abstractDialect.d.ts +4 -19
  13. package/dist/dialect/abstractDialect.js +2 -20
  14. package/dist/dialect/abstractSqlDialect.d.ts +47 -212
  15. package/dist/dialect/abstractSqlDialect.js +68 -222
  16. package/dist/dialect/aliases.d.ts +2 -12
  17. package/dist/dialect/aliases.js +4 -12
  18. package/dist/dialect/hydrateColumn.d.ts +2 -6
  19. package/dist/dialect/hydrateColumn.js +3 -13
  20. package/dist/dialect/jsonArrayElemMatchUtils.d.ts +1 -7
  21. package/dist/dialect/jsonArrayElemMatchUtils.js +1 -7
  22. package/dist/dialect/jsonSql.d.ts +6 -27
  23. package/dist/dialect/jsonSql.js +6 -27
  24. package/dist/dialect/mergeSqlDialect.d.ts +4 -22
  25. package/dist/dialect/mergeSqlDialect.js +4 -22
  26. package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -37
  27. package/dist/dialect/mysqlLikeSqlDialect.js +35 -51
  28. package/dist/dialect/pgLikeSqlDialect.d.ts +8 -22
  29. package/dist/dialect/pgLikeSqlDialect.js +36 -39
  30. package/dist/dialect/queryContext.d.ts +4 -22
  31. package/dist/dialect/queryContext.js +4 -22
  32. package/dist/dialect/queryJoins.d.ts +3 -12
  33. package/dist/dialect/queryJoins.js +3 -12
  34. package/dist/dialect/vectorCast.d.ts +2 -12
  35. package/dist/dialect/vectorCast.js +3 -19
  36. package/dist/dialect/vectorSqlDialect.d.ts +8 -38
  37. package/dist/dialect/vectorSqlDialect.js +7 -38
  38. package/dist/entity/decorator/bag.d.ts +6 -19
  39. package/dist/entity/decorator/bag.js +6 -22
  40. package/dist/entity/decorator/entity.d.ts +2 -7
  41. package/dist/entity/decorator/entity.js +2 -7
  42. package/dist/entity/decorator/members.d.ts +7 -30
  43. package/dist/entity/decorator/members.js +3 -12
  44. package/dist/entity/metadata/definition.d.ts +2 -18
  45. package/dist/entity/metadata/definition.js +6 -28
  46. package/dist/http/handler.d.ts +2 -14
  47. package/dist/index.d.ts +4 -1
  48. package/dist/index.js +3 -1
  49. package/dist/libsql/libsqlDialect.d.ts +1 -8
  50. package/dist/libsql/libsqlDialect.js +1 -8
  51. package/dist/maria/mariaDialect.d.ts +3 -5
  52. package/dist/maria/mariaDialect.js +5 -5
  53. package/dist/maria/mariadbQuerier.js +2 -2
  54. package/dist/maria/mariadbQuerierPool.js +1 -6
  55. package/dist/migrate/builder/migrationBuilder.js +3 -19
  56. package/dist/migrate/builder/splitSqlStatements.d.ts +1 -14
  57. package/dist/migrate/builder/splitSqlStatements.js +2 -22
  58. package/dist/migrate/builder/types.d.ts +2 -15
  59. package/dist/migrate/cli-config.js +2 -11
  60. package/dist/migrate/cli.js +2 -7
  61. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -15
  62. package/dist/migrate/codegen/entityCodeGenerator.js +15 -44
  63. package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -8
  64. package/dist/migrate/codegen/fieldOptionsSource.js +3 -22
  65. package/dist/migrate/ddl/indexDdl.d.ts +2 -5
  66. package/dist/migrate/ddl/indexDdl.js +2 -5
  67. package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -13
  68. package/dist/migrate/ddl/pgIndexDdl.js +3 -13
  69. package/dist/migrate/generator/definitionToNode.d.ts +2 -9
  70. package/dist/migrate/generator/definitionToNode.js +3 -17
  71. package/dist/migrate/generator/indexNodeToSchema.d.ts +2 -3
  72. package/dist/migrate/generator/indexNodeToSchema.js +2 -3
  73. package/dist/migrate/generator/mongoCommand.d.ts +1 -8
  74. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -8
  75. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -8
  76. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +6 -26
  77. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +9 -41
  78. package/dist/migrate/introspection/baseSqlIntrospector.js +0 -1
  79. package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
  80. package/dist/migrate/introspection/mongoIntrospector.js +48 -46
  81. package/dist/migrate/introspection/mssqlIntrospector.d.ts +4 -4
  82. package/dist/migrate/introspection/mssqlIntrospector.js +18 -27
  83. package/dist/migrate/introspection/mysqlIntrospector.d.ts +7 -2
  84. package/dist/migrate/introspection/mysqlIntrospector.js +16 -14
  85. package/dist/migrate/introspection/postgresIntrospector.d.ts +24 -9
  86. package/dist/migrate/introspection/postgresIntrospector.js +68 -59
  87. package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -1
  88. package/dist/migrate/introspection/sqliteIntrospector.js +8 -10
  89. package/dist/migrate/migrator.d.ts +9 -53
  90. package/dist/migrate/migrator.js +32 -65
  91. package/dist/migrate/schemaGenerator.d.ts +16 -66
  92. package/dist/migrate/schemaGenerator.js +21 -74
  93. package/dist/mongo/mongoDialect.d.ts +21 -53
  94. package/dist/mongo/mongoDialect.js +25 -70
  95. package/dist/mongo/mongodbQuerier.d.ts +5 -8
  96. package/dist/mongo/mongodbQuerier.js +31 -65
  97. package/dist/mssql/mssqlDialect.d.ts +8 -34
  98. package/dist/mssql/mssqlDialect.js +37 -51
  99. package/dist/mssql/mssqlQuerier.d.ts +37 -4
  100. package/dist/mssql/mssqlQuerier.js +2 -2
  101. package/dist/mssql/mssqlWireTypes.d.ts +2 -14
  102. package/dist/mssql/mssqlWireTypes.js +2 -14
  103. package/dist/nestjs/uqlModule.js +2 -7
  104. package/dist/pglite/pgliteQuerier.d.ts +1 -9
  105. package/dist/pglite/pgliteQuerierPool.d.ts +4 -26
  106. package/dist/pglite/pgliteQuerierPool.js +3 -18
  107. package/dist/postgres/abstractPgQuerierPool.d.ts +1 -8
  108. package/dist/postgres/abstractPgQuerierPool.js +1 -8
  109. package/dist/postgres/pgNumericTypes.d.ts +3 -26
  110. package/dist/postgres/pgNumericTypes.js +3 -26
  111. package/dist/postgres/postgresDialect.d.ts +4 -10
  112. package/dist/postgres/postgresDialect.js +4 -10
  113. package/dist/querier/abstractQuerier.d.ts +35 -103
  114. package/dist/querier/abstractQuerier.js +105 -201
  115. package/dist/querier/abstractSharedHandleQuerierPool.d.ts +3 -17
  116. package/dist/querier/abstractSharedHandleQuerierPool.js +3 -17
  117. package/dist/querier/abstractSqlQuerier.d.ts +15 -36
  118. package/dist/querier/abstractSqlQuerier.js +49 -131
  119. package/dist/schema/canonicalType.d.ts +3 -21
  120. package/dist/schema/canonicalType.js +22 -67
  121. package/dist/schema/dependencyGraph.d.ts +2 -8
  122. package/dist/schema/dependencyGraph.js +2 -32
  123. package/dist/schema/index.d.ts +1 -25
  124. package/dist/schema/index.js +0 -26
  125. package/dist/schema/indexColumns.d.ts +1 -8
  126. package/dist/schema/indexColumns.js +1 -8
  127. package/dist/schema/indexDifferences.d.ts +7 -40
  128. package/dist/schema/indexDifferences.js +6 -31
  129. package/dist/schema/schemaAST.d.ts +8 -175
  130. package/dist/schema/schemaAST.js +13 -365
  131. package/dist/schema/schemaASTBuilder.d.ts +2 -24
  132. package/dist/schema/schemaASTBuilder.js +2 -30
  133. package/dist/schema/schemaASTDiffer.d.ts +6 -46
  134. package/dist/schema/schemaASTDiffer.js +8 -56
  135. package/dist/schema/types.d.ts +5 -61
  136. package/dist/schema/types.js +3 -6
  137. package/dist/sqlite/abstractSqliteQuerier.d.ts +1 -8
  138. package/dist/sqlite/localSqliteQuerierPool.d.ts +1 -7
  139. package/dist/sqlite/localSqliteQuerierPool.js +1 -7
  140. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +2 -7
  141. package/dist/sqlite/nodeSqliteQuerierPool.js +2 -7
  142. package/dist/sqlite/sqliteDialect.d.ts +5 -20
  143. package/dist/sqlite/sqliteDialect.js +29 -35
  144. package/dist/turso/tursoDialect.d.ts +4 -6
  145. package/dist/turso/tursoDialect.js +4 -6
  146. package/dist/turso/tursoLocalQuerierPool.d.ts +1 -7
  147. package/dist/turso/tursoLocalQuerierPool.js +1 -7
  148. package/dist/turso/tursoQuerierPool.d.ts +2 -6
  149. package/dist/turso/tursoQuerierPool.js +2 -6
  150. package/dist/turso/tursoSessionQuerier.d.ts +1 -7
  151. package/dist/turso/tursoSessionQuerier.js +1 -7
  152. package/dist/type/dialect.d.ts +42 -94
  153. package/dist/type/dialect.js +3 -13
  154. package/dist/type/entity.d.ts +163 -534
  155. package/dist/type/entity.js +26 -9
  156. package/dist/type/logger.d.ts +2 -14
  157. package/dist/type/migration.d.ts +9 -38
  158. package/dist/type/querier.d.ts +9 -28
  159. package/dist/type/querierPool.d.ts +4 -26
  160. package/dist/type/query.d.ts +19 -73
  161. package/dist/type/query.js +2 -7
  162. package/dist/type/queryAggregate.d.ts +18 -98
  163. package/dist/type/queryRaw.d.ts +1 -8
  164. package/dist/type/queryRaw.js +1 -8
  165. package/dist/type/queryWhere.d.ts +13 -61
  166. package/dist/type/universalQuerier.d.ts +18 -105
  167. package/dist/type/utility.d.ts +12 -24
  168. package/dist/type/vector.d.ts +8 -38
  169. package/dist/type/vector.js +1 -1
  170. package/dist/type/wire.d.ts +2 -5
  171. package/dist/util/dialect.util.d.ts +9 -27
  172. package/dist/util/dialect.util.js +10 -27
  173. package/dist/util/field.util.d.ts +2 -31
  174. package/dist/util/field.util.js +3 -43
  175. package/dist/util/fieldOption.util.d.ts +7 -15
  176. package/dist/util/fieldOption.util.js +1 -1
  177. package/dist/util/filters.util.d.ts +2 -5
  178. package/dist/util/filters.util.js +2 -5
  179. package/dist/util/logger.d.ts +2 -6
  180. package/dist/util/logger.js +2 -6
  181. package/dist/util/object.util.d.ts +2 -6
  182. package/dist/util/object.util.js +1 -5
  183. package/dist/util/raw.d.ts +3 -23
  184. package/dist/util/relationQuery.util.d.ts +3 -14
  185. package/dist/util/relationQuery.util.js +3 -14
  186. package/dist/util/rowKey.util.d.ts +2 -10
  187. package/dist/util/rowKey.util.js +2 -10
  188. package/dist/util/sql.util.d.ts +6 -37
  189. package/dist/util/sql.util.js +13 -73
  190. package/dist/util/sqlLiteral.d.ts +2 -13
  191. package/dist/util/sqlLiteral.js +8 -13
  192. package/dist/util/string.util.js +0 -2
  193. package/package.json +4 -4
@@ -1,19 +1,14 @@
1
1
  import { COUNT_ALIAS, TOTAL_ALIAS } from '../dialect/aliases.js';
2
2
  import { decodeColumn } from '../dialect/hydrateColumn.js';
3
- import { getMeta, idOf, namesKey } from '../entity/index.js';
3
+ import { getMeta, namesKey } from '../entity/index.js';
4
4
  import { COUNT_RESULT_KEY } from '../type/index.js';
5
- import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, idOnlyQuery, isAutoIncrement, isPagedQuery, isRecord, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
5
+ import { buildUpdateResult, clone, getInsertFieldKeys, insertShapeOf, isAutoIncrement, isRecord, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, } from '../util/index.js';
6
6
  import { AbstractQuerier } from './abstractQuerier.js';
7
7
  import { streamViaCursor } from './cursorStream.js';
8
8
  import { enrichError } from './queryError.js';
9
9
  /**
10
- * Row indexes grouped by whether the caller supplied the key, payload order kept within each group.
11
- * One group when the batch agrees on it, which is the single statement it has always been.
12
- *
13
- * Deliberately coarser than {@link groupByInsertShape}, and the two must not be merged: an insert's
14
- * `VALUES` list takes the union of the batch's columns and fills the rest with `DEFAULT`, so the
15
- * key's presence is the only thing that changes what the statement can report. Splitting an insert
16
- * by full shape instead would turn a batch of optional fields into a statement per combination.
10
+ * Row indexes split by whether the row names its key, the one thing that changes what an insert can
11
+ * report; coarser than {@link groupByInsertShape} on purpose, since an insert fills a missing column with `DEFAULT`.
17
12
  */
18
13
  function partitionBySuppliedId(payload, idKey) {
19
14
  const supplied = [];
@@ -110,15 +105,21 @@ export class AbstractSqlQuerier extends AbstractQuerier {
110
105
  return this.timed(query, values, () => this.internalRun(query, this.dialect.normalizeValues(values)));
111
106
  });
112
107
  }
108
+ /** The rows of a statement the dialect builds. */
109
+ query(build) {
110
+ const ctx = this.dialect.createContext();
111
+ build(ctx);
112
+ return this.all(ctx.sql, ctx.values);
113
+ }
114
+ /** Runs a statement the dialect builds. */
115
+ exec(build) {
116
+ const ctx = this.dialect.createContext();
117
+ build(ctx);
118
+ return this.run(ctx.sql, ctx.values);
119
+ }
113
120
  /**
114
- * `$lock` outside a transaction is always a bug, and a silent one. Every engine accepts
115
- * `SELECT ... FOR UPDATE` in autocommit and then releases the lock as the statement commits,
116
- * before the caller has seen a row: the SQL is correct, nothing is omitted, and no layer below
117
- * this one can tell. The dialect cannot check it either, being stateless and shared by every
118
- * connection of the pool, so this is the only place it can be caught.
119
- *
120
- * The capability check runs first on purpose: "this engine has no row locks" is the more
121
- * actionable answer, and on SQLite it is the answer either way.
121
+ * Refuses a `$lock` the engine lacks, then one outside a transaction, where the lock would drop as the
122
+ * statement commits; only the querier knows whether one is open.
122
123
  */
123
124
  assertLockable(entity, q) {
124
125
  if (!q.$lock) {
@@ -130,14 +131,8 @@ export class AbstractSqlQuerier extends AbstractQuerier {
130
131
  }
131
132
  }
132
133
  /**
133
- * Run the `SET`s that tune an ANN index for this query, and refuse the ones that would not apply.
134
- *
135
- * Same shape as {@link assertLockable} and for the same reason: a `SET LOCAL` outside a transaction
136
- * is accepted, applies to nothing, and leaves the query running at the engine's default recall -
137
- * correct SQL, silently untuned. Only the querier knows whether a transaction is open.
138
- *
139
- * The statements go through `internalRun`, sharing this querier's single connection with the query
140
- * they precede; `SET LOCAL` then expires with the transaction, so nothing is left behind.
134
+ * Runs the `SET`s tuning an ANN index for the query on its connection, refusing where they would apply to
135
+ * nothing: a `SET LOCAL` outside a transaction.
141
136
  */
142
137
  async applyVectorTuning(entity, q) {
143
138
  // Resolved before the transaction check, so the refusal fires only where the tuning would have
@@ -147,7 +142,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
147
142
  if (!statements.length) {
148
143
  return;
149
144
  }
150
- if (this.dialect.vectorTuningNeedsTransaction && !this.hasOpenTransaction) {
145
+ if (this.dialect.features.vectorTuningNeedsTransaction && !this.hasOpenTransaction) {
151
146
  throw new TypeError(`$candidates requires an open transaction on ${this.dialect.dialectName}; run the query inside pool.transaction(...)`);
152
147
  }
153
148
  for (const statement of statements) {
@@ -157,39 +152,23 @@ export class AbstractSqlQuerier extends AbstractQuerier {
157
152
  async internalFindMany(entity, q, opts) {
158
153
  return this.hydrateRows(entity, await this.selectRows(entity, q, opts));
159
154
  }
160
- /**
161
- * How to count when the total cannot ride along in the read's own `COUNT(*) OVER ()` column, or
162
- * `undefined` when it can. Two clauses rule the window out: `$distinct`, because a window counts
163
- * before the deduplication and so overstates the page, and `$lock` on an engine that refuses the
164
- * pair outright. Both then cost a second statement; only the counting differs.
165
- */
166
- countedSeparately(entity, q, opts) {
167
- if (q.$distinct) {
168
- return (ctx) => this.dialect.countDistinct(ctx, entity, q, opts);
169
- }
170
- if (q.$lock && !this.dialect.supportsWindowWithRowLock) {
171
- return (ctx) => this.dialect.count(ctx, entity, q, opts);
172
- }
173
- return undefined;
155
+ /** Every row `q` matches past its page, deduplicated where it reads `$distinct`. */
156
+ countUnpaged(entity, q, opts) {
157
+ return q.$distinct
158
+ ? this.runCount((ctx) => this.dialect.countDistinct(ctx, entity, q, opts))
159
+ : this.internalCount(entity, { $where: q.$where }, opts);
174
160
  }
175
161
  /**
176
- * One statement for both: the page carries its own unpaged total in an extra column. An empty page
177
- * has no row to carry it, which is the one case still needing a count of its own - a `$skip` past
178
- * the end, or a filter nothing matched.
179
- *
180
- * A `$required` relation needs no special case: the window counts what the INNER JOIN left, which
181
- * is exactly the total a caller of a filtered read is asking for. A `$lock` is the one clause an
182
- * engine may refuse to have in the same statement, which {@link AbstractSqlDialect.supportsWindowWithRowLock}
183
- * answers; where it does, the total comes from a count of its own. A `$distinct` read needs one
184
- * too, and a deduplicating one: see {@link AbstractSqlDialect.countDistinct}.
162
+ * The page and its unpaged total in one statement, a window column on each row, and a count of its own for
163
+ * an empty page. A window counts before `DISTINCT`, and the Postgres family refuses one beside a `$lock`,
164
+ * so those count apart.
185
165
  */
186
166
  async internalFindManyAndCount(entity, q, opts) {
187
- const separately = this.countedSeparately(entity, q, opts);
188
- if (separately) {
189
- return Promise.all([this.internalFindMany(entity, q, opts), this.runCount(separately)]);
167
+ if (q.$distinct || (q.$lock && !this.dialect.features.rowLockWithWindow)) {
168
+ return Promise.all([this.internalFindMany(entity, q, opts), this.countUnpaged(entity, q, opts)]);
190
169
  }
191
170
  const rows = await this.selectRows(entity, q, opts, TOTAL_ALIAS);
192
- const total = rows.length ? Number(rows[0][TOTAL_ALIAS]) : await this.internalCount(entity, q, opts);
171
+ const total = rows.length ? Number(rows[0][TOTAL_ALIAS]) : await this.countUnpaged(entity, q, opts);
193
172
  for (const row of rows) {
194
173
  delete row[TOTAL_ALIAS];
195
174
  }
@@ -203,9 +182,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
203
182
  if (q.$candidates !== undefined) {
204
183
  await this.applyVectorTuning(entity, q);
205
184
  }
206
- const ctx = this.dialect.createContext();
207
- this.dialect.find(ctx, entity, q, opts, totalAlias);
208
- return this.all(ctx.sql, ctx.values);
185
+ return this.query((ctx) => this.dialect.find(ctx, entity, q, opts, totalAlias));
209
186
  }
210
187
  hydrateRows(entity, rows) {
211
188
  const founds = unflatObjects(rows);
@@ -219,8 +196,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
219
196
  await this.applyVectorTuning(entity, q);
220
197
  }
221
198
  const meta = getMeta(entity);
222
- // The one path that does not go through `all`/`run`, so it connects on its own: streaming first on
223
- // a freshly acquired querier used to reach `getConn()` with nothing acquired.
199
+ // The one path not going through `all`/`run`, so it connects on its own.
224
200
  await this.lazyConnect();
225
201
  // No `normalizeValues` here, unlike `all`/`run`: those also take raw SQL, while every value a
226
202
  // context holds was normalized as it was bound.
@@ -319,21 +295,17 @@ export class AbstractSqlQuerier extends AbstractQuerier {
319
295
  * a catalog that does not know the table answers with no row, which is nothing counted.
320
296
  */
321
297
  async runCount(build) {
322
- const ctx = this.dialect.createContext();
323
- build(ctx);
324
- const res = await this.all(ctx.sql, ctx.values);
325
- return Number(res[0]?.[COUNT_ALIAS] ?? 0);
298
+ const [row] = await this.query(build);
299
+ return Number(row?.[COUNT_ALIAS] ?? 0);
326
300
  }
327
- async internalCount(entity, q = {}, opts) {
301
+ async internalCount(entity, q, opts) {
328
302
  return this.runCount((ctx) => this.dialect.count(ctx, entity, q, opts));
329
303
  }
330
304
  async estimatedCount(entity) {
331
305
  return this.runCount((ctx) => this.dialect.estimatedCount(ctx, entity));
332
306
  }
333
307
  async internalAggregate(entity, q, opts) {
334
- const ctx = this.dialect.createContext();
335
- this.dialect.aggregate(ctx, entity, q, opts);
336
- const rows = await this.all(ctx.sql, ctx.values);
308
+ const rows = await this.query((ctx) => this.dialect.aggregate(ctx, entity, q, opts));
337
309
  const hydratable = this.dialect.hydratableAggregates(entity, q);
338
310
  for (const row of rows) {
339
311
  const cells = row;
@@ -354,12 +326,8 @@ export class AbstractSqlQuerier extends AbstractQuerier {
354
326
  const idField = sole ? meta.fields[idKey] : undefined;
355
327
  const generatedKey = !!idField && isAutoIncrement(idField, true);
356
328
  for (const group of partitionBySuppliedId(rows, idKey)) {
357
- // RETURNING-based ids are exact per row. Header-derived ones (LAST_INSERT_ID / lastInsertRowid
358
- // arithmetic) are only sound when the key is database-generated and every row *in this
359
- // statement* left it to the database. That is a property of the statement, not of the batch:
360
- // asking it of the whole batch meant one supplied id made every id `undefined`, which a
361
- // cascade then wrote into a child as a null foreign key. Splitting on that one axis costs at
362
- // most one extra statement and keeps each of them inferable.
329
+ // Header ids are only sound where every row of this statement left its key to the database, so
330
+ // the batch is split on that alone, and a supplied id cannot void the others.
363
331
  const idsReliable = sole &&
364
332
  (this.dialect.insertIdSource === 'returning' ||
365
333
  (generatedKey && group.every((index) => rows[index][idKey] === undefined)));
@@ -371,9 +339,8 @@ export class AbstractSqlQuerier extends AbstractQuerier {
371
339
  // Per group, not per batch: the two carry different columns - one names the key, one does not -
372
340
  // so a budget taken over their union would under-fill the statement that is missing one.
373
341
  for (const indexes of chunkByBindBudget(meta, rows, group, this.dialect.maxBindValues)) {
374
- const ctx = this.dialect.createContext();
375
- this.dialect.insert(ctx, entity, indexes.map((index) => rows[index]));
376
- const { ids = [] } = await this.run(ctx.sql, ctx.values);
342
+ const chunk = indexes.map((index) => rows[index]);
343
+ const { ids = [] } = await this.exec((ctx) => this.dialect.insert(ctx, entity, chunk));
377
344
  if (idsReliable) {
378
345
  for (let position = 0; position < indexes.length; position++) {
379
346
  rows[indexes[position]][idKey] ??= ids[position];
@@ -384,31 +351,9 @@ export class AbstractSqlQuerier extends AbstractQuerier {
384
351
  await this.insertRelations(entity, rows);
385
352
  }
386
353
  async internalUpdateMany(entity, q, payload, opts) {
387
- payload = clone(payload);
388
- // Settled first for the reason `internalDeleteMany` settles: `ORDER BY`/`LIMIT` on an UPDATE is
389
- // MySQL's alone, so a paged update has to name the rows it picked.
390
- let target = q;
391
- if (isPagedQuery(q)) {
392
- const ids = await this.settleIds(entity, q, opts);
393
- if (!ids.length) {
394
- return 0;
395
- }
396
- target = { $where: whereIds(getMeta(entity), ids) };
397
- }
398
- const ctx = this.dialect.createContext();
399
- this.dialect.update(ctx, entity, target, payload, opts);
400
- const { changes = 0 } = await this.run(ctx.sql, ctx.values);
401
- await this.updateRelations(entity, target, payload, opts);
354
+ const { changes = 0 } = await this.exec((ctx) => this.dialect.update(ctx, entity, q, payload, opts));
402
355
  return changes;
403
356
  }
404
- /** The ids matching `q`, in `q`'s own order and page, so a write can name the rows it settled on. */
405
- async settleIds(entity, q, opts) {
406
- const meta = getMeta(entity);
407
- const ctx = this.dialect.createContext();
408
- this.dialect.find(ctx, entity, idOnlyQuery(meta, q), opts);
409
- const founds = await this.all(ctx.sql, ctx.values);
410
- return founds.map((found) => idOf(meta, found));
411
- }
412
357
  async internalUpsertOne(entity, conflictPaths, payload) {
413
358
  return this.internalUpsertMany(entity, conflictPaths, [payload]);
414
359
  }
@@ -425,12 +370,8 @@ export class AbstractSqlQuerier extends AbstractQuerier {
425
370
  if (statements.length === 1) {
426
371
  return this.runUpsert(entity, conflictPaths, payload);
427
372
  }
428
- // `ON CONFLICT DO UPDATE SET` carries one assignment list for the whole statement, so rows of
429
- // different shapes cannot share one. Neither obvious single-statement form works: taking the
430
- // union writes the omitting row's `DEFAULT` (null) over a column it never mentioned, and
431
- // sampling one row drops every column that row happens to lack. One statement per shape is the
432
- // only form that writes exactly what each row asked for. Transactional because it is now more
433
- // than one statement; `transaction` is re-entrant, so this is free inside a caller's own.
373
+ // An upsert's assignment list is the statement's, so rows of different shapes go in statements
374
+ // of their own, together in a transaction (`transaction` is re-entrant).
434
375
  return this.transaction(async () => {
435
376
  let changes = 0;
436
377
  // Placed by index, since grouping by shape reorders the rows. A statement reporting fewer ids
@@ -453,10 +394,9 @@ export class AbstractSqlQuerier extends AbstractQuerier {
453
394
  const meta = getMeta(entity);
454
395
  // Asked first: the statement fills an `onInsert` key into these rows whether it inserts them or not.
455
396
  const unnamed = meta.ids.length === 1 && payload.some((row) => !namesKey(meta, row));
456
- const ctx = this.dialect.createContext();
457
- this.dialect.upsert(ctx, entity, conflictPaths, payload);
458
- const result = await this.run(ctx.sql, ctx.values);
459
- const ordered = payload.length === 1 || (this.dialect.insertIdSource === 'returning' && this.dialect.upsertReturningOrdered);
397
+ const result = await this.exec((ctx) => this.dialect.upsert(ctx, entity, conflictPaths, payload));
398
+ const ordered = payload.length === 1 ||
399
+ (this.dialect.insertIdSource === 'returning' && this.dialect.features.orderedUpsertReturning);
460
400
  if (ordered && result.ids?.length === payload.length) {
461
401
  return result;
462
402
  }
@@ -470,29 +410,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
470
410
  : { changes, created };
471
411
  }
472
412
  async internalDeleteMany(entity, q, opts) {
473
- const meta = getMeta(entity);
474
- // Resolving the ids first is what makes the two hard cases work at all: a cascade needs its
475
- // parents' ids to find their children, and no engine but MySQL accepts `ORDER BY`/`LIMIT` on a
476
- // DELETE, so a paged delete has to name the rows it settled on. A plain predicate needs neither,
477
- // and there the round trip buys nothing: the statement can say what the caller already said.
478
- if (!isPagedQuery(q) && !cascadesOnDelete(meta)) {
479
- const ctx = this.dialect.createContext();
480
- this.dialect.delete(ctx, entity, q, opts);
481
- const { changes = 0 } = await this.run(ctx.sql, ctx.values);
482
- return changes;
483
- }
484
- // A hard delete also targets already-soft-deleted rows, so drop the soft-delete filter when finding ids.
485
- const findOpts = opts?.hardDelete ? { ...opts, filters: withoutSoftDeleteFilter(opts.filters) } : opts;
486
- const ids = await this.settleIds(entity, q, findOpts);
487
- if (!ids.length) {
488
- return 0;
489
- }
490
- // Children first: they hold the foreign key, so deleting the parent ahead of them is rejected
491
- // outright by any schema that declares the constraint without `ON DELETE CASCADE`.
492
- await this.deleteRelations(entity, ids, opts);
493
- const deleteCtx = this.dialect.createContext();
494
- this.dialect.delete(deleteCtx, entity, { $where: whereIds(meta, ids) }, opts);
495
- const { changes = 0 } = await this.run(deleteCtx.sql, deleteCtx.values);
413
+ const { changes = 0 } = await this.exec((ctx) => this.dialect.delete(ctx, entity, q, opts));
496
414
  return changes;
497
415
  }
498
416
  get hasOpenTransaction() {
@@ -1,11 +1,3 @@
1
- /**
2
- * Canonical Type System
3
- *
4
- * Provides bidirectional mapping between:
5
- * - SQL types (dialect-specific)
6
- * - Canonical types (dialect-agnostic)
7
- * - TypeScript types (for entity generation)
8
- */
9
1
  import type { AbstractDialect } from '../dialect/abstractDialect.js';
10
2
  import type { VectorCast } from '../dialect/vectorCast.js';
11
3
  import type { ColumnType, FieldOptions } from '../type/entity.js';
@@ -37,25 +29,15 @@ export declare function canonicalToSql(type: CanonicalType, dialect: AbstractDia
37
29
  */
38
30
  export declare function canonicalToTypeScript(type: CanonicalType): string;
39
31
  /**
40
- * A type as `dialect` would actually store it: rendered to that engine's SQL and read back.
41
- *
42
- * Several canonical types share one storage type per engine - a `boolean` is `TINYINT(1)` on MySQL and
43
- * `INTEGER` on SQLite - and only the engine settles an unstated bound, since `VARCHAR` is 255 on MySQL
44
- * and `TEXT` on Postgres. Both paths that diff a schema compare through this, so a migration and a
45
- * drift report cannot disagree about what changed.
32
+ * A type as `dialect` stores it, rendered and read back: several types share one storage type, and only
33
+ * the engine settles an unstated bound. Migrations and drift both compare through it.
46
34
  */
47
35
  export declare function engineType(dialect: AbstractDialect): (type: CanonicalType) => CanonicalType;
48
36
  /**
49
37
  * Convert UQL FieldOptions to a canonical type.
50
38
  */
51
39
  export declare function fieldOptionsToCanonical(options: FieldOptions): CanonicalType;
52
- /**
53
- * Compare two canonical types for equality. Used for schema diffing.
54
- *
55
- * Nothing here guesses an engine's own default for an unstated bound: `VARCHAR` is 255 on MySQL and
56
- * `TEXT` on Postgres, and a comparison that assumed either was blind to that difference on the other.
57
- * `DiffOptions.normalizeType` is what settles it, by putting both sides through the engine first.
58
- */
40
+ /** Whether two canonical types are equal, guessing no engine default: `DiffOptions.normalizeType` settles those. */
59
41
  export declare function areTypesEqual(a: CanonicalType, b: CanonicalType): boolean;
60
42
  /**
61
43
  * Whether changing a column from one type to the other can lose what it holds.
@@ -1,11 +1,4 @@
1
- /**
2
- * Canonical Type System
3
- *
4
- * Provides bidirectional mapping between:
5
- * - SQL types (dialect-specific)
6
- * - Canonical types (dialect-agnostic)
7
- * - TypeScript types (for entity generation)
8
- */
1
+ // Canonical types, between an engine's SQL types and TypeScript's.
9
2
  import { columnFamily, isIntegerColumn } from '../util/field.util.js';
10
3
  /** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
11
4
  export function isVectorCategory(category) {
@@ -92,7 +85,7 @@ const SQL_TO_CANONICAL = {
92
85
  };
93
86
  /**
94
87
  * pgvector is the only engine with three vector column types, so every other engine maps all three
95
- * canonical categories onto the single type it does have (see `hasNarrowVectorTypes` on the dialect,
88
+ * canonical categories onto the single type it does have (see `SqlDialectFeatures.narrowVectorTypes`,
96
89
  * the dialect-side half of the same fact).
97
90
  */
98
91
  function withVectorType(scalars, vector) {
@@ -242,59 +235,31 @@ const CANONICAL_TO_TS = {
242
235
  */
243
236
  export function sqlToCanonical(sqlType) {
244
237
  const normalized = sqlType.toLowerCase().trim();
245
- // Check for UNSIGNED modifier before extracting base type
246
- const hasUnsigned = normalized.includes('unsigned');
238
+ const unsigned = normalized.includes('unsigned');
247
239
  const withoutUnsigned = normalized.replace(/\s*unsigned\s*/i, ' ').trim();
248
240
  // Extract base type and parameters: "VARCHAR(255)" -> ["varchar", "255"]
249
241
  const match = withoutUnsigned.match(/^([a-z][a-z0-9 ]*?)(?:\(([^)]+)\))?$/);
250
- if (!match) {
242
+ const base = match ? SQL_TO_CANONICAL[match[1]] : undefined;
243
+ if (!match || !base) {
251
244
  return { category: 'string', raw: sqlType };
252
245
  }
253
- const [, baseType, params] = match;
254
- const base = SQL_TO_CANONICAL[baseType];
255
- if (!base) {
256
- // Unknown type - pass through as raw
257
- return { category: 'string', raw: sqlType };
258
- }
259
- const result = {
246
+ const params = match[2]?.split(',').map((param) => param.trim()) ?? [];
247
+ const [first, second] = params
248
+ .map((param) => Number.parseInt(param, 10))
249
+ .map((n) => (Number.isNaN(n) ? undefined : n));
250
+ // A length for a string or a blob, the dimensions for a vector: `VARCHAR(255)`, `VECTOR(1536)`.
251
+ const measured = base.category === 'string' || base.category === 'blob' || isVectorCategory(base.category);
252
+ const decimal = base.category === 'decimal';
253
+ return {
260
254
  category: base.category,
261
- size: base.size,
255
+ // SQL Server's unbounded `(MAX)` is what it creates for a `TEXT`.
256
+ size: measured && params[0] === 'max' ? 'small' : base.size,
262
257
  withTimezone: base.withTimezone,
258
+ length: measured ? first : undefined,
259
+ precision: decimal ? first : undefined,
260
+ scale: decimal ? second : undefined,
261
+ unsigned: unsigned || undefined,
263
262
  };
264
- // Parse parameters
265
- if (params) {
266
- const paramParts = params.split(',').map((p) => p.trim());
267
- if (result.category === 'string' || result.category === 'blob') {
268
- // VARCHAR(255) -> length
269
- const length = Number.parseInt(paramParts[0], 10);
270
- if (!Number.isNaN(length)) {
271
- result.length = length;
272
- }
273
- }
274
- else if (result.category === 'decimal') {
275
- // DECIMAL(10,2) -> precision, scale
276
- const precision = Number.parseInt(paramParts[0], 10);
277
- const scale = paramParts[1] ? Number.parseInt(paramParts[1], 10) : undefined;
278
- if (!Number.isNaN(precision)) {
279
- result.precision = precision;
280
- }
281
- if (scale !== undefined && !Number.isNaN(scale)) {
282
- result.scale = scale;
283
- }
284
- }
285
- else if (isVectorCategory(result.category)) {
286
- // VECTOR(1536), HALFVEC(1536), SPARSEVEC(4000) -> length (dimensions)
287
- const dimensions = Number.parseInt(paramParts[0], 10);
288
- if (!Number.isNaN(dimensions)) {
289
- result.length = dimensions;
290
- }
291
- }
292
- }
293
- // Check for UNSIGNED modifier
294
- if (hasUnsigned) {
295
- result.unsigned = true;
296
- }
297
- return result;
298
263
  }
299
264
  /**
300
265
  * The canonical type for a column an engine reported, merging the metadata columns it reports beside
@@ -363,12 +328,8 @@ export function canonicalToTypeScript(type) {
363
328
  return CANONICAL_TO_TS[type.category];
364
329
  }
365
330
  /**
366
- * A type as `dialect` would actually store it: rendered to that engine's SQL and read back.
367
- *
368
- * Several canonical types share one storage type per engine - a `boolean` is `TINYINT(1)` on MySQL and
369
- * `INTEGER` on SQLite - and only the engine settles an unstated bound, since `VARCHAR` is 255 on MySQL
370
- * and `TEXT` on Postgres. Both paths that diff a schema compare through this, so a migration and a
371
- * drift report cannot disagree about what changed.
331
+ * A type as `dialect` stores it, rendered and read back: several types share one storage type, and only
332
+ * the engine settles an unstated bound. Migrations and drift both compare through it.
372
333
  */
373
334
  export function engineType(dialect) {
374
335
  return (type) => sqlToCanonical(canonicalToSql(type, dialect));
@@ -407,13 +368,7 @@ export function fieldOptionsToCanonical(options) {
407
368
  return { category: 'string', length: options.length };
408
369
  }
409
370
  }
410
- /**
411
- * Compare two canonical types for equality. Used for schema diffing.
412
- *
413
- * Nothing here guesses an engine's own default for an unstated bound: `VARCHAR` is 255 on MySQL and
414
- * `TEXT` on Postgres, and a comparison that assumed either was blind to that difference on the other.
415
- * `DiffOptions.normalizeType` is what settles it, by putting both sides through the engine first.
416
- */
371
+ /** Whether two canonical types are equal, guessing no engine default: `DiffOptions.normalizeType` settles those. */
417
372
  export function areTypesEqual(a, b) {
418
373
  return (a.category === b.category &&
419
374
  a.size === b.size &&
@@ -4,15 +4,9 @@
4
4
  */
5
5
  export type DependenciesOf<N> = (node: N) => Iterable<N>;
6
6
  /**
7
- * Nodes in creation order, dependencies first. Cycle-tolerant by design: a cyclic foreign key is
8
- * legal SQL, handled by deferring the constraint, so a cycle orders arbitrarily rather than
9
- * throwing. Use {@link findCycles} to report one.
7
+ * Nodes in creation order, dependencies first. Cycle-tolerant: a cyclic foreign key is legal SQL,
8
+ * handled by deferring the constraint, so a cycle orders arbitrarily rather than throwing.
10
9
  */
11
10
  export declare function createOrder<N>(nodes: Iterable<N>, dependenciesOf: DependenciesOf<N>): N[];
12
11
  /** Nodes in drop order, dependents first. */
13
12
  export declare function dropOrder<N>(nodes: Iterable<N>, dependenciesOf: DependenciesOf<N>): N[];
14
- /**
15
- * Every dependency cycle, each starting where it closes. A separate walk from {@link createOrder}
16
- * rather than a flag on it: ordering must succeed on any graph, reporting must see every cycle.
17
- */
18
- export declare function findCycles<N>(nodes: Iterable<N>, dependenciesOf: DependenciesOf<N>): N[][];
@@ -3,9 +3,8 @@
3
3
  * describing its edges rather than by adding a branch here.
4
4
  */
5
5
  /**
6
- * Nodes in creation order, dependencies first. Cycle-tolerant by design: a cyclic foreign key is
7
- * legal SQL, handled by deferring the constraint, so a cycle orders arbitrarily rather than
8
- * throwing. Use {@link findCycles} to report one.
6
+ * Nodes in creation order, dependencies first. Cycle-tolerant: a cyclic foreign key is legal SQL,
7
+ * handled by deferring the constraint, so a cycle orders arbitrarily rather than throwing.
9
8
  */
10
9
  export function createOrder(nodes, dependenciesOf) {
11
10
  const ordered = [];
@@ -29,32 +28,3 @@ export function createOrder(nodes, dependenciesOf) {
29
28
  export function dropOrder(nodes, dependenciesOf) {
30
29
  return createOrder(nodes, dependenciesOf).reverse();
31
30
  }
32
- /**
33
- * Every dependency cycle, each starting where it closes. A separate walk from {@link createOrder}
34
- * rather than a flag on it: ordering must succeed on any graph, reporting must see every cycle.
35
- */
36
- export function findCycles(nodes, dependenciesOf) {
37
- const cycles = [];
38
- const visited = new Set();
39
- const onPath = new Set();
40
- const visit = (node, path) => {
41
- // A node on the walk was handed down in `path` too, so the cycle closes where it first appears.
42
- if (onPath.has(node)) {
43
- cycles.push(path.slice(path.indexOf(node)));
44
- return;
45
- }
46
- if (visited.has(node)) {
47
- return;
48
- }
49
- visited.add(node);
50
- onPath.add(node);
51
- for (const dependency of dependenciesOf(node)) {
52
- visit(dependency, [...path, node]);
53
- }
54
- onPath.delete(node);
55
- };
56
- for (const node of nodes) {
57
- visit(node, []);
58
- }
59
- return cycles;
60
- }
@@ -1,26 +1,2 @@
1
- /**
2
- * Schema AST Module
3
- *
4
- * Provides a unified graph representation of database schema for:
5
- * - Schema diffing and migration generation
6
- * - Entity code generation from database
7
- * - Drift detection
8
- * - Smart relation inference
9
- */
10
- import type { SchemaIntrospector } from '../type/migration.js';
11
- import type { SchemaAST } from './schemaAST.js';
12
- export { areTypesEqual, canonicalToColumnType, canonicalToSql, canonicalToTypeScript, fieldOptionsToCanonical, isBreakingTypeChange, sqlToCanonical, } from './canonicalType.js';
13
- /**
14
- * Introspect the database and build a SchemaAST from it.
15
- *
16
- * @param introspector - The schema introspector to use
17
- * @returns The SchemaAST representing the database schema
18
- */
19
- export declare function introspectSchema(introspector: SchemaIntrospector): Promise<SchemaAST>;
20
- export { createOrder, dropOrder, findCycles, type DependenciesOf } from './dependencyGraph.js';
21
1
  export { SchemaAST } from './schemaAST.js';
22
- export type { BuildSchemaASTOptions } from './schemaASTBuilder.js';
23
- export { buildSchemaAST } from './schemaASTBuilder.js';
24
- export type { DiffOptions } from './schemaASTDiffer.js';
25
- export { diffSchemas } from './schemaASTDiffer.js';
26
- export type { CanonicalType, ColumnDiff, ColumnNode, Drift, DriftReport, DriftSeverity, DriftStatus, DriftType, ForeignKeyAction, IndexDiff, IndexNode, IndexSource, IndexSyncStatus, IndexType, RelationshipDiff, RelationshipNode, RelationshipSource, RelationshipType, SchemaAST as ISchemaAST, SchemaDiffResult, SizeVariant, TableDiff, TableNode, TypeCategory, ValidationError, ValidationErrorType, } from './types.js';
2
+ export type { Drift, DriftReport } from './types.js';
@@ -1,27 +1 @@
1
- /**
2
- * Schema AST Module
3
- *
4
- * Provides a unified graph representation of database schema for:
5
- * - Schema diffing and migration generation
6
- * - Entity code generation from database
7
- * - Drift detection
8
- * - Smart relation inference
9
- */
10
- // Canonical type utilities
11
- export { areTypesEqual, canonicalToColumnType, canonicalToSql, canonicalToTypeScript, fieldOptionsToCanonical, isBreakingTypeChange, sqlToCanonical, } from './canonicalType.js';
12
- /**
13
- * Introspect the database and build a SchemaAST from it.
14
- *
15
- * @param introspector - The schema introspector to use
16
- * @returns The SchemaAST representing the database schema
17
- */
18
- export async function introspectSchema(introspector) {
19
- return introspector.introspect();
20
- }
21
- // SchemaAST class
22
- export { createOrder, dropOrder, findCycles } from './dependencyGraph.js';
23
1
  export { SchemaAST } from './schemaAST.js';
24
- // Builder
25
- export { buildSchemaAST } from './schemaASTBuilder.js';
26
- // Differ
27
- export { diffSchemas } from './schemaASTDiffer.js';
@@ -1,10 +1,3 @@
1
1
  import type { ColumnNode, IndexNode } from './types.js';
2
- /**
3
- * The table columns an index resolves to, in order.
4
- *
5
- * Derived rather than stored: an index is defined by its entries, and a second field repeating them
6
- * as columns is a second thing to keep in step. It went out of step - one introspector rebuilt the
7
- * entries from the columns it had just built from the entries, and every fixture had to write both.
8
- * An expression entry resolves to no column at all, which is why the two were never the same list.
9
- */
2
+ /** The table columns an index's entries resolve to, in order; an expression entry resolves to none. */
10
3
  export declare function indexColumns(index: IndexNode): ColumnNode[];
@@ -1,11 +1,4 @@
1
- /**
2
- * The table columns an index resolves to, in order.
3
- *
4
- * Derived rather than stored: an index is defined by its entries, and a second field repeating them
5
- * as columns is a second thing to keep in step. It went out of step - one introspector rebuilt the
6
- * entries from the columns it had just built from the entries, and every fixture had to write both.
7
- * An expression entry resolves to no column at all, which is why the two were never the same list.
8
- */
1
+ /** The table columns an index's entries resolve to, in order; an expression entry resolves to none. */
9
2
  export function indexColumns(index) {
10
3
  return index.entries.flatMap((entry) => (entry.expression ? [] : (index.table.columns.get(entry.column) ?? [])));
11
4
  }