uql-orm 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +4 -4
  4. package/dist/bunSql/bunSql.util.d.ts +3 -14
  5. package/dist/bunSql/bunSql.util.js +33 -56
  6. package/dist/bunSql/bunSqlQuerier.d.ts +3 -6
  7. package/dist/bunSql/bunSqlQuerier.js +7 -13
  8. package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -5
  9. package/dist/bunSql/bunSqlQuerierPool.js +25 -10
  10. package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -3
  11. package/dist/cockroachdb/crdbQuerierPool.js +0 -4
  12. package/dist/cockroachdb/index.d.ts +0 -1
  13. package/dist/cockroachdb/index.js +0 -1
  14. package/dist/d1/d1SqliteDialect.d.ts +5 -0
  15. package/dist/d1/d1SqliteDialect.js +7 -0
  16. package/dist/dialect/abstractSqlDialect.d.ts +9 -9
  17. package/dist/dialect/abstractSqlDialect.js +38 -51
  18. package/dist/dialect/hydrateColumn.js +2 -2
  19. package/dist/dialect/mergeSqlDialect.d.ts +2 -2
  20. package/dist/dialect/mergeSqlDialect.js +0 -4
  21. package/dist/dialect/mysqlLikeSqlDialect.js +2 -3
  22. package/dist/dialect/pgLikeSqlDialect.d.ts +5 -0
  23. package/dist/dialect/pgLikeSqlDialect.js +14 -4
  24. package/dist/dialect/queryJoins.js +2 -4
  25. package/dist/entity/index.d.ts +1 -1
  26. package/dist/entity/index.js +1 -1
  27. package/dist/entity/metadata/definition.d.ts +5 -1
  28. package/dist/entity/metadata/definition.js +32 -26
  29. package/dist/libsql/index.d.ts +0 -1
  30. package/dist/libsql/index.js +0 -1
  31. package/dist/libsql/libsqlQuerierPool.d.ts +3 -5
  32. package/dist/libsql/libsqlQuerierPool.js +2 -5
  33. package/dist/maria/mariadbQuerier.d.ts +0 -3
  34. package/dist/maria/mariadbQuerier.js +6 -7
  35. package/dist/maria/mariadbQuerierPool.js +4 -7
  36. package/dist/migrate/builder/splitSqlStatements.js +2 -2
  37. package/dist/migrate/cli.js +2 -9
  38. package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
  39. package/dist/migrate/ddl/index.d.ts +3 -3
  40. package/dist/migrate/ddl/index.js +3 -3
  41. package/dist/migrate/ddl/pgIndexDdl.d.ts +5 -0
  42. package/dist/migrate/ddl/pgIndexDdl.js +9 -0
  43. package/dist/migrate/drift/driftDetector.js +21 -8
  44. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
  45. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +1 -1
  46. package/dist/migrate/introspection/baseSqlIntrospector.js +65 -76
  47. package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
  48. package/dist/migrate/introspection/mysqlIntrospector.js +5 -8
  49. package/dist/migrate/introspection/sqliteIntrospector.js +2 -5
  50. package/dist/migrate/migrator.d.ts +5 -2
  51. package/dist/migrate/migrator.js +9 -14
  52. package/dist/migrate/schemaGenerator.js +2 -6
  53. package/dist/mongo/index.d.ts +0 -1
  54. package/dist/mongo/index.js +0 -1
  55. package/dist/mongo/mongoDialect.d.ts +1 -2
  56. package/dist/mongo/mongoDialect.js +7 -21
  57. package/dist/mongo/mongodbQuerier.js +12 -11
  58. package/dist/mongo/mongodbQuerierPool.d.ts +2 -2
  59. package/dist/mongo/mongodbQuerierPool.js +2 -2
  60. package/dist/mssql/mssqlDialect.d.ts +3 -5
  61. package/dist/mssql/mssqlDialect.js +9 -8
  62. package/dist/mssql/mssqlQuerier.d.ts +13 -6
  63. package/dist/mssql/mssqlQuerier.js +30 -75
  64. package/dist/mssql/mssqlQuerierPool.js +2 -0
  65. package/dist/mssql/mssqlWireTypes.d.ts +3 -4
  66. package/dist/mssql/mssqlWireTypes.js +5 -8
  67. package/dist/mysql/index.d.ts +0 -1
  68. package/dist/mysql/index.js +0 -1
  69. package/dist/mysql/mysql2Querier.d.ts +1 -4
  70. package/dist/mysql/mysql2Querier.js +0 -3
  71. package/dist/mysql/mysql2QuerierPool.d.ts +2 -2
  72. package/dist/mysql/mysql2QuerierPool.js +5 -3
  73. package/dist/neon/index.d.ts +0 -2
  74. package/dist/neon/index.js +0 -2
  75. package/dist/neon/neonQuerierPool.d.ts +2 -4
  76. package/dist/neon/neonQuerierPool.js +2 -6
  77. package/dist/pglite/index.d.ts +0 -1
  78. package/dist/pglite/index.js +0 -1
  79. package/dist/pglite/pgliteQuerier.d.ts +3 -3
  80. package/dist/pglite/pgliteQuerier.js +1 -1
  81. package/dist/pglite/pgliteQuerierPool.d.ts +7 -2
  82. package/dist/pglite/pgliteQuerierPool.js +16 -6
  83. package/dist/postgres/abstractPgQuerierPool.d.ts +8 -12
  84. package/dist/postgres/abstractPgQuerierPool.js +8 -6
  85. package/dist/postgres/index.d.ts +0 -1
  86. package/dist/postgres/index.js +0 -1
  87. package/dist/postgres/pgNumericTypes.d.ts +5 -5
  88. package/dist/postgres/pgNumericTypes.js +11 -7
  89. package/dist/postgres/pgQuerier.d.ts +22 -4
  90. package/dist/postgres/pgQuerier.js +29 -2
  91. package/dist/postgres/pgQuerierPool.d.ts +2 -4
  92. package/dist/postgres/pgQuerierPool.js +2 -6
  93. package/dist/postgres/postgresDialect.d.ts +5 -5
  94. package/dist/postgres/postgresDialect.js +5 -5
  95. package/dist/postgres/postgresWireDriverCapabilities.d.ts +2 -2
  96. package/dist/postgres/postgresWireDriverCapabilities.js +2 -2
  97. package/dist/querier/abstractPoolQuerier.d.ts +1 -1
  98. package/dist/querier/abstractPoolQuerier.js +1 -1
  99. package/dist/querier/abstractQuerier.js +17 -22
  100. package/dist/querier/abstractSqlQuerier.d.ts +21 -12
  101. package/dist/querier/abstractSqlQuerier.js +61 -43
  102. package/dist/schema/canonicalType.js +0 -2
  103. package/dist/schema/dependencyGraph.js +2 -4
  104. package/dist/schema/indexDifferences.js +2 -2
  105. package/dist/schema/schemaASTBuilder.js +3 -10
  106. package/dist/schema/schemaASTDiffer.d.ts +10 -2
  107. package/dist/schema/schemaASTDiffer.js +12 -11
  108. package/dist/schema/types.d.ts +1 -1
  109. package/dist/sqlite/hranaQuerier.d.ts +6 -8
  110. package/dist/sqlite/hranaQuerier.js +13 -30
  111. package/dist/sqlite/hranaQuerierPool.d.ts +3 -4
  112. package/dist/sqlite/hranaQuerierPool.js +2 -1
  113. package/dist/sqlite/localSqliteQuerierPool.d.ts +3 -3
  114. package/dist/sqlite/localSqliteQuerierPool.js +4 -2
  115. package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -1
  116. package/dist/sqlite/nodeSqliteQuerierPool.js +0 -2
  117. package/dist/sqlite/sqliteDialect.js +2 -1
  118. package/dist/sqlite/sqlitePragmas.d.ts +12 -0
  119. package/dist/sqlite/sqlitePragmas.js +15 -0
  120. package/dist/sqlite/sqliteQuerierPool.d.ts +0 -5
  121. package/dist/sqlite/sqliteQuerierPool.js +2 -13
  122. package/dist/turso/index.d.ts +0 -1
  123. package/dist/turso/index.js +0 -1
  124. package/dist/turso/tursoLocalQuerier.d.ts +0 -1
  125. package/dist/turso/tursoLocalQuerierPool.js +2 -2
  126. package/dist/turso/tursoQuerierPool.d.ts +1 -3
  127. package/dist/turso/tursoQuerierPool.js +0 -4
  128. package/dist/type/dialect.d.ts +1 -1
  129. package/dist/util/dialect.util.d.ts +8 -2
  130. package/dist/util/dialect.util.js +20 -1
  131. package/dist/util/logger.d.ts +17 -9
  132. package/dist/util/logger.js +36 -11
  133. package/dist/util/object.util.d.ts +2 -0
  134. package/dist/util/object.util.js +4 -0
  135. package/dist/util/raw.js +3 -3
  136. package/dist/util/relationQuery.util.d.ts +3 -3
  137. package/dist/util/relationQuery.util.js +8 -2
  138. package/dist/util/sqlLiteral.js +3 -8
  139. package/dist/util/string.util.js +2 -6
  140. package/dist/util/wideNumber.d.ts +14 -0
  141. package/dist/util/wideNumber.js +24 -0
  142. package/package.json +1 -1
  143. package/dist/cockroachdb/crdbQuerier.d.ts +0 -8
  144. package/dist/cockroachdb/crdbQuerier.js +0 -6
  145. package/dist/libsql/libsqlQuerier.d.ts +0 -10
  146. package/dist/libsql/libsqlQuerier.js +0 -10
  147. package/dist/mongo/mongodbNativeDialect.d.ts +0 -9
  148. package/dist/mongo/mongodbNativeDialect.js +0 -9
  149. package/dist/mysql/mysql2Dialect.d.ts +0 -9
  150. package/dist/mysql/mysql2Dialect.js +0 -9
  151. package/dist/neon/neonDialect.d.ts +0 -10
  152. package/dist/neon/neonDialect.js +0 -10
  153. package/dist/neon/neonQuerier.d.ts +0 -5
  154. package/dist/neon/neonQuerier.js +0 -3
  155. package/dist/pglite/pgliteDialect.d.ts +0 -14
  156. package/dist/pglite/pgliteDialect.js +0 -14
  157. package/dist/postgres/abstractPgQuerier.d.ts +0 -24
  158. package/dist/postgres/abstractPgQuerier.js +0 -32
  159. package/dist/postgres/pgDialect.d.ts +0 -10
  160. package/dist/postgres/pgDialect.js +0 -10
  161. package/dist/turso/tursoQuerier.d.ts +0 -10
  162. package/dist/turso/tursoQuerier.js +0 -10
