Updates
This commit is contained in:
Vendored
+114
-16
@@ -1,12 +1,13 @@
|
||||
import buildColumnDefinition from "./build-column-definition";
|
||||
import buildColumnDefinition, { fieldRequiresNotNull, } from "./build-column-definition";
|
||||
import createTable from "./create-table";
|
||||
import getTableColumns from "./get-table-columns";
|
||||
import getTableColumns, {} from "./get-table-columns";
|
||||
import isVectorField from "./is-vector-field";
|
||||
import mapDataType from "./map-data-types";
|
||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||
import recreateTable from "./recreate-table";
|
||||
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||
import schemaCondition from "./schema-condition";
|
||||
import { dropForeignKeysOnColumns } from "./sync-foreign-keys";
|
||||
/**
|
||||
* Compare live COLUMN_TYPE with schema-mapped type.
|
||||
* Live types often include display widths (e.g. bigint(20) vs BIGINT).
|
||||
@@ -41,9 +42,85 @@ function vectorTypeDiverged(liveType, liveComment, field) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function normalizeDefault(value) {
|
||||
if (value == null)
|
||||
return "";
|
||||
let v = String(value).trim();
|
||||
// Strip surrounding quotes MariaDB may include
|
||||
if ((v.startsWith("'") && v.endsWith("'")) ||
|
||||
(v.startsWith('"') && v.endsWith('"'))) {
|
||||
v = v.slice(1, -1);
|
||||
}
|
||||
return v.toLowerCase().replace(/\s+/g, " ");
|
||||
}
|
||||
function expectedDefault(field) {
|
||||
if (field.defaultValue !== undefined) {
|
||||
return String(field.defaultValue);
|
||||
}
|
||||
if (field.defaultValueLiteral) {
|
||||
return field.defaultValueLiteral;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function expectedOnUpdate(field) {
|
||||
if (field.onUpdate)
|
||||
return field.onUpdate;
|
||||
if (field.onUpdateLiteral)
|
||||
return field.onUpdateLiteral;
|
||||
return null;
|
||||
}
|
||||
function defaultsMatch(liveDefault, expected) {
|
||||
if (liveDefault === expected)
|
||||
return true;
|
||||
if (expected.includes("current_timestamp") &&
|
||||
liveDefault.includes("current_timestamp")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function columnAttributesDiverged(live, field) {
|
||||
const wantsNotNull = fieldRequiresNotNull(field);
|
||||
if (wantsNotNull && live.isNullable) {
|
||||
return true;
|
||||
}
|
||||
if (!wantsNotNull && !live.isNullable && !field.primaryKey) {
|
||||
return true;
|
||||
}
|
||||
const liveExtra = (live.extra || "").toLowerCase();
|
||||
const wantsAi = Boolean(field.autoIncrement);
|
||||
const hasAi = liveExtra.includes("auto_increment");
|
||||
if (wantsAi !== hasAi) {
|
||||
return true;
|
||||
}
|
||||
const wantsDefault = expectedDefault(field);
|
||||
if (wantsDefault != null) {
|
||||
const liveDefault = normalizeDefault(live.columnDefault);
|
||||
const expected = normalizeDefault(wantsDefault);
|
||||
if (!defaultsMatch(liveDefault, expected)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const wantsOnUpdate = expectedOnUpdate(field);
|
||||
const hasOnUpdate = /on update/i.test(live.extra || "");
|
||||
if (wantsOnUpdate && !hasOnUpdate) {
|
||||
return true;
|
||||
}
|
||||
if (!wantsOnUpdate && hasOnUpdate) {
|
||||
return true;
|
||||
}
|
||||
if (wantsOnUpdate && hasOnUpdate) {
|
||||
const liveOnUpdate = normalizeDefault((live.extra.match(/on update\s+(.+)/i) || [])[1] || "");
|
||||
const expectedOu = normalizeDefault(wantsOnUpdate);
|
||||
if (liveOnUpdate && expectedOu && !defaultsMatch(liveOnUpdate, expectedOu)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async function addColumn({ tableName, field, config, }) {
|
||||
console.log(`Adding column: ${tableName}.${field.fieldName}`);
|
||||
const columnDef = buildColumnDefinition(field).trim();
|
||||
// omitUnique — unique constraints are applied by syncUniqueConstraints
|
||||
const columnDef = buildColumnDefinition(field, { omitUnique: true }).trim();
|
||||
await runSchemaQuery({
|
||||
query: `ALTER TABLE ${MariaDBQuoteGen(tableName)} ADD COLUMN IF NOT EXISTS ${columnDef}`,
|
||||
config,
|
||||
@@ -51,7 +128,7 @@ async function addColumn({ tableName, field, config, }) {
|
||||
}
|
||||
async function modifyColumn({ tableName, field, config, }) {
|
||||
console.log(`Modifying column: ${tableName}.${field.fieldName}`);
|
||||
const columnDef = buildColumnDefinition(field).trim();
|
||||
const columnDef = buildColumnDefinition(field, { omitUnique: true }).trim();
|
||||
await runSchemaQuery({
|
||||
query: `ALTER TABLE ${MariaDBQuoteGen(tableName)} MODIFY COLUMN ${columnDef}`,
|
||||
config,
|
||||
@@ -73,10 +150,7 @@ export default async function updateTable({ table, config, }) {
|
||||
await createTable({ table, config });
|
||||
return;
|
||||
}
|
||||
const liveFieldsMap = new Map(existingColumns.map((col) => [
|
||||
col.name,
|
||||
{ type: col.type.toLowerCase(), comment: col.comment || "" },
|
||||
]));
|
||||
const liveFieldsMap = new Map(existingColumns.map((col) => [col.name, col]));
|
||||
const codeFieldsMap = new Map((table.fields || []).map((f) => [f.fieldName, f]));
|
||||
const fieldsToAdd = [];
|
||||
const fieldsToModify = [];
|
||||
@@ -87,19 +161,18 @@ export default async function updateTable({ table, config, }) {
|
||||
continue;
|
||||
const liveField = liveFieldsMap.get(field.fieldName);
|
||||
if (!liveField) {
|
||||
// Adding a new vector column can require rebuild if VECTOR INDEX
|
||||
// constraints conflict; still try surgical add first.
|
||||
fieldsToAdd.push(field);
|
||||
}
|
||||
else {
|
||||
let typeDiverged = !columnTypesMatch(liveField.type, mapDataType(field));
|
||||
if (isVectorField(field)) {
|
||||
typeDiverged = vectorTypeDiverged(liveField.type, liveField.comment, field);
|
||||
typeDiverged = vectorTypeDiverged(liveField.type, liveField.comment || "", field);
|
||||
if (typeDiverged) {
|
||||
needsVectorRecreate = true;
|
||||
}
|
||||
}
|
||||
if (typeDiverged) {
|
||||
const attrsDiverged = !typeDiverged && columnAttributesDiverged(liveField, field);
|
||||
if (typeDiverged || attrsDiverged) {
|
||||
fieldsToModify.push(field);
|
||||
}
|
||||
}
|
||||
@@ -122,6 +195,12 @@ export default async function updateTable({ table, config, }) {
|
||||
}
|
||||
console.log(`Surgically updating table structure from database layout: ${table.tableName}`);
|
||||
if (fieldsToDrop.length > 0) {
|
||||
// Drop FKs that reference columns being removed
|
||||
await dropForeignKeysOnColumns({
|
||||
tableName: table.tableName,
|
||||
columns: fieldsToDrop,
|
||||
config,
|
||||
});
|
||||
const schemaCond = schemaCondition(config);
|
||||
const pkRows = await querySchemaRows({
|
||||
query: `SELECT COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE ${schemaCond.where} AND TABLE_NAME = ? AND CONSTRAINT_NAME = 'PRIMARY'`,
|
||||
@@ -149,12 +228,31 @@ export default async function updateTable({ table, config, }) {
|
||||
}
|
||||
for (const indexName of indexesToDrop) {
|
||||
console.log(`Dropping index ${indexName} because it contains a dropped column`);
|
||||
await runSchemaQuery({
|
||||
query: `DROP INDEX ${MariaDBQuoteGen(indexName)} ON ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
try {
|
||||
await runSchemaQuery({
|
||||
query: `DROP INDEX ${MariaDBQuoteGen(indexName)} ON ${MariaDBQuoteGen(table.tableName)}`,
|
||||
config,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
if (String(err?.message || "").includes("needed in a foreign key constraint")) {
|
||||
console.warn(`Skipping drop of index ${indexName}: required by a foreign key constraint`);
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Drop FKs on columns being modified (MODIFY can fail with FK present)
|
||||
if (fieldsToModify.length > 0) {
|
||||
await dropForeignKeysOnColumns({
|
||||
tableName: table.tableName,
|
||||
columns: fieldsToModify
|
||||
.map((f) => f.fieldName)
|
||||
.filter((n) => Boolean(n)),
|
||||
config,
|
||||
});
|
||||
}
|
||||
for (const field of fieldsToAdd) {
|
||||
await addColumn({ tableName: table.tableName, field, config });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user