uql-orm 0.51.0 → 0.53.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 (81) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +5 -5
  4. package/dist/bunSql/bunSql.util.d.ts +33 -10
  5. package/dist/bunSql/bunSql.util.js +57 -42
  6. package/dist/bunSql/bunSqlQuerier.d.ts +13 -8
  7. package/dist/bunSql/bunSqlQuerier.js +17 -8
  8. package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -2
  9. package/dist/bunSql/bunSqlQuerierPool.js +38 -25
  10. package/dist/bunSql/index.d.ts +1 -3
  11. package/dist/bunSql/index.js +0 -3
  12. package/dist/dialect/abstractDialect.d.ts +5 -5
  13. package/dist/dialect/abstractDialect.js +7 -6
  14. package/dist/dialect/abstractSqlDialect.d.ts +59 -6
  15. package/dist/dialect/abstractSqlDialect.js +90 -32
  16. package/dist/dialect/aliases.d.ts +2 -0
  17. package/dist/dialect/aliases.js +2 -0
  18. package/dist/dialect/mergeSqlDialect.d.ts +45 -0
  19. package/dist/dialect/mergeSqlDialect.js +89 -0
  20. package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -1
  21. package/dist/dialect/mysqlLikeSqlDialect.js +3 -3
  22. package/dist/dialect/pgLikeSqlDialect.js +3 -2
  23. package/dist/dialect/vectorSqlDialect.js +2 -1
  24. package/dist/http/handler.js +3 -12
  25. package/dist/http/query.js +4 -1
  26. package/dist/migrate/builder/expressions.js +5 -0
  27. package/dist/migrate/ddl/index.d.ts +8 -0
  28. package/dist/migrate/ddl/index.js +11 -0
  29. package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
  30. package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
  31. package/dist/migrate/ddl/tableDdl.d.ts +34 -0
  32. package/dist/migrate/ddl/tableDdl.js +71 -0
  33. package/dist/migrate/introspection/index.d.ts +2 -0
  34. package/dist/migrate/introspection/index.js +2 -0
  35. package/dist/migrate/introspection/mssqlIntrospector.d.ts +60 -0
  36. package/dist/migrate/introspection/mssqlIntrospector.js +205 -0
  37. package/dist/migrate/introspection/registry.d.ts +3 -0
  38. package/dist/migrate/introspection/registry.js +28 -0
  39. package/dist/migrate/migrator.js +2 -21
  40. package/dist/migrate/schemaGenerator.d.ts +8 -10
  41. package/dist/migrate/schemaGenerator.js +29 -74
  42. package/dist/mongo/mongoDialect.js +12 -14
  43. package/dist/mssql/index.d.ts +3 -0
  44. package/dist/mssql/index.js +3 -0
  45. package/dist/mssql/mssqlDialect.d.ts +155 -0
  46. package/dist/mssql/mssqlDialect.js +341 -0
  47. package/dist/mssql/mssqlQuerier.d.ts +23 -0
  48. package/dist/mssql/mssqlQuerier.js +137 -0
  49. package/dist/mssql/mssqlQuerierPool.d.ts +17 -0
  50. package/dist/mssql/mssqlQuerierPool.js +32 -0
  51. package/dist/mssql/mssqlWireTypes.d.ts +23 -0
  52. package/dist/mssql/mssqlWireTypes.js +44 -0
  53. package/dist/pglite/pgliteQuerier.d.ts +4 -2
  54. package/dist/pglite/pgliteQuerier.js +7 -2
  55. package/dist/postgres/pgCursorStream.d.ts +20 -0
  56. package/dist/postgres/pgCursorStream.js +49 -0
  57. package/dist/postgres/pgDialect.d.ts +1 -1
  58. package/dist/postgres/pgDialect.js +1 -1
  59. package/dist/postgres/postgresWireDriverCapabilities.d.ts +12 -10
  60. package/dist/postgres/postgresWireDriverCapabilities.js +12 -10
  61. package/dist/querier/abstractQuerier.js +13 -11
  62. package/dist/querier/abstractSqlQuerier.js +3 -3
  63. package/dist/schema/canonicalType.js +96 -113
  64. package/dist/sqlite/sqliteDialect.js +3 -2
  65. package/dist/type/dialect.d.ts +24 -5
  66. package/dist/type/migratorDialect.d.ts +1 -1
  67. package/dist/type/migratorDialect.js +1 -0
  68. package/dist/type/query.d.ts +1 -1
  69. package/dist/type/queryWhere.d.ts +7 -19
  70. package/dist/type/vector.d.ts +3 -1
  71. package/dist/util/dialect.util.d.ts +10 -7
  72. package/dist/util/dialect.util.js +16 -29
  73. package/dist/util/object.util.d.ts +2 -0
  74. package/dist/util/object.util.js +4 -0
  75. package/package.json +13 -3
  76. package/dist/bunSql/bunSqlCockroachDialect.d.ts +0 -12
  77. package/dist/bunSql/bunSqlCockroachDialect.js +0 -15
  78. package/dist/bunSql/bunSqlPostgresDialect.d.ts +0 -11
  79. package/dist/bunSql/bunSqlPostgresDialect.js +0 -14
  80. package/dist/bunSql/bunSqliteDialect.d.ts +0 -6
  81. package/dist/bunSql/bunSqliteDialect.js +0 -6
@@ -1,19 +1,28 @@
1
1
  import { SQL } from 'bun';
