@primate/mysql 0.5.0 → 0.6.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 -0
- package/lib/private/MySQL.d.ts +14 -12
- package/lib/private/MySQL.js +91 -24
- package/lib/private/typemap.js +36 -10
- package/package.json +11 -11
package/lib/private/MySQL.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
password: import("pema").OptionalType<import("pema").StringType>;
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
declare const schema: import("pema").ObjectType<import("pema").NormalizeSchemaObject<{
|
|
4
|
+
readonly host: import("pema").DefaultType<import("pema").StringType, "localhost">;
|
|
5
|
+
readonly port: import("pema").DefaultType<import("pema").UintType<"u32">, 3306>;
|
|
6
|
+
readonly database: import("pema").StringType;
|
|
7
|
+
readonly password: import("pema").OptionalType<import("pema").StringType>;
|
|
8
|
+
readonly username: import("pema").OptionalType<import("pema").StringType>;
|
|
9
|
+
}>, {
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
database: string;
|
|
13
|
+
password: string | undefined;
|
|
14
|
+
username: string | undefined;
|
|
10
15
|
}>;
|
|
11
16
|
export default class MySQL implements DB {
|
|
12
17
|
#private;
|
|
@@ -19,10 +24,7 @@ export default class MySQL implements DB {
|
|
|
19
24
|
plans: string[];
|
|
20
25
|
}>;
|
|
21
26
|
close(): Promise<void>;
|
|
22
|
-
get schema():
|
|
23
|
-
create: (as: As, store: StoreSchema) => Promise<void>;
|
|
24
|
-
delete: (table: string) => Promise<void>;
|
|
25
|
-
};
|
|
27
|
+
get schema(): Schema;
|
|
26
28
|
create<O extends Dict>(as: As, record: Dict): Promise<O>;
|
|
27
29
|
read(as: As, args: {
|
|
28
30
|
count: true;
|
package/lib/private/MySQL.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";
|
|
@@ -22,7 +22,7 @@ function order_by(types, sort, alias) {
|
|
|
22
22
|
return "";
|
|
23
23
|
const parts = entries.map(([k, dir]) => {
|
|
24
24
|
const quoted = sql.quote(k);
|
|
25
|
-
const base = alias ? `${alias}.${quoted}` : quoted;
|
|
25
|
+
const base = alias !== undefined ? `${alias}.${quoted}` : quoted;
|
|
26
26
|
const expression = is_bigint_key(types[k]) ? bigint_cast(base) : base;
|
|
27
27
|
return `${expression} ${dir.toLowerCase() === "desc" ? "DESC" : "ASC"}`;
|
|
28
28
|
});
|
|
@@ -42,11 +42,41 @@ function unbind_value(key, value) {
|
|
|
42
42
|
function unbind(types, row) {
|
|
43
43
|
return sql.unbind(types, row, unbind_value);
|
|
44
44
|
}
|
|
45
|
+
function columns_to_types(data_t, column_t) {
|
|
46
|
+
const unsigned = column_t.includes("unsigned");
|
|
47
|
+
if (data_t === "varchar")
|
|
48
|
+
return ["string", "url"];
|
|
49
|
+
if (data_t === "text")
|
|
50
|
+
return ["string", "url", "time"];
|
|
51
|
+
if (data_t === "binary" && column_t.includes("(16)"))
|
|
52
|
+
return base.UUID_TYPES;
|
|
53
|
+
if (data_t === "smallint")
|
|
54
|
+
return unsigned ? ["u16"] : ["i16"];
|
|
55
|
+
if (data_t === "int")
|
|
56
|
+
return unsigned ? ["u32"] : ["i32"];
|
|
57
|
+
if (data_t === "bigint")
|
|
58
|
+
return unsigned ? ["u64"] : ["i64"];
|
|
59
|
+
if (data_t === "decimal")
|
|
60
|
+
return ["u128", "i128"];
|
|
61
|
+
if (data_t === "double")
|
|
62
|
+
return ["f32", "f64"];
|
|
63
|
+
if (data_t === "datetime")
|
|
64
|
+
return ["datetime"];
|
|
65
|
+
if (data_t === "tinyint" && column_t === "tinyint(1)")
|
|
66
|
+
return ["boolean"];
|
|
67
|
+
if (data_t === "tinyint")
|
|
68
|
+
return unsigned ? ["u8"] : ["i8", "boolean"];
|
|
69
|
+
if (data_t === "blob")
|
|
70
|
+
return ["blob"];
|
|
71
|
+
if (data_t === "json")
|
|
72
|
+
return ["json"];
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
45
75
|
const schema = p({
|
|
46
|
-
database: p.string,
|
|
47
76
|
host: p.string.default("localhost"),
|
|
48
|
-
password: p.string.optional(),
|
|
49
77
|
port: p.uint.port().default(3306),
|
|
78
|
+
database: p.string,
|
|
79
|
+
password: p.string.optional(),
|
|
50
80
|
username: p.string.optional(),
|
|
51
81
|
});
|
|
52
82
|
export default class MySQL {
|
|
@@ -94,26 +124,63 @@ export default class MySQL {
|
|
|
94
124
|
}
|
|
95
125
|
get schema() {
|
|
96
126
|
return {
|
|
97
|
-
create: async (
|
|
127
|
+
create: async (table, pk, types) => {
|
|
98
128
|
const columns = [];
|
|
99
|
-
for (const [key, value] of Object.entries(
|
|
100
|
-
const type = get_column(value
|
|
101
|
-
if (key ===
|
|
102
|
-
const is_int =
|
|
103
|
-
const auto =
|
|
129
|
+
for (const [key, value] of Object.entries(types)) {
|
|
130
|
+
const type = get_column(value);
|
|
131
|
+
if (key === pk.name) {
|
|
132
|
+
const is_int = base.INT_TYPES.includes(value);
|
|
133
|
+
const auto = pk.generate && is_int ? " AUTO_INCREMENT" : "";
|
|
104
134
|
// 36 is UUID length
|
|
105
|
-
const pk_type = value
|
|
135
|
+
const pk_type = value === "string" ? "VARCHAR(36)" : type;
|
|
106
136
|
columns.push(`${sql.quote(key)} ${pk_type} PRIMARY KEY${auto}`);
|
|
107
137
|
}
|
|
108
138
|
else {
|
|
109
139
|
columns.push(`${sql.quote(key)} ${type}`);
|
|
110
140
|
}
|
|
111
141
|
}
|
|
112
|
-
await this.#sql(Q `CREATE TABLE IF NOT EXISTS ${
|
|
142
|
+
await this.#sql(Q `CREATE TABLE IF NOT EXISTS ${table} (${columns})`);
|
|
113
143
|
},
|
|
114
144
|
delete: async (table) => {
|
|
115
145
|
await this.#sql(Q `DROP TABLE IF EXISTS ${table}`);
|
|
116
146
|
},
|
|
147
|
+
introspect: async (table) => {
|
|
148
|
+
const rows = await this.#sql(`
|
|
149
|
+
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE
|
|
150
|
+
FROM information_schema.COLUMNS
|
|
151
|
+
WHERE TABLE_SCHEMA = DATABASE()
|
|
152
|
+
AND TABLE_NAME = :name
|
|
153
|
+
`, { name: table });
|
|
154
|
+
if (rows.length === 0)
|
|
155
|
+
return null;
|
|
156
|
+
const result = {};
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
const candidates = columns_to_types(row.DATA_TYPE, row.COLUMN_TYPE);
|
|
159
|
+
if (candidates.length > 0)
|
|
160
|
+
result[row.COLUMN_NAME] = candidates;
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
},
|
|
164
|
+
alter: async (table, diff) => {
|
|
165
|
+
const existing = await this.schema.introspect(table);
|
|
166
|
+
if (existing === null)
|
|
167
|
+
throw E.table_not_found(table);
|
|
168
|
+
for (const [from, to] of diff.rename) {
|
|
169
|
+
await this.#sql(Q `
|
|
170
|
+
ALTER TABLE ${table}
|
|
171
|
+
RENAME COLUMN ${[sql.quote(from)]} TO ${[sql.quote(to)]}`);
|
|
172
|
+
}
|
|
173
|
+
const parts = [];
|
|
174
|
+
for (const field of diff.drop) {
|
|
175
|
+
parts.push(`DROP COLUMN ${sql.quote(field)}`);
|
|
176
|
+
}
|
|
177
|
+
for (const [field, type] of Object.entries(diff.add)) {
|
|
178
|
+
parts.push(`ADD COLUMN ${sql.quote(field)} ${get_column(type)}`);
|
|
179
|
+
}
|
|
180
|
+
if (parts.length > 0) {
|
|
181
|
+
await this.#sql(Q `ALTER TABLE ${table} ${[parts.join(", ")]}`);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
117
184
|
};
|
|
118
185
|
}
|
|
119
186
|
async #where(as, where) {
|
|
@@ -193,9 +260,9 @@ export default class MySQL {
|
|
|
193
260
|
const pk = as.pk;
|
|
194
261
|
const type = as.types[pk];
|
|
195
262
|
const table = as.table;
|
|
196
|
-
if (type
|
|
197
|
-
return
|
|
198
|
-
if (
|
|
263
|
+
if (base.is_uuid_type(type))
|
|
264
|
+
return base.generate_uuid(type);
|
|
265
|
+
if (base.BIGINT_STRING_TYPES.includes(type)) {
|
|
199
266
|
const cast = bigint_cast(sql.quote(pk));
|
|
200
267
|
const q = `SELECT MAX(${cast}) AS v FROM ${sql.quote(table)}`;
|
|
201
268
|
const rows = await this.#sql(q);
|
|
@@ -235,7 +302,7 @@ export default class MySQL {
|
|
|
235
302
|
throw E.pk_required(pk);
|
|
236
303
|
const type = as.types[pk];
|
|
237
304
|
// integer types, use AUTO_INCREMENT
|
|
238
|
-
if (!
|
|
305
|
+
if (!base.BIGINT_STRING_TYPES.includes(type) && !base.is_uuid_type(type)) {
|
|
239
306
|
const [keys, values] = this.#create(record);
|
|
240
307
|
const query = keys.length > 0
|
|
241
308
|
? Q `INSERT INTO ${table} (${keys}) VALUES (${values})`
|
|
@@ -257,7 +324,7 @@ export default class MySQL {
|
|
|
257
324
|
this.#explain = {};
|
|
258
325
|
if (args.count === true)
|
|
259
326
|
return this.#count(as, args.where);
|
|
260
|
-
if (
|
|
327
|
+
if (base.withed(args)) {
|
|
261
328
|
return sql.joinable(as, args.with)
|
|
262
329
|
? this.#read_joined(as, args)
|
|
263
330
|
: this.#read_phased(as, args);
|
|
@@ -292,9 +359,9 @@ export default class MySQL {
|
|
|
292
359
|
return rows.map(r => unbind(as.types, r));
|
|
293
360
|
}
|
|
294
361
|
async #read_phased(as, args) {
|
|
295
|
-
const fields =
|
|
362
|
+
const fields = base.expand(as, args.fields, args.with);
|
|
296
363
|
const rows = await this.#read(as, { ...args, fields });
|
|
297
|
-
const out = rows.map(row =>
|
|
364
|
+
const out = rows.map(row => base.project(row, args.fields));
|
|
298
365
|
for (const [table, relation] of Object.entries(args.with)) {
|
|
299
366
|
await this.#attach_relation(as, { rows, out, table, relation });
|
|
300
367
|
}
|
|
@@ -330,8 +397,8 @@ export default class MySQL {
|
|
|
330
397
|
}
|
|
331
398
|
const rows = grouped.get(join_value) ?? [];
|
|
332
399
|
args.out[i][args.table] = is_many
|
|
333
|
-
? rows.map(r =>
|
|
334
|
-
: rows[0] ?
|
|
400
|
+
? rows.map(r => base.project(r, relation.fields))
|
|
401
|
+
: rows[0] ? base.project(rows[0], relation.fields) : null;
|
|
335
402
|
}
|
|
336
403
|
}
|
|
337
404
|
async #load_related(args) {
|
|
@@ -416,13 +483,13 @@ export default class MySQL {
|
|
|
416
483
|
const tables = [as.table, ...Object.values(args.with).map(r => r.as.table)];
|
|
417
484
|
const aliases = sql.aliases(tables);
|
|
418
485
|
const alias = aliases[as.table];
|
|
419
|
-
const fields =
|
|
486
|
+
const fields = base.fields(args.fields, as.pk) ?? Object.keys(as.types);
|
|
420
487
|
const SELECT = [
|
|
421
488
|
...fields.map(f => `${alias}.${sql.quote(f)} AS ${alias}_${f}`),
|
|
422
489
|
...Object.values(args.with).flatMap(relation => {
|
|
423
490
|
const r_alias = aliases[relation.as.table];
|
|
424
491
|
const r_fields = relation.as.pk !== null
|
|
425
|
-
?
|
|
492
|
+
? base.fields(relation.fields, relation.as.pk)
|
|
426
493
|
: relation.fields;
|
|
427
494
|
return (r_fields ?? Object.keys(relation.as.types))
|
|
428
495
|
.map(f => `${r_alias}.${sql.quote(f)} AS ${r_alias}_${f}`);
|
package/lib/private/typemap.js
CHANGED
|
@@ -12,6 +12,18 @@ function number(column) {
|
|
|
12
12
|
unbind: (value) => Number(value),
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
+
function uuid_to_bin(uuid) {
|
|
16
|
+
return Uint8Array.fromHex(uuid.replace(/-/g, ""));
|
|
17
|
+
}
|
|
18
|
+
function bin_to_uuid(buf) {
|
|
19
|
+
const hex = buf.toHex();
|
|
20
|
+
const a = hex.slice(0, 8);
|
|
21
|
+
const b = hex.slice(8, 12);
|
|
22
|
+
const c = hex.slice(12, 16);
|
|
23
|
+
const d = hex.slice(16, 20);
|
|
24
|
+
const e = hex.slice(20);
|
|
25
|
+
return `${a}-${b}-${c}-${d}-${e}`;
|
|
26
|
+
}
|
|
15
27
|
const typemap = {
|
|
16
28
|
blob: {
|
|
17
29
|
async bind(value) {
|
|
@@ -55,16 +67,15 @@ const typemap = {
|
|
|
55
67
|
},
|
|
56
68
|
},
|
|
57
69
|
i8: number("TINYINT"),
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
},*/
|
|
70
|
+
json: {
|
|
71
|
+
bind(value) {
|
|
72
|
+
return JSON.stringify(value);
|
|
73
|
+
},
|
|
74
|
+
column: "JSON",
|
|
75
|
+
unbind(value) {
|
|
76
|
+
return JSON.parse(value);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
68
79
|
string: identity("TEXT"),
|
|
69
80
|
time: identity("TEXT"),
|
|
70
81
|
u128: {
|
|
@@ -97,6 +108,21 @@ const typemap = {
|
|
|
97
108
|
return new URL(value);
|
|
98
109
|
},
|
|
99
110
|
},
|
|
111
|
+
uuid: {
|
|
112
|
+
bind: uuid_to_bin,
|
|
113
|
+
column: "BINARY(16)",
|
|
114
|
+
unbind: bin_to_uuid,
|
|
115
|
+
},
|
|
116
|
+
uuid_v4: {
|
|
117
|
+
bind: uuid_to_bin,
|
|
118
|
+
column: "BINARY(16)",
|
|
119
|
+
unbind: bin_to_uuid,
|
|
120
|
+
},
|
|
121
|
+
uuid_v7: {
|
|
122
|
+
bind: uuid_to_bin,
|
|
123
|
+
column: "BINARY(16)",
|
|
124
|
+
unbind: bin_to_uuid,
|
|
125
|
+
},
|
|
100
126
|
};
|
|
101
127
|
export default typemap;
|
|
102
128
|
//# sourceMappingURL=typemap.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/mysql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "MySQL databases for Primate",
|
|
5
5
|
"homepage": "https://primate.run/docs/database/mysql",
|
|
6
6
|
"bugs": "https://github.com/primate-run/primate/issues",
|
|
@@ -17,27 +17,27 @@
|
|
|
17
17
|
"!/**/*.spec.*"
|
|
18
18
|
],
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@rcompat/type": "^0.
|
|
20
|
+
"@rcompat/type": "^0.11.1"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@rcompat/assert": "^0.
|
|
24
|
-
"@rcompat/is": "^0.
|
|
25
|
-
"mysql2": "^3.
|
|
26
|
-
"@primate/core": "^0.
|
|
27
|
-
"pema": "0.
|
|
23
|
+
"@rcompat/assert": "^0.8.1",
|
|
24
|
+
"@rcompat/is": "^0.6.1",
|
|
25
|
+
"mysql2": "^3.20.0",
|
|
26
|
+
"@primate/core": "^0.6.0",
|
|
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,6 +45,6 @@
|
|
|
45
45
|
"build": "npm run clean && tsc",
|
|
46
46
|
"clean": "rm -rf ./lib",
|
|
47
47
|
"lint": "eslint .",
|
|
48
|
-
"test": "
|
|
48
|
+
"test": "npx proby"
|
|
49
49
|
}
|
|
50
50
|
}
|