!86 dbms支持sqlite和一些bug修复

* fix: 达梦数据库连接修复,以支持带特殊字符的密码和schema
* fix: oracle bug修复
* feat: dbms支持sqlite
* fix: dbms 修改字段名bug
This commit is contained in:
zongyangleo
2024-01-19 08:59:35 +00:00
committed by Coder慌
parent b017b902f8
commit 9a59749763
27 changed files with 987 additions and 155 deletions

File diff suppressed because one or more lines are too long

View File

@@ -55,11 +55,11 @@
"unicode_decimal": 58905 "unicode_decimal": 58905
}, },
{ {
"icon_id": "11617944", "icon_id": "25271976",
"name": "oracle", "name": "oracle",
"font_class": "oracle", "font_class": "oracle",
"unicode": "e6ea", "unicode": "e507",
"unicode_decimal": 59114 "unicode_decimal": 58631
}, },
{ {
"icon_id": "8105644", "icon_id": "8105644",
@@ -67,6 +67,13 @@
"font_class": "mariadb", "font_class": "mariadb",
"unicode": "e513", "unicode": "e513",
"unicode_decimal": 58643 "unicode_decimal": 58643
},
{
"icon_id": "13601813",
"name": "sqlite",
"font_class": "sqlite",
"unicode": "e546",
"unicode_decimal": 58694
} }
] ]
} }

View File

