uql-orm 0.53.0 → 0.55.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/README.md +2 -2
- package/dist/browser/uql-browser.min.js.map +2 -2
- package/dist/bunSql/bunSql.util.d.ts +3 -14
- package/dist/bunSql/bunSql.util.js +33 -56
- package/dist/bunSql/bunSqlQuerier.d.ts +3 -6
- package/dist/bunSql/bunSqlQuerier.js +7 -13
- package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -5
- package/dist/bunSql/bunSqlQuerierPool.js +25 -10
- package/dist/cockroachdb/cockroachDialect.d.ts +2 -5
- package/dist/cockroachdb/cockroachDialect.js +2 -5
- package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -3
- package/dist/cockroachdb/crdbQuerierPool.js +0 -4
- package/dist/cockroachdb/index.d.ts +0 -1
- package/dist/cockroachdb/index.js +0 -1
- package/dist/d1/d1SqliteDialect.d.ts +5 -0
- package/dist/d1/d1SqliteDialect.js +7 -0
- package/dist/dialect/abstractSqlDialect.d.ts +15 -9
- package/dist/dialect/abstractSqlDialect.js +47 -54
- package/dist/dialect/hydrateColumn.js +2 -2
- package/dist/dialect/mergeSqlDialect.d.ts +2 -2
- package/dist/dialect/mergeSqlDialect.js +0 -4
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -3
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -3
- package/dist/dialect/pgLikeSqlDialect.d.ts +5 -0
- package/dist/dialect/pgLikeSqlDialect.js +12 -2
- package/dist/entity/decorator/members.d.ts +3 -10
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +3 -1
- package/dist/entity/metadata/definition.js +8 -4
- package/dist/libsql/index.d.ts +0 -1
- package/dist/libsql/index.js +0 -1
- package/dist/libsql/libsqlQuerierPool.d.ts +3 -5
- package/dist/libsql/libsqlQuerierPool.js +2 -5
- package/dist/maria/mariadbQuerier.d.ts +0 -3
- package/dist/maria/mariadbQuerier.js +6 -7
- package/dist/maria/mariadbQuerierPool.js +4 -7
- package/dist/migrate/builder/migrationBuilder.js +0 -4
- package/dist/migrate/ddl/index.d.ts +3 -3
- package/dist/migrate/ddl/index.js +3 -3
- package/dist/migrate/migrator.d.ts +3 -7
- package/dist/migrate/migrator.js +3 -7
- package/dist/migrate/schemaGenerator.d.ts +0 -13
- package/dist/migrate/schemaGenerator.js +0 -13
- package/dist/migrate/storage/databaseStorage.d.ts +2 -2
- package/dist/migrate/storage/databaseStorage.js +2 -2
- package/dist/mongo/index.d.ts +0 -1
- package/dist/mongo/index.js +0 -1
- package/dist/mongo/mongoDialect.d.ts +0 -1
- package/dist/mongo/mongoDialect.js +3 -14
- package/dist/mongo/mongodbQuerier.d.ts +3 -5
- package/dist/mongo/mongodbQuerier.js +23 -23
- package/dist/mongo/mongodbQuerierPool.d.ts +2 -2
- package/dist/mongo/mongodbQuerierPool.js +2 -2
- package/dist/mssql/mssqlDialect.d.ts +5 -5
- package/dist/mssql/mssqlDialect.js +11 -8
- package/dist/mssql/mssqlQuerier.d.ts +13 -6
- package/dist/mssql/mssqlQuerier.js +30 -75
- package/dist/mssql/mssqlQuerierPool.js +2 -0
- package/dist/mssql/mssqlWireTypes.d.ts +3 -4
- package/dist/mssql/mssqlWireTypes.js +5 -8
- package/dist/mysql/index.d.ts +0 -1
- package/dist/mysql/index.js +0 -1
- package/dist/mysql/mysql2Querier.d.ts +1 -4
- package/dist/mysql/mysql2Querier.js +0 -3
- package/dist/mysql/mysql2QuerierPool.d.ts +2 -2
- package/dist/mysql/mysql2QuerierPool.js +5 -3
- package/dist/neon/index.d.ts +0 -2
- package/dist/neon/index.js +0 -2
- package/dist/neon/neonQuerierPool.d.ts +2 -4
- package/dist/neon/neonQuerierPool.js +2 -6
- package/dist/pglite/index.d.ts +0 -1
- package/dist/pglite/index.js +0 -1
- package/dist/pglite/pgliteQuerier.d.ts +3 -3
- package/dist/pglite/pgliteQuerier.js +1 -1
- package/dist/pglite/pgliteQuerierPool.d.ts +7 -2
- package/dist/pglite/pgliteQuerierPool.js +16 -6
- package/dist/postgres/abstractPgQuerierPool.d.ts +8 -12
- package/dist/postgres/abstractPgQuerierPool.js +8 -6
- package/dist/postgres/index.d.ts +0 -1
- package/dist/postgres/index.js +0 -1
- package/dist/postgres/pgNumericTypes.d.ts +5 -5
- package/dist/postgres/pgNumericTypes.js +11 -7
- package/dist/postgres/pgQuerier.d.ts +22 -4
- package/dist/postgres/pgQuerier.js +29 -2
- package/dist/postgres/pgQuerierPool.d.ts +2 -4
- package/dist/postgres/pgQuerierPool.js +2 -6
- package/dist/postgres/postgresDialect.d.ts +5 -5
- package/dist/postgres/postgresDialect.js +5 -5
- package/dist/postgres/postgresWireDriverCapabilities.d.ts +2 -2
- package/dist/postgres/postgresWireDriverCapabilities.js +2 -2
- package/dist/querier/abstractPoolQuerier.d.ts +1 -1
- package/dist/querier/abstractPoolQuerier.js +1 -1
- package/dist/querier/abstractQuerier.d.ts +17 -22
- package/dist/querier/abstractQuerier.js +80 -57
- package/dist/querier/abstractSqlQuerier.d.ts +20 -13
- package/dist/querier/abstractSqlQuerier.js +96 -100
- package/dist/schema/schemaASTBuilder.js +7 -7
- package/dist/sqlite/hranaQuerier.d.ts +6 -8
- package/dist/sqlite/hranaQuerier.js +13 -30
- package/dist/sqlite/hranaQuerierPool.d.ts +3 -4
- package/dist/sqlite/hranaQuerierPool.js +2 -1
- package/dist/sqlite/localSqliteQuerierPool.d.ts +3 -3
- package/dist/sqlite/localSqliteQuerierPool.js +4 -2
- package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -1
- package/dist/sqlite/nodeSqliteQuerierPool.js +0 -2
- package/dist/sqlite/sqlitePragmas.d.ts +12 -0
- package/dist/sqlite/sqlitePragmas.js +15 -0
- package/dist/sqlite/sqliteQuerierPool.d.ts +0 -5
- package/dist/sqlite/sqliteQuerierPool.js +2 -13
- package/dist/turso/index.d.ts +0 -1
- package/dist/turso/index.js +0 -1
- package/dist/turso/tursoLocalQuerier.d.ts +0 -1
- package/dist/turso/tursoLocalQuerierPool.js +2 -2
- package/dist/turso/tursoQuerierPool.d.ts +1 -3
- package/dist/turso/tursoQuerierPool.js +0 -4
- package/dist/type/dialect.d.ts +1 -1
- package/dist/type/entity.d.ts +6 -11
- package/dist/type/migration.d.ts +0 -3
- package/dist/type/query.d.ts +12 -12
- package/dist/type/query.js +0 -6
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/util/dialect.util.d.ts +4 -3
- package/dist/util/dialect.util.js +2 -1
- 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 +10 -11
- package/dist/util/logger.js +21 -11
- package/dist/util/raw.d.ts +3 -10
- package/dist/util/raw.js +3 -3
- package/dist/util/sql.util.js +2 -2
- package/dist/util/sqlLiteral.js +3 -8
- package/dist/util/string.util.js +2 -6
- package/dist/util/wideNumber.d.ts +14 -0
- package/dist/util/wideNumber.js +24 -0
- package/package.json +1 -1
- package/dist/cockroachdb/crdbQuerier.d.ts +0 -8
- package/dist/cockroachdb/crdbQuerier.js +0 -6
- package/dist/libsql/libsqlQuerier.d.ts +0 -10
- package/dist/libsql/libsqlQuerier.js +0 -10
- package/dist/mongo/mongodbNativeDialect.d.ts +0 -9
- package/dist/mongo/mongodbNativeDialect.js +0 -9
- package/dist/mysql/mysql2Dialect.d.ts +0 -9
- package/dist/mysql/mysql2Dialect.js +0 -9
- package/dist/neon/neonDialect.d.ts +0 -10
- package/dist/neon/neonDialect.js +0 -10
- package/dist/neon/neonQuerier.d.ts +0 -5
- package/dist/neon/neonQuerier.js +0 -3
- package/dist/pglite/pgliteDialect.d.ts +0 -14
- package/dist/pglite/pgliteDialect.js +0 -14
- package/dist/postgres/abstractPgQuerier.d.ts +0 -24
- package/dist/postgres/abstractPgQuerier.js +0 -32
- package/dist/postgres/pgDialect.d.ts +0 -10
- package/dist/postgres/pgDialect.js +0 -10
- package/dist/turso/tursoQuerier.d.ts +0 -10
- package/dist/turso/tursoQuerier.js +0 -10
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { COUNT_ALIAS, TOTAL_ALIAS } from '../dialect/aliases.js';
|
|
2
2
|
import { decodeColumn } from '../dialect/hydrateColumn.js';
|
|
3
|
-
import { getMeta, idOf,
|
|
3
|
+
import { getMeta, idOf, namesKey } from '../entity/index.js';
|
|
4
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';
|
|
@@ -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) {
|
|
@@ -255,18 +241,19 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
255
241
|
// The one path that does not go through `all`/`run`, so it connects on its own: streaming first on
|
|
256
242
|
// a freshly acquired querier used to reach `getConn()` with nothing acquired.
|
|
257
243
|
await this.lazyConnect();
|
|
244
|
+
// No `normalizeValues` here, unlike `all`/`run`: those also take raw SQL, while every value a
|
|
245
|
+
// context holds was normalized as it was bound.
|
|
258
246
|
const ctx = this.dialect.createContext();
|
|
259
247
|
this.dialect.find(ctx, entity, q, opts);
|
|
260
|
-
const normalizedParams = this.dialect.normalizeValues(ctx.values);
|
|
261
248
|
let attrsPaths;
|
|
262
249
|
try {
|
|
263
|
-
for await (const row of this.internalStream(ctx.sql,
|
|
250
|
+
for await (const row of this.internalStream(ctx.sql, ctx.values)) {
|
|
264
251
|
attrsPaths ??= obtainAttrsPaths(row);
|
|
265
252
|
yield this.hydrateFields(entity, unflatObject(row, attrsPaths));
|
|
266
253
|
}
|
|
267
254
|
}
|
|
268
255
|
catch (err) {
|
|
269
|
-
throw enrichError(err, this.logger, ctx.sql,
|
|
256
|
+
throw enrichError(err, this.logger, ctx.sql, ctx.values);
|
|
270
257
|
}
|
|
271
258
|
}
|
|
272
259
|
/**
|
|
@@ -275,8 +262,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
275
262
|
* Drivers with native cursor/streaming APIs (SQLite, Pg) should override this.
|
|
276
263
|
*/
|
|
277
264
|
async *internalStream(query, values) {
|
|
278
|
-
|
|
279
|
-
yield* rows;
|
|
265
|
+
yield* await this.internalAll(query, values);
|
|
280
266
|
}
|
|
281
267
|
/**
|
|
282
268
|
* Turn what a driver returned back into the types the entity declares, for the row and everything
|
|
@@ -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);
|
|
@@ -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);
|
|
@@ -520,35 +504,47 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
520
504
|
throwPendingTransaction();
|
|
521
505
|
}
|
|
522
506
|
await this.lazyConnect();
|
|
523
|
-
|
|
524
|
-
await this.runTransactionCommand(sql);
|
|
525
|
-
}
|
|
507
|
+
await this.internalBegin(opts);
|
|
526
508
|
this.hasPendingTransaction = true;
|
|
527
509
|
});
|
|
528
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Only an end that succeeded ends the transaction. A `COMMIT` that fails can leave it open (SQLite
|
|
513
|
+
* answers `SQLITE_BUSY` and keeps it), so the flag has to stay set for the `catch` in
|
|
514
|
+
* {@link AbstractQuerier.transaction} or {@link AbstractQuerier.release} to roll it back.
|
|
515
|
+
*/
|
|
529
516
|
async commitTransaction() {
|
|
530
517
|
return this.serialize(async () => {
|
|
531
518
|
if (!this.hasPendingTransaction) {
|
|
532
519
|
throwNoPendingTransaction();
|
|
533
520
|
}
|
|
534
|
-
await this.
|
|
521
|
+
await this.internalCommit();
|
|
522
|
+
this.hasPendingTransaction = false;
|
|
535
523
|
});
|
|
536
524
|
}
|
|
537
525
|
async rollbackTransaction() {
|
|
538
526
|
return this.serialize(async () => {
|
|
539
527
|
if (this.hasPendingTransaction) {
|
|
540
|
-
await this.
|
|
528
|
+
await this.internalRollback();
|
|
529
|
+
this.hasPendingTransaction = false;
|
|
541
530
|
}
|
|
542
531
|
});
|
|
543
532
|
}
|
|
544
533
|
/**
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
534
|
+
* How this driver opens, commits and rolls back: the dialect's statements, unless its transactions
|
|
535
|
+
* are objects rather than statements - Hrana's session handle, `mssql`'s `Transaction` - in which
|
|
536
|
+
* case it overrides all three. The bookkeeping around them stays above, written once.
|
|
548
537
|
*/
|
|
549
|
-
async
|
|
550
|
-
|
|
551
|
-
|
|
538
|
+
async internalBegin(opts) {
|
|
539
|
+
for (const sql of this.dialect.getBeginTransactionStatements(opts?.isolationLevel)) {
|
|
540
|
+
await this.runTransactionCommand(sql);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
internalCommit() {
|
|
544
|
+
return this.runTransactionCommand(this.dialect.commitTransactionCommand);
|
|
545
|
+
}
|
|
546
|
+
internalRollback() {
|
|
547
|
+
return this.runTransactionCommand(this.dialect.rollbackTransactionCommand);
|
|
552
548
|
}
|
|
553
549
|
/** Transaction statements skip `timed()`, so they attach their own query context to a failure. */
|
|
554
550
|
async runTransactionCommand(sql) {
|
|
@@ -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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtraOptions
|
|
1
|
+
import type { ExtraOptions } from '../type/index.js';
|
|
2
2
|
import { AbstractSqliteQuerier, type SqliteBindValue } from './abstractSqliteQuerier.js';
|
|
3
3
|
import type { SqliteDialect } from './sqliteDialect.js';
|
|
4
4
|
/**
|
|
@@ -46,14 +46,12 @@ export declare class HranaQuerier extends AbstractSqliteQuerier {
|
|
|
46
46
|
constructor(client: HranaClient, dialect: SqliteDialect, extra?: ExtraOptions | undefined, connection?: HranaQuerierConnectionOptions);
|
|
47
47
|
internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
|
|
48
48
|
internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
|
|
49
|
-
|
|
50
|
-
beginTransaction(_opts?: TransactionOptions): Promise<void>;
|
|
49
|
+
protected internalBegin(): Promise<void>;
|
|
51
50
|
/**
|
|
52
|
-
* Both drop the handle before the call, not after: one that outlived a failed commit
|
|
53
|
-
*
|
|
54
|
-
* there is nothing open.
|
|
51
|
+
* Both drop the handle before the call, not after: one that outlived a failed commit would carry
|
|
52
|
+
* every later statement into a transaction the server may already have ended.
|
|
55
53
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
protected internalCommit(): Promise<void>;
|
|
55
|
+
protected internalRollback(): Promise<void>;
|
|
58
56
|
internalRelease(): Promise<void>;
|
|
59
57
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { throwNoPendingTransaction, throwPendingTransaction } from '../util/index.js';
|
|
2
1
|
import { AbstractSqliteQuerier } from './abstractSqliteQuerier.js';
|
|
3
2
|
/**
|
|
4
3
|
* Querier for SQLite databases reached through a Hrana client.
|
|
@@ -30,38 +29,22 @@ export class HranaQuerier extends AbstractSqliteQuerier {
|
|
|
30
29
|
// the actual row count when rows were returned.
|
|
31
30
|
return this.buildUpdateResult({ rows, changes: rows.length || res.rowsAffected, id: res.lastInsertRowid });
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
async beginTransaction(_opts) {
|
|
37
|
-
return this.serialize(async () => {
|
|
38
|
-
if (this.tx) {
|
|
39
|
-
throwPendingTransaction();
|
|
40
|
-
}
|
|
41
|
-
this.tx = await this.client.transaction('write');
|
|
42
|
-
});
|
|
32
|
+
async internalBegin() {
|
|
33
|
+
this.tx = await this.client.transaction('write');
|
|
43
34
|
}
|
|
44
35
|
/**
|
|
45
|
-
* Both drop the handle before the call, not after: one that outlived a failed commit
|
|
46
|
-
*
|
|
47
|
-
* there is nothing open.
|
|
36
|
+
* Both drop the handle before the call, not after: one that outlived a failed commit would carry
|
|
37
|
+
* every later statement into a transaction the server may already have ended.
|
|
48
38
|
*/
|
|
49
|
-
async
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
async rollbackTransaction() {
|
|
60
|
-
return this.serialize(async () => {
|
|
61
|
-
const tx = this.tx;
|
|
62
|
-
this.tx = undefined;
|
|
63
|
-
await tx?.rollback();
|
|
64
|
-
});
|
|
39
|
+
async internalCommit() {
|
|
40
|
+
const tx = this.tx;
|
|
41
|
+
this.tx = undefined;
|
|
42
|
+
await tx?.commit();
|
|
43
|
+
}
|
|
44
|
+
async internalRollback() {
|
|
45
|
+
const tx = this.tx;
|
|
46
|
+
this.tx = undefined;
|
|
47
|
+
await tx?.rollback();
|
|
65
48
|
}
|
|
66
49
|
async internalRelease() {
|
|
67
50
|
await super.internalRelease();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractSqlQuerierPool } from '../querier/index.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type HranaClient, HranaQuerier } from './hranaQuerier.js';
|
|
3
3
|
import type { SqliteDialect } from './sqliteDialect.js';
|
|
4
4
|
/**
|
|
5
5
|
* Pool for SQLite databases reached over the Hrana wire protocol (`@libsql/client`,
|
|
@@ -10,12 +10,11 @@ import type { SqliteDialect } from './sqliteDialect.js';
|
|
|
10
10
|
* constructor, so building a pool never throws when the optional driver peer is absent, which is what
|
|
11
11
|
* lets a Workers bundle construct one at module scope.
|
|
12
12
|
*/
|
|
13
|
-
export declare abstract class AbstractHranaQuerierPool<
|
|
13
|
+
export declare abstract class AbstractHranaQuerierPool<D extends SqliteDialect> extends AbstractSqlQuerierPool<HranaQuerier, D> {
|
|
14
14
|
private client?;
|
|
15
15
|
/** False when the caller injected their own client, in which case they own its lifecycle. */
|
|
16
16
|
protected readonly ownsClient: boolean;
|
|
17
17
|
protected abstract openClient(): Promise<HranaClient>;
|
|
18
|
-
|
|
19
|
-
getQuerier(): Promise<Q>;
|
|
18
|
+
getQuerier(): Promise<HranaQuerier>;
|
|
20
19
|
end(): Promise<void>;
|
|
21
20
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AbstractSqlQuerierPool } from '../querier/index.js';
|
|
2
|
+
import { HranaQuerier } from './hranaQuerier.js';
|
|
2
3
|
/**
|
|
3
4
|
* Pool for SQLite databases reached over the Hrana wire protocol (`@libsql/client`,
|
|
4
5
|
* `@tursodatabase/serverless/compat`).
|
|
@@ -14,7 +15,7 @@ export class AbstractHranaQuerierPool extends AbstractSqlQuerierPool {
|
|
|
14
15
|
ownsClient = true;
|
|
15
16
|
async getQuerier() {
|
|
16
17
|
this.client ??= await this.openClient();
|
|
17
|
-
return this.
|
|
18
|
+
return new HranaQuerier(this.client, this.dialect, this.extra);
|
|
18
19
|
}
|
|
19
20
|
async end() {
|
|
20
21
|
if (this.ownsClient) {
|
|
@@ -15,13 +15,13 @@ export type LocalSqlitePoolOptions = {
|
|
|
15
15
|
* Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
|
|
16
16
|
* connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
|
|
17
17
|
*
|
|
18
|
-
* Subclasses supply only {@link createDb}:
|
|
19
|
-
* `better-sqlite3`, `bun:sqlite` and `node:sqlite
|
|
18
|
+
* Subclasses supply only {@link createDb}: configuring the connection on the way up - the pragmas,
|
|
19
|
+
* then the extensions - is the same for `better-sqlite3`, `bun:sqlite` and `node:sqlite`.
|
|
20
20
|
*/
|
|
21
21
|
export declare abstract class AbstractLocalSqliteQuerierPool<O extends LocalSqlitePoolOptions> extends AbstractSharedHandleQuerierPool<SqliteDatabase, SqliteQuerier, SqliteDialect> {
|
|
22
22
|
readonly opts?: O | undefined;
|
|
23
23
|
constructor(opts?: O | undefined, extra?: ExtraOptions);
|
|
24
|
-
/** Opens the driver's database
|
|
24
|
+
/** Opens the driver's database, and nothing more: the caller configures it. */
|
|
25
25
|
protected abstract createDb(): Promise<SqliteDatabase>;
|
|
26
26
|
protected openDb(): Promise<SqliteDatabase>;
|
|
27
27
|
protected buildQuerier(db: SqliteDatabase): SqliteQuerier;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
2
|
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
3
3
|
import { SqliteDialect } from './sqliteDialect.js';
|
|
4
|
+
import { applySqlitePragmas } from './sqlitePragmas.js';
|
|
4
5
|
import { SqliteQuerier } from './sqliteQuerier.js';
|
|
5
6
|
/**
|
|
6
7
|
* Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
|
|
7
8
|
* connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
|
|
8
9
|
*
|
|
9
|
-
* Subclasses supply only {@link createDb}:
|
|
10
|
-
* `better-sqlite3`, `bun:sqlite` and `node:sqlite
|
|
10
|
+
* Subclasses supply only {@link createDb}: configuring the connection on the way up - the pragmas,
|
|
11
|
+
* then the extensions - is the same for `better-sqlite3`, `bun:sqlite` and `node:sqlite`.
|
|
11
12
|
*/
|
|
12
13
|
export class AbstractLocalSqliteQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
13
14
|
opts;
|
|
@@ -17,6 +18,7 @@ export class AbstractLocalSqliteQuerierPool extends AbstractSharedHandleQuerierP
|
|
|
17
18
|
}
|
|
18
19
|
async openDb() {
|
|
19
20
|
const db = await this.createDb();
|
|
21
|
+
await applySqlitePragmas(db);
|
|
20
22
|
for (const extension of this.opts?.extensions ?? []) {
|
|
21
23
|
db.loadExtension(extension);
|
|
22
24
|
}
|
|
@@ -23,8 +23,6 @@ export class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool {
|
|
|
23
23
|
// `node:sqlite` refuses `loadExtension` unless the database was opened with this on.
|
|
24
24
|
...(extensions?.length ? { allowExtension: true } : undefined),
|
|
25
25
|
});
|
|
26
|
-
nodeDb.exec('PRAGMA journal_mode = WAL');
|
|
27
|
-
nodeDb.exec('PRAGMA foreign_keys = ON');
|
|
28
26
|
return adaptNodeSqlite(nodeDb);
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SqlitePreparedStatement } from './abstractSqliteQuerier.js';
|
|
2
|
+
/**
|
|
3
|
+
* How every local SQLite connection opens. WAL, so a reader does not wait on the writer, and
|
|
4
|
+
* `foreign_keys`, which SQLite ships off per connection for backward compatibility: without it the
|
|
5
|
+
* constraints uql's own DDL declares are decorative - a declared `onDelete: 'CASCADE'` silently does
|
|
6
|
+
* nothing and a dangling reference is accepted.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SQLITE_PRAGMAS: readonly ['journal_mode = WAL', 'foreign_keys = ON'];
|
|
9
|
+
/** Runs {@link SQLITE_PRAGMAS} through a driver's own statements, whether it answers now or later. */
|
|
10
|
+
export declare function applySqlitePragmas(db: {
|
|
11
|
+
prepare(sql: string): SqlitePreparedStatement | Promise<SqlitePreparedStatement>;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How every local SQLite connection opens. WAL, so a reader does not wait on the writer, and
|
|
3
|
+
* `foreign_keys`, which SQLite ships off per connection for backward compatibility: without it the
|
|
4
|
+
* constraints uql's own DDL declares are decorative - a declared `onDelete: 'CASCADE'` silently does
|
|
5
|
+
* nothing and a dangling reference is accepted.
|
|
6
|
+
*/
|
|
7
|
+
export const SQLITE_PRAGMAS = ['journal_mode = WAL', 'foreign_keys = ON'];
|
|
8
|
+
/** Runs {@link SQLITE_PRAGMAS} through a driver's own statements, whether it answers now or later. */
|
|
9
|
+
export async function applySqlitePragmas(db) {
|
|
10
|
+
for (const pragma of SQLITE_PRAGMAS) {
|
|
11
|
+
const stmt = await db.prepare(`PRAGMA ${pragma}`);
|
|
12
|
+
// `journal_mode` answers with a row and `foreign_keys` with none; `reader` picks the call for each.
|
|
13
|
+
await (stmt.reader ? stmt.all() : stmt.run());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -11,10 +11,5 @@ export type Sqlite3PoolOptions = Options & LocalSqlitePoolOptions;
|
|
|
11
11
|
export declare class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool<Sqlite3PoolOptions> {
|
|
12
12
|
readonly filename: string | Buffer;
|
|
13
13
|
constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions, extra?: ExtraOptions);
|
|
14
|
-
/**
|
|
15
|
-
* SQLite ships with foreign keys unenforced, per connection, for backward compatibility. UQL emits the
|
|
16
|
-
* constraints in its DDL, so leaving them off means a declared `onDelete: 'CASCADE'` silently does
|
|
17
|
-
* nothing and a dangling reference is accepted. Enabled here on every driver, as TypeORM also does.
|
|
18
|
-
*/
|
|
19
14
|
protected createDb(): Promise<SqliteDatabase>;
|
|
20
15
|
}
|