2
+ import { CockroachDialect } from '../cockroachdb/cockroachDialect.js';
2
3
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
3
4
  import { MariaDialect } from '../maria/mariaDialect.js';
4
5
  import { MySqlDialect } from '../mysql/mysqlDialect.js';
6
+ import { PostgresDialect } from '../postgres/postgresDialect.js';
7
+ import { POSTGRES_WIRE_DRIVER_CAPABILITIES } from '../postgres/postgresWireDriverCapabilities.js';
5
8
  import { AbstractSqlQuerierPool } from '../querier/index.js';
6
- import { getAffectedRows, inferDialectName, isPoolableDialect, normalizeBunOpts, normalizeRows, } from './bunSql.util.js';
7
- import { BunSqlCockroachDialect } from './bunSqlCockroachDialect.js';
8
- import { BunSqlPostgresDialect } from './bunSqlPostgresDialect.js';
9
+ import { SqliteDialect } from '../sqlite/sqliteDialect.js';
10
+ import { getAffectedRows, inferDialectName, normalizeBunOpts, normalizeRows, } from './bunSql.util.js';
9
11
  import { BunSqlQuerier } from './bunSqlQuerier.js';
10
- import { BunSqliteDialect } from './bunSqliteDialect.js';
12
+ /**
13
+ * The dialect each engine `bun:sql` drives is given, and how this driver shapes its parameters.
14
+ *
15
+ * The engine dialects themselves, not `bun:sql` subclasses of them: what Bun changes is the binding,
16
+ * never the SQL, and a per-instance `driverCapabilities` is where the base class already takes that -
17
+ * so the Postgres and CockroachDB entries differ from `PgQuerierPool`'s only by naming the same
18
+ * constant. Total over {@link BunSqlDialectName}, so a new Bun adapter has to be answered here.
19
+ */
11
20
  const DialectMap = {
12
- postgres: BunSqlPostgresDialect,
13
- mysql: MySqlDialect,
14
- mariadb: MariaDialect,
15
- sqlite: BunSqliteDialect,
16
- cockroachdb: BunSqlCockroachDialect,
21
+ postgres: [PostgresDialect, POSTGRES_WIRE_DRIVER_CAPABILITIES],
22
+ cockroachdb: [CockroachDialect, POSTGRES_WIRE_DRIVER_CAPABILITIES],
23
+ mysql: [MySqlDialect],
24
+ mariadb: [MariaDialect],
25
+ sqlite: [SqliteDialect],
17
26
  };