@@ -15,7 +15,7 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </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-col :span="18">
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input> <el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
</el-col> </el-col>
@@ -24,13 +24,18 @@
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input> <el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
</el-col> </el-col>
</el-form-item> </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-form-item v-if="form.type === DbType.oracle" prop="sid" label="SID">
<el-input v-model.trim="form.sid" placeholder="请输入服务id"></el-input> <el-input v-model.trim="form.sid" placeholder="请输入服务id"></el-input>
</el-form-item> </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-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item> </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"> <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix> <template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd"> <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
@@ -169,6 +174,10 @@ const dbTypes = [
type: 'oracle', type: 'oracle',
label: 'oracle', label: 'oracle',
}, },
{
type: 'sqlite',
label: 'sqlite',
},
]; ];
const state = reactive({ const state = reactive({
@@ -176,7 +185,7 @@ const state = reactive({
tabActiveName: 'basic', tabActiveName: 'basic',
form: { form: {
id: null, id: null,
type: null, type: '',
name: null, name: null,
host: '', host: '',
port: null, port: null,
@@ -247,10 +256,12 @@ const testConn = async () => {
}; };
const btnOk = async () => { const btnOk = async () => {
if (!state.form.id) { if (state.form.type !== DbType.sqlite) {
notBlank(state.form.password, '新增操作,密码不可为空'); if (!state.form.id) {
} else if (state.form.username != state.oldUserName) { notBlank(state.form.password, '新增操作,密码不可为空');
notBlank(state.form.password, '已修改用户名,请输入密码'); } else if (state.form.username != state.oldUserName) {
notBlank(state.form.password, '已修改用户名,请输入密码');
}
} }
dbForm.value.validate(async (valid: boolean) => { dbForm.value.validate(async (valid: boolean) => {

View File

@@ -269,26 +269,24 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
nParams.schema = sn; nParams.schema = sn;
nParams.db = nParams.db + '/' + sn; nParams.db = nParams.db + '/' + sn;
nParams.dbs = schemaNames; 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 [ return NodeTypeTables(params);
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),
];
}) })
.withNodeClickFunc(nodeClickChangeDb); .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模式 // 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))]) .withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
.withLoadNodesFunc(async (parentNode: TagTreeNode) => { .withLoadNodesFunc(async (parentNode: TagTreeNode) => NodeTypeTables(parentNode.params))
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),
];
})
.withNodeClickFunc(nodeClickChangeDb); .withNodeClickFunc(nodeClickChangeDb);
// 数据库表菜单节点 // 数据库表菜单节点

View File

@@ -371,7 +371,7 @@ const selectData = async () => {
} }
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition)); 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); let sql = dbInst.getDefaultSelectSql(table, state.condition, state.orderBy, state.pageNum, state.pageSize);
state.sql = sql; state.sql = sql;
if (state.count > 0) { if (state.count > 0) {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <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-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
@@ -26,7 +26,7 @@
:width="item.width" :width="item.width"
> >
<template #default="scope"> <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-select v-else-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
<el-option <el-option
@@ -42,35 +42,30 @@
</el-option> </el-option>
</el-select> </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 <el-checkbox
v-else-if="item.prop === 'auto_increment'" v-else-if="item.prop === 'auto_increment'"
size="small" size="small"
v-model="scope.row.auto_increment" v-model="scope.row.auto_increment"
:disabled="dbType === DbType.postgresql" :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 <el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteRow(scope.$index)">
v-else-if="item.prop === 'action'" <template #reference>
type="danger" <el-link type="danger" plain size="small" :underline="false">删除</el-link>
plain </template>
size="small" </el-popconfirm>
:underline="false"
@click.prevent="deleteRow(scope.$index)"
>删除</el-link
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -110,15 +105,11 @@
<el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input> <el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
<el-link <el-popconfirm v-else-if="item.prop === 'action'" title="确定删除?" @confirm="deleteIndex(scope.$index)">
v-if="item.prop === 'action'" <template #reference>
type="danger" <el-link type="danger" plain size="small" :underline="false">删除</el-link>
plain </template>
size="small" </el-popconfirm>
:underline="false"
@click.prevent="deleteIndex(scope.$index)"
>删除</el-link
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -130,6 +121,7 @@
</el-tabs> </el-tabs>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="cancel()">取消</el-button>
<el-button :loading="btnloading" @click="submit()" type="primary">保存</el-button> <el-button :loading="btnloading" @click="submit()" type="primary">保存</el-button>
</template> </template>
</el-dialog> </el-dialog>
@@ -187,22 +179,27 @@ const state = reactive({
{ {
prop: 'name', prop: 'name',
label: '字段名称', label: '字段名称',
width: 200,
}, },
{ {
prop: 'type', prop: 'type',
label: '字段类型', label: '字段类型',
width: 120,
}, },
{ {
prop: 'length', prop: 'length',
label: '长度', label: '长度',
width: 120,
}, },
{ {
prop: 'numScale', prop: 'numScale',
label: '小数点', label: '小数点',
width: 120,
}, },
{ {
prop: 'value', prop: 'value',
label: '默认值', label: '默认值',
width: 120,
}, },
{ {
@@ -231,6 +228,7 @@ const state = reactive({
}, },
] as ColName[], ] as ColName[],
res: [] as RowDefinition[], res: [] as RowDefinition[],
oldFields: [] as RowDefinition[],
}, },
indexs: { indexs: {
colNames: [ colNames: [
@@ -261,10 +259,12 @@ const state = reactive({
], ],
columns: [{ name: '', remark: '' }], columns: [{ name: '', remark: '' }],
res: [] as IndexDefinition[], res: [] as IndexDefinition[],
oldIndexs: [] as IndexDefinition[],
}, },
tableName: '', tableName: '',
tableComment: '', tableComment: '',
height: 450, height: 450,
db: '',
}, },
}); });
@@ -359,7 +359,10 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
nowArr.forEach((a) => { nowArr.forEach((a) => {
let k = a[key]; let k = a[key];
newMap[k] = a; 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); data.add.push(a);
} }
@@ -376,7 +379,7 @@ const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { d
for (let f in a) { for (let f in a) {
let oldV = a[f]; let oldV = a[f];
let newV = newData[f]; let newV = newData[f];
if (oldV.toString() !== newV.toString()) { if (oldV?.toString() !== newV?.toString()) {
data.upd.push(newData); data.upd.push(newData);
break; break;
} }
@@ -399,11 +402,11 @@ const genSql = () => {
// 修改 // 修改
if (state.activeName === '1') { if (state.activeName === '1') {
// 修改列 // 修改列
let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name'); let changeData = filterChangedData(state.tableData.fields.oldFields, state.tableData.fields.res, 'name');
return dbDialect.getModifyColumnSql(data.tableName, changeData); return dbDialect.getModifyColumnSql(data, data.tableName, changeData);
} else if (state.activeName === '2') { } 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); return dbDialect.getModifyIndexSql(data.tableName, changeData);
} }
} }
@@ -456,7 +459,6 @@ const indexChanges = (row: any) => {
row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`; row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`;
}; };
const oldData = { indexs: [] as any[], fields: [] as RowDefinition[] };
watch( watch(
() => props.data, () => props.data,
(newValue: any) => { (newValue: any) => {
@@ -464,9 +466,10 @@ watch(
// 回显表名表注释 // 回显表名表注释
state.tableData.tableName = row.tableName; state.tableData.tableName = row.tableName;
state.tableData.tableComment = row.tableComment; state.tableData.tableComment = row.tableComment;
state.tableData.db = props.db!;
// 回显列 // 回显列
if (columns && Array.isArray(columns) && columns.length > 0) { if (columns && Array.isArray(columns) && columns.length > 0) {
oldData.fields = []; state.tableData.fields.oldFields = [];
state.tableData.fields.res = []; state.tableData.fields.res = [];
// 索引列下拉选 // 索引列下拉选
state.tableData.indexs.columns = []; state.tableData.indexs.columns = [];
@@ -474,10 +477,17 @@ watch(
let typeObj = a.columnType.replace(')', '').split('('); let typeObj = a.columnType.replace(')', '').split('(');
let type = typeObj[0]; let type = typeObj[0];
let length = (typeObj.length > 1 && typeObj[1]) || ''; 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 = { let data = {
name: a.columnName, name: a.columnName,
oldName: a.columnName,
type, type,
value: a.columnDefault || '', value: defaultValue,
length, length,
numScale: a.numScale, numScale: a.numScale,
notNull: a.nullable !== 'YES', notNull: a.nullable !== 'YES',
@@ -486,14 +496,14 @@ watch(
remark: a.columnComment, remark: a.columnComment,
}; };
state.tableData.fields.res.push(data); 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 }); state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment });
}); });
} }
// 回显索引 // 回显索引
if (indexs && Array.isArray(indexs) && indexs.length > 0) { if (indexs && Array.isArray(indexs) && indexs.length > 0) {
oldData.indexs = []; state.tableData.indexs.oldIndexs = [];
state.tableData.indexs.res = []; state.tableData.indexs.res = [];
// 索引过滤掉主键 // 索引过滤掉主键
indexs indexs
@@ -507,7 +517,7 @@ watch(
indexComment: a.indexComment, indexComment: a.indexComment,
}; };
state.tableData.indexs.res.push(data); state.tableData.indexs.res.push(data);
oldData.indexs.push(JSON.parse(JSON.stringify(data))); state.tableData.indexs.oldIndexs.push(JSON.parse(JSON.stringify(data)));
}); });
} }
} }

