@atscript/db-sqlite 0.1.33 → 0.1.35
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 +363 -196
- package/dist/index.d.ts +17 -2
- package/dist/index.mjs +363 -197
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,207 @@ 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
|
+
colDefs.push(def);
|
|
133
|
+
}
|
|
134
|
+
if (primaryKeys.length > 1) {
|
|
135
|
+
const pkCols = primaryKeys.map((pk) => `"${esc(pk.physicalName)}"`).join(", ");
|
|
136
|
+
colDefs.push(`PRIMARY KEY (${pkCols})`);
|
|
137
|
+
}
|
|
138
|
+
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
139
|
+
const localCols = fk.fields.map((f) => `"${esc(f)}"`).join(", ");
|
|
140
|
+
const targetCols = fk.targetFields.map((f) => `"${esc(f)}"`).join(", ");
|
|
141
|
+
let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc(fk.targetTable)}" (${targetCols})`;
|
|
142
|
+
if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
|
|
143
|
+
if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
|
|
144
|
+
colDefs.push(constraint);
|
|
145
|
+
}
|
|
146
|
+
return `CREATE TABLE IF NOT EXISTS "${esc(table)}" (${colDefs.join(", ")})`;
|
|
147
|
+
}
|
|
148
|
+
function refActionToSql(action) {
|
|
149
|
+
switch (action) {
|
|
150
|
+
case "cascade": return "CASCADE";
|
|
151
|
+
case "restrict": return "RESTRICT";
|
|
152
|
+
case "setNull": return "SET NULL";
|
|
153
|
+
case "setDefault": return "SET DEFAULT";
|
|
154
|
+
default: return "NO ACTION";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function sqliteTypeFromDesignType(designType) {
|
|
158
|
+
switch (designType) {
|
|
159
|
+
case "number":
|
|
160
|
+
case "integer": return "REAL";
|
|
161
|
+
case "boolean": return "INTEGER";
|
|
162
|
+
case "string": return "TEXT";
|
|
163
|
+
default: return "TEXT";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function buildProjection(select) {
|
|
167
|
+
const fields = select?.asArray;
|
|
168
|
+
if (!fields) return "*";
|
|
169
|
+
let sql = "";
|
|
170
|
+
for (let i = 0; i < fields.length; i++) {
|
|
171
|
+
if (i > 0) sql += ", ";
|
|
172
|
+
sql += `"${esc(fields[i])}"`;
|
|
173
|
+
}
|
|
174
|
+
return sql || "*";
|
|
175
|
+
}
|
|
176
|
+
function esc(name) {
|
|
177
|
+
return name.replace(/"/g, "\"\"");
|
|
178
|
+
}
|
|
179
|
+
function toSqliteValue(value) {
|
|
180
|
+
if (value === undefined) return null;
|
|
181
|
+
if (value === null) return null;
|
|
182
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
183
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
function buildCreateView(viewName, plan, columns, resolveFieldRef) {
|
|
187
|
+
const selectCols = columns.map((c) => `"${esc(c.sourceTable)}"."${esc(c.sourceColumn)}" AS "${esc(c.viewColumn)}"`).join(", ");
|
|
188
|
+
let sql = `CREATE VIEW IF NOT EXISTS "${esc(viewName)}" AS SELECT ${selectCols} FROM "${esc(plan.entryTable)}"`;
|
|
189
|
+
for (const join of plan.joins) {
|
|
190
|
+
const onClause = queryNodeToSql(join.condition, resolveFieldRef);
|
|
191
|
+
sql += ` JOIN "${esc(join.targetTable)}" ON ${onClause}`;
|
|
192
|
+
}
|
|
193
|
+
if (plan.filter) {
|
|
194
|
+
const whereClause = queryNodeToSql(plan.filter, resolveFieldRef);
|
|
195
|
+
sql += ` WHERE ${whereClause}`;
|
|
196
|
+
}
|
|
197
|
+
return sql;
|
|
198
|
+
}
|
|
199
|
+
const queryOpToSql = {
|
|
200
|
+
$eq: "=",
|
|
201
|
+
$ne: "!=",
|
|
202
|
+
$gt: ">",
|
|
203
|
+
$gte: ">=",
|
|
204
|
+
$lt: "<",
|
|
205
|
+
$lte: "<="
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Renders an AtscriptQueryNode tree to raw SQL (no parameters — for DDL use only).
|
|
209
|
+
*/ function queryNodeToSql(node, resolveFieldRef) {
|
|
210
|
+
if ("$and" in node) {
|
|
211
|
+
const children = node.$and;
|
|
212
|
+
return children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" AND ");
|
|
213
|
+
}
|
|
214
|
+
if ("$or" in node) {
|
|
215
|
+
const children = node.$or;
|
|
216
|
+
return `(${children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" OR ")})`;
|
|
217
|
+
}
|
|
218
|
+
if ("$not" in node) return `NOT (${queryNodeToSql(node.$not, resolveFieldRef)})`;
|
|
219
|
+
const comp = node;
|
|
220
|
+
const leftSql = resolveFieldRef(comp.left);
|
|
221
|
+
const sqlOp = queryOpToSql[comp.op] || "=";
|
|
222
|
+
if (comp.right && typeof comp.right === "object" && "field" in comp.right) return `${leftSql} ${sqlOp} ${resolveFieldRef(comp.right)}`;
|
|
223
|
+
if (comp.right === null || comp.right === undefined) return comp.op === "$ne" ? `${leftSql} IS NOT NULL` : `${leftSql} IS NULL`;
|
|
224
|
+
if (typeof comp.right === "string") return `${leftSql} ${sqlOp} '${comp.right.replace(/'/g, "''")}'`;
|
|
225
|
+
return `${leftSql} ${sqlOp} ${comp.right}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
//#endregion
|
|
28
229
|
//#region packages/db-sqlite/src/filter-builder.ts
|
|
29
230
|
const EMPTY_AND = {
|
|
30
231
|
sql: "1=1",
|
|
@@ -39,7 +240,7 @@ const EMPTY_OR = {
|
|
|
39
240
|
* into a parameterized SQL WHERE clause.
|
|
40
241
|
*/ const sqlVisitor = {
|
|
41
242
|
comparison(field, op, value) {
|
|
42
|
-
const col = `"${
|
|
243
|
+
const col = `"${esc(field)}"`;
|
|
43
244
|
const v = toSqliteParam(value);
|
|
44
245
|
switch (op) {
|
|
45
246
|
case "$eq": {
|
|
@@ -136,7 +337,7 @@ const EMPTY_OR = {
|
|
|
136
337
|
};
|
|
137
338
|
function buildWhere(filter) {
|
|
138
339
|
if (!filter || Object.keys(filter).length === 0) return EMPTY_AND;
|
|
139
|
-
return (0, __uniqu_core.walkFilter)(filter, sqlVisitor);
|
|
340
|
+
return (0, __uniqu_core.walkFilter)(filter, sqlVisitor) ?? EMPTY_AND;
|
|
140
341
|
}
|
|
141
342
|
/**
|
|
142
343
|
* Basic regex-to-LIKE conversion.
|
|
@@ -158,12 +359,6 @@ function buildWhere(filter) {
|
|
|
158
359
|
return `%${core}%`;
|
|
159
360
|
}
|
|
160
361
|
/**
|
|
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
362
|
* Converts a JS value to a SQLite-bindable parameter.
|
|
168
363
|
* SQLite cannot bind booleans — they must be 0/1.
|
|
169
364
|
*/ function toSqliteParam(value) {
|
|
@@ -171,111 +366,9 @@ function buildWhere(filter) {
|
|
|
171
366
|
return value;
|
|
172
367
|
}
|
|
173
368
|
|
|
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) {
|
|
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
|
-
return `CREATE TABLE IF NOT EXISTS "${esc$1(table)}" (${colDefs.join(", ")})`;
|
|
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
369
|
//#endregion
|
|
277
370
|
//#region packages/db-sqlite/src/sqlite-adapter.ts
|
|
278
|
-
function _define_property
|
|
371
|
+
function _define_property(obj, key, value) {
|
|
279
372
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
280
373
|
value,
|
|
281
374
|
enumerable: true,
|
|
@@ -286,6 +379,19 @@ else obj[key] = value;
|
|
|
286
379
|
return obj;
|
|
287
380
|
}
|
|
288
381
|
var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
382
|
+
async _beginTransaction() {
|
|
383
|
+
this._log("BEGIN");
|
|
384
|
+
this.driver.exec("BEGIN");
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
async _commitTransaction() {
|
|
388
|
+
this._log("COMMIT");
|
|
389
|
+
this.driver.exec("COMMIT");
|
|
390
|
+
}
|
|
391
|
+
async _rollbackTransaction() {
|
|
392
|
+
this._log("ROLLBACK");
|
|
393
|
+
this.driver.exec("ROLLBACK");
|
|
394
|
+
}
|
|
289
395
|
/** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
|
|
290
396
|
return super.resolveTableName(false);
|
|
291
397
|
}
|
|
@@ -294,27 +400,24 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
294
400
|
}
|
|
295
401
|
async insertOne(data) {
|
|
296
402
|
const { sql, params } = buildInsert(this.resolveTableName(), data);
|
|
403
|
+
this._log(sql, params);
|
|
297
404
|
const result = this.driver.run(sql, params);
|
|
298
405
|
return { insertedId: result.lastInsertRowid };
|
|
299
406
|
}
|
|
300
407
|
async insertMany(data) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
try {
|
|
408
|
+
return this.withTransaction(async () => {
|
|
409
|
+
const ids = [];
|
|
304
410
|
for (const row of data) {
|
|
305
411
|
const { sql, params } = buildInsert(this.resolveTableName(), row);
|
|
412
|
+
this._log(sql, params);
|
|
306
413
|
const result = this.driver.run(sql, params);
|
|
307
414
|
ids.push(result.lastInsertRowid);
|
|
308
415
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
return {
|
|
315
|
-
insertedCount: ids.length,
|
|
316
|
-
insertedIds: ids
|
|
317
|
-
};
|
|
416
|
+
return {
|
|
417
|
+
insertedCount: ids.length,
|
|
418
|
+
insertedIds: ids
|
|
419
|
+
};
|
|
420
|
+
});
|
|
318
421
|
}
|
|
319
422
|
async findOne(query) {
|
|
320
423
|
const where = buildWhere(query.filter);
|
|
@@ -323,17 +426,20 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
323
426
|
$limit: 1
|
|
324
427
|
};
|
|
325
428
|
const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
|
|
429
|
+
this._log(sql, params);
|
|
326
430
|
return this.driver.get(sql, params);
|
|
327
431
|
}
|
|
328
432
|
async findMany(query) {
|
|
329
433
|
const where = buildWhere(query.filter);
|
|
330
434
|
const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
|
|
435
|
+
this._log(sql, params);
|
|
331
436
|
return this.driver.all(sql, params);
|
|
332
437
|
}
|
|
333
438
|
async count(query) {
|
|
334
439
|
const where = buildWhere(query.filter);
|
|
335
440
|
const tableName = this.resolveTableName();
|
|
336
441
|
const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
|
|
442
|
+
this._log(sql, where.params);
|
|
337
443
|
const row = this.driver.get(sql, where.params);
|
|
338
444
|
return row?.cnt ?? 0;
|
|
339
445
|
}
|
|
@@ -347,7 +453,9 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
347
453
|
setParams.push(toSqliteValue(value));
|
|
348
454
|
}
|
|
349
455
|
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
350
|
-
const
|
|
456
|
+
const allParams = [...setParams, ...where.params];
|
|
457
|
+
this._log(sql, allParams);
|
|
458
|
+
const result = this.driver.run(sql, allParams);
|
|
351
459
|
return {
|
|
352
460
|
matchedCount: result.changes,
|
|
353
461
|
modifiedCount: result.changes
|
|
@@ -356,6 +464,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
356
464
|
async updateMany(filter, data) {
|
|
357
465
|
const where = buildWhere(filter);
|
|
358
466
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
467
|
+
this._log(sql, params);
|
|
359
468
|
const result = this.driver.run(sql, params);
|
|
360
469
|
return {
|
|
361
470
|
matchedCount: result.changes,
|
|
@@ -365,27 +474,22 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
365
474
|
async replaceOne(filter, data) {
|
|
366
475
|
const where = buildWhere(filter);
|
|
367
476
|
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
|
-
}
|
|
477
|
+
const limitedWhere = {
|
|
478
|
+
sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
|
|
479
|
+
params: where.params
|
|
480
|
+
};
|
|
481
|
+
const { sql, params } = buildUpdate(tableName, data, limitedWhere);
|
|
482
|
+
this._log(sql, params);
|
|
483
|
+
const result = this.driver.run(sql, params);
|
|
484
|
+
return {
|
|
485
|
+
matchedCount: result.changes,
|
|
486
|
+
modifiedCount: result.changes
|
|
487
|
+
};
|
|
385
488
|
}
|
|
386
489
|
async replaceMany(filter, data) {
|
|
387
490
|
const where = buildWhere(filter);
|
|
388
491
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
492
|
+
this._log(sql, params);
|
|
389
493
|
const result = this.driver.run(sql, params);
|
|
390
494
|
return {
|
|
391
495
|
matchedCount: result.changes,
|
|
@@ -396,19 +500,115 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
396
500
|
const where = buildWhere(filter);
|
|
397
501
|
const tableName = this.resolveTableName();
|
|
398
502
|
const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
503
|
+
this._log(sql, where.params);
|
|
399
504
|
const result = this.driver.run(sql, where.params);
|
|
400
505
|
return { deletedCount: result.changes };
|
|
401
506
|
}
|
|
402
507
|
async deleteMany(filter) {
|
|
403
508
|
const where = buildWhere(filter);
|
|
404
509
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
510
|
+
this._log(sql, params);
|
|
405
511
|
const result = this.driver.run(sql, params);
|
|
406
512
|
return { deletedCount: result.changes };
|
|
407
513
|
}
|
|
408
514
|
async ensureTable() {
|
|
409
|
-
|
|
515
|
+
if (this._table instanceof __atscript_utils_db.AtscriptDbView) return this.ensureView();
|
|
516
|
+
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
|
|
517
|
+
this._log(sql);
|
|
518
|
+
this.driver.exec(sql);
|
|
519
|
+
}
|
|
520
|
+
async ensureView() {
|
|
521
|
+
const view = this._table;
|
|
522
|
+
const sql = buildCreateView(this.resolveTableName(), view.viewPlan, view.getViewColumnMappings(), (ref) => view.resolveFieldRef(ref));
|
|
523
|
+
this._log(sql);
|
|
410
524
|
this.driver.exec(sql);
|
|
411
525
|
}
|
|
526
|
+
async getExistingColumns() {
|
|
527
|
+
return this.getExistingColumnsForTable(this.resolveTableName());
|
|
528
|
+
}
|
|
529
|
+
async syncColumns(diff) {
|
|
530
|
+
const tableName = this.resolveTableName();
|
|
531
|
+
const added = [];
|
|
532
|
+
const renamed = [];
|
|
533
|
+
for (const { field, oldName } of diff.renamed ?? []) {
|
|
534
|
+
const ddl = `ALTER TABLE "${esc(tableName)}" RENAME COLUMN "${esc(oldName)}" TO "${esc(field.physicalName)}"`;
|
|
535
|
+
this._log(ddl);
|
|
536
|
+
this.driver.exec(ddl);
|
|
537
|
+
renamed.push(field.physicalName);
|
|
538
|
+
}
|
|
539
|
+
for (const field of diff.added) {
|
|
540
|
+
const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
|
|
541
|
+
let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
|
|
542
|
+
if (!field.optional && !field.isPrimaryKey) ddl += ` NOT NULL DEFAULT ${defaultValueForType(field.designType)}`;
|
|
543
|
+
this._log(ddl);
|
|
544
|
+
this.driver.exec(ddl);
|
|
545
|
+
added.push(field.physicalName);
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
added,
|
|
549
|
+
renamed
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
async recreateTable() {
|
|
553
|
+
const tableName = this.resolveTableName();
|
|
554
|
+
const tempName = `${tableName}__tmp_${Date.now()}`;
|
|
555
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
|
|
556
|
+
this._log(createSql);
|
|
557
|
+
this.driver.exec(createSql);
|
|
558
|
+
const oldCols = (await this.getExistingColumns()).map((c) => c.name);
|
|
559
|
+
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
560
|
+
const oldColSet = new Set(oldCols);
|
|
561
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
562
|
+
if (commonCols.length > 0) {
|
|
563
|
+
const cols = commonCols.map((c) => `"${esc(c)}"`).join(", ");
|
|
564
|
+
const copySql = `INSERT INTO "${esc(tempName)}" (${cols}) SELECT ${cols} FROM "${esc(tableName)}"`;
|
|
565
|
+
this._log(copySql);
|
|
566
|
+
this.driver.exec(copySql);
|
|
567
|
+
}
|
|
568
|
+
this.driver.exec(`DROP TABLE "${esc(tableName)}"`);
|
|
569
|
+
this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
|
|
570
|
+
}
|
|
571
|
+
async dropTable() {
|
|
572
|
+
const tableName = this.resolveTableName();
|
|
573
|
+
const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
|
|
574
|
+
this._log(ddl);
|
|
575
|
+
this.driver.exec(ddl);
|
|
576
|
+
}
|
|
577
|
+
async dropColumns(columns) {
|
|
578
|
+
await this.withTransaction(async () => {
|
|
579
|
+
const tableName = this.resolveTableName();
|
|
580
|
+
for (const col of columns) {
|
|
581
|
+
const ddl = `ALTER TABLE "${esc(tableName)}" DROP COLUMN "${esc(col)}"`;
|
|
582
|
+
this._log(ddl);
|
|
583
|
+
this.driver.exec(ddl);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
async dropTableByName(tableName) {
|
|
588
|
+
const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
|
|
589
|
+
this._log(ddl);
|
|
590
|
+
this.driver.exec(ddl);
|
|
591
|
+
}
|
|
592
|
+
async dropViewByName(viewName) {
|
|
593
|
+
const ddl = `DROP VIEW IF EXISTS "${esc(viewName)}"`;
|
|
594
|
+
this._log(ddl);
|
|
595
|
+
this.driver.exec(ddl);
|
|
596
|
+
}
|
|
597
|
+
async renameTable(oldName) {
|
|
598
|
+
const newName = this.resolveTableName();
|
|
599
|
+
const ddl = `ALTER TABLE "${esc(oldName)}" RENAME TO "${esc(newName)}"`;
|
|
600
|
+
this._log(ddl);
|
|
601
|
+
this.driver.exec(ddl);
|
|
602
|
+
}
|
|
603
|
+
async getExistingColumnsForTable(tableName) {
|
|
604
|
+
const rows = this.driver.all(`PRAGMA table_info("${esc(tableName)}")`);
|
|
605
|
+
return rows.map((r) => ({
|
|
606
|
+
name: r.name,
|
|
607
|
+
type: r.type,
|
|
608
|
+
notnull: r.notnull === 1,
|
|
609
|
+
pk: r.pk > 0
|
|
610
|
+
}));
|
|
611
|
+
}
|
|
412
612
|
async syncIndexes() {
|
|
413
613
|
const tableName = this.resolveTableName();
|
|
414
614
|
await this.syncIndexesWithDiff({
|
|
@@ -416,74 +616,41 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
416
616
|
createIndex: async (index) => {
|
|
417
617
|
const unique = index.type === "unique" ? "UNIQUE " : "";
|
|
418
618
|
const cols = index.fields.map((f) => `"${esc(f.name)}" ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
|
|
419
|
-
|
|
619
|
+
const sql = `CREATE ${unique}INDEX IF NOT EXISTS "${esc(index.key)}" ON "${esc(tableName)}" (${cols})`;
|
|
620
|
+
this._log(sql);
|
|
621
|
+
this.driver.exec(sql);
|
|
420
622
|
},
|
|
421
623
|
dropIndex: async (name) => {
|
|
422
|
-
|
|
624
|
+
const sql = `DROP INDEX IF EXISTS "${esc(name)}"`;
|
|
625
|
+
this._log(sql);
|
|
626
|
+
this.driver.exec(sql);
|
|
423
627
|
},
|
|
424
628
|
shouldSkipType: (type) => type === "fulltext"
|
|
425
629
|
});
|
|
426
630
|
}
|
|
427
631
|
constructor(driver) {
|
|
428
|
-
super(), _define_property
|
|
632
|
+
super(), _define_property(this, "driver", void 0), this.driver = driver;
|
|
633
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
429
634
|
}
|
|
430
635
|
};
|
|
431
|
-
function
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
439
|
-
return value;
|
|
636
|
+
/** Returns a safe SQLite DEFAULT literal for a given design type. */ function defaultValueForType(designType) {
|
|
637
|
+
switch (designType) {
|
|
638
|
+
case "number":
|
|
639
|
+
case "integer": return "0";
|
|
640
|
+
case "boolean": return "0";
|
|
641
|
+
default: return "''";
|
|
642
|
+
}
|
|
440
643
|
}
|
|
441
644
|
|
|
442
645
|
//#endregion
|
|
443
|
-
//#region packages/db-sqlite/src/
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
enumerable: true,
|
|
448
|
-
configurable: true,
|
|
449
|
-
writable: true
|
|
450
|
-
});
|
|
451
|
-
else obj[key] = value;
|
|
452
|
-
return obj;
|
|
646
|
+
//#region packages/db-sqlite/src/index.ts
|
|
647
|
+
function createAdapter(connection, options) {
|
|
648
|
+
const driver = new BetterSqlite3Driver(connection, options);
|
|
649
|
+
return new __atscript_utils_db.DbSpace(() => new SqliteAdapter(driver));
|
|
453
650
|
}
|
|
454
|
-
var BetterSqlite3Driver = class {
|
|
455
|
-
run(sql, params) {
|
|
456
|
-
const stmt = this.db.prepare(sql);
|
|
457
|
-
const result = params ? stmt.run(...params) : stmt.run();
|
|
458
|
-
return {
|
|
459
|
-
changes: result.changes,
|
|
460
|
-
lastInsertRowid: result.lastInsertRowid
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
all(sql, params) {
|
|
464
|
-
const stmt = this.db.prepare(sql);
|
|
465
|
-
return params ? stmt.all(...params) : stmt.all();
|
|
466
|
-
}
|
|
467
|
-
get(sql, params) {
|
|
468
|
-
const stmt = this.db.prepare(sql);
|
|
469
|
-
return (params ? stmt.get(...params) : stmt.get()) ?? null;
|
|
470
|
-
}
|
|
471
|
-
exec(sql) {
|
|
472
|
-
this.db.exec(sql);
|
|
473
|
-
}
|
|
474
|
-
close() {
|
|
475
|
-
this.db.close();
|
|
476
|
-
}
|
|
477
|
-
constructor(pathOrDb, options) {
|
|
478
|
-
_define_property(this, "db", void 0);
|
|
479
|
-
if (typeof pathOrDb === "string") {
|
|
480
|
-
const Database = require("better-sqlite3");
|
|
481
|
-
this.db = new Database(pathOrDb, options);
|
|
482
|
-
} else this.db = pathOrDb;
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
651
|
|
|
486
652
|
//#endregion
|
|
487
653
|
exports.BetterSqlite3Driver = BetterSqlite3Driver
|
|
488
654
|
exports.SqliteAdapter = SqliteAdapter
|
|
489
|
-
exports.buildWhere = buildWhere
|
|
655
|
+
exports.buildWhere = buildWhere
|
|
656
|
+
exports.createAdapter = createAdapter
|