Fix bugs, improve schema gen

This commit is contained in:
2026-10-14 01:47:55 +01:00
parent 2ddf450b9f
commit 155263a79f
25 changed files with 571 additions and 172 deletions
+69 -5
View File
@@ -1,4 +1,8 @@
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
import {
BUN_MARIADB_ForeignKeyRules,
type BUN_MARIADB_FieldSchemaType,
type BUN_MARIADB_ForeignKeyRule,
} from "../../types";
import MariaDBQuoteGen from "./mariadb-quote-gen";
export function defaultForeignKeyName(
@@ -16,21 +20,81 @@ export function resolveForeignKeyName(
return field.foreignKey?.foreignKeyName || defaultForeignKeyName(tableName, fieldName);
}
/**
* Normalize a referential action as MariaDB reports it in
* `information_schema.REFERENTIAL_CONSTRAINTS`. InnoDB treats `NO ACTION`
* exactly like `RESTRICT`, so both compare as `RESTRICT`.
*/
export function normalizeForeignKeyRule(
rule: string | undefined | null,
): BUN_MARIADB_ForeignKeyRule {
const normalized = String(rule || "RESTRICT")
.trim()
.toUpperCase()
.replace(/[_\s]+/g, " ");
if (normalized === "NO ACTION") return "RESTRICT";
if (!(BUN_MARIADB_ForeignKeyRules as readonly string[]).includes(normalized)) {
throw new Error(
`Invalid foreign key rule \`${rule}\`. Expected one of: ${BUN_MARIADB_ForeignKeyRules.join(", ")}`,
);
}
return normalized as BUN_MARIADB_ForeignKeyRule;
}
/**
* Resolve the ON DELETE / ON UPDATE actions for a field's foreign key.
* Precedence: `foreignKey.onDelete` > field `onDeleteLiteral` / `onDelete`
* (legacy) > `cascadeDelete` > `RESTRICT`. Same for update, except the
* field-level `onUpdate*` keys are column `ON UPDATE` clauses, not FK rules.
*/
export function resolveForeignKeyRules(
field: BUN_MARIADB_FieldSchemaType,
tableName?: string,
): { onDelete: BUN_MARIADB_ForeignKeyRule; onUpdate: BUN_MARIADB_ForeignKeyRule } {
const fk = field.foreignKey || {};
const onDelete = normalizeForeignKeyRule(
fk.onDelete ||
field.onDeleteLiteral ||
field.onDelete ||
(fk.cascadeDelete ? "CASCADE" : "RESTRICT"),
);
const onUpdate = normalizeForeignKeyRule(
fk.onUpdate || (fk.cascadeUpdate ? "CASCADE" : "RESTRICT"),
);
if (
(onDelete === "SET NULL" || onUpdate === "SET NULL") &&
(field.notNullValue || field.primaryKey)
) {
throw new Error(
`Foreign key on \`${tableName ? `${tableName}.` : ""}${field.fieldName}\` uses SET NULL but the column is NOT NULL`,
);
}
return { onDelete, onUpdate };
}
export default function buildForeignKeyConstraint(
field: BUN_MARIADB_FieldSchemaType,
tableName: string,
): string {
const fk = field.foreignKey!;
const constraintName = resolveForeignKeyName(field, tableName);
const { onDelete, onUpdate } = resolveForeignKeyRules(field, tableName);
let constraint = `CONSTRAINT ${MariaDBQuoteGen(constraintName)} FOREIGN KEY (${MariaDBQuoteGen(field.fieldName!)}) REFERENCES ${MariaDBQuoteGen(fk.destinationTableName!)}(${MariaDBQuoteGen(fk.destinationTableColumnName!)})`;
if (fk.cascadeDelete) {
constraint += " ON DELETE CASCADE";
if (onDelete !== "RESTRICT") {
constraint += ` ON DELETE ${onDelete}`;
}
if (fk.cascadeUpdate) {
constraint += " ON UPDATE CASCADE";
if (onUpdate !== "RESTRICT") {
constraint += ` ON UPDATE ${onUpdate}`;
}
return constraint;