View File

@@ -181,7 +181,7 @@ const state = reactive({
visible: false, visible: false,
activeName: '1', activeName: '1',
type: '', 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: { data: {
// 修改表时,传递修改数据 // 修改表时,传递修改数据
edit: false, edit: false,

View File

@@ -54,6 +54,7 @@ const DM_TYPE_LIST: sqlColumnType[] = [
{ udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '100G-1' }, { udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '100G-1' },
]; ];
// 参考官方文档https://eco.dameng.com/document/dm/zh-cn/pm/function.html
const replaceFunctions: EditorCompletionItem[] = [ const replaceFunctions: EditorCompletionItem[] = [
// 数值函数 // 数值函数
{ label: 'ABS', insertText: 'ABS(n)', description: '求数值 n 的绝对值' }, { label: 'ABS', insertText: 'ABS(n)', description: '求数值 n 的绝对值' },
@@ -367,7 +368,7 @@ class DMDialect implements DbDialect {
dmDialectInfo = { dmDialectInfo = {
icon: 'iconfont icon-db-dm', icon: 'iconfont icon-db-dm',
defaultPort: 5236, defaultPort: 5236,
formatSqlDialect: 'postgresql', formatSqlDialect: 'plsql',
columnTypes: DM_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)), columnTypes: DM_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
editorCompletions, editorCompletions,
}; };
@@ -500,7 +501,9 @@ class DMDialect implements DbDialect {
// 默认值 // 默认值
let defVal = this.getDefaultValueSql(cl); let defVal = this.getDefaultValueSql(cl);
let incr = cl.auto_increment ? 'IDENTITY' : ''; 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 { getCreateTableSql(data: any): string {
@@ -546,32 +549,75 @@ class DMDialect implements DbDialect {
return sql.join(';'); return sql.join(';');
} }
getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string { getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
let sql: 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) { if (changeData.add.length > 0) {
changeData.add.forEach((a) => { 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) { 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) { if (changeData.upd.length > 0) {
changeData.upd.forEach((a) => { changeData.upd.forEach((a) => {
sql.push(`ALTER TABLE "${tableName}" MODIFY ${this.genColumnBasicSql(a)}`); let cmtSql = `COMMENT ON COLUMN ${dbTable}.${this.quoteIdentifier(a.name)} IS '${a.remark}';`;
if (a.remark) { if (a.remark && a.oldName === a.name) {
sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`); 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) { if (changeData.del.length > 0) {
changeData.del.forEach((a) => { 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 { getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {

View File

@@ -3,6 +3,7 @@ import { PostgresqlDialect } from './postgres_dialect';
import { DMDialect } from '@/views/ops/db/dialect/dm_dialect'; import { DMDialect } from '@/views/ops/db/dialect/dm_dialect';
import { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect'; import { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect';
import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect'; import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect';
import { SqliteDialect } from '@/views/ops/db/dialect/sqlite_dialect';
export interface sqlColumnType { export interface sqlColumnType {
udtName: string; udtName: string;
@@ -14,6 +15,7 @@ export interface sqlColumnType {
export interface RowDefinition { export interface RowDefinition {
name: string; name: string;
oldName?: string;
type: string; type: string;
value: string; value: string;
length: string; length: string;
@@ -110,6 +112,7 @@ export const DbType = {
postgresql: 'postgres', postgresql: 'postgres',
dm: 'dm', // 达梦 dm: 'dm', // 达梦
oracle: 'oracle', oracle: 'oracle',
sqlite: 'sqlite',
}; };
export const compatibleMysql = (dbType: string): boolean => { export const compatibleMysql = (dbType: string): boolean => {
@@ -164,10 +167,11 @@ export interface DbDialect {
/** /**
* 生成编辑列sql * 生成编辑列sql
* @param tableData 表数据,包含表名、列数据、索引数据
* @param tableName 表名 * @param tableName 表名
* @param changeData 改变信息 * @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 * 生成编辑索引sql
@@ -177,7 +181,7 @@ export interface DbDialect {
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string; 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') */ /** 包装字符串数据, 如oracle需要把date类型改为 to_date(str, 'yyyy-mm-dd hh24:mi:ss') */
wrapStrValue(columnType: string, value: string): string; wrapStrValue(columnType: string, value: string): string;
@@ -188,6 +192,7 @@ let mariadbDialect = new MariadbDialect();
let postgresDialect = new PostgresqlDialect(); let postgresDialect = new PostgresqlDialect();
let dmDialect = new DMDialect(); let dmDialect = new DMDialect();
let oracleDialect = new OracleDialect(); let oracleDialect = new OracleDialect();
let sqliteDialect = new SqliteDialect();
export const getDbDialect = (dbType: string | undefined): DbDialect => { export const getDbDialect = (dbType: string | undefined): DbDialect => {
if (!dbType) { if (!dbType) {
@@ -204,6 +209,8 @@ export const getDbDialect = (dbType: string | undefined): DbDialect => {
return dmDialect; return dmDialect;
case DbType.oracle: case DbType.oracle:
return oracleDialect; return oracleDialect;
case DbType.sqlite:
return sqliteDialect;
default: default:
throw new Error('不支持的数据库'); throw new Error('不支持的数据库');
} }

View File

@@ -4,6 +4,7 @@ import { language as mysqlLanguage } from 'monaco-editor/esm/vs/basic-languages/
export { MYSQL_TYPE_LIST, MysqlDialect }; export { MYSQL_TYPE_LIST, MysqlDialect };
// 参考官方文档https://dev.mysql.com/doc/refman/8.0/en/data-types.html
const MYSQL_TYPE_LIST = [ const MYSQL_TYPE_LIST = [
'bigint', 'bigint',
'binary', 'binary',
@@ -31,6 +32,7 @@ const MYSQL_TYPE_LIST = [
'varchar', 'varchar',
]; ];
// 参考官方文档https://dev.mysql.com/doc/refman/8.3/en/functions.html
const replaceFunctions: EditorCompletionItem[] = [ const replaceFunctions: EditorCompletionItem[] = [
/** 字符串相关函数 */ /** 字符串相关函数 */
{ label: 'CONCAT', insertText: 'CONCAT(str1,str2,...)', description: '多字符串合并' }, { label: 'CONCAT', insertText: 'CONCAT(str1,str2,...)', description: '多字符串合并' },
@@ -193,7 +195,7 @@ class MysqlDialect implements DbDialect {
let defVal = val ? `DEFAULT ${val}` : ''; let defVal = val ? `DEFAULT ${val}` : '';
let length = cl.length ? `(${cl.length})` : ''; let length = cl.length ? `(${cl.length})` : '';
let onUpdate = 'update_time' === cl.name ? ' ON UPDATE CURRENT_TIMESTAMP ' : ''; 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' : '' cl.auto_increment ? 'AUTO_INCREMENT' : ''
} ${defVal} ${onUpdate} comment '${cl.remark || ''}' `; } ${defVal} ${onUpdate} comment '${cl.remark || ''}' `;
} }
@@ -223,35 +225,31 @@ class MysqlDialect implements DbDialect {
return sql.substring(0, sql.length - 1) + ';'; return sql.substring(0, sql.length - 1) + ';';
} }
getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string { getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
let addSql = '', let sql = `ALTER TABLE ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)}`;
updSql = '', let arr = [] as string[];
delSql = ''; if (changeData.del.length > 0) {
if (changeData.add.length > 0) { changeData.del.forEach((a) => {
addSql = `ALTER TABLE ${tableName}`; arr.push(` DROP COLUMN ${this.quoteIdentifier(a.name)} `);
changeData.add.forEach((a) => { });
addSql += ` ADD ${this.genColumnBasicSql(a)},`; }
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) { if (changeData.upd.length > 0) {
updSql = `ALTER TABLE ${tableName}`;
let arr = [] as string[];
changeData.upd.forEach((a) => { 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) { return sql + arr.join(',') + ';';
changeData.del.forEach((a) => {
delSql += ` ALTER TABLE ${tableName} DROP COLUMN ${a.name}; `;
});
}
return addSql + updSql + delSql;
} }
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string { getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {

View File

@@ -14,7 +14,7 @@ import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sq
export { OracleDialect, ORACLE_TYPE_LIST }; 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[] = [ const ORACLE_TYPE_LIST: sqlColumnType[] = [
// 字符数据类型 // 字符数据类型
{ udtName: 'CHAR', dataType: 'CHAR', desc: '定长字符串,自动在末尾用空格补全,非unicode', space: '', range: '1 - 2000' }, { 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: '' }, { udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '' },
]; ];
// 参考官方文档https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions001.htm
const replaceFunctions: EditorCompletionItem[] = [ const replaceFunctions: EditorCompletionItem[] = [
// 字符函数 // 字符函数
{ label: 'ASCII', insertText: 'ASCII(x)', description: '返回字符X的ASCII码' }, { label: 'ASCII', insertText: 'ASCII(x)', description: '返回字符X的ASCII码' },
@@ -268,16 +269,22 @@ class OracleDialect implements DbDialect {
return ''; return '';
} }
genColumnBasicSql(cl: RowDefinition): string { genColumnBasicSql(cl: RowDefinition, create: boolean): string {
let length = this.getTypeLengthSql(cl); let length = this.getTypeLengthSql(cl);
// 默认值 // 默认值
let defVal = this.getDefaultValueSql(cl); let defVal = this.getDefaultValueSql(cl);
let incr = cl.auto_increment ? 'generated by default as IDENTITY' : ''; let incr = cl.auto_increment && create ? '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 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 { 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 createSql = '';
let tableCommentSql = ''; let tableCommentSql = '';
let columCommentSql = ''; let columCommentSql = '';
@@ -285,17 +292,17 @@ class OracleDialect implements DbDialect {
// 创建表结构 // 创建表结构
let fields: string[] = []; let fields: string[] = [];
data.fields.res.forEach((item: any) => { data.fields.res.forEach((item: any) => {
item.name && fields.push(this.genColumnBasicSql(item)); item.name && fields.push(this.genColumnBasicSql(item, true));
// 列注释 // 列注释
if (item.remark) { 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) { 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; return createSql + tableCommentSql + columCommentSql;
@@ -304,40 +311,92 @@ class OracleDialect implements DbDialect {
getCreateIndexSql(tableData: any): string { getCreateIndexSql(tableData: any): string {
// CREATE UNIQUE INDEX idx_column_name ON your_table (column1, column2); // CREATE UNIQUE INDEX idx_column_name ON your_table (column1, column2);
// COMMENT ON INDEX idx_column_name IS 'Your index comment here'; // 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[] = []; let sql: string[] = [];
tableData.indexs.res.forEach((a: any) => { 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(';'); return sql.join(';');
} }
getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string { getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
let sql: string[] = []; let schemaArr = tableData.db.split('/');
if (changeData.add.length > 0) { let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0];
changeData.add.forEach((a) => { let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableName)}`;
sql.push(`ALTER TABLE "${tableName}" add COLUMN ${this.genColumnBasicSql(a)}`);
if (a.remark) { let baseSql = `ALTER TABLE ${dbTable} `;
sql.push(`comment on COLUMN "${tableName}"."${a.name}" is '${a.remark}'`);
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) { if (changeData.add.length > 0) {
changeData.upd.forEach((a) => { changeData.add.forEach((a) => {
sql.push(`ALTER TABLE "${tableName}" MODIFY ${this.genColumnBasicSql(a)}`); modifyArr.push(` ADD (${this.genColumnBasicSql(a, false)})`);
if (a.remark) { 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) { if (changeData.del.length > 0) {
changeData.del.forEach((a) => { 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 { getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {

View File

@@ -228,7 +228,7 @@ class PostgresqlDialect implements DbDialect {
let marks = false; let marks = false;
if (this.matchType(cl.type, ['char', 'time', 'date', 'text'])) { if (this.matchType(cl.type, ['char', 'time', 'date', 'text'])) {
// 默认值是now()的time或date不需要加引号 // 默认值是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; marks = false;
} else { } else {
marks = true; marks = true;
@@ -260,7 +260,10 @@ class PostgresqlDialect implements DbDialect {
let length = this.getTypeLengthSql(cl); let length = this.getTypeLengthSql(cl);
// 默认值 // 默认值
let defVal = this.getDefaultValueSql(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 { getCreateTableSql(data: any): string {
@@ -301,7 +304,7 @@ class PostgresqlDialect implements DbDialect {
// 创建索引 // 创建索引
let sql: string[] = []; let sql: string[] = [];
tableData.indexs.res.forEach((a: any) => { 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) { if (a.indexComment) {
sql.push(`COMMENT ON INDEX ${a.indexName} IS '${a.indexComment}'`); sql.push(`COMMENT ON INDEX ${a.indexName} IS '${a.indexComment}'`);
} }
@@ -309,39 +312,57 @@ class PostgresqlDialect implements DbDialect {
return sql.join(';'); return sql.join(';');
} }
getModifyColumnSql(tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string { getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
let sql: 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) { if (changeData.add.length > 0) {
changeData.add.forEach((a) => { changeData.add.forEach((a) => {
let typeLength = this.getTypeLengthSql(a); modifySql += `alter table ${dbTable} add ${this.genColumnBasicSql(a)};`;
let defaultSql = this.getDefaultValueSql(a);
sql.push(`ALTER TABLE ${tableName} add ${a.name} ${a.type}${typeLength} ${defaultSql}`);
if (a.remark) { 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) { if (changeData.upd.length > 0) {
changeData.upd.forEach((a) => { 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); 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); let defaultSql = this.getDefaultValueSql(a);
if (defaultSql) { if (defaultSql) {
sql.push(`alter table ${tableName} alter column ${a.name} set ${defaultSql}`); modifySql += `alter table ${dbTable} alter column ${this.quoteIdentifier(name)} set ${defaultSql} ;`;
}
if (a.remark) {
sql.push(`comment on column "${tableName}"."${a.name}" is '${a.remark}'`);
} }
}); });
} }
if (changeData.del.length > 0) { if (changeData.del.length > 0) {
changeData.del.forEach((a) => { 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 { getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {

View 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}'`;
}
}

View File

@@ -5,9 +5,9 @@ type InstanceForm struct {
Name string `binding:"required" json:"name"` Name string `binding:"required" json:"name"`
Type string `binding:"required" json:"type"` // 类型mysql oracle等 Type string `binding:"required" json:"type"` // 类型mysql oracle等
Host string `binding:"required" json:"host"` Host string `binding:"required" json:"host"`
Port int `binding:"required" json:"port"` Port int `json:"port"`
Sid string `json:"sid"` Sid string `json:"sid"`
Username string `binding:"required" json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
Params string `json:"params"` Params string `json:"params"`
Remark string `json:"remark"` Remark string `json:"remark"`

View File

@@ -93,8 +93,12 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
// 如果配置为0则不校验分页参数 // 如果配置为0则不校验分页参数
maxCount := config.GetDbQueryMaxCount() maxCount := config.GetDbQueryMaxCount()
if maxCount != 0 { if maxCount != 0 {
if !strings.Contains(lowerSql, "limit") { // 兼容oracle rownum分页
return nil, errorx.NewBiz("请完善分页信息后执行") if !strings.Contains(lowerSql, "limit") && !strings.Contains(lowerSql, "rownum") {
// 判断是不是count语句
if !strings.Contains(lowerSql, "count(") {
return nil, errorx.NewBiz("请完善分页信息后执行")
}
} }
} }
} }

View File

@@ -73,9 +73,11 @@ func (app *instanceAppImpl) Save(ctx context.Context, instanceEntity *entity.DbI
err := app.GetBy(oldInstance) err := app.GetBy(oldInstance)
if instanceEntity.Id == 0 { if instanceEntity.Id == 0 {
if instanceEntity.Password == "" {
if instanceEntity.Type != string(dbi.DbTypeSqlite) && instanceEntity.Password == "" {
return errorx.NewBiz("密码不能为空") return errorx.NewBiz("密码不能为空")
} }
if err == nil { if err == nil {
return errorx.NewBiz("该数据库实例已存在") return errorx.NewBiz("该数据库实例已存在")
} }

View File

@@ -16,6 +16,7 @@ const (
DbTypePostgres DbType = "postgres" DbTypePostgres DbType = "postgres"
DbTypeDM DbType = "dm" DbTypeDM DbType = "dm"
DbTypeOracle DbType = "oracle" DbTypeOracle DbType = "oracle"
DbTypeSqlite DbType = "sqlite"
) )
func ToDbType(dbType string) DbType { func ToDbType(dbType string) DbType {

View File

@@ -75,7 +75,7 @@ type Dialect interface {
GetColumns(tableNames ...string) ([]Column, error) GetColumns(tableNames ...string) ([]Column, error)
// 获取表主键字段名,没有主键标识则默认第一个字段 // 获取表主键字段名,没有主键标识则默认第一个字段
GetPrimaryKey(tablename string) (string, error) GetPrimaryKey(tableName string) (string, error)
// 获取表索引信息 // 获取表索引信息
GetTableIndex(tableName string) ([]Index, error) GetTableIndex(tableName string) ([]Index, error)

View 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

View File

@@ -8,6 +8,7 @@ import (
"mayfly-go/internal/db/dbm/mysql" "mayfly-go/internal/db/dbm/mysql"
"mayfly-go/internal/db/dbm/oracle" "mayfly-go/internal/db/dbm/oracle"
"mayfly-go/internal/db/dbm/postgres" "mayfly-go/internal/db/dbm/postgres"
"mayfly-go/internal/db/dbm/sqlite"
"mayfly-go/internal/machine/mcm" "mayfly-go/internal/machine/mcm"
"mayfly-go/pkg/cache" "mayfly-go/pkg/cache"
"mayfly-go/pkg/logx" "mayfly-go/pkg/logx"
@@ -48,6 +49,8 @@ func getDbMetaByType(dt dbi.DbType) dbi.Meta {
return dm.GetMeta() return dm.GetMeta()
case dbi.DbTypeOracle: case dbi.DbTypeOracle:
return oracle.GetMeta() return oracle.GetMeta()
case dbi.DbTypeSqlite:
return sqlite.GetMeta()
default: default:
panic(fmt.Sprintf("invalid database type: %s", dt)) panic(fmt.Sprintf("invalid database type: %s", dt))
} }

View File

@@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"mayfly-go/internal/db/dbm/dbi" "mayfly-go/internal/db/dbm/dbi"
"net/url"
"strings" "strings"
"sync" "sync"
) )
@@ -31,10 +32,12 @@ func (md *DmMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
// dm database可以使用db/schema表示方便连接指定schema, 若不存在schema则使用默认schema // dm database可以使用db/schema表示方便连接指定schema, 若不存在schema则使用默认schema
ss := strings.Split(db, "/") ss := strings.Split(db, "/")
if len(ss) > 1 { 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 { } else {
dbParam = db dbParam = db + "?escapeProcess=true"
} }
} else {
dbParam = "?escapeProcess=true"
} }
err := d.IfUseSshTunnelChangeIpPort() err := d.IfUseSshTunnelChangeIpPort()
@@ -42,7 +45,7 @@ func (md *DmMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
return nil, err 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) return sql.Open(driverName, dsn)
} }

View File

@@ -44,9 +44,25 @@ func (md *OraMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
schema = ss[1] schema = ss[1]
} }
} }
// 解析参数
urlOptions["TIMEOUT"] = "60" if d.Params != "" {
urlOptions["client charset"] = "UTF8" 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) connStr := go_ora.BuildUrl(d.Host, d.Port, d.Sid, d.Username, d.Password, urlOptions)
conn, err := sql.Open(driverName, connStr) conn, err := sql.Open(driverName, connStr)
if err != nil { if err != nil {

View 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
}

View 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}
}

View File

@@ -9,9 +9,9 @@ CREATE TABLE `t_db_instance` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称', `name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
`host` varchar(100) COLLATE utf8mb4_bin NOT NULL, `host` varchar(100) COLLATE utf8mb4_bin NOT NULL,
`port` int(8) NOT NULL, `port` int(8) NULL,
`sid` varchar(255) NULL COMMENT 'oracle数据库需要sid', `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, `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)', `type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
`params` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '其他连接参数', `params` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '其他连接参数',

View File

@@ -0,0 +1,3 @@
ALTER TABLE `t_db_instance`
MODIFY `port` int (8) NULL comment '数据库端口',
MODIFY `username` varchar (255) NULL comment '数据库用户名';