18
27
  export class BunSqlQuerierPool extends AbstractSqlQuerierPool {
19
28
  config;
@@ -22,7 +31,8 @@ export class BunSqlQuerierPool extends AbstractSqlQuerierPool {
22
31
  foreignKeysOn;
23
32
  constructor(config, extra) {
24
33
  const dialectName = inferDialectName(config);
25
- super(new DialectMap[dialectName](dialectOptionsFrom(extra)), extra);
34
+ const [Dialect, driverCapabilities] = DialectMap[dialectName];
35
+ super(new Dialect({ ...dialectOptionsFrom(extra), driverCapabilities }), extra);
26
36
  this.config = config;
27
37
  this.sqlDialectName = dialectName;
28
38
  const opts = normalizeBunOpts(config, dialectName);
@@ -34,28 +44,31 @@ export class BunSqlQuerierPool extends AbstractSqlQuerierPool {
34
44
  */
35
45
  get pool() {
36
46
  return {
37
- query: (text, values) => this.sql.unsafe(text, this.dialect.normalizeValues(values)).then((res) => ({
38
- rows: normalizeRows(res),
39
- rowCount: getAffectedRows(res),
40
- })),
47
+ query: (text, values) => this.sql.unsafe(text, this.dialect.normalizeValues(values)).then((res) => {
48
+ const rows = normalizeRows(res);
49
+ return { rows, rowCount: getAffectedRows(res) ?? rows.length };
50
+ }),
41
51
  on: () => {
42
52
  /* no-op for event listeners */
43
53
  },
44
54
  };
45
55
  }
46
56
  async getQuerier() {
47
- const connFactory = async () => {
48
- // Bun's SQLite adapter does not support connection reservation (it's unpooled), and leaves
49
- // `foreign_keys` off as `bun:sqlite` does, so without the pragma the constraints uql emits in
50
- // its own DDL are decorative. One connection means one pragma, issued on the first acquisition.
51
- if (!isPoolableDialect(this.sqlDialectName)) {
52
- this.foreignKeysOn ??= this.sql.unsafe('PRAGMA foreign_keys = ON');
53
- await this.foreignKeysOn;
54
- return this.sql;
55
- }
57
+ return new BunSqlQuerier(this.sql, this.dialect, () => this.acquire(), this.extra);
58
+ }
59
+ /**
60
+ * Bun's SQLite adapter does not support connection reservation (it's unpooled), and leaves
61
+ * `foreign_keys` off as `bun:sqlite` does, so without the pragma the constraints uql emits in its
62
+ * own DDL are decorative. One connection means one pragma, issued on the first acquisition, and a
63
+ * `release` that does nothing: the handle is the pool's, and outlives every querier over it.
64
+ */
65
+ async acquire() {
66
+ if (this.sqlDialectName !== 'sqlite') {
56
67
  return this.sql.reserve();
57
- };
58
- return new BunSqlQuerier(this.sql, this.dialect, connFactory, this.extra);
68
+ }
69
+ this.foreignKeysOn ??= this.sql.unsafe('PRAGMA foreign_keys = ON');
70
+ await this.foreignKeysOn;
71
+ return { unsafe: this.sql.unsafe.bind(this.sql), release: () => { } };
59
72
  }
60
73
  async end() {
61
74
  await this.sql.close();
@@ -1,5 +1,3 @@
1
- export * from './bunSqliteDialect.js';
2
- export * from './bunSqlCockroachDialect.js';
3
- export * from './bunSqlPostgresDialect.js';
1
+ export type { BunSqlConn, BunSqlDialectName, BunSqlResult } from './bunSql.util.js';
4
2
  export * from './bunSqlQuerier.js';
5
3
  export * from './bunSqlQuerierPool.js';
@@ -1,5 +1,2 @@
1
- export * from './bunSqliteDialect.js';
2
- export * from './bunSqlCockroachDialect.js';
3
- export * from './bunSqlPostgresDialect.js';
4
1
  export * from './bunSqlQuerier.js';
5
2
  export * from './bunSqlQuerierPool.js';
@@ -1,4 +1,4 @@
1
- import type { DialectFeatures, DialectName, EntityMeta, ExtraOptions, FieldOptions, InsertIdSource, NamingStrategy, QueryGroupOp, QueryOptions, QueryWhere, QueryWhereArray, QueryWhereMap } from '../type/index.js';
1
+ import type { DialectFeatures, DialectName, EntityMeta, ExtraOptions, FieldOptions, InsertIdSource, NamingStrategy, QueryGroupOp, QueryOptions, QueryWhere, QueryWhereArray } from '../type/index.js';
2
2
  /**
3
3
  * Options for initializing a dialect.
4
4
  */
@@ -72,11 +72,11 @@ export declare abstract class AbstractDialect {
72
72
  */
73
73
  columnOf<E>(meta: EntityMeta<E>, key: string): string;
74
74
  /**
75
- * A `$where` normalized to a map with the entity's active filters merged in - the only way any
76
- * dialect should enter a new query scope, so a scope cannot be rendered with its `security: true`
77
- * filters skipped. Recursion within one scope renders the returned map directly instead.
75
+ * A `$where` with the entity's active filters merged in - the only way any dialect should enter a
76
+ * new query scope, so a scope cannot be rendered with its `security: true` filters skipped.
77
+ * Recursion within one scope renders the returned map directly instead.
78
78
  */
79
- protected scopedWhereMap<E>(meta: EntityMeta<E>, where?: QueryWhere<E>, opts?: QueryOptions): QueryWhereMap<E>;
79
+ protected scopedWhere<E>(meta: EntityMeta<E>, where?: QueryWhere<E>, opts?: QueryOptions): QueryWhere<E>;
80
80
  /**
81
81
  * How each clause-grouping operator renders: which operator joins its clauses, and whether the
82
82
  * group is negated afterwards - so `$not` is `NOT (a AND b)` and `$nor` is `NOT (a OR b)`. SQL
@@ -1,4 +1,4 @@
1
- import { applyFilters, buildQueryWhereAsMap } from '../util/dialect.util.js';
1
+ import { applyFilters, assertWhere } from '../util/dialect.util.js';
2
2
  import { entityName } from '../util/index.js';
3
3
  import { qualifyName } from '../util/sql.util.js';
4
4
  /**
@@ -81,12 +81,13 @@ export class AbstractDialect {
81
81
  return this.resolveColumnName(key, meta.fields[key]);
82
82
  }
83
83
  /**
84
- * A `$where` normalized to a map with the entity's active filters merged in - the only way any
85
- * dialect should enter a new query scope, so a scope cannot be rendered with its `security: true`
86
- * filters skipped. Recursion within one scope renders the returned map directly instead.
84
+ * A `$where` with the entity's active filters merged in - the only way any dialect should enter a
85
+ * new query scope, so a scope cannot be rendered with its `security: true` filters skipped.
86
+ * Recursion within one scope renders the returned map directly instead.
87
87
  */
88
- scopedWhereMap(meta, where = {}, opts) {
89
- return applyFilters(meta, buildQueryWhereAsMap(meta, where), opts);
88
+ scopedWhere(meta, where = {}, opts) {
89
+ assertWhere(meta, where);
90
+ return applyFilters(meta, where, opts);
90
91
  }
91
92
  /**
92
93
  * How each clause-grouping operator renders: which operator joins its clauses, and whether the
@@ -1,4 +1,4 @@
1
- import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryBuildFn, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryFilter, type QueryGroupMap, type QueryGroupOp, type QueryHavingMap, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorNear, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationMeta, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
1
+ import { type EntityData, type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryBuildFn, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryFilter, type QueryGroupMap, type QueryGroupOp, type QueryHavingMap, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorNear, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereOptions, type RelationMeta, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
2
2
  import { type ParentPartition } from '../util/index.js';
3
3
  import type { HydrateKind } from './hydrateColumn.js';
4
4
  import { type JsonAccessMode } from './jsonSql.js';
@@ -6,6 +6,15 @@ import { type QueryJoins, type QuerySortOptions } from './queryJoins.js';
6
6
  import { VectorSqlDialect } from './vectorSqlDialect.js';
7
7
  /** How a column's values are bound: see {@link AbstractSqlDialect.persistKind}. */
8
8
  type PersistKind = 'plain' | 'json' | 'vector';
9
+ /** What {@link AbstractSqlDialect.insertShape} resolves once for a write, indexed in step. */
10
+ type InsertShape<E> = {
11
+ readonly meta: EntityMeta<E>;
12
+ readonly payloads: EntityData<E>[];
13
+ readonly keys: FieldKey<E>[];
14
+ readonly fields: (FieldOptions | undefined)[];
15
+ readonly columns: string[];
16
+ readonly kinds: PersistKind[];
17
+ };
9
18
  /** One entry of {@link AbstractSqlDialect.hydratableFields}: a field key and how it decodes. */
10
19
  type HydratableField = readonly [string, HydrateKind];
11
20
  export type { HydrateKind };
@@ -45,7 +54,6 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
45
54
  */
46
55
  readonly dropPrimaryKeySyntax: 'DROP CONSTRAINT' | 'DROP PRIMARY KEY';
47
56
  readonly dropIndexSyntax: 'on-table' | 'standalone';
48
- readonly renameTableSyntax: 'rename-table' | 'alter-table';
49
57
  readonly booleanLiteral: 'native' | 'integer';
50
58
  /**
51
59
  * Maximum number of bind parameters the driver accepts in a single statement.
@@ -111,6 +119,11 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
111
119
  protected returningIdExpression<E>(meta: EntityMeta<E>): string;
112
120
  search<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryOptions, joins?: QueryJoins): void;
113
121
  selectFields<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelectValue<E> | undefined, opts?: QuerySelectOptions, exclude?: QueryExclude<E>): void;
122
+ /**
123
+ * What follows `SELECT` before the projection. Empty everywhere but SQL Server, whose `FETCH` will
124
+ * not take a zero and which spells "no rows" as `TOP (0)` instead.
125
+ */
126
+ protected selectModifier<E>(_q: Query<E>): string;
114
127
  /**
115
128
  * The expression a scalar field is read through, the plain column by default. MariaDB reads a
116
129
  * vector column back with `VEC_ToText`, since selecting it raw yields its binary form.
@@ -280,7 +293,9 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
280
293
  protected jsonScalarParam(ctx: QueryContext, value: unknown): string;
281
294
  /** {@link resolveOperandField}, appended. */
282
295
  getComparisonKey<E>(ctx: QueryContext, entity: Type<E>, key: FieldKey<E>, opts?: QueryOptions): void;
283
- sort<E>(ctx: QueryContext, entity: Type<E>, sort: QuerySortMap<E> | undefined, opts?: QuerySortOptions): void;
296
+ /** Appends the `ORDER BY`, reporting whether there was one - which {@link pager} needs on the
297
+ * engines that refuse to page an unordered statement. */
298
+ sort<E>(ctx: QueryContext, entity: Type<E>, sort: QuerySortMap<E> | undefined, opts?: QuerySortOptions): boolean;
284
299
  /**
285
300
  * Walks `$sort` against the metadata of the entity each level addresses, rather than flattening it
286
301
  * to dotted strings and reading every key off the root: only that way does a related column resolve
@@ -293,7 +308,11 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
293
308
  * `$agg` alias - is an output alias, which is never table-qualified and needs no resolving.
294
309
  */
295
310
  private sortColumn;
296
- pager(ctx: QueryContext, opts: QueryPager): void;
311
+ /**
312
+ * `LIMIT`/`OFFSET`. `sorted` says whether an `ORDER BY` was emitted just before, which
313
+ * {@link MergeSqlDialect} needs: SQL Server refuses to page a statement that has none.
314
+ */
315
+ pager(ctx: QueryContext, opts: QueryPager, _sorted?: boolean): void;
297
316
  /** Whether this engine has row locks at all. The SQLite family locks the database instead. */
298
317
  readonly supportsRowLocks: boolean;
299
318
  /**
@@ -306,6 +325,12 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
306
325
  readonly supportsLockOf: boolean;
307
326
  /** Validated before the querier checks for a transaction, so the clearer error wins. */
308
327
  assertLockSupported<E>(entity: Type<E>, q: Query<E>, joins?: QueryJoins): void;
328
+ /**
329
+ * The lock as a hint on the table itself, for the engine that has no trailing `FOR UPDATE`. Empty
330
+ * everywhere else, which is where {@link appendLock} does the work instead - the two are the same
331
+ * lock spelled at opposite ends of the statement, so exactly one of them ever emits.
332
+ */
333
+ protected lockHint<E>(_q: Query<E>): string;
309
334
  /**
310
335
  * The trailing `FOR UPDATE`. Narrowing to the queried table is not a nicety once a relation is
311
336
  * joined: Postgres refuses a bare `FOR UPDATE` over the nullable side of an outer join outright,
@@ -354,11 +379,32 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
354
379
  protected readonly totalOverExpr = "COUNT(*) OVER ()";
355
380
  find<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryOptions, totalAlias?: string): void;
356
381
  insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void;
382
+ /**
383
+ * Where the clause reporting an insert's generated ids goes. `suffix` is `RETURNING ...` at the end
384
+ * of the statement, which every engine here but one spells that way; SQL Server's `OUTPUT` has no
385
+ * trailing form and sits between the column list and `VALUES`.
386
+ *
387
+ * A knob rather than a pair of hooks: one concept decides where the string {@link returningId}
388
+ * already built ends up, so the two ends cannot disagree.
389
+ */
390
+ readonly returningPosition: 'suffix' | 'after-target';
357
391
  /**
358
392
  * `INSERT INTO ... VALUES (...)` and nothing more. The upsert builders extend this rather than
359
393
  * {@link insert}: their own clause has to come before the `RETURNING`, not after it.
360
394
  */
361
- protected appendInsertValues<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[]): void;
395
+ protected appendInsertValues<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[],
396
+ /** Spliced between the column list and `VALUES`; see {@link returningPosition}. */
397
+ afterTarget?: string): void;
398
+ /**
399
+ * The columns an insert writes and the records it writes them from, resolved once.
400
+ *
401
+ * Split out of {@link appendInsertValues} because a `MERGE` needs the same rows as a `VALUES` row
402
+ * source rather than as an `INSERT`, and both have to apply `onInsert` defaults and the
403
+ * JSON/vector binding rules identically.
404
+ */
405
+ protected insertShape<E>(entity: Type<E>, payload: E | E[]): InsertShape<E>;
406
+ /** `(a, b), (c, d)` - the row constructor an INSERT and a MERGE source both write. */
407
+ protected appendValueRows<E>(ctx: QueryContext, { payloads, keys, fields, kinds }: InsertShape<E>): void;
362
408
  /**
363
409
  * Emit the value for a column a payload record does not provide (the column list is the union
364
410
  * across all records). `DEFAULT` delegates to the database default; SQLite overrides this since
@@ -550,7 +596,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
550
596
  */
551
597
  private appendRelationSubquery;
552
598
  /** Filter by relation: a parent matches when {@link appendRelationSubquery} finds one target row. */
553
- protected compareRelation<E>(ctx: QueryContext, entity: Type<E>, val: QueryWhereMap<unknown>, rel: RelationMeta, opts: QueryComparisonOptions): void;
599
+ protected compareRelation<E>(ctx: QueryContext, entity: Type<E>, val: QueryWhere<unknown>, rel: RelationMeta, opts: QueryComparisonOptions): void;
554
600
  /** Filter by relation size: the same subquery, counting instead of testing for existence. */
555
601
  protected compareRelationSize<E>(ctx: QueryContext, entity: Type<E>, sizeVal: number | QuerySizeComparisonOps, rel: RelationMeta, opts: QueryComparisonOptions): void;
556
602
  /**
@@ -587,6 +633,13 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
587
633
  /** ANSI-style single-quote escaping. MySQL-family dialects override this for backslash escaping. */
588
634
  escape(value: unknown): string;
589
635
  protected get regexpOp(): string;
636
+ /**
637
+ * The `$regex` predicate. An infix operator on the MySQL family (`REGEXP`) and the Postgres one
638
+ * (`~`), but a function on Oracle and SQL Server 2025 (`REGEXP_LIKE(col, ?)`) - which is why this
639
+ * is a method rather than the operator token alone. An engine with no regex at all overrides it to
640
+ * throw, the way {@link appendTextSearch} already does.
641
+ */
642
+ protected regexCondition(operand: string, placeholder: string): string;
590
643
  protected get likeFn(): string;
591
644
  /**
592
645
  * Not-equal operator token for non-null comparisons.
@@ -1,7 +1,7 @@
1
1
  import { getMeta, soleIdOf } from '../entity/index.js';
2
2
  import { parseQueryLock, QueryRaw, RAW_ALIAS, RAW_VALUE, VECTOR_QUERY_KEYS, } from '../type/index.js';
3
3
  import { computedExpression, isInlinedExpression } from '../util/field.util.js';
4
- import { asSelectMap, assertNonNegativeInteger, buildQueryWhereAsMap, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, columnFamily, isJsonUpdateOp, isOperatorMap, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, targetKeyColumns, parseGroupMap, parseRelationSize, parseSortByCount, populatesRelations, queryChildrenOf, raw, someValue, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
4
+ import { asSelectMap, assertNonNegativeInteger, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, columnFamily, isJsonUpdateOp, isOperatorMap, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, targetKeyColumns, parseGroupMap, parseRelationSize, parseSortByCount, populatesRelations, queryChildrenOf, raw, someValue, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
5
5
  import { escapeAnsiSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
6
6
  import { COUNT_ALIAS, DISTINCT_DERIVED_ALIAS, JSON_ELEM_ALIAS_PREFIX, PER_PARENT_BRANCH_ALIAS } from './aliases.js';
7
7
  import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
@@ -36,7 +36,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
36
36
  */
37
37
  dropPrimaryKeySyntax = 'DROP CONSTRAINT';
38
38
  dropIndexSyntax = 'standalone';
39
- renameTableSyntax = 'alter-table';
40
39
  booleanLiteral = 'native';
41
40
  /**
42
41
  * Maximum number of bind parameters the driver accepts in a single statement.
@@ -168,8 +167,8 @@ export class AbstractSqlDialect extends VectorSqlDialect {
168
167
  opts = { ...opts, prefix };
169
168
  }
170
169
  this.where(ctx, entity, q.$where, opts);
171
- this.sort(ctx, entity, q.$sort, { prefix, joins, distinct: q.$distinct });
172
- this.pager(ctx, q);
170
+ const sorted = this.sort(ctx, entity, q.$sort, { prefix, joins, distinct: q.$distinct });
171
+ this.pager(ctx, q, sorted);
173
172
  }
174
173
  selectFields(ctx, entity, select, opts = {}, exclude) {
175
174
  const meta = getMeta(entity);
@@ -227,6 +226,13 @@ export class AbstractSqlDialect extends VectorSqlDialect {
227
226
  }
228
227
  });
229
228
  }
229
+ /**
230
+ * What follows `SELECT` before the projection. Empty everywhere but SQL Server, whose `FETCH` will
231
+ * not take a zero and which spells "no rows" as `TOP (0)` instead.
232
+ */
233
+ selectModifier(_q) {
234
+ return '';
235
+ }
230
236
  /**
231
237
  * The expression a scalar field is read through, the plain column by default. MariaDB reads a
232
238
  * vector column back with `VEC_ToText`, since selecting it raw yields its binary form.
@@ -248,6 +254,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
248
254
  const { alias, ref } = this.tableRef(meta);
249
255
  const prefix = this.resolveRelationAwarePrefix(alias, meta, opts, q.$populate, joins);
250
256
  ctx.append(q.$distinct ? 'SELECT DISTINCT ' : 'SELECT ');
257
+ ctx.append(this.selectModifier(q));
251
258
  this.selectFields(ctx, entity, q.$select, { prefix }, q.$exclude);
252
259
  // Add related fields BEFORE FROM clause
253
260
  this.selectRelationFields(ctx, joins);
@@ -261,7 +268,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
261
268
  if (totalAlias) {
262
269
  ctx.append(`, ${this.totalOverExpr} ${this.escapeId(totalAlias, true)}`);
263
270
  }
264
- ctx.append(` FROM ${ref}`);
271
+ ctx.append(` FROM ${ref}${this.lockHint(q)}`);
265
272
  // Add JOINs AFTER FROM clause
266
273
  this.selectRelationJoins(ctx, meta, alias, joins);
267
274
  }
@@ -322,21 +329,19 @@ export class AbstractSqlDialect extends VectorSqlDialect {
322
329
  where(ctx, entity, where = {}, opts = {}) {
323
330
  const meta = getMeta(entity);
324
331
  // Filters are applied once, here at the scope entry point; recursion uses `renderWhere`.
325
- this.renderWhere(ctx, entity, this.scopedWhereMap(meta, where, opts), opts);
332
+ this.renderWhere(ctx, entity, this.scopedWhere(meta, where, opts), opts);
326
333
  }
327
334
  /** Renders a `$where` tree without applying entity filters (used for same-scope group-operator recursion). */
328
335
  renderWhere(ctx, entity, where = {}, opts = {}) {
329
- const meta = getMeta(entity);
330
336
  const { clause = 'WHERE' } = opts;
331
- const whereMap = buildQueryWhereAsMap(meta, where);
332
337
  // An `undefined` value emits nothing, so it must not count towards the terms either: it decides
333
338
  // whether the keys below render as operands of an `AND`.
334
- const whereKeys = getKeys(whereMap).filter((key) => whereMap[key] !== undefined);
339
+ const whereKeys = getKeys(where).filter((key) => where[key] !== undefined);
335
340
  // Each key is an operand of the `AND` joining them; a lone key emits this fragment verbatim, so
336
341
  // it inherits this one's position instead.
337
342
  const childOperand = whereKeys.length > 1 || opts.operand || clause === 'AND';
338
343
  const childOpts = opts.operand === childOperand ? opts : { ...opts, operand: childOperand };
339
- const parts = this.renderOperands(ctx, whereKeys, (fragmentCtx, key) => this.compare(fragmentCtx, entity, key, whereMap[key], childOpts));
344
+ const parts = this.renderOperands(ctx, whereKeys, (fragmentCtx, key) => this.compare(fragmentCtx, entity, key, where[key], childOpts));
340
345
  if (!parts.length) {
341
346
  return;
342
347
  }
@@ -582,7 +587,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
582
587
  case '$ne':
583
588
  return val === null ? `${operand} IS NOT NULL` : this.neExpr(operand, this.addValue(ctx.values, val));
584
589
  case '$regex':
585
- return `${operand} ${this.regexpOp} ${this.addValue(ctx.values, val)}`;
590
+ return this.regexCondition(operand, this.addValue(ctx.values, val));
586
591
  case '$in':
587
592
  case '$nin': {
588
593
  if (!Array.isArray(val)) {
@@ -643,7 +648,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
643
648
  return `${jsonField} IS NOT NULL`;
644
649
  return this.neExpr(comparand(value), this.jsonOperand(ctx, value, asJson));
645
650
  case '$regex':
646
- return `${jsonField} ${this.regexpOp} ${this.addValue(ctx.values, value)}`;
651
+ return this.regexCondition(jsonField, this.addValue(ctx.values, value));
647
652
  case '$in':
648
653
  case '$nin':
649
654
  return this.jsonInNin(ctx, jsonField, comparand, op, value, asJson);
@@ -737,15 +742,18 @@ export class AbstractSqlDialect extends VectorSqlDialect {
737
742
  return this.addValue(ctx.values, value);
738
743
  }
739
744
  ctx.pushValue(JSON.stringify(value));
740
- return this.jsonCast('?');
745
+ // The placeholder for the value just pushed, so a named or numbered one is spelled correctly.
746
+ return this.jsonCast(this.placeholder(ctx.values.length));
741
747
  }
742
748
  /** {@link resolveOperandField}, appended. */
743
749
  getComparisonKey(ctx, entity, key, opts = {}) {
744
750
  ctx.append(this.resolveOperandField(ctx, entity, key, opts));
745
751
  }
752
+ /** Appends the `ORDER BY`, reporting whether there was one - which {@link pager} needs on the
753
+ * engines that refuse to page an unordered statement. */
746
754
  sort(ctx, entity, sort, opts = {}) {
747
755
  if (!hasKeys(sort)) {
748
- return;
756
+ return false;
749
757
  }
750
758
  // Collected before anything is appended so an unorderable key is reported instead of half a
751
759
  // clause, and because a vector distance is the primary ordering wherever it appears in the map.
@@ -756,6 +764,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
756
764
  if (terms.length) {
757
765
  ctx.append(` ORDER BY ${terms.join(', ')}`);
758
766
  }
767
+ return terms.length > 0;
759
768
  }
760
769
  /**
761
770
  * Walks `$sort` against the metadata of the entity each level addresses, rather than flattening it
@@ -815,7 +824,11 @@ export class AbstractSqlDialect extends VectorSqlDialect {
815
824
  const json = this.resolveJsonDotPath(meta, key, prefix);
816
825
  return json ? this.jsonPathExpr(json.column, json.jsonPath, 'text') : this.escapeId(key);
817
826
  }
818
- pager(ctx, opts) {
827
+ /**
828
+ * `LIMIT`/`OFFSET`. `sorted` says whether an `ORDER BY` was emitted just before, which
829
+ * {@link MergeSqlDialect} needs: SQL Server refuses to page a statement that has none.
830
+ */
831
+ pager(ctx, opts, _sorted = false) {
819
832
  // `!== undefined`, not truthiness: `$limit: 0` asks for no rows, where "unset" means every row.
820
833
  if (opts.$limit !== undefined) {
821
834
  ctx.append(` LIMIT ${assertNonNegativeInteger(opts.$limit, '$limit')}`);
@@ -847,6 +860,14 @@ export class AbstractSqlDialect extends VectorSqlDialect {
847
860
  throw new TypeError(`${this.dialectName} cannot narrow a row lock to one table, so $lock cannot be combined with a joined relation`);
848
861
  }
849
862
  }
863
+ /**
864
+ * The lock as a hint on the table itself, for the engine that has no trailing `FOR UPDATE`. Empty
865
+ * everywhere else, which is where {@link appendLock} does the work instead - the two are the same
866
+ * lock spelled at opposite ends of the statement, so exactly one of them ever emits.
867
+ */
868
+ lockHint(_q) {
869
+ return '';
870
+ }
850
871
  /**
851
872
  * The trailing `FOR UPDATE`. Narrowing to the queried table is not a nicety once a relation is
852
873
  * joined: Postgres refuses a bare `FOR UPDATE` over the nullable side of an outer join outright,
@@ -946,8 +967,8 @@ export class AbstractSqlDialect extends VectorSqlDialect {
946
967
  if (q.$having) {
947
968
  this.having(ctx, q.$having, emittedColumns);
948
969
  }
949
- this.aggregateSort(ctx, q.$sort, emittedColumns);
950
- this.pager(ctx, q);
970
+ const sorted = this.aggregateSort(ctx, q.$sort, emittedColumns);
971
+ this.pager(ctx, q, sorted);
951
972
  }
952
973
  /**
953
974
  * ORDER BY for aggregate queries - handles both entity-field and alias references. A grouped
@@ -956,13 +977,14 @@ export class AbstractSqlDialect extends VectorSqlDialect {
956
977
  */
957
978
  aggregateSort(ctx, sort, emittedColumns) {
958
979
  if (!hasKeys(sort))
959
- return;
980
+ return false;
960
981
  ctx.append(' ORDER BY ');
961
982
  Object.entries(sort).forEach(([key, dir], index) => {
962
983
  if (index > 0)
963
984
  ctx.append(', ');
964
985
  ctx.append(this.aggregateRef(emittedColumns, key, '$sort') + this.resolveSortDirection(dir));
965
986
  });
987
+ return true;
966
988
  }
967
989
  /** The SQL referencing one of an aggregate's emitted columns, rejecting any other name. */
968
990
  aggregateRef(emittedColumns, key, clause) {
@@ -1021,22 +1043,48 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1021
1043
  this.appendLock(ctx, entity, q, joins);
1022
1044
  }
1023
1045
  insert(ctx, entity, payload, opts) {
1024
- this.appendInsertValues(ctx, entity, payload);
1025
1046
  // Every engine whose ids come back from the statement itself wants the same clause, so it is
1026
- // appended once here instead of in an identical `insert` override per dialect. `returningId` is
1047
+ // built once here instead of in an identical `insert` override per dialect. `returningId` is
1027
1048
  // empty on a composite key, which has no id to ask for.
1028
- if (this.insertIdSource === 'returning') {
1029
- const returning = this.returningId(getMeta(entity));
1030
- if (returning) {
1031
- ctx.append(` ${returning}`);
1032
- }
1049
+ const returning = this.insertIdSource === 'returning' ? this.returningId(getMeta(entity)) : '';
1050
+ if (returning && this.returningPosition === 'after-target') {
1051
+ this.appendInsertValues(ctx, entity, payload, returning);
1052
+ return;
1053
+ }
1054
+ this.appendInsertValues(ctx, entity, payload);
1055
+ if (returning) {
1056
+ ctx.append(` ${returning}`);
1033
1057
  }
1034
1058
  }
1059
+ /**
1060
+ * Where the clause reporting an insert's generated ids goes. `suffix` is `RETURNING ...` at the end
1061
+ * of the statement, which every engine here but one spells that way; SQL Server's `OUTPUT` has no
1062
+ * trailing form and sits between the column list and `VALUES`.
1063
+ *
1064
+ * A knob rather than a pair of hooks: one concept decides where the string {@link returningId}
1065
+ * already built ends up, so the two ends cannot disagree.
1066
+ */
1067
+ returningPosition = 'suffix';
1035
1068
  /**
1036
1069
  * `INSERT INTO ... VALUES (...)` and nothing more. The upsert builders extend this rather than
1037
1070
  * {@link insert}: their own clause has to come before the `RETURNING`, not after it.
1038
1071
  */
1039
- appendInsertValues(ctx, entity, payload) {
1072
+ appendInsertValues(ctx, entity, payload,
1073
+ /** Spliced between the column list and `VALUES`; see {@link returningPosition}. */
1074
+ afterTarget = '') {
1075
+ const shape = this.insertShape(entity, payload);
1076
+ const tableName = this.escapedTableName(getMeta(entity));
1077
+ ctx.append(`INSERT INTO ${tableName} (${shape.columns.join(', ')})${afterTarget ? ` ${afterTarget}` : ''} VALUES `);
1078
+ this.appendValueRows(ctx, shape);
1079
+ }
1080
+ /**
1081
+ * The columns an insert writes and the records it writes them from, resolved once.
1082
+ *
1083
+ * Split out of {@link appendInsertValues} because a `MERGE` needs the same rows as a `VALUES` row
1084
+ * source rather than as an `INSERT`, and both have to apply `onInsert` defaults and the
1085
+ * JSON/vector binding rules identically.
1086
+ */
1087
+ insertShape(entity, payload) {
1040
1088
  const meta = getMeta(entity);
1041
1089
  const payloads = fillOnFields(meta, payload, 'onInsert');
1042
1090
  const keys = getInsertFieldKeys(meta, payloads);
@@ -1053,12 +1101,13 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1053
1101
  columns[i] = this.escapedColumnName(meta, key);
1054
1102
  kinds[i] = this.persistKind(field);
1055
1103
  }
1056
- const tableName = this.escapedTableName(meta);
1057
- ctx.append(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (`);
1104
+ return { meta, payloads, keys, fields, columns, kinds };
1105
+ }
1106
+ /** `(a, b), (c, d)` - the row constructor an INSERT and a MERGE source both write. */
1107
+ appendValueRows(ctx, { payloads, keys, fields, kinds }) {
1108
+ const width = keys.length;
1058
1109
  for (let r = 0; r < payloads.length; r++) {
1059
- if (r > 0) {
1060
- ctx.append('), (');
1061
- }
1110
+ ctx.append(r > 0 ? '), (' : '(');
1062
1111
  const record = payloads[r];
1063
1112
  for (let i = 0; i < width; i++) {
1064
1113
  if (i > 0) {
@@ -1532,7 +1581,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1532
1581
  const relatedMeta = getMeta(relatedEntity);
1533
1582
  const { alias: relatedAlias, ref: relatedRef } = this.tableRef(relatedMeta);
1534
1583
  // Resolved before any SQL is emitted: it also decides whether the mm form reaches the target.
1535
- const targetWhere = this.scopedWhereMap(relatedMeta, val);
1584
+ const targetWhere = this.scopedWhere(relatedMeta, val);
1536
1585
  ctx.append(`(SELECT ${projection} FROM `);
1537
1586
  // One equality per key of the parent, anded: a composite correlates on every column, and matching
1538
1587
  // on part of one would find the rows of a different parent. `parentJoins` is what keeps the two
@@ -1677,6 +1726,15 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1677
1726
  get regexpOp() {
1678
1727
  return 'REGEXP';
1679
1728
  }
1729
+ /**
1730
+ * The `$regex` predicate. An infix operator on the MySQL family (`REGEXP`) and the Postgres one
1731
+ * (`~`), but a function on Oracle and SQL Server 2025 (`REGEXP_LIKE(col, ?)`) - which is why this
1732
+ * is a method rather than the operator token alone. An engine with no regex at all overrides it to
1733
+ * throw, the way {@link appendTextSearch} already does.
1734
+ */
1735
+ regexCondition(operand, placeholder) {
1736
+ return `${operand} ${this.regexpOp} ${placeholder}`;
1737
+ }
1680
1738
  get likeFn() {
1681
1739
  return 'LIKE';
1682
1740
  }
@@ -32,6 +32,8 @@ export declare const REL_NESTED_KEY = "_uql_target";
32
32
  * syntax and keeps `VALUES(col)`.
33
33
  */
34
34
  export declare const UPSERT_NEW_ROW_ALIAS = "_uql_new";
35
+ /** The row source a `MERGE` upsert reads its incoming values from, on SQL Server and Oracle. */
36
+ export declare const UPSERT_SOURCE_ALIAS = "_uql_src";
35
37
  /**
36
38
  * Where a `$sort` by a relation's size parks its tally until the ordering has run. A function, so the
37
39
  * `$sort` that names the field and the stage that produces it cannot spell it differently - MongoDB
@@ -32,6 +32,8 @@ export const REL_NESTED_KEY = '_uql_target';
32
32
  * syntax and keeps `VALUES(col)`.
33
33
  */
34
34
  export const UPSERT_NEW_ROW_ALIAS = '_uql_new';
35
+ /** The row source a `MERGE` upsert reads its incoming values from, on SQL Server and Oracle. */
36
+ export const UPSERT_SOURCE_ALIAS = '_uql_src';
35
37
  /**
36
38
  * Where a `$sort` by a relation's size parks its tally until the ordering has run. A function, so the
37
39
  * `$sort` that names the field and the stage that produces it cannot spell it differently - MongoDB