Files
bun-mariadb/dist/lib/schema/sync-unique-constraints.js
T
2026-07-30 07:19:07 +01:00

139 lines
5.2 KiB
JavaScript

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;
}
}
}