Updates
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
import isVectorField from "./is-vector-field";
|
||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||
function columnsKey(columns) {
|
||||
return columns.join("\0");
|
||||
}
|
||||
function columnsEqual(a, b) {
|
||||
return a.length === b.length && a.every((col, i) => col === b[i]);
|
||||
}
|
||||
function defaultConstraintName(columns) {
|
||||
return `unique_${columns.join("_")}`;
|
||||
}
|
||||
export function grabDesiredUniqueConstraints(table) {
|
||||
const desired = [];
|
||||
const seenColumnSets = new Set();
|
||||
for (const constraint of table.uniqueConstraints || []) {
|
||||
const columns = (constraint.constraintTableFields || [])
|
||||
.map((field) => field.value)
|
||||
.filter((value) => Boolean(value));
|
||||
if (columns.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const key = columnsKey(columns);
|
||||
if (seenColumnSets.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seenColumnSets.add(key);
|
||||
desired.push({
|
||||
name: constraint.constraintName || defaultConstraintName(columns),
|
||||
columns,
|
||||
});
|
||||
}
|
||||
for (const field of table.fields || []) {
|
||||
if (!field.fieldName ||
|
||||
!field.unique ||
|
||||
field.primaryKey ||
|
||||
isVectorField(field)) {
|
||||
continue;
|
||||
}
|
||||
const columns = [field.fieldName];
|
||||
const key = columnsKey(columns);
|
||||
if (seenColumnSets.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seenColumnSets.add(key);
|
||||
// Matches MariaDB's default name for column-level UNIQUE
|
||||
desired.push({
|
||||
name: field.fieldName,
|
||||
columns,
|
||||
});
|
||||
}
|
||||
return desired;
|
||||
}
|
||||
async function grabLiveUniqueConstraints({ tableName, config, }) {
|
||||
const databaseName = config?.db_name || global.CONFIG?.db_name;
|
||||
const schemaWhere = databaseName
|
||||
? "tc.TABLE_SCHEMA = ?"
|
||||
: "tc.TABLE_SCHEMA = DATABASE()";
|
||||
const schemaValues = databaseName ? [databaseName] : [];
|
||||
const rows = await querySchemaRows({
|
||||
query: `
|
||||
SELECT tc.CONSTRAINT_NAME, kcu.COLUMN_NAME, kcu.ORDINAL_POSITION
|
||||
FROM information_schema.TABLE_CONSTRAINTS tc
|
||||
INNER JOIN information_schema.KEY_COLUMN_USAGE kcu
|
||||
ON tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
|
||||
AND tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
||||
AND tc.TABLE_NAME = kcu.TABLE_NAME
|
||||
WHERE ${schemaWhere}
|
||||
AND tc.TABLE_NAME = ?
|
||||
AND tc.CONSTRAINT_TYPE = 'UNIQUE'
|
||||
ORDER BY tc.CONSTRAINT_NAME, kcu.ORDINAL_POSITION
|
||||
`,
|
||||
values: [...schemaValues, tableName],
|
||||
config,
|
||||
});
|
||||
const map = new Map();
|
||||
for (const row of rows) {
|
||||
if (!map.has(row.CONSTRAINT_NAME)) {
|
||||
map.set(row.CONSTRAINT_NAME, []);
|
||||
}
|
||||
map.get(row.CONSTRAINT_NAME).push(row.COLUMN_NAME);
|
||||
}
|
||||
return [...map.entries()].map(([name, columns]) => ({ name, columns }));
|
||||
}
|
||||
export default async function syncUniqueConstraints({ table, config, }) {
|
||||
const desired = grabDesiredUniqueConstraints(table);
|
||||
const live = await grabLiveUniqueConstraints({
|
||||
tableName: table.tableName,
|
||||
config,
|
||||
});
|
||||
const matchedLiveNames = new Set();
|
||||
for (const want of desired) {
|
||||
const byColumns = live.find((existing) => !matchedLiveNames.has(existing.name) &&
|
||||
columnsEqual(existing.columns, want.columns));
|
||||
if (byColumns) {
|
||||
matchedLiveNames.add(byColumns.name);
|
||||
continue;
|
||||
}
|
||||
const byName = live.find((existing) => existing.name === want.name);
|
||||
if (byName && !matchedLiveNames.has(byName.name)) {
|
||||
console.log(`Recreating changed unique constraint: ${want.name} on ${table.tableName}`);
|
||||
await runSchemaQuery({
|
||||
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} DROP INDEX ${MariaDBQuoteGen(byName.name)}`,
|
||||
config,
|
||||
});
|
||||
matchedLiveNames.add(byName.name);
|
||||
}
|
||||
const fields = want.columns.map((col) => MariaDBQuoteGen(col)).join(", ");
|
||||
console.log(`Creating unique constraint: ${want.name} (${want.columns.join(", ")}) on ${table.tableName}`);
|
||||
await runSchemaQuery({
|
||||
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} ADD CONSTRAINT ${MariaDBQuoteGen(want.name)} UNIQUE (${fields})`,
|
||||
config,
|
||||
});
|
||||
}
|
||||
for (const existing of live) {
|
||||
if (matchedLiveNames.has(existing.name)) {
|
||||
continue;
|
||||
}
|
||||
const stillDesired = desired.some((want) => columnsEqual(want.columns, existing.columns));
|
||||
if (stillDesired) {
|
||||
continue;
|
||||
}
|
||||
console.log(`Dropping unique constraint: ${existing.name} on ${table.tableName}`);
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} DROP INDEX ${MariaDBQuoteGen(existing.name)}`,
|
||||
config,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
if (String(err?.message || "").includes("needed in a foreign key constraint")) {
|
||||
console.warn(`Skipping drop of unique constraint ${existing.name}: required by a foreign key constraint`);
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user