mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
!86 dbms支持sqlite和一些bug修复
* fix: 达梦数据库连接修复,以支持带特殊字符的密码和schema * fix: oracle bug修复 * feat: dbms支持sqlite * fix: dbms 修改字段名bug
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -55,11 +55,11 @@
|
||||
"unicode_decimal": 58905
|
||||
},
|
||||
{
|
||||
"icon_id": "11617944",
|
||||
"icon_id": "25271976",
|
||||
"name": "oracle",
|
||||
"font_class": "oracle",
|
||||
"unicode": "e6ea",
|
||||
"unicode_decimal": 59114
|
||||
"unicode": "e507",
|
||||
"unicode_decimal": 58631
|
||||
},
|
||||
{
|
||||
"icon_id": "8105644",
|
||||
@@ -67,6 +67,13 @@
|
||||
"font_class": "mariadb",
|
||||
"unicode": "e513",
|
||||
"unicode_decimal": 58643
|
||||
},
|
||||
{
|
||||
"icon_id": "13601813",
|
||||
"name": "sqlite",
|
||||
"font_class": "sqlite",
|
||||
"unicode": "e546",
|
||||
"unicode_decimal": 58694
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="host" label="host" required>
|
||||
<el-form-item v-if="form.type !== DbType.sqlite" prop="host" label="host" required>
|
||||
<el-col :span="18">
|
||||
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
||||
</el-col>
|
||||
@@ -24,13 +24,18 @@
|
||||
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.type === DbType.sqlite" prop="host" label="sqlite地址">
|
||||
<el-input v-model.trim="form.host" placeholder="请输入sqlite文件在服务器的绝对地址"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.type === DbType.oracle" prop="sid" label="SID">
|
||||
<el-input v-model.trim="form.sid" placeholder="请输入服务id"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="username" label="用户名" required>
|
||||
<el-form-item v-if="form.type !== DbType.sqlite" prop="username" label="用户名" required>
|
||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码">
|
||||
<el-form-item v-if="form.type !== DbType.sqlite" prop="password" label="密码">
|
||||
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
|
||||
<template v-if="form.id && form.id != 0" #suffix>
|
||||
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
|
||||
@@ -169,6 +174,10 @@ const dbTypes = [
|
||||
type: 'oracle',
|
||||
label: 'oracle',
|
||||
},
|
||||
{
|
||||
type: 'sqlite',
|
||||
label: 'sqlite',
|
||||
},
|
||||
];
|
||||
|
||||
const state = reactive({
|
||||
@@ -176,7 +185,7 @@ const state = reactive({
|
||||
tabActiveName: 'basic',
|
||||
form: {
|
||||
id: null,
|
||||
type: null,
|
||||
type: '',
|
||||
name: null,
|
||||
host: '',
|
||||
port: null,
|
||||
@@ -247,10 +256,12 @@ const testConn = async () => {
|
||||
};
|
||||
|
||||
const btnOk = async () => {
|
||||
if (!state.form.id) {
|
||||
notBlank(state.form.password, '新增操作,密码不可为空');
|
||||
} else if (state.form.username != state.oldUserName) {
|
||||
notBlank(state.form.password, '已修改用户名,请输入密码');
|
||||
if (state.form.type !== DbType.sqlite) {
|
||||
if (!state.form.id) {
|
||||
notBlank(state.form.password, '新增操作,密码不可为空');
|
||||
} else if (state.form.username != state.oldUserName) {
|
||||
notBlank(state.form.password, '已修改用户名,请输入密码');
|
||||
}
|
||||
}
|
||||
|
||||
dbForm.value.validate(async (valid: boolean) => {
|
||||
|
||||
@@ -269,26 +269,24 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||
nParams.schema = sn;
|
||||
nParams.db = nParams.db + '/' + sn;
|
||||
nParams.dbs = schemaNames;
|
||||
return new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresScheam).withParams(nParams).withIcon(SchemaIcon);
|
||||
return new TagTreeNode(`${params.id}.${params.db}.schema.${sn}`, sn, NodeTypePostgresSchema).withParams(nParams).withIcon(SchemaIcon);
|
||||
});
|
||||
}
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
|
||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
|
||||
];
|
||||
return NodeTypeTables(params);
|
||||
})
|
||||
.withNodeClickFunc(nodeClickChangeDb);
|
||||
|
||||
const NodeTypeTables = (params: any) => {
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
|
||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
|
||||
];
|
||||
};
|
||||
|
||||
// postgres schema模式
|
||||
const NodeTypePostgresScheam = new NodeType(SqlExecNodeType.PgSchema)
|
||||
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
||||
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
|
||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
|
||||
];
|
||||
})
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => NodeTypeTables(parentNode.params))
|
||||
.withNodeClickFunc(nodeClickChangeDb);
|
||||
|
||||
// 数据库表菜单节点
|
||||
|
||||
@@ -371,7 +371,7 @@ const selectData = async () => {
|
||||
}
|
||||
|
||||
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
|
||||
state.count = countRes.res[0].count || countRes.res[0].COUNT || 0;
|
||||
state.count = parseInt(countRes.res[0].count || countRes.res[0].COUNT || 0);
|
||||
let sql = dbInst.getDefaultSelectSql(table, state.condition, state.orderBy, state.pageNum, state.pageSize);
|
||||
state.sql = sql;
|
||||
if (state.count > 0) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" width="90%" :close-on-press-escape="false" :close-on-click-modal="false">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" width="70%" :close-on-press-escape="false" :close-on-click-modal="false">
|
||||
<el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
@@ -26,7 +26,7 @@
|
||||
:width="item.width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name"> </el-input>
|
||||
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name" />
|
||||
|
||||
<el-select v-else-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
|
||||
<el-option
|
||||
@@ -42,35 +42,30 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-input v-else-if="item.prop === 'value'" size="small" v-model="scope.row.value"> </el-input>
|
||||
<el-input v-else-if="item.prop === 'value'" size="small" v-model="scope.row.value" />
|
||||
|
||||
<el-input v-else-if="item.prop === 'length'" size="small" v-model="scope.row.length"> </el-input>
|
||||
<el-input v-else-if="item.prop === 'length'" type="number" size="small" v-model.number="scope.row.length" />
|
||||
|
||||
<el-input v-else-if="item.prop === 'numScale'" size="small" v-model="scope.row.numScale"> </el-input>
|
||||
<el-input v-else-if="item.prop === 'numScale'" type="number" size="small" v-model.number="scope.row.numScale" />
|
||||
|
||||
<el-checkbox v-else-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull"> </el-checkbox>
|
||||
<el-checkbox v-else-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull" />
|
||||
|
||||
<el-checkbox v-else-if="item.prop === 'pri'" size="small" v-model="scope.row.pri"> </el-checkbox>
|
||||
<el-checkbox v-else-if="item.prop === 'pri'" size="small" v-model="scope.row.pri" />
|
||||
|
||||
<el-checkbox
|
||||
v-else-if="item.prop === 'auto_increment'"
|
||||
size="small"
|
||||
v-model="scope.row.auto_increment"
|
||||
:disabled="dbType === DbType.postgresql"
|
||||
>
|
||||
</el-checkbox>
|
||||
/>
|
||||
|
||||
<el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark"> </el-input>
|
||||
<el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark" />
|
||||
|
||||
<el-link
|
||||
v-else-if="item.prop === 'action'"
|
||||
type="danger"
|
||||
plain
|
||||
size="small"
|
||||
:underline="false"
|
||||
@click.prevent="deleteRow(scope.$index)"
|
||||
>删除</el-link
|
||||
>
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteRow(scope.$index)">
|
||||
<template #reference>
|
||||
<el-link type="danger" plain size="small" :underline="false">删除</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -110,15 +105,11 @@
|
||||
|
||||
<el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
|
||||
|
||||
<el-link
|
||||
v-if="item.prop === 'action'"
|
||||
type="danger"
|
||||
plain
|
||||
size="small"
|
||||
:underline="false"
|
||||
@click.prevent="deleteIndex(scope.$index)"
|
||||
>删除</el-link
|
||||
>
|
||||
<el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteIndex(scope.$index)">
|
||||
<template #reference>
|
||||
<el-link type="danger" plain size="small" :underline="false">删除</el-link>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -130,6 +121,7 @@
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="cancel()">取消</el-button>
|
||||
<el-button :loading="btnloading" @click="submit()" type="primary">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -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)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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('不支持的数据库');
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
337
mayfly_go_web/src/views/ops/db/dialect/sqlite_dialect.ts
Normal file
337
mayfly_go_web/src/views/ops/db/dialect/sqlite_dialect.ts
Normal file
@@ -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}'`;
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
|
||||
@@ -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("请完善分页信息后执行")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("该数据库实例已存在")
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
DbTypePostgres DbType = "postgres"
|
||||
DbTypeDM DbType = "dm"
|
||||
DbTypeOracle DbType = "oracle"
|
||||
DbTypeSqlite DbType = "sqlite"
|
||||
)
|
||||
|
||||
func ToDbType(dbType string) DbType {
|
||||
|
||||
@@ -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)
|
||||
|
||||
21
server/internal/db/dbm/dbi/metasql/sqlite_meta.sql
Normal file
21
server/internal/db/dbm/dbi/metasql/sqlite_meta.sql
Normal file
@@ -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
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
247
server/internal/db/dbm/sqlite/dialect.go
Normal file
247
server/internal/db/dbm/sqlite/dialect.go
Normal file
@@ -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
|
||||
}
|
||||
38
server/internal/db/dbm/sqlite/meta.go
Normal file
38
server/internal/db/dbm/sqlite/meta.go
Normal file
@@ -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}
|
||||
}
|
||||
Binary file not shown.
@@ -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 '其他连接参数',
|
||||
|
||||
3
server/resources/script/sql/v1.7.1.sql
Normal file
3
server/resources/script/sql/v1.7.1.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `t_db_instance`
|
||||
MODIFY `port` int (8) NULL comment '数据库端口',
|
||||
MODIFY `username` varchar (255) NULL comment '数据库用户名';
|
||||
Reference in New Issue
Block a user