@atscript/db-sqlite 0.1.34 → 0.1.36
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/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/index.cjs +429 -217
- package/dist/index.d.ts +28 -2
- package/dist/index.mjs +429 -218
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter } from "@atscript/utils-db";
|
|
1
|
+
import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/utils-db";
|
|
2
2
|
import { walkFilter } from "@uniqu/core";
|
|
3
3
|
|
|
4
4
|
//#region rolldown:runtime
|
|
@@ -7,6 +7,211 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
7
7
|
throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function.");
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region packages/db-sqlite/src/better-sqlite3-driver.ts
|
|
12
|
+
function _define_property$1(obj, key, value) {
|
|
13
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
14
|
+
value,
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true
|
|
18
|
+
});
|
|
19
|
+
else obj[key] = value;
|
|
20
|
+
return obj;
|
|
21
|
+
}
|
|
22
|
+
var BetterSqlite3Driver = class {
|
|
23
|
+
run(sql, params) {
|
|
24
|
+
const stmt = this.db.prepare(sql);
|
|
25
|
+
const result = params ? stmt.run(...params) : stmt.run();
|
|
26
|
+
return {
|
|
27
|
+
changes: result.changes,
|
|
28
|
+
lastInsertRowid: result.lastInsertRowid
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
all(sql, params) {
|
|
32
|
+
const stmt = this.db.prepare(sql);
|
|
33
|
+
return params ? stmt.all(...params) : stmt.all();
|
|
34
|
+
}
|
|
35
|
+
get(sql, params) {
|
|
36
|
+
const stmt = this.db.prepare(sql);
|
|
37
|
+
return (params ? stmt.get(...params) : stmt.get()) ?? null;
|
|
38
|
+
}
|
|
39
|
+
exec(sql) {
|
|
40
|
+
this.db.exec(sql);
|
|
41
|
+
}
|
|
42
|
+
close() {
|
|
43
|
+
this.db.close();
|
|
44
|
+
}
|
|
45
|
+
constructor(pathOrDb, options) {
|
|
46
|
+
_define_property$1(this, "db", void 0);
|
|
47
|
+
if (typeof pathOrDb === "string") {
|
|
48
|
+
const Database = __require("better-sqlite3");
|
|
49
|
+
this.db = new Database(pathOrDb, options);
|
|
50
|
+
} else this.db = pathOrDb;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region packages/db-sqlite/src/sql-builder.ts
|
|
56
|
+
function buildInsert(table, data) {
|
|
57
|
+
const keys = Object.keys(data);
|
|
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
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function buildSelect(table, where, controls) {
|
|
66
|
+
const cols = buildProjection(controls?.$select);
|
|
67
|
+
let sql = `SELECT ${cols} FROM "${esc(table)}" WHERE ${where.sql}`;
|
|
68
|
+
const params = [...where.params];
|
|
69
|
+
if (controls?.$sort) {
|
|
70
|
+
const orderParts = [];
|
|
71
|
+
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(`"${esc(col)}" ${dir === -1 ? "DESC" : "ASC"}`);
|
|
72
|
+
if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
73
|
+
}
|
|
74
|
+
if (controls?.$limit !== undefined) {
|
|
75
|
+
sql += ` LIMIT ?`;
|
|
76
|
+
params.push(controls.$limit);
|
|
77
|
+
}
|
|
78
|
+
if (controls?.$skip !== undefined) {
|
|
79
|
+
if (controls.$limit === undefined) sql += ` LIMIT -1`;
|
|
80
|
+
sql += ` OFFSET ?`;
|
|
81
|
+
params.push(controls.$skip);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
sql,
|
|
85
|
+
params
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function buildUpdate(table, data, where) {
|
|
89
|
+
const setClauses = [];
|
|
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
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function buildDelete(table, where) {
|
|
101
|
+
return {
|
|
102
|
+
sql: `DELETE FROM "${esc(table)}" WHERE ${where.sql}`,
|
|
103
|
+
params: [...where.params]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function buildCreateTable(table, fields, foreignKeys) {
|
|
107
|
+
const colDefs = [];
|
|
108
|
+
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
109
|
+
for (const field of fields) {
|
|
110
|
+
if (field.ignored) continue;
|
|
111
|
+
const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
|
|
112
|
+
let def = `"${esc(field.physicalName)}" ${sqlType}`;
|
|
113
|
+
if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
|
|
114
|
+
if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
|
|
115
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
|
|
116
|
+
colDefs.push(def);
|
|
117
|
+
}
|
|
118
|
+
if (primaryKeys.length > 1) {
|
|
119
|
+
const pkCols = primaryKeys.map((pk) => `"${esc(pk.physicalName)}"`).join(", ");
|
|
120
|
+
colDefs.push(`PRIMARY KEY (${pkCols})`);
|
|
121
|
+
}
|
|
122
|
+
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
123
|
+
const localCols = fk.fields.map((f) => `"${esc(f)}"`).join(", ");
|
|
124
|
+
const targetCols = fk.targetFields.map((f) => `"${esc(f)}"`).join(", ");
|
|
125
|
+
let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc(fk.targetTable)}" (${targetCols})`;
|
|
126
|
+
if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
|
|
127
|
+
if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
|
|
128
|
+
colDefs.push(constraint);
|
|
129
|
+
}
|
|
130
|
+
return `CREATE TABLE IF NOT EXISTS "${esc(table)}" (${colDefs.join(", ")})`;
|
|
131
|
+
}
|
|
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
|
+
|
|
10
215
|
//#endregion
|
|
11
216
|
//#region packages/db-sqlite/src/filter-builder.ts
|
|
12
217
|
const EMPTY_AND = {
|
|
@@ -22,7 +227,7 @@ const EMPTY_OR = {
|
|
|
22
227
|
* into a parameterized SQL WHERE clause.
|
|
23
228
|
*/ const sqlVisitor = {
|
|
24
229
|
comparison(field, op, value) {
|
|
25
|
-
const col = `"${
|
|
230
|
+
const col = `"${esc(field)}"`;
|
|
26
231
|
const v = toSqliteParam(value);
|
|
27
232
|
switch (op) {
|
|
28
233
|
case "$eq": {
|
|
@@ -119,7 +324,7 @@ const EMPTY_OR = {
|
|
|
119
324
|
};
|
|
120
325
|
function buildWhere(filter) {
|
|
121
326
|
if (!filter || Object.keys(filter).length === 0) return EMPTY_AND;
|
|
122
|
-
return walkFilter(filter, sqlVisitor);
|
|
327
|
+
return walkFilter(filter, sqlVisitor) ?? EMPTY_AND;
|
|
123
328
|
}
|
|
124
329
|
/**
|
|
125
330
|
* Basic regex-to-LIKE conversion.
|
|
@@ -141,12 +346,6 @@ function buildWhere(filter) {
|
|
|
141
346
|
return `%${core}%`;
|
|
142
347
|
}
|
|
143
348
|
/**
|
|
144
|
-
* Escapes a SQL identifier to prevent injection.
|
|
145
|
-
* Doubles any embedded double-quotes.
|
|
146
|
-
*/ function escapeIdent(name) {
|
|
147
|
-
return name.replace(/"/g, "\"\"");
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
349
|
* Converts a JS value to a SQLite-bindable parameter.
|
|
151
350
|
* SQLite cannot bind booleans — they must be 0/1.
|
|
152
351
|
*/ function toSqliteParam(value) {
|
|
@@ -154,128 +353,9 @@ function buildWhere(filter) {
|
|
|
154
353
|
return value;
|
|
155
354
|
}
|
|
156
355
|
|
|
157
|
-
//#endregion
|
|
158
|
-
//#region packages/db-sqlite/src/sql-builder.ts
|
|
159
|
-
function buildInsert(table, data) {
|
|
160
|
-
const keys = Object.keys(data);
|
|
161
|
-
const cols = keys.map((k) => `"${esc$1(k)}"`).join(", ");
|
|
162
|
-
const placeholders = keys.map(() => "?").join(", ");
|
|
163
|
-
return {
|
|
164
|
-
sql: `INSERT INTO "${esc$1(table)}" (${cols}) VALUES (${placeholders})`,
|
|
165
|
-
params: keys.map((k) => toSqliteValue$1(data[k]))
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function buildSelect(table, where, controls) {
|
|
169
|
-
const cols = buildProjection(controls?.$select);
|
|
170
|
-
let sql = `SELECT ${cols} FROM "${esc$1(table)}" WHERE ${where.sql}`;
|
|
171
|
-
const params = [...where.params];
|
|
172
|
-
if (controls?.$sort) {
|
|
173
|
-
const orderParts = [];
|
|
174
|
-
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(`"${esc$1(col)}" ${dir === -1 ? "DESC" : "ASC"}`);
|
|
175
|
-
if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
176
|
-
}
|
|
177
|
-
if (controls?.$limit !== undefined) {
|
|
178
|
-
sql += ` LIMIT ?`;
|
|
179
|
-
params.push(controls.$limit);
|
|
180
|
-
}
|
|
181
|
-
if (controls?.$skip !== undefined) {
|
|
182
|
-
if (controls.$limit === undefined) sql += ` LIMIT -1`;
|
|
183
|
-
sql += ` OFFSET ?`;
|
|
184
|
-
params.push(controls.$skip);
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
sql,
|
|
188
|
-
params
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
function buildUpdate(table, data, where) {
|
|
192
|
-
const setClauses = [];
|
|
193
|
-
const params = [];
|
|
194
|
-
for (const [key, value] of Object.entries(data)) {
|
|
195
|
-
setClauses.push(`"${esc$1(key)}" = ?`);
|
|
196
|
-
params.push(toSqliteValue$1(value));
|
|
197
|
-
}
|
|
198
|
-
return {
|
|
199
|
-
sql: `UPDATE "${esc$1(table)}" SET ${setClauses.join(", ")} WHERE ${where.sql}`,
|
|
200
|
-
params: [...params, ...where.params]
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
function buildDelete(table, where) {
|
|
204
|
-
return {
|
|
205
|
-
sql: `DELETE FROM "${esc$1(table)}" WHERE ${where.sql}`,
|
|
206
|
-
params: [...where.params]
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
function buildCreateTable(table, fields, foreignKeys) {
|
|
210
|
-
const colDefs = [];
|
|
211
|
-
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
212
|
-
for (const field of fields) {
|
|
213
|
-
if (field.ignored) continue;
|
|
214
|
-
const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
|
|
215
|
-
let def = `"${esc$1(field.physicalName)}" ${sqlType}`;
|
|
216
|
-
if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
|
|
217
|
-
if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
|
|
218
|
-
colDefs.push(def);
|
|
219
|
-
}
|
|
220
|
-
if (primaryKeys.length > 1) {
|
|
221
|
-
const pkCols = primaryKeys.map((pk) => `"${esc$1(pk.physicalName)}"`).join(", ");
|
|
222
|
-
colDefs.push(`PRIMARY KEY (${pkCols})`);
|
|
223
|
-
}
|
|
224
|
-
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
225
|
-
const localCols = fk.fields.map((f) => `"${esc$1(f)}"`).join(", ");
|
|
226
|
-
const targetCols = fk.targetFields.map((f) => `"${esc$1(f)}"`).join(", ");
|
|
227
|
-
let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc$1(fk.targetTable)}" (${targetCols})`;
|
|
228
|
-
if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
|
|
229
|
-
if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
|
|
230
|
-
colDefs.push(constraint);
|
|
231
|
-
}
|
|
232
|
-
return `CREATE TABLE IF NOT EXISTS "${esc$1(table)}" (${colDefs.join(", ")})`;
|
|
233
|
-
}
|
|
234
|
-
function refActionToSql(action) {
|
|
235
|
-
switch (action) {
|
|
236
|
-
case "cascade": return "CASCADE";
|
|
237
|
-
case "restrict": return "RESTRICT";
|
|
238
|
-
case "setNull": return "SET NULL";
|
|
239
|
-
case "setDefault": return "SET DEFAULT";
|
|
240
|
-
default: return "NO ACTION";
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
function sqliteTypeFromDesignType(designType) {
|
|
244
|
-
switch (designType) {
|
|
245
|
-
case "number":
|
|
246
|
-
case "integer": return "REAL";
|
|
247
|
-
case "boolean": return "INTEGER";
|
|
248
|
-
case "string": return "TEXT";
|
|
249
|
-
default: return "TEXT";
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function buildProjection(select) {
|
|
253
|
-
const fields = select?.asArray;
|
|
254
|
-
if (!fields) return "*";
|
|
255
|
-
let sql = "";
|
|
256
|
-
for (let i = 0; i < fields.length; i++) {
|
|
257
|
-
if (i > 0) sql += ", ";
|
|
258
|
-
sql += `"${esc$1(fields[i])}"`;
|
|
259
|
-
}
|
|
260
|
-
return sql || "*";
|
|
261
|
-
}
|
|
262
|
-
function esc$1(name) {
|
|
263
|
-
return name.replace(/"/g, "\"\"");
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Converts a JS value to a SQLite-compatible value.
|
|
267
|
-
* Objects and arrays are stored as JSON strings.
|
|
268
|
-
*/ function toSqliteValue$1(value) {
|
|
269
|
-
if (value === undefined) return null;
|
|
270
|
-
if (value === null) return null;
|
|
271
|
-
if (typeof value === "object") return JSON.stringify(value);
|
|
272
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
273
|
-
return value;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
356
|
//#endregion
|
|
277
357
|
//#region packages/db-sqlite/src/sqlite-adapter.ts
|
|
278
|
-
function _define_property
|
|
358
|
+
function _define_property(obj, key, value) {
|
|
279
359
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
280
360
|
value,
|
|
281
361
|
enumerable: true,
|
|
@@ -286,35 +366,73 @@ else obj[key] = value;
|
|
|
286
366
|
return obj;
|
|
287
367
|
}
|
|
288
368
|
var SqliteAdapter = class extends BaseDbAdapter {
|
|
369
|
+
async _beginTransaction() {
|
|
370
|
+
this._log("BEGIN");
|
|
371
|
+
this.driver.exec("BEGIN");
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
async _commitTransaction() {
|
|
375
|
+
this._log("COMMIT");
|
|
376
|
+
this.driver.exec("COMMIT");
|
|
377
|
+
}
|
|
378
|
+
async _rollbackTransaction() {
|
|
379
|
+
this._log("ROLLBACK");
|
|
380
|
+
this.driver.exec("ROLLBACK");
|
|
381
|
+
}
|
|
289
382
|
/** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
|
|
290
383
|
return super.resolveTableName(false);
|
|
291
384
|
}
|
|
385
|
+
/** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */ supportsNativeForeignKeys() {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
292
388
|
prepareId(id, _fieldType) {
|
|
293
389
|
return id;
|
|
294
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Wraps a write operation to catch native SQLite constraint errors
|
|
393
|
+
* and rethrow as structured `DbError`.
|
|
394
|
+
*/ _wrapConstraintError(fn) {
|
|
395
|
+
try {
|
|
396
|
+
return fn();
|
|
397
|
+
} catch (e) {
|
|
398
|
+
if (e instanceof Error) {
|
|
399
|
+
if (e.message.includes("FOREIGN KEY constraint failed")) throw new DbError("FK_VIOLATION", [{
|
|
400
|
+
path: "",
|
|
401
|
+
message: e.message
|
|
402
|
+
}]);
|
|
403
|
+
const uniqueMatch = e.message.match(/UNIQUE constraint failed:\s*\S+\.(\S+)/);
|
|
404
|
+
if (uniqueMatch) throw new DbError("CONFLICT", [{
|
|
405
|
+
path: uniqueMatch[1],
|
|
406
|
+
message: e.message
|
|
407
|
+
}]);
|
|
408
|
+
if (e.message.includes("UNIQUE constraint failed")) throw new DbError("CONFLICT", [{
|
|
409
|
+
path: "",
|
|
410
|
+
message: e.message
|
|
411
|
+
}]);
|
|
412
|
+
}
|
|
413
|
+
throw e;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
295
416
|
async insertOne(data) {
|
|
296
417
|
const { sql, params } = buildInsert(this.resolveTableName(), data);
|
|
297
|
-
|
|
418
|
+
this._log(sql, params);
|
|
419
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
298
420
|
return { insertedId: result.lastInsertRowid };
|
|
299
421
|
}
|
|
300
422
|
async insertMany(data) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
try {
|
|
423
|
+
return this.withTransaction(async () => {
|
|
424
|
+
const ids = [];
|
|
304
425
|
for (const row of data) {
|
|
305
426
|
const { sql, params } = buildInsert(this.resolveTableName(), row);
|
|
306
|
-
|
|
427
|
+
this._log(sql, params);
|
|
428
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
307
429
|
ids.push(result.lastInsertRowid);
|
|
308
430
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
return {
|
|
315
|
-
insertedCount: ids.length,
|
|
316
|
-
insertedIds: ids
|
|
317
|
-
};
|
|
431
|
+
return {
|
|
432
|
+
insertedCount: ids.length,
|
|
433
|
+
insertedIds: ids
|
|
434
|
+
};
|
|
435
|
+
});
|
|
318
436
|
}
|
|
319
437
|
async findOne(query) {
|
|
320
438
|
const where = buildWhere(query.filter);
|
|
@@ -323,17 +441,20 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
323
441
|
$limit: 1
|
|
324
442
|
};
|
|
325
443
|
const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
|
|
444
|
+
this._log(sql, params);
|
|
326
445
|
return this.driver.get(sql, params);
|
|
327
446
|
}
|
|
328
447
|
async findMany(query) {
|
|
329
448
|
const where = buildWhere(query.filter);
|
|
330
449
|
const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
|
|
450
|
+
this._log(sql, params);
|
|
331
451
|
return this.driver.all(sql, params);
|
|
332
452
|
}
|
|
333
453
|
async count(query) {
|
|
334
454
|
const where = buildWhere(query.filter);
|
|
335
455
|
const tableName = this.resolveTableName();
|
|
336
456
|
const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
|
|
457
|
+
this._log(sql, where.params);
|
|
337
458
|
const row = this.driver.get(sql, where.params);
|
|
338
459
|
return row?.cnt ?? 0;
|
|
339
460
|
}
|
|
@@ -347,7 +468,9 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
347
468
|
setParams.push(toSqliteValue(value));
|
|
348
469
|
}
|
|
349
470
|
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
350
|
-
const
|
|
471
|
+
const allParams = [...setParams, ...where.params];
|
|
472
|
+
this._log(sql, allParams);
|
|
473
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, allParams));
|
|
351
474
|
return {
|
|
352
475
|
matchedCount: result.changes,
|
|
353
476
|
modifiedCount: result.changes
|
|
@@ -356,7 +479,8 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
356
479
|
async updateMany(filter, data) {
|
|
357
480
|
const where = buildWhere(filter);
|
|
358
481
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
359
|
-
|
|
482
|
+
this._log(sql, params);
|
|
483
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
360
484
|
return {
|
|
361
485
|
matchedCount: result.changes,
|
|
362
486
|
modifiedCount: result.changes
|
|
@@ -365,28 +489,23 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
365
489
|
async replaceOne(filter, data) {
|
|
366
490
|
const where = buildWhere(filter);
|
|
367
491
|
const tableName = this.resolveTableName();
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
modifiedCount: delResult.changes
|
|
380
|
-
};
|
|
381
|
-
} catch (error) {
|
|
382
|
-
this.driver.exec("ROLLBACK");
|
|
383
|
-
throw error;
|
|
384
|
-
}
|
|
492
|
+
const limitedWhere = {
|
|
493
|
+
sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
|
|
494
|
+
params: where.params
|
|
495
|
+
};
|
|
496
|
+
const { sql, params } = buildUpdate(tableName, data, limitedWhere);
|
|
497
|
+
this._log(sql, params);
|
|
498
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
499
|
+
return {
|
|
500
|
+
matchedCount: result.changes,
|
|
501
|
+
modifiedCount: result.changes
|
|
502
|
+
};
|
|
385
503
|
}
|
|
386
504
|
async replaceMany(filter, data) {
|
|
387
505
|
const where = buildWhere(filter);
|
|
388
506
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
389
|
-
|
|
507
|
+
this._log(sql, params);
|
|
508
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
390
509
|
return {
|
|
391
510
|
matchedCount: result.changes,
|
|
392
511
|
modifiedCount: result.changes
|
|
@@ -396,19 +515,140 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
396
515
|
const where = buildWhere(filter);
|
|
397
516
|
const tableName = this.resolveTableName();
|
|
398
517
|
const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
399
|
-
|
|
518
|
+
this._log(sql, where.params);
|
|
519
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, where.params));
|
|
400
520
|
return { deletedCount: result.changes };
|
|
401
521
|
}
|
|
402
522
|
async deleteMany(filter) {
|
|
403
523
|
const where = buildWhere(filter);
|
|
404
524
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
405
|
-
|
|
525
|
+
this._log(sql, params);
|
|
526
|
+
const result = this._wrapConstraintError(() => this.driver.run(sql, params));
|
|
406
527
|
return { deletedCount: result.changes };
|
|
407
528
|
}
|
|
408
529
|
async ensureTable() {
|
|
530
|
+
if (this._table instanceof AtscriptDbView) return this.ensureView();
|
|
409
531
|
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
|
|
532
|
+
this._log(sql);
|
|
533
|
+
this.driver.exec(sql);
|
|
534
|
+
}
|
|
535
|
+
async ensureView() {
|
|
536
|
+
const view = this._table;
|
|
537
|
+
const sql = buildCreateView(this.resolveTableName(), view.viewPlan, view.getViewColumnMappings(), (ref) => view.resolveFieldRef(ref));
|
|
538
|
+
this._log(sql);
|
|
410
539
|
this.driver.exec(sql);
|
|
411
540
|
}
|
|
541
|
+
async getExistingColumns() {
|
|
542
|
+
return this.getExistingColumnsForTable(this.resolveTableName());
|
|
543
|
+
}
|
|
544
|
+
async syncColumns(diff) {
|
|
545
|
+
const tableName = this.resolveTableName();
|
|
546
|
+
const added = [];
|
|
547
|
+
const renamed = [];
|
|
548
|
+
for (const { field, oldName } of diff.renamed ?? []) {
|
|
549
|
+
const ddl = `ALTER TABLE "${esc(tableName)}" RENAME COLUMN "${esc(oldName)}" TO "${esc(field.physicalName)}"`;
|
|
550
|
+
this._log(ddl);
|
|
551
|
+
this.driver.exec(ddl);
|
|
552
|
+
renamed.push(field.physicalName);
|
|
553
|
+
}
|
|
554
|
+
for (const field of diff.added) {
|
|
555
|
+
const sqlType = this.typeMapper(field);
|
|
556
|
+
let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
|
|
557
|
+
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
558
|
+
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
|
|
559
|
+
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
560
|
+
this._log(ddl);
|
|
561
|
+
this.driver.exec(ddl);
|
|
562
|
+
added.push(field.physicalName);
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
added,
|
|
566
|
+
renamed
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
async recreateTable() {
|
|
570
|
+
const tableName = this.resolveTableName();
|
|
571
|
+
const tempName = `${tableName}__tmp_${Date.now()}`;
|
|
572
|
+
this.driver.exec("PRAGMA foreign_keys = OFF");
|
|
573
|
+
this.driver.exec("PRAGMA legacy_alter_table = ON");
|
|
574
|
+
try {
|
|
575
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
|
|
576
|
+
this._log(createSql);
|
|
577
|
+
this.driver.exec(createSql);
|
|
578
|
+
const oldCols = (await this.getExistingColumns()).map((c) => c.name);
|
|
579
|
+
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
580
|
+
const oldColSet = new Set(oldCols);
|
|
581
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
582
|
+
if (commonCols.length > 0) {
|
|
583
|
+
const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
584
|
+
const colNames = commonCols.map((c) => `"${esc(c)}"`).join(", ");
|
|
585
|
+
const selectExprs = commonCols.map((c) => {
|
|
586
|
+
const field = fieldsByName.get(c);
|
|
587
|
+
if (field && !field.optional && !field.isPrimaryKey) {
|
|
588
|
+
const fallback = field.defaultValue?.kind === "value" ? sqlStringLiteral(field.defaultValue.value) : defaultValueForType(field.designType);
|
|
589
|
+
return `COALESCE("${esc(c)}", ${fallback}) AS "${esc(c)}"`;
|
|
590
|
+
}
|
|
591
|
+
return `"${esc(c)}"`;
|
|
592
|
+
}).join(", ");
|
|
593
|
+
const copySql = `INSERT INTO "${esc(tempName)}" (${colNames}) SELECT ${selectExprs} FROM "${esc(tableName)}"`;
|
|
594
|
+
this._log(copySql);
|
|
595
|
+
this.driver.exec(copySql);
|
|
596
|
+
}
|
|
597
|
+
const oldName = `${tableName}__old_${Date.now()}`;
|
|
598
|
+
this.driver.exec(`ALTER TABLE "${esc(tableName)}" RENAME TO "${esc(oldName)}"`);
|
|
599
|
+
this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
|
|
600
|
+
this.driver.exec(`DROP TABLE IF EXISTS "${esc(oldName)}"`);
|
|
601
|
+
} finally {
|
|
602
|
+
this.driver.exec("PRAGMA legacy_alter_table = OFF");
|
|
603
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async dropTable() {
|
|
607
|
+
const tableName = this.resolveTableName();
|
|
608
|
+
const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
|
|
609
|
+
this._log(ddl);
|
|
610
|
+
this.driver.exec(ddl);
|
|
611
|
+
}
|
|
612
|
+
async dropColumns(columns) {
|
|
613
|
+
await this.withTransaction(async () => {
|
|
614
|
+
const tableName = this.resolveTableName();
|
|
615
|
+
for (const col of columns) {
|
|
616
|
+
const ddl = `ALTER TABLE "${esc(tableName)}" DROP COLUMN "${esc(col)}"`;
|
|
617
|
+
this._log(ddl);
|
|
618
|
+
this.driver.exec(ddl);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
async dropTableByName(tableName) {
|
|
623
|
+
const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
|
|
624
|
+
this._log(ddl);
|
|
625
|
+
this.driver.exec(ddl);
|
|
626
|
+
}
|
|
627
|
+
async dropViewByName(viewName) {
|
|
628
|
+
const ddl = `DROP VIEW IF EXISTS "${esc(viewName)}"`;
|
|
629
|
+
this._log(ddl);
|
|
630
|
+
this.driver.exec(ddl);
|
|
631
|
+
}
|
|
632
|
+
async renameTable(oldName) {
|
|
633
|
+
const newName = this.resolveTableName();
|
|
634
|
+
const ddl = `ALTER TABLE "${esc(oldName)}" RENAME TO "${esc(newName)}"`;
|
|
635
|
+
this._log(ddl);
|
|
636
|
+
this.driver.exec(ddl);
|
|
637
|
+
}
|
|
638
|
+
typeMapper(field) {
|
|
639
|
+
if (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer")) return "INTEGER";
|
|
640
|
+
return sqliteTypeFromDesignType(field.designType);
|
|
641
|
+
}
|
|
642
|
+
async getExistingColumnsForTable(tableName) {
|
|
643
|
+
const rows = this.driver.all(`PRAGMA table_info("${esc(tableName)}")`);
|
|
644
|
+
return rows.map((r) => ({
|
|
645
|
+
name: r.name,
|
|
646
|
+
type: r.type,
|
|
647
|
+
notnull: r.notnull === 1,
|
|
648
|
+
pk: r.pk > 0,
|
|
649
|
+
dflt_value: normalizeSqliteDefault(r.dflt_value)
|
|
650
|
+
}));
|
|
651
|
+
}
|
|
412
652
|
async syncIndexes() {
|
|
413
653
|
const tableName = this.resolveTableName();
|
|
414
654
|
await this.syncIndexesWithDiff({
|
|
@@ -416,73 +656,44 @@ var SqliteAdapter = class extends BaseDbAdapter {
|
|
|
416
656
|
createIndex: async (index) => {
|
|
417
657
|
const unique = index.type === "unique" ? "UNIQUE " : "";
|
|
418
658
|
const cols = index.fields.map((f) => `"${esc(f.name)}" ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
|
|
419
|
-
|
|
659
|
+
const sql = `CREATE ${unique}INDEX IF NOT EXISTS "${esc(index.key)}" ON "${esc(tableName)}" (${cols})`;
|
|
660
|
+
this._log(sql);
|
|
661
|
+
this.driver.exec(sql);
|
|
420
662
|
},
|
|
421
663
|
dropIndex: async (name) => {
|
|
422
|
-
|
|
664
|
+
const sql = `DROP INDEX IF EXISTS "${esc(name)}"`;
|
|
665
|
+
this._log(sql);
|
|
666
|
+
this.driver.exec(sql);
|
|
423
667
|
},
|
|
424
668
|
shouldSkipType: (type) => type === "fulltext"
|
|
425
669
|
});
|
|
426
670
|
}
|
|
427
671
|
constructor(driver) {
|
|
428
|
-
super(), _define_property
|
|
429
|
-
driver.exec("PRAGMA foreign_keys = ON");
|
|
672
|
+
super(), _define_property(this, "driver", void 0), this.driver = driver;
|
|
673
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
430
674
|
}
|
|
431
675
|
};
|
|
432
|
-
function
|
|
433
|
-
|
|
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
|
+
}
|
|
434
683
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if (value
|
|
438
|
-
if (
|
|
439
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
684
|
+
/** Normalizes SQLite PRAGMA dflt_value to match serialized format.
|
|
685
|
+
* PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */ function normalizeSqliteDefault(value) {
|
|
686
|
+
if (value == null) return undefined;
|
|
687
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
440
688
|
return value;
|
|
441
689
|
}
|
|
442
690
|
|
|
443
691
|
//#endregion
|
|
444
|
-
//#region packages/db-sqlite/src/
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
enumerable: true,
|
|
449
|
-
configurable: true,
|
|
450
|
-
writable: true
|
|
451
|
-
});
|
|
452
|
-
else obj[key] = value;
|
|
453
|
-
return obj;
|
|
692
|
+
//#region packages/db-sqlite/src/index.ts
|
|
693
|
+
function createAdapter(connection, options) {
|
|
694
|
+
const driver = new BetterSqlite3Driver(connection, options);
|
|
695
|
+
return new DbSpace(() => new SqliteAdapter(driver));
|
|
454
696
|
}
|
|
455
|
-
var BetterSqlite3Driver = class {
|
|
456
|
-
run(sql, params) {
|
|
457
|
-
const stmt = this.db.prepare(sql);
|
|
458
|
-
const result = params ? stmt.run(...params) : stmt.run();
|
|
459
|
-
return {
|
|
460
|
-
changes: result.changes,
|
|
461
|
-
lastInsertRowid: result.lastInsertRowid
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
all(sql, params) {
|
|
465
|
-
const stmt = this.db.prepare(sql);
|
|
466
|
-
return params ? stmt.all(...params) : stmt.all();
|
|
467
|
-
}
|
|
468
|
-
get(sql, params) {
|
|
469
|
-
const stmt = this.db.prepare(sql);
|
|
470
|
-
return (params ? stmt.get(...params) : stmt.get()) ?? null;
|
|
471
|
-
}
|
|
472
|
-
exec(sql) {
|
|
473
|
-
this.db.exec(sql);
|
|
474
|
-
}
|
|
475
|
-
close() {
|
|
476
|
-
this.db.close();
|
|
477
|
-
}
|
|
478
|
-
constructor(pathOrDb, options) {
|
|
479
|
-
_define_property(this, "db", void 0);
|
|
480
|
-
if (typeof pathOrDb === "string") {
|
|
481
|
-
const Database = __require("better-sqlite3");
|
|
482
|
-
this.db = new Database(pathOrDb, options);
|
|
483
|
-
} else this.db = pathOrDb;
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
697
|
|
|
487
698
|
//#endregion
|
|
488
|
-
export { BetterSqlite3Driver, SqliteAdapter, buildWhere };
|
|
699
|
+
export { BetterSqlite3Driver, SqliteAdapter, buildWhere, createAdapter };
|