@primate/sqlite 0.7.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/SQLite.d.ts +6 -8
- package/lib/private/SQLite.js +83 -31
- package/lib/private/typemap.js +12 -0
- package/package.json +13 -13
package/lib/private/SQLite.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { As, DataDict, DB, Sort, With } from "@primate/core/db";
|
|
1
|
+
import type { As, DataDict, DB, Schema, Sort, With } from "@primate/core/db";
|
|
2
2
|
import type { Dict } from "@rcompat/type";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
declare const schema: import("pema").ObjectType<import("pema").NormalizeSchemaObject<{
|
|
4
|
+
readonly database: import("pema").DefaultType<import("pema").StringType, ":memory:">;
|
|
5
|
+
}>, {
|
|
6
|
+
database: string;
|
|
6
7
|
}>;
|
|
7
8
|
export default class SQLite implements DB {
|
|
8
9
|
#private;
|
|
@@ -14,10 +15,7 @@ export default class SQLite implements DB {
|
|
|
14
15
|
query: string;
|
|
15
16
|
plans: string[];
|
|
16
17
|
}>;
|
|
17
|
-
get schema():
|
|
18
|
-
create: (as: As, store: StoreSchema) => void;
|
|
19
|
-
delete: (table: string) => void;
|
|
20
|
-
};
|
|
18
|
+
get schema(): Schema;
|
|
21
19
|
create<O extends Dict>(as: As, record: Dict): Promise<O>;
|
|
22
20
|
read(as: As, args: {
|
|
23
21
|
count: true;
|
package/lib/private/SQLite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import typemap from "#typemap";
|
|
2
|
-
import
|
|
3
|
-
import E from "@primate/core/db/
|
|
2
|
+
import base from "@primate/core/db";
|
|
3
|
+
import E from "@primate/core/db/errors";
|
|
4
4
|
import sql from "@primate/core/db/sql";
|
|
5
5
|
import assert from "@rcompat/assert";
|
|
6
6
|
import is from "@rcompat/is";
|
|
@@ -65,8 +65,8 @@ function cmp_expr(field, key, op, datatype) {
|
|
|
65
65
|
const rhs = `${BIND_BY}${key}`;
|
|
66
66
|
const sql_op = sql.OPS[op];
|
|
67
67
|
assert.defined(sql_op, E.operator_unknown(field, op));
|
|
68
|
-
if (
|
|
69
|
-
const cmp =
|
|
68
|
+
if (base.BIGINT_STRING_TYPES.includes(datatype)) {
|
|
69
|
+
const cmp = base.UNSIGNED_BIGINT_TYPES.includes(datatype)
|
|
70
70
|
? u_cmp(field, `CAST(${field} AS TEXT)`, `CAST(${rhs} AS TEXT)`, sql_op)
|
|
71
71
|
: i_cmp(field, rhs, sql_op);
|
|
72
72
|
return `(${cmp})`;
|
|
@@ -91,16 +91,35 @@ function unbind_one(key, value) {
|
|
|
91
91
|
async function bind(types, fields) {
|
|
92
92
|
const out = {};
|
|
93
93
|
for (const [key, value] of Object.entries(fields)) {
|
|
94
|
-
if (is.dict(value))
|
|
95
|
-
throw E.operator_scalar(key);
|
|
96
94
|
const raw = key.startsWith("s_") ? key.slice(2) : key;
|
|
97
|
-
|
|
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);
|
|
98
99
|
}
|
|
99
100
|
return out;
|
|
100
101
|
}
|
|
101
102
|
function unbind(types, row) {
|
|
102
103
|
return sql.unbind(types, row, unbind_one);
|
|
103
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
|
+
}
|
|
104
123
|
export default class SQLite {
|
|
105
124
|
#factory;
|
|
106
125
|
#client;
|
|
@@ -137,25 +156,55 @@ export default class SQLite {
|
|
|
137
156
|
}
|
|
138
157
|
get schema() {
|
|
139
158
|
return {
|
|
140
|
-
create: (
|
|
159
|
+
create: (table, pk, types) => {
|
|
141
160
|
const columns = [];
|
|
142
|
-
for (const [key, value] of Object.entries(
|
|
143
|
-
const column_type = get_column(value
|
|
161
|
+
for (const [key, value] of Object.entries(types)) {
|
|
162
|
+
const column_type = get_column(value);
|
|
144
163
|
const column = `${sql.quote(key)} ${column_type}`;
|
|
145
164
|
const is_int = column_type === "INTEGER";
|
|
146
|
-
if (key ===
|
|
147
|
-
const generate =
|
|
165
|
+
if (key === pk.name) {
|
|
166
|
+
const generate = pk.generate && is_int ? " AUTOINCREMENT" : "";
|
|
148
167
|
columns.push(`${column} PRIMARY KEY${generate}`);
|
|
149
168
|
}
|
|
150
169
|
else {
|
|
151
170
|
columns.push(column);
|
|
152
171
|
}
|
|
153
172
|
}
|
|
154
|
-
this.#sql(Q `CREATE TABLE IF NOT EXISTS ${
|
|
173
|
+
this.#sql(Q `CREATE TABLE IF NOT EXISTS ${table} (${columns})`).run();
|
|
155
174
|
},
|
|
156
175
|
delete: (table) => {
|
|
157
176
|
this.#sql(Q `DROP TABLE IF EXISTS ${table}`).run();
|
|
158
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
|
+
},
|
|
159
208
|
};
|
|
160
209
|
}
|
|
161
210
|
#where(types, where, alias) {
|
|
@@ -255,9 +304,9 @@ export default class SQLite {
|
|
|
255
304
|
#generate_pk(as) {
|
|
256
305
|
const pk = as.pk;
|
|
257
306
|
const type = as.types[pk];
|
|
258
|
-
if (type
|
|
259
|
-
return
|
|
260
|
-
if (
|
|
307
|
+
if (base.is_uuid_type(type))
|
|
308
|
+
return base.generate_uuid(type);
|
|
309
|
+
if (base.BIGINT_STRING_TYPES.includes(type)) {
|
|
261
310
|
const query = Q `SELECT ${pk} AS v
|
|
262
311
|
FROM ${as.table} ORDER BY LENGTH(${pk}) DESC, ${pk} DESC LIMIT 1`;
|
|
263
312
|
const rows = this.#sql(query).all();
|
|
@@ -289,7 +338,7 @@ export default class SQLite {
|
|
|
289
338
|
throw E.pk_required(pk);
|
|
290
339
|
const type = as.types[pk];
|
|
291
340
|
// integer types, use RETURNING
|
|
292
|
-
if (!
|
|
341
|
+
if (!base.BIGINT_STRING_TYPES.includes(type) && !base.is_uuid_type(type)) {
|
|
293
342
|
const [keys, values] = this.#create(record);
|
|
294
343
|
const query = keys.length > 0
|
|
295
344
|
? Q `INSERT INTO ${table} (${keys}) VALUES (${values}) RETURNING ${pk}`
|
|
@@ -311,7 +360,7 @@ export default class SQLite {
|
|
|
311
360
|
this.#explain = {};
|
|
312
361
|
if (args.count === true)
|
|
313
362
|
return this.#count(as, args.where);
|
|
314
|
-
if (
|
|
363
|
+
if (base.withed(args)) {
|
|
315
364
|
return sql.joinable(as, args.with)
|
|
316
365
|
? this.#read_joined(as, args)
|
|
317
366
|
: this.#read_phased(as, args);
|
|
@@ -325,9 +374,10 @@ export default class SQLite {
|
|
|
325
374
|
const query = `SELECT COUNT(*) AS n FROM ${sql.quote(table)} ${WHERE}`;
|
|
326
375
|
this.#capture(table, query, binds);
|
|
327
376
|
const rows = this.#sql(query).all(binds);
|
|
328
|
-
|
|
377
|
+
const length = rows.length;
|
|
378
|
+
assert.true(length === 1, E.record_number_invalid(length, "count"));
|
|
329
379
|
const n = rows[0].n;
|
|
330
|
-
assert.bigint(n,
|
|
380
|
+
assert.bigint(n, E.return_invalid(n, "bigint", "count"));
|
|
331
381
|
if (n > BigInt(Number.MAX_SAFE_INTEGER))
|
|
332
382
|
throw E.count_overflow(table, n);
|
|
333
383
|
return Number(n);
|
|
@@ -338,9 +388,9 @@ export default class SQLite {
|
|
|
338
388
|
return this.#query_all(as, query, binds);
|
|
339
389
|
}
|
|
340
390
|
async #read_phased(as, args) {
|
|
341
|
-
const fields =
|
|
391
|
+
const fields = base.expand(as, args.fields, args.with);
|
|
342
392
|
const rows = await this.#read_base(as, { ...args, fields });
|
|
343
|
-
const out = rows.map(row =>
|
|
393
|
+
const out = rows.map(row => base.project(row, args.fields));
|
|
344
394
|
for (const [table, relation] of Object.entries(args.with)) {
|
|
345
395
|
await this.#attach_relation(as, { rows, out, table, relation });
|
|
346
396
|
}
|
|
@@ -348,10 +398,11 @@ export default class SQLite {
|
|
|
348
398
|
}
|
|
349
399
|
async #attach_relation(as, args) {
|
|
350
400
|
const relation = args.relation;
|
|
351
|
-
const
|
|
401
|
+
const reverse = assert.maybe.boolean(relation.reverse) ?? false;
|
|
402
|
+
const by = reverse ? relation.as.pk : relation.fk;
|
|
352
403
|
if (by === null)
|
|
353
404
|
throw E.relation_requires_pk("target");
|
|
354
|
-
const parent_by =
|
|
405
|
+
const parent_by = reverse ? relation.fk : as.pk;
|
|
355
406
|
if (parent_by === null)
|
|
356
407
|
throw E.relation_requires_pk("parent");
|
|
357
408
|
const join_values = [...new Set(args.rows.map(r => r[parent_by]).filter(v => v != null))];
|
|
@@ -376,8 +427,8 @@ export default class SQLite {
|
|
|
376
427
|
}
|
|
377
428
|
const rows = grouped.get(join_value) ?? [];
|
|
378
429
|
args.out[i][args.table] = is_many
|
|
379
|
-
? rows.map(r =>
|
|
380
|
-
: rows[0] ?
|
|
430
|
+
? rows.map(r => base.project(r, relation.fields))
|
|
431
|
+
: rows[0] ? base.project(rows[0], relation.fields) : null;
|
|
381
432
|
}
|
|
382
433
|
}
|
|
383
434
|
async #load_related(args) {
|
|
@@ -400,7 +451,7 @@ export default class SQLite {
|
|
|
400
451
|
const fields = args.fields !== undefined && args.fields.length > 0
|
|
401
452
|
? args.fields
|
|
402
453
|
: all_columns;
|
|
403
|
-
const SELECT = sql.columns(args.as.types,
|
|
454
|
+
const SELECT = sql.columns(args.as.types, base.fields(fields, args.by));
|
|
404
455
|
const base_order = `${sql.quote(args.by)} ASC`;
|
|
405
456
|
const user_order = sql.orderBy(args.as.types, args.sort)
|
|
406
457
|
.replace(/^ ORDER BY /, "");
|
|
@@ -442,11 +493,12 @@ export default class SQLite {
|
|
|
442
493
|
return this.#query_all(args.as, query, binds);
|
|
443
494
|
}
|
|
444
495
|
#join(as, aliases, relation) {
|
|
496
|
+
const reverse = assert.maybe.boolean(relation.reverse) ?? false;
|
|
445
497
|
const parent_alias = aliases[as.table];
|
|
446
498
|
const alias = aliases[relation.as.table];
|
|
447
499
|
const table = sql.quote(relation.as.table);
|
|
448
|
-
const by =
|
|
449
|
-
const parent_by =
|
|
500
|
+
const by = reverse ? relation.as.pk : relation.fk;
|
|
501
|
+
const parent_by = reverse ? relation.fk : as.pk;
|
|
450
502
|
if (by === null)
|
|
451
503
|
throw E.relation_requires_pk("target");
|
|
452
504
|
if (parent_by === null)
|
|
@@ -469,13 +521,13 @@ export default class SQLite {
|
|
|
469
521
|
const tables = [as.table, ...Object.values(args.with).map(r => r.as.table)];
|
|
470
522
|
const aliases = sql.aliases(tables);
|
|
471
523
|
const alias = aliases[as.table];
|
|
472
|
-
const fields =
|
|
524
|
+
const fields = base.fields(args.fields, as.pk);
|
|
473
525
|
// subquery for limited parents
|
|
474
526
|
const SELECT = [
|
|
475
527
|
sql.aliased(aliases, as, fields),
|
|
476
528
|
...Object.values(args.with).map(relation => {
|
|
477
529
|
return sql.aliased(aliases, relation.as, relation.as.pk !== null
|
|
478
|
-
?
|
|
530
|
+
? base.fields(relation.fields, relation.as.pk)
|
|
479
531
|
: relation.fields);
|
|
480
532
|
}),
|
|
481
533
|
].join(", ");
|
package/lib/private/typemap.js
CHANGED
|
@@ -63,6 +63,15 @@ const typemap = {
|
|
|
63
63
|
},
|
|
64
64
|
},
|
|
65
65
|
i8: number("INTEGER"),
|
|
66
|
+
json: {
|
|
67
|
+
bind(value) {
|
|
68
|
+
return JSON.stringify(value);
|
|
69
|
+
},
|
|
70
|
+
column: "TEXT",
|
|
71
|
+
unbind(value) {
|
|
72
|
+
return JSON.parse(value);
|
|
73
|
+
},
|
|
74
|
+
},
|
|
66
75
|
string: identity("TEXT"),
|
|
67
76
|
time: identity("TEXT"),
|
|
68
77
|
u128: {
|
|
@@ -95,6 +104,9 @@ const typemap = {
|
|
|
95
104
|
return new URL(value);
|
|
96
105
|
},
|
|
97
106
|
},
|
|
107
|
+
uuid: identity("TEXT"),
|
|
108
|
+
uuid_v4: identity("TEXT"),
|
|
109
|
+
uuid_v7: identity("TEXT"),
|
|
98
110
|
};
|
|
99
111
|
export default typemap;
|
|
100
112
|
//# sourceMappingURL=typemap.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/sqlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
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",
|
|
@@ -17,27 +17,27 @@
|
|
|
17
17
|
"!/**/*.spec.*"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@rcompat/assert": "^0.
|
|
21
|
-
"@rcompat/is": "^0.
|
|
22
|
-
"@rcompat/sqlite": "^0.
|
|
23
|
-
"@primate/core": "^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"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@rcompat/type": "^0.
|
|
27
|
-
"pema": "^0.
|
|
26
|
+
"@rcompat/type": "^0.11.1",
|
|
27
|
+
"pema": "^0.6.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"primate": "^0.
|
|
30
|
+
"primate": "^0.37.0"
|
|
31
31
|
},
|
|
32
32
|
"imports": {
|
|
33
33
|
"#*": {
|
|
34
|
-
"
|
|
34
|
+
"@primate/source": "./src/private/*.ts",
|
|
35
35
|
"default": "./lib/private/*.js"
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"exports": {
|
|
39
39
|
".": {
|
|
40
|
-
"
|
|
40
|
+
"@primate/source": "./src/public/index.ts",
|
|
41
41
|
"default": "./lib/public/index.js"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"build": "npm run clean && tsc",
|
|
46
46
|
"clean": "rm -rf ./lib",
|
|
47
47
|
"lint": "eslint .",
|
|
48
|
-
"test": "
|
|
49
|
-
"test:bun": "
|
|
50
|
-
"test:deno": "
|
|
48
|
+
"test": "npx proby",
|
|
49
|
+
"test:bun": "bunx --bun proby",
|
|
50
|
+
"test:deno": "deno run -A npm:proby"
|
|
51
51
|
}
|
|
52
52
|
}
|