-
+
@@ -26,7 +26,7 @@
:width="item.width"
>
-
+
-
+
-
+
-
+
-
+
-
+
-
+ />
-
+
- 删除
+
+
+ 删除
+
+
@@ -110,15 +105,11 @@
- 删除
+
+
+ 删除
+
+
@@ -130,6 +121,7 @@
+ 取消
保存
@@ -187,22 +179,27 @@ const state = reactive({
{
prop: 'name',
label: '字段名称',
+ width: 200,
},
{
prop: 'type',
label: '字段类型',
+ width: 120,
},
{
prop: 'length',
label: '长度',
+ width: 120,
},
{
prop: 'numScale',
label: '小数点',
+ width: 120,
},
{
prop: 'value',
label: '默认值',
+ width: 120,
},
{
@@ -231,6 +228,7 @@ const state = reactive({
},
] as ColName[],
res: [] as RowDefinition[],
+ oldFields: [] as RowDefinition[],
},
indexs: {
colNames: [
@@ -261,10 +259,12 @@ const state = reactive({
],
columns: [{ name: '', remark: '' }],
res: [] as IndexDefinition[],
+ oldIndexs: [] as IndexDefinition[],
},
tableName: '',
tableComment: '',
height: 450,
+ db: '',
},
});
@@ -359,7 +359,10 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
nowArr.forEach((a) => {
let k = a[key];
newMap[k] = a;
- if (!oldMap.hasOwnProperty(k)) {
+ // 取oldName,因为修改了name,但是oldName不会变
+ let oldName = a['oldName'];
+ oldName && (newMap[oldName] = a);
+ if (!oldMap.hasOwnProperty(k) && (!oldName || (oldName && !oldMap.hasOwnProperty(oldName)))) {
// 新增
data.add.push(a);
}
@@ -376,7 +379,7 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
for (let f in a) {
let oldV = a[f];
let newV = newData[f];
- if (oldV.toString() !== newV.toString()) {
+ if (oldV?.toString() !== newV?.toString()) {
data.upd.push(newData);
break;
}
@@ -399,11 +402,11 @@ const genSql = () => {
// 修改
if (state.activeName === '1') {
// 修改列
- let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name');
- return dbDialect.getModifyColumnSql(data.tableName, changeData);
+ let changeData = filterChangedData(state.tableData.fields.oldFields, state.tableData.fields.res, 'name');
+ return dbDialect.getModifyColumnSql(data, data.tableName, changeData);
} else if (state.activeName === '2') {
// 修改索引
- let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName');
+ let changeData = filterChangedData(state.tableData.indexs.oldIndexs, state.tableData.indexs.res, 'indexName');
return dbDialect.getModifyIndexSql(data.tableName, changeData);
}
}
@@ -456,7 +459,6 @@ const indexChanges = (row: any) => {
row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`;
};
-const oldData = { indexs: [] as any[], fields: [] as RowDefinition[] };
watch(
() => props.data,
(newValue: any) => {
@@ -464,9 +466,10 @@ watch(
// 回显表名表注释
state.tableData.tableName = row.tableName;
state.tableData.tableComment = row.tableComment;
+ state.tableData.db = props.db!;
// 回显列
if (columns && Array.isArray(columns) && columns.length > 0) {
- oldData.fields = [];
+ state.tableData.fields.oldFields = [];
state.tableData.fields.res = [];
// 索引列下拉选
state.tableData.indexs.columns = [];
@@ -474,10 +477,17 @@ watch(
let typeObj = a.columnType.replace(')', '').split('(');
let type = typeObj[0];
let length = (typeObj.length > 1 && typeObj[1]) || '';
+ let defaultValue = '';
+ if (a.columnDefault) {
+ defaultValue = a.columnDefault.trim().replace(/^'|'$/g, '');
+ // 解决高斯的默认值问题
+ defaultValue = defaultValue.replace("'::character varying", '');
+ }
let data = {
name: a.columnName,
+ oldName: a.columnName,
type,
- value: a.columnDefault || '',
+ value: defaultValue,
length,
numScale: a.numScale,
notNull: a.nullable !== 'YES',
@@ -486,14 +496,14 @@ watch(
remark: a.columnComment,
};
state.tableData.fields.res.push(data);
- oldData.fields.push(JSON.parse(JSON.stringify(data)));
+ state.tableData.fields.oldFields.push(JSON.parse(JSON.stringify(data)));
// 索引字段下拉选项
state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment });
});
}
// 回显索引
if (indexs && Array.isArray(indexs) && indexs.length > 0) {
- oldData.indexs = [];
+ state.tableData.indexs.oldIndexs = [];
state.tableData.indexs.res = [];
// 索引过滤掉主键
indexs
@@ -507,7 +517,7 @@ watch(
indexComment: a.indexComment,
};
state.tableData.indexs.res.push(data);
- oldData.indexs.push(JSON.parse(JSON.stringify(data)));
+ state.tableData.indexs.oldIndexs.push(JSON.parse(JSON.stringify(data)));
});
}
}
diff --git a/mayfly_go_web/src/views/ops/db/component/table/DbTablesOp.vue b/mayfly_go_web/src/views/ops/db/component/table/DbTablesOp.vue
index 70ea953a..c4b2f58a 100644
--- a/mayfly_go_web/src/views/ops/db/component/table/DbTablesOp.vue
+++ b/mayfly_go_web/src/views/ops/db/component/table/DbTablesOp.vue
@@ -181,7 +181,7 @@ const state = reactive({
visible: false,
activeName: '1',
type: '',
- enableEditTypes: [DbType.mysql, DbType.mariadb, DbType.postgresql, DbType.dm, DbType.oracle], // 支持"编辑表"的数据库类型
+ enableEditTypes: [DbType.mysql, DbType.mariadb, DbType.postgresql, DbType.dm, DbType.oracle, DbType.sqlite], // 支持"编辑表"的数据库类型
data: {
// 修改表时,传递修改数据
edit: false,
diff --git a/mayfly_go_web/src/views/ops/db/dialect/dm_dialect.ts b/mayfly_go_web/src/views/ops/db/dialect/dm_dialect.ts
index cbc71e6c..29b00066 100644
--- a/mayfly_go_web/src/views/ops/db/dialect/dm_dialect.ts
+++ b/mayfly_go_web/src/views/ops/db/dialect/dm_dialect.ts
@@ -54,6 +54,7 @@ const DM_TYPE_LIST: sqlColumnType[] = [
{ udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '100G-1' },
];
+// 参考官方文档:https://eco.dameng.com/document/dm/zh-cn/pm/function.html
const replaceFunctions: EditorCompletionItem[] = [
// 数值函数
{ label: 'ABS', insertText: 'ABS(n)', description: '求数值 n 的绝对值' },
@@ -367,7 +368,7 @@ class DMDialect implements DbDialect {
dmDialectInfo = {
icon: 'iconfont icon-db-dm',
defaultPort: 5236,
- formatSqlDialect: 'postgresql',
+ formatSqlDialect: 'plsql',
columnTypes: DM_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
editorCompletions,
};
@@ -500,7 +501,9 @@ class DMDialect implements DbDialect {
// 默认值
let defVal = this.getDefaultValueSql(cl);
let incr = cl.auto_increment ? 'IDENTITY' : '';
- return ` "${cl.name}" ${cl.type}${length} ${incr} ${cl.notNull ? 'NOT NULL' : ''} ${defVal} `;
+ // 如果有原名以原名为准
+ let name = cl.oldName && cl.name !== cl.oldName ? cl.oldName : cl.name;
+ return ` ${this.quoteIdentifier(name)} ${cl.type}${length} ${incr} ${cl.notNull ? 'NOT NULL' : ''} ${defVal} `;
}
getCreateTableSql(data: any): string {
@@ -546,32 +549,75 @@ class DMDialect implements DbDialect {
return sql.join(';');
}
- getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
- let sql: string[] = [];
+ getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
+ let schemaArr = tableData.db.split('/');
+ let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
+
+ let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableName)}`;
+
+ let modifySql = '';
+ let dropSql = '';
+ let renameSql = '';
+ let commentSql = '';
+
+ // 主键字段
+ let priArr = new Set();
+
if (changeData.add.length > 0) {
changeData.add.forEach((a) => {
- sql.push(`ALTER TABLE "${tableName}" add COLUMN ${this.genColumnBasicSql(a)}`);
+ modifySql += `ALTER TABLE ${dbTable} add COLUMN ${this.genColumnBasicSql(a)};`;
if (a.remark) {
- sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`);
+ commentSql += `COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} IS '${a.remark}';`;
+ }
+ if (a.pri) {
+ priArr.add(`"${a.name}"`);
}
});
}
if (changeData.upd.length > 0) {
changeData.upd.forEach((a) => {
- sql.push(`ALTER TABLE "${tableName}" MODIFY ${this.genColumnBasicSql(a)}`);
- if (a.remark) {
- sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`);
+ let cmtSql = `COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} IS '${a.remark}';`;
+ if (a.remark && a.oldName === a.name) {
+ commentSql += cmtSql;
+ }
+ // 修改了字段名
+ if (a.oldName !== a.name) {
+ renameSql += `ALTER TABLE ${dbTable} RENAME COLUMN ${this.quoteIdentifier(a.oldName!)} TO ${this.quoteIdentifier(a.name)};`;
+ if (a.remark) {
+ commentSql += cmtSql;
+ }
+ }
+ modifySql += `ALTER TABLE ${dbTable} MODIFY ${this.genColumnBasicSql(a)};`;
+ if (a.pri) {
+ priArr.add(`${this.quoteIdentifier(a.name)}`);
}
});
}
if (changeData.del.length > 0) {
changeData.del.forEach((a) => {
- sql.push(`ALTER TABLE "${tableName}" DROP COLUMN ${a.name}`);
+ dropSql += `ALTER TABLE ${dbTable} DROP COLUMN ${a.name};`;
});
}
- return sql.join(';');
+
+ // 编辑主键
+ let dropPkSql = '';
+ if (priArr.size > 0) {
+ let resPri = tableData.fields.res.filter((a: RowDefinition) => a.pri);
+ if (resPri) {
+ priArr.add(`${this.quoteIdentifier(resPri.name)}`);
+ }
+ // 如果有编辑主键字段,则删除主键,再添加主键
+ // 解析表字段中是否含有主键,有的话就删除主键
+ if (tableData.fields.oldFields.find((a: RowDefinition) => a.pri)) {
+ dropPkSql = `ALTER TABLE ${dbTable} DROP PRIMARY KEY;`;
+ }
+ }
+
+ let addPkSql = priArr.size > 0 ? `ALTER TABLE ${dbTable} ADD PRIMARY KEY (${Array.from(priArr).join(',')});` : '';
+
+ return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
}
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
diff --git a/mayfly_go_web/src/views/ops/db/dialect/index.ts b/mayfly_go_web/src/views/ops/db/dialect/index.ts
index 8295b2cd..8a92f7dd 100644
--- a/mayfly_go_web/src/views/ops/db/dialect/index.ts
+++ b/mayfly_go_web/src/views/ops/db/dialect/index.ts
@@ -3,6 +3,7 @@ import { PostgresqlDialect } from './postgres_dialect';
import { DMDialect } from '@/views/ops/db/dialect/dm_dialect';
import { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect';
import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect';
+import { SqliteDialect } from '@/views/ops/db/dialect/sqlite_dialect';
export interface sqlColumnType {
udtName: string;
@@ -14,6 +15,7 @@ export interface sqlColumnType {
export interface RowDefinition {
name: string;
+ oldName?: string;
type: string;
value: string;
length: string;
@@ -110,6 +112,7 @@ export const DbType = {
postgresql: 'postgres',
dm: 'dm', // 达梦
oracle: 'oracle',
+ sqlite: 'sqlite',
};
export const compatibleMysql = (dbType: string): boolean => {
@@ -164,10 +167,11 @@ export interface DbDialect {
/**
* 生成编辑列sql
+ * @param tableData 表数据,包含表名、列数据、索引数据
* @param tableName 表名
* @param changeData 改变信息
*/
- getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string;
+ getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string;
/**
* 生成编辑索引sql
@@ -177,7 +181,7 @@ export interface DbDialect {
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string;
/** 通过数据库字段类型,返回基本数据类型 */
- getDataType: (columnType: string) => DataType;
+ getDataType(columnType: string): DataType;
/** 包装字符串数据, 如:oracle需要把date类型改为 to_date(str, 'yyyy-mm-dd hh24:mi:ss') */
wrapStrValue(columnType: string, value: string): string;
@@ -188,6 +192,7 @@ let mariadbDialect = new MariadbDialect();
let postgresDialect = new PostgresqlDialect();
let dmDialect = new DMDialect();
let oracleDialect = new OracleDialect();
+let sqliteDialect = new SqliteDialect();
export const getDbDialect = (dbType: string | undefined): DbDialect => {
if (!dbType) {
@@ -204,6 +209,8 @@ export const getDbDialect = (dbType: string | undefined): DbDialect => {
return dmDialect;
case DbType.oracle:
return oracleDialect;
+ case DbType.sqlite:
+ return sqliteDialect;
default:
throw new Error('不支持的数据库');
}
diff --git a/mayfly_go_web/src/views/ops/db/dialect/mysql_dialect.ts b/mayfly_go_web/src/views/ops/db/dialect/mysql_dialect.ts
index adc2c8b0..de3a37db 100644
--- a/mayfly_go_web/src/views/ops/db/dialect/mysql_dialect.ts
+++ b/mayfly_go_web/src/views/ops/db/dialect/mysql_dialect.ts
@@ -4,6 +4,7 @@ import { language as mysqlLanguage } from 'monaco-editor/esm/vs/basic-languages/
export { MYSQL_TYPE_LIST, MysqlDialect };
+// 参考官方文档:https://dev.mysql.com/doc/refman/8.0/en/data-types.html
const MYSQL_TYPE_LIST = [
'bigint',
'binary',
@@ -31,6 +32,7 @@ const MYSQL_TYPE_LIST = [
'varchar',
];
+// 参考官方文档:https://dev.mysql.com/doc/refman/8.3/en/functions.html
const replaceFunctions: EditorCompletionItem[] = [
/** 字符串相关函数 */
{ label: 'CONCAT', insertText: 'CONCAT(str1,str2,...)', description: '多字符串合并' },
@@ -193,7 +195,7 @@ class MysqlDialect implements DbDialect {
let defVal = val ? `DEFAULT ${val}` : '';
let length = cl.length ? `(${cl.length})` : '';
let onUpdate = 'update_time' === cl.name ? ' ON UPDATE CURRENT_TIMESTAMP ' : '';
- return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${
+ return ` ${this.quoteIdentifier(cl.name)} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${
cl.auto_increment ? 'AUTO_INCREMENT' : ''
} ${defVal} ${onUpdate} comment '${cl.remark || ''}' `;
}
@@ -223,35 +225,31 @@ class MysqlDialect implements DbDialect {
return sql.substring(0, sql.length - 1) + ';';
}
- getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
- let addSql = '',
- updSql = '',
- delSql = '';
- if (changeData.add.length > 0) {
- addSql = `ALTER TABLE ${tableName}`;
- changeData.add.forEach((a) => {
- addSql += ` ADD ${this.genColumnBasicSql(a)},`;
+ getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
+ let sql = `ALTER TABLE ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)}`;
+ let arr = [] as string[];
+ if (changeData.del.length > 0) {
+ changeData.del.forEach((a) => {
+ arr.push(` DROP COLUMN ${this.quoteIdentifier(a.name)} `);
+ });
+ }
+ if (changeData.add.length > 0) {
+ changeData.add.forEach((a) => {
+ arr.push(` ADD COLUMN ${this.genColumnBasicSql(a)} `);
});
- addSql = addSql.substring(0, addSql.length - 1);
- addSql += ';';
}
if (changeData.upd.length > 0) {
- updSql = `ALTER TABLE ${tableName}`;
- let arr = [] as string[];
changeData.upd.forEach((a) => {
- arr.push(` MODIFY ${this.genColumnBasicSql(a)}`);
+ if (a.name === a.oldName) {
+ arr.push(` MODIFY COLUMN ${this.genColumnBasicSql(a)} `);
+ } else {
+ arr.push(` CHANGE COLUMN ${this.quoteIdentifier(a.oldName!)} ${this.genColumnBasicSql(a)} `);
+ }
});
- updSql += arr.join(',');
- updSql += ';';
}
- if (changeData.del.length > 0) {
- changeData.del.forEach((a) => {
- delSql += ` ALTER TABLE ${tableName} DROP COLUMN ${a.name}; `;
- });
- }
- return addSql + updSql + delSql;
+ return sql + arr.join(',') + ';';
}
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
diff --git a/mayfly_go_web/src/views/ops/db/dialect/oracle_dialect.ts b/mayfly_go_web/src/views/ops/db/dialect/oracle_dialect.ts
index 349e0bfb..7ba5822e 100644
--- a/mayfly_go_web/src/views/ops/db/dialect/oracle_dialect.ts
+++ b/mayfly_go_web/src/views/ops/db/dialect/oracle_dialect.ts
@@ -14,7 +14,7 @@ import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sq
export { OracleDialect, ORACLE_TYPE_LIST };
-// 参考文档:https://eco.dameng.com/document/dm/zh-cn/sql-dev/dmpl-sql-datatype.html#%E5%AD%97%E7%AC%A6%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B
+// 参考官方文档:https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements001.htm
const ORACLE_TYPE_LIST: sqlColumnType[] = [
// 字符数据类型
{ udtName: 'CHAR', dataType: 'CHAR', desc: '定长字符串,自动在末尾用空格补全,非unicode', space: '', range: '1 - 2000' },
@@ -50,6 +50,7 @@ const ORACLE_TYPE_LIST: sqlColumnType[] = [
{ udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '' },
];
+// 参考官方文档:https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions001.htm
const replaceFunctions: EditorCompletionItem[] = [
// 字符函数
{ label: 'ASCII', insertText: 'ASCII(x)', description: '返回字符X的ASCII码' },
@@ -268,16 +269,22 @@ class OracleDialect implements DbDialect {
return '';
}
- genColumnBasicSql(cl: RowDefinition): string {
+ genColumnBasicSql(cl: RowDefinition, create: boolean): string {
let length = this.getTypeLengthSql(cl);
// 默认值
let defVal = this.getDefaultValueSql(cl);
- let incr = cl.auto_increment ? 'generated by default as IDENTITY' : '';
- let pri = cl.pri ? 'PRIMARY KEY' : '';
- return ` ${cl.name.toUpperCase()} ${cl.type}${length} ${incr} ${pri} ${defVal} ${cl.notNull ? 'NOT NULL' : ''} `;
+ let incr = cl.auto_increment && create ? 'generated by default as IDENTITY' : '';
+ // 如果有原名以原名为准
+ let name = cl.oldName && cl.name !== cl.oldName ? cl.oldName : cl.name;
+ let baseSql = ` ${this.quoteIdentifier(name)} ${cl.type}${length} ${incr}`;
+ return incr ? baseSql : ` ${baseSql} ${defVal} ${cl.notNull ? 'NOT NULL' : ''} `;
}
getCreateTableSql(data: any): string {
+ let schemaArr = data.db.split('/');
+ let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
+ let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(data.tableName)}`;
+
let createSql = '';
let tableCommentSql = '';
let columCommentSql = '';
@@ -285,17 +292,17 @@ class OracleDialect implements DbDialect {
// 创建表结构
let fields: string[] = [];
data.fields.res.forEach((item: any) => {
- item.name && fields.push(this.genColumnBasicSql(item));
+ item.name && fields.push(this.genColumnBasicSql(item, true));
// 列注释
if (item.remark) {
- columCommentSql += ` comment on column ${data.tableName?.toUpperCase()}.${item.name?.toUpperCase()} is '${item.remark}'; `;
+ columCommentSql += ` COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(item.name)} is '${item.remark}'; `;
}
});
// 建表
- createSql = `CREATE TABLE ${data.tableName?.toUpperCase()} ( ${fields.join(',')} );`;
+ createSql = `CREATE TABLE ${dbTable} ( ${fields.join(',')} );`;
// 表注释
if (data.tableComment) {
- tableCommentSql = ` comment on table ${data.tableName?.toUpperCase()} is '${data.tableComment}'; `;
+ tableCommentSql = ` COMMENT ON TABLE ${dbTable} is '${data.tableComment}'; `;
}
return createSql + tableCommentSql + columCommentSql;
@@ -304,40 +311,92 @@ class OracleDialect implements DbDialect {
getCreateIndexSql(tableData: any): string {
// CREATE UNIQUE INDEX idx_column_name ON your_table (column1, column2);
// COMMENT ON INDEX idx_column_name IS 'Your index comment here';
- // 创建索引
+
+ let schemaArr = tableData.db.split('/');
+ let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
+ let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableData.tableName)}`;
+
let sql: string[] = [];
tableData.indexs.res.forEach((a: any) => {
- sql.push(` CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName} ON "${tableData.tableName}" ("${a.columnNames.join('","')})"`);
+ sql.push(` CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName} ON ${dbTable} ("${a.columnNames.join('","')})"`);
});
return sql.join(';');
}
- getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
- let sql: string[] = [];
- if (changeData.add.length > 0) {
- changeData.add.forEach((a) => {
- sql.push(`ALTER TABLE "${tableName}" add COLUMN ${this.genColumnBasicSql(a)}`);
- if (a.remark) {
- sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`);
+ getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
+ let schemaArr = tableData.db.split('/');
+ let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
+ let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableName)}`;
+
+ let baseSql = `ALTER TABLE ${dbTable} `;
+
+ let modifyArr: string[] = [];
+ let dropArr: string[] = [];
+ // 重命名的sql要一条条执行
+ let renameArr: string[] = [];
+ let commentArr: string[] = [];
+
+ // 主键字段
+ let priArr = new Set();
+
+ if (changeData.upd.length > 0) {
+ changeData.upd.forEach((a) => {
+ let commentSql = `COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} IS '${a.remark}'`;
+ if (a.remark && a.oldName === a.name) {
+ commentArr.push(commentSql);
+ }
+ // 修改了字段名
+ if (a.oldName !== a.name) {
+ renameArr.push(baseSql + ` RENAME COLUMN ${this.quoteIdentifier(a.oldName!)} TO ${this.quoteIdentifier(a.name)} ;`);
+ if (a.remark) {
+ commentArr.push(commentSql);
+ }
+ }
+ modifyArr.push(` MODIFY (${this.genColumnBasicSql(a, false)})`);
+ if (a.pri) {
+ priArr.add(`${this.quoteIdentifier(a.name)}"`);
}
});
}
- if (changeData.upd.length > 0) {
- changeData.upd.forEach((a) => {
- sql.push(`ALTER TABLE "${tableName}" MODIFY ${this.genColumnBasicSql(a)}`);
+ if (changeData.add.length > 0) {
+ changeData.add.forEach((a) => {
+ modifyArr.push(` ADD (${this.genColumnBasicSql(a, false)})`);
if (a.remark) {
- sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`);
+ commentArr.push(`COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} is '${a.remark}'`);
+ }
+ if (a.pri) {
+ priArr.add(`"${a.name}"`);
}
});
}
if (changeData.del.length > 0) {
changeData.del.forEach((a) => {
- sql.push(`ALTER TABLE "${tableName}" DROP COLUMN ${a.name}`);
+ dropArr.push(`${this.quoteIdentifier(a.name)}`);
});
}
- return sql.join(';');
+
+ let dropPkSql = '';
+ if (priArr.size > 0) {
+ let resPri = tableData.fields.res.find((a: RowDefinition) => a.pri);
+ if (resPri) {
+ priArr.add(`"${resPri.name}"`);
+ }
+ // 如果有编辑主键字段,则删除主键,再添加主键
+ // 解析表字段中是否含有主键,有的话就删除主键
+ if (tableData.fields.oldFields.find((a: RowDefinition) => a.pri)) {
+ dropPkSql = `ALTER TABLE ${dbTable} DROP PRIMARY KEY;`;
+ }
+ }
+
+ let modifySql = baseSql + modifyArr.join(' ') + ';';
+ let dropSql = baseSql + ` DROP (${dropArr.join(',')}) ;`;
+ let renameSql = renameArr.join('');
+ let addPkSql = priArr.size > 0 ? `ALTER TABLE ${dbTable} ADD CONSTRAINT "PK_${tableName}" PRIMARY KEY (${Array.from(priArr).join(',')});` : '';
+ let commentSql = commentArr.join(';');
+
+ return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
}
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
diff --git a/mayfly_go_web/src/views/ops/db/dialect/postgres_dialect.ts b/mayfly_go_web/src/views/ops/db/dialect/postgres_dialect.ts
index e5c55fcd..b6001ec2 100644
--- a/mayfly_go_web/src/views/ops/db/dialect/postgres_dialect.ts
+++ b/mayfly_go_web/src/views/ops/db/dialect/postgres_dialect.ts
@@ -228,7 +228,7 @@ class PostgresqlDialect implements DbDialect {
let marks = false;
if (this.matchType(cl.type, ['char', 'time', 'date', 'text'])) {
// 默认值是now()的time或date不需要加引号
- if (cl.value.toLowerCase().replace(' ', '') === 'current_timestamp' && this.matchType(cl.type, ['time', 'date'])) {
+ if (cl.value.toLowerCase() === 'pg_systimestamp()' && this.matchType(cl.type, ['time', 'date'])) {
marks = false;
} else {
marks = true;
@@ -260,7 +260,10 @@ class PostgresqlDialect implements DbDialect {
let length = this.getTypeLengthSql(cl);
// 默认值
let defVal = this.getDefaultValueSql(cl);
- return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : ''} ${defVal} `;
+ // 如果有原名以原名为准
+ let name = cl.oldName && cl.name !== cl.oldName ? cl.oldName : cl.name;
+
+ return ` ${this.quoteIdentifier(name)} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : ''} ${defVal} `;
}
getCreateTableSql(data: any): string {
@@ -301,7 +304,7 @@ class PostgresqlDialect implements DbDialect {
// 创建索引
let sql: string[] = [];
tableData.indexs.res.forEach((a: any) => {
- sql.push(` CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName} USING btree ("${a.columnNames.join('","')})"`);
+ sql.push(` create ${a.unique ? 'UNIQUE' : ''} index ${a.indexName} ("${a.columnNames.join('","')})"`);
if (a.indexComment) {
sql.push(`COMMENT ON INDEX ${a.indexName} IS '${a.indexComment}'`);
}
@@ -309,39 +312,57 @@ class PostgresqlDialect implements DbDialect {
return sql.join(';');
}
- getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
- let sql: string[] = [];
+ getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
+ let schemaArr = tableData.db.split('/');
+ let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
+ let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableName)}`;
+
+ let dropPkSql = '';
+ let modifySql = '';
+ let dropSql = '';
+ let renameSql = '';
+ let addPkSql = '';
+ let commentSql = '';
+
if (changeData.add.length > 0) {
changeData.add.forEach((a) => {
- let typeLength = this.getTypeLengthSql(a);
- let defaultSql = this.getDefaultValueSql(a);
- sql.push(`ALTER TABLE ${tableName} add ${a.name} ${a.type}${typeLength} ${defaultSql}`);
+ modifySql += `alter table ${dbTable} add ${this.genColumnBasicSql(a)};`;
if (a.remark) {
- sql.push(`comment on column "${tableName}"."${a.name}" is '${a.remark}'`);
+ commentSql += `comment on column ${dbTable}.${this.quoteIdentifier(a.name)} is '${a.remark}';`;
}
});
}
if (changeData.upd.length > 0) {
changeData.upd.forEach((a) => {
+ let cmtSql = `comment on column ${dbTable}.${this.quoteIdentifier(a.name)} is '${a.remark}';`;
+ if (a.remark && a.oldName === a.name) {
+ commentSql += cmtSql;
+ }
+ // 修改了字段名
+ if (a.oldName !== a.name) {
+ renameSql += `alter table ${dbTable} rename column ${this.quoteIdentifier(a.oldName!)} to ${this.quoteIdentifier(a.name)};`;
+ if (a.remark) {
+ commentSql += cmtSql;
+ }
+ }
let typeLength = this.getTypeLengthSql(a);
- sql.push(`ALTER TABLE ${tableName} alter column ${a.name} type ${a.type}${typeLength}`);
+ // 如果有原名以原名为准
+ let name = a.oldName && a.name !== a.oldName ? a.oldName : a.name;
+ modifySql += `alter table ${dbTable} alter column ${this.quoteIdentifier(name)} type ${a.type}${typeLength} ;`;
let defaultSql = this.getDefaultValueSql(a);
if (defaultSql) {
- sql.push(`alter table ${tableName} alter column ${a.name} set ${defaultSql}`);
- }
- if (a.remark) {
- sql.push(`comment on column "${tableName}"."${a.name}" is '${a.remark}'`);
+ modifySql += `alter table ${dbTable} alter column ${this.quoteIdentifier(name)} set ${defaultSql} ;`;
}
});
}
if (changeData.del.length > 0) {
changeData.del.forEach((a) => {
- sql.push(`ALTER TABLE ${tableName} DROP COLUMN ${a.name}`);
+ dropSql += `alter table ${dbTable} drop column ${a.name};`;
});
}
- return sql.join(';');
+ return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
}
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
diff --git a/mayfly_go_web/src/views/ops/db/dialect/sqlite_dialect.ts b/mayfly_go_web/src/views/ops/db/dialect/sqlite_dialect.ts
new file mode 100644
index 00000000..d4657bac
--- /dev/null
+++ b/mayfly_go_web/src/views/ops/db/dialect/sqlite_dialect.ts
@@ -0,0 +1,337 @@
+import {
+ commonCustomKeywords,
+ DataType,
+ DbDialect,
+ DialectInfo,
+ EditorCompletion,
+ EditorCompletionItem,
+ IndexDefinition,
+ RowDefinition,
+ sqlColumnType,
+} from './index';
+import { DbInst } from '@/views/ops/db/db';
+import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sql/sql.js';
+
+export { SqliteDialect };
+
+// 参考官方文档:https://www.sqlite.org/datatype3.html
+const SQLITE_TYPE_LIST: sqlColumnType[] = [
+ // INTEGER
+ { udtName: 'int', dataType: 'int', desc: '', space: '', range: '' },
+ { udtName: 'integer', dataType: 'integer', desc: '', space: '', range: '' },
+ { udtName: 'tinyint', dataType: 'tinyint', desc: '', space: '', range: '' },
+ { udtName: 'smallint', dataType: 'smallint', desc: '', space: '', range: '' },
+ { udtName: 'mediumint', dataType: 'mediumint', desc: '', space: '', range: '' },
+ { udtName: 'bigint', dataType: 'bigint', desc: '', space: '', range: '' },
+ { udtName: 'unsigned big int', dataType: 'unsigned big int', desc: '', space: '', range: '' },
+ { udtName: 'int2', dataType: 'int2', desc: '', space: '', range: '' },
+ { udtName: 'int8', dataType: 'int8', desc: '', space: '', range: '' },
+ // TEXT
+ { udtName: 'character', dataType: 'character', desc: '', space: '', range: '' },
+ { udtName: 'varchar', dataType: 'varchar', desc: '', space: '', range: '' },
+ { udtName: 'varying character', dataType: 'varying character', desc: '', space: '', range: '' },
+ { udtName: 'nchar', dataType: 'nchar', desc: '', space: '', range: '' },
+ { udtName: 'native character', dataType: 'native character', desc: '', space: '', range: '' },
+ { udtName: 'nvarchar', dataType: 'nvarchar', desc: '', space: '', range: '' },
+ { udtName: 'text', dataType: 'text', desc: '', space: '', range: '' },
+ { udtName: 'clob', dataType: 'clob', desc: '', space: '', range: '' },
+ // blob
+ { udtName: 'blob', dataType: 'blob', desc: '', space: '', range: '' },
+ { udtName: 'no datatype specified', dataType: 'no datatype specified', desc: '', space: '', range: '' },
+ // REAL
+ { udtName: 'real', dataType: 'real', desc: '', space: '', range: '' },
+ { udtName: 'double', dataType: 'double', desc: '', space: '', range: '' },
+ { udtName: 'double precision', dataType: 'double precision', desc: '', space: '', range: '' },
+ { udtName: 'float', dataType: 'float', desc: '', space: '', range: '' },
+ // NUMERIC
+ { udtName: 'numeric', dataType: 'numeric', desc: '', space: '', range: '' },
+ { udtName: 'decimal', dataType: 'decimal', desc: '', space: '', range: '' },
+ { udtName: 'boolean', dataType: 'boolean', desc: '', space: '', range: '' },
+ { udtName: 'date', dataType: 'date', desc: '', space: '', range: '' },
+ { udtName: 'datetime', dataType: 'datetime', desc: '', space: '', range: '' },
+];
+
+const addCustomKeywords = ['PRAGMA', 'database_list', 'sqlite_master'];
+
+// 参考官方文档:https://www.sqlite.org/lang_corefunc.html
+const functions: EditorCompletionItem[] = [
+ // 字符函数
+ { label: 'abs', insertText: 'abs(X)', description: '返回给定数值的绝对值' },
+ { label: 'changes', insertText: 'changes()', description: '返回最近增删改影响的行数' },
+ { label: 'coalesce', insertText: 'coalesce(X,Y,...)', description: '返回第一个不为空的值' },
+ { label: 'hex', insertText: 'hex(X)', description: '返回给定字符的hex值' },
+ { label: 'ifnull', insertText: 'ifnull(X,Y)', description: '返回第一个不为空的值' },
+ { label: 'iif', insertText: 'iif(X,Y,Z)', description: '如果x为真则返回y,否则返回z' },
+ { label: 'instr', insertText: 'instr(X,Y)', description: '返回字符y在x的第n个位置' },
+ { label: 'length', insertText: 'length(X)', description: '返回给定字符的长度' },
+ { label: 'load_extension', insertText: 'load_extension(X[,Y])', description: '加载扩展块' },
+ { label: 'lower', insertText: 'lower(X)', description: '返回小写字符' },
+ { label: 'ltrim', insertText: 'ltrim(X[,Y])', description: '左trim' },
+ { label: 'nullif', insertText: 'nullif(X,Y)', description: '比较两值相等则返回null,否则返回第一个值' },
+ { label: 'printf', insertText: "printf('%s',...)", description: '字符串格式化拼接,如%s %d' },
+ { label: 'quote', insertText: 'quote(X)', description: '把字符串用引号包起来' },
+ { label: 'random', insertText: 'random()', description: '生成随机数' },
+ { label: 'randomblob', insertText: 'randomblob(N)', description: '生成一个包含N个随机字节的BLOB' },
+ { label: 'replace', insertText: 'replace(X,Y,Z)', description: '替换字符串' },
+ { label: 'round', insertText: 'round(X[,Y])', description: '将数值四舍五入到指定的小数位数' },
+ { label: 'rtrim', insertText: 'rtrim(X[,Y])', description: '右trim' },
+ { label: 'sign', insertText: 'sign(X)', description: '返回数字符号 1正 -1负 0零 null' },
+ { label: 'soundex', insertText: 'soundex(X)', description: '返回字符串X的soundex编码字符串' },
+ { label: 'sqlite_compileoption_get', insertText: 'sqlite_compileoption_get(N)', description: '获取指定编译选项的值' },
+ { label: 'sqlite_compileoption_used', insertText: 'sqlite_compileoption_used(X)', description: '检查SQLite编译时是否使用了指定的编译选项' },
+ { label: 'sqlite_source_id', insertText: 'sqlite_source_id()', description: '获取sqlite源代码标识符' },
+ { label: 'sqlite_version', insertText: 'sqlite_version()', description: '获取sqlite版本' },
+ { label: 'substr', insertText: 'substr(X,Y[,Z])', description: '截取字符串' },
+ { label: 'substring', insertText: 'substring(X,Y[,Z])', description: '截取字符串' },
+ { label: 'trim', insertText: 'trim(X[,Y])', description: '去除给定字符串前后的字符,默认空格' },
+ { label: 'typeof', insertText: 'typeof(X)', description: '返回X的基本类型:null,integer,real,text,blob' },
+ { label: 'unicode', insertText: 'unicode(X)', description: '返回与字符串X的第一个字符相对应的数字unicode代码点' },
+ { label: 'unlikely', insertText: 'unlikely(X)', description: '返回大写字符' },
+ { label: 'upper', insertText: 'upper(X)', description: '返回由0x00的N个字节组成的BLOB' },
+ { label: 'zeroblob', insertText: 'zeroblob(N)', description: '返回分组中的平均值' },
+ { label: 'avg', insertText: 'avg(X)', description: '返回总条数' },
+ { label: 'count', insertText: 'count(*)', description: '返回分组中用给定非空字符串连接的值' },
+ { label: 'group_concat', insertText: 'group_concat(X[,Y])', description: '返回分组中最大值' },
+ { label: 'max', insertText: 'max(X)', description: '返回分组中最小值' },
+ { label: 'min', insertText: 'min(X)', description: '返回分组中非空值的总和。' },
+ { label: 'sum', insertText: 'sum(X)', description: '返回分组中非空值的总和。' },
+ { label: 'total', insertText: 'total(X)', description: '返回YYYY-MM-DD格式的字符串' },
+ { label: 'date', insertText: 'date(time-value[, modifier, ...])', description: '返回HH:MM:SS格式的字符串' },
+ { label: 'time', insertText: 'time(time-value[, modifier, ...])', description: '将日期和时间字符串转换为特定的日期和时间格式' },
+ { label: 'datetime', insertText: 'datetime(time-value[, modifier, ...])', description: '计算日期和时间的儒略日数' },
+ { label: 'julianday', insertText: 'julianday(time-value[, modifier, ...])', description: '将日期和时间格式化为指定的字符串' },
+];
+
+let sqliteDialectInfo: DialectInfo;
+class SqliteDialect implements DbDialect {
+ getInfo(): DialectInfo {
+ if (sqliteDialectInfo) {
+ return sqliteDialectInfo;
+ }
+
+ let { keywords, operators, builtinVariables } = sqlLanguage;
+
+ let editorCompletions: EditorCompletion = {
+ keywords: keywords
+ .filter((a: string) => addCustomKeywords.indexOf(a) === -1)
+ .map((a: string): EditorCompletionItem => ({ label: a, description: 'keyword' }))
+ .concat(commonCustomKeywords.map((a): EditorCompletionItem => ({ label: a, description: 'keyword' })))
+ .concat(addCustomKeywords.map((a): EditorCompletionItem => ({ label: a, description: 'keyword' }))),
+ operators: operators.map((a: string): EditorCompletionItem => ({ label: a, description: 'operator' })),
+ functions,
+ variables: builtinVariables.map((a: string): EditorCompletionItem => ({ label: a, description: 'var' })),
+ };
+
+ sqliteDialectInfo = {
+ icon: 'iconfont icon-sqlite',
+ defaultPort: 0,
+ formatSqlDialect: 'sql',
+ columnTypes: SQLITE_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
+ editorCompletions,
+ };
+ return sqliteDialectInfo;
+ }
+
+ getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
+ return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
+ pageNum,
+ limit
+ )};`;
+ }
+
+ getPageSql(pageNum: number, limit: number) {
+ return ` LIMIT ${(pageNum - 1) * limit}, ${limit}`;
+ }
+
+ getDefaultRows(): RowDefinition[] {
+ return [
+ { name: 'id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
+ { name: 'creator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
+ {
+ name: 'creator',
+ type: 'varchar',
+ length: '100',
+ numScale: '',
+ value: '',
+ notNull: true,
+ pri: false,
+ auto_increment: false,
+ remark: '创建人姓名',
+ },
+ {
+ name: 'create_time',
+ type: 'datetime',
+ length: '',
+ numScale: '',
+ value: 'CURRENT_TIMESTAMP',
+ notNull: true,
+ pri: false,
+ auto_increment: false,
+ remark: '创建时间',
+ },
+ { name: 'updator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
+ { name: 'updator', type: 'varchar', length: '100', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改姓名' },
+ {
+ name: 'update_time',
+ type: 'datetime',
+ length: '',
+ numScale: '',
+ value: 'CURRENT_TIMESTAMP',
+ notNull: true,
+ pri: false,
+ auto_increment: false,
+ remark: '修改时间',
+ },
+ ];
+ }
+
+ getDefaultIndex(): IndexDefinition {
+ return {
+ indexName: '',
+ columnNames: [],
+ unique: false,
+ indexType: 'BTREE',
+ indexComment: '',
+ };
+ }
+
+ quoteIdentifier = (name: string) => {
+ return `\"${name}\"`;
+ };
+
+ genColumnBasicSql(cl: any): string {
+ let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : `'${cl.value}'`) : '';
+ let defVal = val ? `DEFAULT ${val}` : '';
+ let length = cl.length ? `(${cl.length})` : '';
+ let nullAble = cl.notNull ? 'NOT NULL' : 'NULL';
+ if (cl.pri) {
+ return ` ${this.quoteIdentifier(cl.name)} ${cl.type}${length} PRIMARY KEY ${cl.auto_increment ? 'AUTOINCREMENT' : ''} ${nullAble} `;
+ }
+ return ` ${this.quoteIdentifier(cl.name)} ${cl.type}${length} ${nullAble} ${defVal} `;
+ }
+ getCreateTableSql(data: any): string {
+ // 创建表结构
+ let fields: string[] = [];
+ data.fields.res.forEach((item: any) => {
+ item.name && fields.push(this.genColumnBasicSql(item));
+ });
+
+ return `CREATE TABLE ${this.quoteIdentifier(data.db)}.${this.quoteIdentifier(data.tableName)}
+ ( ${fields.join(',')} )`;
+ }
+
+ getCreateIndexSql(data: any): string {
+ // 创建索引
+ let sql = [] as string[];
+ data.indexs.res.forEach((a: any) => {
+ sql.push(
+ `CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(data.db)}.${this.quoteIdentifier(a.indexName)} ON "${data.tableName}" (${a.columnNames.join(',')})`
+ );
+ });
+ return sql.join(';');
+ }
+
+ getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
+ // sqlite修改表结构需要先删除再创建
+
+ // 1.删除旧表索引 DROP INDEX "main"."aa";
+ let sql = [] as string[];
+ tableData.indexs.res.forEach((a: any) => {
+ a.indexName && sql.push(`DROP INDEX ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(a.indexName)}`);
+ });
+
+ // 2.重命名表,备份旧表 ALTER TABLE "main"."t_sys_resource" RENAME TO "_t_sys_resource_old_20240118162712"; new Date().getTime()
+ let oldTableName = `_${tableName}_old_${new Date().getTime()}`;
+ sql.push(`ALTER TABLE ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)} RENAME TO ${this.quoteIdentifier(oldTableName)}`);
+
+ // 3.创建新表
+ sql.push(this.getCreateTableSql(tableData));
+
+ // 4.复制数据 INSERT INTO "库名"."新表名" (${insertFields}) SELECT ${queryFields} FROM "库名"."旧表名";
+ // 查询的字段数据类型和数量应与插入的字段一致
+ // 判断哪些字段需要查询旧表,哪些字段需要插入新表
+ // 解析changeData,统计需要查询旧表的字段,统计需要插入新表的字段
+ let delFields = changeData.del.map((a) => a.name);
+ let addFields = changeData.add.map((a) => a.name);
+
+ let queryFields = [] as string[];
+ let insertFields = [] as string[];
+ tableData.fields.res.forEach((a: any) => {
+ // 新增、删除的字段不需要查询旧表,不需要插入新表
+ if (addFields.includes(a.name) || delFields.includes(a.name)) {
+ return;
+ }
+ // 修改的字段需要查询和插入,判断是否修改了字段名,如果修改了字段名,需要查询旧表原名,插入新表新名
+ // 其余未删除、未修改的字段,需要查询旧表,插入新表
+ queryFields.push(this.quoteIdentifier(a.name === a.oldName ? a.name : a.oldName));
+ insertFields.push(this.quoteIdentifier(a.name));
+ });
+ // 生成sql
+ sql.push(
+ `INSERT INTO ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)} (${insertFields.join(',')}) SELECT ${queryFields.join(
+ ','
+ )} FROM ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(oldTableName)}`
+ );
+
+ // 5.创建索引
+ tableData.indexs.res.forEach((a: any) => {
+ a.indexName &&
+ sql.push(
+ `CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(a.indexName)} ON "${tableName}" (${a.columnNames.join(',')})`
+ );
+ });
+
+ return sql.join(';') + ';';
+ }
+
+ getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
+ // sqlite创建索引需要先删除再创建
+ // CREATE INDEX "main"."aa1" ON "t_sys_resource" ( "ui_path" );
+
+ let sql = [] as string[];
+
+ if (changeData.del.length > 0) {
+ changeData.del.forEach((a) => {
+ sql.push(`DROP INDEX ${this.quoteIdentifier(a.indexName)}`);
+ });
+ }
+
+ let indexData = [] as any[];
+ if (changeData.add.length > 0) {
+ indexData = indexData.concat(changeData.add);
+ }
+ if (changeData.upd.length > 0) {
+ indexData = indexData.concat(changeData.upd);
+ }
+
+ if (indexData.length > 0) {
+ indexData.forEach((a) => {
+ sql.push(`CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(a.indexName)} ON ${tableName} (${a.columnNames.join(',')})`);
+ });
+ }
+ return sql.join(';');
+ }
+
+ getDataType(columnType: string): DataType {
+ if (DbInst.isNumber(columnType)) {
+ return DataType.Number;
+ }
+ // 日期时间类型
+ if (/datetime|timestamp/gi.test(columnType)) {
+ return DataType.DateTime;
+ }
+ // 日期类型
+ if (/date/gi.test(columnType)) {
+ return DataType.Date;
+ }
+ // 时间类型
+ if (/time/gi.test(columnType)) {
+ return DataType.Time;
+ }
+ return DataType.String;
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
+ wrapStrValue(columnType: string, value: string): string {
+ return `'${value}'`;
+ }
+}
diff --git a/server/internal/db/api/form/instance.go b/server/internal/db/api/form/instance.go
index c15a070e..8c0bfa1d 100644
--- a/server/internal/db/api/form/instance.go
+++ b/server/internal/db/api/form/instance.go
@@ -5,9 +5,9 @@ type InstanceForm struct {
Name string `binding:"required" json:"name"`
Type string `binding:"required" json:"type"` // 类型,mysql oracle等
Host string `binding:"required" json:"host"`
- Port int `binding:"required" json:"port"`
+ Port int `json:"port"`
Sid string `json:"sid"`
- Username string `binding:"required" json:"username"`
+ Username string `json:"username"`
Password string `json:"password"`
Params string `json:"params"`
Remark string `json:"remark"`
diff --git a/server/internal/db/application/db_sql_exec.go b/server/internal/db/application/db_sql_exec.go
index 114dc9ac..ddf1907d 100644
--- a/server/internal/db/application/db_sql_exec.go
+++ b/server/internal/db/application/db_sql_exec.go
@@ -93,8 +93,12 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
// 如果配置为0,则不校验分页参数
maxCount := config.GetDbQueryMaxCount()
if maxCount != 0 {
- if !strings.Contains(lowerSql, "limit") {
- return nil, errorx.NewBiz("请完善分页信息后执行")
+ // 兼容oracle rownum分页
+ if !strings.Contains(lowerSql, "limit") && !strings.Contains(lowerSql, "rownum") {
+ // 判断是不是count语句
+ if !strings.Contains(lowerSql, "count(") {
+ return nil, errorx.NewBiz("请完善分页信息后执行")
+ }
}
}
}
diff --git a/server/internal/db/application/instance.go b/server/internal/db/application/instance.go
index d8162c00..bc5fa64f 100644
--- a/server/internal/db/application/instance.go
+++ b/server/internal/db/application/instance.go
@@ -73,9 +73,11 @@ func (app *instanceAppImpl) Save(ctx context.Context, instanceEntity *entity.DbI
err := app.GetBy(oldInstance)
if instanceEntity.Id == 0 {
- if instanceEntity.Password == "" {
+
+ if instanceEntity.Type != string(dbi.DbTypeSqlite) && instanceEntity.Password == "" {
return errorx.NewBiz("密码不能为空")
}
+
if err == nil {
return errorx.NewBiz("该数据库实例已存在")
}
diff --git a/server/internal/db/dbm/dbi/db_type.go b/server/internal/db/dbm/dbi/db_type.go
index 60987a69..b5127d75 100644
--- a/server/internal/db/dbm/dbi/db_type.go
+++ b/server/internal/db/dbm/dbi/db_type.go
@@ -16,6 +16,7 @@ const (
DbTypePostgres DbType = "postgres"
DbTypeDM DbType = "dm"
DbTypeOracle DbType = "oracle"
+ DbTypeSqlite DbType = "sqlite"
)
func ToDbType(dbType string) DbType {
diff --git a/server/internal/db/dbm/dbi/dialect.go b/server/internal/db/dbm/dbi/dialect.go
index cff9e7d4..bd4435ed 100644
--- a/server/internal/db/dbm/dbi/dialect.go
+++ b/server/internal/db/dbm/dbi/dialect.go
@@ -75,7 +75,7 @@ type Dialect interface {
GetColumns(tableNames ...string) ([]Column, error)
// 获取表主键字段名,没有主键标识则默认第一个字段
- GetPrimaryKey(tablename string) (string, error)
+ GetPrimaryKey(tableName string) (string, error)
// 获取表索引信息
GetTableIndex(tableName string) ([]Index, error)
diff --git a/server/internal/db/dbm/dbi/metasql/sqlite_meta.sql b/server/internal/db/dbm/dbi/metasql/sqlite_meta.sql
new file mode 100644
index 00000000..d29ce4d5
--- /dev/null
+++ b/server/internal/db/dbm/dbi/metasql/sqlite_meta.sql
@@ -0,0 +1,21 @@
+--SQLITE_TABLE_INFO 表详细信息
+select tbl_name as tableName,
+ '' as tableComment,
+ '' as createTime,
+ 0 as dataLength,
+ 0 as indexLength,
+ 0 as tableRows
+FROM sqlite_master
+WHERE type = 'table'
+ and name not like 'sqlite_%'
+ORDER BY tbl_name
+---------------------------------------
+--SQLITE_INDEX_INFO 表索引信息
+select name as indexName,
+ `sql` as indexSql,
+ 'normal' as indexType,
+ '' as indexComment
+FROM sqlite_master
+WHERE type = 'index'
+ and tbl_name = '%s'
+ORDER BY name
\ No newline at end of file
diff --git a/server/internal/db/dbm/dbm.go b/server/internal/db/dbm/dbm.go
index 83a0c59e..241f5f2d 100644
--- a/server/internal/db/dbm/dbm.go
+++ b/server/internal/db/dbm/dbm.go
@@ -8,6 +8,7 @@ import (
"mayfly-go/internal/db/dbm/mysql"
"mayfly-go/internal/db/dbm/oracle"
"mayfly-go/internal/db/dbm/postgres"
+ "mayfly-go/internal/db/dbm/sqlite"
"mayfly-go/internal/machine/mcm"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/logx"
@@ -48,6 +49,8 @@ func getDbMetaByType(dt dbi.DbType) dbi.Meta {
return dm.GetMeta()
case dbi.DbTypeOracle:
return oracle.GetMeta()
+ case dbi.DbTypeSqlite:
+ return sqlite.GetMeta()
default:
panic(fmt.Sprintf("invalid database type: %s", dt))
}
diff --git a/server/internal/db/dbm/dm/meta.go b/server/internal/db/dbm/dm/meta.go
index 148aeb50..784aea9b 100644
--- a/server/internal/db/dbm/dm/meta.go
+++ b/server/internal/db/dbm/dm/meta.go
@@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
"mayfly-go/internal/db/dbm/dbi"
+ "net/url"
"strings"
"sync"
)
@@ -31,10 +32,12 @@ func (md *DmMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
// dm database可以使用db/schema表示,方便连接指定schema, 若不存在schema则使用默认schema
ss := strings.Split(db, "/")
if len(ss) > 1 {
- dbParam = fmt.Sprintf("%s?schema=%s", ss[0], ss[len(ss)-1])
+ dbParam = fmt.Sprintf("%s?schema=\"%s\"&escapeProcess=true", ss[0], ss[len(ss)-1])
} else {
- dbParam = db
+ dbParam = db + "?escapeProcess=true"
}
+ } else {
+ dbParam = "?escapeProcess=true"
}
err := d.IfUseSshTunnelChangeIpPort()
@@ -42,7 +45,7 @@ func (md *DmMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
return nil, err
}
- dsn := fmt.Sprintf("dm://%s:%s@%s:%d/%s", d.Username, d.Password, d.Host, d.Port, dbParam)
+ dsn := fmt.Sprintf("dm://%s:%s@%s:%d/%s", d.Username, url.PathEscape(d.Password), d.Host, d.Port, dbParam)
return sql.Open(driverName, dsn)
}
diff --git a/server/internal/db/dbm/oracle/meta.go b/server/internal/db/dbm/oracle/meta.go
index 9b0ef943..f50e2a33 100644
--- a/server/internal/db/dbm/oracle/meta.go
+++ b/server/internal/db/dbm/oracle/meta.go
@@ -44,9 +44,25 @@ func (md *OraMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
schema = ss[1]
}
}
-
- urlOptions["TIMEOUT"] = "60"
- urlOptions["client charset"] = "UTF8"
+ // 解析参数
+ if d.Params != "" {
+ paramArr := strings.Split(d.Params, "&")
+ for _, param := range paramArr {
+ ps := strings.Split(param, "=")
+ if len(ps) > 1 {
+ if ps[0] == "clientCharset" {
+ urlOptions["client charset"] = ps[1]
+ } else {
+ urlOptions[ps[0]] = ps[1]
+ }
+ }
+ }
+ }
+ // 默认设置为UTF8
+ //if urlOptions["client charset"] == "" {
+ // urlOptions["client charset"] = "UTF8"
+ //}
+ urlOptions["TIMEOUT"] = "10"
connStr := go_ora.BuildUrl(d.Host, d.Port, d.Sid, d.Username, d.Password, urlOptions)
conn, err := sql.Open(driverName, connStr)
if err != nil {
diff --git a/server/internal/db/dbm/sqlite/dialect.go b/server/internal/db/dbm/sqlite/dialect.go
new file mode 100644
index 00000000..c6ed2f09
--- /dev/null
+++ b/server/internal/db/dbm/sqlite/dialect.go
@@ -0,0 +1,247 @@
+package sqlite
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "fmt"
+ "mayfly-go/internal/db/dbm/dbi"
+ "mayfly-go/pkg/logx"
+ "mayfly-go/pkg/utils/anyx"
+ "regexp"
+ "strings"
+ "time"
+)
+
+const (
+ SQLITE_META_FILE = "metasql/sqlite_meta.sql"
+ SQLITE_TABLE_INFO_KEY = "SQLITE_TABLE_INFO"
+ SQLITE_INDEX_INFO_KEY = "SQLITE_INDEX_INFO"
+)
+
+type SqliteDialect struct {
+ dc *dbi.DbConn
+}
+
+func (sd *SqliteDialect) GetDbServer() (*dbi.DbServer, error) {
+ _, res, err := sd.dc.Query("SELECT SQLITE_VERSION() as version")
+ if err != nil {
+ return nil, err
+ }
+ ds := &dbi.DbServer{
+ Version: anyx.ConvString(res[0]["version"]),
+ }
+ return ds, nil
+}
+
+func (sd *SqliteDialect) GetDbNames() ([]string, error) {
+ databases := make([]string, 0)
+ _, res, err := sd.dc.Query("PRAGMA database_list")
+ if err != nil {
+ return nil, err
+ }
+ for _, re := range res {
+ databases = append(databases, anyx.ConvString(re["name"]))
+ }
+
+ return databases, nil
+}
+
+// 获取表基础元信息, 如表名等
+func (sd *SqliteDialect) GetTables() ([]dbi.Table, error) {
+ _, res, err := sd.dc.Query(dbi.GetLocalSql(SQLITE_META_FILE, SQLITE_TABLE_INFO_KEY))
+ //cols, res, err := sd.dc.Query("SELECT datetime(1092941466, 'unixepoch')")
+ if err != nil {
+ return nil, err
+ }
+
+ tables := make([]dbi.Table, 0)
+ for _, re := range res {
+ tables = append(tables, dbi.Table{
+ TableName: re["tableName"].(string),
+ TableComment: anyx.ConvString(re["tableComment"]),
+ CreateTime: anyx.ConvString(re["createTime"]),
+ TableRows: anyx.ConvInt(re["tableRows"]),
+ DataLength: anyx.ConvInt64(re["dataLength"]),
+ IndexLength: anyx.ConvInt64(re["indexLength"]),
+ })
+ }
+ return tables, nil
+}
+
+// 获取列元信息, 如列名等
+func (sd *SqliteDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) {
+
+ columns := make([]dbi.Column, 0)
+
+ for i := 0; i < len(tableNames); i++ {
+ tableName := tableNames[i]
+ _, res, err := sd.dc.Query(fmt.Sprintf("PRAGMA table_info(%s)", tableName))
+ if err != nil {
+ logx.Error("获取数据库表字段结构出错", err.Error())
+ continue
+ }
+ for _, re := range res {
+ nullable := "YES"
+ if anyx.ConvInt(re["notnull"]) == 1 {
+ nullable = "NO"
+ }
+ // 去掉默认值的引号
+ defaultValue := anyx.ConvString(re["dflt_value"])
+ if strings.Contains(defaultValue, "'") {
+ defaultValue = strings.ReplaceAll(defaultValue, "'", "")
+ }
+ columnKey := ""
+ if anyx.ConvInt(re["pk"]) == 1 {
+ columnKey = "PRI"
+ }
+ columns = append(columns, dbi.Column{
+ TableName: tableName,
+ ColumnName: re["name"].(string),
+ ColumnType: strings.ToLower(anyx.ConvString(re["type"])),
+ ColumnComment: "",
+ Nullable: nullable,
+ ColumnKey: columnKey,
+ ColumnDefault: defaultValue,
+ NumScale: "0",
+ })
+ }
+ }
+ return columns, nil
+}
+
+func (sd *SqliteDialect) GetPrimaryKey(tableName string) (string, error) {
+ _, res, err := sd.dc.Query(fmt.Sprintf("PRAGMA table_info(%s)", tableName))
+ if err != nil {
+ return "", err
+ }
+ for _, re := range res {
+ if anyx.ConvInt(re["pk"]) == 1 {
+ return re["name"].(string), nil
+ }
+ }
+
+ return "", errors.New("不存在主键")
+}
+
+// 解析索引创建语句以获取字段信息
+func extractIndexFields(indexSQL string) string {
+ // 使用正则表达式提取字段信息
+ re := regexp.MustCompile(`\((.*?)\)`)
+ match := re.FindStringSubmatch(indexSQL)
+ if len(match) > 1 {
+ fields := strings.Split(match[1], ",")
+ for i, field := range fields {
+ // 去除空格
+ fields[i] = strings.TrimSpace(field)
+ }
+ return strings.Join(fields, ",")
+ }
+ return ""
+}
+
+// 获取表索引信息
+func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
+ _, res, err := sd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(SQLITE_META_FILE, SQLITE_INDEX_INFO_KEY), tableName))
+ if err != nil {
+ return nil, err
+ }
+
+ indexs := make([]dbi.Index, 0)
+ for _, re := range res {
+ indexSql := re["indexSql"].(string)
+ isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX")
+ nonUnique := 1
+ if isUnique {
+ nonUnique = 0
+ }
+
+ indexs = append(indexs, dbi.Index{
+ IndexName: re["indexName"].(string),
+ ColumnName: extractIndexFields(indexSql),
+ IndexType: anyx.ConvString(re["indexType"]),
+ IndexComment: anyx.ConvString(re["indexComment"]),
+ NonUnique: nonUnique,
+ SeqInIndex: 1,
+ })
+ }
+ // 把查询结果以索引名分组,索引字段以逗号连接
+ return indexs, nil
+}
+
+// 获取建表ddl
+func (sd *SqliteDialect) GetTableDDL(tableName string) (string, error) {
+ _, res, err := sd.dc.Query("select sql from sqlite_master WHERE name=? order by type desc", tableName)
+ if err != nil {
+ return "", err
+ }
+ var builder strings.Builder
+ for _, re := range res {
+ builder.WriteString(re["sql"].(string))
+ }
+
+ return builder.String(), nil
+}
+
+func (sd *SqliteDialect) WalkTableRecord(tableName string, walkFn dbi.WalkQueryRowsFunc) error {
+ return sd.dc.WalkQueryRows(context.Background(), fmt.Sprintf("SELECT * FROM %s", tableName), walkFn)
+}
+
+func (sd *SqliteDialect) GetSchemas() ([]string, error) {
+ return nil, nil
+}
+
+// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
+func (sd *SqliteDialect) GetDbProgram() dbi.DbProgram {
+ panic("implement me")
+}
+
+func (sd *SqliteDialect) GetDataType(dbColumnType string) dbi.DataType {
+ if regexp.MustCompile(`(?i)int`).MatchString(dbColumnType) {
+ return dbi.DataTypeNumber
+ }
+ if regexp.MustCompile(`(?i)datetime`).MatchString(dbColumnType) {
+ return dbi.DataTypeDateTime
+ }
+ return dbi.DataTypeString
+}
+
+func (sd *SqliteDialect) BatchInsert(tx *sql.Tx, tableName string, columns []string, values [][]any) (int64, error) {
+ // 执行批量insert sql,跟mysql一样 支持批量insert语法
+ // 生成占位符字符串:如:(?,?)
+ // 重复字符串并用逗号连接
+ repeated := strings.Repeat("?,", len(columns))
+ // 去除最后一个逗号,占位符由括号包裹
+ placeholder := fmt.Sprintf("(%s)", strings.TrimSuffix(repeated, ","))
+
+ // 重复占位符字符串n遍
+ repeated = strings.Repeat(placeholder+",", len(values))
+ // 去除最后一个逗号
+ placeholder = strings.TrimSuffix(repeated, ",")
+
+ sqlStr := fmt.Sprintf("insert into %s (%s) values %s", sd.dc.Info.Type.QuoteIdentifier(tableName), strings.Join(columns, ","), placeholder)
+
+ // 把二维数组转为一维数组
+ var args []any
+ for _, v := range values {
+ args = append(args, v...)
+ }
+
+ // 执行批量insert sql
+ return sd.dc.TxExec(tx, sqlStr, args...)
+}
+
+func (sd *SqliteDialect) FormatStrData(dbColumnValue string, dataType dbi.DataType) string {
+ switch dataType {
+ case dbi.DataTypeDateTime: // "2024-01-02T22:08:22.275697+08:00"
+ res, _ := time.Parse(time.RFC3339, dbColumnValue)
+ return res.Format(time.DateTime)
+ case dbi.DataTypeDate: // "2024-01-02T00:00:00+08:00"
+ res, _ := time.Parse(time.RFC3339, dbColumnValue)
+ return res.Format(time.DateOnly)
+ case dbi.DataTypeTime: // "0000-01-01T22:08:22.275688+08:00"
+ res, _ := time.Parse(time.RFC3339, dbColumnValue)
+ return res.Format(time.TimeOnly)
+ }
+ return dbColumnValue
+}
diff --git a/server/internal/db/dbm/sqlite/meta.go b/server/internal/db/dbm/sqlite/meta.go
new file mode 100644
index 00000000..c0a968b8
--- /dev/null
+++ b/server/internal/db/dbm/sqlite/meta.go
@@ -0,0 +1,38 @@
+package sqlite
+
+import (
+ "database/sql"
+ "errors"
+ _ "github.com/mattn/go-sqlite3"
+ "mayfly-go/internal/db/dbm/dbi"
+ "os"
+ "sync"
+)
+
+var (
+ meta dbi.Meta
+ once sync.Once
+)
+
+func GetMeta() dbi.Meta {
+ once.Do(func() {
+ meta = new(SqliteMeta)
+ })
+ return meta
+}
+
+type SqliteMeta struct {
+}
+
+func (md *SqliteMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
+ // 用host字段来存sqlite的文件路径
+ // 检查文件是否存在,否则报错,基于sqlite会自动创建文件,为了服务器文件安全,所以先确定文件存在再连接,不自动创建
+ if _, err := os.Stat(d.Host); err != nil {
+ return nil, errors.New("数据库文件不存在")
+ }
+ return sql.Open("sqlite3", d.Host)
+}
+
+func (md *SqliteMeta) GetDialect(conn *dbi.DbConn) dbi.Dialect {
+ return &SqliteDialect{conn}
+}
diff --git a/server/resources/data/mayfly-go.sqlite b/server/resources/data/mayfly-go.sqlite
index 2b74d2bf..a05146c9 100644
Binary files a/server/resources/data/mayfly-go.sqlite and b/server/resources/data/mayfly-go.sqlite differ
diff --git a/server/resources/script/sql/mayfly-go.sql b/server/resources/script/sql/mayfly-go.sql
index 9546c858..a05d6346 100644
--- a/server/resources/script/sql/mayfly-go.sql
+++ b/server/resources/script/sql/mayfly-go.sql
@@ -9,9 +9,9 @@ CREATE TABLE `t_db_instance` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
`host` varchar(100) COLLATE utf8mb4_bin NOT NULL,
- `port` int(8) NOT NULL,
+ `port` int(8) NULL,
`sid` varchar(255) NULL COMMENT 'oracle数据库需要sid',
- `username` varchar(255) COLLATE utf8mb4_bin NOT NULL,
+ `username` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
`params` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '其他连接参数',
diff --git a/server/resources/script/sql/v1.7.1.sql b/server/resources/script/sql/v1.7.1.sql
new file mode 100644
index 00000000..bda74e34
--- /dev/null
+++ b/server/resources/script/sql/v1.7.1.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `t_db_instance`
+ MODIFY `port` int (8) NULL comment '数据库端口',
+ MODIFY `username` varchar (255) NULL comment '数据库用户名';
\ No newline at end of file