Files
ai-agent-admin/web/apps/web-ele/src/utils/column-sql.ts
T
2026-06-08 18:14:59 +08:00

593 lines
16 KiB
TypeScript

import { mapToDbType } from '#/utils/database-types';
import {
quoteIdentifier,
quoteTable,
} from '#/views/_core/database-manager/utils/sql-identifier';
export interface ColumnFieldDefinition {
name: string;
type: string;
length?: number;
precision?: number;
scale?: number;
nullable: boolean;
default?: string;
primaryKey: boolean;
unique: boolean;
comment?: string;
}
function normalizeDbType(dbType: string): string {
const db = (dbType || 'postgresql').toLowerCase();
if (db === 'sql server' || db === 'mssql') {
return 'sqlserver';
}
return db;
}
function escapeSqlString(value: string): string {
return value.replaceAll("'", "''");
}
function buildTypeDef(field: ColumnFieldDefinition, dbType: string): string {
const db = normalizeDbType(dbType);
let typeDef = mapToDbType(field.type, dbType);
const typesWithLength = [
'varchar',
'char',
'nvarchar',
'nchar',
'varbinary',
'binary',
];
const typesWithPrecision = ['decimal', 'numeric'];
const mysqlTypesWithPrecision = ['float'];
const lowerType = field.type.toLowerCase();
if (
typesWithLength.includes(lowerType) &&
field.length &&
field.length > 0
) {
typeDef += `(${field.length})`;
} else if (typesWithPrecision.includes(lowerType) && field.precision) {
typeDef += field.scale
? `(${field.precision}, ${field.scale})`
: `(${field.precision})`;
} else if (
db === 'mysql' &&
mysqlTypesWithPrecision.includes(lowerType) &&
field.precision
) {
typeDef += field.scale
? `(${field.precision}, ${field.scale})`
: `(${field.precision})`;
}
return typeDef;
}
export function buildColumnDefinition(
field: ColumnFieldDefinition,
dbType: string,
): string {
const db = normalizeDbType(dbType);
const col = quoteIdentifier(field.name, db);
let def = `${col} ${buildTypeDef(field, dbType)}`;
if (!field.nullable) {
def += ' NOT NULL';
}
if (field.default) {
def += ` DEFAULT ${field.default}`;
}
if (field.comment && db === 'mysql') {
def += ` COMMENT '${escapeSqlString(field.comment)}'`;
}
return def;
}
function buildSqlServerColumnExtendedProperty(
tableName: string,
columnName: string,
comment: string,
schema: string,
isUpdate: boolean,
): string {
const proc = isUpdate ? 'sp_updateextendedproperty' : 'sp_addextendedproperty';
return `EXEC ${proc} @name=N'MS_Description', @value=N'${escapeSqlString(comment)}', @level0type=N'SCHEMA', @level0name=N'${escapeSqlString(schema)}', @level1type=N'TABLE', @level1name=N'${escapeSqlString(tableName)}', @level2type=N'COLUMN', @level2name=N'${escapeSqlString(columnName)}';`;
}
function buildSqlServerDropDefaultSql(
tableRef: string,
tableName: string,
columnName: string,
schema: string,
): string {
const schemaName = escapeSqlString(schema || 'dbo');
const table = escapeSqlString(tableName);
const column = escapeSqlString(columnName);
return `DECLARE @sql NVARCHAR(MAX);
SELECT @sql = N'ALTER TABLE ${tableRef} DROP CONSTRAINT ' + QUOTENAME(dc.name)
FROM sys.default_constraints dc
INNER JOIN sys.columns col ON dc.parent_object_id = col.object_id AND dc.parent_column_id = col.column_id
INNER JOIN sys.tables tab ON col.object_id = tab.object_id
INNER JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
WHERE sch.name = N'${schemaName}' AND tab.name = N'${table}' AND col.name = N'${column}';
IF @sql IS NOT NULL EXEC sp_executesql @sql;`;
}
function buildSqlServerTableExtendedProperty(
tableName: string,
comment: string,
schema: string,
isUpdate: boolean,
): string {
const proc = isUpdate ? 'sp_updateextendedproperty' : 'sp_addextendedproperty';
return `EXEC ${proc} @name=N'MS_Description', @value=N'${escapeSqlString(comment)}', @level0type=N'SCHEMA', @level0name=N'${escapeSqlString(schema)}', @level1type=N'TABLE', @level1name=N'${escapeSqlString(tableName)}';`;
}
export function buildAddColumnSql(
tableRef: string,
field: ColumnFieldDefinition,
dbType: string,
options?: { schema?: string; tableName?: string; hadComment?: boolean },
): string[] {
const db = normalizeDbType(dbType);
const columnDef = buildColumnDefinition(field, dbType);
const statements: string[] = [];
switch (db) {
case 'mysql': {
statements.push(`ALTER TABLE ${tableRef} ADD COLUMN ${columnDef};`);
break;
}
case 'sqlserver': {
statements.push(`ALTER TABLE ${tableRef} ADD ${columnDef};`);
if (field.comment && options?.tableName) {
statements.push(
buildSqlServerColumnExtendedProperty(
options.tableName,
field.name,
field.comment,
options.schema || 'dbo',
false,
),
);
}
break;
}
case 'oracle': {
statements.push(`ALTER TABLE ${tableRef} ADD (${columnDef});`);
if (field.comment) {
statements.push(
`COMMENT ON COLUMN ${tableRef}.${quoteIdentifier(field.name, db)} IS '${escapeSqlString(field.comment)}';`,
);
}
break;
}
case 'postgresql':
default: {
statements.push(`ALTER TABLE ${tableRef} ADD COLUMN ${columnDef};`);
if (field.comment) {
statements.push(
`COMMENT ON COLUMN ${tableRef}.${quoteIdentifier(field.name, db)} IS '${escapeSqlString(field.comment)}';`,
);
}
break;
}
}
return statements;
}
export function buildDropColumnSql(
tableRef: string,
columnName: string,
dbType: string,
): string {
const db = normalizeDbType(dbType);
return `ALTER TABLE ${tableRef} DROP COLUMN ${quoteIdentifier(columnName, db)};`;
}
export function buildTableCommentAlterSql(
tableRef: string,
tableName: string,
comment: string,
dbType: string,
schema?: string,
hadComment?: boolean,
): string | null {
const db = normalizeDbType(dbType);
const escaped = escapeSqlString(comment);
switch (db) {
case 'postgresql':
case 'oracle': {
return `COMMENT ON TABLE ${tableRef} IS '${escaped}';`;
}
case 'mysql': {
return `ALTER TABLE ${tableRef} COMMENT = '${escaped}';`;
}
case 'sqlserver': {
return buildSqlServerTableExtendedProperty(
tableName,
comment,
schema || 'dbo',
Boolean(hadComment),
);
}
default: {
return null;
}
}
}
export function buildColumnCommentAlterSql(
tableRef: string,
tableName: string,
field: ColumnFieldDefinition,
dbType: string,
schema?: string,
hadComment?: boolean,
): string[] {
const db = normalizeDbType(dbType);
const col = quoteIdentifier(field.name, db);
const comment = field.comment || '';
const statements: string[] = [];
switch (db) {
case 'postgresql':
case 'oracle': {
statements.push(
`COMMENT ON COLUMN ${tableRef}.${col} IS '${escapeSqlString(comment)}';`,
);
break;
}
case 'mysql': {
const columnDef = buildColumnDefinition(field, dbType);
statements.push(`ALTER TABLE ${tableRef} MODIFY COLUMN ${columnDef};`);
break;
}
case 'sqlserver': {
statements.push(
buildSqlServerColumnExtendedProperty(
tableName,
field.name,
comment,
schema || 'dbo',
Boolean(hadComment),
),
);
break;
}
default: {
break;
}
}
return statements;
}
export function buildFieldAlterSql(
originalFields: ColumnFieldDefinition[],
currentFields: ColumnFieldDefinition[],
tableRef: string,
tableName: string,
dbType: string,
schema?: string,
): string[] {
const db = normalizeDbType(dbType);
const statements: string[] = [];
const colRef = (name: string) => quoteIdentifier(name, db);
for (const origField of originalFields) {
if (!currentFields.find((f) => f.name === origField.name)) {
statements.push(buildDropColumnSql(tableRef, origField.name, dbType));
}
}
for (const field of currentFields) {
if (!originalFields.find((f) => f.name === field.name)) {
statements.push(
...buildAddColumnSql(tableRef, field, dbType, {
schema,
tableName,
}),
);
}
}
for (const field of currentFields) {
const origField = originalFields.find((f) => f.name === field.name);
if (!origField || JSON.stringify(field) === JSON.stringify(origField)) {
continue;
}
const col = colRef(field.name);
const typeDef = buildTypeDef(field, dbType);
const origTypeDef = buildTypeDef(origField, dbType);
if (db === 'postgresql') {
const typeChanged =
field.type !== origField.type ||
field.length !== origField.length ||
field.precision !== origField.precision ||
field.scale !== origField.scale;
if (typeChanged) {
statements.push(
`ALTER TABLE ${tableRef} ALTER COLUMN ${col} TYPE ${typeDef} USING ${col}::${typeDef};`,
);
}
if (field.nullable !== origField.nullable) {
statements.push(
`ALTER TABLE ${tableRef} ALTER COLUMN ${col} ${field.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'};`,
);
}
if (field.default !== origField.default) {
if (field.default) {
statements.push(
`ALTER TABLE ${tableRef} ALTER COLUMN ${col} SET DEFAULT ${field.default};`,
);
} else {
statements.push(
`ALTER TABLE ${tableRef} ALTER COLUMN ${col} DROP DEFAULT;`,
);
}
}
if (field.unique !== origField.unique) {
if (field.unique) {
statements.push(
`ALTER TABLE ${tableRef} ADD CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)} UNIQUE (${col});`,
);
} else {
statements.push(
`ALTER TABLE ${tableRef} DROP CONSTRAINT IF EXISTS ${colRef(`${tableName}_${field.name}_key`)};`,
);
}
}
if (field.primaryKey !== origField.primaryKey) {
if (field.primaryKey) {
statements.push(
`ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`,
);
} else {
statements.push(
`ALTER TABLE ${tableRef} DROP CONSTRAINT IF EXISTS ${colRef(`${tableName}_pkey`)};`,
);
}
}
if (field.comment !== origField.comment) {
statements.push(
...buildColumnCommentAlterSql(
tableRef,
tableName,
field,
dbType,
schema,
Boolean(origField.comment),
),
);
}
} else if (db === 'mysql') {
statements.push(
`ALTER TABLE ${tableRef} MODIFY COLUMN ${buildColumnDefinition(field, dbType)};`,
);
if (field.unique !== origField.unique) {
if (field.unique) {
statements.push(
`ALTER TABLE ${tableRef} ADD UNIQUE INDEX ${colRef(`${tableName}_${field.name}_key`)} (${col});`,
);
} else {
statements.push(
`ALTER TABLE ${tableRef} DROP INDEX IF EXISTS ${colRef(`${tableName}_${field.name}_key`)};`,
);
}
}
if (field.primaryKey !== origField.primaryKey) {
if (field.primaryKey) {
statements.push(`ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`);
} else {
statements.push(`ALTER TABLE ${tableRef} DROP PRIMARY KEY;`);
}
}
} else if (db === 'sqlserver') {
if (typeDef !== origTypeDef) {
let alterDef = `${col} ${typeDef}`;
alterDef += field.nullable ? ' NULL' : ' NOT NULL';
statements.push(`ALTER TABLE ${tableRef} ALTER COLUMN ${alterDef};`);
} else if (field.nullable !== origField.nullable) {
let alterDef = `${col} ${typeDef}`;
alterDef += field.nullable ? ' NULL' : ' NOT NULL';
statements.push(`ALTER TABLE ${tableRef} ALTER COLUMN ${alterDef};`);
}
if (field.default !== origField.default) {
if (field.default) {
statements.push(
`ALTER TABLE ${tableRef} ADD DEFAULT ${field.default} FOR ${col};`,
);
} else if (origField.default) {
statements.push(
buildSqlServerDropDefaultSql(
tableRef,
tableName,
field.name,
schema || 'dbo',
),
);
}
}
if (field.comment !== origField.comment) {
statements.push(
...buildColumnCommentAlterSql(
tableRef,
tableName,
field,
dbType,
schema,
Boolean(origField.comment),
),
);
}
if (field.unique !== origField.unique) {
if (field.unique) {
statements.push(
`ALTER TABLE ${tableRef} ADD CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)} UNIQUE (${col});`,
);
} else {
statements.push(
`ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)};`,
);
}
}
if (field.primaryKey !== origField.primaryKey) {
if (field.primaryKey) {
statements.push(`ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`);
} else {
statements.push(
`ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_pkey`)};`,
);
}
}
} else if (db === 'oracle') {
const structureChanged =
typeDef !== origTypeDef ||
field.nullable !== origField.nullable ||
field.default !== origField.default;
if (structureChanged) {
let modifyDef = `${col} ${typeDef}`;
modifyDef += field.nullable ? ' NULL' : ' NOT NULL';
if (field.default !== origField.default) {
modifyDef += field.default
? ` DEFAULT ${field.default}`
: ' DEFAULT NULL';
}
statements.push(`ALTER TABLE ${tableRef} MODIFY (${modifyDef});`);
}
if (field.comment !== origField.comment) {
statements.push(
...buildColumnCommentAlterSql(
tableRef,
tableName,
field,
dbType,
schema,
Boolean(origField.comment),
),
);
}
if (field.unique !== origField.unique) {
if (field.unique) {
statements.push(
`ALTER TABLE ${tableRef} ADD CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)} UNIQUE (${col});`,
);
} else {
statements.push(
`ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_${field.name}_key`)};`,
);
}
}
if (field.primaryKey !== origField.primaryKey) {
if (field.primaryKey) {
statements.push(`ALTER TABLE ${tableRef} ADD PRIMARY KEY (${col});`);
} else {
statements.push(
`ALTER TABLE ${tableRef} DROP CONSTRAINT ${colRef(`${tableName}_pk`)};`,
);
}
}
}
}
return statements;
}
/** 建表后追加表/列注释 SQL */
export function buildCreateTableCommentSql(
tableName: string,
schema: string | undefined,
dbType: string,
tableComment: string,
fields: ColumnFieldDefinition[],
): string[] {
const db = normalizeDbType(dbType);
const tableRef = quoteTable(schema, tableName, db);
const statements: string[] = [];
if (tableComment) {
switch (db) {
case 'postgresql':
case 'oracle': {
statements.push(
`COMMENT ON TABLE ${tableRef} IS '${escapeSqlString(tableComment)}';`,
);
break;
}
case 'mysql': {
statements.push(
`ALTER TABLE ${tableRef} COMMENT = '${escapeSqlString(tableComment)}';`,
);
break;
}
case 'sqlserver': {
statements.push(
buildSqlServerTableExtendedProperty(
tableName,
tableComment,
schema || 'dbo',
false,
),
);
break;
}
default: {
break;
}
}
}
for (const field of fields) {
if (!field.comment) {
continue;
}
switch (db) {
case 'postgresql':
case 'oracle': {
statements.push(
`COMMENT ON COLUMN ${tableRef}.${quoteIdentifier(field.name, db)} IS '${escapeSqlString(field.comment)}';`,
);
break;
}
case 'sqlserver': {
statements.push(
buildSqlServerColumnExtendedProperty(
tableName,
field.name,
field.comment,
schema || 'dbo',
false,
),
);
break;
}
default: {
break;
}
}
}
return statements;
}