@jskit-ai/database-runtime-mysql 0.1.31 → 0.1.33

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/database-runtime-mysql",
4
- version: "0.1.31",
4
+ version: "0.1.33",
5
5
  kind: "runtime",
6
6
  options: {
7
7
  "db-host": {
@@ -91,7 +91,7 @@ export default Object.freeze({
91
91
  mutations: {
92
92
  dependencies: {
93
93
  runtime: {
94
- "@jskit-ai/database-runtime": "0.1.32",
94
+ "@jskit-ai/database-runtime": "0.1.34",
95
95
  "mysql2": "^3.11.2"
96
96
  },
97
97
  dev: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/database-runtime-mysql",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -12,6 +12,6 @@
12
12
  "./shared/dialect": "./src/shared/dialect.js"
13
13
  },
14
14
  "dependencies": {
15
- "@jskit-ai/database-runtime": "0.1.32"
15
+ "@jskit-ai/database-runtime": "0.1.34"
16
16
  }
17
17
  }
@@ -201,6 +201,8 @@ function normalizeColumn(row = {}) {
201
201
  numericPrecision: toNullableNumber(row.numericPrecision ?? row.numeric_precision),
202
202
  numericScale: toNullableNumber(row.numericScale ?? row.numeric_scale),
203
203
  datetimePrecision: toNullableNumber(row.datetimePrecision ?? row.datetime_precision),
204
+ characterSetName: normalizeText(row.characterSetName ?? row.character_set_name),
205
+ collationName: normalizeText(row.collationName ?? row.collation_name),
204
206
  ordinalPosition: toNullableNumber(row.ordinalPosition ?? row.ordinal_position),
205
207
  enumValues
206
208
  });
@@ -228,9 +230,11 @@ function normalizeIndexes(rows = []) {
228
230
 
229
231
  const seqInIndex = toNullableNumber(row.seqInIndex ?? row.seq_in_index) || 0;
230
232
  const nonUnique = toBoolean(row.nonUnique ?? row.non_unique);
233
+ const indexType = normalizeText(row.indexType || row.index_type).toUpperCase();
231
234
  const existing = byName.get(indexName) || {
232
235
  name: indexName,
233
236
  unique: !nonUnique,
237
+ indexType,
234
238
  columns: []
235
239
  };
236
240
  existing.columns.push({
@@ -246,6 +250,7 @@ function normalizeIndexes(rows = []) {
246
250
  Object.freeze({
247
251
  name: index.name,
248
252
  unique: index.unique,
253
+ indexType: index.indexType,
249
254
  columns: Object.freeze(
250
255
  index.columns
251
256
  .sort((left, right) => left.order - right.order)
@@ -312,6 +317,26 @@ function normalizeForeignKeys(rows = []) {
312
317
  );
313
318
  }
314
319
 
320
+ function normalizeCheckConstraints(rows = []) {
321
+ return Object.freeze(
322
+ (Array.isArray(rows) ? rows : [])
323
+ .map((row) => {
324
+ const name = normalizeText(row.constraintName || row.constraint_name);
325
+ const clause = normalizeText(row.checkClause || row.check_clause);
326
+ if (!name || !clause) {
327
+ return null;
328
+ }
329
+
330
+ return Object.freeze({
331
+ name,
332
+ clause
333
+ });
334
+ })
335
+ .filter(Boolean)
336
+ .sort((left, right) => left.name.localeCompare(right.name))
337
+ );
338
+ }
339
+
315
340
  function requireIdColumn(columns, idColumn) {
316
341
  const normalizedIdColumn = normalizeText(idColumn) || "id";
317
342
  const idSpec = columns.find((column) => column.name === normalizedIdColumn) || null;
@@ -351,6 +376,20 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
351
376
  const schemaRows = normalizeRows(await knex.raw("SELECT DATABASE() AS schemaName"));
352
377
  const schemaName = normalizeDbSchemaName(schemaRows);
353
378
 
379
+ const tableRows = normalizeRows(
380
+ await knex.raw(
381
+ `
382
+ SELECT
383
+ t.table_collation AS tableCollation
384
+ FROM information_schema.tables t
385
+ WHERE t.table_schema = ?
386
+ AND t.table_name = ?
387
+ LIMIT 1
388
+ `,
389
+ [schemaName, resolvedTableName]
390
+ )
391
+ );
392
+
354
393
  const columnRows = normalizeRows(
355
394
  await knex.raw(
356
395
  `
@@ -362,6 +401,8 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
362
401
  c.column_default AS columnDefault,
363
402
  c.extra AS extra,
364
403
  c.character_maximum_length AS characterMaximumLength,
404
+ c.character_set_name AS characterSetName,
405
+ c.collation_name AS collationName,
365
406
  c.numeric_precision AS numericPrecision,
366
407
  c.numeric_scale AS numericScale,
367
408
  c.datetime_precision AS datetimePrecision,
@@ -404,6 +445,7 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
404
445
  SELECT
405
446
  s.index_name AS indexName,
406
447
  s.non_unique AS nonUnique,
448
+ s.index_type AS indexType,
407
449
  s.column_name AS columnName,
408
450
  s.seq_in_index AS seqInIndex
409
451
  FROM information_schema.statistics s
@@ -440,22 +482,44 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
440
482
  )
441
483
  );
442
484
 
485
+ const checkConstraintRows = normalizeRows(
486
+ await knex.raw(
487
+ `
488
+ SELECT
489
+ tc.constraint_name AS constraintName,
490
+ cc.check_clause AS checkClause
491
+ FROM information_schema.table_constraints tc
492
+ JOIN information_schema.check_constraints cc
493
+ ON cc.constraint_schema = tc.constraint_schema
494
+ AND cc.constraint_name = tc.constraint_name
495
+ WHERE tc.table_schema = ?
496
+ AND tc.table_name = ?
497
+ AND tc.constraint_type = 'CHECK'
498
+ ORDER BY tc.constraint_name ASC
499
+ `,
500
+ [schemaName, resolvedTableName]
501
+ )
502
+ );
503
+
443
504
  const columns = Object.freeze(columnRows.map((row) => normalizeColumn(row)));
444
505
  const resolvedIdColumn = requireIdColumn(columns, idColumn);
445
506
  const primaryKeyColumns = normalizePrimaryKeyColumns(primaryRows);
446
507
  requirePrimaryKeyContainsId(primaryKeyColumns, resolvedIdColumn);
508
+ const firstTableRow = Array.isArray(tableRows) ? tableRows[0] : null;
447
509
 
448
510
  const snapshot = Object.freeze({
449
511
  dialect: "mysql2",
450
512
  schemaName,
451
513
  tableName: resolvedTableName,
514
+ tableCollation: normalizeText(firstTableRow?.tableCollation || firstTableRow?.table_collation),
452
515
  idColumn: resolvedIdColumn,
453
516
  primaryKeyColumns,
454
- hasWorkspaceOwnerColumn: columns.some((column) => column.name === "workspace_owner_id"),
455
- hasUserOwnerColumn: columns.some((column) => column.name === "user_owner_id"),
517
+ hasWorkspaceIdColumn: columns.some((column) => column.name === "workspace_id"),
518
+ hasUserIdColumn: columns.some((column) => column.name === "user_id"),
456
519
  columns,
457
520
  indexes: normalizeIndexes(indexRows),
458
- foreignKeys: normalizeForeignKeys(foreignKeyRows)
521
+ foreignKeys: normalizeForeignKeys(foreignKeyRows),
522
+ checkConstraints: normalizeCheckConstraints(checkConstraintRows)
459
523
  });
460
524
 
461
525
  return snapshot;
@@ -5,10 +5,12 @@ import { introspectCrudTableSnapshot } from "../src/shared/introspectCrudTable.j
5
5
 
6
6
  function createKnexRawDouble({
7
7
  schemaName = "appdb",
8
+ tableCollation = "utf8mb4_general_ci",
8
9
  columns = [],
9
10
  primaryKeyColumns = [],
10
11
  indexes = [],
11
- foreignKeys = []
12
+ foreignKeys = [],
13
+ checkConstraints = []
12
14
  } = {}) {
13
15
  const calls = [];
14
16
 
@@ -23,9 +25,15 @@ function createKnexRawDouble({
23
25
  if (normalizedSql.includes("select database() as schemaname")) {
24
26
  return [[{ schemaName }], []];
25
27
  }
28
+ if (normalizedSql.includes("from information_schema.tables")) {
29
+ return [[{ tableCollation }], []];
30
+ }
26
31
  if (normalizedSql.includes("from information_schema.columns")) {
27
32
  return [[...columns], []];
28
33
  }
34
+ if (normalizedSql.includes("information_schema.check_constraints")) {
35
+ return [[...checkConstraints], []];
36
+ }
29
37
  if (normalizedSql.includes("from information_schema.table_constraints")) {
30
38
  return [[...primaryKeyColumns], []];
31
39
  }
@@ -57,32 +65,38 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
57
65
  columnDefault: null,
58
66
  extra: "auto_increment",
59
67
  characterMaximumLength: null,
68
+ characterSetName: null,
69
+ collationName: null,
60
70
  numericPrecision: 10,
61
71
  numericScale: 0,
62
72
  datetimePrecision: null,
63
73
  ordinalPosition: 1
64
74
  },
65
75
  {
66
- columnName: "workspace_owner_id",
76
+ columnName: "workspace_id",
67
77
  dataType: "int",
68
78
  columnType: "int unsigned",
69
79
  isNullable: "YES",
70
80
  columnDefault: "NULL",
71
81
  extra: "",
72
82
  characterMaximumLength: null,
83
+ characterSetName: null,
84
+ collationName: null,
73
85
  numericPrecision: 10,
74
86
  numericScale: 0,
75
87
  datetimePrecision: null,
76
88
  ordinalPosition: 2
77
89
  },
78
90
  {
79
- columnName: "user_owner_id",
91
+ columnName: "user_id",
80
92
  dataType: "int",
81
93
  columnType: "int unsigned",
82
94
  isNullable: "YES",
83
95
  columnDefault: "NULL",
84
96
  extra: "",
85
97
  characterMaximumLength: null,
98
+ characterSetName: null,
99
+ collationName: null,
86
100
  numericPrecision: 10,
87
101
  numericScale: 0,
88
102
  datetimePrecision: null,
@@ -96,6 +110,8 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
96
110
  columnDefault: null,
97
111
  extra: "",
98
112
  characterMaximumLength: 160,
113
+ characterSetName: "utf8mb4",
114
+ collationName: "utf8mb4_general_ci",
99
115
  numericPrecision: null,
100
116
  numericScale: null,
101
117
  datetimePrecision: null,
@@ -109,6 +125,8 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
109
125
  columnDefault: "0",
110
126
  extra: "",
111
127
  characterMaximumLength: null,
128
+ characterSetName: null,
129
+ collationName: null,
112
130
  numericPrecision: 3,
113
131
  numericScale: 0,
114
132
  datetimePrecision: null,
@@ -122,6 +140,8 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
122
140
  columnDefault: "VIP",
123
141
  extra: "",
124
142
  characterMaximumLength: null,
143
+ characterSetName: "utf8mb4",
144
+ collationName: "utf8mb4_general_ci",
125
145
  numericPrecision: null,
126
146
  numericScale: null,
127
147
  datetimePrecision: null,
@@ -135,10 +155,27 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
135
155
  columnDefault: "CURRENT_TIMESTAMP",
136
156
  extra: "",
137
157
  characterMaximumLength: null,
158
+ characterSetName: null,
159
+ collationName: null,
138
160
  numericPrecision: null,
139
161
  numericScale: null,
140
162
  datetimePrecision: 0,
141
163
  ordinalPosition: 7
164
+ },
165
+ {
166
+ columnName: "settings_json",
167
+ dataType: "longtext",
168
+ columnType: "longtext",
169
+ isNullable: "YES",
170
+ columnDefault: null,
171
+ extra: "",
172
+ characterMaximumLength: null,
173
+ characterSetName: "utf8mb4",
174
+ collationName: "utf8mb4_bin",
175
+ numericPrecision: null,
176
+ numericScale: null,
177
+ datetimePrecision: null,
178
+ ordinalPosition: 8
142
179
  }
143
180
  ],
144
181
  primaryKeyColumns: [{ columnName: "id" }],
@@ -158,14 +195,20 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
158
195
  ],
159
196
  foreignKeys: [
160
197
  {
161
- constraintName: "contacts_workspace_owner_id_foreign",
162
- columnName: "workspace_owner_id",
198
+ constraintName: "contacts_workspace_id_foreign",
199
+ columnName: "workspace_id",
163
200
  referencedTableName: "workspaces",
164
201
  referencedColumnName: "id",
165
202
  ordinalPosition: 1,
166
203
  updateRule: "CASCADE",
167
204
  deleteRule: "SET NULL"
168
205
  }
206
+ ],
207
+ checkConstraints: [
208
+ {
209
+ constraintName: "settings_json",
210
+ checkClause: "json_valid(`settings_json`)"
211
+ }
169
212
  ]
170
213
  });
171
214
 
@@ -176,10 +219,11 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
176
219
 
177
220
  assert.equal(snapshot.dialect, "mysql2");
178
221
  assert.equal(snapshot.tableName, "contacts");
222
+ assert.equal(snapshot.tableCollation, "utf8mb4_general_ci");
179
223
  assert.equal(snapshot.idColumn, "id");
180
224
  assert.deepEqual(snapshot.primaryKeyColumns, ["id"]);
181
- assert.equal(snapshot.hasWorkspaceOwnerColumn, true);
182
- assert.equal(snapshot.hasUserOwnerColumn, true);
225
+ assert.equal(snapshot.hasWorkspaceIdColumn, true);
226
+ assert.equal(snapshot.hasUserIdColumn, true);
183
227
 
184
228
  const firstName = snapshot.columns.find((column) => column.name === "first_name");
185
229
  assert.ok(firstName);
@@ -187,9 +231,9 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
187
231
  assert.equal(firstName.typeKind, "string");
188
232
  assert.equal(firstName.maxLength, 160);
189
233
 
190
- const workspaceOwnerId = snapshot.columns.find((column) => column.name === "workspace_owner_id");
191
- assert.ok(workspaceOwnerId);
192
- assert.equal(workspaceOwnerId.hasDefault, false);
234
+ const workspaceId = snapshot.columns.find((column) => column.name === "workspace_id");
235
+ assert.ok(workspaceId);
236
+ assert.equal(workspaceId.hasDefault, false);
193
237
 
194
238
  const vip = snapshot.columns.find((column) => column.name === "vip");
195
239
  assert.ok(vip);
@@ -200,32 +244,45 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
200
244
  assert.ok(contactTier);
201
245
  assert.deepEqual(contactTier.enumValues, ["VIP", "New"]);
202
246
 
247
+ const settingsJson = snapshot.columns.find((column) => column.name === "settings_json");
248
+ assert.ok(settingsJson);
249
+ assert.equal(settingsJson.characterSetName, "utf8mb4");
250
+ assert.equal(settingsJson.collationName, "utf8mb4_bin");
251
+
203
252
  assert.deepEqual(snapshot.indexes, [
204
253
  {
205
254
  name: "idx_contacts_first_name",
206
255
  unique: false,
256
+ indexType: "",
207
257
  columns: ["first_name"]
208
258
  },
209
259
  {
210
260
  name: "uq_contacts_vip",
211
261
  unique: true,
262
+ indexType: "",
212
263
  columns: ["vip"]
213
264
  }
214
265
  ]);
215
266
  assert.deepEqual(snapshot.foreignKeys, [
216
267
  {
217
- name: "contacts_workspace_owner_id_foreign",
268
+ name: "contacts_workspace_id_foreign",
218
269
  referencedTableName: "workspaces",
219
270
  updateRule: "CASCADE",
220
271
  deleteRule: "SET NULL",
221
272
  columns: [
222
273
  {
223
- name: "workspace_owner_id",
274
+ name: "workspace_id",
224
275
  referencedName: "id"
225
276
  }
226
277
  ]
227
278
  }
228
279
  ]);
280
+ assert.deepEqual(snapshot.checkConstraints, [
281
+ {
282
+ name: "settings_json",
283
+ clause: "json_valid(`settings_json`)"
284
+ }
285
+ ]);
229
286
  });
230
287
 
231
288
  test("introspectCrudTableSnapshot rejects unsupported column types", async () => {
@@ -239,6 +296,8 @@ test("introspectCrudTableSnapshot rejects unsupported column types", async () =>
239
296
  columnDefault: null,
240
297
  extra: "auto_increment",
241
298
  characterMaximumLength: null,
299
+ characterSetName: null,
300
+ collationName: null,
242
301
  numericPrecision: 10,
243
302
  numericScale: 0,
244
303
  datetimePrecision: null,
@@ -252,6 +311,8 @@ test("introspectCrudTableSnapshot rejects unsupported column types", async () =>
252
311
  columnDefault: null,
253
312
  extra: "",
254
313
  characterMaximumLength: null,
314
+ characterSetName: null,
315
+ collationName: null,
255
316
  numericPrecision: null,
256
317
  numericScale: null,
257
318
  datetimePrecision: null,
@@ -278,6 +339,8 @@ test("introspectCrudTableSnapshot rejects when primary key does not include id c
278
339
  columnDefault: null,
279
340
  extra: "auto_increment",
280
341
  characterMaximumLength: null,
342
+ characterSetName: null,
343
+ collationName: null,
281
344
  numericPrecision: 10,
282
345
  numericScale: 0,
283
346
  datetimePrecision: null,