import createTable from "./create-table"; import getTableColumns from "./get-table-columns"; import MariaDBQuoteGen from "./mariadb-quote-gen"; import runSchemaQuery, { querySchemaRows } from "./run-schema-query"; import schemaCondition from "./schema-condition"; async function checkIfTableExists({ tableName, config, }) { const schemaCond = schemaCondition(config); const rows = await querySchemaRows({ query: `SELECT 1 AS \`table_exists\` FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_NAME = ? LIMIT 1`, values: [...schemaCond.values, tableName], config, }); return Boolean(rows[0]?.table_exists); } /** * Full table rebuild. For `isVector` tables this drops and recreates in place * (preserving rows when possible). For regular tables it uses a temp-table swap. */ export default async function recreateTable({ table, config, }) { const doesTableExist = await checkIfTableExists({ tableName: table.tableName, config, }); if (!doesTableExist) { await createTable({ table, config }); return; } /** * Vector tables: drop + recreate + reinsert (MariaDB VECTOR INDEX / dim * changes are not reliably alterable in place). */ if (table.isVector) { console.log(`Recreating vector table: ${table.tableName}`); const existingRows = await querySchemaRows({ query: `SELECT * FROM ${MariaDBQuoteGen(table.tableName)}`, config, }); await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config }); try { await runSchemaQuery({ query: `DROP TABLE IF EXISTS ${MariaDBQuoteGen(table.tableName)}`, config, }); await createTable({ table, config }); } finally { await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config }); } if (existingRows.length > 0) { const schemaFieldNames = new Set((table.fields || []) .map((f) => f.fieldName) .filter((n) => Boolean(n))); for (const row of existingRows) { const columns = Object.keys(row).filter((c) => schemaFieldNames.has(c)); if (columns.length === 0) continue; const placeholders = columns.map(() => "?").join(", "); const columnList = columns .map((c) => MariaDBQuoteGen(c)) .join(", "); await runSchemaQuery({ query: `INSERT INTO ${MariaDBQuoteGen(table.tableName)} (${columnList}) VALUES (${placeholders})`, values: columns.map((c) => row[c] ?? null), config, }); } } return; } const tempTableName = `${table.tableName}_temp_${Date.now()}`; const backupOldTableName = `${table.tableName}_old_${Date.now()}`; const existingColumns = await getTableColumns({ tableName: table.tableName, config, }); const columnsToKeep = (table.fields || []) .filter((field) => existingColumns.some((column) => column.name === field.fieldName)) .map((field) => field.fieldName) .filter((fieldName) => Boolean(fieldName)); await createTable({ table: { ...table, tableName: tempTableName }, config, }); if (columnsToKeep.length > 0) { const columnList = columnsToKeep .map((column) => MariaDBQuoteGen(column)) .join(", "); await runSchemaQuery({ query: `INSERT INTO ${MariaDBQuoteGen(tempTableName)} (${columnList}) SELECT ${columnList} FROM ${MariaDBQuoteGen(table.tableName)}`, config, }); } await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config }); try { await runSchemaQuery({ query: `RENAME TABLE ${MariaDBQuoteGen(table.tableName)} TO ${MariaDBQuoteGen(backupOldTableName)}`, config, }); await runSchemaQuery({ query: `RENAME TABLE ${MariaDBQuoteGen(tempTableName)} TO ${MariaDBQuoteGen(table.tableName)}`, config, }); await runSchemaQuery({ query: `DROP TABLE ${MariaDBQuoteGen(backupOldTableName)}`, config, }); } finally { await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config }); } }