@atscript/db-sqlite 0.1.34 → 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 +362 -213
- package/dist/index.d.ts +17 -2
- package/dist/index.mjs +362 -214
- 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,128 +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, 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
369
|
//#endregion
|
|
294
370
|
//#region packages/db-sqlite/src/sqlite-adapter.ts
|
|
295
|
-
function _define_property
|
|
371
|
+
function _define_property(obj, key, value) {
|
|
296
372
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
297
373
|
value,
|
|
298
374
|
enumerable: true,
|
|
@@ -303,6 +379,19 @@ else obj[key] = value;
|
|
|
303
379
|
return obj;
|
|
304
380
|
}
|
|
305
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
|
+
}
|
|
306
395
|
/** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
|
|
307
396
|
return super.resolveTableName(false);
|
|
308
397
|
}
|
|
@@ -311,27 +400,24 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
311
400
|
}
|
|
312
401
|
async insertOne(data) {
|
|
313
402
|
const { sql, params } = buildInsert(this.resolveTableName(), data);
|
|
403
|
+
this._log(sql, params);
|
|
314
404
|
const result = this.driver.run(sql, params);
|
|
315
405
|
return { insertedId: result.lastInsertRowid };
|
|
316
406
|
}
|
|
317
407
|
async insertMany(data) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
try {
|
|
408
|
+
return this.withTransaction(async () => {
|
|
409
|
+
const ids = [];
|
|
321
410
|
for (const row of data) {
|
|
322
411
|
const { sql, params } = buildInsert(this.resolveTableName(), row);
|
|
412
|
+
this._log(sql, params);
|
|
323
413
|
const result = this.driver.run(sql, params);
|
|
324
414
|
ids.push(result.lastInsertRowid);
|
|
325
415
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
insertedCount: ids.length,
|
|
333
|
-
insertedIds: ids
|
|
334
|
-
};
|
|
416
|
+
return {
|
|
417
|
+
insertedCount: ids.length,
|
|
418
|
+
insertedIds: ids
|
|
419
|
+
};
|
|
420
|
+
});
|
|
335
421
|
}
|
|
336
422
|
async findOne(query) {
|
|
337
423
|
const where = buildWhere(query.filter);
|
|
@@ -340,17 +426,20 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
340
426
|
$limit: 1
|
|
341
427
|
};
|
|
342
428
|
const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
|
|
429
|
+
this._log(sql, params);
|
|
343
430
|
return this.driver.get(sql, params);
|
|
344
431
|
}
|
|
345
432
|
async findMany(query) {
|
|
346
433
|
const where = buildWhere(query.filter);
|
|
347
434
|
const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
|
|
435
|
+
this._log(sql, params);
|
|
348
436
|
return this.driver.all(sql, params);
|
|
349
437
|
}
|
|
350
438
|
async count(query) {
|
|
351
439
|
const where = buildWhere(query.filter);
|
|
352
440
|
const tableName = this.resolveTableName();
|
|
353
441
|
const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
|
|
442
|
+
this._log(sql, where.params);
|
|
354
443
|
const row = this.driver.get(sql, where.params);
|
|
355
444
|
return row?.cnt ?? 0;
|
|
356
445
|
}
|
|
@@ -364,7 +453,9 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
364
453
|
setParams.push(toSqliteValue(value));
|
|
365
454
|
}
|
|
366
455
|
const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
|
|
367
|
-
const
|
|
456
|
+
const allParams = [...setParams, ...where.params];
|
|
457
|
+
this._log(sql, allParams);
|
|
458
|
+
const result = this.driver.run(sql, allParams);
|
|
368
459
|
return {
|
|
369
460
|
matchedCount: result.changes,
|
|
370
461
|
modifiedCount: result.changes
|
|
@@ -373,6 +464,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
373
464
|
async updateMany(filter, data) {
|
|
374
465
|
const where = buildWhere(filter);
|
|
375
466
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
467
|
+
this._log(sql, params);
|
|
376
468
|
const result = this.driver.run(sql, params);
|
|
377
469
|
return {
|
|
378
470
|
matchedCount: result.changes,
|
|
@@ -382,27 +474,22 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
382
474
|
async replaceOne(filter, data) {
|
|
383
475
|
const where = buildWhere(filter);
|
|
384
476
|
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
|
-
}
|
|
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
|
+
};
|
|
402
488
|
}
|
|
403
489
|
async replaceMany(filter, data) {
|
|
404
490
|
const where = buildWhere(filter);
|
|
405
491
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
492
|
+
this._log(sql, params);
|
|
406
493
|
const result = this.driver.run(sql, params);
|
|
407
494
|
return {
|
|
408
495
|
matchedCount: result.changes,
|
|
@@ -413,19 +500,115 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
413
500
|
const where = buildWhere(filter);
|
|
414
501
|
const tableName = this.resolveTableName();
|
|
415
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);
|
|
416
504
|
const result = this.driver.run(sql, where.params);
|
|
417
505
|
return { deletedCount: result.changes };
|
|
418
506
|
}
|
|
419
507
|
async deleteMany(filter) {
|
|
420
508
|
const where = buildWhere(filter);
|
|
421
509
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
510
|
+
this._log(sql, params);
|
|
422
511
|
const result = this.driver.run(sql, params);
|
|
423
512
|
return { deletedCount: result.changes };
|
|
424
513
|
}
|
|
425
514
|
async ensureTable() {
|
|
515
|
+
if (this._table instanceof __atscript_utils_db.AtscriptDbView) return this.ensureView();
|
|
426
516
|
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
|
|
517
|
+
this._log(sql);
|
|
427
518
|
this.driver.exec(sql);
|
|
428
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);
|
|
524
|
+
this.driver.exec(sql);
|
|
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
|
+
}
|
|
429
612
|
async syncIndexes() {
|
|
430
613
|
const tableName = this.resolveTableName();
|
|
431
614
|
await this.syncIndexesWithDiff({
|
|
@@ -433,75 +616,41 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
|
|
|
433
616
|
createIndex: async (index) => {
|
|
434
617
|
const unique = index.type === "unique" ? "UNIQUE " : "";
|
|
435
618
|
const cols = index.fields.map((f) => `"${esc(f.name)}" ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
|
|
436
|
-
|
|
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);
|
|
437
622
|
},
|
|
438
623
|
dropIndex: async (name) => {
|
|
439
|
-
|
|
624
|
+
const sql = `DROP INDEX IF EXISTS "${esc(name)}"`;
|
|
625
|
+
this._log(sql);
|
|
626
|
+
this.driver.exec(sql);
|
|
440
627
|
},
|
|
441
628
|
shouldSkipType: (type) => type === "fulltext"
|
|
442
629
|
});
|
|
443
630
|
}
|
|
444
631
|
constructor(driver) {
|
|
445
|
-
super(), _define_property
|
|
446
|
-
driver.exec("PRAGMA foreign_keys = ON");
|
|
632
|
+
super(), _define_property(this, "driver", void 0), this.driver = driver;
|
|
633
|
+
this.driver.exec("PRAGMA foreign_keys = ON");
|
|
447
634
|
}
|
|
448
635
|
};
|
|
449
|
-
function
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
457
|
-
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
|
+
}
|
|
458
643
|
}
|
|
459
644
|
|
|
460
645
|
//#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;
|
|
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));
|
|
471
650
|
}
|
|
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
651
|
|
|
504
652
|
//#endregion
|
|
505
653
|
exports.BetterSqlite3Driver = BetterSqlite3Driver
|
|
506
654
|
exports.SqliteAdapter = SqliteAdapter
|
|
507
|
-
exports.buildWhere = buildWhere
|
|
655
|
+
exports.buildWhere = buildWhere
|
|
656
|
+
exports.createAdapter = createAdapter
|