@jskit-ai/database-runtime-mysql 0.1.126 → 0.1.128
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/package.descriptor.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const CI_DATABASE = Object.freeze({
|
|
|
13
13
|
export default Object.freeze({
|
|
14
14
|
packageVersion: 1,
|
|
15
15
|
packageId: "@jskit-ai/database-runtime-mysql",
|
|
16
|
-
version: "0.1.
|
|
16
|
+
version: "0.1.128",
|
|
17
17
|
kind: "runtime",
|
|
18
18
|
options: {
|
|
19
19
|
"db-host": {
|
|
@@ -133,7 +133,7 @@ export default Object.freeze({
|
|
|
133
133
|
mutations: {
|
|
134
134
|
dependencies: {
|
|
135
135
|
runtime: {
|
|
136
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
136
|
+
"@jskit-ai/database-runtime": "0.1.129",
|
|
137
137
|
"mysql2": "^3.11.2"
|
|
138
138
|
},
|
|
139
139
|
dev: {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/database-runtime-mysql",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.128",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"./shared/dialect": "./src/shared/dialect.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
16
|
-
"@jskit-ai/kernel": "0.1.
|
|
15
|
+
"@jskit-ai/database-runtime": "0.1.129",
|
|
16
|
+
"@jskit-ai/kernel": "0.1.130"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -2,7 +2,11 @@ import { normalizeText } from "@jskit-ai/database-runtime/shared";
|
|
|
2
2
|
import { toCamelCase } from "@jskit-ai/kernel/shared/support/stringCase";
|
|
3
3
|
|
|
4
4
|
const BOOLEAN_TINYINT_PATTERN = /^tinyint\(1\)/;
|
|
5
|
+
const CURRENT_TIMESTAMP_EXPRESSION_PATTERN = /^current_timestamp(?:\((\d*)\))?$/i;
|
|
6
|
+
const ON_UPDATE_CURRENT_TIMESTAMP_PATTERN =
|
|
7
|
+
/(?:^|\s)on\s+update\s+(current_timestamp(?:\((\d*)\))?)(?:\s|$)/i;
|
|
5
8
|
const TABLE_NAME_PATTERN = /^[A-Za-z0-9_]+$/;
|
|
9
|
+
const TEMPORAL_DATA_TYPES = new Set(["datetime", "timestamp"]);
|
|
6
10
|
|
|
7
11
|
function requireKnexRaw(knex) {
|
|
8
12
|
if (!knex || typeof knex.raw !== "function") {
|
|
@@ -77,6 +81,31 @@ function normalizeColumnDefault(value) {
|
|
|
77
81
|
return value;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
function normalizeCurrentTimestampExpression(value) {
|
|
85
|
+
const match = normalizeText(value).match(CURRENT_TIMESTAMP_EXPRESSION_PATTERN);
|
|
86
|
+
if (!match) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const precision =
|
|
91
|
+
match[1] == null || match[1] === ""
|
|
92
|
+
? null
|
|
93
|
+
: Number.parseInt(match[1], 10);
|
|
94
|
+
if (precision != null && (!Number.isInteger(precision) || precision < 0 || precision > 6)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Object.freeze({
|
|
99
|
+
kind: "current_timestamp",
|
|
100
|
+
precision
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function normalizeOnUpdateExpression(value) {
|
|
105
|
+
const match = normalizeText(value).match(ON_UPDATE_CURRENT_TIMESTAMP_PATTERN);
|
|
106
|
+
return match ? normalizeCurrentTimestampExpression(match[1]) : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
80
109
|
function parseEnumValues(columnType = "") {
|
|
81
110
|
const source = normalizeText(columnType);
|
|
82
111
|
if (!source.toLowerCase().startsWith("enum(") || !source.endsWith(")")) {
|
|
@@ -180,6 +209,7 @@ function normalizeColumn(row = {}) {
|
|
|
180
209
|
const autoIncrement = extra.includes("auto_increment");
|
|
181
210
|
const unsigned = columnTypeLower.includes("unsigned");
|
|
182
211
|
const enumValues = parseEnumValues(columnType);
|
|
212
|
+
const supportsTemporalExpressions = TEMPORAL_DATA_TYPES.has(dataType);
|
|
183
213
|
|
|
184
214
|
const normalized = Object.freeze({
|
|
185
215
|
name,
|
|
@@ -194,8 +224,14 @@ function normalizeColumn(row = {}) {
|
|
|
194
224
|
}),
|
|
195
225
|
nullable,
|
|
196
226
|
defaultValue,
|
|
227
|
+
defaultExpression: supportsTemporalExpressions
|
|
228
|
+
? normalizeCurrentTimestampExpression(defaultValue)
|
|
229
|
+
: null,
|
|
197
230
|
hasDefault,
|
|
198
231
|
autoIncrement,
|
|
232
|
+
onUpdateExpression: supportsTemporalExpressions
|
|
233
|
+
? normalizeOnUpdateExpression(extra)
|
|
234
|
+
: null,
|
|
199
235
|
unsigned,
|
|
200
236
|
maxLength: toNullableNumber(row.characterMaximumLength ?? row.character_maximum_length),
|
|
201
237
|
numericPrecision: toNullableNumber(row.numericPrecision ?? row.numeric_precision),
|
|
@@ -218,6 +254,36 @@ function normalizePrimaryKeyColumns(rows = []) {
|
|
|
218
254
|
);
|
|
219
255
|
}
|
|
220
256
|
|
|
257
|
+
function normalizePrimaryKeyColumnsByTable(rows = []) {
|
|
258
|
+
const grouped = new Map();
|
|
259
|
+
|
|
260
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
261
|
+
const tableName = normalizeText(row.tableName || row.table_name);
|
|
262
|
+
const columnName = normalizeText(row.columnName || row.column_name);
|
|
263
|
+
if (!tableName || !columnName) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const columns = grouped.get(tableName) || [];
|
|
268
|
+
columns.push({
|
|
269
|
+
name: columnName,
|
|
270
|
+
order: toNullableNumber(row.ordinalPosition ?? row.ordinal_position) || 0
|
|
271
|
+
});
|
|
272
|
+
grouped.set(tableName, columns);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return new Map(
|
|
276
|
+
[...grouped.entries()].map(([tableName, columns]) => [
|
|
277
|
+
tableName,
|
|
278
|
+
Object.freeze(
|
|
279
|
+
columns
|
|
280
|
+
.sort((left, right) => left.order - right.order)
|
|
281
|
+
.map((column) => column.name)
|
|
282
|
+
)
|
|
283
|
+
])
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
221
287
|
function normalizeIndexes(rows = []) {
|
|
222
288
|
const byName = new Map();
|
|
223
289
|
|
|
@@ -369,6 +435,32 @@ function requirePrimaryKeyContainsId(primaryKeyColumns, idColumn) {
|
|
|
369
435
|
}
|
|
370
436
|
}
|
|
371
437
|
|
|
438
|
+
function requireSupportedForeignKeys(foreignKeys, primaryKeyColumnsByTable) {
|
|
439
|
+
for (const foreignKey of foreignKeys) {
|
|
440
|
+
const columns = Array.isArray(foreignKey?.columns) ? foreignKey.columns : [];
|
|
441
|
+
if (columns.length !== 1) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`CRUD generation supports only single-column foreign keys. Constraint "${foreignKey.name}" has ${columns.length} columns.`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const targetTable = normalizeText(foreignKey.referencedTableName);
|
|
448
|
+
const targetColumn = normalizeText(columns[0]?.referencedName);
|
|
449
|
+
const targetPrimaryKey = primaryKeyColumnsByTable.get(targetTable) || [];
|
|
450
|
+
if (targetPrimaryKey.length !== 1) {
|
|
451
|
+
const actual = targetPrimaryKey.length > 0 ? targetPrimaryKey.join(", ") : "none";
|
|
452
|
+
throw new Error(
|
|
453
|
+
`CRUD foreign key "${foreignKey.name}" targets table "${targetTable}", whose primary key is not single-column (found: ${actual}).`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (targetColumn !== targetPrimaryKey[0]) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`CRUD foreign key "${foreignKey.name}" must target the primary key "${targetTable}.${targetPrimaryKey[0]}", not "${targetTable}.${targetColumn}".`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
372
464
|
async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "id" } = {}) {
|
|
373
465
|
requireKnexRaw(knex);
|
|
374
466
|
const resolvedTableName = requireTableName(tableName);
|
|
@@ -482,6 +574,22 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
|
|
|
482
574
|
)
|
|
483
575
|
);
|
|
484
576
|
|
|
577
|
+
const schemaPrimaryKeyRows = normalizeRows(
|
|
578
|
+
await knex.raw(
|
|
579
|
+
`
|
|
580
|
+
SELECT
|
|
581
|
+
s.table_name AS tableName,
|
|
582
|
+
s.column_name AS columnName,
|
|
583
|
+
s.seq_in_index AS ordinalPosition
|
|
584
|
+
FROM information_schema.statistics s
|
|
585
|
+
WHERE s.table_schema = ?
|
|
586
|
+
AND s.index_name = 'PRIMARY'
|
|
587
|
+
ORDER BY s.table_name ASC, s.seq_in_index ASC
|
|
588
|
+
`,
|
|
589
|
+
[schemaName]
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
|
|
485
593
|
const checkConstraintRows = normalizeRows(
|
|
486
594
|
await knex.raw(
|
|
487
595
|
`
|
|
@@ -505,6 +613,11 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
|
|
|
505
613
|
const resolvedIdColumn = requireIdColumn(columns, idColumn);
|
|
506
614
|
const primaryKeyColumns = normalizePrimaryKeyColumns(primaryRows);
|
|
507
615
|
requirePrimaryKeyContainsId(primaryKeyColumns, resolvedIdColumn);
|
|
616
|
+
const foreignKeys = normalizeForeignKeys(foreignKeyRows);
|
|
617
|
+
requireSupportedForeignKeys(
|
|
618
|
+
foreignKeys,
|
|
619
|
+
normalizePrimaryKeyColumnsByTable(schemaPrimaryKeyRows)
|
|
620
|
+
);
|
|
508
621
|
const firstTableRow = Array.isArray(tableRows) ? tableRows[0] : null;
|
|
509
622
|
|
|
510
623
|
const snapshot = Object.freeze({
|
|
@@ -518,7 +631,7 @@ async function introspectCrudTableSnapshot(knex, { tableName = "", idColumn = "i
|
|
|
518
631
|
hasUserIdColumn: columns.some((column) => column.name === "user_id"),
|
|
519
632
|
columns,
|
|
520
633
|
indexes: normalizeIndexes(indexRows),
|
|
521
|
-
foreignKeys
|
|
634
|
+
foreignKeys,
|
|
522
635
|
checkConstraints: normalizeCheckConstraints(checkConstraintRows)
|
|
523
636
|
});
|
|
524
637
|
|
|
@@ -10,6 +10,7 @@ function createKnexRawDouble({
|
|
|
10
10
|
primaryKeyColumns = [],
|
|
11
11
|
indexes = [],
|
|
12
12
|
foreignKeys = [],
|
|
13
|
+
primaryKeysByTable = {},
|
|
13
14
|
checkConstraints = []
|
|
14
15
|
} = {}) {
|
|
15
16
|
const calls = [];
|
|
@@ -37,6 +38,21 @@ function createKnexRawDouble({
|
|
|
37
38
|
if (normalizedSql.includes("from information_schema.table_constraints")) {
|
|
38
39
|
return [[...primaryKeyColumns], []];
|
|
39
40
|
}
|
|
41
|
+
if (
|
|
42
|
+
normalizedSql.includes("from information_schema.statistics") &&
|
|
43
|
+
normalizedSql.includes("s.index_name = 'primary'")
|
|
44
|
+
) {
|
|
45
|
+
return [
|
|
46
|
+
Object.entries(primaryKeysByTable).flatMap(([tableName, columnNames]) =>
|
|
47
|
+
columnNames.map((columnName, index) => ({
|
|
48
|
+
tableName,
|
|
49
|
+
columnName,
|
|
50
|
+
ordinalPosition: index + 1
|
|
51
|
+
}))
|
|
52
|
+
),
|
|
53
|
+
[]
|
|
54
|
+
];
|
|
55
|
+
}
|
|
40
56
|
if (normalizedSql.includes("from information_schema.statistics")) {
|
|
41
57
|
return [[...indexes], []];
|
|
42
58
|
}
|
|
@@ -150,16 +166,16 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
|
|
|
150
166
|
{
|
|
151
167
|
columnName: "updated_at",
|
|
152
168
|
dataType: "datetime",
|
|
153
|
-
columnType: "datetime",
|
|
169
|
+
columnType: "datetime(3)",
|
|
154
170
|
isNullable: "NO",
|
|
155
|
-
columnDefault: "CURRENT_TIMESTAMP",
|
|
156
|
-
extra: "",
|
|
171
|
+
columnDefault: "CURRENT_TIMESTAMP(3)",
|
|
172
|
+
extra: "DEFAULT_GENERATED on update CURRENT_TIMESTAMP(3)",
|
|
157
173
|
characterMaximumLength: null,
|
|
158
174
|
characterSetName: null,
|
|
159
175
|
collationName: null,
|
|
160
176
|
numericPrecision: null,
|
|
161
177
|
numericScale: null,
|
|
162
|
-
datetimePrecision:
|
|
178
|
+
datetimePrecision: 3,
|
|
163
179
|
ordinalPosition: 7
|
|
164
180
|
},
|
|
165
181
|
{
|
|
@@ -204,6 +220,10 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
|
|
|
204
220
|
deleteRule: "SET NULL"
|
|
205
221
|
}
|
|
206
222
|
],
|
|
223
|
+
primaryKeysByTable: {
|
|
224
|
+
contacts: ["id"],
|
|
225
|
+
workspaces: ["id"]
|
|
226
|
+
},
|
|
207
227
|
checkConstraints: [
|
|
208
228
|
{
|
|
209
229
|
constraintName: "settings_json",
|
|
@@ -249,6 +269,16 @@ test("introspectCrudTableSnapshot maps MySQL table metadata to normalized snapsh
|
|
|
249
269
|
assert.equal(settingsJson.characterSetName, "utf8mb4");
|
|
250
270
|
assert.equal(settingsJson.collationName, "utf8mb4_bin");
|
|
251
271
|
|
|
272
|
+
const updatedAt = snapshot.columns.find((column) => column.name === "updated_at");
|
|
273
|
+
assert.deepEqual(updatedAt.defaultExpression, {
|
|
274
|
+
kind: "current_timestamp",
|
|
275
|
+
precision: 3
|
|
276
|
+
});
|
|
277
|
+
assert.deepEqual(updatedAt.onUpdateExpression, {
|
|
278
|
+
kind: "current_timestamp",
|
|
279
|
+
precision: 3
|
|
280
|
+
});
|
|
281
|
+
|
|
252
282
|
assert.deepEqual(snapshot.indexes, [
|
|
253
283
|
{
|
|
254
284
|
name: "idx_contacts_first_name",
|
|
@@ -355,3 +385,184 @@ test("introspectCrudTableSnapshot rejects when primary key does not include id c
|
|
|
355
385
|
/Primary key must include id column "id"/
|
|
356
386
|
);
|
|
357
387
|
});
|
|
388
|
+
|
|
389
|
+
test("introspectCrudTableSnapshot classifies only allowlisted temporal defaults as SQL expressions", async () => {
|
|
390
|
+
const { knex } = createKnexRawDouble({
|
|
391
|
+
columns: [
|
|
392
|
+
...validIdColumns(),
|
|
393
|
+
{
|
|
394
|
+
columnName: "label",
|
|
395
|
+
dataType: "varchar",
|
|
396
|
+
columnType: "varchar(190)",
|
|
397
|
+
isNullable: "NO",
|
|
398
|
+
columnDefault: "CURRENT_TIMESTAMP(3)",
|
|
399
|
+
extra: "",
|
|
400
|
+
characterMaximumLength: 190,
|
|
401
|
+
characterSetName: "utf8mb4",
|
|
402
|
+
collationName: "utf8mb4_general_ci",
|
|
403
|
+
ordinalPosition: 2
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
columnName: "created_at",
|
|
407
|
+
dataType: "datetime",
|
|
408
|
+
columnType: "datetime",
|
|
409
|
+
isNullable: "NO",
|
|
410
|
+
columnDefault: "CURRENT_TIMESTAMP()",
|
|
411
|
+
extra: "",
|
|
412
|
+
datetimePrecision: 0,
|
|
413
|
+
ordinalPosition: 3
|
|
414
|
+
}
|
|
415
|
+
],
|
|
416
|
+
primaryKeyColumns: [{ columnName: "id" }]
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const snapshot = await introspectCrudTableSnapshot(knex, {
|
|
420
|
+
tableName: "labels"
|
|
421
|
+
});
|
|
422
|
+
const label = snapshot.columns.find((column) => column.name === "label");
|
|
423
|
+
|
|
424
|
+
assert.equal(label.defaultValue, "CURRENT_TIMESTAMP(3)");
|
|
425
|
+
assert.equal(label.defaultExpression, null);
|
|
426
|
+
assert.equal(label.onUpdateExpression, null);
|
|
427
|
+
assert.deepEqual(
|
|
428
|
+
snapshot.columns.find((column) => column.name === "created_at").defaultExpression,
|
|
429
|
+
{
|
|
430
|
+
kind: "current_timestamp",
|
|
431
|
+
precision: null
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
function validIdColumns(extraColumns = []) {
|
|
437
|
+
return [
|
|
438
|
+
{
|
|
439
|
+
columnName: "id",
|
|
440
|
+
dataType: "bigint",
|
|
441
|
+
columnType: "bigint unsigned",
|
|
442
|
+
isNullable: "NO",
|
|
443
|
+
columnDefault: null,
|
|
444
|
+
extra: "auto_increment",
|
|
445
|
+
numericPrecision: 20,
|
|
446
|
+
numericScale: 0,
|
|
447
|
+
ordinalPosition: 1
|
|
448
|
+
},
|
|
449
|
+
...extraColumns
|
|
450
|
+
];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
test("introspectCrudTableSnapshot rejects composite foreign keys", async () => {
|
|
454
|
+
const { knex } = createKnexRawDouble({
|
|
455
|
+
columns: validIdColumns([
|
|
456
|
+
{
|
|
457
|
+
columnName: "workspace_id",
|
|
458
|
+
dataType: "bigint",
|
|
459
|
+
columnType: "bigint unsigned",
|
|
460
|
+
isNullable: "NO",
|
|
461
|
+
extra: "",
|
|
462
|
+
ordinalPosition: 2
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
columnName: "parent_id",
|
|
466
|
+
dataType: "bigint",
|
|
467
|
+
columnType: "bigint unsigned",
|
|
468
|
+
isNullable: "NO",
|
|
469
|
+
extra: "",
|
|
470
|
+
ordinalPosition: 3
|
|
471
|
+
}
|
|
472
|
+
]),
|
|
473
|
+
primaryKeyColumns: [{ columnName: "id" }],
|
|
474
|
+
foreignKeys: [
|
|
475
|
+
{
|
|
476
|
+
constraintName: "items_parent_foreign",
|
|
477
|
+
columnName: "workspace_id",
|
|
478
|
+
referencedTableName: "parents",
|
|
479
|
+
referencedColumnName: "workspace_id",
|
|
480
|
+
ordinalPosition: 1
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
constraintName: "items_parent_foreign",
|
|
484
|
+
columnName: "parent_id",
|
|
485
|
+
referencedTableName: "parents",
|
|
486
|
+
referencedColumnName: "id",
|
|
487
|
+
ordinalPosition: 2
|
|
488
|
+
}
|
|
489
|
+
],
|
|
490
|
+
primaryKeysByTable: {
|
|
491
|
+
items: ["id"],
|
|
492
|
+
parents: ["id"]
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
await assert.rejects(
|
|
497
|
+
() => introspectCrudTableSnapshot(knex, { tableName: "items" }),
|
|
498
|
+
/supports only single-column foreign keys.*2 columns/
|
|
499
|
+
);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("introspectCrudTableSnapshot rejects foreign keys to non-primary business keys", async () => {
|
|
503
|
+
const { knex } = createKnexRawDouble({
|
|
504
|
+
columns: validIdColumns([
|
|
505
|
+
{
|
|
506
|
+
columnName: "workspace_slug",
|
|
507
|
+
dataType: "varchar",
|
|
508
|
+
columnType: "varchar(190)",
|
|
509
|
+
isNullable: "NO",
|
|
510
|
+
extra: "",
|
|
511
|
+
ordinalPosition: 2
|
|
512
|
+
}
|
|
513
|
+
]),
|
|
514
|
+
primaryKeyColumns: [{ columnName: "id" }],
|
|
515
|
+
foreignKeys: [
|
|
516
|
+
{
|
|
517
|
+
constraintName: "items_workspace_slug_foreign",
|
|
518
|
+
columnName: "workspace_slug",
|
|
519
|
+
referencedTableName: "workspaces",
|
|
520
|
+
referencedColumnName: "slug",
|
|
521
|
+
ordinalPosition: 1
|
|
522
|
+
}
|
|
523
|
+
],
|
|
524
|
+
primaryKeysByTable: {
|
|
525
|
+
items: ["id"],
|
|
526
|
+
workspaces: ["id"]
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
await assert.rejects(
|
|
531
|
+
() => introspectCrudTableSnapshot(knex, { tableName: "items" }),
|
|
532
|
+
/must target the primary key "workspaces.id", not "workspaces.slug"/
|
|
533
|
+
);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("introspectCrudTableSnapshot rejects foreign keys to composite primary keys", async () => {
|
|
537
|
+
const { knex } = createKnexRawDouble({
|
|
538
|
+
columns: validIdColumns([
|
|
539
|
+
{
|
|
540
|
+
columnName: "parent_id",
|
|
541
|
+
dataType: "bigint",
|
|
542
|
+
columnType: "bigint unsigned",
|
|
543
|
+
isNullable: "NO",
|
|
544
|
+
extra: "",
|
|
545
|
+
ordinalPosition: 2
|
|
546
|
+
}
|
|
547
|
+
]),
|
|
548
|
+
primaryKeyColumns: [{ columnName: "id" }],
|
|
549
|
+
foreignKeys: [
|
|
550
|
+
{
|
|
551
|
+
constraintName: "items_parent_foreign",
|
|
552
|
+
columnName: "parent_id",
|
|
553
|
+
referencedTableName: "parents",
|
|
554
|
+
referencedColumnName: "id",
|
|
555
|
+
ordinalPosition: 1
|
|
556
|
+
}
|
|
557
|
+
],
|
|
558
|
+
primaryKeysByTable: {
|
|
559
|
+
items: ["id"],
|
|
560
|
+
parents: ["workspace_id", "id"]
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
await assert.rejects(
|
|
565
|
+
() => introspectCrudTableSnapshot(knex, { tableName: "items" }),
|
|
566
|
+
/primary key is not single-column.*workspace_id, id/
|
|
567
|
+
);
|
|
568
|
+
});
|