This commit is contained in:
2026-07-14 08:12:13 +01:00
parent b5e4724e03
commit 3b5fba4063
26 changed files with 1097 additions and 321 deletions
+46
View File
@@ -0,0 +1,46 @@
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
import mapDataType from "./map-data-types";
import MariaDBQuoteGen from "./mariadb-quote-gen";
export default function buildColumnDefinition(
field: BUN_MARIADB_FieldSchemaType,
): string {
if (!field.fieldName) {
throw new Error("Field name is required");
}
const parts: string[] = [MariaDBQuoteGen(field.fieldName)];
parts.push(mapDataType(field));
if (field.autoIncrement) {
parts.push("AUTO_INCREMENT");
}
if (field.notNullValue || field.primaryKey || field.isVector) {
if (!field.primaryKey) {
parts.push("NOT NULL");
}
}
if (field.unique && !field.primaryKey) {
parts.push("UNIQUE");
}
if (field.defaultValue !== undefined) {
if (typeof field.defaultValue === "string") {
parts.push(`DEFAULT '${field.defaultValue.replace(/'/g, "''")}'`);
} else {
parts.push(`DEFAULT ${field.defaultValue}`);
}
} else if (field.defaultValueLiteral) {
parts.push(`DEFAULT ${field.defaultValueLiteral}`);
}
if (field.onUpdate) {
parts.push(`ON UPDATE ${field.onUpdate}`);
} else if (field.onUpdateLiteral) {
parts.push(`ON UPDATE ${field.onUpdateLiteral}`);
}
return parts.join(" ");
}
+22
View File
@@ -0,0 +1,22 @@
import { AppData } from "../../data/app-data";
import type { CreateDBSchemaParams } from "../../types";
import dbHandler from "../db-handler";
import MariaDBQuoteGen from "./mariadb-quote-gen";
export default async function createDBManagerTable({
db_schema,
config,
}: CreateDBSchemaParams) {
let sql = ``;
sql += `CREATE TABLE IF NOT EXISTS ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])} (\n`;
sql += ` table_name VARCHAR(255) NOT NULL PRIMARY KEY,\n`;
sql += ` created_at BIGINT NOT NULL,\n`;
sql += ` updated_at BIGINT NOT NULL\n`;
sql += `) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci\n`;
await dbHandler({
query: sql,
config,
});
}
+23
View File
@@ -0,0 +1,23 @@
import type { CreateDBSchemaParams } from "../../types";
import createDBManagerTable from "./create-db-manager-table";
import handleDBSchemaTables from "./handle-db-schema-tables";
import orderDBSchema from "./order-db-schema";
export default async function createDBSchema(params: CreateDBSchemaParams) {
/**
* Create Schema Manager Table
*/
await createDBManagerTable(params);
/**
* Reorder Tables
*/
const ordered_db_schema = await orderDBSchema(params);
/**
* Handle Tables
*/
await handleDBSchemaTables({ ...params, db_schema: ordered_db_schema });
process.exit();
}
@@ -0,0 +1,18 @@
import { AppData } from "../../data/app-data";
import type {
BUN_MARIADB_DB_TABLE_MANAGER_TABLE,
CreateDBSchemaParams,
} from "../../types";
import dbHandler from "../db-handler";
import MariaDBQuoteGen from "./mariadb-quote-gen";
export default async function getExistingTablesFromTablesManagerTable({
config,
}: CreateDBSchemaParams) {
const rows = await dbHandler<BUN_MARIADB_DB_TABLE_MANAGER_TABLE>({
query: `SELECT table_name FROM ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])}`,
config,
});
return rows.payload?.map((row) => row.table_name) || [];
}
+46
View File
@@ -0,0 +1,46 @@
import { AppData } from "../../data/app-data";
import type {
BUN_MARIADB_DB_TABLE_MANAGER_TABLE,
BUN_MARIADB_INFORMATION_SCHEMA_TABLES,
CreateDBSchemaTableHandlerParams,
} from "../../types";
import dbHandler from "../db-handler";
import DbInsert from "../mariadb/db-insert";
import buildColumnDefinition from "./build-column-definition";
import MariaDBQuoteGen from "./mariadb-quote-gen";
export default async function handleDBSchemaTable({
db_schema,
config,
table,
db_manager_table_name,
existing_live_table,
}: CreateDBSchemaTableHandlerParams) {
if (!db_manager_table_name || !existing_live_table?.TABLE_NAME) {
const insert_table = await DbInsert<BUN_MARIADB_DB_TABLE_MANAGER_TABLE>(
{
data: [
{
table_name: table.tableName,
},
],
table: AppData["DbSchemaManagerTableName"],
config,
},
);
let sql = ``;
sql += `CREATE TABLE IF NOT EXISTS ${MariaDBQuoteGen(table.tableName)}`;
sql += ` (`;
for (let i = 0; i < table.fields.length; i++) {
const field = table.fields[i];
if (!field) continue;
const col_def = buildColumnDefinition(field);
sql += ` ${col_def}`;
}
sql += ` )`;
}
}
+57
View File
@@ -0,0 +1,57 @@
import { AppData } from "../../data/app-data";
import type {
BUN_MARIADB_INFORMATION_SCHEMA_TABLES,
CreateDBSchemaParams,
} from "../../types";
import dbHandler from "../db-handler";
import getExistingTablesFromTablesManagerTable from "./get-existing-tables-from-tables-manager-table";
import handleDBSchemaTable from "./handle-db-schema-table";
export default async function handleDBSchemaTables(
params: CreateDBSchemaParams,
) {
const { db_schema, config } = params;
/**
* Grab Tables that have been recorded in the Schema
* Manager Table
*/
const existing_schema_tables =
await getExistingTablesFromTablesManagerTable(params);
const existing_live_tables =
await dbHandler<BUN_MARIADB_INFORMATION_SCHEMA_TABLES>({
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME != ?`,
config: params.config,
values: [AppData["DbSchemaManagerTableName"]],
});
for (let i = 0; i < db_schema.tables.length; i++) {
const table = db_schema.tables[i];
const existing_table = existing_schema_tables.find(
(t) => t == table?.tableName,
);
const existing_live_table = existing_live_tables.payload?.find(
(t) => t.TABLE_NAME == table?.tableName,
);
if (!table) {
if (existing_live_table?.TABLE_NAME) {
await dbHandler<BUN_MARIADB_INFORMATION_SCHEMA_TABLES>({
query: `DROP TABLE ${existing_live_table.TABLE_NAME}`,
config: params.config,
});
}
continue;
}
await handleDBSchemaTable({
...params,
table,
db_manager_table_name: existing_table,
existing_live_table,
});
}
}
+102
View File
@@ -0,0 +1,102 @@
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
export default function mapDataType(
field: BUN_MARIADB_FieldSchemaType,
): string {
const dataType = field.dataType?.toUpperCase() || "TEXT";
const vectorSize = field.vectorSize || 1536;
if (field.isVector) {
return `LONGTEXT COMMENT 'vector_size=${vectorSize}'`;
}
switch (dataType) {
case "CHAR":
return `CHAR(${field.integerLength || 255})`;
case "VARCHAR":
return `VARCHAR(${field.integerLength || 255})`;
case "TEXT":
return "TEXT";
case "TINYTEXT":
return "TINYTEXT";
case "MEDIUMTEXT":
return "MEDIUMTEXT";
case "LONGTEXT":
return "LONGTEXT";
case "TINYINT":
return field.integerLength
? `TINYINT(${field.integerLength})`
: "TINYINT";
case "SMALLINT":
return field.integerLength
? `SMALLINT(${field.integerLength})`
: "SMALLINT";
case "MEDIUMINT":
return field.integerLength
? `MEDIUMINT(${field.integerLength})`
: "MEDIUMINT";
case "INT":
return field.integerLength ? `INT(${field.integerLength})` : "INT";
case "BIGINT":
return field.integerLength
? `BIGINT(${field.integerLength})`
: "BIGINT";
case "FLOAT":
return "FLOAT";
case "DOUBLE":
return "DOUBLE";
case "DECIMAL":
if (field.integerLength && field.decimals) {
return `DECIMAL(${field.integerLength}, ${field.decimals})`;
}
return "DECIMAL(10,2)";
case "BINARY":
return `BINARY(${field.integerLength || 1})`;
case "VARBINARY":
return `VARBINARY(${field.integerLength || 255})`;
case "BLOB":
return "BLOB";
case "TINYBLOB":
return "TINYBLOB";
case "MEDIUMBLOB":
return "MEDIUMBLOB";
case "LONGBLOB":
return "LONGBLOB";
case "DATE":
return "DATE";
case "TIME":
return "TIME";
case "DATETIME":
return "DATETIME";
case "TIMESTAMP":
return "TIMESTAMP";
case "YEAR":
return "YEAR";
case "UUID":
return "CHAR(36)"; // MariaDB does not have a native UUID type
case "JSON":
return "JSON";
case "INET6":
return "INET6";
case "VECTOR": {
const dimensions = field.vectorSize || 1536;
return `VECTOR(${dimensions})`;
}
case "BOOLEAN":
return "TINYINT(1)";
case "ENUM": {
const enumVals = (field.options || [])
.map((v) => `'${String(v).replace(/'/g, "''")}'`)
.join(", ");
return `ENUM(${enumVals || "''"})`;
}
case "SET": {
const setVals = (field.options || [])
.map((v) => `'${String(v).replace(/'/g, "''")}'`)
.join(", ");
return `SET(${setVals || "''"})`;
}
default:
return "TEXT";
}
}
+3
View File
@@ -0,0 +1,3 @@
export default function MariaDBQuoteGen(str: string) {
return `\`${str.replace(/`/g, "``")}\``;
}
+86
View File
@@ -0,0 +1,86 @@
import _ from "lodash";
import type {
BUN_MARIADB_DatabaseSchemaType,
BUN_MARIADB_TableSchemaType,
CreateDBSchemaParams,
} from "../../types";
export default async function orderDBSchema({
db_schema,
}: CreateDBSchemaParams): Promise<BUN_MARIADB_DatabaseSchemaType> {
let new_db_schema = _.cloneDeep(db_schema);
const tables = new_db_schema.tables;
let new_tables_set = new Set<BUN_MARIADB_TableSchemaType>();
let new_tables_start_set = new Set<BUN_MARIADB_TableSchemaType>();
let new_tables_end_set = new Set<BUN_MARIADB_TableSchemaType>();
function setParentTable(table: BUN_MARIADB_TableSchemaType) {
const fields = table.fields;
let does_table_have_foreign_keys = false;
for (let f = 0; f < fields.length; f++) {
const field = fields[f];
if (!field) continue;
const dst_table_name = field.foreignKey?.destinationTableName;
if (dst_table_name) {
const fk_table = tables.find(
(tb) => tb.tableName == dst_table_name,
);
if (
fk_table &&
!new_tables_start_set.has(fk_table) &&
!new_tables_end_set.has(fk_table)
) {
setParentTable(fk_table);
}
new_tables_end_set.add(table);
if (fk_table) {
new_tables_start_set.add(fk_table);
}
does_table_have_foreign_keys = true;
}
}
return { does_table_have_foreign_keys };
}
for (let i = 0; i < tables.length; i++) {
const table = tables[i];
if (!table) {
continue;
}
let { does_table_have_foreign_keys } = setParentTable(table);
if (does_table_have_foreign_keys) {
new_tables_end_set.add(table);
} else {
new_tables_start_set.add(table);
}
}
const parsed_tables = [
...Array.from(new_tables_start_set),
...Array.from(new_tables_end_set),
];
for (let nt = 0; nt < parsed_tables.length; nt++) {
const new_table = parsed_tables[nt];
if (new_table) {
new_tables_set.add(new_table);
}
}
new_db_schema.tables = Array.from(new_tables_set);
return new_db_schema;
}