uql-orm 0.52.0 → 0.54.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.
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +5 -5
- package/dist/cockroachdb/cockroachDialect.d.ts +2 -5
- package/dist/cockroachdb/cockroachDialect.js +2 -5
- package/dist/dialect/abstractDialect.d.ts +5 -5
- package/dist/dialect/abstractDialect.js +7 -6
- package/dist/dialect/abstractSqlDialect.d.ts +8 -3
- package/dist/dialect/abstractSqlDialect.js +14 -11
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -6
- package/dist/dialect/pgLikeSqlDialect.js +0 -2
- package/dist/dialect/vectorSqlDialect.js +2 -1
- package/dist/entity/decorator/members.d.ts +3 -10
- package/dist/entity/metadata/definition.js +0 -4
- package/dist/http/handler.js +3 -12
- package/dist/http/query.js +4 -1
- package/dist/migrate/builder/migrationBuilder.js +0 -4
- package/dist/migrate/ddl/index.d.ts +8 -0
- package/dist/migrate/ddl/index.js +11 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
- package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
- package/dist/migrate/ddl/tableDdl.d.ts +34 -0
- package/dist/migrate/ddl/tableDdl.js +71 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +6 -9
- package/dist/migrate/introspection/mssqlIntrospector.js +40 -33
- package/dist/migrate/migrator.d.ts +3 -7
- package/dist/migrate/migrator.js +3 -7
- package/dist/migrate/schemaGenerator.d.ts +8 -23
- package/dist/migrate/schemaGenerator.js +29 -87
- package/dist/migrate/storage/databaseStorage.d.ts +2 -2
- package/dist/migrate/storage/databaseStorage.js +2 -2
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.d.ts +3 -5
- package/dist/mongo/mongodbQuerier.js +18 -19
- package/dist/mssql/mssqlDialect.d.ts +14 -1
- package/dist/mssql/mssqlDialect.js +22 -7
- package/dist/querier/abstractQuerier.d.ts +17 -22
- package/dist/querier/abstractQuerier.js +92 -67
- package/dist/querier/abstractSqlQuerier.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.js +71 -87
- package/dist/schema/canonicalType.js +2 -2
- package/dist/schema/schemaASTBuilder.js +7 -7
- package/dist/sqlite/sqliteDialect.js +0 -2
- package/dist/type/dialect.d.ts +0 -8
- package/dist/type/entity.d.ts +6 -11
- package/dist/type/migration.d.ts +0 -3
- package/dist/type/query.d.ts +13 -13
- package/dist/type/query.js +0 -6
- package/dist/type/queryWhere.d.ts +7 -19
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +3 -1
- package/dist/util/dialect.util.d.ts +12 -8
- package/dist/util/dialect.util.js +17 -29
- package/dist/util/field.util.d.ts +4 -16
- package/dist/util/field.util.js +6 -19
- package/dist/util/fieldOption.util.d.ts +1 -4
- package/dist/util/fieldOption.util.js +0 -2
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/logger.js +3 -0
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/raw.d.ts +3 -10
- package/dist/util/sql.util.js +2 -2
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { COUNT_ALIAS, TOTAL_ALIAS } from '../dialect/aliases.js';
|
|
2
2
|
import { decodeColumn } from '../dialect/hydrateColumn.js';
|
|
3
|
-
import { getMeta, idOf,
|
|
4
|
-
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary, hasKeys, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
3
|
+
import { getMeta, idOf, namesKey } from '../entity/index.js';
|
|
4
|
+
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary, hasKeys, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
5
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
6
6
|
import { enrichError } from './queryError.js';
|
|
7
7
|
/**
|
|
@@ -22,50 +22,36 @@ function partitionBySuppliedId(payload, idKey) {
|
|
|
22
22
|
return supplied.length && generated.length ? [supplied, generated] : [supplied.length ? supplied : generated];
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* they all carry the same ones, which is the batch as it stands and needs no grouping at all.
|
|
25
|
+
* Row indexes grouped by the columns their rows carry, payload order kept within each group.
|
|
27
26
|
*
|
|
28
27
|
* Deliberately finer than {@link partitionBySuppliedId}: an upsert's `DO UPDATE SET` is one
|
|
29
28
|
* assignment list for the whole statement, so rows of different shapes cannot share one at all.
|
|
30
29
|
*/
|
|
31
30
|
function groupByInsertShape(meta, payload) {
|
|
32
|
-
const
|
|
33
|
-
let index =
|
|
34
|
-
|
|
35
|
-
index++;
|
|
36
|
-
}
|
|
37
|
-
if (index === payload.length) {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
const groups = new Map([[first, payload.slice(0, index)]]);
|
|
41
|
-
for (; index < payload.length; index++) {
|
|
42
|
-
const row = payload[index];
|
|
43
|
-
const shape = insertShapeOf(meta, row);
|
|
31
|
+
const groups = new Map();
|
|
32
|
+
for (let index = 0; index < payload.length; index++) {
|
|
33
|
+
const shape = insertShapeOf(meta, payload[index]);
|
|
44
34
|
const group = groups.get(shape);
|
|
45
35
|
if (group) {
|
|
46
|
-
group.push(
|
|
36
|
+
group.push(index);
|
|
47
37
|
}
|
|
48
38
|
else {
|
|
49
|
-
groups.set(shape, [
|
|
39
|
+
groups.set(shape, [index]);
|
|
50
40
|
}
|
|
51
41
|
}
|
|
52
42
|
return [...groups.values()];
|
|
53
43
|
}
|
|
54
44
|
/**
|
|
55
|
-
*
|
|
56
|
-
* parameter, so fields-per-record is a safe upper bound. Every multi-row
|
|
57
|
-
* allows 100 binds, which a couple of dozen rows reach.
|
|
45
|
+
* A group's row indexes split into statements within the dialect's bind budget, payload order kept.
|
|
46
|
+
* `DEFAULT` cells bind no parameter, so fields-per-record is a safe upper bound. Every multi-row
|
|
47
|
+
* write splits on this: D1 allows 100 binds, which a couple of dozen rows reach.
|
|
58
48
|
*/
|
|
59
|
-
function
|
|
60
|
-
const fieldsPerRecord = getInsertFieldKeys(meta,
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
/** `rows` split into statement-sized slices, payload order kept. */
|
|
64
|
-
function chunkByBindBudget(meta, rows, maxBindValues) {
|
|
65
|
-
const size = bindBudgetChunkSize(meta, rows, maxBindValues);
|
|
49
|
+
function chunkByBindBudget(meta, payload, group, maxBindValues) {
|
|
50
|
+
const fieldsPerRecord = getInsertFieldKeys(meta, group.map((index) => payload[index])).length;
|
|
51
|
+
const size = Math.max(1, Math.floor(maxBindValues / (fieldsPerRecord || 1)));
|
|
66
52
|
const chunks = [];
|
|
67
|
-
for (let start = 0; start <
|
|
68
|
-
chunks.push(
|
|
53
|
+
for (let start = 0; start < group.length; start += size) {
|
|
54
|
+
chunks.push(group.slice(start, start + size));
|
|
69
55
|
}
|
|
70
56
|
return chunks;
|
|
71
57
|
}
|
|
@@ -186,17 +172,6 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
186
172
|
this.dialect.findPerParent(ctx, entity, q, partition);
|
|
187
173
|
return this.hydrateRows(entity, q, await this.all(ctx.sql, ctx.values));
|
|
188
174
|
}
|
|
189
|
-
/**
|
|
190
|
-
* One statement for both: the page carries its own unpaged total in an extra column. An empty page
|
|
191
|
-
* has no row to carry it, which is the one case still needing a count of its own - a `$skip` past
|
|
192
|
-
* the end, or a filter nothing matched.
|
|
193
|
-
*
|
|
194
|
-
* A `$required` relation needs no special case: the window counts what the INNER JOIN left, which
|
|
195
|
-
* is exactly the total a caller of a filtered read is asking for. A `$lock` is the one clause an
|
|
196
|
-
* engine may refuse to have in the same statement, which {@link AbstractSqlDialect.supportsWindowWithRowLock}
|
|
197
|
-
* answers; where it does, the total comes from a count of its own. A `$distinct` read needs one
|
|
198
|
-
* too, and a deduplicating one: see {@link AbstractSqlDialect.countDistinct}.
|
|
199
|
-
*/
|
|
200
175
|
/**
|
|
201
176
|
* How to count when the total cannot ride along in the read's own `COUNT(*) OVER ()` column, or
|
|
202
177
|
* `undefined` when it can. Two clauses rule the window out: `$distinct`, because a window counts
|
|
@@ -212,6 +187,17 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
212
187
|
}
|
|
213
188
|
return undefined;
|
|
214
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* One statement for both: the page carries its own unpaged total in an extra column. An empty page
|
|
192
|
+
* has no row to carry it, which is the one case still needing a count of its own - a `$skip` past
|
|
193
|
+
* the end, or a filter nothing matched.
|
|
194
|
+
*
|
|
195
|
+
* A `$required` relation needs no special case: the window counts what the INNER JOIN left, which
|
|
196
|
+
* is exactly the total a caller of a filtered read is asking for. A `$lock` is the one clause an
|
|
197
|
+
* engine may refuse to have in the same statement, which {@link AbstractSqlDialect.supportsWindowWithRowLock}
|
|
198
|
+
* answers; where it does, the total comes from a count of its own. A `$distinct` read needs one
|
|
199
|
+
* too, and a deduplicating one: see {@link AbstractSqlDialect.countDistinct}.
|
|
200
|
+
*/
|
|
215
201
|
async internalFindManyAndCount(entity, q, opts) {
|
|
216
202
|
const separately = this.countedSeparately(entity, q, opts);
|
|
217
203
|
if (separately) {
|
|
@@ -359,24 +345,15 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
359
345
|
}
|
|
360
346
|
return res;
|
|
361
347
|
}
|
|
362
|
-
async internalInsertMany(entity,
|
|
363
|
-
if (!payload?.length) {
|
|
364
|
-
return [];
|
|
365
|
-
}
|
|
366
|
-
payload = clone(payload);
|
|
348
|
+
async internalInsertMany(entity, rows) {
|
|
367
349
|
const meta = getMeta(entity);
|
|
368
|
-
// What comes back is one column's value, so
|
|
369
|
-
//
|
|
370
|
-
// is several columns.
|
|
350
|
+
// What comes back is one column's value, so nothing is read back for a composite: its rows
|
|
351
|
+
// already carry every column of it. `sole` is what keeps every id path off such a key.
|
|
371
352
|
const [idKey] = meta.ids;
|
|
372
353
|
const sole = meta.ids.length === 1;
|
|
373
354
|
const idField = sole ? meta.fields[idKey] : undefined;
|
|
374
355
|
const generatedKey = !!idField && isAutoIncrement(idField, true);
|
|
375
|
-
const
|
|
376
|
-
for (const group of partitionBySuppliedId(payload, idKey)) {
|
|
377
|
-
// Per group, not per batch: the two carry different columns - one names the key, one does not -
|
|
378
|
-
// so a budget taken over their union would under-fill the statement that is missing one.
|
|
379
|
-
const chunkSize = bindBudgetChunkSize(meta, group.map((index) => payload[index]), this.dialect.maxBindValues);
|
|
356
|
+
for (const group of partitionBySuppliedId(rows, idKey)) {
|
|
380
357
|
// RETURNING-based ids are exact per row. Header-derived ones (LAST_INSERT_ID / lastInsertRowid
|
|
381
358
|
// arithmetic) are only sound when the key is database-generated and every row *in this
|
|
382
359
|
// statement* left it to the database. That is a property of the statement, not of the batch:
|
|
@@ -385,28 +362,26 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
385
362
|
// most one extra statement and keeps each of them inferable.
|
|
386
363
|
const idsReliable = sole &&
|
|
387
364
|
(this.dialect.insertIdSource === 'returning' ||
|
|
388
|
-
(generatedKey && group.every((index) =>
|
|
365
|
+
(generatedKey && group.every((index) => rows[index][idKey] === undefined)));
|
|
389
366
|
// Inferring multiple ids from the single header id (MySQL) assumes a known stride; a clustered
|
|
390
367
|
// server may set `auto_increment_increment` > 1, so probe it (once, cached) before inferring.
|
|
391
368
|
if (idsReliable && group.length > 1 && this.dialect.insertIdSource === 'firstId') {
|
|
392
369
|
this.#insertIdIncrement ??= await this.loadInsertIdIncrement();
|
|
393
370
|
}
|
|
394
|
-
|
|
395
|
-
|
|
371
|
+
// Per group, not per batch: the two carry different columns - one names the key, one does not -
|
|
372
|
+
// so a budget taken over their union would under-fill the statement that is missing one.
|
|
373
|
+
for (const indexes of chunkByBindBudget(meta, rows, group, this.dialect.maxBindValues)) {
|
|
396
374
|
const ctx = this.dialect.createContext();
|
|
397
|
-
this.dialect.insert(ctx, entity, indexes.map((index) =>
|
|
375
|
+
this.dialect.insert(ctx, entity, indexes.map((index) => rows[index]));
|
|
398
376
|
const { ids = [] } = await this.run(ctx.sql, ctx.values);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
it[idKey] ??= ids[position];
|
|
377
|
+
if (idsReliable) {
|
|
378
|
+
for (let position = 0; position < indexes.length; position++) {
|
|
379
|
+
rows[indexes[position]][idKey] ??= ids[position];
|
|
403
380
|
}
|
|
404
|
-
|
|
405
|
-
});
|
|
381
|
+
}
|
|
406
382
|
}
|
|
407
383
|
}
|
|
408
|
-
await this.insertRelations(entity,
|
|
409
|
-
return payloadIds;
|
|
384
|
+
await this.insertRelations(entity, rows);
|
|
410
385
|
}
|
|
411
386
|
async internalUpdateMany(entity, q, payload, opts) {
|
|
412
387
|
payload = clone(payload);
|
|
@@ -418,7 +393,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
418
393
|
if (!ids.length) {
|
|
419
394
|
return 0;
|
|
420
395
|
}
|
|
421
|
-
target = { $where: ids };
|
|
396
|
+
target = { $where: whereIds(getMeta(entity), ids) };
|
|
422
397
|
}
|
|
423
398
|
const ctx = this.dialect.createContext();
|
|
424
399
|
this.dialect.update(ctx, entity, target, payload, opts);
|
|
@@ -446,8 +421,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
446
421
|
// One statement per shape, each split again to stay inside the bind budget. Grouping first is
|
|
447
422
|
// what makes the budget arithmetic right: every row of a group carries the same columns, so the
|
|
448
423
|
// `DO UPDATE SET` resolves to non-binding `EXCLUDED` references rather than inlined values.
|
|
449
|
-
const
|
|
450
|
-
const statements = (groups ?? [payload]).flatMap((group) => chunkByBindBudget(meta, group, this.dialect.maxBindValues));
|
|
424
|
+
const statements = groupByInsertShape(meta, payload).flatMap((group) => chunkByBindBudget(meta, payload, group, this.dialect.maxBindValues));
|
|
451
425
|
if (statements.length === 1) {
|
|
452
426
|
return this.runUpsert(entity, conflictPaths, payload);
|
|
453
427
|
}
|
|
@@ -459,31 +433,41 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
459
433
|
// than one statement; `transaction` is re-entrant, so this is free inside a caller's own.
|
|
460
434
|
return this.transaction(async () => {
|
|
461
435
|
let changes = 0;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
436
|
+
// Placed by index, since grouping by shape reorders the rows. A statement reporting fewer ids
|
|
437
|
+
// than it wrote places none.
|
|
438
|
+
const ids = new Array(payload.length);
|
|
439
|
+
for (const indexes of statements) {
|
|
440
|
+
const { changes: written = 0, ids: reported } = await this.runUpsert(entity, conflictPaths, indexes.map((index) => payload[index]));
|
|
441
|
+
changes += written;
|
|
442
|
+
if (reported?.length === indexes.length) {
|
|
443
|
+
for (let position = 0; position < indexes.length; position++) {
|
|
444
|
+
ids[indexes[position]] = reported[position];
|
|
445
|
+
}
|
|
468
446
|
}
|
|
469
447
|
}
|
|
470
|
-
// No `created
|
|
471
|
-
return
|
|
448
|
+
// No `created`: it speaks for a single statement, and there were several.
|
|
449
|
+
return { changes, ids };
|
|
472
450
|
});
|
|
473
451
|
}
|
|
474
452
|
async runUpsert(entity, conflictPaths, payload) {
|
|
453
|
+
const meta = getMeta(entity);
|
|
454
|
+
// Asked first: the statement fills an `onInsert` key into these rows whether it inserts them or not.
|
|
455
|
+
const unnamed = meta.ids.length === 1 && payload.some((row) => !namesKey(meta, row));
|
|
475
456
|
const ctx = this.dialect.createContext();
|
|
476
457
|
this.dialect.upsert(ctx, entity, conflictPaths, payload);
|
|
477
458
|
const result = await this.run(ctx.sql, ctx.values);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
459
|
+
const ordered = payload.length === 1 || (this.dialect.insertIdSource === 'returning' && this.dialect.upsertReturningOrdered);
|
|
460
|
+
if (ordered && result.ids?.length === payload.length) {
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
// The statement's ids name its rows only in order and for every one. A MySQL batch reports a
|
|
464
|
+
// weighted count (1=insert, 2=update) instead, CockroachDB and SQL Server answer out of order, and
|
|
465
|
+
// `DO NOTHING` skips rows, so there the ids are read back by the conflict columns.
|
|
466
|
+
const { changes } = result;
|
|
467
|
+
const created = payload.length === 1 ? result.created : undefined;
|
|
468
|
+
return unnamed
|
|
469
|
+
? { changes, created, ids: await this.idsByConflict(entity, conflictPaths, payload) }
|
|
470
|
+
: { changes, created };
|
|
487
471
|
}
|
|
488
472
|
async internalDeleteMany(entity, q, opts) {
|
|
489
473
|
const meta = getMeta(entity);
|
|
@@ -507,7 +491,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
507
491
|
// outright by any schema that declares the constraint without `ON DELETE CASCADE`.
|
|
508
492
|
await this.deleteRelations(entity, ids, opts);
|
|
509
493
|
const deleteCtx = this.dialect.createContext();
|
|
510
|
-
this.dialect.delete(deleteCtx, entity, { $where: ids }, opts);
|
|
494
|
+
this.dialect.delete(deleteCtx, entity, { $where: whereIds(meta, ids) }, opts);
|
|
511
495
|
const { changes = 0 } = await this.run(deleteCtx.sql, deleteCtx.values);
|
|
512
496
|
return changes;
|
|
513
497
|
}
|
|
@@ -213,8 +213,8 @@ const ENGINE_TYPES = {
|
|
|
213
213
|
},
|
|
214
214
|
// SQLite uses affinity, so no size variants.
|
|
215
215
|
sqlite: { scalars: withVectorType(SQLITE_SCALAR_MAP, 'TEXT') },
|
|
216
|
-
//
|
|
217
|
-
mssql: { scalars: withVectorType(MSSQL_SCALAR_MAP, '
|
|
216
|
+
// 2025 and up; below that the server refuses the type rather than storing it as text.
|
|
217
|
+
mssql: { scalars: withVectorType(MSSQL_SCALAR_MAP, 'VECTOR'), sizes: MSSQL_SIZES },
|
|
218
218
|
mongodb: { scalars: withVectorType(MONGO_SCALAR_MAP, 'array') },
|
|
219
219
|
};
|
|
220
220
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { getMeta, soleIdOf } from '../entity/metadata/definition.js';
|
|
9
9
|
import { ddlText } from '../util/ddlExpression.util.js';
|
|
10
|
-
import {
|
|
10
|
+
import { isInlinedExpression } from '../util/field.util.js';
|
|
11
11
|
import { isSoleIdField } from '../util/field.util.js';
|
|
12
12
|
import { isAutoIncrement } from '../util/field.util.js';
|
|
13
13
|
import { derivedForeignKeyName, derivedIndexName, qualifyName } from '../util/sql.util.js';
|
|
@@ -101,7 +101,7 @@ function addTableFromEntity(ctx, meta) {
|
|
|
101
101
|
isPrimaryKey,
|
|
102
102
|
isAutoIncrement: isAutoIncrement(field, isSoleKey),
|
|
103
103
|
isUnique: field.unique ?? false,
|
|
104
|
-
generatedAs: ddlText(
|
|
104
|
+
generatedAs: ddlText(field.computed, `the computed column '${columnName}'`),
|
|
105
105
|
comment: field.comment,
|
|
106
106
|
enum: field.enum,
|
|
107
107
|
table,
|
|
@@ -202,16 +202,16 @@ function addIndexesFromEntity(ctx, meta) {
|
|
|
202
202
|
addCompositeIndex(ctx, table, meta, idxMeta);
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
|
-
/**
|
|
206
|
-
* One `@Index([...])`. Its entries keep the authored form (expression, prefix length, order) with
|
|
207
|
-
* names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
|
|
208
|
-
* subset, which is what diffing and introspection compare.
|
|
209
|
-
*/
|
|
210
205
|
/** An `include` column is named like any other, so a naming strategy has to reach it too. */
|
|
211
206
|
function resolveIncludeColumn(ctx, meta, column) {
|
|
212
207
|
const field = meta.fields[column];
|
|
213
208
|
return field ? ctx.resolveColumnName(column, field) : column;
|
|
214
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* One `@Index([...])`. Its entries keep the authored form (expression, prefix length, order) with
|
|
212
|
+
* names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
|
|
213
|
+
* subset, which is what diffing and introspection compare.
|
|
214
|
+
*/
|
|
215
215
|
function addCompositeIndex(ctx, table, meta, idxMeta) {
|
|
216
216
|
// An entry survives if it is an expression (nothing to resolve) or names a column that exists;
|
|
217
217
|
// an index left with none is dropped, the same as one naming only unknown columns always was.
|
|
@@ -11,7 +11,6 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
11
11
|
indexIfNotExists: true,
|
|
12
12
|
schemas: false, // SQLite's namespaces are attached database files, not declared objects
|
|
13
13
|
dropTableCascade: false,
|
|
14
|
-
renameColumn: true,
|
|
15
14
|
foreignKeyAlter: false, // SQLite does not support adding FKs to existing tables
|
|
16
15
|
primaryKeyAlter: false, // nor changing a key: the only route is rebuilding the table
|
|
17
16
|
generatedColumnAdd: false, // accepted in a CREATE TABLE, rejected in an ALTER
|
|
@@ -21,7 +20,6 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
21
20
|
supportsTimestamptz: false,
|
|
22
21
|
stringSizing: 'text',
|
|
23
22
|
supportsUnsigned: false,
|
|
24
|
-
multipleCascadePaths: true,
|
|
25
23
|
serverSideCursors: false,
|
|
26
24
|
};
|
|
27
25
|
dialectName = 'sqlite';
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -89,7 +89,6 @@ export interface EngineFeatures {
|
|
|
89
89
|
*/
|
|
90
90
|
readonly schemas: boolean;
|
|
91
91
|
readonly dropTableCascade: boolean;
|
|
92
|
-
readonly renameColumn: boolean;
|
|
93
92
|
readonly foreignKeyAlter: boolean;
|
|
94
93
|
/**
|
|
95
94
|
* Whether a table's primary key can be changed on an existing table. False on SQLite, whose only
|
|
@@ -140,13 +139,6 @@ export interface EngineFeatures {
|
|
|
140
139
|
readonly stringSizing: 'text' | 'bounded-text' | 'varchar';
|
|
141
140
|
/** Whether the engine has unsigned integers, so `@Field({ unsigned: true })` reaches the column. */
|
|
142
141
|
readonly supportsUnsigned: boolean;
|
|
143
|
-
/**
|
|
144
|
-
* Whether one table can be reached by two cascading foreign-key paths. SQL Server refuses the
|
|
145
|
-
* constraint outright ("may cause cycles or multiple cascade paths", error 1785) and Oracle
|
|
146
|
-
* likewise, so a cascade is downgraded to `NO ACTION` there rather than emitting DDL the engine
|
|
147
|
-
* rejects - which would otherwise fail on any diamond-shaped schema at create time.
|
|
148
|
-
*/
|
|
149
|
-
readonly multipleCascadePaths: boolean;
|
|
150
142
|
/**
|
|
151
143
|
* Whether the engine has SQL-level cursors (`DECLARE`/`FETCH FORWARD`/`CLOSE`), which is how a
|
|
152
144
|
* driver with no cursor API of its own still streams a result set instead of buffering it - see
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -359,11 +359,6 @@ export type FieldOptions<V = TsTypeOf<FieldType>> = {
|
|
|
359
359
|
* @example `@Field({ type: String, enum: ['draft', 'paid'] as const })`
|
|
360
360
|
*/
|
|
361
361
|
readonly enum?: EnumValues;
|
|
362
|
-
/**
|
|
363
|
-
* @deprecated Renamed to {@link FieldOptions.computed}, which also takes `stored`. `npx uql-codemod`
|
|
364
|
-
* rewrites it. Giving both throws.
|
|
365
|
-
*/
|
|
366
|
-
readonly virtual?: QueryRaw;
|
|
367
362
|
/**
|
|
368
363
|
* An expression the database computes, rather than a value the caller writes. Never part of an
|
|
369
364
|
* insert or update either way.
|
|
@@ -818,12 +813,6 @@ export type EntityMeta<E> = {
|
|
|
818
813
|
/** The revision `getMeta` last finalized, which is what makes finalizing idempotent and re-entrant. */
|
|
819
814
|
processedAt?: number;
|
|
820
815
|
};
|
|
821
|
-
/**
|
|
822
|
-
* Configurable options for an entity (`@Entity()` / `defineEntity`).
|
|
823
|
-
*
|
|
824
|
-
* Optional `fields`, `relations`, `indexes`, and `hooks` register metadata in one call for
|
|
825
|
-
* decorator-free setups. Omit them when using `@Field` / `@ManyToOne` / etc.
|
|
826
|
-
*/
|
|
827
816
|
/**
|
|
828
817
|
* A table-level `CHECK`. The expression is `raw` with no interpolation, like an index expression:
|
|
829
818
|
* this is DDL, so there is no placeholder a bound value could go into.
|
|
@@ -843,6 +832,12 @@ export type EntityMembers = {
|
|
|
843
832
|
readonly relations?: Readonly<Record<string, RelationOptions | undefined>>;
|
|
844
833
|
readonly hooks?: Readonly<Partial<Record<HookEvent, readonly string[]>>>;
|
|
845
834
|
};
|
|
835
|
+
/**
|
|
836
|
+
* Configurable options for an entity (`@Entity()` / `defineEntity`).
|
|
837
|
+
*
|
|
838
|
+
* Optional `fields`, `relations`, `indexes`, and `hooks` register metadata in one call for
|
|
839
|
+
* decorator-free setups. Omit them when using `@Field` / `@ManyToOne` / etc.
|
|
840
|
+
*/
|
|
846
841
|
export type EntityOptions<E = unknown> = {
|
|
847
842
|
readonly name?: string;
|
|
848
843
|
/**
|
package/dist/type/migration.d.ts
CHANGED
|
@@ -372,9 +372,6 @@ export interface SchemaIntrospector {
|
|
|
372
372
|
* the database side never reports it, and no migration can close the gap.
|
|
373
373
|
*/
|
|
374
374
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
375
|
-
/**
|
|
376
|
-
* Introspect entire database schema and return SchemaAST.
|
|
377
|
-
*/
|
|
378
375
|
/** The whole database, or just the tables named. Names nothing matches are left out. */
|
|
379
376
|
introspect(tables?: readonly string[]): Promise<SchemaAST>;
|
|
380
377
|
/**
|
package/dist/type/query.d.ts
CHANGED
|
@@ -59,17 +59,17 @@ export type QueryExclude<E> = QuerySelect<E>;
|
|
|
59
59
|
export type QueryPopulate<E> = {
|
|
60
60
|
[K in RelationKey<E>]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* The key a read carries its relation tallies under. One spelling for the type and the runtime that
|
|
64
|
+
* fills it: they sit in different modules, so a drift would type-check and answer `undefined`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const COUNT_RESULT_KEY = "_count";
|
|
62
67
|
/**
|
|
63
68
|
* How many rows each named relation holds per parent, `true` for all of them or a filter to narrow
|
|
64
69
|
* which ones count. One statement per relation named here, batched over every parent at once, so it
|
|
65
70
|
* stays flat however many rows the read returned. Comes back under `_count`, which keeps it clear of
|
|
66
71
|
* a relation of the same name that `$populate` filled with rows.
|
|
67
72
|
*/
|
|
68
|
-
/**
|
|
69
|
-
* The key a read carries its relation tallies under. One spelling for the type and the runtime that
|
|
70
|
-
* fills it: they sit in different modules, so a drift would type-check and answer `undefined`.
|
|
71
|
-
*/
|
|
72
|
-
export declare const COUNT_RESULT_KEY = "_count";
|
|
73
73
|
export type QueryCount<E> = {
|
|
74
74
|
[K in ToManyRelationKey<E>]?: BooleanLike | QueryFilter<RelationTarget<E[K]>>;
|
|
75
75
|
};
|
|
@@ -141,9 +141,16 @@ type ToOneRelationKey<E> = {
|
|
|
141
141
|
}[RelationKey<E>];
|
|
142
142
|
/** The relation names a parent holds many rows of, which a populated query fills with a list. */
|
|
143
143
|
type ToManyRelationKey<E> = Exclude<RelationKey<E>, ToOneRelationKey<E>>;
|
|
144
|
+
/**
|
|
145
|
+
* Ordering parents by how many rows a to-many relation holds - "the ten users with the most posts".
|
|
146
|
+
* The tally is computed per parent as a correlated count, never by loading the rows.
|
|
147
|
+
*/
|
|
148
|
+
export type QuerySortByCount = {
|
|
149
|
+
$count: QuerySortDirection;
|
|
150
|
+
};
|
|
144
151
|
/**
|
|
145
152
|
* sort by map - supports field keys, JSON dot-notation paths (restricted to real JSON fields,
|
|
146
|
-
* like `
|
|
153
|
+
* like `QueryWhere`), relation sort via nested objects, and vector similarity search on
|
|
147
154
|
* `number[]` fields. `Vector` is what confines a vector search to the level the statement ranks:
|
|
148
155
|
* the queried entity. A relation of it is joined in one row at a time, so there is nothing to rank
|
|
149
156
|
* there - the SQL dialects throw, and MongoDB would quietly drop it, so this is its only guard.
|
|
@@ -153,13 +160,6 @@ type ToManyRelationKey<E> = Exclude<RelationKey<E>, ToOneRelationKey<E>>;
|
|
|
153
160
|
* against an intersection is repeated per constituent, which made this the single most expensive
|
|
154
161
|
* type in the package to check.
|
|
155
162
|
*/
|
|
156
|
-
/**
|
|
157
|
-
* Ordering parents by how many rows a to-many relation holds - "the ten users with the most posts".
|
|
158
|
-
* The tally is computed per parent as a correlated count, never by loading the rows.
|
|
159
|
-
*/
|
|
160
|
-
export type QuerySortByCount = {
|
|
161
|
-
$count: QuerySortDirection;
|
|
162
|
-
};
|
|
163
163
|
export type QuerySortMap<E, Vector extends boolean = true> = {
|
|
164
164
|
[K in FieldKey<E> | JsonFieldPaths<E> | RelationKey<E>]?: K extends RelationKey<E> ? IsMany<E[K]> extends true ? QuerySortByCount : QuerySortMap<RelationTarget<E[K]>, false> : K extends FieldKey<E> ? Vector extends true ? NonNullable<E[K]> extends readonly number[] ? QuerySortValue : QuerySortDirection : QuerySortDirection : QuerySortDirection;
|
|
165
165
|
};
|
package/dist/type/query.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How many rows each named relation holds per parent, `true` for all of them or a filter to narrow
|
|
3
|
-
* which ones count. One statement per relation named here, batched over every parent at once, so it
|
|
4
|
-
* stays flat however many rows the read returned. Comes back under `_count`, which keeps it clear of
|
|
5
|
-
* a relation of the same name that `$populate` filled with rows.
|
|
6
|
-
*/
|
|
7
1
|
/**
|
|
8
2
|
* The key a read carries its relation tallies under. One spelling for the type and the runtime that
|
|
9
3
|
* fills it: they sit in different modules, so a drift would type-check and answer `undefined`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FieldKey, JsonFieldPaths, JsonFieldPathValue, RelationKey, RelationTarget } from './entity.js';
|
|
2
2
|
import type { QueryRaw } from './queryRaw.js';
|
|
3
3
|
import type { ExpandScalar, IsMany, QueryComparableScalar, Scalar } from './utility.js';
|
|
4
4
|
import type { QueryVectorQuery } from './vector.js';
|
|
@@ -20,12 +20,6 @@ export type QueryTextSearchOptions<E> = {
|
|
|
20
20
|
*/
|
|
21
21
|
$config?: string;
|
|
22
22
|
};
|
|
23
|
-
/**
|
|
24
|
-
* comparison by fields.
|
|
25
|
-
*/
|
|
26
|
-
export type QueryWhereFieldMap<E> = {
|
|
27
|
-
[K in FieldKey<E>]?: QueryWhereFieldValue<E[K]>;
|
|
28
|
-
};
|
|
29
23
|
/**
|
|
30
24
|
* Field comparison, JSON dot-path access, and relation filtering - all fully typed.
|
|
31
25
|
* JSON dot-paths are restricted to real JSON fields, and typed payloads type each path's value
|
|
@@ -37,9 +31,12 @@ export type QueryWhereFieldMap<E> = {
|
|
|
37
31
|
* {@link QuerySortMap} is: the sets are disjoint, and an assignability check against an
|
|
38
32
|
* intersection is repeated per constituent, which every `$where` in a codebase pays. The root
|
|
39
33
|
* operators stay a separate member - they are a fixed shape, not keyed off the entity.
|
|
34
|
+
*
|
|
35
|
+
* An object and nothing else: in a union with ids or lists, TypeScript reports a wrong value against
|
|
36
|
+
* the whole `$where` instead of the key holding it. Ids are `{ id: 1 }`, or the by-id methods.
|
|
40
37
|
*/
|
|
41
|
-
export type
|
|
42
|
-
[K in FieldKey<E> | RelationKey<E> | JsonFieldPaths<E>]?: K extends FieldKey<E> ? QueryWhereFieldValue<E[K]> : K extends RelationKey<E> ?
|
|
38
|
+
export type QueryWhere<E> = QueryWhereRootOperator<E> & {
|
|
39
|
+
[K in FieldKey<E> | RelationKey<E> | JsonFieldPaths<E>]?: K extends FieldKey<E> ? QueryWhereFieldValue<E[K]> : K extends RelationKey<E> ? QueryWhere<RelationTarget<E[K]>> | QueryRelationSizeFilter : QueryWhereFieldValue<JsonFieldPathValue<E, K & string>>;
|
|
43
40
|
};
|
|
44
41
|
/**
|
|
45
42
|
* Filter a to-many relation by its row count.
|
|
@@ -332,14 +329,5 @@ export type QueryWhereFieldValue<T> = T | (undefined extends T ? null : never) |
|
|
|
332
329
|
/**
|
|
333
330
|
* query filter array - the value every {@link QueryGroupOp} takes.
|
|
334
331
|
*/
|
|
335
|
-
export type QueryWhereArray<E> = (
|
|
336
|
-
/**
|
|
337
|
-
* query filter.
|
|
338
|
-
*/
|
|
339
|
-
/**
|
|
340
|
-
* `EntityId` rather than `IdValue`: a by-id method reduces to `$where: id`, and a composite key is
|
|
341
|
-
* addressed by an object carrying every key. That object is a where map naming those columns, so the
|
|
342
|
-
* two spellings meet here rather than needing a conversion.
|
|
343
|
-
*/
|
|
344
|
-
export type QueryWhere<E> = EntityId<E> | EntityId<E>[] | QueryWhereMap<E> | QueryWhereArray<E> | QueryRaw;
|
|
332
|
+
export type QueryWhereArray<E> = (QueryWhere<E> | QueryRaw)[];
|
|
345
333
|
export {};
|
|
@@ -126,7 +126,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
126
126
|
* on MySQL/SQLite they are inferred from the driver header, which is only reliable for
|
|
127
127
|
* auto-increment keys in batches without explicit IDs - otherwise those entries are
|
|
128
128
|
* `undefined` rather than potentially wrong values. A composite key is never one the statement
|
|
129
|
-
* reports, so those rows are named
|
|
129
|
+
* reports, so those rows are named as written, `onInsert` columns included.
|
|
130
130
|
* @param entity the entity to persist on
|
|
131
131
|
* @param payload the data to be persisted
|
|
132
132
|
* @return the IDs
|
|
@@ -137,7 +137,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
137
137
|
* @param entity the entity to persist on
|
|
138
138
|
* @param conflictPaths the keys to use for the unique search
|
|
139
139
|
* @param payload the data to be persisted
|
|
140
|
-
* @return
|
|
140
|
+
* @return the id and whether it was created; see {@link QueryUpsertOneResult}
|
|
141
141
|
*/
|
|
142
142
|
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
|
|
143
143
|
/**
|
|
@@ -145,7 +145,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
145
145
|
* @param entity the entity to persist on
|
|
146
146
|
* @param conflictPaths the keys to use for the unique search
|
|
147
147
|
* @param payload the data to be persisted
|
|
148
|
-
* @return
|
|
148
|
+
* @return the ids, in payload order; see {@link QueryUpsertManyResult}
|
|
149
149
|
*/
|
|
150
150
|
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
151
151
|
/**
|
package/dist/type/vector.d.ts
CHANGED
|
@@ -58,13 +58,15 @@ export type WithDistance<E, K extends string = '_distance'> = E & Record<K, numb
|
|
|
58
58
|
*
|
|
59
59
|
* `opsSuffix` rides along on the operator form because pgvector's index operator class is named from
|
|
60
60
|
* the same metric (`vector_cosine_ops`): keeping them together is what stops a dialect from having
|
|
61
|
-
* the operator but not the class it indexes with.
|
|
61
|
+
* the operator but not the class it indexes with. `metricArg` is for the engine with one function
|
|
62
|
+
* taking the metric by name: SQL Server's `VECTOR_DISTANCE('cosine', a, b)`.
|
|
62
63
|
*/
|
|
63
64
|
export type VectorMetric = {
|
|
64
65
|
readonly op: string;
|
|
65
66
|
readonly opsSuffix: string;
|
|
66
67
|
} | {
|
|
67
68
|
readonly fn: string;
|
|
69
|
+
readonly metricArg?: string;
|
|
68
70
|
};
|
|
69
71
|
/** The operator form, for the pgvector-family dialects whose index DDL also needs `opsSuffix`. */
|
|
70
72
|
export type VectorOperatorMetric = Extract<VectorMetric, {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CascadeType, type EntityData, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryVectorSearch, type QueryWhere, type
|
|
1
|
+
import { type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload } from '../type/index.js';
|
|
2
2
|
export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
|
|
3
3
|
export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: EntityData<E>, callbackKey: CallbackKey): FieldKey<E>[];
|
|
4
4
|
/** Appends `record`'s not-yet-`seen` insertable keys (real, caller-written, defined value) to `keys`. */
|
|
@@ -35,7 +35,8 @@ export declare function getSoftDeleteValue(field: FieldOptions): string | number
|
|
|
35
35
|
}[] | readonly number[] | Uint8Array<ArrayBufferLike> | {
|
|
36
36
|
readonly __json?: never;
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
/** Fills each field `callbackKey` generates on `payload` in place, where the caller left it unset. */
|
|
39
|
+
export declare function fillOnFields<E, R extends EntityData<E> | UpdatePayload<E>>(meta: EntityMeta<E>, payload: R | R[], callbackKey: CallbackKey): R[];
|
|
39
40
|
/**
|
|
40
41
|
* The relation keys present in `payload` whose cascade configuration allows `action`. Only
|
|
41
42
|
* `payload`'s keys are read, so any keys-bearing object works (an entity, an update payload,
|
|
@@ -90,13 +91,16 @@ export declare function findVectorIndex<E>(meta: EntityMeta<E>, key: string): En
|
|
|
90
91
|
export declare function hasVectorNear(where: unknown): boolean;
|
|
91
92
|
/** Type guard: checks whether an update payload value is a JSON operator object. */
|
|
92
93
|
export declare function isJsonUpdateOp(value: unknown): value is JsonUpdateOp;
|
|
93
|
-
export declare function augmentWhere<E>(meta: EntityMeta<E>, target?: QueryWhere<E>, source?: QueryWhere<E>): QueryWhere<E>;
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* {@link applyFilters} and {@link augmentWhere} return new objects instead.
|
|
95
|
+
* The `$where` naming rows by key: a bare value names the one key column (refused on a composite), a
|
|
96
|
+
* composite's key map is a `$where` already, and a list is an `IN` of bare values or an OR of maps.
|
|
98
97
|
*/
|
|
99
|
-
export declare function
|
|
98
|
+
export declare function whereIds<E>(meta: EntityMeta<E>, ids: EntityId<E> | EntityId<E>[]): QueryWhere<E>;
|
|
99
|
+
/**
|
|
100
|
+
* Refuses a `$where` that is not a map. Untyped JS and parsed JSON can still pass an id or a list of
|
|
101
|
+
* them, and a scalar read as a map has no keys: the statement would address every row.
|
|
102
|
+
*/
|
|
103
|
+
export declare function assertWhere<E>(meta: EntityMeta<E>, where: unknown): void;
|
|
100
104
|
/** Returns a `QueryOptions.filters` value with the built-in soft-delete filter disabled (used by hard delete). */
|
|
101
105
|
export declare function withoutSoftDeleteFilter(filters: QueryOptions['filters']): QueryOptions['filters'];
|
|
102
106
|
/**
|
|
@@ -112,7 +116,7 @@ export declare function withoutSoftDeleteFilter(filters: QueryOptions['filters']
|
|
|
112
116
|
* (`{}`) resolved to "no restriction" and adds nothing - the escape hatch for trusted cross-tenant
|
|
113
117
|
* work (e.g. a maintenance job running under a `system` context).
|
|
114
118
|
*/
|
|
115
|
-
export declare function applyFilters<E>(meta: EntityMeta<E>, whereMap:
|
|
119
|
+
export declare function applyFilters<E>(meta: EntityMeta<E>, whereMap: QueryWhere<E>, opts?: QueryOptions): QueryWhere<E>;
|
|
116
120
|
/**
|
|
117
121
|
* Parsed entry from a `$group` map - either a raw group key or an aggregate function call.
|
|
118
122
|
*/
|