@@ -2,7 +2,7 @@ import { ObjectId } from 'mongodb';
2
2
  import { AbstractDialect } from '../dialect/abstractDialect.js';
3
3
  import { COUNT_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, sortCountField } from '../dialect/aliases.js';
4
4
  import { resolveQueryJoins, resolveSortableJoin } from '../dialect/queryJoins.js';
5
- import { assertSoleId, getMeta, soleIdOf } from '../entity/index.js';
5
+ import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
6
6
  import { QueryRaw } from '../type/queryRaw.js';
7
7
  import { asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, entityName, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isJsonUpdateOp, isOperatorMap, isOperatorObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationSize, parseSortByCount, someKey, targetKeyColumns, } from '../util/index.js';
8
8
  /** Default {@link DialectFeatures} for MongoDB; shared by {@link MongoDialect} and its schema generator. */
@@ -39,14 +39,6 @@ export class MongoDialect extends AbstractDialect {
39
39
  static ID_KEY = '_id';
40
40
  /** Atlas rejects a `$vectorSearch` asking for more candidates than this. */
41
41
  static MAX_NUM_CANDIDATES = 10_000;
42
- // Direct field aggregates → MongoDB accumulator. `$count` is handled separately (COUNT(*) vs
43
- // COUNT(field) differ), so it is not listed here.
44
- static AGGREGATE_OP_MAP = new Map([
45
- ['$sum', '$sum'],
46
- ['$avg', '$avg'],
47
- ['$min', '$min'],
48
- ['$max', '$max'],
49
- ]);
50
42
  /**
51
43
  * MongoDB stores the primary key as `_id`; everything else resolves as usual. Projections, sorts,
52
44
  * `$group` refs and `$where` keys all map through here, so no read path can address a property name
@@ -511,7 +503,7 @@ export class MongoDialect extends AbstractDialect {
511
503
  */
512
504
  aliasSort(sort) {
513
505
  const normalized = {};
514
- for (const [alias, dir] of Object.entries(sort ?? {})) {
506
+ for (const [alias, dir] of Object.entries(sort)) {
515
507
  normalized[alias] = sortDirection(dir);
516
508
  }
517
509
  return normalized;
@@ -730,7 +722,7 @@ export class MongoDialect extends AbstractDialect {
730
722
  #renamedColumns = new WeakMap();
731
723
  #referenceKeys = new WeakMap();
732
724
  normalizeIds(meta, docs) {
733
- return docs?.map((doc) => this.normalizeId(meta, doc));
725
+ return docs.map((doc) => this.normalizeId(meta, doc));
734
726
  }
735
727
  /** `doc` is the wire shape - `_id`, stored names, `ObjectId`s - and what comes back is the code's. */
736
728
  normalizeId(meta, doc) {
@@ -763,10 +755,7 @@ export class MongoDialect extends AbstractDialect {
763
755
  }
764
756
  const relKeys = getKeys(meta.relations).filter((key) => res[key]);
765
757
  for (const relKey of relKeys) {
766
- const relOpts = meta.relations[relKey];
767
- if (!relOpts)
768
- continue;
769
- const relMeta = getMeta(relOpts.entity());
758
+ const relMeta = getMeta(relationOf(meta, relKey).entity());
770
759
  res[relKey] = Array.isArray(res[relKey])
771
760
  ? this.normalizeIds(relMeta, res[relKey])
772
761
  : this.normalizeId(relMeta, res[relKey]);
@@ -902,7 +891,7 @@ export class MongoDialect extends AbstractDialect {
902
891
  const named = inserting && it[idKey] != null;
903
892
  if (inserting && !named) {
904
893
  // Nothing named the key, so the database is being asked to mint one.
905
- this.assertMintableKey(meta, meta.fields[idKey]);
894
+ this.assertMintableKey(meta, fieldOf(meta, idKey));
906
895
  }
907
896
  const doc = named ? { [MongoDialect.ID_KEY]: this.toWireId(it[idKey]) } : {};
908
897
  for (const key of filterFieldKeys(meta, it, callbackKey)) {
@@ -999,11 +988,8 @@ export class MongoDialect extends AbstractDialect {
999
988
  entry.fieldRef === '*' ? { $sum: 1 } : { $sum: { $cond: [{ $ne: [ref, null] }, 1, 0] } };
1000
989
  }
1001
990
  else {
1002
- const mongoOp = MongoDialect.AGGREGATE_OP_MAP.get(entry.op);
1003
- if (!mongoOp) {
1004
- throw TypeError(`unsupported aggregate operator: ${entry.op}`);
1005
- }
1006
- groupAccumulators[entry.alias] = { [mongoOp]: ref };
991
+ // `$sum`, `$avg`, `$min` and `$max` are MongoDB accumulators of the same name.
992
+ groupAccumulators[entry.alias] = { [entry.op]: ref };
1007
993
  }
1008
994
  }
1009
995
  return { groupId, groupAccumulators, distinctReducers };
@@ -1,6 +1,6 @@
1
1
  import { COUNT_ALIAS } from '../dialect/aliases.js';
2
2
  import { hasRequiredJoin } from '../dialect/queryJoins.js';
3
- import { getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
3
+ import { fieldOf, getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
4
4
  import { AbstractQuerier, enrichError } from '../querier/index.js';
5
5
  import { clone, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, idOnlyQuery, isPagedQuery, populatesRelations, queryChildrenOf, throwNoPendingTransaction, throwPendingTransaction, withoutSoftDeleteFilter, } from '../util/index.js';
6
6
  /**
@@ -63,7 +63,7 @@ export class MongodbQuerier extends AbstractQuerier {
63
63
  else {
64
64
  const cursor = this.buildFindCursor(entity, q, opts);
65
65
  documents = await this.execute(() => cursor.toArray());
66
- documents = this.dialect.normalizeIds(meta, documents) || [];
66
+ documents = this.dialect.normalizeIds(meta, documents);
67
67
  }
68
68
  }
69
69
  return documents;
@@ -92,7 +92,7 @@ export class MongodbQuerier extends AbstractQuerier {
92
92
  const pipelines = queries.map((it) => this.dialect.aggregationPipeline(entity, it));
93
93
  // Counted, not estimated: the leading branch's own length grows with every `$lookup` a populate
94
94
  // adds, so a fixed parent budget would let a richer query overflow at the server instead.
95
- const stages = (pipelines[0]?.length ?? 0) + pipelines.length - 1;
95
+ const stages = pipelines[0].length + pipelines.length - 1;
96
96
  return stages > MAX_PIPELINE_STAGES
97
97
  ? this.readEachInTurn(entity, queries)
98
98
  : this.readInOnePipeline(entity, q, pipelines);
@@ -139,7 +139,7 @@ export class MongodbQuerier extends AbstractQuerier {
139
139
  const cursor = this.buildFindCursor(entity, q, opts);
140
140
  try {
141
141
  for await (const doc of cursor) {
142
- const [normalized] = this.dialect.normalizeIds(meta, [doc]) || [doc];
142
+ const [normalized] = this.dialect.normalizeIds(meta, [doc]);
143
143
  yield normalized;
144
144
  }
145
145
  }
@@ -177,7 +177,7 @@ export class MongodbQuerier extends AbstractQuerier {
177
177
  /** Execute an aggregation pipeline and normalize `_id` → `id`. */
178
178
  async runPipeline(entity, meta, pipeline) {
179
179
  const documents = await this.execute((session) => this.collection(entity).aggregate(pipeline, { session }).toArray());
180
- return this.dialect.normalizeIds(meta, documents) || [];
180
+ return this.dialect.normalizeIds(meta, documents);
181
181
  }
182
182
  /**
183
183
  * Build an aggregation pipeline for vector similarity search.
@@ -257,7 +257,7 @@ export class MongodbQuerier extends AbstractQuerier {
257
257
  const founds = await this.execute((session) => this.collection(entity).aggregate(pipeline, { session }).toArray());
258
258
  // `normalizeIds` has already spread a compound `_id` back into its columns, so the settled rows
259
259
  // are named the same way every other driver names them.
260
- return (this.dialect.normalizeIds(meta, founds) || []).map((found) => idOf(meta, found));
260
+ return this.dialect.normalizeIds(meta, founds).map((found) => idOf(meta, found));
261
261
  }
262
262
  async internalInsertMany(entity, rows) {
263
263
  return this.timed('internalInsertMany', undefined, async () => {
@@ -356,7 +356,7 @@ export class MongodbQuerier extends AbstractQuerier {
356
356
  };
357
357
  });
358
358
  const res = await this.execute((session) => this.collection(entity).bulkWrite(operations, { session }));
359
- const changes = (res.upsertedCount ?? 0) + (res.modifiedCount ?? 0);
359
+ const changes = res.upsertedCount + res.modifiedCount;
360
360
  // `upsertedIds` names only the documents inserted, keyed by operation index, so each lands on
361
361
  // its own row; an updated document's `_id` is read back by the conflict fields instead.
362
362
  const reported = payload.map((_, index) => {
@@ -372,9 +372,9 @@ export class MongodbQuerier extends AbstractQuerier {
372
372
  return this.timed('internalDeleteMany', undefined, async () => {
373
373
  const meta = getMeta(entity);
374
374
  // Soft-delete (stamp) unless `hardDelete` is requested or the entity has no soft-delete field.
375
- const field = !opts.hardDelete && meta.softDelete ? meta.fields[meta.softDelete] : undefined;
375
+ const softDelete = opts.hardDelete ? undefined : meta.softDelete;
376
376
  // Hard delete targets matching rows regardless of soft-delete state (keeps other filters).
377
- const findOpts = field ? opts : { ...opts, filters: withoutSoftDeleteFilter(opts.filters) };
377
+ const findOpts = softDelete ? opts : { ...opts, filters: withoutSoftDeleteFilter(opts.filters) };
378
378
  // Delete has always resolved its ids first (it stamps or removes them by `_id`), so a relation
379
379
  // condition needs nothing extra here - and passing the whole query is what makes its page apply.
380
380
  const ids = await this.settleIds(entity, qm, findOpts);
@@ -382,10 +382,11 @@ export class MongodbQuerier extends AbstractQuerier {
382
382
  return 0;
383
383
  }
384
384
  let changes;
385
- if (field) {
385
+ if (softDelete) {
386
+ const field = fieldOf(meta, softDelete);
386
387
  // Stamp the mapped column: reads filter on it, so a `@Field({ name })` mismatch here would
387
388
  // report a successful delete and leave the row visible.
388
- const softDeleteColumn = this.dialect.resolveColumnName(meta.softDelete, field);
389
+ const softDeleteColumn = this.dialect.resolveColumnName(softDelete, field);
389
390
  const updateResult = await this.execute((session) => this.collection(entity).updateMany({ _id: { $in: this.dialect.toWireId(ids) } }, { $set: { [softDeleteColumn]: getSoftDeleteValue(field) } }, {
390
391
  session,
391
392
  }));
@@ -1,9 +1,9 @@
1
1
  import { type MongoClientOptions } from 'mongodb';
2
2
  import { AbstractQuerierPool } from '../querier/index.js';
3
3
  import type { ExtraOptions } from '../type/index.js';
4
- import { MongodbNativeDialect } from './mongodbNativeDialect.js';
4
+ import { MongoDialect } from './mongoDialect.js';
5
5
  import { MongodbQuerier } from './mongodbQuerier.js';
6
- export declare class MongodbQuerierPool extends AbstractQuerierPool<MongodbQuerier, MongodbNativeDialect> {
6
+ export declare class MongodbQuerierPool extends AbstractQuerierPool<MongodbQuerier, MongoDialect> {
7
7
  private readonly client;
8
8
  constructor(uri: string, opts?: MongoClientOptions, extra?: ExtraOptions);
9
9
  getQuerier(): Promise<MongodbQuerier>;
@@ -1,12 +1,12 @@
1
1
  import { MongoClient } from 'mongodb';
2
2
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
3
3
  import { AbstractQuerierPool } from '../querier/index.js';
4
- import { MongodbNativeDialect } from './mongodbNativeDialect.js';
4
+ import { MongoDialect } from './mongoDialect.js';
5
5
  import { MongodbQuerier } from './mongodbQuerier.js';
6
6
  export class MongodbQuerierPool extends AbstractQuerierPool {
7
7
  client;
8
8
  constructor(uri, opts, extra) {
9
- super(new MongodbNativeDialect(dialectOptionsFrom(extra)), extra);
9
+ super(new MongoDialect(dialectOptionsFrom(extra)), extra);
10
10
  this.client = new MongoClient(uri, opts);
11
11
  }
12
12
  async getQuerier() {
@@ -18,12 +18,10 @@ export declare class MsSqlDialect extends MergeSqlDialect {
18
18
  readonly commitTransactionCommand = "COMMIT TRANSACTION";
19
19
  readonly rollbackTransactionCommand = "ROLLBACK TRANSACTION";
20
20
  /**
21
- * The level rides the `BEGIN` rather than preceding it as its own statement: a `SET TRANSACTION
22
- * ISOLATION LEVEL` sent on its own would go to whichever pooled connection served it, not the one
23
- * the transaction opens on, and would then stick to that connection for unrelated later queries.
24
- * `MsSqlQuerier` reads the level back off this command and hands it to the driver.
21
+ * T-SQL has no inline form, so the level is set before the `BEGIN`. What a driver that sends these
22
+ * as statements would run; `MsSqlQuerier` opens its transactions through the driver instead.
25
23
  */
26
- readonly isolationLevelStrategy = "inline";
24
+ readonly isolationLevelStrategy = "set-before";
27
25
  readonly dropIndexSyntax = "on-table";
28
26
  readonly booleanLiteral = "integer";
29
27
  /** [The hard server limit](https://github.com/yiisoft/yii2/issues/10371), not a driver preference. */
@@ -52,12 +52,10 @@ export class MsSqlDialect extends MergeSqlDialect {
52
52
  commitTransactionCommand = 'COMMIT TRANSACTION';
53
53
  rollbackTransactionCommand = 'ROLLBACK TRANSACTION';
54
54
  /**
55
- * The level rides the `BEGIN` rather than preceding it as its own statement: a `SET TRANSACTION
56
- * ISOLATION LEVEL` sent on its own would go to whichever pooled connection served it, not the one
57
- * the transaction opens on, and would then stick to that connection for unrelated later queries.
58
- * `MsSqlQuerier` reads the level back off this command and hands it to the driver.
55
+ * T-SQL has no inline form, so the level is set before the `BEGIN`. What a driver that sends these
56
+ * as statements would run; `MsSqlQuerier` opens its transactions through the driver instead.
59
57
  */
60
- isolationLevelStrategy = 'inline';
58
+ isolationLevelStrategy = 'set-before';
61
59
  dropIndexSyntax = 'on-table';
62
60
  booleanLiteral = 'integer';
63
61
  /** [The hard server limit](https://github.com/yiisoft/yii2/issues/10371), not a driver preference. */
@@ -270,11 +268,14 @@ export class MsSqlDialect extends MergeSqlDialect {
270
268
  return typeof value === 'boolean' ? `CAST(${placeholder} AS BIT)` : placeholder;
271
269
  }
272
270
  /**
273
- * An object or array bound as JSON, which is the half both binders spell the same way, or
274
- * `undefined` for a scalar - where they diverge.
271
+ * An object or array bound as JSON, or a `raw()` rendered in place, which is the half both binders
272
+ * spell the same way; `undefined` for a scalar - where they diverge.
275
273
  */
276
274
  #jsonCompound(ctx, value) {
277
- if (value === null || typeof value !== 'object' || value instanceof QueryRaw) {
275
+ if (value instanceof QueryRaw) {
276
+ return this.rawFragment(ctx, value);
277
+ }
278
+ if (value === null || typeof value !== 'object') {
278
279
  return undefined;
279
280
  }
280
281
  ctx.pushValue(JSON.stringify(value));
@@ -1,7 +1,6 @@
1
1
  import type { ConnectionPool } from 'mssql';
2
2
  import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
3
- import type { ExtraOptions, QueryUpdateResult } from '../type/index.js';
4
- import type { MsSqlDialect } from './mssqlDialect.js';
3
+ import type { QueryUpdateResult, TransactionOptions } from '../type/index.js';
5
4
  /**
6
5
  * A connection is a `ConnectionPool` handle here rather than a checked-out socket: `mssql` owns its
7
6
  * own pool and hands out `Request`s, so what UQL holds is the pool plus, once a transaction opens,
@@ -9,15 +8,23 @@ import type { MsSqlDialect } from './mssqlDialect.js';
9
8
  */
10
9
  export declare class MsSqlQuerier extends AbstractPoolQuerier<ConnectionPool> {
11
10
  #private;
12
- constructor(connect: () => Promise<ConnectionPool>, dialect: MsSqlDialect, extra?: ExtraOptions);
13
11
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
14
12
  internalRun(query: string, values?: unknown[]): Promise<QueryUpdateResult>;
15
13
  /**
16
- * `tedious` streams by event, not by async iterator, so rows are handed over as they arrive rather
17
- * than collected first - buffering the whole result set would make this `all()` with extra steps.
18
- * The `query` promise is awaited at the end so its rejection surfaces rather than going unhandled.
14
+ * The driver's own stream over the request, which pauses the request while the loop is behind, so
15
+ * rows arrive only as fast as they are read. A failure the driver reports through the promise alone
16
+ * would leave the loop waiting for rows, so it ends the stream instead.
19
17
  */
20
18
  internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
19
+ /**
20
+ * `mssql` owns its pool and hands out a `Request` per call, so a `BEGIN TRANSACTION` sent as text
21
+ * would open one on a connection the next call may not be given. Its `Transaction` object is the
22
+ * only thing that pins them together, so the transaction is that object rather than statements, and
23
+ * the level goes to its `begin`: sent as a statement it would land on whichever connection served it.
24
+ */
25
+ protected internalBegin(opts?: TransactionOptions): Promise<void>;
26
+ protected internalCommit(): Promise<void>;
27
+ protected internalRollback(): Promise<void>;
21
28
  /** The pool owns the socket; releasing a querier only drops this one's claim on it. */
22
29
  protected releaseConn(_conn: ConnectionPool, _discard: boolean): Promise<void>;
23
30
  }
@@ -8,9 +8,6 @@ import { decodeWireTypes } from './mssqlWireTypes.js';
8
8
  */
9
9
  export class MsSqlQuerier extends AbstractPoolQuerier {
10
10
  #transaction;
11
- constructor(connect, dialect, extra) {
12
- super(dialect, connect, extra);
13
- }
14
11
  /**
15
12
  * Values bind by name, `@p1` upward, matching {@link MsSqlDialect.placeholder}. `tedious` infers
16
13
  * a type from the JS value, which is why a `Date` and a `Uint8Array` reach it unconverted - the
@@ -26,9 +23,6 @@ export class MsSqlQuerier extends AbstractPoolQuerier {
26
23
  return decodeWireTypes(res.recordset, res.recordset?.columns);
27
24
  }
28
25
  async internalRun(query, values) {
29
- if (await this.#driveTransaction(query)) {
30
- return { changes: 0 };
31
- }
32
26
  const res = (await this.#request(values).query(query));
33
27
  return this.buildUpdateResult({
34
28
  // `rowsAffected` carries one entry per statement, and a `MERGE` upsert emits its `OUTPUT`
@@ -38,57 +32,24 @@ export class MsSqlQuerier extends AbstractPoolQuerier {
38
32
  });
39
33
  }
40
34
  /**
41
- * `tedious` streams by event, not by async iterator, so rows are handed over as they arrive rather
42
- * than collected first - buffering the whole result set would make this `all()` with extra steps.
43
- * The `query` promise is awaited at the end so its rejection surfaces rather than going unhandled.
35
+ * The driver's own stream over the request, which pauses the request while the loop is behind, so
36
+ * rows arrive only as fast as they are read. A failure the driver reports through the promise alone
37
+ * would leave the loop waiting for rows, so it ends the stream instead.
44
38
  */
45
39
  async *internalStream(query, values) {
46
40
  const request = this.#request(values);
47
- request.stream = true;
48
- let pending = [];
49
- let done = false;
50
- let failure;
51
- let wake;
52
- const arrived = () => {
53
- wake?.();
54
- wake = undefined;
55
- };
56
- request.on('row', (row) => {
57
- pending.push(row);
58
- arrived();
59
- });
60
- request.on('error', (err) => {
61
- failure ??= err;
62
- arrived();
63
- });
64
- request.on('done', () => {
65
- done = true;
66
- arrived();
67
- });
41
+ const rows = request.toReadableStream();
68
42
  const completed = request.query(query).catch((err) => {
69
- failure ??= err instanceof Error ? err : new Error(String(err));
70
- arrived();
43
+ rows.destroy(err instanceof Error ? err : new Error(String(err)));
71
44
  });
72
45
  try {
73
- while (true) {
74
- if (pending.length) {
75
- const batch = pending;
76
- pending = [];
77
- yield* batch;
78
- continue;
79
- }
80
- if (failure)
81
- throw failure;
82
- if (done)
83
- return;
84
- await new Promise((resolve) => {
85
- wake = resolve;
86
- });
46
+ for await (const row of rows) {
47
+ yield row;
87
48
  }
88
49
  }
89
50
  finally {
90
- // `completed` is awaited rather than left floating so a late rejection is handled; its own
91
- // `catch` has already recorded it, and the loop above is what raises one to the caller.
51
+ // The cancel reports itself as an error on the stream, and the loop that would hear it is gone.
52
+ rows.on('error', () => { });
92
53
  request.cancel();
93
54
  await completed;
94
55
  }
@@ -96,24 +57,23 @@ export class MsSqlQuerier extends AbstractPoolQuerier {
96
57
  /**
97
58
  * `mssql` owns its pool and hands out a `Request` per call, so a `BEGIN TRANSACTION` sent as text
98
59
  * would open one on a connection the next call may not be given. Its `Transaction` object is the
99
- * only thing that pins them together, so the three commands the dialect names are driven through
100
- * it here instead of being sent. Compared against the dialect's own strings rather than literals,
101
- * so renaming one cannot silently turn it back into text.
60
+ * only thing that pins them together, so the transaction is that object rather than statements, and
61
+ * the level goes to its `begin`: sent as a statement it would land on whichever connection served it.
102
62
  */
103
- async #driveTransaction(query) {
104
- const { beginTransactionCommand, commitTransactionCommand, rollbackTransactionCommand } = this.dialect;
105
- if (query.startsWith(beginTransactionCommand)) {
106
- this.#transaction = this.getConn().transaction();
107
- await this.#transaction.begin(isolationLevelOf(query.slice(beginTransactionCommand.length)));
108
- return true;
109
- }
110
- if (query !== commitTransactionCommand && query !== rollbackTransactionCommand) {
111
- return false;
112
- }
63
+ async internalBegin(opts) {
64
+ const transaction = this.getConn().transaction();
65
+ await transaction.begin(opts?.isolationLevel && ISOLATION_LEVEL[ISOLATION[opts.isolationLevel]]);
66
+ this.#transaction = transaction;
67
+ }
68
+ async internalCommit() {
69
+ const transaction = this.#transaction;
70
+ this.#transaction = undefined;
71
+ await transaction?.commit();
72
+ }
73
+ async internalRollback() {
113
74
  const transaction = this.#transaction;
114
75
  this.#transaction = undefined;
115
- await (query === commitTransactionCommand ? transaction?.commit() : transaction?.rollback());
116
- return true;
76
+ await transaction?.rollback();
117
77
  }
118
78
  /** The pool owns the socket; releasing a querier only drops this one's claim on it. */
119
79
  async releaseConn(_conn, _discard) {
@@ -123,15 +83,10 @@ export class MsSqlQuerier extends AbstractPoolQuerier {
123
83
  await transaction?.rollback().catch(() => undefined);
124
84
  }
125
85
  }
126
- /**
127
- * The driver constant for the level the dialect spelled into its `BEGIN`, so it applies to the
128
- * connection the transaction actually opens on. Undefined leaves the server's own default.
129
- */
130
- function isolationLevelOf(suffix) {
131
- const level = suffix
132
- .replace(/^\s*ISOLATION LEVEL\s*/i, '')
133
- .trim()
134
- .toUpperCase()
135
- .replaceAll(' ', '_');
136
- return level ? ISOLATION_LEVEL[level] : undefined;
137
- }
86
+ /** The driver's constant for each level UQL names; total, so a new level is a compile error here. */
87
+ const ISOLATION = {
88
+ 'read uncommitted': 'READ_UNCOMMITTED',
89
+ 'read committed': 'READ_COMMITTED',
90
+ 'repeatable read': 'REPEATABLE_READ',
91
+ serializable: 'SERIALIZABLE',
92
+ };
@@ -1,6 +1,7 @@
1
1
  import { ConnectionPool } from 'mssql';
2
2
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
3
3
  import { AbstractSqlQuerierPool } from '../querier/index.js';
4
+ import { attachPoolErrorHandler } from '../util/index.js';
4
5
  import { MsSqlDialect } from './mssqlDialect.js';
5
6
  import { MsSqlQuerier } from './mssqlQuerier.js';
6
7
  export class MsSqlQuerierPool extends AbstractSqlQuerierPool {
@@ -9,6 +10,7 @@ export class MsSqlQuerierPool extends AbstractSqlQuerierPool {
9
10
  constructor(opts, extra) {
10
11
  super(new MsSqlDialect(dialectOptionsFrom(extra)), extra);
11
12
  this.pool = new ConnectionPool(opts);
13
+ attachPoolErrorHandler(this.pool, 'Idle SQL Server pool connection encountered an error', extra?.logger);
12
14
  }
13
15
  /**
14
16
  * `mssql` connects the pool as a whole rather than per checkout, so the promise is shared. A
@@ -14,10 +14,9 @@ type ColumnTypes = Record<string, {
14
14
  * reads, the ids an `OUTPUT` reports, raw SQL, counts, aggregates - where the ORM's own hydration
15
15
  * only ever sees entity reads.
16
16
  *
17
- * Exact to 2^53, which covers any generated id. A value past it keeps the string the driver gave,
18
- * because a number could no longer represent it: that is the one case where handing back the exact
19
- * text is more useful than handing back the type that was asked for. The lighter escape hatch for a
20
- * column that big is the declaration - `@Field({ type: String, columnType: 'bigint' })`.
17
+ * By the rule every driver here shares, `decodeWideNumber`: a number where one is exact, the driver's
18
+ * exact text past 2^53. The lighter escape hatch for a column that big is the declaration -
19
+ * `@Field({ type: String, columnType: 'bigint' })`.
21
20
  */
22
21
  export declare function decodeWireTypes<T>(rows: T[] | undefined, columns: ColumnTypes | undefined): T[];
23
22
  export {};
@@ -1,3 +1,4 @@
1
+ import { decodeWideNumber } from '../util/wideNumber.js';
1
2
  /**
2
3
  * Decode `BIGINT` as a JS number, leaving every other type to the driver.
3
4
  *
@@ -10,10 +11,9 @@
10
11
  * reads, the ids an `OUTPUT` reports, raw SQL, counts, aggregates - where the ORM's own hydration
11
12
  * only ever sees entity reads.
12
13
  *
13
- * Exact to 2^53, which covers any generated id. A value past it keeps the string the driver gave,
14
- * because a number could no longer represent it: that is the one case where handing back the exact
15
- * text is more useful than handing back the type that was asked for. The lighter escape hatch for a
16
- * column that big is the declaration - `@Field({ type: String, columnType: 'bigint' })`.
14
+ * By the rule every driver here shares, `decodeWideNumber`: a number where one is exact, the driver's
15
+ * exact text past 2^53. The lighter escape hatch for a column that big is the declaration -
16
+ * `@Field({ type: String, columnType: 'bigint' })`.
17
17
  */
18
18
  export function decodeWireTypes(rows, columns) {
19
19
  if (!rows?.length || !columns) {
@@ -29,10 +29,7 @@ export function decodeWireTypes(rows, columns) {
29
29
  for (const name of wide) {
30
30
  const value = decoded[name];
31
31
  if (typeof value === 'string') {
32
- const asNumber = Number(value);
33
- if (Number.isSafeInteger(asNumber)) {
34
- decoded[name] = asNumber;
35
- }
32
+ decoded[name] = decodeWideNumber(value);
36
33
  }
37
34
  }
38
35
  return decoded;
@@ -1,4 +1,3 @@
1
- export * from './mysql2Dialect.js';
2
1
  export * from './mysql2Querier.js';
3
2
  export * from './mysql2QuerierPool.js';
4
3
  export * from './mysqlDialect.js';
@@ -1,4 +1,3 @@
1
- export * from './mysql2Dialect.js';
2
1
  export * from './mysql2Querier.js';
3
2
  export * from './mysql2QuerierPool.js';
4
3
  export * from './mysqlDialect.js';
@@ -1,11 +1,8 @@
1
1
  import type { PoolConnection } from 'mysql2/promise';
2
2
  import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
3
- import type { ExtraOptions } from '../type/index.js';
4
- import type { MySqlDialect } from './mysqlDialect.js';
5
3
  export declare class MySql2Querier extends AbstractPoolQuerier<PoolConnection> {
6
- constructor(connect: () => Promise<PoolConnection>, dialect: MySqlDialect, extra?: ExtraOptions);
7
4
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
8
- internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
5
+ internalRun(query: string, values?: unknown[]): Promise<import("../index.js").QueryUpdateResult>;
9
6
  internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
10
7
  protected releaseConn(conn: PoolConnection, discard: boolean): Promise<void>;
11
8
  }
@@ -1,8 +1,5 @@
1
1
  import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
2
2
  export class MySql2Querier extends AbstractPoolQuerier {
3
- constructor(connect, dialect, extra) {
4
- super(dialect, connect, extra);
5
- }
6
3
  async internalAll(query, values) {
7
4
  const [res] = await this.getConn().query(query, values);
8
5
  return res;
@@ -1,9 +1,9 @@
1
1
  import { type Pool, type PoolOptions } from 'mysql2/promise';
2
2
  import { AbstractSqlQuerierPool } from '../querier/index.js';
3
3
  import type { ExtraOptions } from '../type/index.js';
4
- import { MySql2Dialect } from './mysql2Dialect.js';
5
4
  import { MySql2Querier } from './mysql2Querier.js';
6
- export declare class MySql2QuerierPool extends AbstractSqlQuerierPool<MySql2Querier, MySql2Dialect> {
5
+ import { MySqlDialect } from './mysqlDialect.js';
6
+ export declare class MySql2QuerierPool extends AbstractSqlQuerierPool<MySql2Querier, MySqlDialect> {
7
7
  readonly pool: Pool;
8
8
  constructor(opts: PoolOptions, extra?: ExtraOptions);
9
9
  getQuerier(): Promise<MySql2Querier>;
@@ -1,13 +1,15 @@
1
1
  import { createPool } from 'mysql2/promise';
2
2
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
3
3
  import { AbstractSqlQuerierPool } from '../querier/index.js';
4
- import { MySql2Dialect } from './mysql2Dialect.js';
5
4
  import { MySql2Querier } from './mysql2Querier.js';
5
+ import { MySqlDialect } from './mysqlDialect.js';
6
6
  export class MySql2QuerierPool extends AbstractSqlQuerierPool {
7
7
  pool;
8
8
  constructor(opts, extra) {
9
- super(new MySql2Dialect(dialectOptionsFrom(extra)), extra);
10
- this.pool = createPool(opts);
9
+ super(new MySqlDialect(dialectOptionsFrom(extra)), extra);
10
+ // A BIGINT past 2^53 as its exact text rather than a rounded number, the rule every driver here
11
+ // decodes by (`decodeWideNumber`); within that range it stays a number, and DECIMAL is untouched.
12
+ this.pool = createPool({ supportBigNumbers: true, ...opts });
11
13
  }
12
14
  async getQuerier() {
13
15
  return new MySql2Querier(() => this.pool.getConnection(), this.dialect, this.extra);
@@ -1,3 +1 @@
1
- export * from './neonDialect.js';
2
- export * from './neonQuerier.js';
3
1
  export * from './neonQuerierPool.js';
@@ -1,3 +1 @@
1
- export * from './neonDialect.js';
2
- export * from './neonQuerier.js';
3
1
  export * from './neonQuerierPool.js';
@@ -1,10 +1,8 @@
1
1
  import { Pool, type PoolClient, type PoolConfig } from '@neondatabase/serverless';
2
2
  import { AbstractPgQuerierPool } from '../postgres/abstractPgQuerierPool.js';
3
+ import { PostgresDialect } from '../postgres/postgresDialect.js';
3
4
  import type { ExtraOptions } from '../type/index.js';
4
- import { NeonDialect } from './neonDialect.js';
5
- import { NeonQuerier } from './neonQuerier.js';
6
- export declare class NeonQuerierPool extends AbstractPgQuerierPool<PoolClient, NeonQuerier, NeonDialect> {
5
+ export declare class NeonQuerierPool extends AbstractPgQuerierPool<PoolClient, PostgresDialect> {
7
6
  readonly pool: Pool;
8
7
  constructor(opts: PoolConfig, extra?: ExtraOptions);
9
- protected buildQuerier(connect: () => Promise<PoolClient>): NeonQuerier;
10
8
  }
@@ -2,14 +2,10 @@ import { Pool, types } from '@neondatabase/serverless';
2
2
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
3
3
  import { AbstractPgQuerierPool } from '../postgres/abstractPgQuerierPool.js';
4
4
  import { numericTypes } from '../postgres/pgNumericTypes.js';
5
- import { NeonDialect } from './neonDialect.js';
6
- import { NeonQuerier } from './neonQuerier.js';
5
+ import { PostgresDialect } from '../postgres/postgresDialect.js';
7
6
  export class NeonQuerierPool extends AbstractPgQuerierPool {
8
7
  constructor(opts, extra) {
9
8
  // Neon's own `types`, not `pg`'s: this entry has to load on an edge runtime where `pg` is absent.
10
- super(new NeonDialect(dialectOptionsFrom(extra)), new Pool({ types: numericTypes(types), ...opts }), extra);
11
- }
12
- buildQuerier(connect) {
13
- return new NeonQuerier(connect, this.dialect, this.extra);
9
+ super(new PostgresDialect(dialectOptionsFrom(extra)), new Pool({ types: numericTypes(types), ...opts }), extra);
14
10
  }
15
11
  }
@@ -1,3 +1,2 @@
1
- export * from './pgliteDialect.js';
2
1
  export * from './pgliteQuerier.js';
3
2
  export * from './pgliteQuerierPool.js';
@@ -1,3 +1,2 @@
1
- export * from './pgliteDialect.js';
2
1
  export * from './pgliteQuerier.js';
3
2
  export * from './pgliteQuerierPool.js';