feat(sql): add table generate sql menu

This commit is contained in:
t8y2 committed 2026-06-19 10:53:26 +08:00
1 parent 535d247ae1
commit 59ea4eafdc
10 files changed
+354 -199

No files matched your search

+151
View File
@@ -0,0 +1,151 @@
import type { ColumnInfo, DatabaseType } from "@/types/database";
import { qualifiedTableName, quoteTableIdentifier } from "@/lib/tableSelectSql";
export interface TableSqlTemplateOptions {
databaseType?: DatabaseType;
schema?: string;
tableName: string;
columns?: ColumnInfo[];
}
export function buildTableSelectTemplate(options: TableSqlTemplateOptions): string {
const tableName = templateTableName(options);
const columns = options.columns ?? [];
if (!columns.length) {
return `SELECT *\nFROM ${tableName};`;
}
const selectColumns = columns.map((column) => quoteTableIdentifier(options.databaseType, column.name));
return `SELECT ${selectColumns.join(", ")}\nFROM ${tableName};`;
}
export function buildTableInsertTemplate(options: TableSqlTemplateOptions): string {
const tableName = templateTableName(options);
const columns = (options.columns ?? []).filter((column) => !isAutoGeneratedColumn(column));
if (!columns.length) {
return `INSERT INTO ${tableName}\n/* TODO: add column values */\nVALUES ();`;
}
const columnNames = columns.map((column) => quoteTableIdentifier(options.databaseType, column.name));
const values = columns.map((column) => columnPlaceholderValue(column, options.databaseType));
return `INSERT INTO ${tableName} (${columnNames.join(", ")})\nVALUES (${values.join(", ")});`;
}
export function buildTableUpdateTemplate(options: TableSqlTemplateOptions): string {
const tableName = templateTableName(options);
const columns = options.columns ?? [];
const primaryKeys = columns.filter((column) => column.is_primary_key);
const primaryKeyNames = new Set(primaryKeys.map((column) => column.name));
const updateColumns = columns.filter((column) => !primaryKeyNames.has(column.name));
const setClause = updateColumns.length ? updateColumns.map((column) => `${quoteTableIdentifier(options.databaseType, column.name)} = ${columnPlaceholderValue(column, options.databaseType)}`).join(",\n ") : "/* TODO: set column = value */";
return `UPDATE ${tableName}\nSET ${setClause}\n${buildWhereClause(options.databaseType, primaryKeys)};`;
}
export function buildTableDeleteTemplate(options: TableSqlTemplateOptions): string {
const tableName = templateTableName(options);
const primaryKeys = (options.columns ?? []).filter((column) => column.is_primary_key);
return `DELETE FROM ${tableName}\n${buildWhereClause(options.databaseType, primaryKeys)};`;
}
function templateTableName(options: TableSqlTemplateOptions): string {
return qualifiedTableName({
databaseType: options.databaseType,
schema: options.schema,
tableName: options.tableName,
});
}
function buildWhereClause(databaseType: DatabaseType | undefined, primaryKeys: ColumnInfo[]): string {
if (!primaryKeys.length) return "WHERE /* TODO: add WHERE clause */";
const conditions = primaryKeys.map((column) => `${quoteTableIdentifier(databaseType, column.name)} = ${columnPlaceholderValue(column, databaseType)}`);
return `WHERE ${conditions.join(" AND ")}`;
}
function isAutoGeneratedColumn(column: ColumnInfo): boolean {
const extra = (column.extra ?? "").toLowerCase().trim();
const colDefault = (column.column_default ?? "").toLowerCase().trim();
const dataType = (column.data_type ?? "").trim();
if (typeLooksPostgresTextSearchVector(dataType)) return true;
if (extra.includes("auto_increment")) return true;
if (extra.includes("autoincrement")) return true;
if (extra.includes("identity")) return true;
if (extra.includes("generated") && (extra.includes("as identity") || extra.includes("always as"))) return true;
if (colDefault.includes("nextval(")) return true;
if (colDefault.includes(".nextval")) return true;
if ((extra.includes("virtual") || extra.includes("computed")) && extra.includes("generated")) return true;
return false;
}
function columnPlaceholderValue(column: ColumnInfo, databaseType?: DatabaseType): string {
const dataType = (column.data_type ?? "").trim();
const colName = column.name;
if (typeLooksNumeric(dataType)) return "0";
if (typeLooksBoolean(dataType)) return databaseType === "mysql" || databaseType === "sqlite" || databaseType === "rqlite" ? "1" : "TRUE";
if (typeLooksDateOnly(dataType)) return "'2024-01-01'";
if (typeLooksTimeOnly(dataType)) return "'12:00:00'";
if (typeLooksTimestamp(dataType)) return "CURRENT_TIMESTAMP";
if (typeLooksJson(dataType)) return databaseType === "postgres" || databaseType === "redshift" ? "'{}'::jsonb" : "'{}'";
if (typeLooksBinary(dataType)) return "''";
if (typeLooksUuid(dataType)) return databaseType === "postgres" || databaseType === "redshift" ? "'00000000-0000-0000-0000-000000000000'::uuid" : "'00000000-0000-0000-0000-000000000000'";
if (typeLooksXml(dataType)) return "'<root/>'";
if (typeLooksSpatial(dataType)) return "NULL";
if (typeLooksArray(dataType)) return "'{}'";
return `'${colName}_value'`;
}
function typeLooksNumeric(dataType: string): boolean {
return /^(tinyint|smallint|mediumint|int\d*|integer\d*|bigint|smallserial|serial\d*|bigserial|number|numeric|decimal|dec|float\d*|double|real|double\s+precision|money|smallmoney|fixed)/i.test(dataType.trim());
}
function typeLooksBoolean(dataType: string): boolean {
return /^(bool|boolean|bit\s*\(\s*1\s*\))$/i.test(dataType.trim());
}
function typeLooksDateOnly(dataType: string): boolean {
return /^date$/i.test(dataType.trim());
}
function typeLooksTimeOnly(dataType: string): boolean {
return /^time\b(?!stamp|stamptz|with|without)/i.test(dataType.trim());
}
function typeLooksTimestamp(dataType: string): boolean {
return /^(datetime|timestamp|timestamptz|timestamp\s+with\s+time\s+zone|timestamp\s+without\s+time\s+zone|smalldatetime|datetime2|datetimeoffset)/i.test(dataType.trim());
}
function typeLooksJson(dataType: string): boolean {
return /^(json|jsonb)$/i.test(dataType.trim());
}
function typeLooksBinary(dataType: string): boolean {
return /^(bytea|blob|binary|varbinary|image|raw|longblob|mediumblob|tinyblob|long\s+raw|bfile)$/i.test(dataType.trim());
}
function typeLooksUuid(dataType: string): boolean {
return /^uuid$/i.test(dataType.trim());
}
function typeLooksXml(dataType: string): boolean {
return /^xml$/i.test(dataType.trim());
}
function typeLooksSpatial(dataType: string): boolean {
return /^(geometry|geography|point|polygon|linestring|box|circle|path|line|lseg)/i.test(dataType.trim());
}
function typeLooksArray(dataType: string): boolean {
return /^(array|vector|_float\d*|_int\d*|_text|_varchar|_bool|_uuid)/i.test(dataType.trim());
}
function typeLooksPostgresTextSearchVector(dataType: string): boolean {
const normalized = dataType
.trim()
.replace(/^"+|"+$/g, "")
.toLowerCase();
return normalized === "tsvector" || normalized.endsWith(".tsvector");
}