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