From eb55f938645ebf082fee9bba46fa4f61c5929e1b Mon Sep 17 00:00:00 2001 From: "meilin.huang" <954537473@qq.com> Date: Mon, 11 Mar 2024 20:04:20 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20dbm=E5=8C=85=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mayfly_go_web/package.json | 4 +- mayfly_go_web/src/App.vue | 2 +- mayfly_go_web/src/views/flow/ProcinstList.vue | 8 +- .../src/views/flow/ProcinstTaskList.vue | 1 + server/internal/db/api/db.go | 18 +- server/internal/db/api/db_instance.go | 2 +- server/internal/db/application/db_instance.go | 5 +- server/internal/db/application/db_sql_exec.go | 2 +- server/internal/db/dbm/dbi/dialect.go | 109 +- server/internal/db/dbm/dbi/info.go | 12 + server/internal/db/dbm/dbi/meta.go | 4 +- server/internal/db/dbm/dbi/metadata.go | 112 ++ server/internal/db/dbm/dm/dialect.go | 236 +--- server/internal/db/dbm/dm/metadata.go | 239 ++++ server/internal/db/dbm/mssql/dialect.go | 294 +---- server/internal/db/dbm/mssql/metadata.go | 273 ++++ server/internal/db/dbm/mysql/dialect.go | 153 +-- server/internal/db/dbm/mysql/metadata.go | 166 +++ server/internal/db/dbm/oracle/dialect.go | 252 +--- server/internal/db/dbm/oracle/metadata.go | 261 ++++ server/internal/db/dbm/postgres/dialect.go | 171 +-- server/internal/db/dbm/postgres/metadata.go | 182 +++ server/internal/db/dbm/sqlite/dialect.go | 167 +-- server/internal/db/dbm/sqlite/metadata.go | 178 +++ server/internal/flow/domain/entity/query.go | 2 +- server/resources/data/mayfly-go.sqlite | Bin 270336 -> 274432 bytes .../resources/script/sql/mayfly-go-sqlite.sql | 1095 ----------------- server/resources/script/sql/mayfly-go.sql | 5 +- 28 files changed, 1489 insertions(+), 2464 deletions(-) create mode 100644 server/internal/db/dbm/dbi/metadata.go create mode 100644 server/internal/db/dbm/dm/metadata.go create mode 100644 server/internal/db/dbm/mssql/metadata.go create mode 100644 server/internal/db/dbm/mysql/metadata.go create mode 100644 server/internal/db/dbm/oracle/metadata.go create mode 100644 server/internal/db/dbm/postgres/metadata.go create mode 100644 server/internal/db/dbm/sqlite/metadata.go delete mode 100644 server/resources/script/sql/mayfly-go-sqlite.sql diff --git a/mayfly_go_web/package.json b/mayfly_go_web/package.json index 3bdc4181..8eca716d 100644 --- a/mayfly_go_web/package.json +++ b/mayfly_go_web/package.json @@ -17,12 +17,12 @@ "countup.js": "^2.8.0", "cropperjs": "^1.6.1", "echarts": "^5.5.0", - "element-plus": "^2.6.0", + "element-plus": "^2.6.1", "js-base64": "^3.7.7", "jsencrypt": "^3.3.2", "lodash": "^4.17.21", "mitt": "^3.0.1", - "monaco-editor": "^0.46.0", + "monaco-editor": "^0.47.0", "monaco-sql-languages": "^0.11.0", "monaco-themes": "^0.4.4", "nprogress": "^0.2.0", diff --git a/mayfly_go_web/src/App.vue b/mayfly_go_web/src/App.vue index ffc77c88..8d2356e8 100644 --- a/mayfly_go_web/src/App.vue +++ b/mayfly_go_web/src/App.vue @@ -4,7 +4,7 @@ :zIndex="10000000" :width="210" v-if="themeConfig.isWatermark" - :font="{ color: 'rgba(180, 180, 180, 0.5)' }" + :font="{ color: 'rgba(180, 180, 180, 0.3)' }" :content="themeConfig.watermarkText" class="h100" > diff --git a/mayfly_go_web/src/views/flow/ProcinstList.vue b/mayfly_go_web/src/views/flow/ProcinstList.vue index 8ee5ba27..e41e3bb9 100644 --- a/mayfly_go_web/src/views/flow/ProcinstList.vue +++ b/mayfly_go_web/src/views/flow/ProcinstList.vue @@ -50,11 +50,17 @@ import { FlowBizType, ProcinstBizStatus, ProcinstStatus } from './enums'; import { ElMessage } from 'element-plus'; import { formatTime } from '@/common/utils/format'; -const searchItems = [SearchItem.select('status', '流程状态').withEnum(ProcinstStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)]; +const searchItems = [ + SearchItem.select('status', '流程状态').withEnum(ProcinstStatus), + SearchItem.select('bizType', '业务类型').withEnum(FlowBizType), + SearchItem.input('bizKey', '业务key'), +]; + const columns = [ TableColumn.new('bizType', '业务').typeTag(FlowBizType), TableColumn.new('remark', '备注'), TableColumn.new('creator', '发起人'), + TableColumn.new('bizKey', '业务key'), TableColumn.new('procdefName', '流程名'), TableColumn.new('status', '流程状态').typeTag(ProcinstStatus), TableColumn.new('bizStatus', '业务状态').typeTag(ProcinstBizStatus), diff --git a/mayfly_go_web/src/views/flow/ProcinstTaskList.vue b/mayfly_go_web/src/views/flow/ProcinstTaskList.vue index 2c14c728..a269d13b 100644 --- a/mayfly_go_web/src/views/flow/ProcinstTaskList.vue +++ b/mayfly_go_web/src/views/flow/ProcinstTaskList.vue @@ -46,6 +46,7 @@ const columns = [ TableColumn.new('procinst.creator', '发起人'), TableColumn.new('procinst.status', '流程状态').typeTag(ProcinstStatus), TableColumn.new('status', '任务状态').typeTag(ProcinstTaskStatus), + TableColumn.new('procinst.bizKey', '业务key'), TableColumn.new('procinst.procdefName', '流程名'), TableColumn.new('taskName', '当前节点'), TableColumn.new('procinst.createTime', '发起时间').isTime(), diff --git a/server/internal/db/api/db.go b/server/internal/db/api/db.go index 0c9b7bd7..b1c06af5 100644 --- a/server/internal/db/api/db.go +++ b/server/internal/db/api/db.go @@ -332,7 +332,7 @@ func (d *Db) dumpDb(ctx context.Context, writer *gzipWriter, dbId uint64, dbName dbMeta := dbConn.GetDialect() if len(tables) == 0 { - ti, err := dbMeta.GetTables() + ti, err := dbMeta.GetMetaData().GetTables() biz.ErrIsNil(err) tables = make([]string, len(ti)) for i, table := range ti { @@ -346,7 +346,7 @@ func (d *Db) dumpDb(ctx context.Context, writer *gzipWriter, dbId uint64, dbName if needStruct { writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表结构: %s \n-- ----------------------------\n", table)) writer.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS %s;\n", quotedTable)) - ddl, err := dbMeta.GetTableDDL(table) + ddl, err := dbMeta.GetMetaData().GetTableDDL(table) biz.ErrIsNil(err) writer.WriteString(ddl + "\n") } @@ -386,7 +386,7 @@ func (d *Db) dumpDb(ctx context.Context, writer *gzipWriter, dbId uint64, dbName } func (d *Db) TableInfos(rc *req.Ctx) { - res, err := d.getDbConn(rc).GetDialect().GetTables() + res, err := d.getDbConn(rc).GetDialect().GetMetaData().GetTables() biz.ErrIsNilAppendErr(err, "获取表信息失败: %s") rc.ResData = res } @@ -394,7 +394,7 @@ func (d *Db) TableInfos(rc *req.Ctx) { func (d *Db) TableIndex(rc *req.Ctx) { tn := rc.Query("tableName") biz.NotEmpty(tn, "tableName不能为空") - res, err := d.getDbConn(rc).GetDialect().GetTableIndex(tn) + res, err := d.getDbConn(rc).GetDialect().GetMetaData().GetTableIndex(tn) biz.ErrIsNilAppendErr(err, "获取表索引信息失败: %s") rc.ResData = res } @@ -405,7 +405,7 @@ func (d *Db) ColumnMA(rc *req.Ctx) { biz.NotEmpty(tn, "tableName不能为空") dbi := d.getDbConn(rc) - res, err := dbi.GetDialect().GetColumns(tn) + res, err := dbi.GetDialect().GetMetaData().GetColumns(tn) biz.ErrIsNilAppendErr(err, "获取数据库列信息失败: %s") rc.ResData = res } @@ -416,7 +416,7 @@ func (d *Db) HintTables(rc *req.Ctx) { dm := dbi.GetDialect() // 获取所有表 - tables, err := dm.GetTables() + tables, err := dm.GetMetaData().GetTables() biz.ErrIsNil(err) tableNames := make([]string, 0) for _, v := range tables { @@ -432,7 +432,7 @@ func (d *Db) HintTables(rc *req.Ctx) { } // 获取所有表下的所有列信息 - columnMds, err := dm.GetColumns(tableNames...) + columnMds, err := dm.GetMetaData().GetColumns(tableNames...) biz.ErrIsNil(err) for _, v := range columnMds { tName := v.TableName @@ -455,13 +455,13 @@ func (d *Db) HintTables(rc *req.Ctx) { func (d *Db) GetTableDDL(rc *req.Ctx) { tn := rc.Query("tableName") biz.NotEmpty(tn, "tableName不能为空") - res, err := d.getDbConn(rc).GetDialect().GetTableDDL(tn) + res, err := d.getDbConn(rc).GetDialect().GetMetaData().GetTableDDL(tn) biz.ErrIsNilAppendErr(err, "获取表ddl失败: %s") rc.ResData = res } func (d *Db) GetSchemas(rc *req.Ctx) { - res, err := d.getDbConn(rc).GetDialect().GetSchemas() + res, err := d.getDbConn(rc).GetDialect().GetMetaData().GetSchemas() biz.ErrIsNilAppendErr(err, "获取schemas失败: %s") rc.ResData = res } diff --git a/server/internal/db/api/db_instance.go b/server/internal/db/api/db_instance.go index 22fcfd3a..6dea3be4 100644 --- a/server/internal/db/api/db_instance.go +++ b/server/internal/db/api/db_instance.go @@ -107,7 +107,7 @@ func (d *Instance) GetDbServer(rc *req.Ctx) { instanceId := getInstanceId(rc) conn, err := d.DbApp.GetDbConnByInstanceId(instanceId) biz.ErrIsNil(err) - res, err := conn.GetDialect().GetDbServer() + res, err := conn.GetDialect().GetMetaData().GetDbServer() biz.ErrIsNil(err) rc.ResData = res } diff --git a/server/internal/db/application/db_instance.go b/server/internal/db/application/db_instance.go index 39574b7b..db55b6f8 100644 --- a/server/internal/db/application/db_instance.go +++ b/server/internal/db/application/db_instance.go @@ -3,7 +3,6 @@ package application import ( "context" "errors" - "gorm.io/gorm" "mayfly-go/internal/db/dbm" "mayfly-go/internal/db/dbm/dbi" "mayfly-go/internal/db/domain/entity" @@ -12,6 +11,8 @@ import ( "mayfly-go/pkg/biz" "mayfly-go/pkg/errorx" "mayfly-go/pkg/model" + + "gorm.io/gorm" ) type Instance interface { @@ -159,5 +160,5 @@ func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance) ([]string, error } defer dbConn.Close() - return dbConn.GetDialect().GetDbNames() + return dbConn.GetDialect().GetMetaData().GetDbNames() } diff --git a/server/internal/db/application/db_sql_exec.go b/server/internal/db/application/db_sql_exec.go index 881610da..6a414881 100644 --- a/server/internal/db/application/db_sql_exec.go +++ b/server/internal/db/application/db_sql_exec.go @@ -278,7 +278,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, update *sqlparser.Updat } // 获取表主键列名,排除使用别名 - primaryKey, err := dbConn.GetDialect().GetPrimaryKey(tableName) + primaryKey, err := dbConn.GetDialect().GetMetaData().GetPrimaryKey(tableName) if err != nil { return nil, errorx.NewBiz("获取表主键信息失败") } diff --git a/server/internal/db/dbm/dbi/dialect.go b/server/internal/db/dbm/dbi/dialect.go index 0af0623c..89f74538 100644 --- a/server/internal/db/dbm/dbi/dialect.go +++ b/server/internal/db/dbm/dbi/dialect.go @@ -2,12 +2,6 @@ package dbi import ( "database/sql" - "embed" - "strings" - - "mayfly-go/pkg/biz" - "mayfly-go/pkg/utils/collx" - "mayfly-go/pkg/utils/stringx" ) type DataType string @@ -29,46 +23,6 @@ const ( DuplicateStrategyUpdate = 2 ) -// 数据库服务实例信息 -type DbServer struct { - Version string `json:"version"` // 版本信息 - Extra collx.M `json:"extra"` // 其他额外信息 -} - -// 表信息 -type Table struct { - TableName string `json:"tableName"` // 表名 - TableComment string `json:"tableComment"` // 表备注 - CreateTime string `json:"createTime"` // 创建时间 - TableRows int `json:"tableRows"` - DataLength int64 `json:"dataLength"` - IndexLength int64 `json:"indexLength"` -} - -// 表的列信息 -type Column struct { - TableName string `json:"tableName"` // 表名 - ColumnName string `json:"columnName"` // 列名 - ColumnType string `json:"columnType"` // 列类型 - ColumnComment string `json:"columnComment"` // 列备注 - IsPrimaryKey bool `json:"isPrimaryKey"` // 是否为主键 - IsIdentity bool `json:"isIdentity"` // 是否自增 - ColumnDefault string `json:"columnDefault"` // 默认值 - Nullable string `json:"nullable"` // 是否可为null - NumScale string `json:"numScale"` // 小数点 - Extra collx.M `json:"extra"` // 其他额外信息 -} - -// 表索引信息 -type Index struct { - IndexName string `json:"indexName"` // 索引名 - ColumnName string `json:"columnName"` // 列名 - IndexType string `json:"indexType"` // 索引类型 - IndexComment string `json:"indexComment"` // 备注 - SeqInIndex int `json:"seqInIndex"` - IsUnique bool `json:"isUnique"` -} - type DbCopyTable struct { Id uint64 `json:"id"` Db string `json:"db" ` @@ -90,31 +44,11 @@ type DataConverter interface { } // -----------------------------------元数据接口定义------------------------------------------ -// 数据库方言、元信息接口(表、列、获取表数据等元信息) +// 数据库方言 用于获取元信息接口、批量插入等各个数据库方言不一致的实现方式 type Dialect interface { - // 获取数据库服务实例信息 - GetDbServer() (*DbServer, error) - - // 获取数据库名称列表 - GetDbNames() ([]string, error) - - // 获取表信息 - GetTables() ([]Table, error) - - // 获取指定表名的所有列元信息 - GetColumns(tableNames ...string) ([]Column, error) - - // 获取表主键字段名,没有主键标识则默认第一个字段 - GetPrimaryKey(tableName string) (string, error) - - // 获取表索引信息 - GetTableIndex(tableName string) ([]Index, error) - - // 获取建表ddl - GetTableDDL(tableName string) (string, error) - - GetSchemas() ([]string, error) + // 获取元数据信息接口 + GetMetaData() MetaData // GetDbProgram 获取数据库程序模块,用于数据库备份与恢复 GetDbProgram() (DbProgram, error) @@ -127,40 +61,3 @@ type Dialect interface { CopyTable(copy *DbCopyTable) error } - -// ------------------------- 元数据sql操作 ------------------------- -// -//go:embed metasql/* -var metasql embed.FS - -// sql缓存 key: sql备注的key 如:MYSQL_TABLE_MA value: sql内容 -var sqlCache = make(map[string]string, 20) - -// 获取本地文件的sql内容,并进行解析,获取对应key的sql内容 -func GetLocalSql(file, key string) string { - sql := sqlCache[key] - if sql != "" { - return sql - } - - bytes, err := metasql.ReadFile(file) - biz.ErrIsNilAppendErr(err, "获取sql meta文件内容失败: %s") - allSql := string(bytes) - - sqls := strings.Split(allSql, "---------------------------------------") - var resSql string - for _, sql := range sqls { - sql = stringx.TrimSpaceAndBr(sql) - // 获取sql第一行的sql备注信息如:--MYSQL_TABLE_MA 表信息元数据 - info := strings.SplitN(sql, "\n", 2) - // 原始sql,即去除第一行的key与备注信息 - rowSql := info[1] - // 获取sql key;如:MYSQL_TABLE_MA - sqlKey := strings.Split(strings.Split(info[0], " ")[0], "--")[1] - if key == sqlKey { - resSql = rowSql - } - sqlCache[sqlKey] = rowSql - } - return resSql -} diff --git a/server/internal/db/dbm/dbi/info.go b/server/internal/db/dbm/dbi/info.go index ccc878ba..517aa518 100644 --- a/server/internal/db/dbm/dbi/info.go +++ b/server/internal/db/dbm/dbi/info.go @@ -6,6 +6,7 @@ import ( "mayfly-go/internal/machine/mcm" "mayfly-go/pkg/errorx" "mayfly-go/pkg/logx" + "strings" ) type DbInfo struct { @@ -89,6 +90,17 @@ func (di *DbInfo) IfUseSshTunnelChangeIpPort() error { return nil } +// 获取当前库的schema +func (di *DbInfo) CurrentSchema() string { + dbName := di.Database + schema := "" + arr := strings.Split(dbName, "/") + if len(arr) == 2 { + schema = arr[1] + } + return schema +} + // 根据ssh tunnel机器id返回ssh tunnel func GetSshTunnel(sshTunnelMachineId int) (*mcm.SshTunnelMachine, error) { return machineapp.GetMachineApp().GetSshTunnelMachine(sshTunnelMachineId) diff --git a/server/internal/db/dbm/dbi/meta.go b/server/internal/db/dbm/dbi/meta.go index fb692386..f3ff44a1 100644 --- a/server/internal/db/dbm/dbi/meta.go +++ b/server/internal/db/dbm/dbi/meta.go @@ -16,11 +16,11 @@ func GetMeta(dt DbType) Meta { return metas[dt] } -// 数据库元信息获取,如获取sql.DB、Dialect等 +// 数据库元信息,如获取sql.DB、Dialect等 type Meta interface { // 根据数据库信息获取sql.DB GetSqlDb(*DbInfo) (*sql.DB, error) - // 获取数据库方言,用于获取表结构等信息 + // 获取数据库方言 GetDialect(*DbConn) Dialect } diff --git a/server/internal/db/dbm/dbi/metadata.go b/server/internal/db/dbm/dbi/metadata.go new file mode 100644 index 00000000..a67b1784 --- /dev/null +++ b/server/internal/db/dbm/dbi/metadata.go @@ -0,0 +1,112 @@ +package dbi + +import ( + "embed" + "mayfly-go/pkg/biz" + "mayfly-go/pkg/utils/collx" + "mayfly-go/pkg/utils/stringx" + "strings" +) + +// 元数据接口(表、列、等元信息) +type MetaData interface { + // 获取数据库服务实例信息 + GetDbServer() (*DbServer, error) + + // 获取数据库名称列表 + GetDbNames() ([]string, error) + + // 获取表信息 + GetTables() ([]Table, error) + + // 获取指定表名的所有列元信息 + GetColumns(tableNames ...string) ([]Column, error) + + // 获取表主键字段名,没有主键标识则默认第一个字段 + GetPrimaryKey(tableName string) (string, error) + + // 获取表索引信息 + GetTableIndex(tableName string) ([]Index, error) + + // 获取建表ddl + GetTableDDL(tableName string) (string, error) + + GetSchemas() ([]string, error) +} + +// 数据库服务实例信息 +type DbServer struct { + Version string `json:"version"` // 版本信息 + Extra collx.M `json:"extra"` // 其他额外信息 +} + +// 表信息 +type Table struct { + TableName string `json:"tableName"` // 表名 + TableComment string `json:"tableComment"` // 表备注 + CreateTime string `json:"createTime"` // 创建时间 + TableRows int `json:"tableRows"` + DataLength int64 `json:"dataLength"` + IndexLength int64 `json:"indexLength"` +} + +// 表的列信息 +type Column struct { + TableName string `json:"tableName"` // 表名 + ColumnName string `json:"columnName"` // 列名 + ColumnType string `json:"columnType"` // 列类型 + ColumnComment string `json:"columnComment"` // 列备注 + IsPrimaryKey bool `json:"isPrimaryKey"` // 是否为主键 + IsIdentity bool `json:"isIdentity"` // 是否自增 + ColumnDefault string `json:"columnDefault"` // 默认值 + Nullable string `json:"nullable"` // 是否可为null + NumScale string `json:"numScale"` // 小数点 + Extra collx.M `json:"extra"` // 其他额外信息 +} + +// 表索引信息 +type Index struct { + IndexName string `json:"indexName"` // 索引名 + ColumnName string `json:"columnName"` // 列名 + IndexType string `json:"indexType"` // 索引类型 + IndexComment string `json:"indexComment"` // 备注 + SeqInIndex int `json:"seqInIndex"` + IsUnique bool `json:"isUnique"` +} + +// ------------------------- 元数据sql操作 ------------------------- +// +//go:embed metasql/* +var metasql embed.FS + +// sql缓存 key: sql备注的key 如:MYSQL_TABLE_MA value: sql内容 +var sqlCache = make(map[string]string, 20) + +// 获取本地文件的sql内容,并进行解析,获取对应key的sql内容 +func GetLocalSql(file, key string) string { + sql := sqlCache[key] + if sql != "" { + return sql + } + + bytes, err := metasql.ReadFile(file) + biz.ErrIsNilAppendErr(err, "获取sql meta文件内容失败: %s") + allSql := string(bytes) + + sqls := strings.Split(allSql, "---------------------------------------") + var resSql string + for _, sql := range sqls { + sql = stringx.TrimSpaceAndBr(sql) + // 获取sql第一行的sql备注信息如:--MYSQL_TABLE_MA 表信息元数据 + info := strings.SplitN(sql, "\n", 2) + // 原始sql,即去除第一行的key与备注信息 + rowSql := info[1] + // 获取sql key;如:MYSQL_TABLE_MA + sqlKey := strings.Split(strings.Split(info[0], " ")[0], "--")[1] + if key == sqlKey { + resSql = rowSql + } + sqlCache[sqlKey] = rowSql + } + return resSql +} diff --git a/server/internal/db/dbm/dm/dialect.go b/server/internal/db/dbm/dm/dialect.go index 335ce816..46d3ae52 100644 --- a/server/internal/db/dbm/dm/dialect.go +++ b/server/internal/db/dbm/dm/dialect.go @@ -4,7 +4,6 @@ import ( "database/sql" "fmt" "mayfly-go/internal/db/dbm/dbi" - "mayfly-go/pkg/errorx" "mayfly-go/pkg/logx" "mayfly-go/pkg/utils/anyx" "mayfly-go/pkg/utils/collx" @@ -18,233 +17,14 @@ import ( _ "gitee.com/chunanyong/dm" ) -const ( - DM_META_FILE = "metasql/dm_meta.sql" - DM_DB_SCHEMAS = "DM_DB_SCHEMAS" - DM_TABLE_INFO_KEY = "DM_TABLE_INFO" - DM_INDEX_INFO_KEY = "DM_INDEX_INFO" - DM_COLUMN_MA_KEY = "DM_COLUMN_MA" -) - type DMDialect struct { dc *dbi.DbConn } -func (dd *DMDialect) GetDbServer() (*dbi.DbServer, error) { - _, res, err := dd.dc.Query("select * from v$instance") - if err != nil { - return nil, err +func (dd *DMDialect) GetMetaData() dbi.MetaData { + return &DMMetaData{ + dc: dd.dc, } - ds := &dbi.DbServer{ - Version: anyx.ConvString(res[0]["SVR_VERSION"]), - } - return ds, nil -} - -func (dd *DMDialect) GetDbNames() ([]string, error) { - _, res, err := dd.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 (dd *DMDialect) 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 := dd.dc.Query(dbi.GetLocalSql(DM_META_FILE, DM_TABLE_INFO_KEY)) - if err != nil { - return nil, err - } - - tables := make([]dbi.Table, 0) - for _, re := range res { - tables = append(tables, dbi.Table{ - TableName: anyx.ConvString(re["TABLE_NAME"]), - 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 (dd *DMDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) { - dbType := dd.dc.Info.Type - tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { - return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) - }), ",") - - _, res, err := dd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(DM_META_FILE, DM_COLUMN_MA_KEY), tableName)) - if err != nil { - return nil, err - } - - columns := make([]dbi.Column, 0) - for _, re := range res { - columns = append(columns, dbi.Column{ - TableName: anyx.ConvString(re["TABLE_NAME"]), - ColumnName: anyx.ConvString(re["COLUMN_NAME"]), - ColumnType: anyx.ConvString(re["COLUMN_TYPE"]), - ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]), - Nullable: anyx.ConvString(re["NULLABLE"]), - IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1, - IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1, - ColumnDefault: anyx.ConvString(re["COLUMN_DEFAULT"]), - NumScale: anyx.ConvString(re["NUM_SCALE"]), - }) - } - return columns, nil -} - -func (dd *DMDialect) GetPrimaryKey(tablename string) (string, error) { - columns, err := dd.GetColumns(tablename) - if err != nil { - return "", err - } - if len(columns) == 0 { - return "", errorx.NewBiz("[%s] 表不存在", tablename) - } - for _, v := range columns { - if v.IsPrimaryKey { - return v.ColumnName, nil - } - } - - return columns[0].ColumnName, nil -} - -// 获取表索引信息 -func (dd *DMDialect) GetTableIndex(tableName string) ([]dbi.Index, error) { - _, res, err := dd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(DM_META_FILE, DM_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: anyx.ConvString(re["INDEX_NAME"]), - ColumnName: anyx.ConvString(re["COLUMN_NAME"]), - IndexType: anyx.ConvString(re["INDEX_TYPE"]), - IndexComment: anyx.ConvString(re["INDEX_COMMENT"]), - IsUnique: anyx.ConvInt(re["IS_UNIQUE"]) == 1, - 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 (dd *DMDialect) GetTableDDL(tableName string) (string, error) { - ddlSql := fmt.Sprintf("CALL SP_TABLEDEF((SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)), '%s')", tableName) - _, res, err := dd.dc.Query(ddlSql) - if err != nil { - return "", err - } - // 建表ddl - var builder strings.Builder - for _, re := range res { - builder.WriteString(re["COLUMN_VALUE"].(string)) - } - - // 表注释 - _, res, err = dd.dc.Query(fmt.Sprintf(` - select OWNER, COMMENTS from ALL_TAB_COMMENTS where TABLE_TYPE='TABLE' and TABLE_NAME = '%s' - and owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)) - `, 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 USER_COL_COMMENTS - WHERE OWNER = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)) - AND TABLE_NAME = '%s' - `, tableName) - _, res, err = dd.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 indexdef(b.object_id,1) 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 SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)) - and a.table_name = '%s' - and indexdef(b.object_id,1) != '禁止查看系统定义的索引信息' - `, tableName) - _, res, err = dd.dc.Query(indexSql) - if err != nil { - return "", err - } - for _, re := range res { - builder.WriteString("\n\n" + re["INDEX_DEF"].(string)) - } - - return builder.String(), nil -} - -// 获取DM当前连接的库可访问的schemaNames -func (dd *DMDialect) GetSchemas() ([]string, error) { - sql := dbi.GetLocalSql(DM_META_FILE, DM_DB_SCHEMAS) - _, res, err := dd.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 获取数据库程序模块,用于数据库备份与恢复 @@ -304,7 +84,8 @@ func (dd *DMDialect) batchInsertMerge(dbType dbi.DbType, tx *sql.Tx, tableName s // 查询主键字段 uniqueCols := make([]string, 0) caseSqls := make([]string, 0) - tableCols, _ := dd.GetColumns(tableName) + metadata := dd.GetMetaData() + tableCols, _ := metadata.GetColumns(tableName) identityCols := make([]string, 0) for _, col := range tableCols { if col.IsPrimaryKey { @@ -317,7 +98,7 @@ func (dd *DMDialect) batchInsertMerge(dbType dbi.DbType, tx *sql.Tx, tableName s } } // 查询唯一索引涉及到的字段,并组装到match条件内 - indexs, _ := dd.GetTableIndex(tableName) + indexs, _ := metadata.GetTableIndex(tableName) if indexs != nil { for _, index := range indexs { if index.IsUnique { @@ -428,7 +209,8 @@ func (dd *DataConverter) ParseData(dbColumnValue any, dataType dbi.DataType) any func (dd *DMDialect) CopyTable(copy *dbi.DbCopyTable) error { tableName := copy.TableName - ddl, err := dd.GetTableDDL(tableName) + metadata := dd.GetMetaData() + ddl, err := metadata.GetTableDDL(tableName) if err != nil { return err } @@ -450,7 +232,7 @@ func (dd *DMDialect) CopyTable(copy *dbi.DbCopyTable) error { // 设置允许填充自增列之后,显示指定列名可以插入自增列 _, _ = dd.dc.Exec(fmt.Sprintf("set identity_insert \"%s\" on", newTableName)) // 获取列名 - columns, _ := dd.GetColumns(tableName) + columns, _ := metadata.GetColumns(tableName) columnArr := make([]string, 0) for _, column := range columns { columnArr = append(columnArr, fmt.Sprintf("\"%s\"", column.ColumnName)) diff --git a/server/internal/db/dbm/dm/metadata.go b/server/internal/db/dbm/dm/metadata.go new file mode 100644 index 00000000..bca9fe6a --- /dev/null +++ b/server/internal/db/dbm/dm/metadata.go @@ -0,0 +1,239 @@ +package dm + +import ( + "fmt" + "mayfly-go/internal/db/dbm/dbi" + "mayfly-go/pkg/errorx" + "mayfly-go/pkg/utils/anyx" + "mayfly-go/pkg/utils/collx" + "strings" +) + +const ( + DM_META_FILE = "metasql/dm_meta.sql" + DM_DB_SCHEMAS = "DM_DB_SCHEMAS" + DM_TABLE_INFO_KEY = "DM_TABLE_INFO" + DM_INDEX_INFO_KEY = "DM_INDEX_INFO" + DM_COLUMN_MA_KEY = "DM_COLUMN_MA" +) + +type DMMetaData struct { + dc *dbi.DbConn +} + +func (dd *DMMetaData) GetDbServer() (*dbi.DbServer, error) { + _, res, err := dd.dc.Query("select * from v$instance") + if err != nil { + return nil, err + } + ds := &dbi.DbServer{ + Version: anyx.ConvString(res[0]["SVR_VERSION"]), + } + return ds, nil +} + +func (dd *DMMetaData) GetDbNames() ([]string, error) { + _, res, err := dd.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 (dd *DMMetaData) 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 := dd.dc.Query(dbi.GetLocalSql(DM_META_FILE, DM_TABLE_INFO_KEY)) + if err != nil { + return nil, err + } + + tables := make([]dbi.Table, 0) + for _, re := range res { + tables = append(tables, dbi.Table{ + TableName: anyx.ConvString(re["TABLE_NAME"]), + 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 (dd *DMMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) { + dbType := dd.dc.Info.Type + tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { + return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) + }), ",") + + _, res, err := dd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(DM_META_FILE, DM_COLUMN_MA_KEY), tableName)) + if err != nil { + return nil, err + } + + columns := make([]dbi.Column, 0) + for _, re := range res { + columns = append(columns, dbi.Column{ + TableName: anyx.ConvString(re["TABLE_NAME"]), + ColumnName: anyx.ConvString(re["COLUMN_NAME"]), + ColumnType: anyx.ConvString(re["COLUMN_TYPE"]), + ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]), + Nullable: anyx.ConvString(re["NULLABLE"]), + IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1, + IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1, + ColumnDefault: anyx.ConvString(re["COLUMN_DEFAULT"]), + NumScale: anyx.ConvString(re["NUM_SCALE"]), + }) + } + return columns, nil +} + +func (dd *DMMetaData) GetPrimaryKey(tablename string) (string, error) { + columns, err := dd.GetColumns(tablename) + if err != nil { + return "", err + } + if len(columns) == 0 { + return "", errorx.NewBiz("[%s] 表不存在", tablename) + } + for _, v := range columns { + if v.IsPrimaryKey { + return v.ColumnName, nil + } + } + + return columns[0].ColumnName, nil +} + +// 获取表索引信息 +func (dd *DMMetaData) GetTableIndex(tableName string) ([]dbi.Index, error) { + _, res, err := dd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(DM_META_FILE, DM_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: anyx.ConvString(re["INDEX_NAME"]), + ColumnName: anyx.ConvString(re["COLUMN_NAME"]), + IndexType: anyx.ConvString(re["INDEX_TYPE"]), + IndexComment: anyx.ConvString(re["INDEX_COMMENT"]), + IsUnique: anyx.ConvInt(re["IS_UNIQUE"]) == 1, + 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 (dd *DMMetaData) GetTableDDL(tableName string) (string, error) { + ddlSql := fmt.Sprintf("CALL SP_TABLEDEF((SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)), '%s')", tableName) + _, res, err := dd.dc.Query(ddlSql) + if err != nil { + return "", err + } + // 建表ddl + var builder strings.Builder + for _, re := range res { + builder.WriteString(re["COLUMN_VALUE"].(string)) + } + + // 表注释 + _, res, err = dd.dc.Query(fmt.Sprintf(` + select OWNER, COMMENTS from ALL_TAB_COMMENTS where TABLE_TYPE='TABLE' and TABLE_NAME = '%s' + and owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)) + `, 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 USER_COL_COMMENTS + WHERE OWNER = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)) + AND TABLE_NAME = '%s' + `, tableName) + _, res, err = dd.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 indexdef(b.object_id,1) 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 SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)) + and a.table_name = '%s' + and indexdef(b.object_id,1) != '禁止查看系统定义的索引信息' + `, tableName) + _, res, err = dd.dc.Query(indexSql) + if err != nil { + return "", err + } + for _, re := range res { + builder.WriteString("\n\n" + re["INDEX_DEF"].(string)) + } + + return builder.String(), nil +} + +// 获取DM当前连接的库可访问的schemaNames +func (dd *DMMetaData) GetSchemas() ([]string, error) { + sql := dbi.GetLocalSql(DM_META_FILE, DM_DB_SCHEMAS) + _, res, err := dd.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 +} diff --git a/server/internal/db/dbm/mssql/dialect.go b/server/internal/db/dbm/mssql/dialect.go index 3d4f02f8..3406e11a 100644 --- a/server/internal/db/dbm/mssql/dialect.go +++ b/server/internal/db/dbm/mssql/dialect.go @@ -1,11 +1,9 @@ package mssql import ( - "context" "database/sql" "fmt" "mayfly-go/internal/db/dbm/dbi" - "mayfly-go/pkg/errorx" "mayfly-go/pkg/logx" "mayfly-go/pkg/utils/anyx" "mayfly-go/pkg/utils/collx" @@ -14,282 +12,12 @@ import ( "time" ) -const ( - MSSQL_META_FILE = "metasql/mssql_meta.sql" - MSSQL_DBS_KEY = "MSSQL_DBS" - MSSQL_DB_SCHEMAS_KEY = "MSSQL_DB_SCHEMAS" - MSSQL_TABLE_INFO_KEY = "MSSQL_TABLE_INFO" - MSSQL_INDEX_INFO_KEY = "MSSQL_INDEX_INFO" - MSSQL_COLUMN_MA_KEY = "MSSQL_COLUMN_MA" - MSSQL_TABLE_DETAIL_KEY = "MSSQL_TABLE_DETAIL" - MSSQL_TABLE_INDEX_DDL_KEY = "MSSQL_TABLE_INDEX_DDL" -) - type MssqlDialect struct { dc *dbi.DbConn } -func (md *MssqlDialect) GetDbServer() (*dbi.DbServer, error) { - _, res, err := md.dc.Query("SELECT @@VERSION as version") - if err != nil { - return nil, err - } - ds := &dbi.DbServer{ - Version: anyx.ConvString(res[0]["version"]), - } - return ds, nil -} - -func (md *MssqlDialect) GetDbNames() ([]string, error) { - _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_DBS_KEY)) - if err != nil { - return nil, err - } - - databases := make([]string, 0) - for _, re := range res { - databases = append(databases, anyx.ConvString(re["dbname"])) - } - - return databases, nil -} - -// 从连接信息中获取数据库和schema信息 -func (md *MssqlDialect) currentSchema() string { - dbName := md.dc.Info.Database - schema := "" - arr := strings.Split(dbName, "/") - if len(arr) == 2 { - schema = arr[1] - } - return schema -} - -// 获取表基础元信息, 如表名等 -func (md *MssqlDialect) GetTables() ([]dbi.Table, error) { - _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_INFO_KEY), md.currentSchema()) - - if err != nil { - return nil, err - } - - tables := make([]dbi.Table, 0) - for _, re := range res { - tables = append(tables, dbi.Table{ - TableName: anyx.ConvString(re["tableName"]), - 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 (md *MssqlDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) { - dbType := md.dc.Info.Type - tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { - return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) - }), ",") - - _, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_COLUMN_MA_KEY), tableName), md.currentSchema()) - if err != nil { - return nil, err - } - - columns := make([]dbi.Column, 0) - for _, re := range res { - columns = append(columns, dbi.Column{ - TableName: anyx.ToString(re["TABLE_NAME"]), - ColumnName: anyx.ToString(re["COLUMN_NAME"]), - ColumnType: anyx.ToString(re["COLUMN_TYPE"]), - ColumnComment: anyx.ToString(re["COLUMN_COMMENT"]), - Nullable: anyx.ToString(re["NULLABLE"]), - IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1, - IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1, - ColumnDefault: anyx.ToString(re["COLUMN_DEFAULT"]), - NumScale: anyx.ToString(re["NUM_SCALE"]), - }) - } - return columns, nil -} - -// 获取表主键字段名,不存在主键标识则默认第一个字段 -func (md *MssqlDialect) GetPrimaryKey(tablename string) (string, error) { - columns, err := md.GetColumns(tablename) - if err != nil { - return "", err - } - if len(columns) == 0 { - return "", errorx.NewBiz("[%s] 表不存在", tablename) - } - - for _, v := range columns { - if v.IsPrimaryKey { - return v.ColumnName, nil - } - } - - return columns[0].ColumnName, nil -} - -func (md *MssqlDialect) getTableIndexWithPK(tableName string) ([]dbi.Index, error) { - _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_INDEX_INFO_KEY), md.currentSchema(), tableName) - if err != nil { - return nil, err - } - indexs := make([]dbi.Index, 0) - for _, re := range res { - indexs = append(indexs, dbi.Index{ - IndexName: anyx.ConvString(re["indexName"]), - ColumnName: anyx.ConvString(re["columnName"]), - IndexType: anyx.ConvString(re["indexType"]), - IndexComment: anyx.ConvString(re["indexComment"]), - IsUnique: anyx.ConvInt(re["isUnique"]) == 1, - SeqInIndex: anyx.ConvInt(re["seqInIndex"]), - }) - } - // 把查询结果以索引名分组,多个索引字段以逗号连接 - 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 indexs, nil -} - -// 获取表索引信息 -func (md *MssqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) { - indexs, _ := md.getTableIndexWithPK(tableName) - result := make([]dbi.Index, 0) - // 过滤掉主键索引,主键索引名为PK__开头的 - for _, v := range indexs { - in := v.IndexName - if strings.HasPrefix(in, "PK__") { - continue - } - } - return result, nil -} - -func (md MssqlDialect) CopyTableDDL(tableName string, newTableName string) (string, error) { - if newTableName == "" { - newTableName = tableName - } - - // 根据列信息生成建表语句 - var builder strings.Builder - var commentBuilder strings.Builder - - // 查询表名和表注释, 设置表注释 - _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_DETAIL_KEY), md.currentSchema(), tableName) - if err != nil { - return "", err - } - tableComment := "" - if len(res) > 0 { - tableComment = anyx.ToString(res[0]["tableComment"]) - if tableComment != "" { - // 注释转义单引号 - tableComment = strings.ReplaceAll(tableComment, "'", "\\'") - commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s';\n", tableComment, md.currentSchema(), newTableName)) - } - } - - baseTable := fmt.Sprintf("%s.%s", md.dc.Info.Type.QuoteIdentifier(md.currentSchema()), md.dc.Info.Type.QuoteIdentifier(newTableName)) - - // 查询列信息 - columns, err := md.GetColumns(tableName) - if err != nil { - return "", err - } - - builder.WriteString(fmt.Sprintf("CREATE TABLE %s (\n", baseTable)) - pks := make([]string, 0) - for i, v := range columns { - nullAble := "NULL" - if v.Nullable == "NO" { - nullAble = "NOT NULL" - } - builder.WriteString(fmt.Sprintf("\t[%s] %s %s", v.ColumnName, v.ColumnType, nullAble)) - if v.IsIdentity { - builder.WriteString(" IDENTITY(1,11)") - } - if v.ColumnDefault != "" { - builder.WriteString(fmt.Sprintf(" DEFAULT %s", v.ColumnDefault)) - } - if v.IsPrimaryKey { - pks = append(pks, fmt.Sprintf("[%s]", v.ColumnName)) - } - if i < len(columns)-1 { - builder.WriteString(",") - } - builder.WriteString("\n") - } - // 设置主键 - if len(pks) > 0 { - builder.WriteString(fmt.Sprintf("\tCONSTRAINT PK_%s PRIMARY KEY ( %s )", newTableName, strings.Join(pks, ","))) - } - builder.WriteString("\n);\n") - - // 设置字段注释 - for _, v := range columns { - if v.ColumnComment != "" { - // 注释转义单引号 - v.ColumnComment = strings.ReplaceAll(v.ColumnComment, "'", "\\'") - commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s', N'COLUMN', N'%s';\n", v.ColumnComment, md.currentSchema(), newTableName, v.ColumnName)) - } - } - - // 设置索引 - indexs, err := md.GetTableIndex(tableName) - if err != nil { - return "", err - } - for _, v := range indexs { - builder.WriteString(fmt.Sprintf("\nCREATE NONCLUSTERED INDEX [%s] ON %s (%s);\n", v.IndexName, baseTable, v.ColumnName)) - // 设置索引注释 - if v.IndexComment != "" { - // 注释转义单引号 - v.IndexComment = strings.ReplaceAll(v.IndexComment, "'", "\\'") - commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s', N'INDEX', N'%s';\n", v.IndexComment, md.currentSchema(), newTableName, v.IndexName)) - } - } - return builder.String() + commentBuilder.String(), nil -} - -// 获取建表ddl -func (md *MssqlDialect) GetTableDDL(tableName string) (string, error) { - return md.CopyTableDDL(tableName, "") -} - -func (md *MssqlDialect) WalkTableRecord(tableName string, walkFn dbi.WalkQueryRowsFunc) error { - return md.dc.WalkQueryRows(context.Background(), fmt.Sprintf("SELECT * FROM %s", tableName), walkFn) -} - -func (md *MssqlDialect) GetSchemas() ([]string, error) { - _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_DB_SCHEMAS_KEY)) - if err != nil { - return nil, err - } - - schemas := make([]string, 0) - for _, re := range res { - schemas = append(schemas, anyx.ConvString(re["SCHEMA_NAME"])) - } - return schemas, nil +func (md *MssqlDialect) GetMetaData() dbi.MetaData { + return &MssqlMetaData{dc: md.dc} } // GetDbProgram 获取数据库程序模块,用于数据库备份与恢复 @@ -307,11 +35,12 @@ func (md *MssqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []stri } func (md *MssqlDialect) batchInsertSimple(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error) { - schema := md.currentSchema() + msMetadata := md.GetMetaData().(*MssqlMetaData) + schema := md.dc.Info.CurrentSchema() ignoreDupSql := "" if duplicateStrategy == dbi.DuplicateStrategyIgnore { // ALTER TABLE dbo.TEST ADD CONSTRAINT uniqueRows UNIQUE (ColA, ColB, ColC, ColD) WITH (IGNORE_DUP_KEY = ON) - indexs, _ := md.getTableIndexWithPK(tableName) + indexs, _ := msMetadata.getTableIndexWithPK(tableName) // 收集唯一索引涉及到的字段 uniqueColumns := make([]string, 0) for _, index := range indexs { @@ -365,7 +94,8 @@ func (md *MssqlDialect) batchInsertSimple(tx *sql.Tx, tableName string, columns } func (md *MssqlDialect) batchInsertMerge(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error) { - schema := md.currentSchema() + msMetadata := md.GetMetaData().(*MssqlMetaData) + schema := md.dc.Info.CurrentSchema() dbType := md.dc.Info.Type // 收集MERGE 语句的 ON 子句条件 @@ -374,7 +104,7 @@ func (md *MssqlDialect) batchInsertMerge(tx *sql.Tx, tableName string, columns [ // 查询取出自增列字段, merge update不能修改自增列 identityCols := make([]string, 0) - cols, err := md.GetColumns(tableName) + cols, err := msMetadata.GetColumns(tableName) for _, col := range cols { if col.IsIdentity { identityCols = append(identityCols, col.ColumnName) @@ -491,14 +221,14 @@ func (dc *DataConverter) ParseData(dbColumnValue any, dataType dbi.DataType) any } func (md *MssqlDialect) CopyTable(copy *dbi.DbCopyTable) error { - - schema := md.currentSchema() + msMetadata := md.GetMetaData().(*MssqlMetaData) + schema := md.dc.Info.CurrentSchema() // 生成新表名,为老表明+_copy_时间戳 newTableName := copy.TableName + "_copy_" + time.Now().Format("20060102150405") // 复制建表语句 - ddl, err := md.CopyTableDDL(copy.TableName, newTableName) + ddl, err := msMetadata.CopyTableDDL(copy.TableName, newTableName) if err != nil { return err } @@ -512,7 +242,7 @@ func (md *MssqlDialect) CopyTable(copy *dbi.DbCopyTable) error { if copy.CopyData { go func() { // 查询所有的列 - columns, err := md.GetColumns(copy.TableName) + columns, err := msMetadata.GetColumns(copy.TableName) if err != nil { logx.Warnf("复制表[%s]数据失败: %s", copy.TableName, err.Error()) return diff --git a/server/internal/db/dbm/mssql/metadata.go b/server/internal/db/dbm/mssql/metadata.go new file mode 100644 index 00000000..486b1128 --- /dev/null +++ b/server/internal/db/dbm/mssql/metadata.go @@ -0,0 +1,273 @@ +package mssql + +import ( + "fmt" + "mayfly-go/internal/db/dbm/dbi" + "mayfly-go/pkg/errorx" + "mayfly-go/pkg/utils/anyx" + "mayfly-go/pkg/utils/collx" + "strings" +) + +const ( + MSSQL_META_FILE = "metasql/mssql_meta.sql" + MSSQL_DBS_KEY = "MSSQL_DBS" + MSSQL_DB_SCHEMAS_KEY = "MSSQL_DB_SCHEMAS" + MSSQL_TABLE_INFO_KEY = "MSSQL_TABLE_INFO" + MSSQL_INDEX_INFO_KEY = "MSSQL_INDEX_INFO" + MSSQL_COLUMN_MA_KEY = "MSSQL_COLUMN_MA" + MSSQL_TABLE_DETAIL_KEY = "MSSQL_TABLE_DETAIL" + MSSQL_TABLE_INDEX_DDL_KEY = "MSSQL_TABLE_INDEX_DDL" +) + +type MssqlMetaData struct { + dc *dbi.DbConn +} + +func (md *MssqlMetaData) GetDbServer() (*dbi.DbServer, error) { + _, res, err := md.dc.Query("SELECT @@VERSION as version") + if err != nil { + return nil, err + } + ds := &dbi.DbServer{ + Version: anyx.ConvString(res[0]["version"]), + } + return ds, nil +} + +func (md *MssqlMetaData) GetDbNames() ([]string, error) { + _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_DBS_KEY)) + 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 (md *MssqlMetaData) GetTables() ([]dbi.Table, error) { + _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_INFO_KEY), md.dc.Info.CurrentSchema()) + + if err != nil { + return nil, err + } + + tables := make([]dbi.Table, 0) + for _, re := range res { + tables = append(tables, dbi.Table{ + TableName: anyx.ConvString(re["tableName"]), + 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 (md *MssqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) { + dbType := md.dc.Info.Type + tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { + return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) + }), ",") + + _, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_COLUMN_MA_KEY), tableName), md.dc.Info.CurrentSchema()) + if err != nil { + return nil, err + } + + columns := make([]dbi.Column, 0) + for _, re := range res { + columns = append(columns, dbi.Column{ + TableName: anyx.ToString(re["TABLE_NAME"]), + ColumnName: anyx.ToString(re["COLUMN_NAME"]), + ColumnType: anyx.ToString(re["COLUMN_TYPE"]), + ColumnComment: anyx.ToString(re["COLUMN_COMMENT"]), + Nullable: anyx.ToString(re["NULLABLE"]), + IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1, + IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1, + ColumnDefault: anyx.ToString(re["COLUMN_DEFAULT"]), + NumScale: anyx.ToString(re["NUM_SCALE"]), + }) + } + return columns, nil +} + +// 获取表主键字段名,不存在主键标识则默认第一个字段 +func (md *MssqlMetaData) GetPrimaryKey(tablename string) (string, error) { + columns, err := md.GetColumns(tablename) + if err != nil { + return "", err + } + if len(columns) == 0 { + return "", errorx.NewBiz("[%s] 表不存在", tablename) + } + + for _, v := range columns { + if v.IsPrimaryKey { + return v.ColumnName, nil + } + } + + return columns[0].ColumnName, nil +} + +func (md *MssqlMetaData) getTableIndexWithPK(tableName string) ([]dbi.Index, error) { + _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_INDEX_INFO_KEY), md.dc.Info.CurrentSchema(), tableName) + if err != nil { + return nil, err + } + indexs := make([]dbi.Index, 0) + for _, re := range res { + indexs = append(indexs, dbi.Index{ + IndexName: anyx.ConvString(re["indexName"]), + ColumnName: anyx.ConvString(re["columnName"]), + IndexType: anyx.ConvString(re["indexType"]), + IndexComment: anyx.ConvString(re["indexComment"]), + IsUnique: anyx.ConvInt(re["isUnique"]) == 1, + SeqInIndex: anyx.ConvInt(re["seqInIndex"]), + }) + } + // 把查询结果以索引名分组,多个索引字段以逗号连接 + 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 indexs, nil +} + +// 获取表索引信息 +func (md *MssqlMetaData) GetTableIndex(tableName string) ([]dbi.Index, error) { + indexs, _ := md.getTableIndexWithPK(tableName) + result := make([]dbi.Index, 0) + // 过滤掉主键索引,主键索引名为PK__开头的 + for _, v := range indexs { + in := v.IndexName + if strings.HasPrefix(in, "PK__") { + continue + } + } + return result, nil +} + +func (md MssqlMetaData) CopyTableDDL(tableName string, newTableName string) (string, error) { + if newTableName == "" { + newTableName = tableName + } + + // 根据列信息生成建表语句 + var builder strings.Builder + var commentBuilder strings.Builder + + // 查询表名和表注释, 设置表注释 + _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_DETAIL_KEY), md.dc.Info.CurrentSchema(), tableName) + if err != nil { + return "", err + } + tableComment := "" + if len(res) > 0 { + tableComment = anyx.ToString(res[0]["tableComment"]) + if tableComment != "" { + // 注释转义单引号 + tableComment = strings.ReplaceAll(tableComment, "'", "\\'") + commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s';\n", tableComment, md.dc.Info.CurrentSchema(), newTableName)) + } + } + + baseTable := fmt.Sprintf("%s.%s", md.dc.Info.Type.QuoteIdentifier(md.dc.Info.CurrentSchema()), md.dc.Info.Type.QuoteIdentifier(newTableName)) + + // 查询列信息 + columns, err := md.GetColumns(tableName) + if err != nil { + return "", err + } + + builder.WriteString(fmt.Sprintf("CREATE TABLE %s (\n", baseTable)) + pks := make([]string, 0) + for i, v := range columns { + nullAble := "NULL" + if v.Nullable == "NO" { + nullAble = "NOT NULL" + } + builder.WriteString(fmt.Sprintf("\t[%s] %s %s", v.ColumnName, v.ColumnType, nullAble)) + if v.IsIdentity { + builder.WriteString(" IDENTITY(1,11)") + } + if v.ColumnDefault != "" { + builder.WriteString(fmt.Sprintf(" DEFAULT %s", v.ColumnDefault)) + } + if v.IsPrimaryKey { + pks = append(pks, fmt.Sprintf("[%s]", v.ColumnName)) + } + if i < len(columns)-1 { + builder.WriteString(",") + } + builder.WriteString("\n") + } + // 设置主键 + if len(pks) > 0 { + builder.WriteString(fmt.Sprintf("\tCONSTRAINT PK_%s PRIMARY KEY ( %s )", newTableName, strings.Join(pks, ","))) + } + builder.WriteString("\n);\n") + + // 设置字段注释 + for _, v := range columns { + if v.ColumnComment != "" { + // 注释转义单引号 + v.ColumnComment = strings.ReplaceAll(v.ColumnComment, "'", "\\'") + commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s', N'COLUMN', N'%s';\n", v.ColumnComment, md.dc.Info.CurrentSchema(), newTableName, v.ColumnName)) + } + } + + // 设置索引 + indexs, err := md.GetTableIndex(tableName) + if err != nil { + return "", err + } + for _, v := range indexs { + builder.WriteString(fmt.Sprintf("\nCREATE NONCLUSTERED INDEX [%s] ON %s (%s);\n", v.IndexName, baseTable, v.ColumnName)) + // 设置索引注释 + if v.IndexComment != "" { + // 注释转义单引号 + v.IndexComment = strings.ReplaceAll(v.IndexComment, "'", "\\'") + commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s', N'INDEX', N'%s';\n", v.IndexComment, md.dc.Info.CurrentSchema(), newTableName, v.IndexName)) + } + } + return builder.String() + commentBuilder.String(), nil +} + +// 获取建表ddl +func (md *MssqlMetaData) GetTableDDL(tableName string) (string, error) { + return md.CopyTableDDL(tableName, "") +} + +func (md *MssqlMetaData) GetSchemas() ([]string, error) { + _, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_DB_SCHEMAS_KEY)) + if err != nil { + return nil, err + } + + schemas := make([]string, 0) + for _, re := range res { + schemas = append(schemas, anyx.ConvString(re["SCHEMA_NAME"])) + } + return schemas, nil +} diff --git a/server/internal/db/dbm/mysql/dialect.go b/server/internal/db/dbm/mysql/dialect.go index 8ceed6b8..53e38a44 100644 --- a/server/internal/db/dbm/mysql/dialect.go +++ b/server/internal/db/dbm/mysql/dialect.go @@ -4,167 +4,18 @@ import ( "database/sql" "fmt" "mayfly-go/internal/db/dbm/dbi" - "mayfly-go/pkg/errorx" "mayfly-go/pkg/utils/anyx" - "mayfly-go/pkg/utils/collx" "regexp" "strings" "time" ) -const ( - MYSQL_META_FILE = "metasql/mysql_meta.sql" - MYSQL_DBS = "MYSQL_DBS" - MYSQL_TABLE_INFO_KEY = "MYSQL_TABLE_INFO" - MYSQL_INDEX_INFO_KEY = "MYSQL_INDEX_INFO" - MYSQL_COLUMN_MA_KEY = "MYSQL_COLUMN_MA" -) - type MysqlDialect struct { dc *dbi.DbConn } -func (md *MysqlDialect) GetDbServer() (*dbi.DbServer, error) { - _, res, err := md.dc.Query("SELECT VERSION() version") - if err != nil { - return nil, err - } - ds := &dbi.DbServer{ - Version: anyx.ConvString(res[0]["version"]), - } - return ds, nil -} - -func (md *MysqlDialect) GetDbNames() ([]string, error) { - _, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_DBS)) - 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 (md *MysqlDialect) GetTables() ([]dbi.Table, error) { - _, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_INFO_KEY)) - if err != nil { - return nil, err - } - - tables := make([]dbi.Table, 0) - for _, re := range res { - tables = append(tables, dbi.Table{ - TableName: anyx.ConvString(re["tableName"]), - 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 (md *MysqlDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) { - dbType := md.dc.Info.Type - tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { - return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) - }), ",") - - _, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_COLUMN_MA_KEY), tableName)) - if err != nil { - return nil, err - } - - columns := make([]dbi.Column, 0) - for _, re := range res { - columns = append(columns, dbi.Column{ - TableName: anyx.ConvString(re["tableName"]), - ColumnName: anyx.ConvString(re["columnName"]), - ColumnType: anyx.ConvString(re["columnType"]), - ColumnComment: anyx.ConvString(re["columnComment"]), - Nullable: anyx.ConvString(re["nullable"]), - IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1, - IsIdentity: anyx.ConvInt(re["isIdentity"]) == 1, - ColumnDefault: anyx.ConvString(re["columnDefault"]), - NumScale: anyx.ConvString(re["numScale"]), - }) - } - return columns, nil -} - -// 获取表主键字段名,不存在主键标识则默认第一个字段 -func (md *MysqlDialect) GetPrimaryKey(tablename string) (string, error) { - columns, err := md.GetColumns(tablename) - if err != nil { - return "", err - } - if len(columns) == 0 { - return "", errorx.NewBiz("[%s] 表不存在", tablename) - } - - for _, v := range columns { - if v.IsPrimaryKey { - return v.ColumnName, nil - } - } - - return columns[0].ColumnName, nil -} - -// 获取表索引信息 -func (md *MysqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) { - _, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_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: anyx.ConvString(re["indexName"]), - ColumnName: anyx.ConvString(re["columnName"]), - IndexType: anyx.ConvString(re["indexType"]), - IndexComment: anyx.ConvString(re["indexComment"]), - IsUnique: anyx.ConvInt(re["isUnique"]) == 1, - SeqInIndex: anyx.ConvInt(re["seqInIndex"]), - }) - } - // 把查询结果以索引名分组,索引字段以逗号连接 - 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 (md *MysqlDialect) GetTableDDL(tableName string) (string, error) { - _, res, err := md.dc.Query(fmt.Sprintf("show create table `%s` ", tableName)) - if err != nil { - return "", err - } - return anyx.ConvString(res[0]["Create Table"]) + ";", nil -} - -func (md *MysqlDialect) GetSchemas() ([]string, error) { - return nil, nil +func (md *MysqlDialect) GetMetaData() dbi.MetaData { + return &MysqlMetaData{dc: md.dc} } // GetDbProgram 获取数据库程序模块,用于数据库备份与恢复 diff --git a/server/internal/db/dbm/mysql/metadata.go b/server/internal/db/dbm/mysql/metadata.go new file mode 100644 index 00000000..af02c4b7 --- /dev/null +++ b/server/internal/db/dbm/mysql/metadata.go @@ -0,0 +1,166 @@ +package mysql + +import ( + "errors" + "fmt" + "mayfly-go/internal/db/dbm/dbi" + "mayfly-go/pkg/errorx" + "mayfly-go/pkg/utils/anyx" + "mayfly-go/pkg/utils/collx" + "strings" +) + +const ( + MYSQL_META_FILE = "metasql/mysql_meta.sql" + MYSQL_DBS = "MYSQL_DBS" + MYSQL_TABLE_INFO_KEY = "MYSQL_TABLE_INFO" + MYSQL_INDEX_INFO_KEY = "MYSQL_INDEX_INFO" + MYSQL_COLUMN_MA_KEY = "MYSQL_COLUMN_MA" +) + +type MysqlMetaData struct { + dc *dbi.DbConn +} + +func (md *MysqlMetaData) GetDbServer() (*dbi.DbServer, error) { + _, res, err := md.dc.Query("SELECT VERSION() version") + if err != nil { + return nil, err + } + ds := &dbi.DbServer{ + Version: anyx.ConvString(res[0]["version"]), + } + return ds, nil +} + +func (md *MysqlMetaData) GetDbNames() ([]string, error) { + _, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_DBS)) + 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 (md *MysqlMetaData) GetTables() ([]dbi.Table, error) { + _, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_INFO_KEY)) + if err != nil { + return nil, err + } + + tables := make([]dbi.Table, 0) + for _, re := range res { + tables = append(tables, dbi.Table{ + TableName: anyx.ConvString(re["tableName"]), + 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 (md *MysqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) { + dbType := md.dc.Info.Type + tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { + return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) + }), ",") + + _, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_COLUMN_MA_KEY), tableName)) + if err != nil { + return nil, err + } + + columns := make([]dbi.Column, 0) + for _, re := range res { + columns = append(columns, dbi.Column{ + TableName: anyx.ConvString(re["tableName"]), + ColumnName: anyx.ConvString(re["columnName"]), + ColumnType: anyx.ConvString(re["columnType"]), + ColumnComment: anyx.ConvString(re["columnComment"]), + Nullable: anyx.ConvString(re["nullable"]), + IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1, + IsIdentity: anyx.ConvInt(re["isIdentity"]) == 1, + ColumnDefault: anyx.ConvString(re["columnDefault"]), + NumScale: anyx.ConvString(re["numScale"]), + }) + } + return columns, nil +} + +// 获取表主键字段名,不存在主键标识则默认第一个字段 +func (md *MysqlMetaData) GetPrimaryKey(tablename string) (string, error) { + columns, err := md.GetColumns(tablename) + if err != nil { + return "", err + } + if len(columns) == 0 { + return "", errorx.NewBiz("[%s] 表不存在", tablename) + } + + for _, v := range columns { + if v.IsPrimaryKey { + return v.ColumnName, nil + } + } + + return columns[0].ColumnName, nil +} + +// 获取表索引信息 +func (md *MysqlMetaData) GetTableIndex(tableName string) ([]dbi.Index, error) { + _, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_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: anyx.ConvString(re["indexName"]), + ColumnName: anyx.ConvString(re["columnName"]), + IndexType: anyx.ConvString(re["indexType"]), + IndexComment: anyx.ConvString(re["indexComment"]), + IsUnique: anyx.ConvInt(re["isUnique"]) == 1, + SeqInIndex: anyx.ConvInt(re["seqInIndex"]), + }) + } + // 把查询结果以索引名分组,索引字段以逗号连接 + 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 (md *MysqlMetaData) GetTableDDL(tableName string) (string, error) { + _, res, err := md.dc.Query(fmt.Sprintf("show create table `%s` ", tableName)) + if err != nil { + return "", err + } + return anyx.ConvString(res[0]["Create Table"]) + ";", nil +} + +func (md *MysqlMetaData) GetSchemas() ([]string, error) { + return nil, errors.New("不支持schema") +} diff --git a/server/internal/db/dbm/oracle/dialect.go b/server/internal/db/dbm/oracle/dialect.go index 7a42073c..fa2b84a4 100644 --- a/server/internal/db/dbm/oracle/dialect.go +++ b/server/internal/db/dbm/oracle/dialect.go @@ -4,7 +4,6 @@ import ( "database/sql" "fmt" "mayfly-go/internal/db/dbm/dbi" - "mayfly-go/pkg/errorx" "mayfly-go/pkg/logx" "mayfly-go/pkg/utils/anyx" "mayfly-go/pkg/utils/collx" @@ -15,255 +14,12 @@ import ( _ "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: anyx.ConvString(re["TABLE_NAME"]), - 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) { - dbType := od.dc.Info.Type - tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { - return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) - }), ",") - - // 如果表数量超过了1000,需要分批查询 - if len(tableNames) > 1000 { - columns := make([]dbi.Column, 0) - for i := 0; i < len(tableNames); i += 1000 { - end := i + 1000 - if end > len(tableNames) { - end = len(tableNames) - } - tables := tableNames[i:end] - cols, err := od.GetColumns(tables...) - if err != nil { - return nil, err - } - columns = append(columns, cols...) - } - return columns, nil - } - - _, 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: anyx.ConvString(re["TABLE_NAME"]), - ColumnName: anyx.ConvString(re["COLUMN_NAME"]), - ColumnType: anyx.ConvString(re["COLUMN_TYPE"]), - ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]), - Nullable: anyx.ConvString(re["NULLABLE"]), - IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1, - IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1, - 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.IsPrimaryKey { - 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: anyx.ConvString(re["INDEX_NAME"]), - ColumnName: anyx.ConvString(re["COLUMN_NAME"]), - IndexType: anyx.ConvString(re["INDEX_TYPE"]), - IndexComment: anyx.ConvString(re["INDEX_COMMENT"]), - IsUnique: anyx.ConvInt(re["IS_UNIQUE"]) == 1, - 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(anyx.ConvString(re["TABLE_DDL"])) - } - - // 表注释 - _, 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" + anyx.ConvString(re["INDEX_DEF"])) - } - - return builder.String(), nil -} - -// 获取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["USERNAME"])) - } - return schemaNames, nil +func (od *OracleDialect) GetMetaData() dbi.MetaData { + return &OracleMetaData{dc: od.dc} } // GetDbProgram 获取数据库程序模块,用于数据库备份与恢复 @@ -296,7 +52,7 @@ func (od *OracleDialect) batchInsertSimple(dbType dbi.DbType, tableName string, ignore := "" if duplicateStrategy == dbi.DuplicateStrategyIgnore { // 查出唯一索引涉及的字段 - indexs, _ := od.GetTableIndex(tableName) + indexs, _ := od.GetMetaData().GetTableIndex(tableName) if indexs != nil { arr := make([]string, 0) for _, index := range indexs { @@ -336,7 +92,7 @@ func (od *OracleDialect) batchInsertMergeSql(dbType dbi.DbType, tableName string uniqueCols := make([]string, 0) caseSqls := make([]string, 0) // 查询唯一索引涉及到的字段,并组装到match条件内 - indexs, _ := od.GetTableIndex(tableName) + indexs, _ := od.GetMetaData().GetTableIndex(tableName) if indexs != nil { for _, index := range indexs { if index.IsUnique { diff --git a/server/internal/db/dbm/oracle/metadata.go b/server/internal/db/dbm/oracle/metadata.go new file mode 100644 index 00000000..76c9d795 --- /dev/null +++ b/server/internal/db/dbm/oracle/metadata.go @@ -0,0 +1,261 @@ +package oracle + +import ( + "fmt" + "mayfly-go/internal/db/dbm/dbi" + "mayfly-go/pkg/errorx" + "mayfly-go/pkg/utils/anyx" + "mayfly-go/pkg/utils/collx" + "strings" +) + +// ---------------------------------- 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 OracleMetaData struct { + dc *dbi.DbConn +} + +func (od *OracleMetaData) 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 *OracleMetaData) 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 *OracleMetaData) 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: anyx.ConvString(re["TABLE_NAME"]), + 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 *OracleMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) { + dbType := od.dc.Info.Type + tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { + return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) + }), ",") + + // 如果表数量超过了1000,需要分批查询 + if len(tableNames) > 1000 { + columns := make([]dbi.Column, 0) + for i := 0; i < len(tableNames); i += 1000 { + end := i + 1000 + if end > len(tableNames) { + end = len(tableNames) + } + tables := tableNames[i:end] + cols, err := od.GetColumns(tables...) + if err != nil { + return nil, err + } + columns = append(columns, cols...) + } + return columns, nil + } + + _, 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: anyx.ConvString(re["TABLE_NAME"]), + ColumnName: anyx.ConvString(re["COLUMN_NAME"]), + ColumnType: anyx.ConvString(re["COLUMN_TYPE"]), + ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]), + Nullable: anyx.ConvString(re["NULLABLE"]), + IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1, + IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1, + ColumnDefault: defaultVal, + NumScale: anyx.ConvString(re["NUM_SCALE"]), + }) + } + return columns, nil +} + +func (od *OracleMetaData) 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.IsPrimaryKey { + return v.ColumnName, nil + } + } + + return columns[0].ColumnName, nil +} + +// 获取表索引信息 +func (od *OracleMetaData) 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: anyx.ConvString(re["INDEX_NAME"]), + ColumnName: anyx.ConvString(re["COLUMN_NAME"]), + IndexType: anyx.ConvString(re["INDEX_TYPE"]), + IndexComment: anyx.ConvString(re["INDEX_COMMENT"]), + IsUnique: anyx.ConvInt(re["IS_UNIQUE"]) == 1, + 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 *OracleMetaData) 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(anyx.ConvString(re["TABLE_DDL"])) + } + + // 表注释 + _, 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" + anyx.ConvString(re["INDEX_DEF"])) + } + + return builder.String(), nil +} + +// 获取DM当前连接的库可访问的schemaNames +func (od *OracleMetaData) 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["USERNAME"])) + } + return schemaNames, nil +} diff --git a/server/internal/db/dbm/postgres/dialect.go b/server/internal/db/dbm/postgres/dialect.go index 2a03a68f..a58e696d 100644 --- a/server/internal/db/dbm/postgres/dialect.go +++ b/server/internal/db/dbm/postgres/dialect.go @@ -4,7 +4,6 @@ import ( "database/sql" "fmt" "mayfly-go/internal/db/dbm/dbi" - "mayfly-go/pkg/errorx" "mayfly-go/pkg/utils/anyx" "mayfly-go/pkg/utils/collx" "regexp" @@ -12,176 +11,12 @@ import ( "time" ) -const ( - PGSQL_META_FILE = "metasql/pgsql_meta.sql" - PGSQL_DB_SCHEMAS = "PGSQL_DB_SCHEMAS" - PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO" - PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO" - PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA" - PGSQL_TABLE_DDL_KEY = "PGSQL_TABLE_DDL_FUNC" -) - type PgsqlDialect struct { dc *dbi.DbConn } -func (pd *PgsqlDialect) GetDbServer() (*dbi.DbServer, error) { - _, res, err := pd.dc.Query("SELECT version() as server_version") - if err != nil { - return nil, err - } - ds := &dbi.DbServer{ - Version: anyx.ConvString(res[0]["server_version"]), - } - return ds, nil -} - -func (pd *PgsqlDialect) GetDbNames() ([]string, error) { - _, res, err := pd.dc.Query("SELECT datname AS dbname FROM pg_database WHERE datistemplate = false AND has_database_privilege(datname, 'CONNECT')") - 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 (pd *PgsqlDialect) GetTables() ([]dbi.Table, error) { - _, res, err := pd.dc.Query(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_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["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 (pd *PgsqlDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) { - dbType := pd.dc.Info.Type - tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { - return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) - }), ",") - - _, res, err := pd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_COLUMN_MA_KEY), tableName)) - if err != nil { - return nil, err - } - - columns := make([]dbi.Column, 0) - for _, re := range res { - columns = append(columns, dbi.Column{ - TableName: anyx.ConvString(re["tableName"]), - ColumnName: anyx.ConvString(re["columnName"]), - ColumnType: anyx.ConvString(re["columnType"]), - ColumnComment: anyx.ConvString(re["columnComment"]), - Nullable: anyx.ConvString(re["nullable"]), - IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1, - IsIdentity: anyx.ConvInt(re["isIdentity"]) == 1, - ColumnDefault: anyx.ConvString(re["columnDefault"]), - NumScale: anyx.ConvString(re["numScale"]), - }) - } - return columns, nil -} - -func (pd *PgsqlDialect) GetPrimaryKey(tablename string) (string, error) { - columns, err := pd.GetColumns(tablename) - if err != nil { - return "", err - } - if len(columns) == 0 { - return "", errorx.NewBiz("[%s] 表不存在", tablename) - } - for _, v := range columns { - if v.IsPrimaryKey { - return v.ColumnName, nil - } - } - - return columns[0].ColumnName, nil -} - -// 获取表索引信息 -func (pd *PgsqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) { - _, res, err := pd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_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: anyx.ConvString(re["indexName"]), - ColumnName: anyx.ConvString(re["columnName"]), - IndexType: anyx.ConvString(re["IndexType"]), - IndexComment: anyx.ConvString(re["indexComment"]), - IsUnique: anyx.ConvInt(re["isUnique"]) == 1, - SeqInIndex: anyx.ConvInt(re["seqInIndex"]), - }) - } - // 把查询结果以索引名分组,索引字段以逗号连接 - 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 (pd *PgsqlDialect) GetTableDDL(tableName string) (string, error) { - _, err := pd.dc.Exec(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_DDL_KEY)) - if err != nil { - return "", err - } - - ddlSql := fmt.Sprintf("select showcreatetable('%s','%s') as sql", pd.currentSchema(), tableName) - _, res, err := pd.dc.Query(ddlSql) - if err != nil { - return "", err - } - - return res[0]["sql"].(string), nil -} - -// 获取pgsql当前连接的库可访问的schemaNames -func (pd *PgsqlDialect) GetSchemas() ([]string, error) { - sql := dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_DB_SCHEMAS) - _, res, err := pd.dc.Query(sql) - if err != nil { - return nil, err - } - schemaNames := make([]string, 0) - for _, re := range res { - schemaNames = append(schemaNames, anyx.ConvString(re["schemaName"])) - } - return schemaNames, nil +func (pd *PgsqlDialect) GetMetaData() dbi.MetaData { + return &PgsqlMetaData{dc: pd.dc} } // GetDbProgram 获取数据库程序模块,用于数据库备份与恢复 @@ -261,7 +96,7 @@ func (pd PgsqlDialect) gaussOnDuplicateStrategySql(duplicateStrategy int, tableN // 查出表里的唯一键涉及的字段 var uniqueColumns []string - indexs, err := pd.GetTableIndex(tableName) + indexs, err := pd.GetMetaData().GetTableIndex(tableName) if err == nil { for _, index := range indexs { if index.IsUnique { diff --git a/server/internal/db/dbm/postgres/metadata.go b/server/internal/db/dbm/postgres/metadata.go new file mode 100644 index 00000000..e986eae0 --- /dev/null +++ b/server/internal/db/dbm/postgres/metadata.go @@ -0,0 +1,182 @@ +package postgres + +import ( + "fmt" + "mayfly-go/internal/db/dbm/dbi" + "mayfly-go/pkg/errorx" + "mayfly-go/pkg/utils/anyx" + "mayfly-go/pkg/utils/collx" + "strings" +) + +const ( + PGSQL_META_FILE = "metasql/pgsql_meta.sql" + PGSQL_DB_SCHEMAS = "PGSQL_DB_SCHEMAS" + PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO" + PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO" + PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA" + PGSQL_TABLE_DDL_KEY = "PGSQL_TABLE_DDL_FUNC" +) + +type PgsqlMetaData struct { + dc *dbi.DbConn +} + +func (pd *PgsqlMetaData) GetDbServer() (*dbi.DbServer, error) { + _, res, err := pd.dc.Query("SELECT version() as server_version") + if err != nil { + return nil, err + } + ds := &dbi.DbServer{ + Version: anyx.ConvString(res[0]["server_version"]), + } + return ds, nil +} + +func (pd *PgsqlMetaData) GetDbNames() ([]string, error) { + _, res, err := pd.dc.Query("SELECT datname AS dbname FROM pg_database WHERE datistemplate = false AND has_database_privilege(datname, 'CONNECT')") + 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 (pd *PgsqlMetaData) GetTables() ([]dbi.Table, error) { + _, res, err := pd.dc.Query(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_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["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 (pd *PgsqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) { + dbType := pd.dc.Info.Type + tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string { + return fmt.Sprintf("'%s'", dbType.RemoveQuote(val)) + }), ",") + + _, res, err := pd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_COLUMN_MA_KEY), tableName)) + if err != nil { + return nil, err + } + + columns := make([]dbi.Column, 0) + for _, re := range res { + columns = append(columns, dbi.Column{ + TableName: anyx.ConvString(re["tableName"]), + ColumnName: anyx.ConvString(re["columnName"]), + ColumnType: anyx.ConvString(re["columnType"]), + ColumnComment: anyx.ConvString(re["columnComment"]), + Nullable: anyx.ConvString(re["nullable"]), + IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1, + IsIdentity: anyx.ConvInt(re["isIdentity"]) == 1, + ColumnDefault: anyx.ConvString(re["columnDefault"]), + NumScale: anyx.ConvString(re["numScale"]), + }) + } + return columns, nil +} + +func (pd *PgsqlMetaData) GetPrimaryKey(tablename string) (string, error) { + columns, err := pd.GetColumns(tablename) + if err != nil { + return "", err + } + if len(columns) == 0 { + return "", errorx.NewBiz("[%s] 表不存在", tablename) + } + for _, v := range columns { + if v.IsPrimaryKey { + return v.ColumnName, nil + } + } + + return columns[0].ColumnName, nil +} + +// 获取表索引信息 +func (pd *PgsqlMetaData) GetTableIndex(tableName string) ([]dbi.Index, error) { + _, res, err := pd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_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: anyx.ConvString(re["indexName"]), + ColumnName: anyx.ConvString(re["columnName"]), + IndexType: anyx.ConvString(re["IndexType"]), + IndexComment: anyx.ConvString(re["indexComment"]), + IsUnique: anyx.ConvInt(re["isUnique"]) == 1, + SeqInIndex: anyx.ConvInt(re["seqInIndex"]), + }) + } + // 把查询结果以索引名分组,索引字段以逗号连接 + 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 (pd *PgsqlMetaData) GetTableDDL(tableName string) (string, error) { + _, err := pd.dc.Exec(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_DDL_KEY)) + if err != nil { + return "", err + } + + ddlSql := fmt.Sprintf("select showcreatetable('%s','%s') as sql", pd.dc.Info.CurrentSchema(), tableName) + _, res, err := pd.dc.Query(ddlSql) + if err != nil { + return "", err + } + + return res[0]["sql"].(string), nil +} + +// 获取pgsql当前连接的库可访问的schemaNames +func (pd *PgsqlMetaData) GetSchemas() ([]string, error) { + sql := dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_DB_SCHEMAS) + _, res, err := pd.dc.Query(sql) + if err != nil { + return nil, err + } + schemaNames := make([]string, 0) + for _, re := range res { + schemaNames = append(schemaNames, anyx.ConvString(re["schemaName"])) + } + return schemaNames, nil +} diff --git a/server/internal/db/dbm/sqlite/dialect.go b/server/internal/db/dbm/sqlite/dialect.go index b7da4a13..a7202fd6 100644 --- a/server/internal/db/dbm/sqlite/dialect.go +++ b/server/internal/db/dbm/sqlite/dialect.go @@ -2,181 +2,20 @@ package sqlite import ( "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: anyx.ConvString(re["tableName"]), - 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, "'", "") - } - columns = append(columns, dbi.Column{ - TableName: tableName, - ColumnName: anyx.ConvString(re["name"]), - ColumnType: strings.ToLower(anyx.ConvString(re["type"])), - ColumnComment: "", - Nullable: nullable, - IsPrimaryKey: anyx.ConvInt(re["pk"]) == 1, - IsIdentity: anyx.ConvInt(re["pk"]) == 1, - 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 anyx.ConvString(re["name"]), 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 := anyx.ConvString(re["indexSql"]) - isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX") - - indexs = append(indexs, dbi.Index{ - IndexName: anyx.ConvString(re["indexName"]), - ColumnName: extractIndexFields(indexSql), - IndexType: anyx.ConvString(re["indexType"]), - IndexComment: anyx.ConvString(re["indexComment"]), - IsUnique: isUnique, - 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 tbl_name=? order by type desc", tableName) - if err != nil { - return "", err - } - var builder strings.Builder - for _, re := range res { - builder.WriteString(anyx.ConvString(re["sql"]) + "; \n\n") - } - - return builder.String(), nil -} - -func (sd *SqliteDialect) GetSchemas() ([]string, error) { - return nil, nil +func (sd *SqliteDialect) GetMetaData() dbi.MetaData { + return &SqliteMetaData{dc: sd.dc} } // GetDbProgram 获取数据库程序模块,用于数据库备份与恢复 @@ -267,7 +106,7 @@ func (sd *SqliteDialect) CopyTable(copy *dbi.DbCopyTable) error { // 生成新表名,为老表明+_copy_时间戳 newTableName := tableName + "_copy_" + time.Now().Format("20060102150405") - ddl, err := sd.GetTableDDL(tableName) + ddl, err := sd.GetMetaData().GetTableDDL(tableName) if err != nil { return err } diff --git a/server/internal/db/dbm/sqlite/metadata.go b/server/internal/db/dbm/sqlite/metadata.go new file mode 100644 index 00000000..8805940e --- /dev/null +++ b/server/internal/db/dbm/sqlite/metadata.go @@ -0,0 +1,178 @@ +package sqlite + +import ( + "errors" + "fmt" + "mayfly-go/internal/db/dbm/dbi" + "mayfly-go/pkg/logx" + "mayfly-go/pkg/utils/anyx" + "regexp" + "strings" +) + +const ( + SQLITE_META_FILE = "metasql/sqlite_meta.sql" + SQLITE_TABLE_INFO_KEY = "SQLITE_TABLE_INFO" + SQLITE_INDEX_INFO_KEY = "SQLITE_INDEX_INFO" +) + +type SqliteMetaData struct { + dc *dbi.DbConn +} + +func (sd *SqliteMetaData) 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 *SqliteMetaData) 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 *SqliteMetaData) 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: anyx.ConvString(re["tableName"]), + 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 *SqliteMetaData) 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, "'", "") + } + columns = append(columns, dbi.Column{ + TableName: tableName, + ColumnName: anyx.ConvString(re["name"]), + ColumnType: strings.ToLower(anyx.ConvString(re["type"])), + ColumnComment: "", + Nullable: nullable, + IsPrimaryKey: anyx.ConvInt(re["pk"]) == 1, + IsIdentity: anyx.ConvInt(re["pk"]) == 1, + ColumnDefault: defaultValue, + NumScale: "0", + }) + } + } + return columns, nil +} + +func (sd *SqliteMetaData) 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 anyx.ConvString(re["name"]), 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 *SqliteMetaData) 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 := anyx.ConvString(re["indexSql"]) + isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX") + + indexs = append(indexs, dbi.Index{ + IndexName: anyx.ConvString(re["indexName"]), + ColumnName: extractIndexFields(indexSql), + IndexType: anyx.ConvString(re["indexType"]), + IndexComment: anyx.ConvString(re["indexComment"]), + IsUnique: isUnique, + SeqInIndex: 1, + }) + } + // 把查询结果以索引名分组,索引字段以逗号连接 + return indexs, nil +} + +// 获取建表ddl +func (sd *SqliteMetaData) GetTableDDL(tableName string) (string, error) { + _, res, err := sd.dc.Query("select sql from sqlite_master WHERE tbl_name=? order by type desc", tableName) + if err != nil { + return "", err + } + var builder strings.Builder + for _, re := range res { + builder.WriteString(anyx.ConvString(re["sql"]) + "; \n\n") + } + + return builder.String(), nil +} + +func (sd *SqliteMetaData) GetSchemas() ([]string, error) { + return nil, nil +} diff --git a/server/internal/flow/domain/entity/query.go b/server/internal/flow/domain/entity/query.go index c0c751f5..333d861b 100644 --- a/server/internal/flow/domain/entity/query.go +++ b/server/internal/flow/domain/entity/query.go @@ -5,7 +5,7 @@ type ProcinstQuery struct { ProcdefName string `json:"procdefName"` // 流程定义名称 BizType string `json:"bizType" form:"bizType"` // 业务类型 - BizKey string `json:"bizKey"` // 业务key + BizKey string `json:"bizKey" form:"bizKey"` // 业务key Status ProcinstStatus `json:"status" form:"status"` // 状态 CreatorId uint64 diff --git a/server/resources/data/mayfly-go.sqlite b/server/resources/data/mayfly-go.sqlite index 7dade145ab43e3d5bff98804b4b69ae5bb5c1e30..12da2ed0fc23d345f6224490e6bed708c2a0357d 100644 GIT binary patch delta 1025 zcmaizZAcSw9LMirZgV>CT;;IR=0&Kr$=&7yE(m-=qS*^INGis;o%wLvbURHG(Wv*rw&mMeL!1T4say40$ET7Okc1^*RL!@XBN%Y3Qm9I)WCGbKbok=D?pfxwr zl`147!`*Q$PM2zkSHoS7lonT1t+*@kVeed&Z|bnar(#w%w!M;bwemt0 zC+t{-9`pn!JmEPp^zgyTXfG6+dDgU9|BNHE?#Ro&$kgO^*IJ-II5hHgerPRlKkT`) z78qCy3~t*5r@MP2<4=OO?uKUjsjf?%yHhbzbu90SrnBn_jT8;h&Ui*DpQgH^afe4= z1l=>+p|e(7gI%kq^|?CQpri9OjKOH2jd{!;A;!t5kZYt1j7hdJ`szApYXsJ4+s@0!87H%K2o3GfuHHwFGo!X(MKFjdaQUS z%aJ^kq@X5vOD?K#m)FSDnVIO#3eo02f|iIw{v422iD!yw&lR9?9Ve3N9WH^=2wc0M z$zo`5Itxt)O*tr;2#J&ZtB9|>`@qF@oD^SsRiFbF2jOp#tvP$nWYQ`$8cOJB=3*|u z{vJ)sfNuJKJM5fr#o=sA==!{k$-@su= fCnf=w4g45rT0|LO+rpKYBHNep17AHcPLsa@tJ`%E delta 227 zcmZp8Akc6?V1l$@90LP`6A;6|{fRoptZ@u_$&4FQ7OfZh$-sS&GnuWFHI2E4;U{-D z!-C0z0^OV(Am!XnjQl{J@8*)tSsYv~6$b3$>gtT0`qRTcGRrgaPA~h&tT#De8}H`0 zjbcpA_jYf;x0_LUi6~GfI|~~F&qf|5?lR8P9Ix1_S=crU3VdMR&X&Sdz|PXh$ssVk zDx0Z;rBR1nU^`n5)4$JL%=|kU_;2zb<=+WZx|)Cc&U|L?1|fD9F$Vs2er3MJTxZzz TSmv>aEfy4D*)H~l`IR;RdA2|8 diff --git a/server/resources/script/sql/mayfly-go-sqlite.sql b/server/resources/script/sql/mayfly-go-sqlite.sql deleted file mode 100644 index c65f4225..00000000 --- a/server/resources/script/sql/mayfly-go-sqlite.sql +++ /dev/null @@ -1,1095 +0,0 @@ --- --- Text encoding used: UTF-8 --- -PRAGMA foreign_keys = off; -BEGIN TRANSACTION; - --- Table: t_auth_cert -CREATE TABLE IF NOT EXISTS "t_auth_cert" ( - "id" integer NOT NULL, - "name" text(32), - "auth_method" integer(4) NOT NULL, - "password" text(4200), - "passphrase" text(32), - "remark" text(255), - "create_time" datetime NOT NULL, - "creator" text(16) NOT NULL, - "creator_id" integer(20) NOT NULL, - "update_time" datetime NOT NULL, - "modifier" text(12) NOT NULL, - "modifier_id" integer(20) NOT NULL, - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db -CREATE TABLE IF NOT EXISTS "t_db" ( - "id" integer NOT NULL, - "code" text(32), - "name" text(32), - "database" text(1000), - "remark" text(125), - "instance_id" integer(20) NOT NULL, - "create_time" datetime, - "creator_id" integer(20), - "creator" text(32), - "update_time" datetime, - "modifier_id" integer(20), - "modifier" text(32), - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - "flow_procdef_key" text(64), - PRIMARY KEY ("id") -); - --- Table: t_db_backup -CREATE TABLE IF NOT EXISTS "t_db_backup" ( - "id" integer NOT NULL, - "name" text(32) NOT NULL, - "db_instance_id" integer(20) NOT NULL, - "db_name" text(64) NOT NULL, - "repeated" integer(1), - "interval" integer(20), - "max_save_days" integer(8) NOT NULL DEFAULT '0', - "start_time" datetime, - "enabled" integer(1), - "enabled_desc" text(64), - "last_status" integer(4), - "last_result" text(256), - "last_time" datetime, - "create_time" datetime, - "creator_id" integer(20), - "creator" text(32), - "update_time" datetime, - "modifier_id" integer(20), - "modifier" text(32), - "is_deleted" integer(1) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db_backup_history -CREATE TABLE IF NOT EXISTS "t_db_backup_history" ( - "id" integer NOT NULL, - "name" text(64) NOT NULL, - "db_backup_id" integer(20) NOT NULL, - "db_instance_id" integer(20) NOT NULL, - "db_name" text(64) NOT NULL, - "uuid" text(36) NOT NULL, - "binlog_file_name" text(32), - "binlog_sequence" integer(20), - "binlog_position" integer(20), - "create_time" datetime, - "is_deleted" integer(1) NOT NULL, - "delete_time" datetime, - "restoring" integer(1) NOT NULL DEFAULT '0', - "deleting" integer(1) NOT NULL DEFAULT '0', - PRIMARY KEY ("id") -); - --- Table: t_db_binlog -CREATE TABLE IF NOT EXISTS "t_db_binlog" ( - "id" integer NOT NULL, - "db_instance_id" integer(20) NOT NULL, - "last_status" integer(20), - "last_result" text(256), - "last_time" datetime, - "create_time" datetime, - "creator_id" integer(20), - "creator" text(32), - "update_time" datetime, - "modifier_id" integer(20), - "modifier" text(32), - "is_deleted" integer(1) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db_binlog_history -CREATE TABLE IF NOT EXISTS "t_db_binlog_history" ( - "id" integer NOT NULL, - "db_instance_id" integer(20) NOT NULL, - "file_name" text(32), - "file_size" integer(20), - "sequence" integer(20), - "first_event_time" datetime, - "last_event_time" datetime, - "create_time" datetime, - "is_deleted" integer(4) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db_data_sync_log -CREATE TABLE IF NOT EXISTS "t_db_data_sync_log" ( - "id" integer NOT NULL, - "task_id" integer(20) NOT NULL, - "create_time" datetime NOT NULL, - "data_sql_full" text NOT NULL, - "res_num" integer(11), - "err_text" text, - "status" integer(4) NOT NULL, - "is_deleted" integer(1) NOT NULL, - PRIMARY KEY ("id") -); - --- Table: t_db_data_sync_task -CREATE TABLE IF NOT EXISTS "t_db_data_sync_task" ( - "id" integer NOT NULL, - "creator_id" integer(20) NOT NULL, - "creator" text(100) NOT NULL, - "create_time" datetime NOT NULL, - "update_time" datetime NOT NULL, - "modifier" text(100) NOT NULL, - "modifier_id" integer(20) NOT NULL, - "task_name" text(500) NOT NULL, - "task_cron" text(50) NOT NULL, - "src_db_id" integer(20) NOT NULL, - "src_db_name" text(100), - "src_tag_path" text(200), - "target_db_id" integer(20) NOT NULL, - "target_db_name" text(100), - "target_tag_path" text(200), - "target_table_name" text(100), - "data_sql" text NOT NULL, - "page_size" integer(11) NOT NULL, - "upd_field" text(100) NOT NULL, - "upd_field_val" text(100), - "id_rule" integer(2) NOT NULL, - "pk_field" text(100), - "field_map" text, - "is_deleted" integer(8), - "delete_time" datetime, - "status" integer(1) NOT NULL, - "recent_state" integer(1) NOT NULL, - "task_key" text(100), - "running_state" integer(1), - "duplicate_strategy" integer(1), - PRIMARY KEY ("id") -); - --- Table: t_db_instance -CREATE TABLE IF NOT EXISTS "t_db_instance" ( - "id" integer NOT NULL, - "name" text(32), - "host" text(100) NOT NULL, - "port" integer(8) NOT NULL, - "sid" text(255) NOT NULL, - "username" text(255) NOT NULL, - "password" text(255), - "type" text(20) NOT NULL, - "params" text(125), - "network" text(20), - "ssh_tunnel_machine_id" integer(20), - "remark" text(125), - "create_time" datetime, - "creator_id" integer(20), - "creator" text(32), - "update_time" datetime, - "modifier_id" integer(20), - "modifier" text(32), - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db_restore -CREATE TABLE IF NOT EXISTS "t_db_restore" ( - "id" integer NOT NULL, - "db_instance_id" integer(20) NOT NULL, - "db_name" text(64) NOT NULL, - "repeated" integer(1), - "interval" integer(20), - "start_time" datetime, - "enabled" integer(1), - "enabled_desc" text(64), - "last_status" integer(4), - "last_result" text(256), - "last_time" datetime, - "point_in_time" datetime, - "db_backup_id" integer(20), - "db_backup_history_id" integer(20), - "db_backup_history_name" text(64), - "create_time" datetime, - "creator_id" integer(20), - "creator" text(32), - "update_time" datetime, - "modifier_id" integer(20), - "modifier" text(32), - "is_deleted" integer(1) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db_restore_history -CREATE TABLE IF NOT EXISTS "t_db_restore_history" ( - "id" integer NOT NULL, - "db_restore_id" integer(20) NOT NULL, - "create_time" datetime, - "is_deleted" integer(4) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db_sql -CREATE TABLE IF NOT EXISTS "t_db_sql" ( - "id" integer NOT NULL, - "db_id" integer(20) NOT NULL, - "db" text(125) NOT NULL, - "name" text(60), - "sql" text, - "type" integer(8) NOT NULL, - "creator_id" integer(20) NOT NULL, - "creator" text(32), - "create_time" datetime NOT NULL, - "update_time" datetime NOT NULL, - "modifier_id" integer(20), - "modifier" text(255), - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_db_sql_exec -CREATE TABLE IF NOT EXISTS "t_db_sql_exec" ( - "id" integer NOT NULL, - "db_id" integer(20) NOT NULL, - "db" text(128) NOT NULL, - "table" text(128) NOT NULL, - "type" text(255) NOT NULL, - "sql" text(5000) NOT NULL, - "old_value" text(5000), - "remark" text(128), - "create_time" datetime NOT NULL, - "creator" text(36) NOT NULL, - "creator_id" integer(20) NOT NULL, - "update_time" datetime NOT NULL, - "modifier" text(36) NOT NULL, - "modifier_id" integer(20) NOT NULL, - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - "status" integer(8), - "flow_biz_key" text(128), - "res" text(1000), - PRIMARY KEY ("id") -); - --- Table: t_machine -CREATE TABLE IF NOT EXISTS "t_machine" ( - "id" integer NOT NULL, - "code" text(32), - "name" text(32), - "ip" text(50) NOT NULL, - "port" integer(12) NOT NULL, - "username" text(12) NOT NULL, - "auth_method" integer(2), - "password" text(100), - "auth_cert_id" integer(20), - "ssh_tunnel_machine_id" integer(20), - "enable_recorder" integer(2), - "status" integer(2) NOT NULL, - "remark" text(255), - "need_monitor" integer(2), - "create_time" datetime NOT NULL, - "creator" text(16), - "creator_id" integer(32), - "update_time" datetime NOT NULL, - "modifier" text(12), - "modifier_id" integer(32), - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_machine_cron_job -CREATE TABLE IF NOT EXISTS "t_machine_cron_job" ( - "id" integer NOT NULL, - "key" text(32) NOT NULL, - "name" text(255) NOT NULL, - "cron" text(255) NOT NULL, - "script" text, - "remark" text(255), - "status" integer(4), - "save_exec_res_type" integer(4), - "last_exec_time" datetime, - "creator_id" integer(20), - "creator" text(32), - "modifier_id" integer(20), - "modifier" text(255), - "create_time" datetime, - "update_time" datetime, - "is_deleted" integer(4) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_machine_cron_job_exec -CREATE TABLE IF NOT EXISTS "t_machine_cron_job_exec" ( - "id" integer NOT NULL, - "cron_job_id" integer(20), - "machine_id" integer(20), - "status" integer(4), - "res" text(1000), - "exec_time" datetime, - "is_deleted" integer(4) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_machine_cron_job_relate -CREATE TABLE IF NOT EXISTS "t_machine_cron_job_relate" ( - "id" integer NOT NULL, - "cron_job_id" integer(20), - "machine_id" integer(20), - "creator_id" integer(20), - "creator" text(32), - "create_time" datetime, - "is_deleted" integer(4) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_machine_file -CREATE TABLE IF NOT EXISTS "t_machine_file" ( - "id" integer NOT NULL, - "machine_id" integer(20) NOT NULL, - "name" text(45) NOT NULL, - "path" text(45) NOT NULL, - "type" text(45) NOT NULL, - "creator_id" integer(20), - "creator" text(45), - "modifier_id" integer(20), - "modifier" text(45), - "create_time" datetime NOT NULL, - "update_time" datetime, - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_machine_monitor -CREATE TABLE IF NOT EXISTS "t_machine_monitor" ( - "id" integer NOT NULL, - "machine_id" integer(20) NOT NULL, - "cpu_rate" real(255,2), - "mem_rate" real(255,2), - "sys_load" text(32), - "create_time" datetime NOT NULL, - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); - --- Table: t_machine_script -CREATE TABLE IF NOT EXISTS "t_machine_script" ( - "id" integer NOT NULL, - "name" text(255) NOT NULL, - "machine_id" integer(64) NOT NULL, - "script" text, - "params" text(512), - "description" text(255), - "type" integer(8), - "creator_id" integer(20), - "creator" text(32), - "modifier_id" integer(20), - "modifier" text(255), - "create_time" datetime, - "update_time" datetime, - "is_deleted" integer(8) NOT NULL, - "delete_time" datetime, - PRIMARY KEY ("id") -); -INSERT INTO t_machine_script (id, name, machine_id, script, params, description, type, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES (1, 'sys_info', 9999999, '# 获取系统cpu信息 -function get_cpu_info() { - Physical_CPUs=$(grep "physical id" /proc/cpuinfo | sort | uniq | wc -l) - Virt_CPUs=$(grep "processor" /proc/cpuinfo | wc -l) - CPU_Kernels=$(grep "cores" /proc/cpuinfo | uniq | awk -F '': '' ''{print $2}'') - CPU_Type=$(grep "model name" /proc/cpuinfo | awk -F '': '' ''{print $2}'' | sort | uniq) - CPU_Arch=$(uname -m) - echo -e ''\n-------------------------- CPU信息 --------------------------'' - cat <