@zmdb/mysql 1.0.0-beta.1
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 +6 -0
- package/README.md +111 -0
- package/dist/dialect.d.ts +2 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +91 -0
- package/dist/dialect.js.map +1 -0
- package/dist/driver.d.ts +48 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +125 -0
- package/dist/driver.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect.d.ts +11 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +306 -0
- package/dist/introspect.js.map +1 -0
- package/dist/migrations.d.ts +25 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +471 -0
- package/dist/migrations.js.map +1 -0
- package/package.json +59 -0
- package/src/__fixtures__/mysql-8.4.11.json +261 -0
- package/src/dialect.ts +99 -0
- package/src/driver.ts +219 -0
- package/src/index.ts +35 -0
- package/src/introspect.ts +430 -0
- package/src/migrations.ts +637 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provenance": {
|
|
3
|
+
"capturedOn": "2026-09-04",
|
|
4
|
+
"captureFormat": "filtered deterministic subset of mysql --batch --raw capture; every retained non-NULL catalog cell is text",
|
|
5
|
+
"image": "docker.io/library/mysql:8.4",
|
|
6
|
+
"imageCreated": "2026-07-27T22:08:26.634911873Z",
|
|
7
|
+
"imageDigest": "sha256:1d6b6a8fcee8ff758ff151d017f5203cd06792a0e698f0a593c9dfcb14609cf0",
|
|
8
|
+
"imageId": "bced325a4ab7aec848f4688371c7433351dcb5dba26fbcc29c67727d898ae5cb",
|
|
9
|
+
"schema": "zmdb430",
|
|
10
|
+
"server": {
|
|
11
|
+
"version": "8.4.11",
|
|
12
|
+
"versionComment": "MySQL Community Server - GPL",
|
|
13
|
+
"versionCompileMachine": "x86_64",
|
|
14
|
+
"versionCompileOs": "Linux"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"rows": {
|
|
18
|
+
"tables": [
|
|
19
|
+
{
|
|
20
|
+
"TABLE_SCHEMA": "zmdb430",
|
|
21
|
+
"TABLE_NAME": "accounts",
|
|
22
|
+
"TABLE_TYPE": "BASE TABLE",
|
|
23
|
+
"ENGINE": "InnoDB"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"TABLE_SCHEMA": "zmdb430",
|
|
27
|
+
"TABLE_NAME": "memberships",
|
|
28
|
+
"TABLE_TYPE": "BASE TABLE",
|
|
29
|
+
"ENGINE": "InnoDB"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"TABLE_SCHEMA": "zmdb430",
|
|
33
|
+
"TABLE_NAME": "posts",
|
|
34
|
+
"TABLE_TYPE": "BASE TABLE",
|
|
35
|
+
"ENGINE": "InnoDB"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"columns": [
|
|
39
|
+
{
|
|
40
|
+
"TABLE_NAME": "accounts",
|
|
41
|
+
"ORDINAL_POSITION": "1",
|
|
42
|
+
"COLUMN_NAME": "id",
|
|
43
|
+
"IS_NULLABLE": "NO",
|
|
44
|
+
"DATA_TYPE": "bigint",
|
|
45
|
+
"COLUMN_TYPE": "bigint",
|
|
46
|
+
"CHARACTER_MAXIMUM_LENGTH": null,
|
|
47
|
+
"NUMERIC_PRECISION": "19",
|
|
48
|
+
"NUMERIC_SCALE": "0",
|
|
49
|
+
"COLUMN_DEFAULT": null,
|
|
50
|
+
"EXTRA": "auto_increment",
|
|
51
|
+
"GENERATION_EXPRESSION": ""
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"TABLE_NAME": "accounts",
|
|
55
|
+
"ORDINAL_POSITION": "2",
|
|
56
|
+
"COLUMN_NAME": "email",
|
|
57
|
+
"IS_NULLABLE": "NO",
|
|
58
|
+
"DATA_TYPE": "varchar",
|
|
59
|
+
"COLUMN_TYPE": "varchar(255)",
|
|
60
|
+
"CHARACTER_MAXIMUM_LENGTH": "255",
|
|
61
|
+
"NUMERIC_PRECISION": null,
|
|
62
|
+
"NUMERIC_SCALE": null,
|
|
63
|
+
"COLUMN_DEFAULT": null,
|
|
64
|
+
"EXTRA": "",
|
|
65
|
+
"GENERATION_EXPRESSION": ""
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"TABLE_NAME": "memberships",
|
|
69
|
+
"ORDINAL_POSITION": "1",
|
|
70
|
+
"COLUMN_NAME": "tenant_id",
|
|
71
|
+
"IS_NULLABLE": "NO",
|
|
72
|
+
"DATA_TYPE": "int",
|
|
73
|
+
"COLUMN_TYPE": "int",
|
|
74
|
+
"CHARACTER_MAXIMUM_LENGTH": null,
|
|
75
|
+
"NUMERIC_PRECISION": "10",
|
|
76
|
+
"NUMERIC_SCALE": "0",
|
|
77
|
+
"COLUMN_DEFAULT": null,
|
|
78
|
+
"EXTRA": "",
|
|
79
|
+
"GENERATION_EXPRESSION": ""
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"TABLE_NAME": "memberships",
|
|
83
|
+
"ORDINAL_POSITION": "2",
|
|
84
|
+
"COLUMN_NAME": "user_id",
|
|
85
|
+
"IS_NULLABLE": "NO",
|
|
86
|
+
"DATA_TYPE": "int",
|
|
87
|
+
"COLUMN_TYPE": "int",
|
|
88
|
+
"CHARACTER_MAXIMUM_LENGTH": null,
|
|
89
|
+
"NUMERIC_PRECISION": "10",
|
|
90
|
+
"NUMERIC_SCALE": "0",
|
|
91
|
+
"COLUMN_DEFAULT": null,
|
|
92
|
+
"EXTRA": "",
|
|
93
|
+
"GENERATION_EXPRESSION": ""
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"TABLE_NAME": "posts",
|
|
97
|
+
"ORDINAL_POSITION": "1",
|
|
98
|
+
"COLUMN_NAME": "id",
|
|
99
|
+
"IS_NULLABLE": "NO",
|
|
100
|
+
"DATA_TYPE": "int",
|
|
101
|
+
"COLUMN_TYPE": "int",
|
|
102
|
+
"CHARACTER_MAXIMUM_LENGTH": null,
|
|
103
|
+
"NUMERIC_PRECISION": "10",
|
|
104
|
+
"NUMERIC_SCALE": "0",
|
|
105
|
+
"COLUMN_DEFAULT": null,
|
|
106
|
+
"EXTRA": "auto_increment",
|
|
107
|
+
"GENERATION_EXPRESSION": ""
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"TABLE_NAME": "posts",
|
|
111
|
+
"ORDINAL_POSITION": "2",
|
|
112
|
+
"COLUMN_NAME": "account_id",
|
|
113
|
+
"IS_NULLABLE": "NO",
|
|
114
|
+
"DATA_TYPE": "bigint",
|
|
115
|
+
"COLUMN_TYPE": "bigint",
|
|
116
|
+
"CHARACTER_MAXIMUM_LENGTH": null,
|
|
117
|
+
"NUMERIC_PRECISION": "19",
|
|
118
|
+
"NUMERIC_SCALE": "0",
|
|
119
|
+
"COLUMN_DEFAULT": null,
|
|
120
|
+
"EXTRA": "",
|
|
121
|
+
"GENERATION_EXPRESSION": ""
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"TABLE_NAME": "posts",
|
|
125
|
+
"ORDINAL_POSITION": "3",
|
|
126
|
+
"COLUMN_NAME": "slug",
|
|
127
|
+
"IS_NULLABLE": "NO",
|
|
128
|
+
"DATA_TYPE": "varchar",
|
|
129
|
+
"COLUMN_TYPE": "varchar(120)",
|
|
130
|
+
"CHARACTER_MAXIMUM_LENGTH": "120",
|
|
131
|
+
"NUMERIC_PRECISION": null,
|
|
132
|
+
"NUMERIC_SCALE": null,
|
|
133
|
+
"COLUMN_DEFAULT": null,
|
|
134
|
+
"EXTRA": "",
|
|
135
|
+
"GENERATION_EXPRESSION": ""
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
"statistics": [
|
|
139
|
+
{
|
|
140
|
+
"TABLE_NAME": "accounts",
|
|
141
|
+
"INDEX_NAME": "accounts_email_uq",
|
|
142
|
+
"NON_UNIQUE": "0",
|
|
143
|
+
"SEQ_IN_INDEX": "1",
|
|
144
|
+
"COLUMN_NAME": "email",
|
|
145
|
+
"EXPRESSION": null,
|
|
146
|
+
"INDEX_TYPE": "BTREE"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"TABLE_NAME": "accounts",
|
|
150
|
+
"INDEX_NAME": "PRIMARY",
|
|
151
|
+
"NON_UNIQUE": "0",
|
|
152
|
+
"SEQ_IN_INDEX": "1",
|
|
153
|
+
"COLUMN_NAME": "id",
|
|
154
|
+
"EXPRESSION": null,
|
|
155
|
+
"INDEX_TYPE": "BTREE"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"TABLE_NAME": "memberships",
|
|
159
|
+
"INDEX_NAME": "PRIMARY",
|
|
160
|
+
"NON_UNIQUE": "0",
|
|
161
|
+
"SEQ_IN_INDEX": "1",
|
|
162
|
+
"COLUMN_NAME": "user_id",
|
|
163
|
+
"EXPRESSION": null,
|
|
164
|
+
"INDEX_TYPE": "BTREE"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"TABLE_NAME": "memberships",
|
|
168
|
+
"INDEX_NAME": "PRIMARY",
|
|
169
|
+
"NON_UNIQUE": "0",
|
|
170
|
+
"SEQ_IN_INDEX": "2",
|
|
171
|
+
"COLUMN_NAME": "tenant_id",
|
|
172
|
+
"EXPRESSION": null,
|
|
173
|
+
"INDEX_TYPE": "BTREE"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"TABLE_NAME": "posts",
|
|
177
|
+
"INDEX_NAME": "posts_account_idx",
|
|
178
|
+
"NON_UNIQUE": "1",
|
|
179
|
+
"SEQ_IN_INDEX": "1",
|
|
180
|
+
"COLUMN_NAME": "account_id",
|
|
181
|
+
"EXPRESSION": null,
|
|
182
|
+
"INDEX_TYPE": "BTREE"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"TABLE_NAME": "posts",
|
|
186
|
+
"INDEX_NAME": "PRIMARY",
|
|
187
|
+
"NON_UNIQUE": "0",
|
|
188
|
+
"SEQ_IN_INDEX": "1",
|
|
189
|
+
"COLUMN_NAME": "id",
|
|
190
|
+
"EXPRESSION": null,
|
|
191
|
+
"INDEX_TYPE": "BTREE"
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"keyColumnUsage": [
|
|
195
|
+
{
|
|
196
|
+
"TABLE_NAME": "accounts",
|
|
197
|
+
"CONSTRAINT_NAME": "accounts_email_uq",
|
|
198
|
+
"ORDINAL_POSITION": "1",
|
|
199
|
+
"POSITION_IN_UNIQUE_CONSTRAINT": null,
|
|
200
|
+
"COLUMN_NAME": "email",
|
|
201
|
+
"REFERENCED_TABLE_NAME": null,
|
|
202
|
+
"REFERENCED_COLUMN_NAME": null
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"TABLE_NAME": "accounts",
|
|
206
|
+
"CONSTRAINT_NAME": "PRIMARY",
|
|
207
|
+
"ORDINAL_POSITION": "1",
|
|
208
|
+
"POSITION_IN_UNIQUE_CONSTRAINT": null,
|
|
209
|
+
"COLUMN_NAME": "id",
|
|
210
|
+
"REFERENCED_TABLE_NAME": null,
|
|
211
|
+
"REFERENCED_COLUMN_NAME": null
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"TABLE_NAME": "memberships",
|
|
215
|
+
"CONSTRAINT_NAME": "PRIMARY",
|
|
216
|
+
"ORDINAL_POSITION": "1",
|
|
217
|
+
"POSITION_IN_UNIQUE_CONSTRAINT": null,
|
|
218
|
+
"COLUMN_NAME": "user_id",
|
|
219
|
+
"REFERENCED_TABLE_NAME": null,
|
|
220
|
+
"REFERENCED_COLUMN_NAME": null
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"TABLE_NAME": "memberships",
|
|
224
|
+
"CONSTRAINT_NAME": "PRIMARY",
|
|
225
|
+
"ORDINAL_POSITION": "2",
|
|
226
|
+
"POSITION_IN_UNIQUE_CONSTRAINT": null,
|
|
227
|
+
"COLUMN_NAME": "tenant_id",
|
|
228
|
+
"REFERENCED_TABLE_NAME": null,
|
|
229
|
+
"REFERENCED_COLUMN_NAME": null
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"TABLE_NAME": "posts",
|
|
233
|
+
"CONSTRAINT_NAME": "posts_account_fkey",
|
|
234
|
+
"ORDINAL_POSITION": "1",
|
|
235
|
+
"POSITION_IN_UNIQUE_CONSTRAINT": "1",
|
|
236
|
+
"COLUMN_NAME": "account_id",
|
|
237
|
+
"REFERENCED_TABLE_NAME": "accounts",
|
|
238
|
+
"REFERENCED_COLUMN_NAME": "id"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
"TABLE_NAME": "posts",
|
|
242
|
+
"CONSTRAINT_NAME": "PRIMARY",
|
|
243
|
+
"ORDINAL_POSITION": "1",
|
|
244
|
+
"POSITION_IN_UNIQUE_CONSTRAINT": null,
|
|
245
|
+
"COLUMN_NAME": "id",
|
|
246
|
+
"REFERENCED_TABLE_NAME": null,
|
|
247
|
+
"REFERENCED_COLUMN_NAME": null
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
"referentialConstraints": [
|
|
251
|
+
{
|
|
252
|
+
"TABLE_NAME": "posts",
|
|
253
|
+
"CONSTRAINT_NAME": "posts_account_fkey",
|
|
254
|
+
"UNIQUE_CONSTRAINT_NAME": "PRIMARY",
|
|
255
|
+
"MATCH_OPTION": "NONE",
|
|
256
|
+
"UPDATE_RULE": "RESTRICT",
|
|
257
|
+
"DELETE_RULE": "CASCADE"
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
}
|
package/src/dialect.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineSqlDialect,
|
|
3
|
+
type DatabaseCapabilities,
|
|
4
|
+
type PaginationTail,
|
|
5
|
+
type ResolvedDialectTraits,
|
|
6
|
+
} from '@zmdb/sql';
|
|
7
|
+
|
|
8
|
+
import { mysqlIntrospector } from './introspect.js';
|
|
9
|
+
import { createMysqlMigrations } from './migrations.js';
|
|
10
|
+
|
|
11
|
+
const OPERATOR_TOKEN = /^(?!.*--)[A-Za-z@<>=!~*&|?-]{1,4}$/;
|
|
12
|
+
|
|
13
|
+
function acceptsOperator(operator: string): boolean {
|
|
14
|
+
if (operator === '#>' || operator === '#>>') return false;
|
|
15
|
+
return OPERATOR_TOKEN.test(operator) && !operator.includes('?');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function paginate({ limit, offset }: PaginationTail): string {
|
|
19
|
+
if (limit === undefined && offset !== undefined) {
|
|
20
|
+
return ` LIMIT 18446744073709551615 OFFSET ${String(offset)}`;
|
|
21
|
+
}
|
|
22
|
+
let text = '';
|
|
23
|
+
if (limit !== undefined) text += ` LIMIT ${String(limit)}`;
|
|
24
|
+
if (offset !== undefined) text += ` OFFSET ${String(offset)}`;
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const traits: ResolvedDialectTraits = {
|
|
29
|
+
placeholder: 'positional',
|
|
30
|
+
quote: Object.freeze(['`', '`']),
|
|
31
|
+
paginate,
|
|
32
|
+
paginationRequiresOrder: false,
|
|
33
|
+
rowValueIn: true,
|
|
34
|
+
returning: Object.freeze({
|
|
35
|
+
insert: 'none',
|
|
36
|
+
upsert: 'none',
|
|
37
|
+
update: 'none',
|
|
38
|
+
delete: 'none',
|
|
39
|
+
}),
|
|
40
|
+
upsert: 'onDuplicateKey',
|
|
41
|
+
fts: 'match',
|
|
42
|
+
concat: 'function',
|
|
43
|
+
booleanNot: 'not',
|
|
44
|
+
types: Object.freeze({
|
|
45
|
+
serial: 'INT AUTO_INCREMENT',
|
|
46
|
+
integer: 'INT',
|
|
47
|
+
bigint: 'BIGINT',
|
|
48
|
+
numeric: 'DECIMAL',
|
|
49
|
+
text: 'TEXT',
|
|
50
|
+
varchar: 'VARCHAR',
|
|
51
|
+
boolean: 'TINYINT(1)',
|
|
52
|
+
timestamp: 'DATETIME(3)',
|
|
53
|
+
json: 'JSON',
|
|
54
|
+
jsonEnum: 'TEXT',
|
|
55
|
+
}),
|
|
56
|
+
paramLimit: 60000,
|
|
57
|
+
retryableCodes: Object.freeze([]),
|
|
58
|
+
acceptsOperator,
|
|
59
|
+
functions: true,
|
|
60
|
+
procedures: true,
|
|
61
|
+
tableFunctions: false,
|
|
62
|
+
vectorDistance: false,
|
|
63
|
+
spatialPredicates: false,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const capabilities: DatabaseCapabilities = {
|
|
67
|
+
returning: Object.freeze({
|
|
68
|
+
insert: false,
|
|
69
|
+
upsert: false,
|
|
70
|
+
update: false,
|
|
71
|
+
delete: false,
|
|
72
|
+
}),
|
|
73
|
+
transactionalDdl: false,
|
|
74
|
+
schemas: true,
|
|
75
|
+
sequences: false,
|
|
76
|
+
generatedColumns: true,
|
|
77
|
+
partialIndexes: false,
|
|
78
|
+
foreignKeys: true,
|
|
79
|
+
rowLevelSecurity: false,
|
|
80
|
+
streaming: false,
|
|
81
|
+
cancellation: false,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const mysql = defineSqlDialect({
|
|
85
|
+
name: 'mysql',
|
|
86
|
+
family: 'mysql',
|
|
87
|
+
telemetrySystem: 'mysql',
|
|
88
|
+
traits,
|
|
89
|
+
capabilities,
|
|
90
|
+
migrations: createMysqlMigrations('mysql'),
|
|
91
|
+
introspector: mysqlIntrospector,
|
|
92
|
+
outbox: Object.freeze({
|
|
93
|
+
createTable: 'CREATE TABLE',
|
|
94
|
+
pendingIndex: 'full',
|
|
95
|
+
epochLiteral: "'1970-01-01 00:00:00.000'",
|
|
96
|
+
createdAtDefault: 'CURRENT_TIMESTAMP(3)',
|
|
97
|
+
boundedTextType: (length: number) => `VARCHAR(${String(length)})`,
|
|
98
|
+
}),
|
|
99
|
+
});
|
package/src/driver.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { type ExecuteOptions, type SelectedDriver, type TransactionalDriver } from '@zmdb/orm';
|
|
2
|
+
import { type CompiledQuery, type SqlDialect } from '@zmdb/sql';
|
|
3
|
+
|
|
4
|
+
export interface MysqlResultHeader {
|
|
5
|
+
readonly affectedRows: number;
|
|
6
|
+
readonly insertId: number | string | bigint;
|
|
7
|
+
readonly warningStatus?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type MysqlQueryResult = readonly Readonly<Record<string, unknown>>[] | MysqlResultHeader;
|
|
11
|
+
|
|
12
|
+
export type MysqlParameter =
|
|
13
|
+
| string
|
|
14
|
+
| number
|
|
15
|
+
| bigint
|
|
16
|
+
| boolean
|
|
17
|
+
| Date
|
|
18
|
+
| null
|
|
19
|
+
| Uint8Array
|
|
20
|
+
| MysqlParameter[]
|
|
21
|
+
| { readonly [key: string]: MysqlParameter };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The mysql2/promise surface used by the adapter. It is deliberately structural:
|
|
25
|
+
* importing @zmdb/mysql does not resolve or load mysql2.
|
|
26
|
+
*/
|
|
27
|
+
export interface MysqlQueryable {
|
|
28
|
+
execute(sql: string, values?: MysqlParameter[]): Promise<readonly [unknown, readonly unknown[]]>;
|
|
29
|
+
/** Optional mysql2 text protocol for metadata statements that the prepared protocol rejects. */
|
|
30
|
+
query?(sql: string, values?: MysqlParameter[]): Promise<readonly [unknown, readonly unknown[]]>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MysqlConnection extends MysqlQueryable {
|
|
34
|
+
beginTransaction(): Promise<unknown>;
|
|
35
|
+
commit(): Promise<unknown>;
|
|
36
|
+
rollback(): Promise<unknown>;
|
|
37
|
+
release?(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MysqlPool extends MysqlQueryable {
|
|
41
|
+
getConnection(): Promise<MysqlConnection>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MysqlOptions {
|
|
45
|
+
/** Optional application-to-wire conversion applied before mysql2 receives a value. */
|
|
46
|
+
readonly mapParameter?: (value: unknown, index: number) => MysqlParameter;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type MysqlExecutionResult =
|
|
50
|
+
| {
|
|
51
|
+
readonly kind: 'rows';
|
|
52
|
+
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
readonly kind: 'command';
|
|
56
|
+
readonly affectedRows: number;
|
|
57
|
+
readonly insertId: number | string | bigint;
|
|
58
|
+
readonly warningStatus?: number;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export interface MysqlDriver<Name extends string = 'mysql'> extends TransactionalDriver<Name> {
|
|
62
|
+
readonly dialect: SqlDialect<Name>;
|
|
63
|
+
executeResult(query: CompiledQuery, options?: ExecuteOptions): Promise<MysqlExecutionResult>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isPool(client: MysqlQueryable): client is MysqlPool {
|
|
67
|
+
return typeof Reflect.get(client, 'getConnection') === 'function';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isConnection(client: MysqlQueryable): client is MysqlConnection {
|
|
71
|
+
return (
|
|
72
|
+
typeof Reflect.get(client, 'beginTransaction') === 'function' &&
|
|
73
|
+
typeof Reflect.get(client, 'commit') === 'function' &&
|
|
74
|
+
typeof Reflect.get(client, 'rollback') === 'function'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resultHeader(value: unknown): MysqlResultHeader {
|
|
79
|
+
if (value === null || typeof value !== 'object') {
|
|
80
|
+
throw new TypeError('mysql2 returned neither rows nor a result header');
|
|
81
|
+
}
|
|
82
|
+
const affectedRows = Reflect.get(value, 'affectedRows');
|
|
83
|
+
const insertId = Reflect.get(value, 'insertId');
|
|
84
|
+
const warningStatus = Reflect.get(value, 'warningStatus');
|
|
85
|
+
if (typeof affectedRows !== 'number' || !Number.isSafeInteger(affectedRows) || affectedRows < 0) {
|
|
86
|
+
throw new TypeError('mysql2 result header affectedRows must be a non-negative safe integer');
|
|
87
|
+
}
|
|
88
|
+
if (
|
|
89
|
+
(typeof insertId !== 'number' || !Number.isSafeInteger(insertId)) &&
|
|
90
|
+
typeof insertId !== 'string' &&
|
|
91
|
+
typeof insertId !== 'bigint'
|
|
92
|
+
) {
|
|
93
|
+
throw new TypeError('mysql2 result header insertId must be a safe integer, string, or bigint');
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
warningStatus !== undefined &&
|
|
97
|
+
(typeof warningStatus !== 'number' || !Number.isSafeInteger(warningStatus) || warningStatus < 0)
|
|
98
|
+
) {
|
|
99
|
+
throw new TypeError('mysql2 result header warningStatus must be a non-negative safe integer');
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
affectedRows,
|
|
103
|
+
insertId,
|
|
104
|
+
...(warningStatus === undefined ? {} : { warningStatus }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isPlainRecord(value: object): value is Readonly<Record<string, unknown>> {
|
|
109
|
+
const prototype = Object.getPrototypeOf(value);
|
|
110
|
+
return prototype === Object.prototype || prototype === null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function mysqlParameter(value: unknown, label: string): MysqlParameter {
|
|
114
|
+
if (
|
|
115
|
+
value === null ||
|
|
116
|
+
typeof value === 'string' ||
|
|
117
|
+
typeof value === 'number' ||
|
|
118
|
+
typeof value === 'bigint' ||
|
|
119
|
+
typeof value === 'boolean' ||
|
|
120
|
+
value instanceof Date ||
|
|
121
|
+
value instanceof Uint8Array
|
|
122
|
+
) {
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
if (Array.isArray(value)) {
|
|
126
|
+
return value.map((entry, index) => mysqlParameter(entry, `${label}[${String(index)}]`));
|
|
127
|
+
}
|
|
128
|
+
if (typeof value === 'object' && isPlainRecord(value)) {
|
|
129
|
+
return Object.fromEntries(
|
|
130
|
+
Object.entries(value).map(([key, entry]) => [key, mysqlParameter(entry, `${label}.${key}`)]),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
throw new TypeError(`${label} is not a mysql2 execute parameter`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function mapParameters(query: CompiledQuery, options: MysqlOptions | undefined): MysqlParameter[] {
|
|
137
|
+
const map = options?.mapParameter;
|
|
138
|
+
return query.parameters.map((value, index) =>
|
|
139
|
+
map === undefined ? mysqlParameter(value, `query parameter ${String(index + 1)}`) : map(value, index),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function runTransaction<Name extends string, Result>(
|
|
144
|
+
dialect: SqlDialect<Name>,
|
|
145
|
+
connection: MysqlConnection,
|
|
146
|
+
options: MysqlOptions | undefined,
|
|
147
|
+
run: (driver: SelectedDriver<Name>) => Promise<Result>,
|
|
148
|
+
): Promise<Result> {
|
|
149
|
+
await connection.beginTransaction();
|
|
150
|
+
const transactionDriver = createMysqlDriver(dialect, connection, options, true);
|
|
151
|
+
try {
|
|
152
|
+
const result = await run(transactionDriver);
|
|
153
|
+
await connection.commit();
|
|
154
|
+
return result;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
await connection.rollback();
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createMysqlDriver<Name extends string>(
|
|
162
|
+
dialect: SqlDialect<Name>,
|
|
163
|
+
client: MysqlQueryable,
|
|
164
|
+
options: MysqlOptions | undefined,
|
|
165
|
+
pinned: boolean,
|
|
166
|
+
): MysqlDriver<Name> {
|
|
167
|
+
const driver: MysqlDriver<Name> = {
|
|
168
|
+
dialect,
|
|
169
|
+
async executeResult(query, executeOptions) {
|
|
170
|
+
const signal = executeOptions?.signal;
|
|
171
|
+
signal?.throwIfAborted();
|
|
172
|
+
const parameters = mapParameters(query, options);
|
|
173
|
+
const textQuery = /^\s*(?:SHOW|DESCRIBE)\b/iu.test(query.text) ? client.query : undefined;
|
|
174
|
+
const [result] =
|
|
175
|
+
textQuery === undefined
|
|
176
|
+
? await client.execute(query.text, parameters)
|
|
177
|
+
: await textQuery.call(client, query.text, parameters);
|
|
178
|
+
signal?.throwIfAborted();
|
|
179
|
+
if (Array.isArray(result)) {
|
|
180
|
+
if (result.some(row => row === null || typeof row !== 'object' || Array.isArray(row))) {
|
|
181
|
+
throw new TypeError('mysql2 row results must contain objects');
|
|
182
|
+
}
|
|
183
|
+
return { kind: 'rows', rows: result };
|
|
184
|
+
}
|
|
185
|
+
return { kind: 'command', ...resultHeader(result) };
|
|
186
|
+
},
|
|
187
|
+
async execute(query, executeOptions) {
|
|
188
|
+
const result = await driver.executeResult(query, executeOptions);
|
|
189
|
+
return result.kind === 'rows' ? result.rows : [];
|
|
190
|
+
},
|
|
191
|
+
async transaction<Result>(run: (transactionDriver: SelectedDriver<Name>) => Promise<Result>): Promise<Result> {
|
|
192
|
+
if (pinned) {
|
|
193
|
+
if (!isConnection(client)) throw new TypeError('a pinned mysql transaction lost its connection');
|
|
194
|
+
return runTransaction(dialect, client, options, run);
|
|
195
|
+
}
|
|
196
|
+
if (isPool(client)) {
|
|
197
|
+
const connection = await client.getConnection();
|
|
198
|
+
try {
|
|
199
|
+
return await runTransaction(dialect, connection, options, run);
|
|
200
|
+
} finally {
|
|
201
|
+
connection.release?.();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!isConnection(client)) {
|
|
205
|
+
throw new TypeError('mysqlDriver.transaction requires a mysql2 pool or connection');
|
|
206
|
+
}
|
|
207
|
+
return runTransaction(dialect, client, options, run);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
return driver;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function mysqlFamilyDriver<Name extends string>(
|
|
214
|
+
dialect: SqlDialect<Name>,
|
|
215
|
+
client: MysqlQueryable,
|
|
216
|
+
options?: MysqlOptions,
|
|
217
|
+
): MysqlDriver<Name> {
|
|
218
|
+
return createMysqlDriver(dialect, client, options, false);
|
|
219
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type DatabaseVertical } from '@zmdb/orm';
|
|
2
|
+
|
|
3
|
+
import { mysql } from './dialect.js';
|
|
4
|
+
import { mysqlFamilyDriver, type MysqlDriver, type MysqlOptions, type MysqlQueryable } from './driver.js';
|
|
5
|
+
|
|
6
|
+
export { mysql } from './dialect.js';
|
|
7
|
+
export {
|
|
8
|
+
mysqlFamilyDriver,
|
|
9
|
+
type MysqlConnection,
|
|
10
|
+
type MysqlDriver,
|
|
11
|
+
type MysqlExecutionResult,
|
|
12
|
+
type MysqlOptions,
|
|
13
|
+
type MysqlParameter,
|
|
14
|
+
type MysqlPool,
|
|
15
|
+
type MysqlQueryable,
|
|
16
|
+
type MysqlQueryResult,
|
|
17
|
+
type MysqlResultHeader,
|
|
18
|
+
} from './driver.js';
|
|
19
|
+
export { mysqlFamilyIntrospector, mysqlIntrospector, mysqlSnapshot, type MysqlCatalogOverrides } from './introspect.js';
|
|
20
|
+
export {
|
|
21
|
+
createMysqlMigrations,
|
|
22
|
+
mysqlFamilyMigrations,
|
|
23
|
+
type MysqlMigrationOverrides,
|
|
24
|
+
type MysqlTableDdlExtension,
|
|
25
|
+
type MysqlTableDdlHelpers,
|
|
26
|
+
} from './migrations.js';
|
|
27
|
+
|
|
28
|
+
export function mysqlDriver(client: MysqlQueryable, options?: MysqlOptions): MysqlDriver<'mysql'> {
|
|
29
|
+
return mysqlFamilyDriver(mysql, client, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const mysqlVertical: DatabaseVertical<'mysql', MysqlQueryable, MysqlOptions> = Object.freeze({
|
|
33
|
+
dialect: mysql,
|
|
34
|
+
driver: mysqlDriver,
|
|
35
|
+
});
|