feat(sqlite): support table structure changes

This commit is contained in:
onenewcode authored and GitHub committed 2026-07-10 20:02:56 +08:00
1 parent f7eec00c8e
commit fc8d407a62
25 files changed
+2320 -50

No files matched your search

@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";
import { getTableStructureCapabilities } from "@/lib/table/tableStructureCapabilities";
describe("tableStructureCapabilities", () => {
it("uses table rebuilds only for native SQLite connections", () => {
expect(getTableStructureCapabilities("sqlite", "sqlite")).toMatchObject({
alterStrategy: "sqlite-rebuild",
alterExistingColumn: true,
alterType: true,
});
for (const [databaseType, connectionType] of [
["rqlite", "rqlite"],
["turso", "turso"],
["sqlite", "jdbc"],
["sqlite", undefined],
] as const) {
expect(getTableStructureCapabilities(databaseType, connectionType)).toMatchObject({
alterStrategy: "none",
alterExistingColumn: false,
alterType: false,
});
}
});
it("marks databases with native ALTER COLUMN support as direct", () => {
expect(getTableStructureCapabilities("mysql", "mysql").alterStrategy).toBe("direct");
expect(getTableStructureCapabilities("postgres", "postgres").alterStrategy).toBe("direct");
});
});
@@ -3,6 +3,8 @@ import {
combineDataTypeForDatabase,
createColumnDrafts,
dataTypeLengthInputValue,
getDefaultLengthForType,
hasExistingColumnTypeChange,
isDataTypeLengthDisabled,
isMysqlCharacterDataType,
isMysqlEnumDataType,
@@ -96,6 +98,40 @@ describe("tableStructureEditorState", () => {
expect(combineDataTypeForDatabase("mysql", "integer", "11")).toBe("integer(11)");
});
it("does not add MySQL display lengths when choosing SQLite-family types", () => {
for (const databaseType of ["sqlite", "rqlite", "turso"] as const) {
expect(getDefaultLengthForType(databaseType, "integer")).toBe("");
expect(getDefaultLengthForType(databaseType, "real")).toBe("");
expect(combineDataTypeForDatabase(databaseType, "integer", getDefaultLengthForType(databaseType, "integer"))).toBe("integer");
}
expect(getDefaultLengthForType("mysql", "integer")).toBe("11");
});
it("requires a SQLite rebuild only for a retained existing column type change", () => {
const [column] = createColumnDrafts(
[
{
name: "status",
data_type: "integer",
is_nullable: false,
column_default: null,
is_primary_key: false,
extra: null,
},
],
"sqlite",
);
expect(hasExistingColumnTypeChange([column])).toBe(false);
column.name = "state";
expect(hasExistingColumnTypeChange([column])).toBe(false);
column.dataType = "text";
expect(hasExistingColumnTypeChange([column])).toBe(true);
column.markedForDrop = true;
expect(hasExistingColumnTypeChange([column])).toBe(false);
});
it("strips SQL Server metadata parentheses from editable defaults", () => {
const drafts = createColumnDrafts(
[
+2
View File
@@ -185,6 +185,8 @@ export const buildEditableObjectSource = forward("buildEditableObjectSource");
export const buildRoutineRenameObjectSourceStatements = forward("buildRoutineRenameObjectSourceStatements");
export const buildViewDdlSql = forward("buildViewDdlSql");
export const buildTableStructureChangeSql = forward("buildTableStructureChangeSql");
export const previewSqliteTableStructureChange = forward("previewSqliteTableStructureChange");
export const applySqliteTableStructureChange = forward("applySqliteTableStructureChange");
export const buildCreateTableSql = forward("buildCreateTableSql");
export const buildSingleColumnAlterSql = forward("buildSingleColumnAlterSql");
export const analyzeEditableQueryEditability = forward("analyzeEditableQueryEditability");
+9 -1
View File
@@ -112,7 +112,7 @@ import type {
DataGridSaveStatementOptions,
HiveTablePropertiesSqlOptions,
} from "@/lib/dataGrid/dataGridSql";
import type { BuildTableStructureChangeSqlOptions, BuildSingleColumnAlterSqlOptions, TableStructureChangeSql } from "@/lib/table/tableStructureEditorSql";
import type { BuildTableStructureChangeSqlOptions, BuildSingleColumnAlterSqlOptions, SqliteTableStructureChangePreview, TableStructureChangeSql } from "@/lib/table/tableStructureEditorSql";
import type { BuildTableSelectSqlOptions } from "@/lib/table/tableSelectSql";
import type { DatabaseSearchSql, DatabaseSearchSqlOptions, SearchResultWhereOptions } from "@/lib/database/databaseSearch";
import type { BuildEditableObjectSourceSqlInput, BuildRoutineRenameObjectSourceInput } from "@/lib/table/objectSourceEditor";
@@ -855,6 +855,14 @@ export async function buildTableStructureChangeSql(options: BuildTableStructureC
return post("/api/query/build-table-structure-change-sql", { options });
}
export async function previewSqliteTableStructureChange(connectionId: string, database: string, options: BuildTableStructureChangeSqlOptions): Promise<SqliteTableStructureChangePreview> {
return post("/api/query/preview-sqlite-table-structure-change", { connectionId, database, options });
}
export async function applySqliteTableStructureChange(connectionId: string, database: string, options: BuildTableStructureChangeSqlOptions, schemaRevision: string): Promise<QueryResult> {
return post("/api/query/apply-sqlite-table-structure-change", { connectionId, database, options, schemaRevision });
}
export async function buildCreateTableSql(options: BuildTableStructureChangeSqlOptions): Promise<TableStructureChangeSql> {
return post("/api/query/build-create-table-sql", { options });
}
+9 -1
View File
@@ -52,7 +52,7 @@ import type {
} from "@/lib/dataGrid/dataGridSql";
import type { DataCompareFromTablesOptions, DataCompareFromTablesPreparation, DataCompareSyncPlan, DataCompareSyncPlanOptions, DataComparePreparation, DataComparePreparationOptions } from "@/lib/dataGrid/dataCompare";
import type { SchemaDiffPreparation, SchemaDiffPreparationOptions, TableDiff, FunctionDiff, SequenceDiff, RuleDiff, OwnerDiff } from "@/lib/schema/schemaDiff";
import type { BuildTableStructureChangeSqlOptions, BuildSingleColumnAlterSqlOptions, TableStructureChangeSql } from "@/lib/table/tableStructureEditorSql";
import type { BuildTableStructureChangeSqlOptions, BuildSingleColumnAlterSqlOptions, SqliteTableStructureChangePreview, TableStructureChangeSql } from "@/lib/table/tableStructureEditorSql";
import type { BuildTableSelectSqlOptions } from "@/lib/table/tableSelectSql";
import type { DatabaseSearchSql, DatabaseSearchSqlOptions, SearchResultWhereOptions } from "@/lib/database/databaseSearch";
import type { BuildEditableObjectSourceSqlInput, BuildRoutineRenameObjectSourceInput } from "@/lib/table/objectSourceEditor";
@@ -946,6 +946,14 @@ export async function buildTableStructureChangeSql(options: BuildTableStructureC
return invoke("build_table_structure_change_sql", { options });
}
export async function previewSqliteTableStructureChange(connectionId: string, database: string, options: BuildTableStructureChangeSqlOptions): Promise<SqliteTableStructureChangePreview> {
return invoke("preview_sqlite_table_structure_change", { connectionId, database, options });
}
export async function applySqliteTableStructureChange(connectionId: string, database: string, options: BuildTableStructureChangeSqlOptions, schemaRevision: string): Promise<QueryResult> {
return invoke("apply_sqlite_table_structure_change", { connectionId, database, options, schemaRevision });
}
export async function buildCreateTableSql(options: BuildTableStructureChangeSqlOptions): Promise<TableStructureChangeSql> {
return invoke("build_create_table_sql", { options });
}
@@ -1,9 +1,11 @@
import type { DatabaseType } from "@/types/database";
export type TableStructureDialect = "mysql" | "postgres" | "sqlite" | "duckdb" | "sqlserver" | "oracle" | "h2" | "clickhouse" | "informix" | "influxdb" | "unsupported";
export type TableStructureAlterStrategy = "none" | "direct" | "sqlite-rebuild";
export interface TableStructureCapabilities {
dialect: TableStructureDialect;
alterStrategy: TableStructureAlterStrategy;
createTable: boolean;
addColumn: boolean;
dropColumn: boolean;
@@ -27,6 +29,7 @@ export interface TableStructureCapabilities {
const unsupportedCapabilities: TableStructureCapabilities = {
dialect: "unsupported",
alterStrategy: "none",
createTable: false,
addColumn: false,
dropColumn: false,
@@ -49,7 +52,11 @@ const unsupportedCapabilities: TableStructureCapabilities = {
};
function capabilities(overrides: Partial<TableStructureCapabilities>): TableStructureCapabilities {
return { ...unsupportedCapabilities, ...overrides };
const resolved = { ...unsupportedCapabilities, ...overrides };
if (overrides.alterStrategy === undefined && resolved.alterExistingColumn) {
resolved.alterStrategy = "direct";
}
return resolved;
}
const mysqlCapabilities = capabilities({
@@ -128,6 +135,13 @@ const sqliteCapabilities = capabilities({
indexFilter: true,
});
const nativeSqliteCapabilities = capabilities({
...sqliteCapabilities,
alterStrategy: "sqlite-rebuild",
alterExistingColumn: true,
alterType: true,
});
const duckdbCapabilities = capabilities({
dialect: "duckdb",
createTable: true,
@@ -315,7 +329,8 @@ const capabilityByType: Partial<Record<DatabaseType, TableStructureCapabilities>
manticoresearch: manticoreSearchCapabilities,
};
export function getTableStructureCapabilities(dbType?: DatabaseType): TableStructureCapabilities {
export function getTableStructureCapabilities(dbType?: DatabaseType, connectionDbType?: DatabaseType): TableStructureCapabilities {
if (dbType === "sqlite" && connectionDbType === "sqlite") return nativeSqliteCapabilities;
return dbType ? (capabilityByType[dbType] ?? unsupportedCapabilities) : unsupportedCapabilities;
}
@@ -88,6 +88,10 @@ export interface TableStructureChangeSql {
warnings: string[];
}
export interface SqliteTableStructureChangePreview extends TableStructureChangeSql {
schemaRevision: string;
}
export interface BuildSingleColumnAlterSqlOptions {
databaseType?: DatabaseType;
schema?: string;
@@ -1,6 +1,10 @@
import type { ColumnInfo, DatabaseType, ForeignKeyInfo, IndexInfo, TriggerInfo } from "@/types/database.ts";
import type { ColumnExtra, EditableStructureColumn, EditableStructureForeignKey, EditableStructureIndex, EditableStructureTrigger } from "@/lib/table/tableStructureEditorSql.ts";
export function hasExistingColumnTypeChange(columns: readonly EditableStructureColumn[]): boolean {
return columns.some((column) => !!column.original && !column.markedForDrop && column.dataType !== column.original.data_type);
}
export const DATA_TYPE_OPTIONS: Record<string, string[]> = {
mysql: [
"tinyint",
@@ -938,7 +942,9 @@ function isValidTemporalPrecision(dbType: DatabaseType | undefined, params: stri
export function getDefaultLengthForType(_dbType: DatabaseType | undefined, baseType: string): string {
const key = baseType.trim().toLowerCase();
if (_dbType === "questdb") {
if (_dbType === "sqlite" || _dbType === "rqlite" || _dbType === "turso") {
return "";
} else if (_dbType === "questdb") {
return QUESTDB_TYPE_LENGTHS[key] ?? "";
} else if (_dbType === "sqlserver") {
return SQLSERVER_TYPE_LENGTHS[key] ?? "";