mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
1
mayfly_go_web/.gitignore
vendored
1
mayfly_go_web/.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
node_modules
|
||||
/dist
|
||||
*.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -53,6 +53,20 @@
|
||||
"font_class": "redis",
|
||||
"unicode": "e619",
|
||||
"unicode_decimal": 58905
|
||||
},
|
||||
{
|
||||
"icon_id": "11617944",
|
||||
"name": "oracle",
|
||||
"font_class": "oracle",
|
||||
"unicode": "e6ea",
|
||||
"unicode_decimal": 59114
|
||||
},
|
||||
{
|
||||
"icon_id": "8105644",
|
||||
"name": "mariadb",
|
||||
"font_class": "mariadb",
|
||||
"unicode": "e513",
|
||||
"unicode_decimal": 58643
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" label="类型" required>
|
||||
<el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
|
||||
<el-option v-for="dt in dbTypes" :key="dt.type" :value="dt.type">
|
||||
<el-option v-for="dt in dbTypes" :key="dt.type" :value="dt.type" :label="dt.label">
|
||||
<SvgIcon :name="getDbDialect(dt.type).getInfo().icon" :size="18" />
|
||||
{{ dt.label }}
|
||||
</el-option>
|
||||
@@ -24,6 +24,9 @@
|
||||
<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.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-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
@@ -87,7 +90,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { notBlank } from '@/common/assert';
|
||||
import { RsaEncrypt } from '@/common/rsa';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
import { getDbDialect } from './dialect';
|
||||
import { DbType, getDbDialect } from './dialect';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -134,6 +137,13 @@ const rules = {
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
sid: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入SID',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const dbForm: any = ref(null);
|
||||
@@ -155,6 +165,10 @@ const dbTypes = [
|
||||
type: 'dm',
|
||||
label: '达梦',
|
||||
},
|
||||
{
|
||||
type: 'oracle',
|
||||
label: 'oracle',
|
||||
},
|
||||
];
|
||||
|
||||
const state = reactive({
|
||||
@@ -167,6 +181,7 @@ const state = reactive({
|
||||
host: '',
|
||||
port: null,
|
||||
username: null,
|
||||
sid: null, // oracle类项目需要服务id
|
||||
password: null,
|
||||
params: null,
|
||||
remark: '',
|
||||
|
||||
@@ -165,7 +165,7 @@ import { dbApi } from './api';
|
||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu';
|
||||
import { getDbDialect } from './dialect/index';
|
||||
import { DbType, getDbDialect } from './dialect/index';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
@@ -259,7 +259,7 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
// pg类数据库会多一层schema
|
||||
if (params.type == 'postgres' || params.type === 'dm') {
|
||||
if (params.type == DbType.postgresql || params.type === DbType.dm || params.type === DbType.oracle) {
|
||||
const params = parentNode.params;
|
||||
const { id, db } = params;
|
||||
const schemaNames = await dbApi.pgSchemas.request({ id, db });
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item prop="pageSize" label="分页大小" required>
|
||||
<el-input type="number" v-model.trim="form.pageSize" placeholder="同步数据时查询的每页数据大小" auto-complete="off" />
|
||||
<el-input type="number" v-model.number="form.pageSize" placeholder="同步数据时查询的每页数据大小" auto-complete="off" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, toRefs, watch, computed } from 'vue';
|
||||
import { computed, reactive, ref, toRefs, watch } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
|
||||
|
||||
@@ -18,6 +18,10 @@ export const dbApi = {
|
||||
sqlExec: Api.newPost('/dbs/{id}/exec-sql').withBeforeHandler((param: any) => {
|
||||
// sql编码处理
|
||||
if (param.sql) {
|
||||
// 判断是开发环境就打印sql
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(param.sql);
|
||||
}
|
||||
param.sql = Base64.encode(param.sql);
|
||||
}
|
||||
return param;
|
||||
@@ -58,7 +62,7 @@ export const dbApi = {
|
||||
enableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/enable'),
|
||||
disableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/disable'),
|
||||
saveDbRestore: Api.newPut('/dbs/{dbId}/restores/{id}'),
|
||||
|
||||
|
||||
// 数据同步相关
|
||||
datasyncTasks: Api.newGet('/datasync/tasks'),
|
||||
saveDatasyncTask: Api.newPost('/datasync/tasks/save').withBeforeHandler((param: any) => {
|
||||
|
||||
@@ -386,8 +386,8 @@ const onRunSql = async (newTab = false) => {
|
||||
const tableName = sql.split(/from/i)[1];
|
||||
if (tableName) {
|
||||
const tn = tableName.trim().split(' ')[0].split('\n')[0];
|
||||
execRes.table = tn;
|
||||
execRes.table = tn;
|
||||
// 去除表名前后的字符`或者"
|
||||
execRes.table = tn.replace(/`/g, '').replace(/"/g, '');
|
||||
} else {
|
||||
execRes.table = '';
|
||||
}
|
||||
|
||||
@@ -714,6 +714,7 @@ const submitUpdateFields = async () => {
|
||||
|
||||
const db = state.db;
|
||||
let res = '';
|
||||
const dbDialect = getDbDialect(dbInst.type);
|
||||
|
||||
for (let updateRow of cellUpdateMap.values()) {
|
||||
let sql = `UPDATE ${dbInst.wrapName(state.table)} SET `;
|
||||
@@ -731,7 +732,8 @@ const submitUpdateFields = async () => {
|
||||
}
|
||||
// 更新字段列信息
|
||||
const updateColumn = await dbInst.loadTableColumn(db, state.table, k);
|
||||
sql += ` ${dbInst.wrapName(k)} = ${DbInst.wrapColumnValue(updateColumn.columnType, rowData[k])},`;
|
||||
|
||||
sql += ` ${dbInst.wrapName(k)} = ${DbInst.wrapColumnValue(updateColumn.columnType, rowData[k], dbDialect)},`;
|
||||
|
||||
// 如果修改的字段是主键
|
||||
if (k === primaryKeyName) {
|
||||
@@ -790,6 +792,10 @@ const getFormatTimeValue = (dataType: DataType, originValue: string): string =>
|
||||
if (!originValue || dataType === DataType.Number || dataType === DataType.String) {
|
||||
return originValue;
|
||||
}
|
||||
|
||||
// 把Z去掉
|
||||
originValue = originValue.replace('Z', '');
|
||||
|
||||
switch (dataType) {
|
||||
case DataType.Time:
|
||||
return dateStrFormat('HH:mm:ss', originValue);
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
import { reactive, ref, toRefs, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||
import { getDbDialect, DbType, RowDefinition, IndexDefinition } from '../../dialect/index';
|
||||
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
|
||||
@@ -181,7 +181,7 @@ const state = reactive({
|
||||
visible: false,
|
||||
activeName: '1',
|
||||
type: '',
|
||||
enableEditTypes: [DbType.mysql, DbType.mariadb, DbType.postgresql, DbType.dm], // 支持"编辑表"的数据库类型
|
||||
enableEditTypes: [DbType.mysql, DbType.mariadb, DbType.postgresql, DbType.dm, DbType.oracle], // 支持"编辑表"的数据库类型
|
||||
data: {
|
||||
// 修改表时,传递修改数据
|
||||
edit: false,
|
||||
@@ -321,7 +321,7 @@ const dropTable = async (row: any) => {
|
||||
dbId: props.dbId as any,
|
||||
db: props.db as any,
|
||||
runSuccessCallback: async () => {
|
||||
state.tables = await dbApi.tableInfos.request({ id: props.dbId, db: props.db });
|
||||
await getTables();
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -357,7 +357,7 @@ const openEditTable = async (row: any) => {
|
||||
|
||||
const onSubmitSql = async (row: { tableName: string }) => {
|
||||
await openEditTable(row);
|
||||
state.tables = await dbApi.tableInfos.request({ id: props.dbId, db: props.db });
|
||||
await getTables();
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -221,7 +221,7 @@ export class DbInst {
|
||||
* @returns count sql
|
||||
*/
|
||||
getDefaultCountSql = (table: string, condition?: string) => {
|
||||
return `SELECT COUNT(*) count FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''} limit 1`;
|
||||
return `SELECT COUNT(*) count FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''}`;
|
||||
};
|
||||
|
||||
// 获取指定表的默认查询sql
|
||||
@@ -358,11 +358,14 @@ export class DbInst {
|
||||
/**
|
||||
* 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可
|
||||
*/
|
||||
static wrapColumnValue(columnType: string, value: any) {
|
||||
static wrapColumnValue(columnType: string, value: any, dbDialect?: DbDialect) {
|
||||
if (this.isNumber(columnType)) {
|
||||
return value;
|
||||
}
|
||||
return `'${value}'`;
|
||||
if (!dbDialect) {
|
||||
return `${value}`;
|
||||
}
|
||||
return dbDialect.wrapStrValue(columnType, value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -541,7 +541,7 @@ class DMDialect 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} ON "${tableData.tableName}" ("${a.columnNames.join('","')})"`);
|
||||
});
|
||||
return sql.join(';');
|
||||
}
|
||||
@@ -608,10 +608,7 @@ class DMDialect implements DbDialect {
|
||||
|
||||
if (addIndexs.length > 0) {
|
||||
addIndexs.forEach((a) => {
|
||||
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}'`);
|
||||
}
|
||||
sql.push(`CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName} ON "${tableName}" (${a.columnNames.join(',')})`);
|
||||
});
|
||||
}
|
||||
return sql.join(';');
|
||||
@@ -637,4 +634,9 @@ class DMDialect implements DbDialect {
|
||||
}
|
||||
return DataType.String;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
wrapStrValue(columnType: string, value: string): string {
|
||||
return `'${value}'`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { MysqlDialect } from './mysql_dialect';
|
||||
import { PostgresqlDialect } from './postgres_dialect';
|
||||
import { DMDialect } from '@/views/ops/db/dialect/dm_dialect';
|
||||
import { SqlLanguage } from 'sql-formatter/lib/src/sqlFormatter';
|
||||
import { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect';
|
||||
import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect';
|
||||
|
||||
export interface sqlColumnType {
|
||||
udtName: string;
|
||||
@@ -108,6 +110,7 @@ export const DbType = {
|
||||
mariadb: 'mariadb',
|
||||
postgresql: 'postgres',
|
||||
dm: 'dm', // 达梦
|
||||
oracle: 'oracle',
|
||||
};
|
||||
|
||||
export const compatibleMysql = (dbType: string): boolean => {
|
||||
@@ -176,11 +179,16 @@ export interface DbDialect {
|
||||
|
||||
/** 通过数据库字段类型,返回基本数据类型 */
|
||||
getDataType: (columnType: string) => DataType;
|
||||
|
||||
/** 包装字符串数据, 如:oracle需要把date类型改为 to_date(str, 'yyyy-mm-dd hh24:mi:ss') */
|
||||
wrapStrValue(columnType: string, value: string): string;
|
||||
}
|
||||
|
||||
let mysqlDialect = new MysqlDialect();
|
||||
let mariadbDialect = new MariadbDialect();
|
||||
let postgresDialect = new PostgresqlDialect();
|
||||
let dmDialect = new DMDialect();
|
||||
let oracleDialect = new OracleDialect();
|
||||
|
||||
export const getDbDialect = (dbType: string | undefined): DbDialect => {
|
||||
if (!dbType) {
|
||||
@@ -188,12 +196,15 @@ export const getDbDialect = (dbType: string | undefined): DbDialect => {
|
||||
}
|
||||
switch (dbType) {
|
||||
case DbType.mysql:
|
||||
case DbType.mariadb:
|
||||
return mysqlDialect;
|
||||
case DbType.mariadb:
|
||||
return mariadbDialect;
|
||||
case DbType.postgresql:
|
||||
return postgresDialect;
|
||||
case DbType.dm:
|
||||
return dmDialect;
|
||||
case DbType.oracle:
|
||||
return oracleDialect;
|
||||
default:
|
||||
throw new Error('不支持的数据库');
|
||||
}
|
||||
|
||||
18
mayfly_go_web/src/views/ops/db/dialect/mariadb_dialect.ts
Normal file
18
mayfly_go_web/src/views/ops/db/dialect/mariadb_dialect.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { DbDialect, DialectInfo } from './index';
|
||||
import { MysqlDialect } from '@/views/ops/db/dialect/mysql_dialect';
|
||||
|
||||
export { MariadbDialect };
|
||||
|
||||
let mariadbDialectInfo: DialectInfo;
|
||||
class MariadbDialect extends MysqlDialect implements DbDialect {
|
||||
getInfo(): DialectInfo {
|
||||
if (mariadbDialectInfo) {
|
||||
return mariadbDialectInfo;
|
||||
}
|
||||
|
||||
mariadbDialectInfo = {} as DialectInfo;
|
||||
Object.assign(mariadbDialectInfo, super.getInfo());
|
||||
mariadbDialectInfo.icon = 'iconfont icon-mariadb';
|
||||
return mariadbDialectInfo;
|
||||
}
|
||||
}
|
||||
@@ -328,4 +328,8 @@ class MysqlDialect implements DbDialect {
|
||||
}
|
||||
return DataType.String;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
wrapStrValue(columnType: string, value: string): string {
|
||||
return `'${value}'`;
|
||||
}
|
||||
}
|
||||
|
||||
402
mayfly_go_web/src/views/ops/db/dialect/oracle_dialect.ts
Normal file
402
mayfly_go_web/src/views/ops/db/dialect/oracle_dialect.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
import { DbInst } from '../db';
|
||||
import {
|
||||
commonCustomKeywords,
|
||||
DataType,
|
||||
DbDialect,
|
||||
DialectInfo,
|
||||
EditorCompletion,
|
||||
EditorCompletionItem,
|
||||
IndexDefinition,
|
||||
RowDefinition,
|
||||
sqlColumnType,
|
||||
} from './index';
|
||||
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sql/sql.js';
|
||||
|
||||
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
|
||||
const ORACLE_TYPE_LIST: sqlColumnType[] = [
|
||||
// 字符数据类型
|
||||
{ udtName: 'CHAR', dataType: 'CHAR', desc: '定长字符串,自动在末尾用空格补全,非unicode', space: '', range: '1 - 2000' },
|
||||
{ udtName: 'NCHAR', dataType: 'NCHAR', desc: '定长字符串,自动在末尾用空格补全,unicode', space: '', range: '1 - 1000' },
|
||||
{ udtName: 'VARCHAR2', dataType: 'VARCHAR2', desc: '变长字符串,不自动补全空格,非unicode', space: '', range: '1 - 4000' },
|
||||
{ udtName: 'NVARCHAR2', dataType: 'NVARCHAR2', desc: '变长字符串,不自动补全空格,unicode', space: '', range: '1 - 2000' },
|
||||
|
||||
// 精确数值数据类型 NUMERIC、DECIMAL、DEC 类型、NUMBER 类型、INTEGER 类型、INT 类型、BIGINT 类型、TINYINT 类型、BYTE 类型、SMALLINT
|
||||
{ udtName: 'NUMBER', dataType: 'NUMBER', desc: 'NUMBER(p,s)', space: '1-38', range: '' },
|
||||
{ udtName: 'INTEGER', dataType: 'INTEGER', desc: '同于number(38)', space: '', range: '' },
|
||||
{ udtName: 'INT', dataType: 'INT', desc: '同INTEGER', space: '10', range: '' },
|
||||
{ udtName: 'SMALLINT', dataType: 'SMALLINT', desc: '同于number(38)', space: '', range: '' },
|
||||
{ udtName: 'DECIMAL', dataType: 'DECIMAL', desc: 'decimal(p,s) 默认number(38)', space: '', range: '' },
|
||||
{ udtName: 'FLOAT', dataType: 'FLOAT', desc: 'float(b二进制进度),b的取值范围[1,126],默认126', space: '', range: '' },
|
||||
{ udtName: 'REAL', dataType: 'REAL', desc: '同FLOAT(63)', space: '', range: '' },
|
||||
{ udtName: 'BINARY_FLOAT', dataType: 'BINARY_FLOAT', desc: '32位单精度浮点数数据类型', space: '', range: '' },
|
||||
{ udtName: 'BINARY_DOUBLE', dataType: 'BINARY_DOUBLE', desc: '64位双精度浮点数数据类型', space: '', range: '' },
|
||||
|
||||
// 一般日期时间数据类型 DATE TIME TIMESTAMP 默认精度 6
|
||||
// 多媒体数据类型 TEXT/LONG/LONGVARCHAR 类型:变长字符串类型 IMAGE/LONGVARBINARY 类型 BLOB CLOB BFILE 100G-1
|
||||
{ udtName: 'DATE', dataType: 'DATE', desc: '世纪,年,月,日,时,分,秒', space: '', range: '' },
|
||||
{ udtName: 'TIMESTAMP', dataType: 'TIMESTAMP', desc: '', space: '', range: '' },
|
||||
// { udtName: 'timestamp(precision) with time zone', dataType: 'TIMESTAMP', desc: '在timestamp(precison)的基础上加入了时区偏移量的值', space: '', range: '' },
|
||||
// { udtName: 'timestamp with local time zone', dataType: 'TIMESTAMP', desc: '存储时转化为数据库时区进行规范化存储,但不存储时区信息,客户端检索时,按客户端时区的时间数据返回给客户端', space: '', range: '' },
|
||||
// { udtName: 'interval year(precision) to month', dataType: 'interval year(precision) to month', desc: '可以用来表示几年几月的时间间隔', space: '', range: '' },
|
||||
// { udtName: 'nterval day(days_precision) to second(seconds_precision)', dataType: 'nterval day(days_precision) to second(seconds_precision)', desc: '可以用来存储天、小时、分和秒的时间间隔', space: '', range: '' },
|
||||
|
||||
{ udtName: 'LONG', dataType: 'LONG', desc: '文本类型,不能作为主键或唯一约束', space: '', range: '最多达2GB' },
|
||||
{ udtName: 'LONG RAW', dataType: 'LONG RAW', desc: '可变长二进制数据,不用进行字符集转换的数据', space: '', range: '最多达2GB' },
|
||||
{ udtName: 'BLOB', dataType: 'BLOB', desc: '二进制大型对象', space: '', range: '最大长度4G' },
|
||||
{ udtName: 'CLOB', dataType: 'CLOB', desc: '字符大型对象', space: '', range: '最大长度4G' },
|
||||
{ udtName: 'NCLOB', dataType: 'NCLOB', desc: 'Unicode类型的数据', space: '', range: '最大长度4G' },
|
||||
{ udtName: 'BFILE', dataType: 'BFILE', desc: '二进制文件', space: '', range: '' },
|
||||
];
|
||||
|
||||
const replaceFunctions: EditorCompletionItem[] = [
|
||||
// 字符函数
|
||||
{ label: 'ASCII', insertText: 'ASCII(x)', description: '返回字符X的ASCII码' },
|
||||
{ label: 'CONCAT', insertText: 'CONCAT(x,y)', description: '连接字符串X和Y' },
|
||||
{ label: 'INSTR', insertText: 'INSTR(X,STR[,START][,N)', description: '从X中查找str,可以指定从start开始,也可以指定从n开始' },
|
||||
{ label: 'LENGTH', insertText: 'LENGTH(x)', description: '返回X的长度' },
|
||||
{ label: 'LOWER', insertText: 'LOWER(X)', description: 'X转换成小写' },
|
||||
{ label: 'UPPER', insertText: 'UPPER(X)', description: 'X转换成大写' },
|
||||
{ label: 'LTRIM', insertText: 'LTRIM(X[,TRIM_STR])', description: '把X的左边截去trim_str字符串,缺省截去空格' },
|
||||
{ label: 'RTRIM', insertText: 'RTRIM(X[,TRIM_STR])', description: '把X的右边截去trim_str字符串,缺省截去空格' },
|
||||
{ label: 'TRIM', insertText: 'TRIM(X[,TRIM_STR])', description: '把X的两边截去trim_str字符串,缺省截去空格' },
|
||||
{ label: 'REPLACE', insertText: 'REPLACE(X,old,new)', description: '在X中查找old,并替换成new' },
|
||||
{ label: 'SUBSTR', insertText: 'SUBSTR(X,start[,length])', description: '返回X的字串,从start处开始,截取length个字符,缺省length,默认到结尾' },
|
||||
// 数值函数
|
||||
{ label: 'ABS', insertText: 'ABS(X)', description: 'X的绝对值' },
|
||||
{ label: 'ACOS', insertText: 'ACOS(X)', description: 'X的反余弦' },
|
||||
{ label: 'COS', insertText: 'COS(X)', description: '余弦' },
|
||||
{ label: 'CEIL', insertText: 'CEIL(X)', description: '大于或等于X的最小值' },
|
||||
{ label: 'FLOOR', insertText: 'FLOOR(X)', description: '小于或等于X的最大值' },
|
||||
{ label: 'LOG', insertText: 'LOG(X,Y)', description: 'X为底Y的对数' },
|
||||
{ label: 'MOD', insertText: 'MOD(X,Y)', description: 'X除以Y的余数' },
|
||||
{ label: 'POWER', insertText: 'POWER(X,Y)', description: 'X的Y次幂' },
|
||||
{ label: 'ROUND', insertText: 'ROUND(X [,Y]})', description: 'X在第Y位四舍五入' },
|
||||
{ label: 'SQRT', insertText: 'SQRT(n)', description: '求数值 n 的平方根' },
|
||||
{ label: 'TRUNC', insertText: 'TRUNC(n [,m])', description: "截取数值函数,str 内只能为数字和'-', '+', '.' 的组合" },
|
||||
//日期时间函数
|
||||
{ label: 'ADD_MONTHS', insertText: 'ADD_MONTHS(date,n)', description: '在输入日期上加上指定的几个月返回一个新日期' },
|
||||
{ label: 'LAST_DAY', insertText: 'LAST_DAY(date)', description: '返回输入日期所在月份最后一天的日期' },
|
||||
{ label: 'EXTRACT', insertText: 'EXTRACT(fmt FROM d)', description: '提取日期中的特定部分' },
|
||||
{ label: 'CURRENT_DATE', insertText: 'CURRENT_DATE', description: '获取当前日期' },
|
||||
{ label: 'CURRENT_TIMESTAMP', insertText: 'TIMESTAMP', description: '获取当前时间' },
|
||||
// 转换函数
|
||||
{ label: 'TO_CHAR', insertText: 'TO_CHAR(d|n[,fmt])', description: '把日期和数字转换为制定格式的字符串' },
|
||||
{ label: 'TO_DATE', insertText: 'TO_DATE(X,[,fmt])', description: '把一个字符串以fmt格式转换成一个日期类型' },
|
||||
{ label: 'TO_NUMBER', insertText: 'TO_NUMBER(X,[,fmt])', description: '把一个字符串以fmt格式转换为一个数字' },
|
||||
{ label: 'TO_TIMESTAMP', insertText: 'TO_TIMESTAMP(X,[,fmt])', description: '把一个字符串以fmt格式转换为日期类型' },
|
||||
// 其他
|
||||
{ label: 'NVL', insertText: 'NVL(X,VALUE)', description: '如果X为空,返回value,否则返回X' },
|
||||
{ label: 'NVL2', insertText: 'NVL2(x,value1,value2)', description: '如果x非空,返回value1,否则返回value2' },
|
||||
];
|
||||
|
||||
const addCustomKeywords = ['ROWNUM', 'DUAL'];
|
||||
|
||||
let oracleDialectInfo: DialectInfo;
|
||||
class OracleDialect implements DbDialect {
|
||||
getInfo(): DialectInfo {
|
||||
if (oracleDialectInfo) {
|
||||
return oracleDialectInfo;
|
||||
}
|
||||
|
||||
let { keywords, operators, builtinVariables } = sqlLanguage;
|
||||
let functionNames = replaceFunctions.map((a) => a.label);
|
||||
let excludeKeywords = new Set(functionNames.concat(operators));
|
||||
|
||||
let editorCompletions: EditorCompletion = {
|
||||
keywords: keywords
|
||||
.filter((a: string) => !excludeKeywords.has(a)) // 移除已存在的operator、function
|
||||
.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: replaceFunctions,
|
||||
variables: builtinVariables.map((a: string): EditorCompletionItem => ({ label: a, description: 'var' })),
|
||||
};
|
||||
|
||||
oracleDialectInfo = {
|
||||
icon: 'iconfont icon-oracle',
|
||||
defaultPort: 1521,
|
||||
formatSqlDialect: 'plsql',
|
||||
columnTypes: ORACLE_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
|
||||
editorCompletions,
|
||||
};
|
||||
return oracleDialectInfo;
|
||||
}
|
||||
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
return `
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT t.*, ROWNUM AS rn
|
||||
FROM "${table}" t
|
||||
WHERE ROWNUM <=${pageNum * limit} ${condition ? ' and ' + condition : ''}
|
||||
${orderBy ? orderBy : ''}
|
||||
)
|
||||
WHERE rn > ${(pageNum - 1) * limit}
|
||||
`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
getPageSql(pageNum: number, limit: number) {
|
||||
return ``;
|
||||
}
|
||||
|
||||
getDefaultRows(): RowDefinition[] {
|
||||
return [
|
||||
{ name: 'ID', type: 'NUMBER', length: '', numScale: '', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
|
||||
{ name: 'CREATOR_ID', type: 'NUMBER', length: '', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
|
||||
{
|
||||
name: 'CREATOR',
|
||||
type: 'VARCHAR2',
|
||||
length: '100',
|
||||
numScale: '',
|
||||
value: '',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '创建人姓名',
|
||||
},
|
||||
{
|
||||
name: 'CREATE_TIME',
|
||||
type: 'DATE',
|
||||
length: '',
|
||||
numScale: '',
|
||||
value: 'CURRENT_TIMESTAMP',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '创建时间',
|
||||
},
|
||||
{ name: 'UPDATOR_ID', type: 'NUMBER', length: '', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
|
||||
{
|
||||
name: 'UPDATOR',
|
||||
type: 'VARCHAR2',
|
||||
length: '100',
|
||||
numScale: '',
|
||||
value: '',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '修改人姓名',
|
||||
},
|
||||
{
|
||||
name: 'UPDATE_TIME',
|
||||
type: 'DATE',
|
||||
length: '',
|
||||
numScale: '',
|
||||
value: 'CURRENT_TIMESTAMP',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '修改时间',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getDefaultIndex(): IndexDefinition {
|
||||
return {
|
||||
indexName: '',
|
||||
columnNames: [],
|
||||
unique: false,
|
||||
indexType: 'NORMAL',
|
||||
indexComment: '',
|
||||
};
|
||||
}
|
||||
|
||||
quoteIdentifier = (name: string) => {
|
||||
return `"${name}"`;
|
||||
};
|
||||
|
||||
matchType(text: string, arr: string[]): boolean {
|
||||
if (!text || !arr || arr.length === 0) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (text.indexOf(arr[i]) > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getDefaultValueSql(cl: any): string {
|
||||
if (cl.value && cl.value.length > 0) {
|
||||
// 哪些字段默认值需要加引号
|
||||
let marks = false;
|
||||
if (this.matchType(cl.type, ['CHAR', 'TIME', 'DATE', 'LONG', 'CLOB', 'BLOB', 'BFILE'])) {
|
||||
// 默认值是时间日期函数的必须要加引号
|
||||
let val = cl.value.toUpperCase().replace(' ', '');
|
||||
if (this.matchType(cl.type, ['DATE', 'TIMESTAMP']) && ['CURRENT_DATE', 'CURRENT_TIMESTAMP'].includes(val)) {
|
||||
marks = false;
|
||||
} else {
|
||||
marks = true;
|
||||
}
|
||||
}
|
||||
return ` DEFAULT ${marks ? "'" : ''}${cl.value}${marks ? "'" : ''}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
getTypeLengthSql(cl: any) {
|
||||
// 哪些字段可以指定长度 VARCHAR/VARCHAR2/CHAR/BIT/NUMBER/NUMERIC/TIME、TIMESTAMP(可以指定小数秒精度)
|
||||
if (cl.length && this.matchType(cl.type, ['CHAR', 'BIT', 'TIME', 'NUM', 'DEC'])) {
|
||||
// 哪些字段类型可以指定小数点
|
||||
if (cl.numScale && this.matchType(cl.type, ['NUM', 'DEC'])) {
|
||||
return `(${cl.length}, ${cl.numScale})`;
|
||||
} else {
|
||||
return `(${cl.length})`;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
genColumnBasicSql(cl: RowDefinition): 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' : ''} `;
|
||||
}
|
||||
|
||||
getCreateTableSql(data: any): string {
|
||||
let createSql = '';
|
||||
let tableCommentSql = '';
|
||||
let columCommentSql = '';
|
||||
|
||||
// 创建表结构
|
||||
let fields: string[] = [];
|
||||
data.fields.res.forEach((item: any) => {
|
||||
item.name && fields.push(this.genColumnBasicSql(item));
|
||||
// 列注释
|
||||
if (item.remark) {
|
||||
columCommentSql += ` comment on column ${data.tableName?.toUpperCase()}.${item.name?.toUpperCase()} is '${item.remark}'; `;
|
||||
}
|
||||
});
|
||||
// 建表
|
||||
createSql = `CREATE TABLE ${data.tableName?.toUpperCase()} ( ${fields.join(',')} );`;
|
||||
// 表注释
|
||||
if (data.tableComment) {
|
||||
tableCommentSql = ` comment on table ${data.tableName?.toUpperCase()} is '${data.tableComment}'; `;
|
||||
}
|
||||
|
||||
return createSql + tableCommentSql + columCommentSql;
|
||||
}
|
||||
|
||||
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 sql: string[] = [];
|
||||
tableData.indexs.res.forEach((a: any) => {
|
||||
sql.push(` CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName} ON "${tableData.tableName}" ("${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}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (changeData.del.length > 0) {
|
||||
changeData.del.forEach((a) => {
|
||||
sql.push(`ALTER TABLE "${tableName}" DROP COLUMN ${a.name}`);
|
||||
});
|
||||
}
|
||||
return sql.join(';');
|
||||
}
|
||||
|
||||
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
// 不能直接修改索引名或字段、需要先删后加
|
||||
let dropIndexNames: string[] = [];
|
||||
let addIndexs: any[] = [];
|
||||
|
||||
if (changeData.upd.length > 0) {
|
||||
changeData.upd.forEach((a) => {
|
||||
dropIndexNames.push(a.indexName);
|
||||
addIndexs.push(a);
|
||||
});
|
||||
}
|
||||
|
||||
if (changeData.del.length > 0) {
|
||||
changeData.del.forEach((a) => {
|
||||
dropIndexNames.push(a.indexName);
|
||||
});
|
||||
}
|
||||
|
||||
if (changeData.add.length > 0) {
|
||||
changeData.add.forEach((a) => {
|
||||
addIndexs.push(a);
|
||||
});
|
||||
}
|
||||
|
||||
if (dropIndexNames.length > 0 || addIndexs.length > 0) {
|
||||
let sql: string[] = [];
|
||||
if (dropIndexNames.length > 0) {
|
||||
dropIndexNames.forEach((a) => {
|
||||
sql.push(`DROP INDEX ${a}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (addIndexs.length > 0) {
|
||||
addIndexs.forEach((a) => {
|
||||
sql.push(`CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName} ON "${tableName}" (${a.columnNames.join(',')})`);
|
||||
});
|
||||
}
|
||||
return sql.join(';');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
getDataType(columnType: string): DataType {
|
||||
if (DbInst.isNumber(columnType)) {
|
||||
return DataType.Number;
|
||||
}
|
||||
// 日期时间类型 oracle只有date和timestamp类型
|
||||
if (/timestamp|date/gi.test(columnType)) {
|
||||
return DataType.DateTime;
|
||||
}
|
||||
return DataType.String;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
wrapStrValue(columnType: string, value: string): string {
|
||||
if (value && this.getDataType(columnType) === DataType.DateTime) {
|
||||
return `to_timestamp('${value}', 'yyyy-mm-dd hh24:mi:ss')`;
|
||||
}
|
||||
return `'${value}'`;
|
||||
}
|
||||
}
|
||||
@@ -407,4 +407,9 @@ class PostgresqlDialect implements DbDialect {
|
||||
}
|
||||
return DataType.String;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
wrapStrValue(value: string, type: string): string {
|
||||
return `'${value}'`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ require (
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/robfig/cron/v3 v3.0.1 // 定时任务
|
||||
github.com/sijms/go-ora/v2 v2.8.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.mongodb.org/mongo-driver v1.13.1 // mongo
|
||||
golang.org/x/crypto v0.18.0 // ssh
|
||||
|
||||
@@ -349,7 +349,11 @@ func (d *Db) dumpDb(writer *gzipWriter, dbId uint64, dbName string, tables []str
|
||||
continue
|
||||
}
|
||||
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表记录: %s \n-- ----------------------------\n", table))
|
||||
writer.WriteString("BEGIN;\n")
|
||||
|
||||
// 达梦不支持begin语句
|
||||
if dbConn.Info.Type != dbi.DbTypeDM {
|
||||
writer.WriteString("BEGIN;\n")
|
||||
}
|
||||
insertSql := "INSERT INTO %s VALUES (%s);\n"
|
||||
dbMeta.WalkTableRecord(table, func(record map[string]any, columns []*dbi.QueryColumn) error {
|
||||
var values []string
|
||||
|
||||
@@ -6,6 +6,7 @@ type InstanceForm struct {
|
||||
Type string `binding:"required" json:"type"` // 类型,mysql oracle等
|
||||
Host string `binding:"required" json:"host"`
|
||||
Port int `binding:"required" json:"port"`
|
||||
Sid string `json:"sid"`
|
||||
Username string `binding:"required" json:"username"`
|
||||
Password string `json:"password"`
|
||||
Params string `json:"params"`
|
||||
|
||||
@@ -9,6 +9,7 @@ type InstanceListVO struct {
|
||||
Port *int `json:"port"`
|
||||
Type *string `json:"type"`
|
||||
Params *string `json:"params"`
|
||||
Sid *string `json:"sid"`
|
||||
Username *string `json:"username"`
|
||||
Remark *string `json:"remark"`
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
|
||||
@@ -155,7 +155,7 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
|
||||
|
||||
checkDb := dbName
|
||||
// 兼容pgsql/dm db/schema模式
|
||||
if dbi.DbTypePostgres.Equal(instance.Type) || dbi.DbTypeDM.Equal(instance.Type) {
|
||||
if dbi.DbTypePostgres.Equal(instance.Type) || dbi.DbTypeDM.Equal(instance.Type) || dbi.DbTypeOracle.Equal(instance.Type) {
|
||||
ss := strings.Split(dbName, "/")
|
||||
if len(ss) > 1 {
|
||||
checkDb = ss[0]
|
||||
@@ -195,6 +195,7 @@ func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbi.DbConn, error
|
||||
func toDbInfo(instance *entity.DbInstance, dbId uint64, database string, tagPath ...string) *dbi.DbInfo {
|
||||
di := new(dbi.DbInfo)
|
||||
di.InstanceId = instance.Id
|
||||
di.Sid = instance.Sid
|
||||
di.Id = dbId
|
||||
di.Database = database
|
||||
di.TagPath = tagPath
|
||||
|
||||
@@ -206,6 +206,9 @@ func valueConvert(data []byte, colType *sql.ColumnType) any {
|
||||
if stringV == "" {
|
||||
return ""
|
||||
}
|
||||
if colType == nil || colType.ScanType() == nil {
|
||||
return stringV
|
||||
}
|
||||
colScanType := strings.ToLower(colType.ScanType().Name())
|
||||
|
||||
if strings.Contains(colScanType, "int") {
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
DbTypeMariadb DbType = "mariadb"
|
||||
DbTypePostgres DbType = "postgres"
|
||||
DbTypeDM DbType = "dm"
|
||||
DbTypeOracle DbType = "oracle"
|
||||
)
|
||||
|
||||
func ToDbType(dbType string) DbType {
|
||||
|
||||
@@ -16,6 +16,7 @@ type DbInfo struct {
|
||||
Type DbType // 类型,mysql postgres等
|
||||
Host string
|
||||
Port int
|
||||
Sid string // oracle数据库需要指定sid
|
||||
Network string
|
||||
Username string
|
||||
Password string
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
select
|
||||
distinct owner as SCHEMA_NAME
|
||||
from all_objects
|
||||
order by owner
|
||||
---------------------------------------
|
||||
--DM_TABLE_INFO 表详细信息
|
||||
SELECT a.object_name as TABLE_NAME,
|
||||
@@ -29,6 +30,7 @@ FROM all_objects a
|
||||
WHERE a.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||
AND a.object_type = 'TABLE'
|
||||
AND a.status = 'VALID'
|
||||
ORDER BY a.object_name
|
||||
---------------------------------------
|
||||
--DM_INDEX_INFO 表索引信息
|
||||
select
|
||||
|
||||
@@ -5,6 +5,7 @@ FROM
|
||||
information_schema.SCHEMATA
|
||||
WHERE
|
||||
SCHEMA_NAME NOT IN ('mysql', 'information_schema', 'performance_schema')
|
||||
ORDER BY SCHEMA_NAME
|
||||
---------------------------------------
|
||||
--MYSQL_TABLE_INFO 表详细信息
|
||||
SELECT
|
||||
@@ -22,6 +23,7 @@ WHERE
|
||||
SELECT
|
||||
database ()
|
||||
)
|
||||
ORDER BY table_name
|
||||
---------------------------------------
|
||||
--MYSQL_INDEX_INFO 索引信息
|
||||
SELECT
|
||||
|
||||
70
server/internal/db/dbm/dbi/metasql/oracle_meta.sql
Normal file
70
server/internal/db/dbm/dbi/metasql/oracle_meta.sql
Normal file
@@ -0,0 +1,70 @@
|
||||
--ORACLE_DB_SCHEMAS 库schemas
|
||||
select distinct owner as SCHEMA_NAME
|
||||
from all_objects
|
||||
order by owner
|
||||
---------------------------------------
|
||||
--ORACLE_TABLE_INFO 表详细信息
|
||||
select a.TABLE_NAME,
|
||||
b.COMMENTS as TABLE_COMMENT,
|
||||
c.CREATED as CREATE_TIME,
|
||||
d.BYTES as DATA_LENGTH,
|
||||
0 as INDEX_LENGTH,
|
||||
a.NUM_ROWS as TABLE_ROWS
|
||||
from all_tables a
|
||||
left join ALL_TAB_COMMENTS b on b.TABLE_NAME = a.TABLE_NAME AND b.OWNER = a.OWNER
|
||||
left join all_objects c on c.OBJECT_TYPE = 'TABLE' AND c.OWNER = a.OWNER AND c.OBJECT_NAME = a.TABLE_NAME
|
||||
left join dba_segments d on d.SEGMENT_TYPE = 'TABLE' AND d.OWNER = a.OWNER AND d.SEGMENT_NAME = a.TABLE_NAME
|
||||
where a.owner = (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual)
|
||||
ORDER BY a.TABLE_NAME
|
||||
---------------------------------------
|
||||
--ORACLE_INDEX_INFO 表索引信息
|
||||
SELECT ai.INDEX_NAME AS INDEX_NAME,
|
||||
ai.INDEX_TYPE AS INDEX_TYPE,
|
||||
CASE
|
||||
WHEN ai.uniqueness = 'UNIQUE' THEN 'NO'
|
||||
ELSE 'YES'
|
||||
END AS NON_UNIQUE,
|
||||
(SELECT LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_position)
|
||||
FROM ALL_IND_COLUMNS aic
|
||||
WHERE aic.INDEX_NAME = ai.INDEX_NAME
|
||||
AND aic.TABLE_NAME = ai.TABLE_NAME) AS COLUMN_NAME,
|
||||
1 AS SEQ_IN_INDEX,
|
||||
(SELECT comments
|
||||
FROM ALL_IND_COLUMNS aic
|
||||
LEFT JOIN ALL_COL_COMMENTS acc ON aic.column_name = acc.column_name
|
||||
WHERE aic.INDEX_OWNER = ai.OWNER
|
||||
AND aic.INDEX_NAME = ai.INDEX_NAME
|
||||
AND aic.TABLE_NAME = ai.TABLE_NAME
|
||||
AND ROWNUM = 1) AS INDEX_COMMENT
|
||||
FROM ALL_INDEXES ai
|
||||
WHERE ai.OWNER = (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM DUAL)
|
||||
AND ai.table_name = '%s'
|
||||
---------------------------------------
|
||||
--ORACLE_COLUMN_MA 表列信息
|
||||
SELECT a.TABLE_NAME as TABLE_NAME,
|
||||
a.COLUMN_NAME as COLUMN_NAME,
|
||||
case
|
||||
when a.NULLABLE = 'Y' then 'YES'
|
||||
when a.NULLABLE = 'N' then 'NO'
|
||||
else 'NO' end as NULLABLE,
|
||||
case
|
||||
when a.DATA_PRECISION > 0 then a.DATA_TYPE
|
||||
else (a.DATA_TYPE || '(' || a.DATA_LENGTH || ')') end as COLUMN_TYPE,
|
||||
b.COMMENTS as COLUMN_COMMENT,
|
||||
a.DATA_DEFAULT as COLUMN_DEFAULT,
|
||||
a.DATA_SCALE as NUM_SCALE,
|
||||
CASE
|
||||
WHEN d.pri IS NOT NULL THEN 'PRI'
|
||||
END as COLUMN_KEY
|
||||
FROM all_tab_columns a
|
||||
LEFT JOIN all_col_comments b
|
||||
on a.OWNER = b.OWNER AND a.TABLE_NAME = b.TABLE_NAME AND a.COLUMN_NAME = b.COLUMN_NAME
|
||||
LEFT JOIN (select ac.TABLE_NAME, ac.OWNER, cc.COLUMN_NAME, 1 as pri
|
||||
from all_constraints ac
|
||||
join all_cons_columns cc on cc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME AND cc.OWNER = ac.OWNER
|
||||
where cc.CONSTRAINT_NAME IS NOT NULL
|
||||
AND ac.CONSTRAINT_TYPE = 'P') d
|
||||
on d.OWNER = a.OWNER AND d.TABLE_NAME = a.TABLE_NAME AND d.COLUMN_NAME = a.COLUMN_NAME
|
||||
WHERE a.OWNER = (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM DUAL)
|
||||
AND a.TABLE_NAME in (%s)
|
||||
order by a.COLUMN_ID
|
||||
@@ -9,6 +9,8 @@ where
|
||||
and n.nspname not like 'dbms_%'
|
||||
and n.nspname not like 'utl_%'
|
||||
and n.nspname != 'information_schema'
|
||||
order by
|
||||
n.nspname
|
||||
---------------------------------------
|
||||
--PGSQL_TABLE_INFO 表详细信息
|
||||
select
|
||||
@@ -27,6 +29,7 @@ where
|
||||
has_table_privilege(CAST(c.oid AS regclass), 'SELECT')
|
||||
and n.nspname = current_schema()
|
||||
and c.reltype > 0
|
||||
order by c.relname
|
||||
---------------------------------------
|
||||
--PGSQL_INDEX_INFO 表索引信息
|
||||
SELECT
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/dbm/dm"
|
||||
"mayfly-go/internal/db/dbm/mysql"
|
||||
"mayfly-go/internal/db/dbm/oracle"
|
||||
"mayfly-go/internal/db/dbm/postgres"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
"mayfly-go/pkg/cache"
|
||||
@@ -45,6 +46,8 @@ func getDbMetaByType(dt dbi.DbType) dbi.Meta {
|
||||
return postgres.GetMeta()
|
||||
case dbi.DbTypeDM:
|
||||
return dm.GetMeta()
|
||||
case dbi.DbTypeOracle:
|
||||
return oracle.GetMeta()
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid database type: %s", dt))
|
||||
}
|
||||
|
||||
344
server/internal/db/dbm/oracle/dialect.go
Normal file
344
server/internal/db/dbm/oracle/dialect.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "gitee.com/chunanyong/dm"
|
||||
)
|
||||
|
||||
// ---------------------------------- DM元数据 -----------------------------------
|
||||
const (
|
||||
ORACLE_META_FILE = "metasql/oracle_meta.sql"
|
||||
ORACLE_DB_SCHEMAS = "ORACLE_DB_SCHEMAS"
|
||||
ORACLE_TABLE_INFO_KEY = "ORACLE_TABLE_INFO"
|
||||
ORACLE_INDEX_INFO_KEY = "ORACLE_INDEX_INFO"
|
||||
ORACLE_COLUMN_MA_KEY = "ORACLE_COLUMN_MA"
|
||||
)
|
||||
|
||||
type OracleDialect struct {
|
||||
dc *dbi.DbConn
|
||||
}
|
||||
|
||||
func (od *OracleDialect) GetDbServer() (*dbi.DbServer, error) {
|
||||
_, res, err := od.dc.Query("select * from v$instance")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ds := &dbi.DbServer{
|
||||
Version: anyx.ConvString(res[0]["VERSION"]),
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
func (od *OracleDialect) GetDbNames() ([]string, error) {
|
||||
_, res, err := od.dc.Query("SELECT name AS DBNAME FROM v$database")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
databases := make([]string, 0)
|
||||
for _, re := range res {
|
||||
databases = append(databases, anyx.ConvString(re["DBNAME"]))
|
||||
}
|
||||
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (od *OracleDialect) GetTables() ([]dbi.Table, error) {
|
||||
|
||||
// 首先执行更新统计信息sql 这个统计信息在数据量比较大的时候就比较耗时,所以最好定时执行
|
||||
// _, _, err := pd.dc.Query("dbms_stats.GATHER_SCHEMA_stats(SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))")
|
||||
|
||||
// 查询表信息
|
||||
_, res, err := od.dc.Query(dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_TABLE_INFO_KEY))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]dbi.Table, 0)
|
||||
for _, re := range res {
|
||||
tables = append(tables, dbi.Table{
|
||||
TableName: re["TABLE_NAME"].(string),
|
||||
TableComment: anyx.ConvString(re["TABLE_COMMENT"]),
|
||||
CreateTime: anyx.ConvString(re["CREATE_TIME"]),
|
||||
TableRows: anyx.ConvInt(re["TABLE_ROWS"]),
|
||||
DataLength: anyx.ConvInt64(re["DATA_LENGTH"]),
|
||||
IndexLength: anyx.ConvInt64(re["INDEX_LENGTH"]),
|
||||
})
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
// 获取列元信息, 如列名等
|
||||
func (od *OracleDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||
tableName := ""
|
||||
for i := 0; i < len(tableNames); i++ {
|
||||
if i != 0 {
|
||||
tableName = tableName + ", "
|
||||
}
|
||||
tableName = tableName + "'" + tableNames[i] + "'"
|
||||
}
|
||||
|
||||
_, res, err := od.dc.Query(fmt.Sprintf(dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_COLUMN_MA_KEY), tableName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := make([]dbi.Column, 0)
|
||||
for _, re := range res {
|
||||
defaultVal := anyx.ConvString(re["COLUMN_DEFAULT"])
|
||||
// 如果默认值包含.nextval,说明是序列,默认值为null
|
||||
if strings.Contains(defaultVal, ".nextval") {
|
||||
defaultVal = ""
|
||||
}
|
||||
columns = append(columns, dbi.Column{
|
||||
TableName: re["TABLE_NAME"].(string),
|
||||
ColumnName: re["COLUMN_NAME"].(string),
|
||||
ColumnType: anyx.ConvString(re["COLUMN_TYPE"]),
|
||||
ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]),
|
||||
Nullable: anyx.ConvString(re["NULLABLE"]),
|
||||
ColumnKey: anyx.ConvString(re["COLUMN_KEY"]),
|
||||
ColumnDefault: defaultVal,
|
||||
NumScale: anyx.ConvString(re["NUM_SCALE"]),
|
||||
})
|
||||
}
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
func (od *OracleDialect) GetPrimaryKey(tablename string) (string, error) {
|
||||
columns, err := od.GetColumns(tablename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||
}
|
||||
for _, v := range columns {
|
||||
if v.ColumnKey == "PRI" {
|
||||
return v.ColumnName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return columns[0].ColumnName, nil
|
||||
}
|
||||
|
||||
// 获取表索引信息
|
||||
func (od *OracleDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
||||
_, res, err := od.dc.Query(fmt.Sprintf(dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_INDEX_INFO_KEY), tableName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexs := make([]dbi.Index, 0)
|
||||
for _, re := range res {
|
||||
indexs = append(indexs, dbi.Index{
|
||||
IndexName: re["INDEX_NAME"].(string),
|
||||
ColumnName: anyx.ConvString(re["COLUMN_NAME"]),
|
||||
IndexType: anyx.ConvString(re["INDEX_TYPE"]),
|
||||
IndexComment: anyx.ConvString(re["INDEX_COMMENT"]),
|
||||
NonUnique: anyx.ConvInt(re["NON_UNIQUE"]),
|
||||
SeqInIndex: anyx.ConvInt(re["SEQ_IN_INDEX"]),
|
||||
})
|
||||
}
|
||||
// 把查询结果以索引名分组,索引字段以逗号连接
|
||||
result := make([]dbi.Index, 0)
|
||||
key := ""
|
||||
for _, v := range indexs {
|
||||
// 当前的索引名
|
||||
in := v.IndexName
|
||||
if key == in {
|
||||
// 索引字段已根据名称和顺序排序,故取最后一个即可
|
||||
i := len(result) - 1
|
||||
// 同索引字段以逗号连接
|
||||
result[i].ColumnName = result[i].ColumnName + "," + v.ColumnName
|
||||
} else {
|
||||
key = in
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 获取建表ddl
|
||||
func (od *OracleDialect) GetTableDDL(tableName string) (string, error) {
|
||||
ddlSql := fmt.Sprintf("SELECT DBMS_METADATA.GET_DDL('TABLE', '%s', (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual)) AS TABLE_DDL FROM DUAL", tableName)
|
||||
_, res, err := od.dc.Query(ddlSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 建表ddl
|
||||
var builder strings.Builder
|
||||
for _, re := range res {
|
||||
builder.WriteString(re["TABLE_DDL"].(string))
|
||||
}
|
||||
|
||||
// 表注释
|
||||
_, res, err = od.dc.Query(fmt.Sprintf(`
|
||||
select OWNER, COMMENTS from ALL_TAB_COMMENTS where TABLE_TYPE='TABLE' and TABLE_NAME = '%s'
|
||||
and owner = (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual) `, tableName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, re := range res {
|
||||
// COMMENT ON TABLE "SYS_MENU" IS '菜单表';
|
||||
if re["COMMENTS"] != nil {
|
||||
tableComment := fmt.Sprintf("\n\nCOMMENT ON TABLE \"%s\".\"%s\" IS '%s';", re["OWNER"].(string), tableName, re["COMMENTS"].(string))
|
||||
builder.WriteString(tableComment)
|
||||
}
|
||||
}
|
||||
|
||||
// 字段注释
|
||||
fieldSql := fmt.Sprintf(`
|
||||
SELECT OWNER, COLUMN_NAME, COMMENTS
|
||||
FROM ALL_COL_COMMENTS
|
||||
WHERE OWNER = (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual)
|
||||
AND TABLE_NAME = '%s'
|
||||
`, tableName)
|
||||
_, res, err = od.dc.Query(fieldSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
for _, re := range res {
|
||||
// COMMENT ON COLUMN "SYS_MENU"."BIZ_CODE" IS '业务编码,应用编码1';
|
||||
if re["COMMENTS"] != nil {
|
||||
fieldComment := fmt.Sprintf("\nCOMMENT ON COLUMN \"%s\".\"%s\".\"%s\" IS '%s';", re["OWNER"].(string), tableName, re["COLUMN_NAME"].(string), re["COMMENTS"].(string))
|
||||
builder.WriteString(fieldComment)
|
||||
}
|
||||
}
|
||||
|
||||
// 索引信息
|
||||
indexSql := fmt.Sprintf(`
|
||||
select DBMS_METADATA.GET_DDL('INDEX', a.INDEX_NAME, a.OWNER) AS INDEX_DEF from ALL_INDEXES a
|
||||
join ALL_objects b on a.owner = b.owner and b.object_name = a.index_name and b.object_type = 'INDEX'
|
||||
where a.owner = (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual)
|
||||
and a.table_name = '%s'
|
||||
`, tableName)
|
||||
_, res, err = od.dc.Query(indexSql)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, re := range res {
|
||||
builder.WriteString("\n\n" + re["INDEX_DEF"].(string))
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func (od *OracleDialect) WalkTableRecord(tableName string, walkFn dbi.WalkQueryRowsFunc) error {
|
||||
return od.dc.WalkQueryRows(context.Background(), fmt.Sprintf("SELECT * FROM %s", tableName), walkFn)
|
||||
}
|
||||
|
||||
// 获取DM当前连接的库可访问的schemaNames
|
||||
func (od *OracleDialect) GetSchemas() ([]string, error) {
|
||||
sql := dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_DB_SCHEMAS)
|
||||
_, res, err := od.dc.Query(sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemaNames := make([]string, 0)
|
||||
for _, re := range res {
|
||||
schemaNames = append(schemaNames, anyx.ConvString(re["SCHEMA_NAME"]))
|
||||
}
|
||||
return schemaNames, nil
|
||||
}
|
||||
|
||||
// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
|
||||
func (od *OracleDialect) GetDbProgram() dbi.DbProgram {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (od *OracleDialect) GetDataType(dbColumnType string) dbi.DataType {
|
||||
if regexp.MustCompile(`(?i)int|double|float|number|decimal|byte|bit`).MatchString(dbColumnType) {
|
||||
return dbi.DataTypeNumber
|
||||
}
|
||||
// 日期时间类型
|
||||
if regexp.MustCompile(`(?i)datetime|timestamp`).MatchString(dbColumnType) {
|
||||
return dbi.DataTypeDateTime
|
||||
}
|
||||
// 日期类型
|
||||
if regexp.MustCompile(`(?i)date`).MatchString(dbColumnType) {
|
||||
return dbi.DataTypeDate
|
||||
}
|
||||
// 时间类型
|
||||
if regexp.MustCompile(`(?i)time`).MatchString(dbColumnType) {
|
||||
return dbi.DataTypeTime
|
||||
}
|
||||
return dbi.DataTypeString
|
||||
}
|
||||
|
||||
func (od *OracleDialect) BatchInsert(tx *sql.Tx, tableName string, columns []string, values [][]any) (int64, error) {
|
||||
//INSERT ALL
|
||||
//INTO my_table(field_1,field_2) VALUES (value_1,value_2)
|
||||
//INTO my_table(field_1,field_2) VALUES (value_3,value_4)
|
||||
//INTO my_table(field_1,field_2) VALUES (value_5,value_6)
|
||||
//SELECT 1 FROM DUAL;
|
||||
|
||||
if len(values) <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// 把二维数组转为一维数组
|
||||
var args []any
|
||||
for _, v := range values {
|
||||
args = append(args, v...)
|
||||
}
|
||||
|
||||
// 拼接oracle批量插入语句
|
||||
sqlArr := make([]string, 0)
|
||||
sqlArr = append(sqlArr, "INSERT ALL")
|
||||
|
||||
// 拼接带占位符的sql oracle的占位符是:1,:2,:3....
|
||||
for i := 0; i < len(args); i += len(columns) {
|
||||
var placeholder []string
|
||||
for j := 0; j < len(columns); j++ {
|
||||
// 判断字符串数据格式是时间"2023-06-25 10:40:10" 占位符需要变成 to_date(:x, 'fmt')
|
||||
if reflect.TypeOf(args[i+j]) == reflect.TypeOf("") {
|
||||
if regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`).MatchString(args[i+j].(string)) {
|
||||
placeholder = append(placeholder, fmt.Sprintf("to_date(:%d, 'yyyy-mm-dd hh24:mi:ss')", i+j+1))
|
||||
} else if regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`).MatchString(args[i+j].(string)) {
|
||||
// 只有年月日的数据,oracle会自动补零时分秒,如:2024-01-02: to_date('2024-01-02','yyyy-mm-dd') 输出:2024-01-02 00:00:00
|
||||
placeholder = append(placeholder, fmt.Sprintf("to_date(:%d, 'yyyy-mm-dd')", i+j+1))
|
||||
} else if regexp.MustCompile(`^\d{2}:\d{2}:\d{2}$`).MatchString(args[i+j].(string)) {
|
||||
// 只有时间的数据,oracle会拼接当前月份的年月日,如当前月份是2024-01: to_date('13:23:11','hh24:mi:ss') 输出:2024-01-01 13:23:11
|
||||
placeholder = append(placeholder, fmt.Sprintf("to_date(:%d, 'hh24:mi:ss')", i+j+1))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
placeholder = append(placeholder, fmt.Sprintf(":%d", i+j+1))
|
||||
}
|
||||
sqlArr = append(sqlArr, fmt.Sprintf("INTO %s (%s) VALUES (%s)", od.dc.Info.Type.QuoteIdentifier(tableName), strings.Join(columns, ","), strings.Join(placeholder, ",")))
|
||||
}
|
||||
sqlArr = append(sqlArr, "SELECT 1 FROM DUAL")
|
||||
|
||||
// 执行批量insert sql
|
||||
res, err := od.dc.TxExec(tx, strings.Join(sqlArr, " "), args...)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (od *OracleDialect) 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
|
||||
}
|
||||
67
server/internal/db/dbm/oracle/meta.go
Normal file
67
server/internal/db/dbm/oracle/meta.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
go_ora "github.com/sijms/go-ora/v2"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
meta dbi.Meta
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func GetMeta() dbi.Meta {
|
||||
once.Do(func() {
|
||||
meta = new(OraMeta)
|
||||
})
|
||||
return meta
|
||||
}
|
||||
|
||||
type OraMeta struct {
|
||||
}
|
||||
|
||||
func (md *OraMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
|
||||
driverName := "oracle"
|
||||
|
||||
err := d.IfUseSshTunnelChangeIpPort()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 参数参考 https://github.com/sijms/go-ora?tab=readme-ov-file#other-connection-options
|
||||
urlOptions := make(map[string]string)
|
||||
|
||||
db := d.Database
|
||||
schema := ""
|
||||
if db != "" {
|
||||
// oracle database可以使用db/schema表示,方便连接指定schema, 若不存在schema则使用默认schema
|
||||
ss := strings.Split(db, "/")
|
||||
if len(ss) > 1 {
|
||||
// user=hr&defaultSchema=hr
|
||||
schema = ss[1]
|
||||
}
|
||||
}
|
||||
|
||||
urlOptions["TIMEOUT"] = "60"
|
||||
urlOptions["client charset"] = "UTF8"
|
||||
connStr := go_ora.BuildUrl(d.Host, d.Port, d.Sid, d.Username, d.Password, urlOptions)
|
||||
conn, err := sql.Open(driverName, connStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 目前没找到如何连接的时候就获取schema的方法,只能连接后再设置
|
||||
if schema != "" {
|
||||
_, err := conn.Exec(fmt.Sprintf("ALTER SESSION SET CURRENT_SCHEMA=%s", schema))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (md *OraMeta) GetDialect(conn *dbi.DbConn) dbi.Dialect {
|
||||
return &OracleDialect{conn}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ type DbInstance struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Network string `json:"network"`
|
||||
Sid string `json:"sid"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"-"`
|
||||
Params string `json:"params"`
|
||||
|
||||
@@ -2,6 +2,7 @@ package anyx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
@@ -40,6 +41,10 @@ func ConvInt(val any) int {
|
||||
return int(value)
|
||||
case uint8:
|
||||
return int(value)
|
||||
case float32:
|
||||
return int(value)
|
||||
case float64:
|
||||
return int(math.Round(value))
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -10,6 +10,7 @@ CREATE TABLE `t_db_instance` (
|
||||
`name` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库实例名称',
|
||||
`host` varchar(100) COLLATE utf8mb4_bin NOT NULL,
|
||||
`port` int(8) NOT NULL,
|
||||
`sid` varchar(255) NULL COMMENT 'oracle数据库需要sid',
|
||||
`username` varchar(255) COLLATE utf8mb4_bin NOT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
|
||||
|
||||
@@ -234,3 +234,7 @@ INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permiss
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('数据库备份恢复', 'DbBackupRestore', '[{"model":"backupPath","name":"备份路径","placeholder":"备份文件存储路径"}]', '{"backupPath":"./db/backup"}', '', 'admin,', '2023-12-29 09:55:26', 1, 'admin', '2023-12-29 15:45:24', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('Mysql可执行文件', 'MysqlBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mysql/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('MariaDB可执行文件', 'MariadbBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mariadb/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
|
||||
|
||||
|
||||
ALTER TABLE `t_db_instance`
|
||||
ADD COLUMN `sid` varchar(255) NULL COMMENT 'oracle数据库需要sid' AFTER `port`;
|
||||
Reference in New Issue
Block a user