@atscript/db-sqlite 0.1.146 → 0.1.148
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/index.cjs +74 -14
- package/dist/index.d.cts +33 -4
- package/dist/index.d.mts +33 -4
- package/dist/index.mjs +76 -16
- package/package.json +10 -10
package/dist/index.cjs
CHANGED
|
@@ -78,7 +78,9 @@ var BetterSqlite3Driver = class {
|
|
|
78
78
|
registerFunction(name, fn, opts) {
|
|
79
79
|
this.db.function(name, { deterministic: opts?.deterministic ?? false }, fn);
|
|
80
80
|
}
|
|
81
|
+
/** Idempotent: closing an already closed database is a no-op. */
|
|
81
82
|
close() {
|
|
83
|
+
if (!this.db.open) return;
|
|
82
84
|
this.db.close();
|
|
83
85
|
}
|
|
84
86
|
};
|
|
@@ -86,7 +88,8 @@ var BetterSqlite3Driver = class {
|
|
|
86
88
|
//#region src/calendar-bucket.ts
|
|
87
89
|
/**
|
|
88
90
|
* Name of the scalar SQL function the adapter registers on its driver:
|
|
89
|
-
* `atscript_bucket(value, unit, tz, weekStart)` → TEXT `'YYYY-MM-DD'`
|
|
91
|
+
* `atscript_bucket(value, unit, tz, weekStart)` → TEXT `'YYYY-MM-DD'`
|
|
92
|
+
* (`'YYYY-MM-DDTHH:00'` for unit `hour`) or NULL.
|
|
90
93
|
*/
|
|
91
94
|
const SQLITE_BUCKET_FN = "atscript_bucket";
|
|
92
95
|
/**
|
|
@@ -287,6 +290,7 @@ const sqliteDialect = {
|
|
|
287
290
|
},
|
|
288
291
|
calendarBucket: sqliteCalendarBucket,
|
|
289
292
|
jsonExtract: sqliteJsonExtract,
|
|
293
|
+
castDouble: (expr) => `CAST(${expr} AS REAL)`,
|
|
290
294
|
createViewPrefix: "CREATE VIEW IF NOT EXISTS"
|
|
291
295
|
};
|
|
292
296
|
/**
|
|
@@ -438,8 +442,9 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
438
442
|
colDefs.push(`PRIMARY KEY (${pkCols})`);
|
|
439
443
|
}
|
|
440
444
|
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
441
|
-
const
|
|
442
|
-
const
|
|
445
|
+
const { fields, targetFields } = (0, _atscript_db.fkColumns)(fk);
|
|
446
|
+
const localCols = fields.map((f) => `"${esc(f)}"`).join(", ");
|
|
447
|
+
const targetCols = targetFields.map((f) => `"${esc(f)}"`).join(", ");
|
|
443
448
|
let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc(fk.targetTable)}" (${targetCols})`;
|
|
444
449
|
if (fk.onDelete) constraint += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
445
450
|
if (fk.onUpdate) constraint += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
@@ -452,23 +457,28 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
452
457
|
/**
|
|
453
458
|
* Translates a uniqu filter expression into a parameterized SQL WHERE clause.
|
|
454
459
|
*
|
|
460
|
+
* Relational predicates (`$some` / `$none`) render as correlated `EXISTS`
|
|
461
|
+
* subqueries referencing the outer table by `opts.qualifier` (default: the
|
|
462
|
+
* source table name) — a statement with an aliased FROM must pass its alias.
|
|
463
|
+
*
|
|
464
|
+
* @param opts - since 0.1.147
|
|
455
465
|
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
456
466
|
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
457
467
|
*/
|
|
458
|
-
function buildWhere(filter) {
|
|
459
|
-
return (0, _atscript_db_sql_tools.buildWhere)(sqliteDialect, filter);
|
|
468
|
+
function buildWhere(filter, opts) {
|
|
469
|
+
return (0, _atscript_db_sql_tools.buildWhere)(sqliteDialect, filter, opts);
|
|
460
470
|
}
|
|
461
471
|
/**
|
|
462
472
|
* Like {@link buildWhere} but prefixes all column references with a table alias.
|
|
463
473
|
* Produces `alias."col"` instead of `"col"` — needed for JOINed queries (e.g. FTS5 search).
|
|
474
|
+
* Relational predicates correlate to the same alias (since 0.1.147); their
|
|
475
|
+
* own subquery aliases stay unprefixed.
|
|
464
476
|
*/
|
|
465
477
|
function buildPrefixedWhere(alias, filter) {
|
|
466
|
-
return (0, _atscript_db_sql_tools.buildWhere)({
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
}, filter);
|
|
478
|
+
return (0, _atscript_db_sql_tools.buildWhere)(sqliteDialect, filter, {
|
|
479
|
+
columnRef: (name) => `${alias}."${esc(name)}"`,
|
|
480
|
+
qualifier: alias
|
|
481
|
+
});
|
|
472
482
|
}
|
|
473
483
|
//#endregion
|
|
474
484
|
//#region src/tx-gate.ts
|
|
@@ -706,10 +716,18 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
706
716
|
calendarBucketUnits() {
|
|
707
717
|
return this._hasBucketFn ? _atscript_db.ALL_BUCKET_UNITS : super.calendarBucketUnits();
|
|
708
718
|
}
|
|
709
|
-
/** Every aggregate function
|
|
719
|
+
/** Every aggregate function: `countDistinct`, `first` and `last` included. */
|
|
710
720
|
aggregateFns() {
|
|
711
721
|
return _atscript_db.ALL_AGGREGATE_FNS;
|
|
712
722
|
}
|
|
723
|
+
/** Arithmetic in an aggregate `$select` (`{ $expr }`, `{ $fn, $expr }`). */
|
|
724
|
+
supportsAggregateExpressions() {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
/** Computed view columns and first-row joins. */
|
|
728
|
+
viewCapabilities() {
|
|
729
|
+
return _atscript_db.ALL_VIEW_CAPABILITIES;
|
|
730
|
+
}
|
|
713
731
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
714
732
|
_supportsVector;
|
|
715
733
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -865,6 +883,15 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
865
883
|
resolveTableName() {
|
|
866
884
|
return super.resolveTableName(false);
|
|
867
885
|
}
|
|
886
|
+
/**
|
|
887
|
+
* Relational predicates (`$some` / `$none`) render as correlated
|
|
888
|
+
* `[NOT] EXISTS` subqueries — in reads and in mutation filters alike.
|
|
889
|
+
*
|
|
890
|
+
* @since 0.1.147
|
|
891
|
+
*/
|
|
892
|
+
supportsRelationFilters(_mode) {
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
868
895
|
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
869
896
|
supportsNativeForeignKeys() {
|
|
870
897
|
return true;
|
|
@@ -919,6 +946,29 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
919
946
|
};
|
|
920
947
|
});
|
|
921
948
|
}
|
|
949
|
+
supportsInsertIgnore() {
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Per row `INSERT … ON CONFLICT DO NOTHING` (covers the primary key and every
|
|
954
|
+
* unique index; NOT NULL / CHECK / FK still raise — never `INSERT OR IGNORE`).
|
|
955
|
+
* `changes === 0` marks a skipped row.
|
|
956
|
+
*/
|
|
957
|
+
async insertManyIgnore(data) {
|
|
958
|
+
return this.withTransaction(async () => {
|
|
959
|
+
const slots = [];
|
|
960
|
+
const tableName = this.resolveTableName();
|
|
961
|
+
for (const row of data) {
|
|
962
|
+
const built = buildInsert(tableName, row);
|
|
963
|
+
const sql = `${built.sql} ON CONFLICT DO NOTHING`;
|
|
964
|
+
const params = built.params;
|
|
965
|
+
this._log(sql, params);
|
|
966
|
+
const result = await this._stmt(() => this._wrapConstraintError(() => this.driver.run(sql, params)));
|
|
967
|
+
slots.push(result.changes === 0 ? null : { insertedId: this._resolveInsertedId(row, result.lastInsertRowid) });
|
|
968
|
+
}
|
|
969
|
+
return slots;
|
|
970
|
+
});
|
|
971
|
+
}
|
|
922
972
|
async findOne(query) {
|
|
923
973
|
const where = buildWhere(query.filter);
|
|
924
974
|
const controls = {
|
|
@@ -935,6 +985,16 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
935
985
|
this._log(sql, params);
|
|
936
986
|
return this._stmt(() => this.driver.all(sql, params));
|
|
937
987
|
}
|
|
988
|
+
/**
|
|
989
|
+
* `$skip` / `$limit` per partition in one statement: a `ROW_NUMBER()`
|
|
990
|
+
* window over `partitionBy` (the generic `$with` loader's per-parent page).
|
|
991
|
+
*/
|
|
992
|
+
async findManyPerPartition(query, partitionBy) {
|
|
993
|
+
const where = buildWhere(query.filter);
|
|
994
|
+
const { sql, params } = (0, _atscript_db_sql_tools.buildPartitionedSelect)(sqliteDialect, this.resolveTableName(), where, query.controls, partitionBy);
|
|
995
|
+
this._log(sql, params);
|
|
996
|
+
return (0, _atscript_db_sql_tools.stripPartitionRowNumber)(await this._stmt(() => this.driver.all(sql, params)));
|
|
997
|
+
}
|
|
938
998
|
async count(query) {
|
|
939
999
|
const where = buildWhere(query.filter);
|
|
940
1000
|
const sql = `SELECT COUNT(*) as cnt FROM "${esc(this.resolveTableName())}" WHERE ${where.sql}`;
|
|
@@ -1658,7 +1718,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1658
1718
|
const controls = query.controls ?? {};
|
|
1659
1719
|
return {
|
|
1660
1720
|
tableName: this.resolveTableName(),
|
|
1661
|
-
where: buildWhere(query.filter),
|
|
1721
|
+
where: buildWhere(query.filter, { qualifier: sqliteDialect.quoteTable(_atscript_db_sql_tools.SEARCH_SOURCE_ALIAS) }),
|
|
1662
1722
|
dist: haversineDistanceExpr(`"${esc(column)}"`, point),
|
|
1663
1723
|
window: (0, _atscript_db_sql_tools.geoWindowFromControls)(controls),
|
|
1664
1724
|
controls
|
|
@@ -1790,7 +1850,7 @@ function createAdapter(connection, options) {
|
|
|
1790
1850
|
transactionWaitTimeoutMs,
|
|
1791
1851
|
transactionWaitWarnMs
|
|
1792
1852
|
};
|
|
1793
|
-
return new _atscript_db.DbSpace(() => new SqliteAdapter(driver, adapterOptions));
|
|
1853
|
+
return new _atscript_db.DbSpace(() => new SqliteAdapter(driver, adapterOptions), { onClose: () => driver.close() });
|
|
1794
1854
|
}
|
|
1795
1855
|
//#endregion
|
|
1796
1856
|
exports.BetterSqlite3Driver = BetterSqlite3Driver;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
1
|
+
import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair, TViewCapability } from "@atscript/db";
|
|
2
2
|
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
3
3
|
import { FilterExpr as FilterExpr$1 } from "@uniqu/core";
|
|
4
|
-
import { TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
4
|
+
import { TFilterVisitorOptions, TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
5
5
|
|
|
6
6
|
//#region src/types.d.ts
|
|
7
7
|
/**
|
|
@@ -121,6 +121,7 @@ declare class BetterSqlite3Driver implements TSqliteDriver {
|
|
|
121
121
|
registerFunction(name: string, fn: (...args: any[]) => unknown, opts?: {
|
|
122
122
|
deterministic?: boolean;
|
|
123
123
|
}): void;
|
|
124
|
+
/** Idempotent: closing an already closed database is a no-op. */
|
|
124
125
|
close(): void;
|
|
125
126
|
}
|
|
126
127
|
//#endregion
|
|
@@ -267,8 +268,12 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
267
268
|
* none otherwise (`BUCKET_NOT_SUPPORTED`, a clean 400).
|
|
268
269
|
*/
|
|
269
270
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
270
|
-
/** Every aggregate function
|
|
271
|
+
/** Every aggregate function: `countDistinct`, `first` and `last` included. */
|
|
271
272
|
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
273
|
+
/** Arithmetic in an aggregate `$select` (`{ $expr }`, `{ $fn, $expr }`). */
|
|
274
|
+
supportsAggregateExpressions(): boolean;
|
|
275
|
+
/** Computed view columns and first-row joins. */
|
|
276
|
+
viewCapabilities(): ReadonlySet<TViewCapability>;
|
|
272
277
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
273
278
|
private _supportsVector;
|
|
274
279
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -319,6 +324,13 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
319
324
|
protected _rollbackTransaction(state: unknown): Promise<void>;
|
|
320
325
|
/** SQLite does not use schemas — override to always exclude schema. */
|
|
321
326
|
resolveTableName(): string;
|
|
327
|
+
/**
|
|
328
|
+
* Relational predicates (`$some` / `$none`) render as correlated
|
|
329
|
+
* `[NOT] EXISTS` subqueries — in reads and in mutation filters alike.
|
|
330
|
+
*
|
|
331
|
+
* @since 0.1.147
|
|
332
|
+
*/
|
|
333
|
+
supportsRelationFilters(_mode: "read" | "write"): boolean;
|
|
322
334
|
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
323
335
|
supportsNativeForeignKeys(): boolean;
|
|
324
336
|
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
@@ -329,8 +341,20 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
329
341
|
private _wrapConstraintError;
|
|
330
342
|
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
331
343
|
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
344
|
+
supportsInsertIgnore(): boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Per row `INSERT … ON CONFLICT DO NOTHING` (covers the primary key and every
|
|
347
|
+
* unique index; NOT NULL / CHECK / FK still raise — never `INSERT OR IGNORE`).
|
|
348
|
+
* `changes === 0` marks a skipped row.
|
|
349
|
+
*/
|
|
350
|
+
insertManyIgnore(data: Array<Record<string, unknown>>): Promise<TDbInsertIgnoreSlot[]>;
|
|
332
351
|
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
333
352
|
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
353
|
+
/**
|
|
354
|
+
* `$skip` / `$limit` per partition in one statement: a `ROW_NUMBER()`
|
|
355
|
+
* window over `partitionBy` (the generic `$with` loader's per-parent page).
|
|
356
|
+
*/
|
|
357
|
+
findManyPerPartition(query: DbQuery, partitionBy: readonly string[]): Promise<Array<Record<string, unknown>>>;
|
|
334
358
|
count(query: DbQuery): Promise<number>;
|
|
335
359
|
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
336
360
|
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
@@ -488,10 +512,15 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
488
512
|
/**
|
|
489
513
|
* Translates a uniqu filter expression into a parameterized SQL WHERE clause.
|
|
490
514
|
*
|
|
515
|
+
* Relational predicates (`$some` / `$none`) render as correlated `EXISTS`
|
|
516
|
+
* subqueries referencing the outer table by `opts.qualifier` (default: the
|
|
517
|
+
* source table name) — a statement with an aliased FROM must pass its alias.
|
|
518
|
+
*
|
|
519
|
+
* @param opts - since 0.1.147
|
|
491
520
|
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
492
521
|
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
493
522
|
*/
|
|
494
|
-
declare function buildWhere(filter: FilterExpr$1): TSqlFragment$1;
|
|
523
|
+
declare function buildWhere(filter: FilterExpr$1, opts?: TFilterVisitorOptions): TSqlFragment$1;
|
|
495
524
|
//#endregion
|
|
496
525
|
//#region src/index.d.ts
|
|
497
526
|
/** `createAdapter` options: driver options plus the transaction-gate options (since 0.1.128). */
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
2
|
-
import { TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
1
|
+
import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair, TViewCapability } from "@atscript/db";
|
|
2
|
+
import { TFilterVisitorOptions, TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
3
3
|
import { FilterExpr as FilterExpr$1 } from "@uniqu/core";
|
|
4
4
|
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
5
5
|
|
|
@@ -121,6 +121,7 @@ declare class BetterSqlite3Driver implements TSqliteDriver {
|
|
|
121
121
|
registerFunction(name: string, fn: (...args: any[]) => unknown, opts?: {
|
|
122
122
|
deterministic?: boolean;
|
|
123
123
|
}): void;
|
|
124
|
+
/** Idempotent: closing an already closed database is a no-op. */
|
|
124
125
|
close(): void;
|
|
125
126
|
}
|
|
126
127
|
//#endregion
|
|
@@ -267,8 +268,12 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
267
268
|
* none otherwise (`BUCKET_NOT_SUPPORTED`, a clean 400).
|
|
268
269
|
*/
|
|
269
270
|
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
270
|
-
/** Every aggregate function
|
|
271
|
+
/** Every aggregate function: `countDistinct`, `first` and `last` included. */
|
|
271
272
|
aggregateFns(): ReadonlySet<AggregateFn>;
|
|
273
|
+
/** Arithmetic in an aggregate `$select` (`{ $expr }`, `{ $fn, $expr }`). */
|
|
274
|
+
supportsAggregateExpressions(): boolean;
|
|
275
|
+
/** Computed view columns and first-row joins. */
|
|
276
|
+
viewCapabilities(): ReadonlySet<TViewCapability>;
|
|
272
277
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
273
278
|
private _supportsVector;
|
|
274
279
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -319,6 +324,13 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
319
324
|
protected _rollbackTransaction(state: unknown): Promise<void>;
|
|
320
325
|
/** SQLite does not use schemas — override to always exclude schema. */
|
|
321
326
|
resolveTableName(): string;
|
|
327
|
+
/**
|
|
328
|
+
* Relational predicates (`$some` / `$none`) render as correlated
|
|
329
|
+
* `[NOT] EXISTS` subqueries — in reads and in mutation filters alike.
|
|
330
|
+
*
|
|
331
|
+
* @since 0.1.147
|
|
332
|
+
*/
|
|
333
|
+
supportsRelationFilters(_mode: "read" | "write"): boolean;
|
|
322
334
|
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
323
335
|
supportsNativeForeignKeys(): boolean;
|
|
324
336
|
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
@@ -329,8 +341,20 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
329
341
|
private _wrapConstraintError;
|
|
330
342
|
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
331
343
|
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
344
|
+
supportsInsertIgnore(): boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Per row `INSERT … ON CONFLICT DO NOTHING` (covers the primary key and every
|
|
347
|
+
* unique index; NOT NULL / CHECK / FK still raise — never `INSERT OR IGNORE`).
|
|
348
|
+
* `changes === 0` marks a skipped row.
|
|
349
|
+
*/
|
|
350
|
+
insertManyIgnore(data: Array<Record<string, unknown>>): Promise<TDbInsertIgnoreSlot[]>;
|
|
332
351
|
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
333
352
|
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
353
|
+
/**
|
|
354
|
+
* `$skip` / `$limit` per partition in one statement: a `ROW_NUMBER()`
|
|
355
|
+
* window over `partitionBy` (the generic `$with` loader's per-parent page).
|
|
356
|
+
*/
|
|
357
|
+
findManyPerPartition(query: DbQuery, partitionBy: readonly string[]): Promise<Array<Record<string, unknown>>>;
|
|
334
358
|
count(query: DbQuery): Promise<number>;
|
|
335
359
|
aggregate(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
336
360
|
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
@@ -488,10 +512,15 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
488
512
|
/**
|
|
489
513
|
* Translates a uniqu filter expression into a parameterized SQL WHERE clause.
|
|
490
514
|
*
|
|
515
|
+
* Relational predicates (`$some` / `$none`) render as correlated `EXISTS`
|
|
516
|
+
* subqueries referencing the outer table by `opts.qualifier` (default: the
|
|
517
|
+
* source table name) — a statement with an aliased FROM must pass its alias.
|
|
518
|
+
*
|
|
519
|
+
* @param opts - since 0.1.147
|
|
491
520
|
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
492
521
|
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
493
522
|
*/
|
|
494
|
-
declare function buildWhere(filter: FilterExpr$1): TSqlFragment$1;
|
|
523
|
+
declare function buildWhere(filter: FilterExpr$1, opts?: TFilterVisitorOptions): TSqlFragment$1;
|
|
495
524
|
//#endregion
|
|
496
525
|
//#region src/index.d.ts
|
|
497
526
|
/** `createAdapter` options: driver options plus the transaction-gate options (since 0.1.128). */
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, searchIndexNotFoundMessage, vectorIndexNotFoundMessage } from "@atscript/db";
|
|
2
|
+
import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, ALL_VIEW_CAPABILITIES, BaseDbAdapter, DbError, DbSpace, fkColumns, searchIndexNotFoundMessage, vectorIndexNotFoundMessage } from "@atscript/db";
|
|
3
3
|
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
4
|
-
import { EMPTY_AND, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, geoWindowFromControls, jsonDollarPath, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
|
|
4
|
+
import { EMPTY_AND, SEARCH_SOURCE_ALIAS, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildPartitionedSelect, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, geoWindowFromControls, jsonDollarPath, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue } from "@atscript/db-sql-tools";
|
|
5
5
|
import { bucketer } from "@uniqu/core";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
//#region src/better-sqlite3-driver.ts
|
|
@@ -77,7 +77,9 @@ var BetterSqlite3Driver = class {
|
|
|
77
77
|
registerFunction(name, fn, opts) {
|
|
78
78
|
this.db.function(name, { deterministic: opts?.deterministic ?? false }, fn);
|
|
79
79
|
}
|
|
80
|
+
/** Idempotent: closing an already closed database is a no-op. */
|
|
80
81
|
close() {
|
|
82
|
+
if (!this.db.open) return;
|
|
81
83
|
this.db.close();
|
|
82
84
|
}
|
|
83
85
|
};
|
|
@@ -85,7 +87,8 @@ var BetterSqlite3Driver = class {
|
|
|
85
87
|
//#region src/calendar-bucket.ts
|
|
86
88
|
/**
|
|
87
89
|
* Name of the scalar SQL function the adapter registers on its driver:
|
|
88
|
-
* `atscript_bucket(value, unit, tz, weekStart)` → TEXT `'YYYY-MM-DD'`
|
|
90
|
+
* `atscript_bucket(value, unit, tz, weekStart)` → TEXT `'YYYY-MM-DD'`
|
|
91
|
+
* (`'YYYY-MM-DDTHH:00'` for unit `hour`) or NULL.
|
|
89
92
|
*/
|
|
90
93
|
const SQLITE_BUCKET_FN = "atscript_bucket";
|
|
91
94
|
/**
|
|
@@ -286,6 +289,7 @@ const sqliteDialect = {
|
|
|
286
289
|
},
|
|
287
290
|
calendarBucket: sqliteCalendarBucket,
|
|
288
291
|
jsonExtract: sqliteJsonExtract,
|
|
292
|
+
castDouble: (expr) => `CAST(${expr} AS REAL)`,
|
|
289
293
|
createViewPrefix: "CREATE VIEW IF NOT EXISTS"
|
|
290
294
|
};
|
|
291
295
|
/**
|
|
@@ -437,8 +441,9 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
437
441
|
colDefs.push(`PRIMARY KEY (${pkCols})`);
|
|
438
442
|
}
|
|
439
443
|
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
440
|
-
const
|
|
441
|
-
const
|
|
444
|
+
const { fields, targetFields } = fkColumns(fk);
|
|
445
|
+
const localCols = fields.map((f) => `"${esc(f)}"`).join(", ");
|
|
446
|
+
const targetCols = targetFields.map((f) => `"${esc(f)}"`).join(", ");
|
|
442
447
|
let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc(fk.targetTable)}" (${targetCols})`;
|
|
443
448
|
if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
|
|
444
449
|
if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
|
|
@@ -451,23 +456,28 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
451
456
|
/**
|
|
452
457
|
* Translates a uniqu filter expression into a parameterized SQL WHERE clause.
|
|
453
458
|
*
|
|
459
|
+
* Relational predicates (`$some` / `$none`) render as correlated `EXISTS`
|
|
460
|
+
* subqueries referencing the outer table by `opts.qualifier` (default: the
|
|
461
|
+
* source table name) — a statement with an aliased FROM must pass its alias.
|
|
462
|
+
*
|
|
463
|
+
* @param opts - since 0.1.147
|
|
454
464
|
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
455
465
|
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
456
466
|
*/
|
|
457
|
-
function buildWhere(filter) {
|
|
458
|
-
return buildWhere$1(sqliteDialect, filter);
|
|
467
|
+
function buildWhere(filter, opts) {
|
|
468
|
+
return buildWhere$1(sqliteDialect, filter, opts);
|
|
459
469
|
}
|
|
460
470
|
/**
|
|
461
471
|
* Like {@link buildWhere} but prefixes all column references with a table alias.
|
|
462
472
|
* Produces `alias."col"` instead of `"col"` — needed for JOINed queries (e.g. FTS5 search).
|
|
473
|
+
* Relational predicates correlate to the same alias (since 0.1.147); their
|
|
474
|
+
* own subquery aliases stay unprefixed.
|
|
463
475
|
*/
|
|
464
476
|
function buildPrefixedWhere(alias, filter) {
|
|
465
|
-
return buildWhere$1({
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
}, filter);
|
|
477
|
+
return buildWhere$1(sqliteDialect, filter, {
|
|
478
|
+
columnRef: (name) => `${alias}."${esc(name)}"`,
|
|
479
|
+
qualifier: alias
|
|
480
|
+
});
|
|
471
481
|
}
|
|
472
482
|
//#endregion
|
|
473
483
|
//#region src/tx-gate.ts
|
|
@@ -705,10 +715,18 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
|
|
|
705
715
|
calendarBucketUnits() {
|
|
706
716
|
return this._hasBucketFn ? ALL_BUCKET_UNITS : super.calendarBucketUnits();
|
|
707
717
|
}
|
|
708
|
-
/** Every aggregate function
|
|
718
|
+
/** Every aggregate function: `countDistinct`, `first` and `last` included. */
|
|
709
719
|
aggregateFns() {
|
|
710
720
|
return ALL_AGGREGATE_FNS;
|
|
711
721
|
}
|
|
722
|
+
/** Arithmetic in an aggregate `$select` (`{ $expr }`, `{ $fn, $expr }`). */
|
|
723
|
+
supportsAggregateExpressions() {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
/** Computed view columns and first-row joins. */
|
|
727
|
+
viewCapabilities() {
|
|
728
|
+
return ALL_VIEW_CAPABILITIES;
|
|
729
|
+
}
|
|
712
730
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
713
731
|
_supportsVector;
|
|
714
732
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -864,6 +882,15 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
|
|
|
864
882
|
resolveTableName() {
|
|
865
883
|
return super.resolveTableName(false);
|
|
866
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* Relational predicates (`$some` / `$none`) render as correlated
|
|
887
|
+
* `[NOT] EXISTS` subqueries — in reads and in mutation filters alike.
|
|
888
|
+
*
|
|
889
|
+
* @since 0.1.147
|
|
890
|
+
*/
|
|
891
|
+
supportsRelationFilters(_mode) {
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
867
894
|
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
|
|
868
895
|
supportsNativeForeignKeys() {
|
|
869
896
|
return true;
|
|
@@ -918,6 +945,29 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
|
|
|
918
945
|
};
|
|
919
946
|
});
|
|
920
947
|
}
|
|
948
|
+
supportsInsertIgnore() {
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Per row `INSERT … ON CONFLICT DO NOTHING` (covers the primary key and every
|
|
953
|
+
* unique index; NOT NULL / CHECK / FK still raise — never `INSERT OR IGNORE`).
|
|
954
|
+
* `changes === 0` marks a skipped row.
|
|
955
|
+
*/
|
|
956
|
+
async insertManyIgnore(data) {
|
|
957
|
+
return this.withTransaction(async () => {
|
|
958
|
+
const slots = [];
|
|
959
|
+
const tableName = this.resolveTableName();
|
|
960
|
+
for (const row of data) {
|
|
961
|
+
const built = buildInsert$1(tableName, row);
|
|
962
|
+
const sql = `${built.sql} ON CONFLICT DO NOTHING`;
|
|
963
|
+
const params = built.params;
|
|
964
|
+
this._log(sql, params);
|
|
965
|
+
const result = await this._stmt(() => this._wrapConstraintError(() => this.driver.run(sql, params)));
|
|
966
|
+
slots.push(result.changes === 0 ? null : { insertedId: this._resolveInsertedId(row, result.lastInsertRowid) });
|
|
967
|
+
}
|
|
968
|
+
return slots;
|
|
969
|
+
});
|
|
970
|
+
}
|
|
921
971
|
async findOne(query) {
|
|
922
972
|
const where = buildWhere(query.filter);
|
|
923
973
|
const controls = {
|
|
@@ -934,6 +984,16 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
|
|
|
934
984
|
this._log(sql, params);
|
|
935
985
|
return this._stmt(() => this.driver.all(sql, params));
|
|
936
986
|
}
|
|
987
|
+
/**
|
|
988
|
+
* `$skip` / `$limit` per partition in one statement: a `ROW_NUMBER()`
|
|
989
|
+
* window over `partitionBy` (the generic `$with` loader's per-parent page).
|
|
990
|
+
*/
|
|
991
|
+
async findManyPerPartition(query, partitionBy) {
|
|
992
|
+
const where = buildWhere(query.filter);
|
|
993
|
+
const { sql, params } = buildPartitionedSelect(sqliteDialect, this.resolveTableName(), where, query.controls, partitionBy);
|
|
994
|
+
this._log(sql, params);
|
|
995
|
+
return stripPartitionRowNumber(await this._stmt(() => this.driver.all(sql, params)));
|
|
996
|
+
}
|
|
937
997
|
async count(query) {
|
|
938
998
|
const where = buildWhere(query.filter);
|
|
939
999
|
const sql = `SELECT COUNT(*) as cnt FROM "${esc(this.resolveTableName())}" WHERE ${where.sql}`;
|
|
@@ -1657,7 +1717,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
|
|
|
1657
1717
|
const controls = query.controls ?? {};
|
|
1658
1718
|
return {
|
|
1659
1719
|
tableName: this.resolveTableName(),
|
|
1660
|
-
where: buildWhere(query.filter),
|
|
1720
|
+
where: buildWhere(query.filter, { qualifier: sqliteDialect.quoteTable(SEARCH_SOURCE_ALIAS) }),
|
|
1661
1721
|
dist: haversineDistanceExpr(`"${esc(column)}"`, point),
|
|
1662
1722
|
window: geoWindowFromControls(controls),
|
|
1663
1723
|
controls
|
|
@@ -1789,7 +1849,7 @@ function createAdapter(connection, options) {
|
|
|
1789
1849
|
transactionWaitTimeoutMs,
|
|
1790
1850
|
transactionWaitWarnMs
|
|
1791
1851
|
};
|
|
1792
|
-
return new DbSpace(() => new SqliteAdapter(driver, adapterOptions));
|
|
1852
|
+
return new DbSpace(() => new SqliteAdapter(driver, adapterOptions), { onClose: () => driver.close() });
|
|
1793
1853
|
}
|
|
1794
1854
|
//#endregion
|
|
1795
1855
|
export { BetterSqlite3Driver, SqliteAdapter, SqliteTxGate, SqliteTxState, buildWhere, createAdapter, getSqliteTxGate };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.148",
|
|
4
4
|
"description": "SQLite adapter for @atscript/db with swappable driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -36,21 +36,21 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@atscript/core": "^0.1.
|
|
40
|
-
"@atscript/typescript": "^0.1.
|
|
39
|
+
"@atscript/core": "^0.1.100",
|
|
40
|
+
"@atscript/typescript": "^0.1.100",
|
|
41
41
|
"@types/better-sqlite3": "^7.6.13",
|
|
42
|
-
"@uniqu/core": "^0.1.
|
|
42
|
+
"@uniqu/core": "^0.1.13",
|
|
43
43
|
"better-sqlite3": "^12.6.2",
|
|
44
44
|
"sqlite-vec": "^0.1.9",
|
|
45
|
-
"unplugin-atscript": "^0.1.
|
|
45
|
+
"unplugin-atscript": "^0.1.100"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@atscript/core": "^0.1.
|
|
49
|
-
"@atscript/typescript": "^0.1.
|
|
50
|
-
"@uniqu/core": "^0.1.
|
|
48
|
+
"@atscript/core": "^0.1.100",
|
|
49
|
+
"@atscript/typescript": "^0.1.100",
|
|
50
|
+
"@uniqu/core": "^0.1.13",
|
|
51
51
|
"better-sqlite3": ">=11.0.0",
|
|
52
|
-
"@atscript/db": "^0.1.
|
|
53
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
52
|
+
"@atscript/db": "^0.1.148",
|
|
53
|
+
"@atscript/db-sql-tools": "^0.1.148"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"better-sqlite3": {
|