@atscript/db-sqlite 0.1.37 → 0.1.39
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 +29 -183
- package/dist/index.cjs +281 -295
- package/dist/index.d.ts +39 -6
- package/dist/index.mjs +287 -308
- package/package.json +7 -6
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/
|
|
2
|
-
import {
|
|
1
|
+
import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, refActionToSql, toSqlValue } from "@atscript/db-sql-tools";
|
|
3
4
|
|
|
4
|
-
//#region rolldown:runtime
|
|
5
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function.");
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
//#endregion
|
|
11
5
|
//#region packages/db-sqlite/src/better-sqlite3-driver.ts
|
|
12
6
|
function _define_property$1(obj, key, value) {
|
|
13
7
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
@@ -45,7 +39,8 @@ var BetterSqlite3Driver = class {
|
|
|
45
39
|
constructor(pathOrDb, options) {
|
|
46
40
|
_define_property$1(this, "db", void 0);
|
|
47
41
|
if (typeof pathOrDb === "string") {
|
|
48
|
-
const
|
|
42
|
+
const req = createRequire(import.meta.url);
|
|
43
|
+
const Database = req("better-sqlite3");
|
|
49
44
|
this.db = new Database(pathOrDb, options);
|
|
50
45
|
} else this.db = pathOrDb;
|
|
51
46
|
}
|
|
@@ -53,55 +48,80 @@ var BetterSqlite3Driver = class {
|
|
|
53
48
|
|
|
54
49
|
//#endregion
|
|
55
50
|
//#region packages/db-sqlite/src/sql-builder.ts
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
const cols = keys.map((k) => `"${esc(k)}"`).join(", ");
|
|
59
|
-
const placeholders = keys.map(() => "?").join(", ");
|
|
60
|
-
return {
|
|
61
|
-
sql: `INSERT INTO "${esc(table)}" (${cols}) VALUES (${placeholders})`,
|
|
62
|
-
params: keys.map((k) => toSqliteValue(data[k]))
|
|
63
|
-
};
|
|
51
|
+
function esc(name) {
|
|
52
|
+
return name.replace(/"/g, "\"\"");
|
|
64
53
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Basic regex-to-LIKE conversion.
|
|
56
|
+
* - `^abc` → `abc%`
|
|
57
|
+
* - `abc$` → `%abc`
|
|
58
|
+
* - `^abc$` → `abc`
|
|
59
|
+
* - `abc` → `%abc%`
|
|
60
|
+
*/ function regexToLike(pattern) {
|
|
61
|
+
const hasStart = pattern.startsWith("^");
|
|
62
|
+
const hasEnd = pattern.endsWith("$");
|
|
63
|
+
let core = pattern;
|
|
64
|
+
if (hasStart) core = core.slice(1);
|
|
65
|
+
if (hasEnd) core = core.slice(0, -1);
|
|
66
|
+
core = core.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
67
|
+
core = core.replace(/\.\*/g, "%").replace(/\./g, "_");
|
|
68
|
+
if (hasStart && hasEnd) return core;
|
|
69
|
+
if (hasStart) return `${core}%`;
|
|
70
|
+
if (hasEnd) return `%${core}`;
|
|
71
|
+
return `%${core}%`;
|
|
72
|
+
}
|
|
73
|
+
const sqliteDialect = {
|
|
74
|
+
quoteIdentifier(name) {
|
|
75
|
+
return `"${esc(name)}"`;
|
|
76
|
+
},
|
|
77
|
+
quoteTable(name) {
|
|
78
|
+
return `"${esc(name)}"`;
|
|
79
|
+
},
|
|
80
|
+
unlimitedLimit: "-1",
|
|
81
|
+
toValue: toSqlValue,
|
|
82
|
+
toParam(value) {
|
|
83
|
+
if (value === undefined) return null;
|
|
84
|
+
return typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
85
|
+
},
|
|
86
|
+
regex(quotedCol, value) {
|
|
87
|
+
const pattern = regexToLike(value instanceof RegExp ? value.source : String(value));
|
|
88
|
+
return {
|
|
89
|
+
sql: `${quotedCol} LIKE ?`,
|
|
90
|
+
params: [pattern]
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
createViewPrefix: "CREATE VIEW IF NOT EXISTS"
|
|
94
|
+
};
|
|
95
|
+
function buildInsert$1(table, data) {
|
|
96
|
+
return buildInsert(sqliteDialect, table, data);
|
|
87
97
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
const params = [];
|
|
91
|
-
for (const [key, value] of Object.entries(data)) {
|
|
92
|
-
setClauses.push(`"${esc(key)}" = ?`);
|
|
93
|
-
params.push(toSqliteValue(value));
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
sql: `UPDATE "${esc(table)}" SET ${setClauses.join(", ")} WHERE ${where.sql}`,
|
|
97
|
-
params: [...params, ...where.params]
|
|
98
|
-
};
|
|
98
|
+
function buildSelect$1(table, where, controls) {
|
|
99
|
+
return buildSelect(sqliteDialect, table, where, controls);
|
|
99
100
|
}
|
|
100
|
-
function
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
function buildUpdate$1(table, data, where) {
|
|
102
|
+
return buildUpdate(sqliteDialect, table, data, where);
|
|
103
|
+
}
|
|
104
|
+
function buildDelete$1(table, where) {
|
|
105
|
+
return buildDelete(sqliteDialect, table, where);
|
|
106
|
+
}
|
|
107
|
+
function buildCreateView$1(viewName, plan, columns, resolveFieldRef) {
|
|
108
|
+
return buildCreateView(sqliteDialect, viewName, plan, columns, resolveFieldRef);
|
|
109
|
+
}
|
|
110
|
+
function buildAggregateSelect$1(table, where, controls) {
|
|
111
|
+
return buildAggregateSelect(sqliteDialect, table, where, controls);
|
|
112
|
+
}
|
|
113
|
+
function buildAggregateCount$1(table, where, controls) {
|
|
114
|
+
return buildAggregateCount(sqliteDialect, table, where, controls);
|
|
115
|
+
}
|
|
116
|
+
function sqliteTypeFromDesignType(designType) {
|
|
117
|
+
switch (designType) {
|
|
118
|
+
case "number":
|
|
119
|
+
case "integer":
|
|
120
|
+
case "decimal": return "REAL";
|
|
121
|
+
case "boolean": return "INTEGER";
|
|
122
|
+
case "string": return "TEXT";
|
|
123
|
+
default: return "TEXT";
|
|
124
|
+
}
|
|
105
125
|
}
|
|
106
126
|
function buildCreateTable(table, fields, foreignKeys) {
|
|
107
127
|
const colDefs = [];
|
|
@@ -110,9 +130,13 @@ function buildCreateTable(table, fields, foreignKeys) {
|
|
|
110
130
|
if (field.ignored) continue;
|
|
111
131
|
const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
|
|
112
132
|
let def = `"${esc(field.physicalName)}" ${sqlType}`;
|
|
113
|
-
if (field.isPrimaryKey && primaryKeys.length === 1)
|
|
133
|
+
if (field.isPrimaryKey && primaryKeys.length === 1) {
|
|
134
|
+
def += " PRIMARY KEY";
|
|
135
|
+
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment" && (field.designType === "number" || field.designType === "integer")) def += " AUTOINCREMENT";
|
|
136
|
+
}
|
|
114
137
|
if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
|
|
115
|
-
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${
|
|
138
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
139
|
+
if (field.collate) def += ` COLLATE ${field.collate.toUpperCase()}`;
|
|
116
140
|
colDefs.push(def);
|
|
117
141
|
}
|
|
118
142
|
if (primaryKeys.length > 1) {
|
|
@@ -129,228 +153,19 @@ function buildCreateTable(table, fields, foreignKeys) {
|
|
|
129
153
|
}
|
|
130
154
|
return `CREATE TABLE IF NOT EXISTS "${esc(table)}" (${colDefs.join(", ")})`;
|
|
131
155
|
}
|
|
132
|
-
function refActionToSql(action) {
|
|
133
|
-
switch (action) {
|
|
134
|
-
case "cascade": return "CASCADE";
|
|
135
|
-
case "restrict": return "RESTRICT";
|
|
136
|
-
case "setNull": return "SET NULL";
|
|
137
|
-
case "setDefault": return "SET DEFAULT";
|
|
138
|
-
default: return "NO ACTION";
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function sqliteTypeFromDesignType(designType) {
|
|
142
|
-
switch (designType) {
|
|
143
|
-
case "number":
|
|
144
|
-
case "integer": return "REAL";
|
|
145
|
-
case "boolean": return "INTEGER";
|
|
146
|
-
case "string": return "TEXT";
|
|
147
|
-
default: return "TEXT";
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function buildProjection(select) {
|
|
151
|
-
const fields = select?.asArray;
|
|
152
|
-
if (!fields) return "*";
|
|
153
|
-
let sql = "";
|
|
154
|
-
for (let i = 0; i < fields.length; i++) {
|
|
155
|
-
if (i > 0) sql += ", ";
|
|
156
|
-
sql += `"${esc(fields[i])}"`;
|
|
157
|
-
}
|
|
158
|
-
return sql || "*";
|
|
159
|
-
}
|
|
160
|
-
function esc(name) {
|
|
161
|
-
return name.replace(/"/g, "\"\"");
|
|
162
|
-
}
|
|
163
|
-
function sqlStringLiteral(value) {
|
|
164
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
165
|
-
}
|
|
166
|
-
function toSqliteValue(value) {
|
|
167
|
-
if (value === undefined) return null;
|
|
168
|
-
if (value === null) return null;
|
|
169
|
-
if (typeof value === "object") return JSON.stringify(value);
|
|
170
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
171
|
-
return value;
|
|
172
|
-
}
|
|
173
|
-
function buildCreateView(viewName, plan, columns, resolveFieldRef) {
|
|
174
|
-
const selectCols = columns.map((c) => `"${esc(c.sourceTable)}"."${esc(c.sourceColumn)}" AS "${esc(c.viewColumn)}"`).join(", ");
|
|
175
|
-
let sql = `CREATE VIEW IF NOT EXISTS "${esc(viewName)}" AS SELECT ${selectCols} FROM "${esc(plan.entryTable)}"`;
|
|
176
|
-
for (const join of plan.joins) {
|
|
177
|
-
const onClause = queryNodeToSql(join.condition, resolveFieldRef);
|
|
178
|
-
sql += ` JOIN "${esc(join.targetTable)}" ON ${onClause}`;
|
|
179
|
-
}
|
|
180
|
-
if (plan.filter) {
|
|
181
|
-
const whereClause = queryNodeToSql(plan.filter, resolveFieldRef);
|
|
182
|
-
sql += ` WHERE ${whereClause}`;
|
|
183
|
-
}
|
|
184
|
-
return sql;
|
|
185
|
-
}
|
|
186
|
-
const queryOpToSql = {
|
|
187
|
-
$eq: "=",
|
|
188
|
-
$ne: "!=",
|
|
189
|
-
$gt: ">",
|
|
190
|
-
$gte: ">=",
|
|
191
|
-
$lt: "<",
|
|
192
|
-
$lte: "<="
|
|
193
|
-
};
|
|
194
|
-
/**
|
|
195
|
-
* Renders an AtscriptQueryNode tree to raw SQL (no parameters — for DDL use only).
|
|
196
|
-
*/ function queryNodeToSql(node, resolveFieldRef) {
|
|
197
|
-
if ("$and" in node) {
|
|
198
|
-
const children = node.$and;
|
|
199
|
-
return children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" AND ");
|
|
200
|
-
}
|
|
201
|
-
if ("$or" in node) {
|
|
202
|
-
const children = node.$or;
|
|
203
|
-
return `(${children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" OR ")})`;
|
|
204
|
-
}
|
|
205
|
-
if ("$not" in node) return `NOT (${queryNodeToSql(node.$not, resolveFieldRef)})`;
|
|
206
|
-
const comp = node;
|
|
207
|
-
const leftSql = resolveFieldRef(comp.left);
|
|
208
|
-
const sqlOp = queryOpToSql[comp.op] || "=";
|
|
209
|
-
if (comp.right && typeof comp.right === "object" && "field" in comp.right) return `${leftSql} ${sqlOp} ${resolveFieldRef(comp.right)}`;
|
|
210
|
-
if (comp.right === null || comp.right === undefined) return comp.op === "$ne" ? `${leftSql} IS NOT NULL` : `${leftSql} IS NULL`;
|
|
211
|
-
if (typeof comp.right === "string") return `${leftSql} ${sqlOp} '${comp.right.replace(/'/g, "''")}'`;
|
|
212
|
-
return `${leftSql} ${sqlOp} ${comp.right}`;
|
|
213
|
-
}
|
|
214
156
|
|
|
215
157
|
//#endregion
|
|
216
158
|
//#region packages/db-sqlite/src/filter-builder.ts
|
|
217
|
-
const EMPTY_AND = {
|
|
218
|
-
sql: "1=1",
|
|
219
|
-
params: []
|
|
220
|
-
};
|
|
221
|
-
const EMPTY_OR = {
|
|
222
|
-
sql: "0=1",
|
|
223
|
-
params: []
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* SQL visitor for `walkFilter` — renders a filter expression tree
|
|
227
|
-
* into a parameterized SQL WHERE clause.
|
|
228
|
-
*/ const sqlVisitor = {
|
|
229
|
-
comparison(field, op, value) {
|
|
230
|
-
const col = `"${esc(field)}"`;
|
|
231
|
-
const v = toSqliteParam(value);
|
|
232
|
-
switch (op) {
|
|
233
|
-
case "$eq": {
|
|
234
|
-
if (v === null) return {
|
|
235
|
-
sql: `${col} IS NULL`,
|
|
236
|
-
params: []
|
|
237
|
-
};
|
|
238
|
-
return {
|
|
239
|
-
sql: `${col} = ?`,
|
|
240
|
-
params: [v]
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
case "$ne": {
|
|
244
|
-
if (v === null) return {
|
|
245
|
-
sql: `${col} IS NOT NULL`,
|
|
246
|
-
params: []
|
|
247
|
-
};
|
|
248
|
-
return {
|
|
249
|
-
sql: `${col} != ?`,
|
|
250
|
-
params: [v]
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
case "$gt": return {
|
|
254
|
-
sql: `${col} > ?`,
|
|
255
|
-
params: [v]
|
|
256
|
-
};
|
|
257
|
-
case "$gte": return {
|
|
258
|
-
sql: `${col} >= ?`,
|
|
259
|
-
params: [v]
|
|
260
|
-
};
|
|
261
|
-
case "$lt": return {
|
|
262
|
-
sql: `${col} < ?`,
|
|
263
|
-
params: [v]
|
|
264
|
-
};
|
|
265
|
-
case "$lte": return {
|
|
266
|
-
sql: `${col} <= ?`,
|
|
267
|
-
params: [v]
|
|
268
|
-
};
|
|
269
|
-
case "$in": {
|
|
270
|
-
const arr = value.map(toSqliteParam);
|
|
271
|
-
if (arr.length === 0) return EMPTY_OR;
|
|
272
|
-
const placeholders = arr.map(() => "?").join(", ");
|
|
273
|
-
return {
|
|
274
|
-
sql: `${col} IN (${placeholders})`,
|
|
275
|
-
params: [...arr]
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
case "$nin": {
|
|
279
|
-
const arr = value.map(toSqliteParam);
|
|
280
|
-
if (arr.length === 0) return EMPTY_AND;
|
|
281
|
-
const placeholders = arr.map(() => "?").join(", ");
|
|
282
|
-
return {
|
|
283
|
-
sql: `${col} NOT IN (${placeholders})`,
|
|
284
|
-
params: [...arr]
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
case "$exists": return value ? {
|
|
288
|
-
sql: `${col} IS NOT NULL`,
|
|
289
|
-
params: []
|
|
290
|
-
} : {
|
|
291
|
-
sql: `${col} IS NULL`,
|
|
292
|
-
params: []
|
|
293
|
-
};
|
|
294
|
-
case "$regex": {
|
|
295
|
-
const pattern = regexToLike(value instanceof RegExp ? value.source : String(value));
|
|
296
|
-
return {
|
|
297
|
-
sql: `${col} LIKE ?`,
|
|
298
|
-
params: [pattern]
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
default: throw new Error(`Unsupported filter operator: ${op}`);
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
and(children) {
|
|
305
|
-
if (children.length === 0) return EMPTY_AND;
|
|
306
|
-
return {
|
|
307
|
-
sql: children.map((c) => c.sql).join(" AND "),
|
|
308
|
-
params: children.flatMap((c) => c.params)
|
|
309
|
-
};
|
|
310
|
-
},
|
|
311
|
-
or(children) {
|
|
312
|
-
if (children.length === 0) return EMPTY_OR;
|
|
313
|
-
return {
|
|
314
|
-
sql: `(${children.map((c) => c.sql).join(" OR ")})`,
|
|
315
|
-
params: children.flatMap((c) => c.params)
|
|
316
|
-
};
|
|
317
|
-
},
|
|
318
|
-
not(child) {
|
|
319
|
-
return {
|
|
320
|
-
sql: `NOT (${child.sql})`,
|
|
321
|
-
params: child.params
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
159
|
function buildWhere(filter) {
|
|
326
|
-
|
|
327
|
-
return walkFilter(filter, sqlVisitor) ?? EMPTY_AND;
|
|
160
|
+
return buildWhere$1(sqliteDialect, filter);
|
|
328
161
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const hasStart = pattern.startsWith("^");
|
|
337
|
-
const hasEnd = pattern.endsWith("$");
|
|
338
|
-
let core = pattern;
|
|
339
|
-
if (hasStart) core = core.slice(1);
|
|
340
|
-
if (hasEnd) core = core.slice(0, -1);
|
|
341
|
-
core = core.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
342
|
-
core = core.replace(/\.\*/g, "%").replace(/\./g, "_");
|
|
343
|
-
if (hasStart && hasEnd) return core;
|
|
344
|
-
if (hasStart) return `${core}%`;
|
|
345
|
-
if (hasEnd) return `%${core}`;
|
|
346
|
-
return `%${core}%`;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Converts a JS value to a SQLite-bindable parameter.
|
|
350
|
-
* SQLite cannot bind booleans — they must be 0/1.
|
|
351
|
-
*/ function toSqliteParam(value) {
|
|
352
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
353
|
-
return value;
|
|
162
|
+
function buildPrefixedWhere(alias, filter) {
|
|
163
|
+
return buildWhere$1({
|
|
164
|
+
...sqliteDialect,
|
|
165
|
+
quoteIdentifier(name) {
|
|
166
|
+
return `${alias}."${esc(name)}"`;
|
|
167
|
+
}
|
|
168
|
+
}, filter);
|
|
354
169
|
}
|
|
355
170
|
|
|
356
171
|
//#endregion
|
|
@@ -366,6 +181,9 @@ else obj[key] = value;
|
|
|
366
181
|
return obj;
|
|
367
182
|
}
|
|
368
183
|
var SqliteAdapter = class extends BaseDbAdapter {
|
|
184
|
+
supportsNativeValueDefaults() {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
369
187
|
async _beginTransaction() {
|
|
370
188
|
this._log("BEGIN");
|
|
371
189
|
this.driver.exec("BEGIN");
|
|
@@ -394,39 +212,39 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
394
212
|
*/ _wrapConstraintError(fn) {
|
|
395
213
|
try {
|
|
396
214
|
return fn();
|
|
397
|
-
} catch (
|
|
398
|
-
if (
|
|
399
|
-
if (
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (error instanceof Error) {
|
|
217
|
+
if (error.message.includes("FOREIGN KEY constraint failed")) throw new DbError("FK_VIOLATION", [{
|
|
400
218
|
path: "",
|
|
401
|
-
message:
|
|
219
|
+
message: error.message
|
|
402
220
|
}]);
|
|
403
|
-
const uniqueMatch =
|
|
221
|
+
const uniqueMatch = error.message.match(/UNIQUE constraint failed:\s*\S+\.(\S+)/);
|
|
404
222
|
if (uniqueMatch) throw new DbError("CONFLICT", [{
|
|
405
223
|
path: uniqueMatch[1],
|
|
406
|
-
message:
|
|
224
|
+
message: error.message
|
|
407
225
|
}]);
|
|
408
|
-
if (
|
|
226
|
+
if (error.message.includes("UNIQUE constraint failed")) throw new DbError("CONFLICT", [{
|
|
409
227
|
path: "",
|
|
410
|
-
message:
|
|
228
|
+
message: error.message
|
|
411
229
|
}]);
|
|
412
230
|
}
|
|
413
|
-
throw
|
|
231
|
+
throw error;
|
|
414
232
|
}
|
|
415
233
|
}
|
|
416
234
|
async insertOne(data) {
|
|
417
|
-
const { sql, params } = buildInsert(this.resolveTableName(), data);
|
|
235
|
+
const { sql, params } = buildInsert$1(this.resolveTableName(), data);
|
|
418
236
|
this._log(sql, params);
|
|
419
237
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
420
|
-
return { insertedId: result.lastInsertRowid };
|
|
238
|
+
return { insertedId: this._resolveInsertedId(data, result.lastInsertRowid) };
|
|
421
239
|
}
|
|
422
240
|
async insertMany(data) {
|
|
423
241
|
return this.withTransaction(async () => {
|
|
424
242
|
const ids = [];
|
|
425
243
|
for (const row of data) {
|
|
426
|
-
const { sql, params } = buildInsert(this.resolveTableName(), row);
|
|
244
|
+
const { sql, params } = buildInsert$1(this.resolveTableName(), row);
|
|
427
245
|
this._log(sql, params);
|
|
428
246
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
429
|
-
ids.push(result.lastInsertRowid);
|
|
247
|
+
ids.push(this._resolveInsertedId(row, result.lastInsertRowid));
|
|
430
248
|
}
|
|
431
249
|
return {
|
|
432
250
|
insertedCount: ids.length,
|
|
@@ -440,13 +258,13 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
440
258
|
...query.controls,
|
|
441
259
|
$limit: 1
|
|
442
260
|
};
|
|
443
|
-
const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
|
|
261
|
+
const { sql, params } = buildSelect$1(this.resolveTableName(), where, controls);
|
|
444
262
|
this._log(sql, params);
|
|
445
263
|
return this.driver.get(sql, params);
|
|
446
264
|
}
|
|
447
265
|
async findMany(query) {
|
|
448
266
|
const where = buildWhere(query.filter);
|
|
449
|
-
const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
|
|
267
|
+
const { sql, params } = buildSelect$1(this.resolveTableName(), where, query.controls);
|
|
450
268
|
this._log(sql, params);
|
|
451
269
|
return this.driver.all(sql, params);
|
|
452
270
|
}
|
|
@@ -458,6 +276,19 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
458
276
|
const row = this.driver.get(sql, where.params);
|
|
459
277
|
return row?.cnt ?? 0;
|
|
460
278
|
}
|
|
279
|
+
async aggregate(query) {
|
|
280
|
+
const where = buildWhere(query.filter);
|
|
281
|
+
const tableName = this.resolveTableName();
|
|
282
|
+
if (query.controls.$count) {
|
|
283
|
+
const { sql: sql$1, params: params$1 } = buildAggregateCount$1(tableName, where, query.controls);
|
|
284
|
+
this._log(sql$1, params$1);
|
|
285
|
+
const row = this.driver.get(sql$1, params$1);
|
|
286
|
+
return [{ count: row?.count ?? 0 }];
|
|
287
|
+
}
|
|
288
|
+
const { sql, params } = buildAggregateSelect$1(tableName, where, query.controls);
|
|
289
|
+
this._log(sql, params);
|
|
290
|
+
return this.driver.all(sql, params);
|
|
291
|
+
}
|
|
461
292
|
async updateOne(filter, data) {
|
|
462
293
|
const where = buildWhere(filter);
|
|
463
294
|
const tableName = this.resolveTableName();
|
|
@@ -465,7 +296,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
465
296
|
const setParams = [];
|
|
466
297
|
for (const [key, value] of Object.entries(data)) {
|
|
467
298
|
setClauses.push(`"${esc(key)}" = ?`);
|
|
468
|
-
setParams.push(
|
|
299
|
+
setParams.push(toSqlValue(value));
|
|
469
300
|
}
|
|
470
301
|
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
471
302
|
const allParams = [...setParams, ...where.params];
|
|
@@ -478,7 +309,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
478
309
|
}
|
|
479
310
|
async updateMany(filter, data) {
|
|
480
311
|
const where = buildWhere(filter);
|
|
481
|
-
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
312
|
+
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where);
|
|
482
313
|
this._log(sql, params);
|
|
483
314
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
484
315
|
return {
|
|
@@ -493,7 +324,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
493
324
|
sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
|
|
494
325
|
params: where.params
|
|
495
326
|
};
|
|
496
|
-
const { sql, params } = buildUpdate(tableName, data, limitedWhere);
|
|
327
|
+
const { sql, params } = buildUpdate$1(tableName, data, limitedWhere);
|
|
497
328
|
this._log(sql, params);
|
|
498
329
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
499
330
|
return {
|
|
@@ -503,7 +334,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
503
334
|
}
|
|
504
335
|
async replaceMany(filter, data) {
|
|
505
336
|
const where = buildWhere(filter);
|
|
506
|
-
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
337
|
+
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where);
|
|
507
338
|
this._log(sql, params);
|
|
508
339
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
509
340
|
return {
|
|
@@ -521,7 +352,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
521
352
|
}
|
|
522
353
|
async deleteMany(filter) {
|
|
523
354
|
const where = buildWhere(filter);
|
|
524
|
-
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
355
|
+
const { sql, params } = buildDelete$1(this.resolveTableName(), where);
|
|
525
356
|
this._log(sql, params);
|
|
526
357
|
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
527
358
|
return { deletedCount: result.changes };
|
|
@@ -531,10 +362,26 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
531
362
|
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
|
|
532
363
|
this._log(sql);
|
|
533
364
|
this.driver.exec(sql);
|
|
365
|
+
this._seedIncrementStart();
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Seeds the sqlite_sequence table for auto-increment fields that have a start value.
|
|
369
|
+
* Only applies once per adapter instance (idempotent via INSERT OR IGNORE + flag).
|
|
370
|
+
*/ _seedIncrementStart() {
|
|
371
|
+
if (this._incrementSeeded) return;
|
|
372
|
+
this._incrementSeeded = true;
|
|
373
|
+
const tableName = this.resolveTableName();
|
|
374
|
+
for (const def of this._table.defaults.values()) if (def.kind === "fn" && def.fn === "increment" && typeof def.start === "number") {
|
|
375
|
+
const seedSql = `INSERT OR IGNORE INTO sqlite_sequence(name, seq) VALUES(?, ?)`;
|
|
376
|
+
const params = [tableName, def.start - 1];
|
|
377
|
+
this._log(seedSql, params);
|
|
378
|
+
this.driver.run(seedSql, params);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
534
381
|
}
|
|
535
382
|
async ensureView() {
|
|
536
383
|
const view = this._table;
|
|
537
|
-
const sql = buildCreateView(this.resolveTableName(), view.viewPlan, view.getViewColumnMappings(), (ref) => view.resolveFieldRef(ref));
|
|
384
|
+
const sql = buildCreateView$1(this.resolveTableName(), view.viewPlan, view.getViewColumnMappings(), (ref) => view.resolveFieldRef(ref));
|
|
538
385
|
this._log(sql);
|
|
539
386
|
this.driver.exec(sql);
|
|
540
387
|
}
|
|
@@ -555,8 +402,9 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
555
402
|
const sqlType = this.typeMapper(field);
|
|
556
403
|
let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
|
|
557
404
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
558
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${
|
|
405
|
+
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
559
406
|
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
407
|
+
if (field.collate) ddl += ` COLLATE ${field.collate.toUpperCase()}`;
|
|
560
408
|
this._log(ddl);
|
|
561
409
|
this.driver.exec(ddl);
|
|
562
410
|
added.push(field.physicalName);
|
|
@@ -569,6 +417,7 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
569
417
|
async recreateTable() {
|
|
570
418
|
const tableName = this.resolveTableName();
|
|
571
419
|
const tempName = `${tableName}__tmp_${Date.now()}`;
|
|
420
|
+
this._dropAllFtsTables(tableName);
|
|
572
421
|
this.driver.exec("PRAGMA foreign_keys = OFF");
|
|
573
422
|
this.driver.exec("PRAGMA legacy_alter_table = ON");
|
|
574
423
|
try {
|
|
@@ -585,7 +434,7 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
585
434
|
const selectExprs = commonCols.map((c) => {
|
|
586
435
|
const field = fieldsByName.get(c);
|
|
587
436
|
if (field && !field.optional && !field.isPrimaryKey) {
|
|
588
|
-
const fallback = field.defaultValue?.kind === "value" ?
|
|
437
|
+
const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral(field.designType, field.defaultValue.value) : defaultValueForType(field.designType);
|
|
589
438
|
return `COALESCE("${esc(c)}", ${fallback}) AS "${esc(c)}"`;
|
|
590
439
|
}
|
|
591
440
|
return `"${esc(c)}"`;
|
|
@@ -605,6 +454,7 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
605
454
|
}
|
|
606
455
|
async dropTable() {
|
|
607
456
|
const tableName = this.resolveTableName();
|
|
457
|
+
this._dropAllFtsTables(tableName);
|
|
608
458
|
const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
|
|
609
459
|
this._log(ddl);
|
|
610
460
|
this.driver.exec(ddl);
|
|
@@ -620,6 +470,7 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
620
470
|
});
|
|
621
471
|
}
|
|
622
472
|
async dropTableByName(tableName) {
|
|
473
|
+
this._dropAllFtsTables(tableName);
|
|
623
474
|
const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
|
|
624
475
|
this._log(ddl);
|
|
625
476
|
this.driver.exec(ddl);
|
|
@@ -667,23 +518,151 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
667
518
|
},
|
|
668
519
|
shouldSkipType: (type) => type === "fulltext"
|
|
669
520
|
});
|
|
521
|
+
this._syncFtsIndexes(tableName);
|
|
522
|
+
}
|
|
523
|
+
getSearchIndexes() {
|
|
524
|
+
return this._getFulltextIndexes().map((idx) => ({
|
|
525
|
+
name: idx.name,
|
|
526
|
+
description: `FTS5 index (${idx.fields.map((f) => f.name).join(", ")})`,
|
|
527
|
+
type: "text"
|
|
528
|
+
}));
|
|
529
|
+
}
|
|
530
|
+
async search(text, query, indexName) {
|
|
531
|
+
if (!text.trim()) return [];
|
|
532
|
+
const base = this._buildFtsBase(text, query.filter, indexName);
|
|
533
|
+
const controls = query.controls || {};
|
|
534
|
+
let cols = "t.*";
|
|
535
|
+
if (controls.$select?.asArray?.length) cols = controls.$select.asArray.map((c) => `t."${esc(c)}"`).join(", ");
|
|
536
|
+
let sql = `SELECT ${cols} ${base.fromWhere}`;
|
|
537
|
+
const params = [...base.params];
|
|
538
|
+
if (controls.$sort) {
|
|
539
|
+
const orderParts = [];
|
|
540
|
+
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(`t."${esc(col)}" ${dir === -1 ? "DESC" : "ASC"}`);
|
|
541
|
+
if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
542
|
+
}
|
|
543
|
+
if (controls.$limit !== undefined) {
|
|
544
|
+
sql += ` LIMIT ?`;
|
|
545
|
+
params.push(controls.$limit);
|
|
546
|
+
}
|
|
547
|
+
if (controls.$skip !== undefined) {
|
|
548
|
+
if (controls.$limit === undefined) sql += ` LIMIT -1`;
|
|
549
|
+
sql += ` OFFSET ?`;
|
|
550
|
+
params.push(controls.$skip);
|
|
551
|
+
}
|
|
552
|
+
this._log(sql, params);
|
|
553
|
+
return this.driver.all(sql, params);
|
|
554
|
+
}
|
|
555
|
+
async searchWithCount(text, query, indexName) {
|
|
556
|
+
if (!text.trim()) return {
|
|
557
|
+
data: [],
|
|
558
|
+
count: 0
|
|
559
|
+
};
|
|
560
|
+
const data = await this.search(text, query, indexName);
|
|
561
|
+
const base = this._buildFtsBase(text, query.filter, indexName);
|
|
562
|
+
const countSql = `SELECT COUNT(*) as cnt ${base.fromWhere}`;
|
|
563
|
+
this._log(countSql, base.params);
|
|
564
|
+
const row = this.driver.get(countSql, base.params);
|
|
565
|
+
return {
|
|
566
|
+
data,
|
|
567
|
+
count: row?.cnt ?? 0
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
/** Builds FTS table name from index name: `<table>__fts__<indexName>`. */ _ftsTableName(indexName) {
|
|
571
|
+
return `${this.resolveTableName()}__fts__${indexName}`;
|
|
572
|
+
}
|
|
573
|
+
/** Returns fulltext indexes from table metadata. */ _getFulltextIndexes() {
|
|
574
|
+
const result = [];
|
|
575
|
+
for (const index of this._table.indexes.values()) if (index.type === "fulltext") result.push(index);
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
/** Resolves a fulltext index by name, or returns the first available. */ _resolveFtsIndex(indexName) {
|
|
579
|
+
const ftIndexes = this._getFulltextIndexes();
|
|
580
|
+
if (ftIndexes.length === 0) throw new Error("No search index available");
|
|
581
|
+
if (indexName) {
|
|
582
|
+
const found = ftIndexes.find((idx) => idx.name === indexName);
|
|
583
|
+
if (!found) throw new Error(`Search index "${indexName}" not found`);
|
|
584
|
+
return found;
|
|
585
|
+
}
|
|
586
|
+
return ftIndexes[0];
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Builds the shared FROM+JOIN+WHERE fragment for FTS5 queries.
|
|
590
|
+
* Both data and count queries reuse this to avoid duplicating index resolution and filter translation.
|
|
591
|
+
*/ _buildFtsBase(text, filter, indexName) {
|
|
592
|
+
const ftsIndex = this._resolveFtsIndex(indexName);
|
|
593
|
+
const ftsTable = this._ftsTableName(ftsIndex.name);
|
|
594
|
+
const tableName = this.resolveTableName();
|
|
595
|
+
const where = buildPrefixedWhere("t", filter);
|
|
596
|
+
let fromWhere = `FROM "${esc(tableName)}" AS t`;
|
|
597
|
+
fromWhere += ` JOIN "${esc(ftsTable)}" AS fts ON t.rowid = fts.rowid`;
|
|
598
|
+
fromWhere += ` WHERE fts."${esc(ftsTable)}" MATCH ?`;
|
|
599
|
+
const params = [text];
|
|
600
|
+
if (where.sql !== "1=1") {
|
|
601
|
+
fromWhere += ` AND (${where.sql})`;
|
|
602
|
+
params.push(...where.params);
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
fromWhere,
|
|
606
|
+
params
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Creates/drops FTS5 virtual tables and sync triggers to match desired fulltext indexes.
|
|
611
|
+
*/ _syncFtsIndexes(tableName) {
|
|
612
|
+
const ftIndexes = this._getFulltextIndexes();
|
|
613
|
+
const desiredFtsTables = new Set(ftIndexes.map((idx) => this._ftsTableName(idx.name)));
|
|
614
|
+
const existingFts = this.driver.all(`SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE ?`, [`${tableName}__fts__%`]).filter((r) => r.sql.startsWith("CREATE VIRTUAL TABLE")).map((r) => r.name);
|
|
615
|
+
for (const name of existingFts) if (!desiredFtsTables.has(name)) this._dropFtsTable(name);
|
|
616
|
+
const existingSet = new Set(existingFts);
|
|
617
|
+
for (const index of ftIndexes) {
|
|
618
|
+
const ftsTable = this._ftsTableName(index.name);
|
|
619
|
+
if (!existingSet.has(ftsTable)) this._createFtsTable(tableName, ftsTable, index);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/** Creates an FTS5 virtual table with sync triggers and rebuilds the index. */ _createFtsTable(tableName, ftsTable, index) {
|
|
623
|
+
const fieldNames = index.fields.map((f) => `"${esc(f.name)}"`);
|
|
624
|
+
const fieldList = fieldNames.join(", ");
|
|
625
|
+
const createSql = `CREATE VIRTUAL TABLE IF NOT EXISTS "${esc(ftsTable)}" USING fts5(${fieldList}, content='${tableName.replace(/'/g, "''")}', content_rowid='rowid')`;
|
|
626
|
+
this._log(createSql);
|
|
627
|
+
this.driver.exec(createSql);
|
|
628
|
+
const newFields = index.fields.map((f) => `new."${esc(f.name)}"`).join(", ");
|
|
629
|
+
const oldFields = index.fields.map((f) => `old."${esc(f.name)}"`).join(", ");
|
|
630
|
+
const ef = esc(ftsTable);
|
|
631
|
+
const aiSql = `CREATE TRIGGER IF NOT EXISTS "${esc(ftsTable + "__ai")}" AFTER INSERT ON "${esc(tableName)}" BEGIN INSERT INTO "${ef}"(rowid, ${fieldList}) VALUES (new.rowid, ${newFields}); END`;
|
|
632
|
+
this._log(aiSql);
|
|
633
|
+
this.driver.exec(aiSql);
|
|
634
|
+
const adSql = `CREATE TRIGGER IF NOT EXISTS "${esc(ftsTable + "__ad")}" AFTER DELETE ON "${esc(tableName)}" BEGIN INSERT INTO "${ef}"("${ef}", rowid, ${fieldList}) VALUES ('delete', old.rowid, ${oldFields}); END`;
|
|
635
|
+
this._log(adSql);
|
|
636
|
+
this.driver.exec(adSql);
|
|
637
|
+
const auSql = `CREATE TRIGGER IF NOT EXISTS "${esc(ftsTable + "__au")}" AFTER UPDATE ON "${esc(tableName)}" BEGIN INSERT INTO "${ef}"("${ef}", rowid, ${fieldList}) VALUES ('delete', old.rowid, ${oldFields}); INSERT INTO "${ef}"(rowid, ${fieldList}) VALUES (new.rowid, ${newFields}); END`;
|
|
638
|
+
this._log(auSql);
|
|
639
|
+
this.driver.exec(auSql);
|
|
640
|
+
const rebuildSql = `INSERT INTO "${ef}"("${ef}") VALUES ('rebuild')`;
|
|
641
|
+
this._log(rebuildSql);
|
|
642
|
+
this.driver.exec(rebuildSql);
|
|
643
|
+
}
|
|
644
|
+
/** Drops an FTS5 virtual table and its sync triggers. */ _dropFtsTable(ftsTable) {
|
|
645
|
+
for (const suffix of [
|
|
646
|
+
"__ai",
|
|
647
|
+
"__ad",
|
|
648
|
+
"__au"
|
|
649
|
+
]) this.driver.exec(`DROP TRIGGER IF EXISTS "${esc(ftsTable + suffix)}"`);
|
|
650
|
+
const sql = `DROP TABLE IF EXISTS "${esc(ftsTable)}"`;
|
|
651
|
+
this._log(sql);
|
|
652
|
+
this.driver.exec(sql);
|
|
653
|
+
}
|
|
654
|
+
/** Drops all FTS virtual tables and triggers for a content table. */ _dropAllFtsTables(tableName) {
|
|
655
|
+
const ftsTables = this.driver.all(`SELECT name, sql FROM sqlite_master WHERE type='table' AND name LIKE ?`, [`${tableName}__fts__%`]).filter((r) => r.sql.startsWith("CREATE VIRTUAL TABLE"));
|
|
656
|
+
for (const { name } of ftsTables) this._dropFtsTable(name);
|
|
670
657
|
}
|
|
671
658
|
constructor(driver) {
|
|
672
|
-
super(), _define_property(this, "driver", void 0), this.driver = driver;
|
|
659
|
+
super(), _define_property(this, "driver", void 0), _define_property(this, "_incrementSeeded", void 0), this.driver = driver, this._incrementSeeded = false;
|
|
673
660
|
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
674
661
|
}
|
|
675
662
|
};
|
|
676
|
-
/** Returns a safe SQLite DEFAULT literal for a given design type. */ function defaultValueForType(designType) {
|
|
677
|
-
switch (designType) {
|
|
678
|
-
case "number":
|
|
679
|
-
case "integer": return "0";
|
|
680
|
-
case "boolean": return "0";
|
|
681
|
-
default: return "''";
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
663
|
/** Normalizes SQLite PRAGMA dflt_value to match serialized format.
|
|
685
664
|
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */ function normalizeSqliteDefault(value) {
|
|
686
|
-
if (value
|
|
665
|
+
if (value === null) return undefined;
|
|
687
666
|
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
688
667
|
return value;
|
|
689
668
|
}
|