@atscript/db-sqlite 0.1.131 → 0.1.133
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 +1 -1
- package/dist/index.cjs +95 -0
- package/dist/index.d.cts +27 -1
- package/dist/index.d.mts +28 -2
- package/dist/index.mjs +97 -2
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ await users.insertOne({ name: "John", email: "john@example.com" });
|
|
|
39
39
|
|
|
40
40
|
## Features
|
|
41
41
|
|
|
42
|
-
- Swappable driver via `TSqliteDriver` interface (5 methods)
|
|
42
|
+
- Swappable driver via `TSqliteDriver` interface (5 methods, plus the optional `registerFunction` hook calendar-bucket grouping needs)
|
|
43
43
|
- Built-in `BetterSqlite3Driver` for immediate use (with optional `{ vector: true }` and `loadExtensions` hooks)
|
|
44
44
|
- MongoDB-style filter translation to parameterized SQL (no injection risk)
|
|
45
45
|
- Automatic schema management from `@db.*` annotations
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ let _atscript_db = require("@atscript/db");
|
|
|
3
3
|
let node_module = require("node:module");
|
|
4
4
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
5
5
|
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
6
|
+
let _uniqu_core = require("@uniqu/core");
|
|
6
7
|
let node_async_hooks = require("node:async_hooks");
|
|
7
8
|
//#region src/better-sqlite3-driver.ts
|
|
8
9
|
/**
|
|
@@ -74,11 +75,93 @@ var BetterSqlite3Driver = class {
|
|
|
74
75
|
exec(sql) {
|
|
75
76
|
this.db.exec(sql);
|
|
76
77
|
}
|
|
78
|
+
registerFunction(name, fn, opts) {
|
|
79
|
+
this.db.function(name, { deterministic: opts?.deterministic ?? false }, fn);
|
|
80
|
+
}
|
|
77
81
|
close() {
|
|
78
82
|
this.db.close();
|
|
79
83
|
}
|
|
80
84
|
};
|
|
81
85
|
//#endregion
|
|
86
|
+
//#region src/calendar-bucket.ts
|
|
87
|
+
/**
|
|
88
|
+
* Name of the scalar SQL function the adapter registers on its driver:
|
|
89
|
+
* `atscript_bucket(value, unit, tz, weekStart)` → TEXT `'YYYY-MM-DD'` or NULL.
|
|
90
|
+
*/
|
|
91
|
+
const SQLITE_BUCKET_FN = "atscript_bucket";
|
|
92
|
+
/**
|
|
93
|
+
* Prepared kernels by `unit|tz|weekStart`. Bounded by construction: the
|
|
94
|
+
* arguments are the literals `sqliteCalendarBucket` inlines — closed unit /
|
|
95
|
+
* week-start sets and validated IANA zones — and an invalid combination
|
|
96
|
+
* throws before it is stored.
|
|
97
|
+
*/
|
|
98
|
+
const bucketers = /* @__PURE__ */ new Map();
|
|
99
|
+
/** The last arguments and their kernel — a query calls the UDF with one combination per row. */
|
|
100
|
+
let last;
|
|
101
|
+
function cachedBucketer(unit, tz, weekStart) {
|
|
102
|
+
if (last && last.unit === unit && last.tz === tz && last.weekStart === weekStart) return last.fn;
|
|
103
|
+
const u = String(unit);
|
|
104
|
+
const z = String(tz);
|
|
105
|
+
const w = String(weekStart);
|
|
106
|
+
const key = `${u}|${z}|${w}`;
|
|
107
|
+
let fn = bucketers.get(key);
|
|
108
|
+
if (!fn) {
|
|
109
|
+
fn = (0, _uniqu_core.bucketer)(u, z, w);
|
|
110
|
+
bucketers.set(key, fn);
|
|
111
|
+
}
|
|
112
|
+
last = {
|
|
113
|
+
unit,
|
|
114
|
+
tz,
|
|
115
|
+
weekStart,
|
|
116
|
+
fn
|
|
117
|
+
};
|
|
118
|
+
return fn;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Body of the `atscript_bucket` UDF: the `@uniqu/core` calendar kernel — the
|
|
122
|
+
* same definition the in-memory adapter groups with. INTEGER / REAL values
|
|
123
|
+
* arrive as `number` (or `bigint` in better-sqlite3's safe-integer mode);
|
|
124
|
+
* NULL and any other storage class (TEXT, BLOB) label as NULL, as does an
|
|
125
|
+
* instant outside the supported range. The kernel is prepared once per
|
|
126
|
+
* (unit, zone, week start), not per row.
|
|
127
|
+
*
|
|
128
|
+
* Four declared parameters on purpose: SQLite drivers take a UDF's arity from
|
|
129
|
+
* `fn.length`.
|
|
130
|
+
*/
|
|
131
|
+
function atscriptBucketUdf(value, unit, tz, weekStart) {
|
|
132
|
+
if (typeof value !== "number" && typeof value !== "bigint") return null;
|
|
133
|
+
return cachedBucketer(unit, tz, weekStart)(value);
|
|
134
|
+
}
|
|
135
|
+
/** Drivers the UDF is registered on. */
|
|
136
|
+
const registered = /* @__PURE__ */ new WeakSet();
|
|
137
|
+
/**
|
|
138
|
+
* Registers {@link SQLITE_BUCKET_FN} on `driver` once — every table adapter
|
|
139
|
+
* of a space shares one driver — and reports whether the driver has the UDF.
|
|
140
|
+
* A driver without {@link TSqliteDriver.registerFunction} cannot run calendar
|
|
141
|
+
* buckets (the adapter then advertises no bucket units → `BUCKET_NOT_SUPPORTED`).
|
|
142
|
+
* A failing `registerFunction` propagates: it is a broken driver, not a
|
|
143
|
+
* missing capability.
|
|
144
|
+
*/
|
|
145
|
+
function registerBucketFunction(driver) {
|
|
146
|
+
if (typeof driver.registerFunction !== "function") return false;
|
|
147
|
+
if (!registered.has(driver)) {
|
|
148
|
+
driver.registerFunction(SQLITE_BUCKET_FN, atscriptBucketUdf, { deterministic: true });
|
|
149
|
+
registered.add(driver);
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The SQLite dialect's calendar-bucket expression:
|
|
155
|
+
* `atscript_bucket("col", 'week', 'Europe/Berlin', 'sun')`. Parameter-free —
|
|
156
|
+
* the shared builder asserts the unit and week start against their closed
|
|
157
|
+
* sets, and the zone passes {@link sqlTimeZoneLiteral}'s charset — so SELECT,
|
|
158
|
+
* GROUP BY and HAVING render identical text. Deterministic, so SQLite treats
|
|
159
|
+
* repeated calls as one expression.
|
|
160
|
+
*/
|
|
161
|
+
function sqliteCalendarBucket(quotedCol, b) {
|
|
162
|
+
return `${SQLITE_BUCKET_FN}(${quotedCol}, '${b.unit}', ${(0, _atscript_db_sql_tools.sqlTimeZoneLiteral)(b.tz)}, '${b.weekStart}')`;
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
82
165
|
//#region src/sql-builder.ts
|
|
83
166
|
function esc(name) {
|
|
84
167
|
return name.replace(/"/g, "\"\"");
|
|
@@ -189,6 +272,7 @@ const sqliteDialect = {
|
|
|
189
272
|
params: [...dist.params, circle.radius]
|
|
190
273
|
};
|
|
191
274
|
},
|
|
275
|
+
calendarBucket: sqliteCalendarBucket,
|
|
192
276
|
createViewPrefix: "CREATE VIEW IF NOT EXISTS"
|
|
193
277
|
};
|
|
194
278
|
/**
|
|
@@ -579,6 +663,14 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
579
663
|
supportsNativeValueDefaults() {
|
|
580
664
|
return true;
|
|
581
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Every calendar-bucket unit when the driver could register the
|
|
668
|
+
* `atscript_bucket` UDF (at construction — see {@link registerBucketFunction}),
|
|
669
|
+
* none otherwise (`BUCKET_NOT_SUPPORTED`, a clean 400).
|
|
670
|
+
*/
|
|
671
|
+
calendarBucketUnits() {
|
|
672
|
+
return this._hasBucketFn ? _atscript_db.ALL_BUCKET_UNITS : super.calendarBucketUnits();
|
|
673
|
+
}
|
|
582
674
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
583
675
|
_supportsVector;
|
|
584
676
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -592,12 +684,15 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
592
684
|
/** Per-driver transaction gate (shared by every adapter over this driver). */
|
|
593
685
|
_gate;
|
|
594
686
|
_txOptions;
|
|
687
|
+
/** Whether the driver has the calendar-bucket UDF — fixed at construction, when it is registered. */
|
|
688
|
+
_hasBucketFn;
|
|
595
689
|
constructor(driver, options) {
|
|
596
690
|
super();
|
|
597
691
|
this.driver = driver;
|
|
598
692
|
this._gate = getSqliteTxGate(driver);
|
|
599
693
|
this._txOptions = { ...options };
|
|
600
694
|
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
695
|
+
this._hasBucketFn = registerBucketFunction(driver);
|
|
601
696
|
}
|
|
602
697
|
onFieldScanned(field, _type, metadata) {
|
|
603
698
|
const vectorMeta = metadata.get("db.search.vector");
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
1
|
+
import { BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
2
2
|
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
3
3
|
import { FilterExpr as FilterExpr$1 } from "@uniqu/core";
|
|
4
4
|
import { TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
@@ -47,6 +47,21 @@ interface TSqliteDriver {
|
|
|
47
47
|
* Close the database connection.
|
|
48
48
|
*/
|
|
49
49
|
close(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Registers a scalar SQL function (a user-defined function) on the
|
|
52
|
+
* connection. Optional: the adapter registers `atscript_bucket` through it
|
|
53
|
+
* for calendar-bucket grouping, and a driver without it advertises no
|
|
54
|
+
* calendar-bucket units (`BUCKET_NOT_SUPPORTED`). The function's arity is
|
|
55
|
+
* `fn.length`; `deterministic` marks it pure, which lets SQLite use it in
|
|
56
|
+
* GROUP BY / indexes and fold repeated calls.
|
|
57
|
+
*
|
|
58
|
+
* better-sqlite3: `db.function(name, { deterministic }, fn)`;
|
|
59
|
+
* node:sqlite: `DatabaseSync#function(name, { deterministic }, fn)` (Node ≥ 22.13).
|
|
60
|
+
* Since 0.1.132.
|
|
61
|
+
*/
|
|
62
|
+
registerFunction?(name: string, fn: (...args: any[]) => unknown, opts?: {
|
|
63
|
+
deterministic?: boolean;
|
|
64
|
+
}): void;
|
|
50
65
|
/**
|
|
51
66
|
* Whether a vector search extension (e.g., `sqlite-vec`) is loaded.
|
|
52
67
|
* Consumers gate vec0 virtual tables and KNN queries on this flag.
|
|
@@ -103,6 +118,9 @@ declare class BetterSqlite3Driver implements TSqliteDriver {
|
|
|
103
118
|
all<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
104
119
|
get<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
|
|
105
120
|
exec(sql: string): void;
|
|
121
|
+
registerFunction(name: string, fn: (...args: any[]) => unknown, opts?: {
|
|
122
|
+
deterministic?: boolean;
|
|
123
|
+
}): void;
|
|
106
124
|
close(): void;
|
|
107
125
|
}
|
|
108
126
|
//#endregion
|
|
@@ -243,6 +261,12 @@ declare function getSqliteTxGate(driver: TSqliteDriver): SqliteTxGate;
|
|
|
243
261
|
declare class SqliteAdapter extends BaseDbAdapter {
|
|
244
262
|
protected readonly driver: TSqliteDriver;
|
|
245
263
|
supportsNativeValueDefaults(): boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Every calendar-bucket unit when the driver could register the
|
|
266
|
+
* `atscript_bucket` UDF (at construction — see {@link registerBucketFunction}),
|
|
267
|
+
* none otherwise (`BUCKET_NOT_SUPPORTED`, a clean 400).
|
|
268
|
+
*/
|
|
269
|
+
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
246
270
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
247
271
|
private _supportsVector;
|
|
248
272
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -256,6 +280,8 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
256
280
|
/** Per-driver transaction gate (shared by every adapter over this driver). */
|
|
257
281
|
private readonly _gate;
|
|
258
282
|
private readonly _txOptions;
|
|
283
|
+
/** Whether the driver has the calendar-bucket UDF — fixed at construction, when it is registered. */
|
|
284
|
+
private readonly _hasBucketFn;
|
|
259
285
|
constructor(driver: TSqliteDriver, options?: SqliteAdapterOptions);
|
|
260
286
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
261
287
|
formatValue(field: TDbFieldMeta): TValueFormatterPair | undefined;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
1
|
+
import { BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TFieldOps, TGenericLogger, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
|
|
2
2
|
import { TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
3
|
-
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
4
3
|
import { FilterExpr as FilterExpr$1 } from "@uniqu/core";
|
|
4
|
+
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
5
5
|
|
|
6
6
|
//#region src/types.d.ts
|
|
7
7
|
/**
|
|
@@ -47,6 +47,21 @@ interface TSqliteDriver {
|
|
|
47
47
|
* Close the database connection.
|
|
48
48
|
*/
|
|
49
49
|
close(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Registers a scalar SQL function (a user-defined function) on the
|
|
52
|
+
* connection. Optional: the adapter registers `atscript_bucket` through it
|
|
53
|
+
* for calendar-bucket grouping, and a driver without it advertises no
|
|
54
|
+
* calendar-bucket units (`BUCKET_NOT_SUPPORTED`). The function's arity is
|
|
55
|
+
* `fn.length`; `deterministic` marks it pure, which lets SQLite use it in
|
|
56
|
+
* GROUP BY / indexes and fold repeated calls.
|
|
57
|
+
*
|
|
58
|
+
* better-sqlite3: `db.function(name, { deterministic }, fn)`;
|
|
59
|
+
* node:sqlite: `DatabaseSync#function(name, { deterministic }, fn)` (Node ≥ 22.13).
|
|
60
|
+
* Since 0.1.132.
|
|
61
|
+
*/
|
|
62
|
+
registerFunction?(name: string, fn: (...args: any[]) => unknown, opts?: {
|
|
63
|
+
deterministic?: boolean;
|
|
64
|
+
}): void;
|
|
50
65
|
/**
|
|
51
66
|
* Whether a vector search extension (e.g., `sqlite-vec`) is loaded.
|
|
52
67
|
* Consumers gate vec0 virtual tables and KNN queries on this flag.
|
|
@@ -103,6 +118,9 @@ declare class BetterSqlite3Driver implements TSqliteDriver {
|
|
|
103
118
|
all<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
104
119
|
get<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
|
|
105
120
|
exec(sql: string): void;
|
|
121
|
+
registerFunction(name: string, fn: (...args: any[]) => unknown, opts?: {
|
|
122
|
+
deterministic?: boolean;
|
|
123
|
+
}): void;
|
|
106
124
|
close(): void;
|
|
107
125
|
}
|
|
108
126
|
//#endregion
|
|
@@ -243,6 +261,12 @@ declare function getSqliteTxGate(driver: TSqliteDriver): SqliteTxGate;
|
|
|
243
261
|
declare class SqliteAdapter extends BaseDbAdapter {
|
|
244
262
|
protected readonly driver: TSqliteDriver;
|
|
245
263
|
supportsNativeValueDefaults(): boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Every calendar-bucket unit when the driver could register the
|
|
266
|
+
* `atscript_bucket` UDF (at construction — see {@link registerBucketFunction}),
|
|
267
|
+
* none otherwise (`BUCKET_NOT_SUPPORTED`, a clean 400).
|
|
268
|
+
*/
|
|
269
|
+
calendarBucketUnits(): ReadonlySet<BucketUnit>;
|
|
246
270
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
247
271
|
private _supportsVector;
|
|
248
272
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -256,6 +280,8 @@ declare class SqliteAdapter extends BaseDbAdapter {
|
|
|
256
280
|
/** Per-driver transaction gate (shared by every adapter over this driver). */
|
|
257
281
|
private readonly _gate;
|
|
258
282
|
private readonly _txOptions;
|
|
283
|
+
/** Whether the driver has the calendar-bucket UDF — fixed at construction, when it is registered. */
|
|
284
|
+
private readonly _hasBucketFn;
|
|
259
285
|
constructor(driver: TSqliteDriver, options?: SqliteAdapterOptions);
|
|
260
286
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
261
287
|
formatValue(field: TDbFieldMeta): TValueFormatterPair | undefined;
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
2
|
+
import { ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
3
3
|
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
4
|
-
import { EMPTY_AND, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, geoWindowFromControls, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, toSqlValue } from "@atscript/db-sql-tools";
|
|
4
|
+
import { EMPTY_AND, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, geoWindowFromControls, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
|
|
5
|
+
import { bucketer } from "@uniqu/core";
|
|
5
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
7
|
//#region src/better-sqlite3-driver.ts
|
|
7
8
|
/**
|
|
@@ -73,11 +74,93 @@ var BetterSqlite3Driver = class {
|
|
|
73
74
|
exec(sql) {
|
|
74
75
|
this.db.exec(sql);
|
|
75
76
|
}
|
|
77
|
+
registerFunction(name, fn, opts) {
|
|
78
|
+
this.db.function(name, { deterministic: opts?.deterministic ?? false }, fn);
|
|
79
|
+
}
|
|
76
80
|
close() {
|
|
77
81
|
this.db.close();
|
|
78
82
|
}
|
|
79
83
|
};
|
|
80
84
|
//#endregion
|
|
85
|
+
//#region src/calendar-bucket.ts
|
|
86
|
+
/**
|
|
87
|
+
* Name of the scalar SQL function the adapter registers on its driver:
|
|
88
|
+
* `atscript_bucket(value, unit, tz, weekStart)` → TEXT `'YYYY-MM-DD'` or NULL.
|
|
89
|
+
*/
|
|
90
|
+
const SQLITE_BUCKET_FN = "atscript_bucket";
|
|
91
|
+
/**
|
|
92
|
+
* Prepared kernels by `unit|tz|weekStart`. Bounded by construction: the
|
|
93
|
+
* arguments are the literals `sqliteCalendarBucket` inlines — closed unit /
|
|
94
|
+
* week-start sets and validated IANA zones — and an invalid combination
|
|
95
|
+
* throws before it is stored.
|
|
96
|
+
*/
|
|
97
|
+
const bucketers = /* @__PURE__ */ new Map();
|
|
98
|
+
/** The last arguments and their kernel — a query calls the UDF with one combination per row. */
|
|
99
|
+
let last;
|
|
100
|
+
function cachedBucketer(unit, tz, weekStart) {
|
|
101
|
+
if (last && last.unit === unit && last.tz === tz && last.weekStart === weekStart) return last.fn;
|
|
102
|
+
const u = String(unit);
|
|
103
|
+
const z = String(tz);
|
|
104
|
+
const w = String(weekStart);
|
|
105
|
+
const key = `${u}|${z}|${w}`;
|
|
106
|
+
let fn = bucketers.get(key);
|
|
107
|
+
if (!fn) {
|
|
108
|
+
fn = bucketer(u, z, w);
|
|
109
|
+
bucketers.set(key, fn);
|
|
110
|
+
}
|
|
111
|
+
last = {
|
|
112
|
+
unit,
|
|
113
|
+
tz,
|
|
114
|
+
weekStart,
|
|
115
|
+
fn
|
|
116
|
+
};
|
|
117
|
+
return fn;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Body of the `atscript_bucket` UDF: the `@uniqu/core` calendar kernel — the
|
|
121
|
+
* same definition the in-memory adapter groups with. INTEGER / REAL values
|
|
122
|
+
* arrive as `number` (or `bigint` in better-sqlite3's safe-integer mode);
|
|
123
|
+
* NULL and any other storage class (TEXT, BLOB) label as NULL, as does an
|
|
124
|
+
* instant outside the supported range. The kernel is prepared once per
|
|
125
|
+
* (unit, zone, week start), not per row.
|
|
126
|
+
*
|
|
127
|
+
* Four declared parameters on purpose: SQLite drivers take a UDF's arity from
|
|
128
|
+
* `fn.length`.
|
|
129
|
+
*/
|
|
130
|
+
function atscriptBucketUdf(value, unit, tz, weekStart) {
|
|
131
|
+
if (typeof value !== "number" && typeof value !== "bigint") return null;
|
|
132
|
+
return cachedBucketer(unit, tz, weekStart)(value);
|
|
133
|
+
}
|
|
134
|
+
/** Drivers the UDF is registered on. */
|
|
135
|
+
const registered = /* @__PURE__ */ new WeakSet();
|
|
136
|
+
/**
|
|
137
|
+
* Registers {@link SQLITE_BUCKET_FN} on `driver` once — every table adapter
|
|
138
|
+
* of a space shares one driver — and reports whether the driver has the UDF.
|
|
139
|
+
* A driver without {@link TSqliteDriver.registerFunction} cannot run calendar
|
|
140
|
+
* buckets (the adapter then advertises no bucket units → `BUCKET_NOT_SUPPORTED`).
|
|
141
|
+
* A failing `registerFunction` propagates: it is a broken driver, not a
|
|
142
|
+
* missing capability.
|
|
143
|
+
*/
|
|
144
|
+
function registerBucketFunction(driver) {
|
|
145
|
+
if (typeof driver.registerFunction !== "function") return false;
|
|
146
|
+
if (!registered.has(driver)) {
|
|
147
|
+
driver.registerFunction(SQLITE_BUCKET_FN, atscriptBucketUdf, { deterministic: true });
|
|
148
|
+
registered.add(driver);
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The SQLite dialect's calendar-bucket expression:
|
|
154
|
+
* `atscript_bucket("col", 'week', 'Europe/Berlin', 'sun')`. Parameter-free —
|
|
155
|
+
* the shared builder asserts the unit and week start against their closed
|
|
156
|
+
* sets, and the zone passes {@link sqlTimeZoneLiteral}'s charset — so SELECT,
|
|
157
|
+
* GROUP BY and HAVING render identical text. Deterministic, so SQLite treats
|
|
158
|
+
* repeated calls as one expression.
|
|
159
|
+
*/
|
|
160
|
+
function sqliteCalendarBucket(quotedCol, b) {
|
|
161
|
+
return `${SQLITE_BUCKET_FN}(${quotedCol}, '${b.unit}', ${sqlTimeZoneLiteral(b.tz)}, '${b.weekStart}')`;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
81
164
|
//#region src/sql-builder.ts
|
|
82
165
|
function esc(name) {
|
|
83
166
|
return name.replace(/"/g, "\"\"");
|
|
@@ -188,6 +271,7 @@ const sqliteDialect = {
|
|
|
188
271
|
params: [...dist.params, circle.radius]
|
|
189
272
|
};
|
|
190
273
|
},
|
|
274
|
+
calendarBucket: sqliteCalendarBucket,
|
|
191
275
|
createViewPrefix: "CREATE VIEW IF NOT EXISTS"
|
|
192
276
|
};
|
|
193
277
|
/**
|
|
@@ -578,6 +662,14 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
|
|
|
578
662
|
supportsNativeValueDefaults() {
|
|
579
663
|
return true;
|
|
580
664
|
}
|
|
665
|
+
/**
|
|
666
|
+
* Every calendar-bucket unit when the driver could register the
|
|
667
|
+
* `atscript_bucket` UDF (at construction — see {@link registerBucketFunction}),
|
|
668
|
+
* none otherwise (`BUCKET_NOT_SUPPORTED`, a clean 400).
|
|
669
|
+
*/
|
|
670
|
+
calendarBucketUnits() {
|
|
671
|
+
return this._hasBucketFn ? ALL_BUCKET_UNITS : super.calendarBucketUnits();
|
|
672
|
+
}
|
|
581
673
|
/** Whether the SQLite connection has the sqlite-vec extension loaded. */
|
|
582
674
|
_supportsVector;
|
|
583
675
|
/** Whether SQLite math functions (radians/asin/...) are available — geo search. */
|
|
@@ -591,12 +683,15 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
|
|
|
591
683
|
/** Per-driver transaction gate (shared by every adapter over this driver). */
|
|
592
684
|
_gate;
|
|
593
685
|
_txOptions;
|
|
686
|
+
/** Whether the driver has the calendar-bucket UDF — fixed at construction, when it is registered. */
|
|
687
|
+
_hasBucketFn;
|
|
594
688
|
constructor(driver, options) {
|
|
595
689
|
super();
|
|
596
690
|
this.driver = driver;
|
|
597
691
|
this._gate = getSqliteTxGate(driver);
|
|
598
692
|
this._txOptions = { ...options };
|
|
599
693
|
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
694
|
+
this._hasBucketFn = registerBucketFunction(driver);
|
|
600
695
|
}
|
|
601
696
|
onFieldScanned(field, _type, metadata) {
|
|
602
697
|
const vectorMeta = metadata.get("db.search.vector");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.133",
|
|
4
4
|
"description": "SQLite adapter for @atscript/db with swappable driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@atscript/core": "^0.1.92",
|
|
40
40
|
"@atscript/typescript": "^0.1.92",
|
|
41
41
|
"@types/better-sqlite3": "^7.6.13",
|
|
42
|
-
"@uniqu/core": "^0.1.
|
|
42
|
+
"@uniqu/core": "^0.1.10",
|
|
43
43
|
"better-sqlite3": "^12.6.2",
|
|
44
44
|
"sqlite-vec": "^0.1.9",
|
|
45
45
|
"unplugin-atscript": "^0.1.92"
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@atscript/core": "^0.1.92",
|
|
49
49
|
"@atscript/typescript": "^0.1.92",
|
|
50
|
-
"@uniqu/core": "^0.1.
|
|
50
|
+
"@uniqu/core": "^0.1.10",
|
|
51
51
|
"better-sqlite3": ">=11.0.0",
|
|
52
|
-
"@atscript/db": "^0.1.
|
|
53
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
52
|
+
"@atscript/db": "^0.1.133",
|
|
53
|
+
"@atscript/db-sql-tools": "^0.1.133"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"better-sqlite3": {
|