@primate/sqlite 0.6.0 → 0.8.0
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/lib/private/ColumnTypes.d.ts +2 -3
- package/lib/private/SQLite.d.ts +42 -0
- package/lib/private/SQLite.js +567 -0
- package/lib/private/typemap.d.ts +1 -1
- package/lib/private/typemap.js +8 -10
- package/lib/public/index.d.ts +2 -2
- package/lib/public/index.js +2 -2
- package/package.json +22 -20
- package/lib/private/Database.d.ts +0 -42
- package/lib/private/Database.js +0 -104
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type PrimitiveParam from "@rcompat/sqlite
|
|
2
|
-
import type TypedArray from "@rcompat/type
|
|
1
|
+
import type { PrimitiveParam } from "@rcompat/sqlite";
|
|
2
|
+
import type { TypedArray } from "@rcompat/type";
|
|
3
3
|
type Validate<T extends {
|
|
4
4
|
[K in keyof T]: PrimitiveParam;
|
|
5
5
|
}> = T;
|
|
6
6
|
type ColumnTypes = Validate<{
|
|
7
7
|
BLOB: TypedArray;
|
|
8
8
|
INTEGER: bigint | number;
|
|
9
|
-
"INTEGER PRIMARY KEY": number;
|
|
10
9
|
NULL: null;
|
|
11
10
|
REAL: number;
|
|
12
11
|
TEXT: string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { As, DataDict, DB, Schema, Sort, With } from "@primate/core/db";
|
|
2
|
+
import type { Dict } from "@rcompat/type";
|
|
3
|
+
declare const schema: import("pema").ObjectType<import("pema").NormalizeSchemaObject<{
|
|
4
|
+
readonly database: import("pema").DefaultType<import("pema").StringType, ":memory:">;
|
|
5
|
+
}>, {
|
|
6
|
+
database: string;
|
|
7
|
+
}>;
|
|
8
|
+
export default class SQLite implements DB {
|
|
9
|
+
#private;
|
|
10
|
+
static config: typeof schema.input;
|
|
11
|
+
constructor(config?: typeof schema.input, options?: {
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
});
|
|
14
|
+
get explain(): Dict<{
|
|
15
|
+
query: string;
|
|
16
|
+
plans: string[];
|
|
17
|
+
}>;
|
|
18
|
+
get schema(): Schema;
|
|
19
|
+
create<O extends Dict>(as: As, record: Dict): Promise<O>;
|
|
20
|
+
read(as: As, args: {
|
|
21
|
+
count: true;
|
|
22
|
+
where: DataDict;
|
|
23
|
+
with?: never;
|
|
24
|
+
}): Promise<number>;
|
|
25
|
+
read(as: As, args: {
|
|
26
|
+
where: DataDict;
|
|
27
|
+
fields?: string[];
|
|
28
|
+
limit?: number;
|
|
29
|
+
sort?: Sort;
|
|
30
|
+
with?: With;
|
|
31
|
+
}): Promise<Dict[]>;
|
|
32
|
+
update(as: As, args: {
|
|
33
|
+
set: DataDict;
|
|
34
|
+
where: DataDict;
|
|
35
|
+
}): Promise<number>;
|
|
36
|
+
delete(as: As, args: {
|
|
37
|
+
where: DataDict;
|
|
38
|
+
}): Promise<number>;
|
|
39
|
+
close(): void;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=SQLite.d.ts.map
|
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import typemap from "#typemap";
|
|
2
|
+
import base from "@primate/core/db";
|
|
3
|
+
import E from "@primate/core/db/errors";
|
|
4
|
+
import sql from "@primate/core/db/sql";
|
|
5
|
+
import assert from "@rcompat/assert";
|
|
6
|
+
import is from "@rcompat/is";
|
|
7
|
+
import Client from "@rcompat/sqlite";
|
|
8
|
+
import p from "pema";
|
|
9
|
+
const BIND_BY = "$";
|
|
10
|
+
const Q = sql.Q;
|
|
11
|
+
const schema = p({ database: p.string.default(":memory:") });
|
|
12
|
+
function like_to_glob(pattern) {
|
|
13
|
+
return pattern
|
|
14
|
+
.replace(/\\%/g, "<<PERCENT>>")
|
|
15
|
+
.replace(/\\_/g, "<<UNDERSCORE>>")
|
|
16
|
+
.replace(/\[/g, "<<LBRACKET>>")
|
|
17
|
+
.replace(/\]/g, "<<RBRACKET>>")
|
|
18
|
+
.replace(/\*/g, "[*]")
|
|
19
|
+
.replace(/\?/g, "[?]")
|
|
20
|
+
.replace(/%/g, "*")
|
|
21
|
+
.replace(/_/g, "?")
|
|
22
|
+
.replace(/<<PERCENT>>/g, "%")
|
|
23
|
+
.replace(/<<UNDERSCORE>>/g, "_")
|
|
24
|
+
.replace(/<<LBRACKET>>/g, "[[]")
|
|
25
|
+
.replace(/<<RBRACKET>>/g, "[]]");
|
|
26
|
+
}
|
|
27
|
+
function u_cmp(field, a, b, op) {
|
|
28
|
+
const la = `LENGTH(${a})`;
|
|
29
|
+
const lb = `LENGTH(${b})`;
|
|
30
|
+
switch (op) {
|
|
31
|
+
case ">":
|
|
32
|
+
return `${la}>${lb} OR (${la}=${lb} AND ${a}>${b})`;
|
|
33
|
+
case ">=":
|
|
34
|
+
return `${a}=${b} OR ${la}>${lb} OR (${la}=${lb} AND ${a}>${b})`;
|
|
35
|
+
case "<":
|
|
36
|
+
return `${la}<${lb} OR (${la}=${lb} AND ${a}<${b})`;
|
|
37
|
+
case "<=":
|
|
38
|
+
return `${a}=${b} OR ${la}<${lb} OR (${la}=${lb} AND ${a}<${b})`;
|
|
39
|
+
default:
|
|
40
|
+
throw E.operator_unknown(field, op);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function i_cmp(field, binding, op) {
|
|
44
|
+
const a = `CAST(${field} AS TEXT)`;
|
|
45
|
+
const b = `CAST(${binding} AS TEXT)`;
|
|
46
|
+
const a_neg = `(${a} LIKE '-%')`;
|
|
47
|
+
const b_neg = `(${b} LIKE '-%')`;
|
|
48
|
+
const a_abs = `CASE WHEN ${a_neg} THEN SUBSTR(${a}, 2) ELSE ${a} END`;
|
|
49
|
+
const b_abs = `CASE WHEN ${b_neg} THEN SUBSTR(${b}, 2) ELSE ${b} END`;
|
|
50
|
+
const is_gt = op === ">" || op === ">=";
|
|
51
|
+
const strict = op === ">" || op === "<";
|
|
52
|
+
const pos_neg_case = is_gt
|
|
53
|
+
? `(${a_neg} = 0 AND ${b_neg} = 1)`
|
|
54
|
+
: `(${a_neg} = 1 AND ${b_neg} = 0)`;
|
|
55
|
+
const abs_op_positive = strict ? op : (is_gt ? ">" : "<");
|
|
56
|
+
const abs_op_negative = is_gt ? (strict ? "<" : "<=") : (strict ? ">" : ">=");
|
|
57
|
+
const same_sign = `(${a_neg} = ${b_neg} AND (` +
|
|
58
|
+
`(${a_neg} = 0 AND (${u_cmp(field, a, b, abs_op_positive)})) OR ` +
|
|
59
|
+
`(${a_neg} = 1 AND (${u_cmp(field, a_abs, b_abs, abs_op_negative)}))` +
|
|
60
|
+
"))";
|
|
61
|
+
const core = `${pos_neg_case} OR ${same_sign}`;
|
|
62
|
+
return strict ? `(${core})` : `(${a} = ${b} OR (${core}))`;
|
|
63
|
+
}
|
|
64
|
+
function cmp_expr(field, key, op, datatype) {
|
|
65
|
+
const rhs = `${BIND_BY}${key}`;
|
|
66
|
+
const sql_op = sql.OPS[op];
|
|
67
|
+
assert.defined(sql_op, E.operator_unknown(field, op));
|
|
68
|
+
if (base.BIGINT_STRING_TYPES.includes(datatype)) {
|
|
69
|
+
const cmp = base.UNSIGNED_BIGINT_TYPES.includes(datatype)
|
|
70
|
+
? u_cmp(field, `CAST(${field} AS TEXT)`, `CAST(${rhs} AS TEXT)`, sql_op)
|
|
71
|
+
: i_cmp(field, rhs, sql_op);
|
|
72
|
+
return `(${cmp})`;
|
|
73
|
+
}
|
|
74
|
+
return `${field} ${sql_op} ${rhs}`;
|
|
75
|
+
}
|
|
76
|
+
function like(field, key, case_insensitive) {
|
|
77
|
+
const rhs = `${BIND_BY}${key}`;
|
|
78
|
+
if (case_insensitive)
|
|
79
|
+
return `LOWER(${field}) LIKE LOWER(${rhs})`;
|
|
80
|
+
return `CAST(${field} AS TEXT) GLOB ${rhs}`;
|
|
81
|
+
}
|
|
82
|
+
function get_column(key) {
|
|
83
|
+
return typemap[key].column;
|
|
84
|
+
}
|
|
85
|
+
function bind_one(key, value) {
|
|
86
|
+
return value === null ? null : typemap[key].bind(value);
|
|
87
|
+
}
|
|
88
|
+
function unbind_one(key, value) {
|
|
89
|
+
return typemap[key].unbind(value);
|
|
90
|
+
}
|
|
91
|
+
async function bind(types, fields) {
|
|
92
|
+
const out = {};
|
|
93
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
94
|
+
const raw = key.startsWith("s_") ? key.slice(2) : key;
|
|
95
|
+
const type = types[raw.split("__")[0]];
|
|
96
|
+
if (is.dict(value) && type !== "json")
|
|
97
|
+
throw E.operator_scalar(key);
|
|
98
|
+
out[`${BIND_BY}${key}`] = await bind_one(type, value);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function unbind(types, row) {
|
|
103
|
+
return sql.unbind(types, row, unbind_one);
|
|
104
|
+
}
|
|
105
|
+
function columns_to_types(type) {
|
|
106
|
+
const t = type.toUpperCase();
|
|
107
|
+
if (t === "TEXT")
|
|
108
|
+
return [
|
|
109
|
+
"string", "url", "time", "datetime", "json",
|
|
110
|
+
"u64", "u128", "i64", "i128",
|
|
111
|
+
...base.UUID_TYPES,
|
|
112
|
+
];
|
|
113
|
+
if (t === "INTEGER")
|
|
114
|
+
return [
|
|
115
|
+
"boolean", "i8", "u8", "i16", "u16", "i32", "u32",
|
|
116
|
+
];
|
|
117
|
+
if (t === "REAL")
|
|
118
|
+
return ["f32", "f64"];
|
|
119
|
+
if (t === "BLOB")
|
|
120
|
+
return ["blob"];
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
export default class SQLite {
|
|
124
|
+
#factory;
|
|
125
|
+
#client;
|
|
126
|
+
#debug = false;
|
|
127
|
+
#explain = {};
|
|
128
|
+
static config;
|
|
129
|
+
constructor(config, options) {
|
|
130
|
+
const parsed = schema.parse(config);
|
|
131
|
+
this.#factory = () => new Client(parsed.database, { safeIntegers: true });
|
|
132
|
+
this.#debug = options?.debug ?? false;
|
|
133
|
+
}
|
|
134
|
+
get #db() {
|
|
135
|
+
return this.#client ??= this.#factory();
|
|
136
|
+
}
|
|
137
|
+
#sql(query) {
|
|
138
|
+
return this.#db.prepare(query);
|
|
139
|
+
}
|
|
140
|
+
#query_all(as, query, binds) {
|
|
141
|
+
this.#capture(as.table, query, binds);
|
|
142
|
+
const rows = this.#sql(query).all(binds);
|
|
143
|
+
return rows.map(r => unbind(as.types, r));
|
|
144
|
+
}
|
|
145
|
+
#query_run(query, binds) {
|
|
146
|
+
return Number(this.#sql(query).run(binds).changes);
|
|
147
|
+
}
|
|
148
|
+
#capture(table, query, binds) {
|
|
149
|
+
if (!this.#debug)
|
|
150
|
+
return;
|
|
151
|
+
const rows = this.#sql(`EXPLAIN QUERY PLAN ${query}`).all(binds);
|
|
152
|
+
this.#explain[table] = { query, plans: rows.map(r => r.detail) };
|
|
153
|
+
}
|
|
154
|
+
get explain() {
|
|
155
|
+
return this.#explain;
|
|
156
|
+
}
|
|
157
|
+
get schema() {
|
|
158
|
+
return {
|
|
159
|
+
create: (table, pk, types) => {
|
|
160
|
+
const columns = [];
|
|
161
|
+
for (const [key, value] of Object.entries(types)) {
|
|
162
|
+
const column_type = get_column(value);
|
|
163
|
+
const column = `${sql.quote(key)} ${column_type}`;
|
|
164
|
+
const is_int = column_type === "INTEGER";
|
|
165
|
+
if (key === pk.name) {
|
|
166
|
+
const generate = pk.generate && is_int ? " AUTOINCREMENT" : "";
|
|
167
|
+
columns.push(`${column} PRIMARY KEY${generate}`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
columns.push(column);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this.#sql(Q `CREATE TABLE IF NOT EXISTS ${table} (${columns})`).run();
|
|
174
|
+
},
|
|
175
|
+
delete: (table) => {
|
|
176
|
+
this.#sql(Q `DROP TABLE IF EXISTS ${table}`).run();
|
|
177
|
+
},
|
|
178
|
+
introspect: (table) => {
|
|
179
|
+
const rows = this.#sql(`PRAGMA table_info(${sql.quote(table)})`)
|
|
180
|
+
.all();
|
|
181
|
+
if (rows.length === 0)
|
|
182
|
+
return null;
|
|
183
|
+
const result = {};
|
|
184
|
+
for (const row of rows) {
|
|
185
|
+
const candidates = columns_to_types(row.type);
|
|
186
|
+
if (candidates.length > 0)
|
|
187
|
+
result[row.name] = candidates;
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
},
|
|
191
|
+
alter: (table, diff) => {
|
|
192
|
+
const existing = this.schema.introspect(table);
|
|
193
|
+
if (existing === null)
|
|
194
|
+
throw E.table_not_found(table);
|
|
195
|
+
for (const [from, to] of diff.rename) {
|
|
196
|
+
this.#sql(Q `ALTER TABLE ${table}
|
|
197
|
+
RENAME COLUMN ${[sql.quote(from)]} TO ${[sql.quote(to)]}`).run();
|
|
198
|
+
}
|
|
199
|
+
for (const field of diff.drop) {
|
|
200
|
+
this.#sql(Q `ALTER TABLE ${table}
|
|
201
|
+
DROP COLUMN ${[sql.quote(field)]}`).run();
|
|
202
|
+
}
|
|
203
|
+
for (const [field, type] of Object.entries(diff.add)) {
|
|
204
|
+
this.#sql(Q `ALTER TABLE ${table}
|
|
205
|
+
ADD COLUMN ${[`${sql.quote(field)} ${get_column(type)}`]}`).run();
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
#where(types, where, alias) {
|
|
211
|
+
const fields = Object.keys(where);
|
|
212
|
+
if (fields.length === 0)
|
|
213
|
+
return "";
|
|
214
|
+
const parts = [];
|
|
215
|
+
for (const field of fields) {
|
|
216
|
+
const value = where[field];
|
|
217
|
+
const datatype = types[field];
|
|
218
|
+
const q = sql.quote(field);
|
|
219
|
+
const quoted = alias ? `${alias}.${q}` : q;
|
|
220
|
+
if (value === null) {
|
|
221
|
+
parts.push(`${quoted} IS NULL`);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (is.dict(value)) {
|
|
225
|
+
const ops = Object.entries(value);
|
|
226
|
+
if (ops.length === 0)
|
|
227
|
+
throw E.operator_empty(field);
|
|
228
|
+
for (const [op] of ops) {
|
|
229
|
+
const bound_key = sql.bindKey(field, op);
|
|
230
|
+
switch (op) {
|
|
231
|
+
case "$like":
|
|
232
|
+
parts.push(like(quoted, bound_key, false));
|
|
233
|
+
break;
|
|
234
|
+
case "$ilike":
|
|
235
|
+
parts.push(like(quoted, bound_key, true));
|
|
236
|
+
break;
|
|
237
|
+
case "$ne":
|
|
238
|
+
parts.push(`${quoted} != ${BIND_BY}${bound_key}`);
|
|
239
|
+
break;
|
|
240
|
+
case "$gt":
|
|
241
|
+
case "$gte":
|
|
242
|
+
case "$lt":
|
|
243
|
+
case "$lte":
|
|
244
|
+
case "$after":
|
|
245
|
+
case "$before":
|
|
246
|
+
parts.push(cmp_expr(quoted, bound_key, op, datatype));
|
|
247
|
+
break;
|
|
248
|
+
default:
|
|
249
|
+
throw E.operator_unknown(field, op);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
parts.push(`${quoted} = ${BIND_BY}${field}`);
|
|
255
|
+
}
|
|
256
|
+
return `WHERE ${parts.join(" AND ")}`;
|
|
257
|
+
}
|
|
258
|
+
async #bind_where(types, where) {
|
|
259
|
+
const filtered = {};
|
|
260
|
+
for (const [field, value] of Object.entries(where)) {
|
|
261
|
+
if (value === null)
|
|
262
|
+
continue;
|
|
263
|
+
if (is.dict(value)) {
|
|
264
|
+
const ops = Object.entries(value);
|
|
265
|
+
for (const [op, op_value] of ops) {
|
|
266
|
+
const bound_key = sql.bindKey(field, op);
|
|
267
|
+
switch (op) {
|
|
268
|
+
case "$like":
|
|
269
|
+
filtered[bound_key] = like_to_glob(op_value);
|
|
270
|
+
break;
|
|
271
|
+
case "$ilike":
|
|
272
|
+
case "$gte":
|
|
273
|
+
case "$gt":
|
|
274
|
+
case "$after":
|
|
275
|
+
case "$lte":
|
|
276
|
+
case "$lt":
|
|
277
|
+
case "$before":
|
|
278
|
+
case "$ne":
|
|
279
|
+
filtered[bound_key] = op_value;
|
|
280
|
+
break;
|
|
281
|
+
default:
|
|
282
|
+
throw E.operator_unknown(field, op);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
filtered[field] = value;
|
|
288
|
+
}
|
|
289
|
+
return bind(types, filtered);
|
|
290
|
+
}
|
|
291
|
+
#set(set) {
|
|
292
|
+
const columns = Object.keys(set);
|
|
293
|
+
if (columns.length === 0)
|
|
294
|
+
throw E.field_required("set");
|
|
295
|
+
return Q `SET ${columns.map(c => `${sql.quote(c)}=${BIND_BY}s_${c}`)}`;
|
|
296
|
+
}
|
|
297
|
+
async #bind_set(types, set) {
|
|
298
|
+
const columns = Object.keys(set);
|
|
299
|
+
if (columns.length === 0)
|
|
300
|
+
throw E.field_required("set");
|
|
301
|
+
const raw = Object.fromEntries(columns.map(c => [`s_${c}`, set[c]]));
|
|
302
|
+
return await bind(types, raw);
|
|
303
|
+
}
|
|
304
|
+
#generate_pk(as) {
|
|
305
|
+
const pk = as.pk;
|
|
306
|
+
const type = as.types[pk];
|
|
307
|
+
if (base.is_uuid_type(type))
|
|
308
|
+
return base.generate_uuid(type);
|
|
309
|
+
if (base.BIGINT_STRING_TYPES.includes(type)) {
|
|
310
|
+
const query = Q `SELECT ${pk} AS v
|
|
311
|
+
FROM ${as.table} ORDER BY LENGTH(${pk}) DESC, ${pk} DESC LIMIT 1`;
|
|
312
|
+
const rows = this.#sql(query).all();
|
|
313
|
+
return rows[0]?.v ? BigInt(rows[0].v) + 1n : 1n;
|
|
314
|
+
}
|
|
315
|
+
throw "unreachable";
|
|
316
|
+
}
|
|
317
|
+
#create(record) {
|
|
318
|
+
const fields = Object.keys(record);
|
|
319
|
+
return [
|
|
320
|
+
fields.map(sql.quote),
|
|
321
|
+
fields.map(field => `${BIND_BY}${field}`),
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
async create(as, record) {
|
|
325
|
+
assert.dict(record);
|
|
326
|
+
const pk = as.pk;
|
|
327
|
+
const table = as.table;
|
|
328
|
+
// PK provided or none defined, simple insert
|
|
329
|
+
if (pk === null || pk in record) {
|
|
330
|
+
const [keys, values] = this.#create(record);
|
|
331
|
+
const query = keys.length > 0
|
|
332
|
+
? Q `INSERT INTO ${as.table} (${keys}) VALUES (${values})`
|
|
333
|
+
: Q `INSERT INTO ${as.table} DEFAULT VALUES`;
|
|
334
|
+
this.#sql(query).run(await bind(as.types, record));
|
|
335
|
+
return record;
|
|
336
|
+
}
|
|
337
|
+
if (as.generate_pk === false)
|
|
338
|
+
throw E.pk_required(pk);
|
|
339
|
+
const type = as.types[pk];
|
|
340
|
+
// integer types, use RETURNING
|
|
341
|
+
if (!base.BIGINT_STRING_TYPES.includes(type) && !base.is_uuid_type(type)) {
|
|
342
|
+
const [keys, values] = this.#create(record);
|
|
343
|
+
const query = keys.length > 0
|
|
344
|
+
? Q `INSERT INTO ${table} (${keys}) VALUES (${values}) RETURNING ${pk}`
|
|
345
|
+
: Q `INSERT INTO ${table} DEFAULT VALUES RETURNING ${pk}`;
|
|
346
|
+
const rows = this.#sql(query).all(await bind(as.types, record));
|
|
347
|
+
const pk_value = unbind_one(type, rows[0][pk]);
|
|
348
|
+
return { ...record, [pk]: pk_value };
|
|
349
|
+
}
|
|
350
|
+
// string or bigint, generate manually
|
|
351
|
+
const pk_value = this.#generate_pk(as);
|
|
352
|
+
const to_insert = { ...record, [pk]: pk_value };
|
|
353
|
+
const [keys, values] = this.#create(to_insert);
|
|
354
|
+
const query = Q `INSERT INTO ${table} (${keys}) VALUES (${values})`;
|
|
355
|
+
this.#sql(query).run(await bind(as.types, to_insert));
|
|
356
|
+
return to_insert;
|
|
357
|
+
}
|
|
358
|
+
async read(as, args) {
|
|
359
|
+
assert.dict(args.where);
|
|
360
|
+
this.#explain = {};
|
|
361
|
+
if (args.count === true)
|
|
362
|
+
return this.#count(as, args.where);
|
|
363
|
+
if (base.withed(args)) {
|
|
364
|
+
return sql.joinable(as, args.with)
|
|
365
|
+
? this.#read_joined(as, args)
|
|
366
|
+
: this.#read_phased(as, args);
|
|
367
|
+
}
|
|
368
|
+
return this.#read_base(as, args);
|
|
369
|
+
}
|
|
370
|
+
async #count(as, where) {
|
|
371
|
+
const WHERE = this.#where(as.types, where);
|
|
372
|
+
const binds = await this.#bind_where(as.types, where);
|
|
373
|
+
const table = as.table;
|
|
374
|
+
const query = `SELECT COUNT(*) AS n FROM ${sql.quote(table)} ${WHERE}`;
|
|
375
|
+
this.#capture(table, query, binds);
|
|
376
|
+
const rows = this.#sql(query).all(binds);
|
|
377
|
+
const length = rows.length;
|
|
378
|
+
assert.true(length === 1, E.record_number_invalid(length, "count"));
|
|
379
|
+
const n = rows[0].n;
|
|
380
|
+
assert.bigint(n, E.return_invalid(n, "bigint", "count"));
|
|
381
|
+
if (n > BigInt(Number.MAX_SAFE_INTEGER))
|
|
382
|
+
throw E.count_overflow(table, n);
|
|
383
|
+
return Number(n);
|
|
384
|
+
}
|
|
385
|
+
async #read_base(as, args) {
|
|
386
|
+
const query = this.#base_query(as, args);
|
|
387
|
+
const binds = await this.#bind_where(as.types, args.where);
|
|
388
|
+
return this.#query_all(as, query, binds);
|
|
389
|
+
}
|
|
390
|
+
async #read_phased(as, args) {
|
|
391
|
+
const fields = base.expand(as, args.fields, args.with);
|
|
392
|
+
const rows = await this.#read_base(as, { ...args, fields });
|
|
393
|
+
const out = rows.map(row => base.project(row, args.fields));
|
|
394
|
+
for (const [table, relation] of Object.entries(args.with)) {
|
|
395
|
+
await this.#attach_relation(as, { rows, out, table, relation });
|
|
396
|
+
}
|
|
397
|
+
return out;
|
|
398
|
+
}
|
|
399
|
+
async #attach_relation(as, args) {
|
|
400
|
+
const relation = args.relation;
|
|
401
|
+
const reverse = assert.maybe.boolean(relation.reverse) ?? false;
|
|
402
|
+
const by = reverse ? relation.as.pk : relation.fk;
|
|
403
|
+
if (by === null)
|
|
404
|
+
throw E.relation_requires_pk("target");
|
|
405
|
+
const parent_by = reverse ? relation.fk : as.pk;
|
|
406
|
+
if (parent_by === null)
|
|
407
|
+
throw E.relation_requires_pk("parent");
|
|
408
|
+
const join_values = [...new Set(args.rows.map(r => r[parent_by]).filter(v => v != null))];
|
|
409
|
+
const is_many = relation.kind === "many";
|
|
410
|
+
const many = is_many ? [] : null;
|
|
411
|
+
if (join_values.length === 0) {
|
|
412
|
+
for (const row of args.out)
|
|
413
|
+
row[args.table] = many;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const related = await this.#load_related({ by, join_values, ...relation });
|
|
417
|
+
const grouped = new Map();
|
|
418
|
+
for (const row of related) {
|
|
419
|
+
const key = row[by];
|
|
420
|
+
grouped.set(key, grouped.get(key)?.concat(row) ?? [row]);
|
|
421
|
+
}
|
|
422
|
+
for (let i = 0; i < args.out.length; i++) {
|
|
423
|
+
const join_value = args.rows[i][parent_by];
|
|
424
|
+
if (join_value == null) {
|
|
425
|
+
args.out[i][args.table] = many;
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const rows = grouped.get(join_value) ?? [];
|
|
429
|
+
args.out[i][args.table] = is_many
|
|
430
|
+
? rows.map(r => base.project(r, relation.fields))
|
|
431
|
+
: rows[0] ? base.project(rows[0], relation.fields) : null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async #load_related(args) {
|
|
435
|
+
const per_parent = args.kind === "one" ? 1 : args.limit;
|
|
436
|
+
const in_binds = {};
|
|
437
|
+
const placeholders = [];
|
|
438
|
+
for (let i = 0; i < args.join_values.length; i++) {
|
|
439
|
+
const key = `${args.by}__in${i}`;
|
|
440
|
+
in_binds[key] = args.join_values[i];
|
|
441
|
+
placeholders.push(`${BIND_BY}${key}`);
|
|
442
|
+
}
|
|
443
|
+
const where = this.#where(args.as.types, args.where);
|
|
444
|
+
const where_part = where ? where.slice("WHERE ".length) : "";
|
|
445
|
+
const in_part = Q `${args.by} IN (${placeholders})`;
|
|
446
|
+
const where_parts = where_part
|
|
447
|
+
? [`(${where_part})`, `(${in_part})`]
|
|
448
|
+
: [in_part];
|
|
449
|
+
const WHERE = `WHERE ${where_parts.join(" AND ")}`;
|
|
450
|
+
const all_columns = Object.keys(args.as.types);
|
|
451
|
+
const fields = args.fields !== undefined && args.fields.length > 0
|
|
452
|
+
? args.fields
|
|
453
|
+
: all_columns;
|
|
454
|
+
const SELECT = sql.columns(args.as.types, base.fields(fields, args.by));
|
|
455
|
+
const base_order = `${sql.quote(args.by)} ASC`;
|
|
456
|
+
const user_order = sql.orderBy(args.as.types, args.sort)
|
|
457
|
+
.replace(/^ ORDER BY /, "");
|
|
458
|
+
const ORDER_BY = user_order
|
|
459
|
+
? ` ORDER BY ${base_order}, ${user_order}`
|
|
460
|
+
: ` ORDER BY ${base_order}`;
|
|
461
|
+
const where_binds = await this.#bind_where(args.as.types, args.where);
|
|
462
|
+
const in_binds_bound = await bind(args.as.types, in_binds);
|
|
463
|
+
let query;
|
|
464
|
+
let binds;
|
|
465
|
+
const table = args.as.table;
|
|
466
|
+
if (per_parent !== undefined) {
|
|
467
|
+
const per_key = `${args.by}__limit__per_parent`;
|
|
468
|
+
query = `
|
|
469
|
+
WITH ranked AS (
|
|
470
|
+
SELECT
|
|
471
|
+
${SELECT},
|
|
472
|
+
ROW_NUMBER() OVER (
|
|
473
|
+
PARTITION BY ${sql.quote(args.by)}
|
|
474
|
+
${user_order ? `ORDER BY ${user_order}` : ""}
|
|
475
|
+
) AS __rn
|
|
476
|
+
FROM ${sql.quote(table)}
|
|
477
|
+
${WHERE}
|
|
478
|
+
)
|
|
479
|
+
SELECT ${SELECT}
|
|
480
|
+
FROM ranked
|
|
481
|
+
WHERE __rn <= ${BIND_BY}${per_key}
|
|
482
|
+
${ORDER_BY}
|
|
483
|
+
`;
|
|
484
|
+
binds = {
|
|
485
|
+
...where_binds, ...in_binds_bound,
|
|
486
|
+
[`${BIND_BY}${per_key}`]: per_parent,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
query = `SELECT ${SELECT} FROM ${sql.quote(table)} ${WHERE}${ORDER_BY}`;
|
|
491
|
+
binds = { ...where_binds, ...in_binds_bound };
|
|
492
|
+
}
|
|
493
|
+
return this.#query_all(args.as, query, binds);
|
|
494
|
+
}
|
|
495
|
+
#join(as, aliases, relation) {
|
|
496
|
+
const reverse = assert.maybe.boolean(relation.reverse) ?? false;
|
|
497
|
+
const parent_alias = aliases[as.table];
|
|
498
|
+
const alias = aliases[relation.as.table];
|
|
499
|
+
const table = sql.quote(relation.as.table);
|
|
500
|
+
const by = reverse ? relation.as.pk : relation.fk;
|
|
501
|
+
const parent_by = reverse ? relation.fk : as.pk;
|
|
502
|
+
if (by === null)
|
|
503
|
+
throw E.relation_requires_pk("target");
|
|
504
|
+
if (parent_by === null)
|
|
505
|
+
throw E.relation_requires_pk("parent");
|
|
506
|
+
const left = `${alias}.${sql.quote(by)}`;
|
|
507
|
+
const right = `${parent_alias}.${sql.quote(parent_by)}`;
|
|
508
|
+
return `LEFT JOIN ${table} ${alias} ON ${left} = ${right}`;
|
|
509
|
+
}
|
|
510
|
+
#base_query(as, args) {
|
|
511
|
+
const SELECT = sql.columns(as.types, args.fields);
|
|
512
|
+
const WHERE = this.#where(as.types, args.where);
|
|
513
|
+
const ORDER_BY = sql.orderBy(as.types, args.sort);
|
|
514
|
+
const LIMIT = sql.limit(args.limit);
|
|
515
|
+
const table = sql.quote(as.table);
|
|
516
|
+
return `SELECT ${SELECT} FROM ${table} ${WHERE}${ORDER_BY}${LIMIT}`;
|
|
517
|
+
}
|
|
518
|
+
async #read_joined(as, args) {
|
|
519
|
+
if (as.pk === null)
|
|
520
|
+
throw E.relation_requires_pk("parent");
|
|
521
|
+
const tables = [as.table, ...Object.values(args.with).map(r => r.as.table)];
|
|
522
|
+
const aliases = sql.aliases(tables);
|
|
523
|
+
const alias = aliases[as.table];
|
|
524
|
+
const fields = base.fields(args.fields, as.pk);
|
|
525
|
+
// subquery for limited parents
|
|
526
|
+
const SELECT = [
|
|
527
|
+
sql.aliased(aliases, as, fields),
|
|
528
|
+
...Object.values(args.with).map(relation => {
|
|
529
|
+
return sql.aliased(aliases, relation.as, relation.as.pk !== null
|
|
530
|
+
? base.fields(relation.fields, relation.as.pk)
|
|
531
|
+
: relation.fields);
|
|
532
|
+
}),
|
|
533
|
+
].join(", ");
|
|
534
|
+
const SUBQUERY = `(${this.#base_query(as, { ...args, fields })})`;
|
|
535
|
+
const FROM = `${SUBQUERY} ${alias}`;
|
|
536
|
+
const JOINS = Object.values(args.with)
|
|
537
|
+
.map(relation => this.#join(as, aliases, relation))
|
|
538
|
+
.join("\n");
|
|
539
|
+
const ORDER_BY = sql.orderBy(as.types, args.sort, alias);
|
|
540
|
+
const query = `SELECT ${SELECT} FROM ${FROM} ${JOINS} ${ORDER_BY}`;
|
|
541
|
+
const binds = await this.#bind_where(as.types, args.where);
|
|
542
|
+
const rows = this.#sql(query).all(binds);
|
|
543
|
+
this.#capture(as.table, query, binds);
|
|
544
|
+
return sql.nest(as, { rows, aliases }, args, unbind_one);
|
|
545
|
+
}
|
|
546
|
+
async update(as, args) {
|
|
547
|
+
assert.nonempty(args.set);
|
|
548
|
+
assert.dict(args.where);
|
|
549
|
+
const WHERE = this.#where(as.types, args.where);
|
|
550
|
+
const where_binds = await this.#bind_where(as.types, args.where);
|
|
551
|
+
const SET = this.#set(args.set);
|
|
552
|
+
const set_binds = await this.#bind_set(as.types, args.set);
|
|
553
|
+
const query = `UPDATE ${sql.quote(as.table)} ${SET} ${WHERE}`;
|
|
554
|
+
return this.#query_run(query, { ...where_binds, ...set_binds });
|
|
555
|
+
}
|
|
556
|
+
async delete(as, args) {
|
|
557
|
+
assert.nonempty(args.where);
|
|
558
|
+
const WHERE = this.#where(as.types, args.where);
|
|
559
|
+
const query = `DELETE FROM ${sql.quote(as.table)} ${WHERE}`;
|
|
560
|
+
const binds = await this.#bind_where(as.types, args.where);
|
|
561
|
+
return this.#query_run(query, binds);
|
|
562
|
+
}
|
|
563
|
+
close() {
|
|
564
|
+
this.#db.close();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
//# sourceMappingURL=SQLite.js.map
|
package/lib/private/typemap.d.ts
CHANGED
package/lib/private/typemap.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import numeric from "@rcompat/is/numeric";
|
|
2
1
|
function identity(column) {
|
|
3
2
|
return {
|
|
4
3
|
bind: value => value,
|
|
@@ -16,8 +15,7 @@ function number(column) {
|
|
|
16
15
|
const typemap = {
|
|
17
16
|
blob: {
|
|
18
17
|
async bind(value) {
|
|
19
|
-
|
|
20
|
-
return new Uint8Array(arrayBuffer);
|
|
18
|
+
return new Uint8Array(await value.arrayBuffer());
|
|
21
19
|
},
|
|
22
20
|
column: "BLOB",
|
|
23
21
|
unbind(value) {
|
|
@@ -65,16 +63,13 @@ const typemap = {
|
|
|
65
63
|
},
|
|
66
64
|
},
|
|
67
65
|
i8: number("INTEGER"),
|
|
68
|
-
|
|
66
|
+
json: {
|
|
69
67
|
bind(value) {
|
|
70
|
-
|
|
71
|
-
return Number(value);
|
|
72
|
-
}
|
|
73
|
-
throw new Error(`\`${value}\` is not a valid primary key value`);
|
|
68
|
+
return JSON.stringify(value);
|
|
74
69
|
},
|
|
75
|
-
column: "
|
|
70
|
+
column: "TEXT",
|
|
76
71
|
unbind(value) {
|
|
77
|
-
return
|
|
72
|
+
return JSON.parse(value);
|
|
78
73
|
},
|
|
79
74
|
},
|
|
80
75
|
string: identity("TEXT"),
|
|
@@ -109,6 +104,9 @@ const typemap = {
|
|
|
109
104
|
return new URL(value);
|
|
110
105
|
},
|
|
111
106
|
},
|
|
107
|
+
uuid: identity("TEXT"),
|
|
108
|
+
uuid_v4: identity("TEXT"),
|
|
109
|
+
uuid_v7: identity("TEXT"),
|
|
112
110
|
};
|
|
113
111
|
export default typemap;
|
|
114
112
|
//# sourceMappingURL=typemap.js.map
|
package/lib/public/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
declare const _default: (config?: typeof
|
|
1
|
+
import SQLite from "#SQLite";
|
|
2
|
+
declare const _default: (config?: typeof SQLite.config) => SQLite;
|
|
3
3
|
export default _default;
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/public/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default (config) => new
|
|
1
|
+
import SQLite from "#SQLite";
|
|
2
|
+
export default (config) => new SQLite(config);
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,41 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/sqlite",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "SQLite databases for Primate",
|
|
5
5
|
"homepage": "https://primate.run/docs/database/sqlite",
|
|
6
6
|
"bugs": "https://github.com/primate-run/primate/issues",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"license": "MIT",
|
|
8
|
-
"files": [
|
|
9
|
-
"/lib/**/*.js",
|
|
10
|
-
"/lib/**/*.d.ts",
|
|
11
|
-
"!/**/*.spec.*"
|
|
12
|
-
],
|
|
13
9
|
"repository": {
|
|
14
10
|
"type": "git",
|
|
15
11
|
"url": "https://github.com/primate-run/primate",
|
|
16
12
|
"directory": "packages/sqlite"
|
|
17
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"/lib/**/*.js",
|
|
16
|
+
"/lib/**/*.d.ts",
|
|
17
|
+
"!/**/*.spec.*"
|
|
18
|
+
],
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@rcompat/assert": "^0.
|
|
20
|
-
"@rcompat/
|
|
21
|
-
"@rcompat/
|
|
22
|
-
"@
|
|
23
|
-
"@rcompat/sqlite": "^0.4.0",
|
|
24
|
-
"@primate/core": "^0.4.0"
|
|
20
|
+
"@rcompat/assert": "^0.8.1",
|
|
21
|
+
"@rcompat/is": "^0.6.1",
|
|
22
|
+
"@rcompat/sqlite": "^0.11.1",
|
|
23
|
+
"@primate/core": "^0.6.0"
|
|
25
24
|
},
|
|
26
25
|
"devDependencies": {
|
|
27
|
-
"
|
|
26
|
+
"@rcompat/type": "^0.11.1",
|
|
27
|
+
"pema": "^0.6.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"primate": "^0.37.0"
|
|
28
31
|
},
|
|
29
|
-
"type": "module",
|
|
30
32
|
"imports": {
|
|
31
33
|
"#*": {
|
|
32
|
-
"
|
|
34
|
+
"@primate/source": "./src/private/*.ts",
|
|
33
35
|
"default": "./lib/private/*.js"
|
|
34
36
|
}
|
|
35
37
|
},
|
|
36
38
|
"exports": {
|
|
37
39
|
".": {
|
|
38
|
-
"
|
|
40
|
+
"@primate/source": "./src/public/index.ts",
|
|
39
41
|
"default": "./lib/public/index.js"
|
|
40
42
|
}
|
|
41
43
|
},
|
|
@@ -43,8 +45,8 @@
|
|
|
43
45
|
"build": "npm run clean && tsc",
|
|
44
46
|
"clean": "rm -rf ./lib",
|
|
45
47
|
"lint": "eslint .",
|
|
46
|
-
"test": "
|
|
47
|
-
"test:bun": "
|
|
48
|
-
"test:deno": "
|
|
48
|
+
"test": "npx proby",
|
|
49
|
+
"test:bun": "bunx --bun proby",
|
|
50
|
+
"test:deno": "deno run -A npm:proby"
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import Database from "@primate/core/Database";
|
|
2
|
-
import type As from "@primate/core/database/As";
|
|
3
|
-
import type DataDict from "@primate/core/database/DataDict";
|
|
4
|
-
import type TypeMap from "@primate/core/database/TypeMap";
|
|
5
|
-
import type Dict from "@rcompat/type/Dict";
|
|
6
|
-
import type StoreSchema from "pema/StoreSchema";
|
|
7
|
-
declare const schema: import("pema").ObjectType<{
|
|
8
|
-
database: import("pema").DefaultType<import("pema/string").StringType, ":memory:">;
|
|
9
|
-
}>;
|
|
10
|
-
export default class SQLiteDatabase extends Database {
|
|
11
|
-
#private;
|
|
12
|
-
static config: typeof schema.input;
|
|
13
|
-
constructor(config?: typeof schema.input);
|
|
14
|
-
close(): void;
|
|
15
|
-
get typemap(): TypeMap<Dict>;
|
|
16
|
-
get schema(): {
|
|
17
|
-
create: (name: string, store: StoreSchema) => void;
|
|
18
|
-
delete: (name: string) => void;
|
|
19
|
-
};
|
|
20
|
-
create<O extends Dict>(as: As, args: {
|
|
21
|
-
record: DataDict;
|
|
22
|
-
}): Promise<O>;
|
|
23
|
-
read(as: As, args: {
|
|
24
|
-
count: true;
|
|
25
|
-
criteria: DataDict;
|
|
26
|
-
}): Promise<number>;
|
|
27
|
-
read(as: As, args: {
|
|
28
|
-
criteria: DataDict;
|
|
29
|
-
fields?: string[];
|
|
30
|
-
limit?: number;
|
|
31
|
-
sort?: Dict<"asc" | "desc">;
|
|
32
|
-
}): Promise<Dict[]>;
|
|
33
|
-
update(as: As, args: {
|
|
34
|
-
changes: DataDict;
|
|
35
|
-
criteria: DataDict;
|
|
36
|
-
}): Promise<number>;
|
|
37
|
-
delete(as: As, args: {
|
|
38
|
-
criteria: DataDict;
|
|
39
|
-
}): Promise<number>;
|
|
40
|
-
}
|
|
41
|
-
export {};
|
|
42
|
-
//# sourceMappingURL=Database.d.ts.map
|
package/lib/private/Database.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import typemap from "#typemap";
|
|
2
|
-
import Database from "@primate/core/Database";
|
|
3
|
-
import assert from "@rcompat/assert";
|
|
4
|
-
import Client from "@rcompat/sqlite";
|
|
5
|
-
import pema from "pema";
|
|
6
|
-
import string from "pema/string";
|
|
7
|
-
const schema = pema({
|
|
8
|
-
database: string.default(":memory:"),
|
|
9
|
-
});
|
|
10
|
-
const options = { safeIntegers: true };
|
|
11
|
-
export default class SQLiteDatabase extends Database {
|
|
12
|
-
#factory;
|
|
13
|
-
#client;
|
|
14
|
-
static config;
|
|
15
|
-
constructor(config) {
|
|
16
|
-
super("$");
|
|
17
|
-
const parsed = schema.parse(config);
|
|
18
|
-
this.#factory = () => new Client(parsed.database, options);
|
|
19
|
-
}
|
|
20
|
-
#get() {
|
|
21
|
-
if (this.#client === undefined) {
|
|
22
|
-
this.#client = this.#factory();
|
|
23
|
-
}
|
|
24
|
-
return this.#client;
|
|
25
|
-
}
|
|
26
|
-
close() {
|
|
27
|
-
this.#get().close();
|
|
28
|
-
}
|
|
29
|
-
get typemap() {
|
|
30
|
-
return typemap;
|
|
31
|
-
}
|
|
32
|
-
#new(name, store) {
|
|
33
|
-
const body = Object.entries(store)
|
|
34
|
-
.map(([key, value]) => `${this.ident(key)} ${this.column(value.datatype)}`)
|
|
35
|
-
.join(",");
|
|
36
|
-
const query = `CREATE TABLE IF NOT EXISTS ${this.ident(name)} (${body})`;
|
|
37
|
-
this.#get().prepare(query).run();
|
|
38
|
-
}
|
|
39
|
-
#drop(name) {
|
|
40
|
-
const query = `DROP TABLE IF EXISTS ${this.ident(name)}`;
|
|
41
|
-
this.#get().prepare(query).run();
|
|
42
|
-
}
|
|
43
|
-
get schema() {
|
|
44
|
-
return {
|
|
45
|
-
create: this.#new.bind(this),
|
|
46
|
-
delete: this.#drop.bind(this),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
async create(as, args) {
|
|
50
|
-
const keys = Object.keys(args.record);
|
|
51
|
-
const columns = keys.map(k => this.ident(k));
|
|
52
|
-
const values = keys.map(key => `$${key}`).join(",");
|
|
53
|
-
const payload = columns.length > 0
|
|
54
|
-
? `(${columns.join(",")}) VALUES (${values})`
|
|
55
|
-
: "DEFAULT VALUES";
|
|
56
|
-
const query = `INSERT INTO ${this.table(as)} ${payload} RETURNING id;`;
|
|
57
|
-
const binds = await this.bind(as.types, args.record);
|
|
58
|
-
const statement = this.#get().prepare(query);
|
|
59
|
-
const changes = statement.run(binds);
|
|
60
|
-
const id = BigInt(changes.lastInsertRowid);
|
|
61
|
-
return this.unbind(as.types, { ...args.record, id });
|
|
62
|
-
}
|
|
63
|
-
async read(as, args) {
|
|
64
|
-
const where = this.toWhere(as.types, args.criteria);
|
|
65
|
-
const binds = await this.bindCriteria(as.types, args.criteria);
|
|
66
|
-
if (args.count === true) {
|
|
67
|
-
const query = `SELECT COUNT(*) AS n FROM ${this.table(as)} ${where};`;
|
|
68
|
-
const [{ n }] = this.#get().prepare(query).all(binds);
|
|
69
|
-
return Number(n);
|
|
70
|
-
}
|
|
71
|
-
const select = this.toSelect(as.types, args.fields);
|
|
72
|
-
const sort = this.toSort(as.types, args.sort);
|
|
73
|
-
const limit = this.toLimit(args.limit);
|
|
74
|
-
const query = `SELECT
|
|
75
|
-
${select} FROM ${this.table(as)} ${where}${sort}${limit};`;
|
|
76
|
-
const records = this.#get().prepare(query).all(binds);
|
|
77
|
-
return records.map(record => this.unbind(as.types, record));
|
|
78
|
-
}
|
|
79
|
-
async update(as, args) {
|
|
80
|
-
assert(Object.keys(args.criteria).length > 0, "update: no criteria");
|
|
81
|
-
const where = this.toWhere(as.types, args.criteria);
|
|
82
|
-
const criteria = await this.bindCriteria(as.types, args.criteria);
|
|
83
|
-
const { set, binds: set_binds } = await this.toSet(as.types, args.changes);
|
|
84
|
-
const binds = { ...criteria, ...set_binds };
|
|
85
|
-
const query = `
|
|
86
|
-
WITH to_update AS (
|
|
87
|
-
SELECT id FROM ${this.table(as)}
|
|
88
|
-
${where}
|
|
89
|
-
)
|
|
90
|
-
UPDATE ${this.table(as)}
|
|
91
|
-
${set}
|
|
92
|
-
WHERE id IN (SELECT id FROM to_update)
|
|
93
|
-
`;
|
|
94
|
-
return Number(this.#get().prepare(`${query};`).run(binds).changes);
|
|
95
|
-
}
|
|
96
|
-
async delete(as, args) {
|
|
97
|
-
assert(Object.keys(args.criteria).length > 0, "delete: no criteria");
|
|
98
|
-
const where = this.toWhere(as.types, args.criteria);
|
|
99
|
-
const binds = await this.bindCriteria(as.types, args.criteria);
|
|
100
|
-
const query = `DELETE FROM ${this.table(as)} ${where}`;
|
|
101
|
-
return Number(this.#get().prepare(query).run(binds).changes);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
//# sourceMappingURL=Database.js.map
|