Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,592 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user