import type { BUN_SQLITE_TableSchemaType, CreateDBSchemaParams, } from "../../types"; import { sqlQuoteIdent as q } from "../../utils/sql-safe"; import checkIfTableExists from "./check-if-table-exists"; import createTable from "./create-table"; import getTableColumns from "./get-table-columns"; import resolveTable from "./resolve-table"; type Params = CreateDBSchemaParams & { table: BUN_SQLITE_TableSchemaType; }; /** * # Update an existing table * @description SQLite has limited ALTER TABLE support, so the table is * rebuilt: create a temp table with the new schema, copy the data over, * drop the old table and rename the temp table. */ export default function updateTable(params: Params) { const { db, db_schema, table } = params; console.log(`Updating table: ${table.tableName}`); if (table.isVector) { recreateVectorTable(params); return; } const tempTableName = `${table.tableName}_temp_${Date.now()}`; // Columns are matched by name only: SQLite converts values to the new // column affinity on copy, so a type change keeps the data instead of // silently dropping the column. const existingColumns = getTableColumns({ db, table_name: table.tableName }); const columnsToKeep = resolveTable({ table, db_schema }) .fields.filter( (f) => f.fieldName && existingColumns.find((c) => c.name == f.fieldName), ) .map((f) => f.fieldName as string); // Create temp table with new schema createTable({ ...params, table: { ...table, tableName: tempTableName } }); // Copy data if there are common columns if (columnsToKeep.length > 0) { const columnList = columnsToKeep.map(q).join(", "); db.run( `INSERT INTO ${q(tempTableName)} (${columnList}) SELECT ${columnList} FROM ${q(table.tableName)}`, ); } db.run(`DROP TABLE ${q(table.tableName)}`); db.run(`ALTER TABLE ${q(tempTableName)} RENAME TO ${q(table.tableName)}`); } /** * Vector tables are only rebuilt when `recreate_vector_table` is set. */ function recreateVectorTable(params: Params) { const { db, table, recreate_vector_table } = params; if (!recreate_vector_table) { return; } let existingRows: { [k: string]: any }[] = []; if (checkIfTableExists({ db, table_name: table.tableName })) { existingRows = db .query(`SELECT * FROM ${q(table.tableName)}`) .all() as { [k: string]: any }[]; db.run(`DROP TABLE ${q(table.tableName)}`); } createTable(params); for (const row of existingRows) { const columns = Object.keys(row); const placeholders = columns.map(() => "?").join(", "); db.run( `INSERT INTO ${q(table.tableName)} (${columns.map(q).join(", ")}) VALUES (${placeholders})`, Object.values(row), ); } }