@jskit-ai/database-runtime-mysql 0.1.17 → 0.1.19

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.17",
4
+ version: "0.1.19",
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.18",
94
+ "@jskit-ai/database-runtime": "0.1.20",
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.17",
3
+ "version": "0.1.19",
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.18"
15
+ "@jskit-ai/database-runtime": "0.1.20"
16
16
  }
17
17
  }
@@ -257,6 +257,61 @@ function normalizeIndexes(rows = []) {
257
257
  );
258
258
  }
259
259
 
260
+ function normalizeForeignKeys(rows = []) {
261
+ const grouped = new Map();
262
+
263
+ for (const row of Array.isArray(rows) ? rows : []) {
264
+ const name = normalizeText(row.constraintName || row.constraint_name);
265
+ const columnName = normalizeText(row.columnName || row.column_name);
266
+ const referencedTableName = normalizeText(row.referencedTableName || row.referenced_table_name);
267
+ const referencedColumnName = normalizeText(row.referencedColumnName || row.referenced_column_name);
268
+ if (!name || !columnName || !referencedTableName || !referencedColumnName) {
269
+ continue;
270
+ }
271
+
272
+ const ordinalPosition = toNullableNumber(row.ordinalPosition ?? row.ordinal_position) || 0;
273
+ const updateRule = normalizeText(row.updateRule || row.update_rule).toUpperCase();
274
+ const deleteRule = normalizeText(row.deleteRule || row.delete_rule).toUpperCase();
275
+ const existing = grouped.get(name) || {
276
+ name,
277
+ referencedTableName,
278
+ updateRule,
279
+ deleteRule,
280
+ columns: []
281
+ };
282
+
283
+ existing.columns.push({
284
+ name: columnName,
285
+ referencedName: referencedColumnName,
286
+ order: ordinalPosition
287
+ });
288
+ grouped.set(name, existing);
289
+ }
290
+
291
+ return Object.freeze(
292
+ [...grouped.values()]
293
+ .map((foreignKey) =>
294
+ Object.freeze({
295
+ name: foreignKey.name,
296
+ referencedTableName: foreignKey.referencedTableName,
297
+ updateRule: foreignKey.updateRule,
298
+ deleteRule: foreignKey.deleteRule,
299
+ columns: Object.freeze(
300
+ foreignKey.columns
301
+ .sort((left, right) => left.order - right.order)
302
+ .map((column) =>
303
+ Object.freeze({
304
+ name: column.name,
305
+ referencedName: column.referencedName
306
+ })
307
+ )
308
+ )
309
+ })
310
+ )
311
+ .sort((left, right) => left.name.localeCompare(right.name))
312
+ );
313
+ }
314
+
260
315
  function requireIdColumn(columns, idColumn) {
261
316
  const normalizedIdColumn = normalizeText(idColumn) || "id";
262
317
  const idSpec = columns.find((column) => column.name === normalizedIdColumn) || null;
@@ -361,6 +416,30 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
361
416
  )
362
417
  );
363
418
 
419
+ const foreignKeyRows = normalizeRows(
420
+ await knex.raw(
421
+ `
422
+ SELECT
423
+ rc.constraint_name AS constraintName,
424
+ k.column_name AS columnName,
425
+ k.referenced_table_name AS referencedTableName,
426
+ k.referenced_column_name AS referencedColumnName,
427
+ k.ordinal_position AS ordinalPosition,
428
+ rc.update_rule AS updateRule,
429
+ rc.delete_rule AS deleteRule
430
+ FROM information_schema.referential_constraints rc
431
+ JOIN information_schema.key_column_usage k
432
+ ON k.constraint_name = rc.constraint_name
433
+ AND k.constraint_schema = rc.constraint_schema
434
+ AND k.table_name = rc.table_name
435
+ WHERE rc.constraint_schema = ?
436
+ AND rc.table_name = ?
437
+ ORDER BY rc.constraint_name ASC, k.ordinal_position ASC
438
+ `,
439
+ [schemaName, resolvedTableName]
440
+ )
441
+ );
442
+
364
443
  const columns = Object.freeze(columnRows.map((row) => normalizeColumn(row)));
365
444
  const resolvedIdColumn = requireIdColumn(columns, idColumn);
366
445
  const primaryKeyColumns = normalizePrimaryKeyColumns(primaryRows);
@@ -375,7 +454,8 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
375
454
  hasWorkspaceOwnerColumn: columns.some((column) => column.name === "workspace_owner_id"),
376
455
  hasUserOwnerColumn: columns.some((column) => column.name === "user_owner_id"),
377
456
  columns,
378
- indexes: normalizeIndexes(indexRows)
457
+ indexes: normalizeIndexes(indexRows),
458
+ foreignKeys: normalizeForeignKeys(foreignKeyRows)
379
459
  });
380
460
 
381
461
  return snapshot;
@@ -7,7 +7,8 @@ function createKnexRawDouble({
7
7
  schemaName = "appdb",
8
8
  columns = [],
9
9
  primaryKeyColumns = [],
10
- indexes = []
10
+ indexes = [],
11
+ foreignKeys = []
11
12
  } = {}) {
12
13
  const calls = [];
13
14
 
@@ -31,6 +32,9 @@ function createKnexRawDouble({
31
32
  if (normalizedSql.includes("from information_schema.statistics")) {
32
33
  return [[...indexes], []];
33
34
  }
35
+ if (normalizedSql.includes("from information_schema.referential_constraints")) {
36
+ return [[...foreignKeys], []];
37
+ }
34
38
 
35
39
  throw new Error(`Unexpected SQL in test double: ${normalizedSql}`);
36
40
  }
@@ -151,6 +155,17 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
151
155
  columnName: "vip",
152
156
  seqInIndex: 1
153
157
  }
158
+ ],
159
+ foreignKeys: [
160
+ {
161
+ constraintName: "contacts_workspace_owner_id_foreign",
162
+ columnName: "workspace_owner_id",
163
+ referencedTableName: "workspaces",
164
+ referencedColumnName: "id",
165
+ ordinalPosition: 1,
166
+ updateRule: "CASCADE",
167
+ deleteRule: "SET NULL"
168
+ }
154
169
  ]
155
170
  });
156
171
 
@@ -197,6 +212,20 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
197
212
  columns: ["vip"]
198
213
  }
199
214
  ]);
215
+ assert.deepEqual(snapshot.foreignKeys, [
216
+ {
217
+ name: "contacts_workspace_owner_id_foreign",
218
+ referencedTableName: "workspaces",
219
+ updateRule: "CASCADE",
220
+ deleteRule: "SET NULL",
221
+ columns: [
222
+ {
223
+ name: "workspace_owner_id",
224
+ referencedName: "id"
225
+ }
226
+ ]
227
+ }
228
+ ]);
200
229
  });
201
230
 
202
231
  test("introspectCrudTableSnapshot rejects unsupported column types", async () => {