| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963 |
- package v8
- import (
- "context"
- "database/sql"
- "dbview/service/internal/common/databases/meta"
- "encoding/json"
- "fmt"
- "reflect"
- "sort"
- "strings"
- "time"
- )
- // GetRootObjects 获取MySQL实例下的所有数据库(根对象)
- // connInfo 仅用于错误提示/上下文,不用于连接建立(连接由外部 db 实例提供)
- func (q *MySQLDriver) GetRootObjects(ctx context.Context, connID string, connInfo string, page, pageSize int, fetch bool) ([]meta.GenericObject, int64, error) {
- db := q.db
- // 2. 查询总数(分页用)
- var total int64
- if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA").Scan(&total); err != nil {
- return nil, 0, fmt.Errorf("查询数据库实例下的 schema 总数失败(INFORMATION_SCHEMA.SCHEMATA):%w", err)
- }
- // 3. 分页查询库列表
- offset := (page - 1) * pageSize
- // 某些 MySQL 版本或驱动对在 LIMIT/OFFSET 中使用占位符兼容性不好,改为直接插入整数常量(page,pageSize 为内部控制,非外部注入)
- query := fmt.Sprintf(
- "SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA LIMIT %d OFFSET %d",
- pageSize, offset)
- // 如果不需要取具体数据(fetch==false),只返回总数和 root 的 type 信息(前端可用来决定后续请求)
- if !fetch {
- // root 层的类型通常为 database
- // 为类型占位对象添加 Attrs,包含前端可用的请求模板(占位符形式);Description 已移到结构体字段
- types := []meta.GenericObject{{
- ID: fmt.Sprintf("%s.type-database", connID),
- Name: "database",
- Description: "数据库(root)类型,占位用于导航;前端可据此请求数据库下的子类型/条目",
- Type: "database",
- ParentID: connInfo,
- DBType: "mysql",
- Attrs: map[string]string{},
- }}
- return types, total, nil
- }
- rows, err := db.QueryContext(ctx, query)
- if err != nil {
- return nil, 0, fmt.Errorf("分页查询 schema 列表失败(conn=%s,page=%d,pageSize=%d, sql=%s):%w", connInfo, page, pageSize, query, err)
- }
- defer rows.Close()
- // 4. 映射为GenericObject(根对象类型为"database")
- var objects []meta.GenericObject
- for rows.Next() {
- var dbName sql.NullString
- var charset sql.NullString
- if err := rows.Scan(&dbName, &charset); err != nil {
- // 忽略该行并继续
- continue
- }
- objects = append(objects, meta.GenericObject{
- ID: fmt.Sprintf("%s.db-%s", connID, dbName.String), // ID格式:connID.db-库名
- Name: dbName.String,
- Type: "database", // 根对象类型为数据库
- ParentID: connInfo, // 父对象为实例(connInfo)
- DBType: "mysql",
- Attrs: map[string]string{
- "charset": charset.String, // MySQL库特有属性:默认字符集
- "createTime": "",
- },
- Children: []meta.GenericObject{}, // 子对象暂不加载
- })
- }
- if err := rows.Err(); err != nil {
- return nil, 0, fmt.Errorf("遍历 schema 结果集出错:%w", err)
- }
- return objects, total, nil
- }
- // GetChildObjects 获取父对象下的子对象(使用结构化路径定位父对象)
- // path: 从根到父对象的完整路径(见 meta.ObjectPath),最后一项为父对象自身
- // childType: 可选,若指定仅返回该子类型(例如 "column"、"index")
- func (q *MySQLDriver) GetChildObjects(ctx context.Context, path meta.ObjectPath, childType string, page, pageSize int, fetch bool) ([]meta.GenericObject, int64, error) {
- db := q.db
- // path 必须至少包含父对象自身
- if len(path) == 0 {
- return nil, 0, fmt.Errorf("empty path")
- }
- parentEntry := path[len(path)-1]
- // parentEntry.Name 在库级场景下为库名(TABLE_SCHEMA)
- dbName := parentEntry.Name
- // 解析筛选类型(默认 table),优先使用 childType 参数
- objectType := strings.ToLower(childType)
- if objectType == "" {
- objectType = "table"
- }
- // 当不需要拉取具体数据时,返回可用类型清单(前端据此发起具体类型的请求)
- if !fetch {
- // 目前不再支持传入表级 parent 来列出表级子类型(请使用 GetObjectDetails 获取表的列/索引等信息)
- // 为每种类型占位添加 Attrs;Description 已移到结构体字段
- types := []meta.GenericObject{
- {ID: fmt.Sprintf("%s.type-table", dbName), Name: "table", Description: "库级的表类型占位;前端可据此请求 /database/metadata/children 获取具体表列表", Type: "table", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
- {ID: fmt.Sprintf("%s.type-view", dbName), Name: "view", Description: "视图(view)类型占位;前端可据此请求 /database/metadata/children?type=view", Type: "view", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
- {ID: fmt.Sprintf("%s.type-index", dbName), Name: "index", Description: "索引(index)类型占位;可用于列出库级索引或提示按表查看索引", Type: "index", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
- {ID: fmt.Sprintf("%s.type-procedure", dbName), Name: "procedure", Description: "存储过程(procedure)类型占位;前端可据此请求对应类型列表", Type: "procedure", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
- {ID: fmt.Sprintf("%s.type-trigger", dbName), Name: "trigger", Description: "触发器(trigger)类型占位;前端可据此请求对应触发器列表", Type: "trigger", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
- }
- return types, 0, nil
- }
- switch objectType {
- case "table":
- tableType := "BASE TABLE"
- objType := "table"
- // count
- var total int64
- if err := db.QueryRowContext(ctx, `
- SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
- WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
- `, dbName, tableType).Scan(&total); err != nil {
- return nil, 0, fmt.Errorf("查询库 %s 中 %s 数量失败:%w", dbName, tableType, err)
- }
- // page
- offset := (page - 1) * pageSize
- rows, err := db.QueryContext(ctx, `
- SELECT TABLE_NAME, ENGINE, CREATE_TIME
- FROM INFORMATION_SCHEMA.TABLES
- WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
- LIMIT ? OFFSET ?
- `, dbName, tableType, pageSize, offset)
- if err != nil {
- return nil, 0, fmt.Errorf("分页查询库 %s 的 %s 列表失败(page=%d,pageSize=%d):%w", dbName, tableType, page, pageSize, err)
- }
- defer rows.Close()
- var objects []meta.GenericObject
- for rows.Next() {
- var tName sql.NullString
- var engine sql.NullString
- var createTime sql.NullString
- if err := rows.Scan(&tName, &engine, &createTime); err != nil {
- continue
- }
- objects = append(objects, meta.GenericObject{
- ID: fmt.Sprintf("%s.table-%s", dbName, tName.String),
- Name: tName.String,
- Type: objType,
- ParentID: dbName,
- DBType: "mysql",
- Attrs: map[string]string{
- "engine": engine.String,
- "createTime": createTime.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return nil, 0, fmt.Errorf("遍历表结果集出错(%s.%s):%w", dbName, tableType, err)
- }
- return objects, total, nil
- case "view":
- tableType := "VIEW"
- objType := "view"
- // count
- var total int64
- if err := db.QueryRowContext(ctx, `
- SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
- WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
- `, dbName, tableType).Scan(&total); err != nil {
- return nil, 0, fmt.Errorf("查询库 %s 中 %s 数量失败:%w", dbName, tableType, err)
- }
- // page
- offset := (page - 1) * pageSize
- rows, err := db.QueryContext(ctx, `
- SELECT TABLE_NAME, NULL AS ENGINE, CREATE_TIME
- FROM INFORMATION_SCHEMA.TABLES
- WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
- LIMIT ? OFFSET ?
- `, dbName, tableType, pageSize, offset)
- if err != nil {
- return nil, 0, fmt.Errorf("分页查询库 %s 的 %s 列表失败(page=%d,pageSize=%d):%w", dbName, tableType, page, pageSize, err)
- }
- defer rows.Close()
- var objects []meta.GenericObject
- for rows.Next() {
- var tName sql.NullString
- var engine sql.NullString
- var createTime sql.NullString
- if err := rows.Scan(&tName, &engine, &createTime); err != nil {
- continue
- }
- objects = append(objects, meta.GenericObject{
- ID: fmt.Sprintf("%s.table-%s", dbName, tName.String),
- Name: tName.String,
- Type: objType,
- ParentID: dbName,
- DBType: "mysql",
- Attrs: map[string]string{
- "engine": engine.String,
- "createTime": createTime.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return nil, 0, fmt.Errorf("遍历视图结果集出错(%s.%s):%w", dbName, tableType, err)
- }
- return objects, total, nil
- case "index":
- // 仅支持库级索引列出(表级索引请通过 GetObjectDetails 读取表详情以获取索引)
- var total int64
- if err := db.QueryRowContext(ctx, `
- SELECT COUNT(DISTINCT CONCAT(TABLE_NAME,'::',INDEX_NAME))
- FROM INFORMATION_SCHEMA.STATISTICS
- WHERE TABLE_SCHEMA = ?
- `, dbName).Scan(&total); err != nil {
- return nil, 0, err
- }
- offset := (page - 1) * pageSize
- rows, err := db.QueryContext(ctx, `
- SELECT DISTINCT INDEX_NAME, TABLE_NAME, NON_UNIQUE
- FROM INFORMATION_SCHEMA.STATISTICS
- WHERE TABLE_SCHEMA = ?
- LIMIT ? OFFSET ?
- `, dbName, pageSize, offset)
- if err != nil {
- return nil, 0, err
- }
- defer rows.Close()
- var objects []meta.GenericObject
- for rows.Next() {
- var idxName sql.NullString
- var tName sql.NullString
- var nonUnique sql.NullInt64
- if err := rows.Scan(&idxName, &tName, &nonUnique); err != nil {
- continue
- }
- id := fmt.Sprintf("%s.index-%s-%s", dbName, tName.String, idxName.String)
- objects = append(objects, meta.GenericObject{
- ID: id,
- Name: idxName.String,
- Type: "index",
- ParentID: dbName,
- DBType: "mysql",
- Attrs: map[string]string{
- "table": tName.String,
- "nonUnique": fmt.Sprintf("%d", nonUnique.Int64),
- },
- })
- }
- if err := rows.Err(); err != nil {
- return nil, 0, fmt.Errorf("遍历索引结果集出错(库级,%s):%w", dbName, err)
- }
- return objects, total, nil
- case "procedure", "proc":
- var total int64
- if err := db.QueryRowContext(ctx, `
- SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES
- WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE'
- `, dbName).Scan(&total); err != nil {
- return nil, 0, fmt.Errorf("查询库 %s 存储过程数量失败:%w", dbName, err)
- }
- offset := (page - 1) * pageSize
- rows, err := db.QueryContext(ctx, `
- SELECT ROUTINE_NAME, ROUTINE_DEFINITION, CREATED
- FROM INFORMATION_SCHEMA.ROUTINES
- WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE'
- LIMIT ? OFFSET ?
- `, dbName, pageSize, offset)
- if err != nil {
- return nil, 0, fmt.Errorf("分页查询库 %s 存储过程列表失败(page=%d,pageSize=%d):%w", dbName, page, pageSize, err)
- }
- defer rows.Close()
- var objects []meta.GenericObject
- for rows.Next() {
- var rName sql.NullString
- var def sql.NullString
- var created sql.NullString
- if err := rows.Scan(&rName, &def, &created); err != nil {
- continue
- }
- id := fmt.Sprintf("%s.proc-%s", dbName, rName.String)
- objects = append(objects, meta.GenericObject{
- ID: id,
- Name: rName.String,
- Type: "procedure",
- ParentID: dbName,
- DBType: "mysql",
- Attrs: map[string]string{
- "definition": def.String,
- "created": created.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return nil, 0, fmt.Errorf("遍历存储过程结果出错(%s):%w", dbName, err)
- }
- return objects, total, nil
- case "trigger":
- // 列出库中的触发器(不再支持表级 parentName)
- var total int64
- if err := db.QueryRowContext(ctx, `
- SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS
- WHERE TRIGGER_SCHEMA = ?
- `, dbName).Scan(&total); err != nil {
- return nil, 0, fmt.Errorf("查询库 %s 触发器数量失败:%w", dbName, err)
- }
- offset := (page - 1) * pageSize
- rows, err := db.QueryContext(ctx, `
- SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT
- FROM INFORMATION_SCHEMA.TRIGGERS
- WHERE TRIGGER_SCHEMA = ?
- LIMIT ? OFFSET ?
- `, dbName, pageSize, offset)
- if err != nil {
- return nil, 0, fmt.Errorf("分页查询库 %s 触发器列表失败(page=%d,pageSize=%d):%w", dbName, page, pageSize, err)
- }
- defer rows.Close()
- var objects []meta.GenericObject
- for rows.Next() {
- var tName sql.NullString
- var event sql.NullString
- var objTable sql.NullString
- var timing sql.NullString
- var stmt sql.NullString
- if err := rows.Scan(&tName, &event, &objTable, &timing, &stmt); err != nil {
- continue
- }
- id := fmt.Sprintf("%s.trigger-%s", dbName, tName.String)
- objects = append(objects, meta.GenericObject{
- ID: id,
- Name: tName.String,
- Type: "trigger",
- ParentID: dbName,
- DBType: "mysql",
- Attrs: map[string]string{
- "event": event.String,
- "table": objTable.String,
- "timing": timing.String,
- "statement": stmt.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return nil, 0, fmt.Errorf("遍历触发器结果出错(%s):%w", dbName, err)
- }
- return objects, total, nil
- default:
- return nil, 0, fmt.Errorf("不支持的类型: %s", objectType)
- }
- }
- // GetObjectDetails 获取对象详情(含子对象,如表的字段、索引)
- // path: 从根到目标对象的完整路径(见 meta.ObjectPath),最后一项为目标对象
- // 注意:不再兼容旧的 dotted objectID 格式,调用方应传入结构化路径
- func (q *MySQLDriver) GetObjectDetails(ctx context.Context, path meta.ObjectPath, fetch bool) (meta.GenericObject, error) {
- if len(path) == 0 {
- return meta.GenericObject{}, fmt.Errorf("empty path")
- }
- last := path[len(path)-1]
- t := strings.ToLower(last.Type)
- switch t {
- case "table", "view":
- tableName := last.Name
- var dbName string
- if len(path) >= 2 {
- dbName = path[len(path)-2].Name
- } else {
- dbName = ""
- }
- // 构造表对象的 ID 与 parentID
- parentID := dbName
- objectID := fmt.Sprintf("%s.table-%s", parentID, tableName)
- // 获取表基本信息
- tableObj, err := q.getTableBaseInfo(ctx, dbName, tableName, parentID)
- if err != nil {
- return meta.GenericObject{}, fmt.Errorf("获取表 %s.%s 的基础信息失败:%w", dbName, tableName, err)
- }
- // 如果不需要拉取子对象细节(如列/索引),返回可用子类型清单(供前端按类型按需加载)
- if !fetch {
- var types []meta.GenericObject
- // columns
- types = append(types, meta.GenericObject{
- ID: fmt.Sprintf("%s.type-column", objectID),
- Name: "column",
- Description: "列(column)类型占位;调用同一对象详情接口并设置 fetch=true 可获取列列表",
- Type: "column",
- ParentID: objectID,
- DBType: "mysql",
- Attrs: map[string]string{},
- })
- // indexes
- types = append(types, meta.GenericObject{
- ID: fmt.Sprintf("%s.type-index", objectID),
- Name: "index",
- Description: "索引(index)类型占位;调用对象详情接口并设置 fetch=true 可获取索引信息",
- Type: "index",
- ParentID: objectID,
- DBType: "mysql",
- Attrs: map[string]string{},
- })
- // 将 types 作为 Children 返回(空的 Children 表示未展开,带 types 表示可按类型加载)
- tableObj.Children = types
- return tableObj, nil
- }
- // 查询字段(子对象)
- columns, err := q.getTableColumns(ctx, dbName, tableName, objectID)
- if err != nil {
- return meta.GenericObject{}, fmt.Errorf("获取表 %s.%s 列信息失败:%w", dbName, tableName, err)
- }
- tableObj.Children = append(tableObj.Children, columns...)
- // 查询索引(子对象)
- indexes, err := q.getTableIndexes(ctx, dbName, tableName, objectID)
- if err != nil {
- return meta.GenericObject{}, fmt.Errorf("获取表 %s.%s 索引信息失败:%w", dbName, tableName, err)
- }
- tableObj.Children = append(tableObj.Children, indexes...)
- return tableObj, nil
- default:
- return meta.GenericObject{}, fmt.Errorf("不支持的对象类型: %s", last.Type)
- }
- }
- // getTableBaseInfo 返回表的基础信息,用于构造 table 对象
- func (q *MySQLDriver) getTableBaseInfo(ctx context.Context, dbName, tableName, parentName string) (meta.GenericObject, error) {
- db := q.db
- var engine sql.NullString
- var tableType sql.NullString
- var createTime sql.NullString
- if err := db.QueryRowContext(ctx, `
- SELECT ENGINE, TABLE_TYPE, CREATE_TIME FROM INFORMATION_SCHEMA.TABLES
- WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
- `, dbName, tableName).Scan(&engine, &tableType, &createTime); err != nil {
- return meta.GenericObject{}, fmt.Errorf("查询表 %s.%s 的基础信息失败:%w", dbName, tableName, err)
- }
- objType := "table"
- if strings.EqualFold(tableType.String, "VIEW") {
- objType = "view"
- }
- parentID := parentName
- objectID := fmt.Sprintf("%s.table-%s", parentName, tableName)
- return meta.GenericObject{
- ID: objectID,
- Name: tableName,
- Type: objType,
- ParentID: parentID,
- DBType: "mysql",
- Attrs: map[string]string{
- "engine": engine.String,
- "createTime": createTime.String,
- },
- Children: []meta.GenericObject{},
- }, nil
- }
- // getTableColumns 返回表的列信息(作为子对象)
- func (q *MySQLDriver) getTableColumns(ctx context.Context, dbName, tableName, objectID string) ([]meta.GenericObject, error) {
- db := q.db
- rows, err := db.QueryContext(ctx, `
- SELECT COLUMN_NAME, COLUMN_TYPE, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, CHARACTER_MAXIMUM_LENGTH, ORDINAL_POSITION
- FROM INFORMATION_SCHEMA.COLUMNS
- WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
- ORDER BY ORDINAL_POSITION
- `, dbName, tableName)
- if err != nil {
- return nil, fmt.Errorf("查询表 %s.%s 的列信息失败:%w", dbName, tableName, err)
- }
- defer rows.Close()
- var cols []meta.GenericObject
- for rows.Next() {
- var colName sql.NullString
- var columnType sql.NullString
- var dataType sql.NullString
- var isNullable sql.NullString
- var colDefault sql.NullString
- var charMax sql.NullInt64
- var ord sql.NullInt64
- if err := rows.Scan(&colName, &columnType, &dataType, &isNullable, &colDefault, &charMax, &ord); err != nil {
- continue
- }
- id := fmt.Sprintf("%s.column-%s", objectID, colName.String)
- cols = append(cols, meta.GenericObject{
- ID: id,
- Name: colName.String,
- Type: "column",
- ParentID: objectID,
- DBType: "mysql",
- Attrs: map[string]string{
- "columnType": columnType.String,
- "dataType": dataType.String,
- "nullable": isNullable.String,
- "default": colDefault.String,
- "charMax": fmt.Sprintf("%d", charMax.Int64),
- "position": fmt.Sprintf("%d", ord.Int64),
- },
- })
- }
- if err := rows.Err(); err != nil {
- return nil, fmt.Errorf("遍历列结果集出错(%s.%s):%w", dbName, tableName, err)
- }
- return cols, nil
- }
- // getTableIndexes 返回表的索引信息(作为子对象)
- func (q *MySQLDriver) getTableIndexes(ctx context.Context, dbName, tableName, objectID string) ([]meta.GenericObject, error) {
- db := q.db
- rows, err := db.QueryContext(ctx, `
- SELECT INDEX_NAME, NON_UNIQUE, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS columns
- FROM INFORMATION_SCHEMA.STATISTICS
- WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
- GROUP BY INDEX_NAME, NON_UNIQUE
- `, dbName, tableName)
- if err != nil {
- return nil, fmt.Errorf("查询表 %s.%s 的索引信息失败:%w", dbName, tableName, err)
- }
- defer rows.Close()
- var idxs []meta.GenericObject
- for rows.Next() {
- var idxName sql.NullString
- var nonUnique sql.NullInt64
- var cols sql.NullString
- if err := rows.Scan(&idxName, &nonUnique, &cols); err != nil {
- continue
- }
- id := fmt.Sprintf("%s.index-%s", objectID, idxName.String)
- idxs = append(idxs, meta.GenericObject{
- ID: id,
- Name: idxName.String,
- Type: "index",
- ParentID: objectID,
- DBType: "mysql",
- Attrs: map[string]string{
- "columns": cols.String,
- "nonUnique": fmt.Sprintf("%d", nonUnique.Int64),
- },
- })
- }
- if err := rows.Err(); err != nil {
- return nil, fmt.Errorf("遍历索引结果集出错(%s.%s):%w", dbName, tableName, err)
- }
- return idxs, nil
- }
- // DeleteRootObjects:根据前端传入的 rootName(支持通配符 * 或 SQL %)、typeName(如 database/table/view)
- // 返回匹配到的对象列表与总数(注:默认不直接执行 DROP,仅列出匹配项)
- func (q *MySQLDriver) DeleteRootObjects(ctx context.Context, req meta.ObjectOperationRequest) (meta.ObjectOperationResponse, error) {
- var resp meta.ObjectOperationResponse
- db := q.db
- rootName := req.Object.Name
- typeName := req.Object.Type
- t := strings.ToLower(typeName)
- // 处理通配符
- pattern := rootName
- if strings.Contains(pattern, "*") {
- pattern = strings.ReplaceAll(pattern, "*", "%")
- }
- useLike := strings.Contains(pattern, "%") || strings.Contains(pattern, "_")
- switch t {
- case "database", "schema":
- var rows *sql.Rows
- var err error
- if pattern == "" {
- rows, err = db.QueryContext(ctx, `SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA`)
- } else if useLike {
- rows, err = db.QueryContext(ctx, `SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME LIKE ?`, pattern)
- } else {
- rows, err = db.QueryContext(ctx, `SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`, pattern)
- }
- if err != nil {
- return resp, fmt.Errorf("查询 schema 列表以供删除匹配失败:%w", err)
- }
- defer rows.Close()
- var objs []meta.GenericObject
- var total int64
- for rows.Next() {
- var name sql.NullString
- var charset sql.NullString
- if err := rows.Scan(&name, &charset); err != nil {
- continue
- }
- total++
- objs = append(objs, meta.GenericObject{
- ID: fmt.Sprintf("db-%s", name.String),
- Name: name.String,
- Type: "database",
- ParentID: "",
- DBType: "mysql",
- Attrs: map[string]string{
- "charset": charset.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return resp, fmt.Errorf("遍历 schema 结果出错:%w", err)
- }
- resp.Affected = total
- resp.ObjectID = ""
- // 将匹配对象放入 Options 供前端显示(不能放在 Object 因为可能是多个)
- respMap := map[string]interface{}{"matches": objs}
- if respMapBytes, err := json.Marshal(respMap); err == nil {
- // 当仅生成 SQL(不执行)时,把匹配对象的 JSON 放到 Sql 字段供前端查看;
- // 若 Execute==true,则我们会尝试执行对应的 DROP 语句并在 Sql 中也返回执行的语句列表。
- resp.Sql = string(respMapBytes)
- }
- // 如果请求不要求执行,直接返回列出匹配项
- if !req.Execute {
- return resp, nil
- }
- // Execute==true: 执行删除操作(注意:对 DDL 的事务语义依赖于具体数据库;MySQL 的 DROP 在多数情况下不可回滚)
- tx, err := db.BeginTx(ctx, nil)
- if err != nil {
- return resp, fmt.Errorf("开始事务失败:%w", err)
- }
- var execCount int64
- var execSQLs []string
- for _, o := range objs {
- // 对不同类型生成对应的 DROP 语句
- switch o.Type {
- case "database":
- sqlStr := fmt.Sprintf("DROP DATABASE `%s`", o.Name)
- if _, err := tx.ExecContext(ctx, sqlStr); err != nil {
- _ = tx.Rollback()
- return resp, fmt.Errorf("执行 SQL 失败:%s, err: %w", sqlStr, err)
- }
- execSQLs = append(execSQLs, sqlStr)
- execCount++
- default:
- // 其他 root 类型目前不支持直接删除,记录并继续
- }
- }
- if err := tx.Commit(); err != nil {
- _ = tx.Rollback()
- return resp, fmt.Errorf("提交事务失败:%w", err)
- }
- resp.Affected = execCount
- if b, err := json.Marshal(execSQLs); err == nil {
- resp.Sql = string(b)
- }
- return resp, nil
- case "table", "view":
- tableType := "BASE TABLE"
- if t == "view" {
- tableType = "VIEW"
- }
- var rows *sql.Rows
- var err error
- if pattern == "" {
- rows, err = db.QueryContext(ctx, `SELECT TABLE_SCHEMA, TABLE_NAME, NULL AS ENGINE, CREATE_TIME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ?`, tableType)
- } else if useLike {
- rows, err = db.QueryContext(ctx, `SELECT TABLE_SCHEMA, TABLE_NAME, NULL AS ENGINE, CREATE_TIME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ? AND TABLE_NAME LIKE ?`, tableType, pattern)
- } else {
- rows, err = db.QueryContext(ctx, `SELECT TABLE_SCHEMA, TABLE_NAME, NULL AS ENGINE, CREATE_TIME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ? AND TABLE_NAME = ?`, tableType, pattern)
- }
- if err != nil {
- return resp, fmt.Errorf("查询表/视图以供删除匹配失败:%w", err)
- }
- defer rows.Close()
- var objs []meta.GenericObject
- var total int64
- for rows.Next() {
- var schema sql.NullString
- var tname sql.NullString
- var engine sql.NullString
- var ctime sql.NullString
- if err := rows.Scan(&schema, &tname, &engine, &ctime); err != nil {
- continue
- }
- total++
- pid := fmt.Sprintf("db-%s", schema.String)
- id := fmt.Sprintf("%s.table-%s", pid, tname.String)
- objs = append(objs, meta.GenericObject{
- ID: id,
- Name: tname.String,
- Type: t,
- ParentID: pid,
- DBType: "mysql",
- Attrs: map[string]string{
- "engine": engine.String,
- "createTime": ctime.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return resp, fmt.Errorf("遍历表/视图结果出错:%w", err)
- }
- resp.Affected = total
- matchMap := map[string]interface{}{"matches": objs}
- if b, err := json.Marshal(matchMap); err == nil {
- resp.Sql = string(b)
- }
- return resp, nil
- default:
- return resp, fmt.Errorf("不支持的 root 类型: %s", typeName)
- }
- }
- // DeleteChildObjects:根据 parentID(如 db-xxx 或 conn.db-xxx)和 filter(type/name)返回匹配的子对象列表与总数
- func (q *MySQLDriver) DeleteChildObjects(ctx context.Context, req meta.ObjectOperationRequest) (meta.ObjectOperationResponse, error) {
- var resp meta.ObjectOperationResponse
- parentID := req.Object.ParentID
- filter := map[string]string{}
- if req.Object.Name != "" {
- filter["name"] = req.Object.Name
- }
- if req.Object.Type != "" {
- filter["type"] = req.Object.Type
- }
- // reuse previous logic but adapt to return ObjectOperationResponse
- parts := strings.Split(parentID, ".")
- var dbPart string
- if len(parts) >= 2 {
- dbPart = parts[1]
- } else {
- dbPart = parentID
- }
- dbName := strings.TrimPrefix(dbPart, "db-")
- objectType := strings.ToLower(filter["type"])
- namePattern := filter["name"]
- if strings.Contains(namePattern, "*") {
- namePattern = strings.ReplaceAll(namePattern, "*", "%")
- }
- useLike := strings.Contains(namePattern, "%") || strings.Contains(namePattern, "_")
- db := q.db
- switch objectType {
- case "index":
- // 表级或库级索引
- var rows *sql.Rows
- var err error
- if namePattern == "" {
- rows, err = db.QueryContext(ctx, `SELECT INDEX_NAME, TABLE_NAME, NON_UNIQUE FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = ? GROUP BY INDEX_NAME, TABLE_NAME, NON_UNIQUE`, dbName)
- } else if useLike {
- rows, err = db.QueryContext(ctx, `SELECT INDEX_NAME, TABLE_NAME, NON_UNIQUE FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = ? AND INDEX_NAME LIKE ? GROUP BY INDEX_NAME, TABLE_NAME, NON_UNIQUE`, dbName, namePattern)
- } else {
- rows, err = db.QueryContext(ctx, `SELECT INDEX_NAME, TABLE_NAME, NON_UNIQUE FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = ? AND INDEX_NAME = ? GROUP BY INDEX_NAME, TABLE_NAME, NON_UNIQUE`, dbName, namePattern)
- }
- if err != nil {
- return resp, fmt.Errorf("查询索引以供删除匹配失败:%w", err)
- }
- defer rows.Close()
- var res []meta.GenericObject
- var total int64
- for rows.Next() {
- var idx sql.NullString
- var tname sql.NullString
- var nonUnique sql.NullInt64
- if err := rows.Scan(&idx, &tname, &nonUnique); err != nil {
- continue
- }
- total++
- id := fmt.Sprintf("%s.table-%s.index-%s", parentID, tname.String, idx.String)
- res = append(res, meta.GenericObject{
- ID: id,
- Name: idx.String,
- Type: "index",
- ParentID: fmt.Sprintf("%s.table-%s", parentID, tname.String),
- DBType: "mysql",
- Attrs: map[string]string{
- "table": tname.String,
- "nonUnique": fmt.Sprintf("%d", nonUnique.Int64),
- },
- })
- }
- if err := rows.Err(); err != nil {
- return resp, fmt.Errorf("遍历索引结果出错:%w", err)
- }
- resp.Affected = total
- m := map[string]interface{}{"matches": res}
- if b, err := json.Marshal(m); err == nil {
- resp.Sql = string(b)
- }
- return resp, nil
- case "procedure", "proc":
- var rows *sql.Rows
- var err error
- if namePattern == "" {
- rows, err = db.QueryContext(ctx, `SELECT ROUTINE_NAME, ROUTINE_DEFINITION, CREATED FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE'`, dbName)
- } else if useLike {
- rows, err = db.QueryContext(ctx, `SELECT ROUTINE_NAME, ROUTINE_DEFINITION, CREATED FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE' AND ROUTINE_NAME LIKE ?`, dbName, namePattern)
- } else {
- rows, err = db.QueryContext(ctx, `SELECT ROUTINE_NAME, ROUTINE_DEFINITION, CREATED FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE' AND ROUTINE_NAME = ?`, dbName, namePattern)
- }
- if err != nil {
- return resp, fmt.Errorf("查询存储过程以供删除匹配失败:%w", err)
- }
- defer rows.Close()
- var res []meta.GenericObject
- var total int64
- for rows.Next() {
- var rName sql.NullString
- var def sql.NullString
- var created sql.NullString
- if err := rows.Scan(&rName, &def, &created); err != nil {
- continue
- }
- total++
- id := fmt.Sprintf("%s.proc-%s", parentID, rName.String)
- res = append(res, meta.GenericObject{
- ID: id,
- Name: rName.String,
- Type: "procedure",
- ParentID: parentID,
- DBType: "mysql",
- Attrs: map[string]string{
- "definition": def.String,
- "created": created.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return resp, fmt.Errorf("遍历存储过程结果出错:%w", err)
- }
- resp.Affected = total
- m := map[string]interface{}{"matches": res}
- if b, err := json.Marshal(m); err == nil {
- resp.Sql = string(b)
- }
- return resp, nil
- case "trigger":
- var rows *sql.Rows
- var err error
- if namePattern == "" {
- rows, err = db.QueryContext(ctx, `SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = ?`, dbName)
- } else if useLike {
- rows, err = db.QueryContext(ctx, `SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = ? AND TRIGGER_NAME LIKE ?`, dbName, namePattern)
- } else {
- rows, err = db.QueryContext(ctx, `SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = ? AND TRIGGER_NAME = ?`, dbName, namePattern)
- }
- if err != nil {
- return resp, fmt.Errorf("查询触发器以供删除匹配失败:%w", err)
- }
- defer rows.Close()
- var res []meta.GenericObject
- var total int64
- for rows.Next() {
- var tName sql.NullString
- var event sql.NullString
- var objTable sql.NullString
- var timing sql.NullString
- var stmt sql.NullString
- if err := rows.Scan(&tName, &event, &objTable, &timing, &stmt); err != nil {
- continue
- }
- total++
- id := fmt.Sprintf("%s.trigger-%s", parentID, tName.String)
- res = append(res, meta.GenericObject{
- ID: id,
- Name: tName.String,
- Type: "trigger",
- ParentID: parentID,
- DBType: "mysql",
- Attrs: map[string]string{
- "event": event.String,
- "table": objTable.String,
- "timing": timing.String,
- "statement": stmt.String,
- },
- })
- }
- if err := rows.Err(); err != nil {
- return resp, fmt.Errorf("遍历触发器结果出错:%w", err)
- }
- resp.Affected = total
- m := map[string]interface{}{"matches": res}
- if b, err := json.Marshal(m); err == nil {
- resp.Sql = string(b)
- }
- return resp, nil
- default:
- return resp, fmt.Errorf("不支持的 child 类型: %s", objectType)
- }
- }
- // DescribeCreateTemplate 返回创建指定类型对象的表单模板(供前端渲染)
- // getDatabaseTemplateFields 返回数据库相关的模板字段
- func getDatabaseTemplateFields(isUpdate bool, currentValues map[string]string) []meta.TemplateField {
- fields := []meta.TemplateField{
- {Name: "databaseName", Label: "Database Name", Type: meta.FieldTypeString, Required: true},
- {Name: "charset", Label: "Character Set", Type: meta.FieldTypeEnum, Required: false, Default: "utf8mb4", EnumOptions: []string{"utf8mb4", "utf8", "latin1", "ascii", "ucs2", "utf16", "utf32", "big5", "gbk"}},
- {Name: "collation", Label: "Collation", Type: meta.FieldTypeString, Required: false},
- }
- if !isUpdate {
- // 创建模式:添加ifNotExists字段
- fields = append(fields, meta.TemplateField{Name: "ifNotExists", Label: "If Not Exists", Type: meta.FieldTypeBool, Required: false, Default: "false"})
- fields[0].Help = "要创建的数据库名称,不含前缀"
- } else {
- // 修改模式:设置当前值和可编辑性(currentValues 采用 map[string]string)
- if currentValues != nil {
- if dbName, ok := currentValues["databaseName"]; ok {
- fields[0].Current = dbName
- fields[0].Editable = &[]bool{false}[0] // 数据库名通常不可修改
- }
- if charset, ok := currentValues["charset"]; ok {
- fields[1].Current = charset
- }
- if collation, ok := currentValues["collation"]; ok {
- fields[2].Current = collation
- }
- }
- }
- return fields
- }
- // getTableTemplateFields 返回表相关的模板字段
- func getTableTemplateFields(isUpdate bool, currentValues map[string]string) []meta.TemplateField {
- fields := []meta.TemplateField{
- {Name: "tableName", Label: "Table Name", Type: meta.FieldTypeString, Required: true},
- {Name: "engine", Label: "Engine", Type: meta.FieldTypeEnum, Required: false, EnumOptions: []string{"InnoDB", "MyISAM"}, Default: "InnoDB"},
- {Name: "charset", Label: "Charset", Type: meta.FieldTypeEnum, Required: false, Default: "utf8mb4", EnumOptions: []string{"utf8mb4", "utf8", "latin1", "ascii", "ucs2", "utf16", "utf32", "big5", "gbk"}},
- {Name: "collation", Label: "Collation", Type: meta.FieldTypeString, Required: false},
- }
- if !isUpdate {
- // 创建模式:添加ifNotExists和columns字段
- fields = append(fields, meta.TemplateField{Name: "ifNotExists", Label: "If Not Exists", Type: meta.FieldTypeBool, Required: false, Default: "false"})
- fields = append(fields, meta.TemplateField{
- Name: "columns", Label: "Columns", Type: meta.FieldTypeList, Required: true, NestedFields: []meta.TemplateField{
- {Name: "name", Label: "Column Name", Type: meta.FieldTypeString, Required: true},
- {Name: "type", Label: "Data Type", Type: meta.FieldTypeString, Required: true},
- {Name: "nullable", Label: "Nullable", Type: meta.FieldTypeBool, Required: false, Default: "false"},
- {Name: "default", Label: "Default", Type: meta.FieldTypeString, Required: false},
- {Name: "autoIncrement", Label: "Auto Increment", Type: meta.FieldTypeBool, Required: false, Default: "false"},
- }})
- } else {
- // 修改模式:设置当前值和可编辑性(currentValues 采用 map[string]string)
- if currentValues != nil {
- if tableName, ok := currentValues["tableName"]; ok {
- fields[0].Current = tableName
- fields[0].Editable = &[]bool{false}[0] // 表名修改需要重命名
- }
- if engine, ok := currentValues["engine"]; ok {
- fields[1].Current = engine
- }
- if charset, ok := currentValues["charset"]; ok {
- fields[2].Current = charset
- }
- if collation, ok := currentValues["collation"]; ok {
- fields[3].Current = collation
- }
- }
- }
- return fields
- }
- // getIndexTemplateFields 返回索引相关的模板字段
- func getIndexTemplateFields(isUpdate bool, currentValues map[string]string) []meta.TemplateField {
- fields := []meta.TemplateField{
- {Name: "indexName", Label: "Index Name", Type: meta.FieldTypeString, Required: true},
- {Name: "columns", Label: "Columns (comma separated)", Type: meta.FieldTypeString, Required: true},
- {Name: "unique", Label: "Unique", Type: meta.FieldTypeBool, Required: false, Default: "false"},
- }
- if isUpdate && currentValues != nil {
- // 修改模式:设置当前值(currentValues 为 map[string]string)
- if indexName, ok := currentValues["indexName"]; ok {
- fields[0].Current = indexName
- }
- if columns, ok := currentValues["columns"]; ok {
- fields[1].Current = columns
- }
- if uniqueStr, ok := currentValues["unique"]; ok {
- fields[2].Current = uniqueStr
- }
- }
- return fields
- }
- // getCurrentDatabaseInfo 获取数据库的当前信息
- func (q *MySQLDriver) getCurrentDatabaseInfo(ctx context.Context, dbName string) (map[string]string, error) {
- var charset, collation sql.NullString
- query := "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?"
- err := q.db.QueryRowContext(ctx, query, dbName).Scan(&charset, &collation)
- if err != nil {
- return nil, fmt.Errorf("获取数据库信息失败: %w", err)
- }
- return map[string]string{
- "databaseName": dbName,
- "charset": charset.String,
- "collation": collation.String,
- }, nil
- }
- // getCurrentTableInfo 获取表的当前信息
- func (q *MySQLDriver) getCurrentTableInfo(ctx context.Context, dbName, tableName string) (map[string]string, error) {
- var engine, charset, collation sql.NullString
- query := "SELECT ENGINE, TABLE_COLLATION, SUBSTRING_INDEX(TABLE_COLLATION, '_', 1) AS TABLE_CHARSET FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?"
- err := q.db.QueryRowContext(ctx, query, dbName, tableName).Scan(&engine, &collation, &charset)
- if err != nil {
- return nil, fmt.Errorf("获取表信息失败: %w", err)
- }
- return map[string]string{
- "tableName": tableName,
- "engine": engine.String,
- "charset": charset.String,
- "collation": collation.String,
- }, nil
- }
- func (q *MySQLDriver) DescribeCreateTemplate(ctx context.Context, path meta.ObjectPath) (meta.ObjectTemplate, error) {
- // 从路径中提取对象类型和父名称
- var objectType, parentName string
- if len(path) > 0 {
- lastEntry := path[len(path)-1]
- objectType = strings.ToLower(lastEntry.Type)
- // 对于创建操作,最后一个元素是待创建的对象类型
- // 前面的元素构成父上下文
- if len(path) > 1 {
- parentEntry := path[len(path)-2] // 父对象是倒数第二个元素
- parentName = parentEntry.Name
- }
- } else {
- return meta.ObjectTemplate{}, fmt.Errorf("创建模板需要指定对象类型路径")
- }
- switch objectType {
- case "database":
- // 示例值均以字符串形式表示
- ex := map[string]string{"databaseName": "mydb", "charset": "utf8mb4", "ifNotExists": "true"}
- tpl := meta.ObjectTemplate{
- Operation: "create",
- ObjectType: "database",
- ParentHint: "",
- Fields: getDatabaseTemplateFields(false, nil),
- Example: ex,
- }
- return tpl, nil
- case "table":
- parentHint := "parentName should be the database name"
- if parentName != "" {
- parentHint = "database: " + parentName
- }
- // 将复杂的 columns 示例编码为 JSON 字符串以便前端展示/解析
- colsExample := []map[string]interface{}{{"name": "id", "type": "INT", "nullable": false}}
- colsB, _ := json.Marshal(colsExample)
- ex := map[string]string{"tableName": "users", "columns": string(colsB), "engine": "InnoDB"}
- tpl := meta.ObjectTemplate{
- Operation: "create",
- ObjectType: "table",
- ParentHint: parentHint,
- Fields: getTableTemplateFields(false, nil),
- Example: ex,
- }
- return tpl, nil
- case "index":
- parentHint := "parentName can be table name"
- if parentName != "" {
- parentHint = "table: " + parentName
- }
- ex := map[string]string{"indexName": "idx_users_email", "columns": "email", "unique": "true"}
- tpl := meta.ObjectTemplate{
- Operation: "create",
- ObjectType: "index",
- ParentHint: parentHint,
- Fields: getIndexTemplateFields(false, nil),
- Example: ex,
- }
- return tpl, nil
- default:
- return meta.ObjectTemplate{}, fmt.Errorf("不支持的 create 类型: %s", objectType)
- }
- }
- // GetMetadataInfo 返回数据库的元信息(关键字、字段类型、能力等)
- func (q *MySQLDriver) GetMetadataInfo(ctx context.Context) (meta.MetadataCapabilities, error) {
- var caps meta.MetadataCapabilities
- // 1) 尝试从 INFORMATION_SCHEMA.COLUMNS 获取字段类型
- rows, err := q.db.QueryContext(ctx, "SELECT DISTINCT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS")
- if err == nil {
- defer rows.Close()
- for rows.Next() {
- var dt sql.NullString
- if err := rows.Scan(&dt); err != nil {
- continue
- }
- if dt.Valid && dt.String != "" {
- caps.FieldTypes = append(caps.FieldTypes, strings.ToUpper(dt.String))
- }
- }
- } else {
- // 回退到一组常见类型
- caps.FieldTypes = []string{"INT", "BIGINT", "VARCHAR", "TEXT", "DATETIME", "TIMESTAMP", "DATE", "CHAR", "FLOAT", "DOUBLE", "DECIMAL", "BOOLEAN"}
- }
- // 2) 尝试从 mysql.help_keyword 获取关键字(部分 MySQL 安装提供)
- rows2, err2 := q.db.QueryContext(ctx, "SELECT DISTINCT word FROM mysql.help_keyword")
- if err2 == nil {
- defer rows2.Close()
- for rows2.Next() {
- var kw sql.NullString
- if err := rows2.Scan(&kw); err != nil {
- continue
- }
- if kw.Valid && kw.String != "" {
- caps.Keywords = append(caps.Keywords, kw.String)
- }
- }
- } else {
- // 3) 尝试 INFORMATION_SCHEMA.KEYWORDS(部分版本可用)
- rows3, err3 := q.db.QueryContext(ctx, "SELECT DISTINCT keyword FROM INFORMATION_SCHEMA.KEYWORDS")
- if err3 == nil {
- defer rows3.Close()
- for rows3.Next() {
- var kw sql.NullString
- if err := rows3.Scan(&kw); err != nil {
- continue
- }
- if kw.Valid && kw.String != "" {
- caps.Keywords = append(caps.Keywords, kw.String)
- }
- }
- }
- }
- // 4) 去重与排序
- if len(caps.FieldTypes) > 0 {
- m := map[string]struct{}{}
- for _, v := range caps.FieldTypes {
- m[v] = struct{}{}
- }
- caps.FieldTypes = caps.FieldTypes[:0]
- for k := range m {
- caps.FieldTypes = append(caps.FieldTypes, k)
- }
- sort.Strings(caps.FieldTypes)
- }
- if len(caps.Keywords) > 0 {
- m := map[string]struct{}{}
- for _, v := range caps.Keywords {
- m[v] = struct{}{}
- }
- caps.Keywords = caps.Keywords[:0]
- for k := range m {
- caps.Keywords = append(caps.Keywords, k)
- }
- sort.Strings(caps.Keywords)
- }
- // 5) 常见能力标记
- caps.Capabilities = map[string]bool{
- "supportsTransactions": true,
- "supportsDDLTransaction": false,
- }
- return caps, nil
- }
- // CreateObject 仅实现 preview(execute==false)路径:校验输入并生成 SQL,execute=true 尚未实现
- func (q *MySQLDriver) CreateObject(ctx context.Context, req meta.CreateObjectRequest) (meta.CreateObjectResponse, error) {
- var resp meta.CreateObjectResponse
- t := strings.ToLower(req.ObjectType)
- // Basic validation
- if req.ObjectType == "" {
- return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: objectType")
- }
- switch t {
- case "database":
- props := req.Properties
- nameI, ok := props["databaseName"]
- if !ok {
- return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: databaseName")
- }
- name, _ := nameI.(string)
- if name == "" {
- return meta.CreateObjectResponse{}, fmt.Errorf("databaseName 不能为空")
- }
- if req.Execute {
- return meta.CreateObjectResponse{}, fmt.Errorf("execute 路径未实现(当前仅支持预览 execute=false)")
- }
- charset := "utf8mb4"
- if cs, ok := props["charset"].(string); ok && cs != "" {
- charset = cs
- }
- ifNot := false
- if v, ok := props["ifNotExists"].(bool); ok {
- ifNot = v
- }
- sql := "CREATE DATABASE"
- if ifNot {
- sql += " IF NOT EXISTS"
- }
- sql = fmt.Sprintf("%s `%s` DEFAULT CHARACTER SET = %s;", sql, name, charset)
- resp.GeneratedSQL = []string{sql}
- return resp, nil
- case "table":
- props := req.Properties
- tnameI, ok := props["tableName"]
- if !ok {
- return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: tableName")
- }
- tname, _ := tnameI.(string)
- if tname == "" {
- return meta.CreateObjectResponse{}, fmt.Errorf("tableName 不能为空")
- }
- colsI, ok := props["columns"]
- if !ok {
- return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: columns")
- }
- colsSlice, ok := colsI.([]interface{})
- if !ok {
- return meta.CreateObjectResponse{}, fmt.Errorf("columns 格式无效,期望为数组类型的列定义,例如 [{\"name\":\"id\",\"type\":\"INT\"}]")
- }
- if req.Execute {
- return meta.CreateObjectResponse{}, fmt.Errorf("execute 路径未实现(当前仅支持预览 execute=false)")
- }
- engine := "InnoDB"
- if e, ok := props["engine"].(string); ok && e != "" {
- engine = e
- }
- charset := "utf8mb4"
- if cs, ok := props["charset"].(string); ok && cs != "" {
- charset = cs
- }
- ifNot := false
- if v, ok := props["ifNotExists"].(bool); ok {
- ifNot = v
- }
- // build column definitions
- var colDefs []string
- for _, ci := range colsSlice {
- m, ok := ci.(map[string]interface{})
- if !ok {
- continue
- }
- cname, _ := m["name"].(string)
- ctype, _ := m["type"].(string)
- if cname == "" || ctype == "" {
- // skip invalid
- continue
- }
- nullable := true
- if n, ok := m["nullable"].(bool); ok {
- nullable = n
- }
- autoInc := false
- if a, ok := m["autoIncrement"].(bool); ok {
- autoInc = a
- }
- defStr := ""
- if d, ok := m["default"]; ok && d != nil {
- // simple formatting: quote strings, otherwise use fmt.Sprint
- switch v := d.(type) {
- case string:
- defStr = fmt.Sprintf(" DEFAULT '%s'", strings.ReplaceAll(v, "'", "\\'"))
- default:
- defStr = fmt.Sprintf(" DEFAULT %v", v)
- }
- }
- col := fmt.Sprintf("`%s` %s", cname, ctype)
- if !nullable {
- col += " NOT NULL"
- }
- if autoInc {
- col += " AUTO_INCREMENT"
- }
- col += defStr
- colDefs = append(colDefs, col)
- }
- if len(colDefs) == 0 {
- return meta.CreateObjectResponse{}, fmt.Errorf("未找到有效的列定义,请检查 properties.columns 字段,示例格式: [{\"name\":\"id\",\"type\":\"INT\"}]")
- }
- // build create statement (include parent/db if provided)
- createStmt := "CREATE TABLE"
- if ifNot {
- createStmt += " IF NOT EXISTS"
- }
- // If parent provided, include database qualifier
- if req.ParentName != "" {
- createStmt = fmt.Sprintf("%s `%s`.`%s` ( %s ) ENGINE=%s DEFAULT CHARSET=%s;", createStmt, req.ParentName, tname, strings.Join(colDefs, ", "), engine, charset)
- } else {
- createStmt = fmt.Sprintf("%s `%s` ( %s ) ENGINE=%s DEFAULT CHARSET=%s;", createStmt, tname, strings.Join(colDefs, ", "), engine, charset)
- }
- resp.GeneratedSQL = []string{createStmt}
- return resp, nil
- case "index":
- props := req.Properties
- iname, _ := props["indexName"].(string)
- cols, _ := props["columns"].(string)
- unique := false
- if u, ok := props["unique"].(bool); ok {
- unique = u
- }
- if iname == "" || cols == "" {
- return meta.CreateObjectResponse{}, fmt.Errorf("indexName 与 columns 为必填字段")
- }
- if req.Execute {
- return meta.CreateObjectResponse{}, fmt.Errorf("execute 路径未实现(当前仅支持预览 execute=false)")
- }
- uq := ""
- if unique {
- uq = "UNIQUE "
- }
- // parentName ideally should be table name
- tableRef := iname
- if req.ParentName != "" {
- tableRef = req.ParentName
- }
- stmt := fmt.Sprintf("CREATE %sINDEX `%s` ON `%s` (%s);", uq, iname, tableRef, cols)
- resp.GeneratedSQL = []string{stmt}
- return resp, nil
- default:
- return meta.CreateObjectResponse{}, fmt.Errorf("不支持的 create 类型: %s", req.ObjectType)
- }
- }
- // QueryData 执行数据查询(实现 DataReader 接口)
- func (q *MySQLDriver) QueryData(ctx context.Context, path meta.ObjectPath, req meta.DataQueryRequest) (meta.QueryResult, error) {
- db := q.db
- // 解析路径:path 应为 [{"type":"database","name":"db"},{"type":"table","name":"table"}]
- if len(path) < 2 {
- return meta.QueryResult{}, fmt.Errorf("invalid path: need at least database and table")
- }
- dbName := path[0].Name
- tableName := path[1].Name
- // 切换到指定数据库(如果需要)
- if _, err := db.ExecContext(ctx, fmt.Sprintf("USE `%s`", dbName)); err != nil {
- return meta.QueryResult{}, fmt.Errorf("failed to switch to database %s: %w", dbName, err)
- }
- // 构建列信息
- var columns []meta.DataMeta
- var columnNames []string
- if len(req.Columns) == 0 {
- // 查询所有列
- query := fmt.Sprintf("SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' ORDER BY ORDINAL_POSITION", dbName, tableName)
- rows, err := db.QueryContext(ctx, query)
- if err != nil {
- return meta.QueryResult{}, fmt.Errorf("failed to query columns: %w", err)
- }
- defer rows.Close()
- for rows.Next() {
- var colName, dataType, nullable sql.NullString
- if err := rows.Scan(&colName, &dataType, &nullable); err != nil {
- continue
- }
- columns = append(columns, meta.DataMeta{
- Name: colName.String,
- Type: "column",
- DBType: dataType.String,
- Nullable: nullable.String == "YES",
- })
- columnNames = append(columnNames, colName.String)
- }
- } else {
- // 指定列
- columnNames = req.Columns
- for _, col := range req.Columns {
- columns = append(columns, meta.DataMeta{
- Name: col,
- Type: "column",
- })
- }
- }
- // 构建 SELECT 语句
- selectClause := "*"
- if len(columnNames) > 0 {
- selectClause = "`" + strings.Join(columnNames, "`, `") + "`"
- }
- sql := fmt.Sprintf("SELECT %s FROM `%s`", selectClause, tableName)
- // 添加 WHERE 子句
- if len(req.Filters) > 0 {
- var conditions []string
- for col, val := range req.Filters {
- conditions = append(conditions, fmt.Sprintf("`%s` = '%s'", col, val)) // 简单等值,注意 SQL 注入风险(生产环境需参数化)
- }
- sql += " WHERE " + strings.Join(conditions, " AND ")
- }
- // 添加 ORDER BY
- if len(req.Order) > 0 {
- var orders []string
- for _, o := range req.Order {
- dir := "ASC"
- if o.Desc {
- dir = "DESC"
- }
- orders = append(orders, fmt.Sprintf("`%s` %s", o.Column, dir))
- }
- sql += " ORDER BY " + strings.Join(orders, ", ")
- }
- // 添加 LIMIT
- if req.Limit > 0 {
- sql += fmt.Sprintf(" LIMIT %d", req.Limit)
- if req.Offset > 0 {
- sql += fmt.Sprintf(" OFFSET %d", req.Offset)
- }
- }
- // 如果 Fetch=false,仅返回 SQL,不执行查询,但计算总数
- if !req.Fetch {
- var total int64
- countSQL := fmt.Sprintf("SELECT COUNT(*) FROM `%s`", tableName)
- if len(req.Filters) > 0 {
- var conditions []string
- for col, val := range req.Filters {
- conditions = append(conditions, fmt.Sprintf("`%s` = '%s'", col, val))
- }
- countSQL += " WHERE " + strings.Join(conditions, " AND ")
- }
- if err := db.QueryRowContext(ctx, countSQL).Scan(&total); err != nil {
- return meta.QueryResult{}, fmt.Errorf("failed to count total: %w", err)
- }
- return meta.QueryResult{
- Columns: columns,
- Rows: []meta.DataMeta{},
- Total: &total,
- Returned: int(total),
- Truncated: false,
- Stats: map[string]string{"sql": sql},
- }, nil
- }
- // 执行查询
- rows, err := db.QueryContext(ctx, sql)
- if err != nil {
- return meta.QueryResult{}, fmt.Errorf("failed to execute query: %w", err)
- }
- defer rows.Close()
- // 获取列信息(如果未指定列)
- if len(req.Columns) == 0 {
- colTypes, err := rows.ColumnTypes()
- if err != nil {
- return meta.QueryResult{}, fmt.Errorf("failed to get column types: %w", err)
- }
- for i, ct := range colTypes {
- nullable, _ := ct.Nullable()
- columns[i].DBType = ct.DatabaseTypeName()
- columns[i].Nullable = nullable
- }
- }
- // 读取数据
- var resultRows []meta.DataMeta
- for rows.Next() {
- values := make([]interface{}, len(columns))
- valuePtrs := make([]interface{}, len(columns))
- for i := range values {
- valuePtrs[i] = &values[i]
- }
- if err := rows.Scan(valuePtrs...); err != nil {
- return meta.QueryResult{}, fmt.Errorf("failed to scan row: %w", err)
- }
- var children []meta.DataMeta
- for i, val := range values {
- // 安全检查:确保val是有效的interface{}
- if i >= len(columns) {
- continue // 跳过超出列数的字段
- }
- // 处理 []byte 类型,转为 string 以正确显示中文
- if val != nil {
- if b, ok := val.([]byte); ok {
- val = string(b)
- }
- }
- // 根据值类型设置 ValueType
- var valueType meta.FieldType
- if val == nil {
- valueType = meta.FieldTypeString // nil值当作字符串处理
- } else {
- // 根据值类型设置 ValueType
- switch reflect.TypeOf(val).Kind() {
- case reflect.String:
- valueType = meta.FieldTypeString
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- valueType = meta.FieldTypeInt
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- valueType = meta.FieldTypeInt
- case reflect.Float32, reflect.Float64:
- valueType = meta.FieldTypeNumber
- case reflect.Bool:
- valueType = meta.FieldTypeBool
- default:
- valueType = meta.FieldTypeString // 默认当作字符串
- }
- }
- children = append(children, meta.DataMeta{
- Name: columns[i].Name,
- Type: "value",
- Value: val, // 现在 val 是原生类型,但 []byte 已转为 string
- ValueType: valueType,
- })
- }
- resultRows = append(resultRows, meta.DataMeta{
- Name: getRowName(children), // 设置行 name 为第一个列的值(通常为主键或 ID)
- Type: "row",
- RowID: "",
- Children: children,
- })
- }
- // 计算总数(如果请求)
- var total *int64
- if req.IncludeTotal {
- countSQL := fmt.Sprintf("SELECT COUNT(*) FROM `%s`", tableName)
- if len(req.Filters) > 0 {
- var conditions []string
- for col, val := range req.Filters {
- conditions = append(conditions, fmt.Sprintf("`%s` = '%s'", col, val))
- }
- countSQL += " WHERE " + strings.Join(conditions, " AND ")
- }
- var t int64
- if err := db.QueryRowContext(ctx, countSQL).Scan(&t); err != nil {
- return meta.QueryResult{}, fmt.Errorf("failed to count total: %w", err)
- }
- total = &t
- }
- return meta.QueryResult{
- Columns: columns,
- Rows: resultRows,
- Total: total,
- Returned: len(resultRows),
- Truncated: req.Limit > 0 && len(resultRows) == req.Limit,
- Stats: map[string]string{"query": sql},
- }, nil
- }
- // ExecuteSQL 执行SQL语句并返回执行结果
- func (q *MySQLDriver) ExecuteSQL(ctx context.Context, path meta.ObjectPath, sql string, params []interface{}) (meta.ExecuteResult, error) {
- startTime := time.Now()
- result := meta.ExecuteResult{
- Success: false,
- ExecutionTime: 0,
- }
- // 清理和标准化SQL
- sql = strings.TrimSpace(sql)
- if sql == "" {
- result.ErrorMessage = "SQL语句不能为空:请提供有效的SQL查询或执行语句"
- return result, nil
- }
- // 分析SQL类型
- sqlType := q.analyzeSQLType(sql)
- db := q.db
- // 对于SELECT语句,使用Query执行
- if strings.HasPrefix(strings.ToUpper(sqlType), "SELECT") {
- rows, err := db.QueryContext(ctx, sql, params...)
- if err != nil {
- result.ErrorMessage = fmt.Sprintf("SELECT查询执行失败:SQL语法错误或数据库连接问题。原始错误:%v", err)
- result.ExecutionTime = time.Since(startTime).Milliseconds()
- return result, nil
- }
- defer rows.Close()
- // 获取列信息
- columns, err := rows.Columns()
- if err != nil {
- result.ErrorMessage = fmt.Sprintf("获取查询结果列信息失败:无法解析结果集结构。原始错误:%v", err)
- result.ExecutionTime = time.Since(startTime).Milliseconds()
- return result, nil
- }
- // 构建列元信息
- var columnMetas []meta.DataMeta
- columnTypes, err := rows.ColumnTypes()
- if err == nil {
- for i, col := range columns {
- dbType := ""
- if i < len(columnTypes) {
- dbType = columnTypes[i].DatabaseTypeName()
- }
- columnMetas = append(columnMetas, meta.DataMeta{
- Name: col,
- Type: "column",
- DBType: dbType,
- ValueType: "string", // 简化处理
- })
- }
- }
- // 读取数据
- var resultRows []meta.DataMeta
- rowCount := 0
- for rows.Next() && rowCount < 1000 { // 限制最大行数
- values := make([]interface{}, len(columns))
- valuePtrs := make([]interface{}, len(columns))
- for i := range values {
- valuePtrs[i] = &values[i]
- }
- if err := rows.Scan(valuePtrs...); err != nil {
- result.ErrorMessage = fmt.Sprintf("扫描查询结果行数据失败:数据类型不匹配或结果集损坏。原始错误:%v", err)
- result.ExecutionTime = time.Since(startTime).Milliseconds()
- return result, nil
- }
- // 构建行数据
- var rowValues []meta.DataMeta
- for i, val := range values {
- colName := ""
- if i < len(columns) {
- colName = columns[i]
- }
- // 转换值为字符串
- var strVal string
- if val != nil {
- strVal = fmt.Sprintf("%v", val)
- }
- rowValues = append(rowValues, meta.DataMeta{
- Name: colName,
- Type: "value",
- Value: strVal,
- })
- }
- resultRows = append(resultRows, meta.DataMeta{
- Type: "row",
- RowID: "",
- Children: rowValues,
- })
- rowCount++
- }
- // 构建查询结果
- queryResult := &meta.QueryResult{
- Columns: columnMetas,
- Rows: resultRows,
- Returned: len(resultRows),
- Truncated: rowCount >= 1000,
- }
- result.Success = true
- result.SQLType = sqlType
- result.Data = queryResult
- result.ExecutionTime = time.Since(startTime).Milliseconds()
- result.RowsReturned = len(resultRows)
- // 对于非SELECT语句,使用Exec执行
- execResult, err := db.ExecContext(ctx, sql, params...)
- if err != nil {
- result.ErrorMessage = fmt.Sprintf("%s语句执行失败:SQL语法错误、权限不足或数据库约束冲突。原始错误:%v", sqlType, err)
- result.ExecutionTime = time.Since(startTime).Milliseconds()
- return result, nil
- }
- // 获取影响行数
- affectedRows, _ := execResult.RowsAffected()
- result.AffectedRows = affectedRows
- // 获取最后插入ID(如果适用)
- if strings.HasPrefix(strings.ToUpper(sqlType), "INSERT") {
- lastInsertID, _ := execResult.LastInsertId()
- result.LastInsertID = lastInsertID
- }
- result.Success = true
- result.SQLType = sqlType
- result.ExecutionTime = time.Since(startTime).Milliseconds()
- }
- return result, nil
- }
- // analyzeSQLType 分析SQL语句类型
- func (q *MySQLDriver) analyzeSQLType(sql string) string {
- upperSQL := strings.ToUpper(strings.TrimSpace(sql))
- switch {
- case strings.HasPrefix(upperSQL, "SELECT"):
- return "SELECT"
- case strings.HasPrefix(upperSQL, "INSERT"):
- return "INSERT"
- case strings.HasPrefix(upperSQL, "UPDATE"):
- return "UPDATE"
- case strings.HasPrefix(upperSQL, "DELETE"):
- return "DELETE"
- case strings.HasPrefix(upperSQL, "CREATE"):
- if strings.Contains(upperSQL, "TABLE") {
- return "CREATE TABLE"
- } else if strings.Contains(upperSQL, "DATABASE") {
- return "CREATE DATABASE"
- } else if strings.Contains(upperSQL, "INDEX") {
- return "CREATE INDEX"
- }
- return "CREATE"
- case strings.HasPrefix(upperSQL, "DROP"):
- if strings.Contains(upperSQL, "TABLE") {
- return "DROP TABLE"
- } else if strings.Contains(upperSQL, "DATABASE") {
- return "DROP DATABASE"
- } else if strings.Contains(upperSQL, "INDEX") {
- return "DROP INDEX"
- }
- return "DROP"
- case strings.HasPrefix(upperSQL, "ALTER"):
- return "ALTER"
- case strings.HasPrefix(upperSQL, "TRUNCATE"):
- return "TRUNCATE"
- default:
- return "UNKNOWN"
- }
- }
- // getRowName 安全地获取行名,避免空指针引用
- func getRowName(children []meta.DataMeta) string {
- if len(children) == 0 {
- return ""
- }
- if children[0].Value == nil {
- return "<NULL>"
- }
- return fmt.Sprintf("%v", children[0].Value)
- }
- // DescribeUpdateTemplate 返回指定对象的修改模板,用于前端动态生成表单
- func (q *MySQLDriver) DescribeUpdateTemplate(ctx context.Context, path meta.ObjectPath) (meta.ObjectTemplate, error) {
- if len(path) == 0 {
- return meta.ObjectTemplate{}, fmt.Errorf("路径不能为空")
- }
- lastEntry := path[len(path)-1]
- objectType := strings.ToLower(lastEntry.Type)
- objectName := lastEntry.Name
- switch objectType {
- case "database":
- // 获取数据库的当前信息
- currentValues, err := q.getCurrentDatabaseInfo(ctx, objectName)
- if err != nil {
- return meta.ObjectTemplate{}, err
- }
- tpl := meta.ObjectTemplate{
- Operation: "update",
- ObjectType: "database",
- ParentHint: "",
- Fields: getDatabaseTemplateFields(true, currentValues),
- Current: currentValues,
- Example: map[string]string{"charset": "utf8mb4"},
- }
- return tpl, nil
- case "table":
- if len(path) < 2 {
- return meta.ObjectTemplate{}, fmt.Errorf("表路径需要包含数据库信息")
- }
- dbName := path[0].Name
- tableName := objectName
- // 获取表的当前信息
- currentValues, err := q.getCurrentTableInfo(ctx, dbName, tableName)
- if err != nil {
- return meta.ObjectTemplate{}, err
- }
- tpl := meta.ObjectTemplate{
- Operation: "update",
- ObjectType: "table",
- ParentHint: "database: " + dbName,
- Fields: getTableTemplateFields(true, currentValues),
- Current: currentValues,
- Example: map[string]string{"engine": "InnoDB", "charset": "utf8mb4"},
- }
- return tpl, nil
- default:
- return meta.ObjectTemplate{}, fmt.Errorf("不支持的 update 类型: %s", objectType)
- }
- }
- // UpdateObject 执行对象的修改操作
- func (q *MySQLDriver) UpdateObject(ctx context.Context, req meta.UpdateObjectRequest) (meta.UpdateObjectResponse, error) {
- if len(req.Path) == 0 {
- return meta.UpdateObjectResponse{}, fmt.Errorf("路径不能为空")
- }
- lastEntry := req.Path[len(req.Path)-1]
- objectType := strings.ToLower(lastEntry.Type)
- objectName := lastEntry.Name
- var sqls []string
- switch objectType {
- case "database":
- dbName := objectName
- charset, charsetOk := req.Properties["charset"].(string)
- collation, collationOk := req.Properties["collation"].(string)
- if charsetOk && charset != "" {
- sql := fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET %s", dbName, charset)
- sqls = append(sqls, sql)
- }
- if collationOk && collation != "" {
- sql := fmt.Sprintf("ALTER DATABASE `%s` COLLATE %s", dbName, collation)
- sqls = append(sqls, sql)
- }
- case "table":
- if len(req.Path) < 2 {
- return meta.UpdateObjectResponse{}, fmt.Errorf("表路径需要包含数据库信息")
- }
- dbName := req.Path[0].Name
- tableName := objectName
- engine, engineOk := req.Properties["engine"].(string)
- charset, charsetOk := req.Properties["charset"].(string)
- collation, collationOk := req.Properties["collation"].(string)
- if engineOk && engine != "" {
- sql := fmt.Sprintf("ALTER TABLE `%s`.`%s` ENGINE = %s", dbName, tableName, engine)
- sqls = append(sqls, sql)
- }
- if charsetOk && charset != "" {
- sql := fmt.Sprintf("ALTER TABLE `%s`.`%s` CONVERT TO CHARACTER SET %s", dbName, tableName, charset)
- sqls = append(sqls, sql)
- }
- if collationOk && collation != "" {
- sql := fmt.Sprintf("ALTER TABLE `%s`.`%s` COLLATE %s", dbName, tableName, collation)
- sqls = append(sqls, sql)
- }
- default:
- return meta.UpdateObjectResponse{}, fmt.Errorf("不支持的 update 类型: %s", objectType)
- }
- if !req.Execute {
- // 预览模式,只返回生成的SQL
- return meta.UpdateObjectResponse{
- GeneratedSQL: sqls,
- }, nil
- }
- // 执行模式
- for _, sql := range sqls {
- if _, err := q.db.ExecContext(ctx, sql); err != nil {
- return meta.UpdateObjectResponse{}, fmt.Errorf("执行SQL失败: %s, 错误: %v", sql, err)
- }
- }
- return meta.UpdateObjectResponse{
- GeneratedSQL: sqls,
- }, nil
- }
- // DescribeDeleteTemplate 返回指定对象的删除模板,用于前端显示删除确认和影响预览
- func (q *MySQLDriver) DescribeDeleteTemplate(ctx context.Context, path meta.ObjectPath) (meta.ObjectTemplate, error) {
- if len(path) == 0 {
- return meta.ObjectTemplate{}, fmt.Errorf("路径不能为空")
- }
- lastEntry := path[len(path)-1]
- objectType := strings.ToLower(lastEntry.Type)
- objectName := lastEntry.Name
- switch objectType {
- case "database":
- dbName := objectName
- // 查询数据库中的表数量
- var tableCount int64
- err := q.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?", dbName).Scan(&tableCount)
- if err != nil {
- return meta.ObjectTemplate{}, fmt.Errorf("获取数据库表数量失败: %w", err)
- }
- tpl := meta.ObjectTemplate{
- Operation: "delete",
- ObjectType: "database",
- ParentHint: "",
- Current: map[string]string{
- "databaseName": dbName,
- "tableCount": fmt.Sprintf("%d", tableCount),
- },
- Fields: []meta.TemplateField{
- {Name: "databaseName", Label: "Database Name", Type: meta.FieldTypeString, Required: true, Current: dbName, Editable: &[]bool{false}[0]},
- {Name: "tableCount", Label: "Tables Count", Type: meta.FieldTypeString, Required: false, Current: fmt.Sprintf("%d", tableCount), Editable: &[]bool{false}[0]},
- {Name: "confirmDelete", Label: "Confirm Delete", Type: meta.FieldTypeBool, Required: true, Help: "删除数据库将同时删除所有表和数据,此操作不可恢复"},
- },
- Notes: "警告:删除数据库将永久删除所有表、数据和相关对象。此操作不可恢复。",
- }
- return tpl, nil
- case "table":
- if len(path) < 2 {
- return meta.ObjectTemplate{}, fmt.Errorf("表路径需要包含数据库信息")
- }
- dbName := path[0].Name
- tableName := objectName
- // 查询表中的行数
- var rowCount int64
- query := fmt.Sprintf("SELECT COUNT(*) FROM `%s`.`%s`", dbName, tableName)
- err := q.db.QueryRowContext(ctx, query).Scan(&rowCount)
- if err != nil {
- // 如果查询失败,可能表不存在或权限问题
- rowCount = -1
- }
- tpl := meta.ObjectTemplate{
- Operation: "delete",
- ObjectType: "table",
- ParentHint: "database: " + dbName,
- Current: map[string]string{
- "tableName": tableName,
- "rowCount": fmt.Sprintf("%d", rowCount),
- },
- Fields: []meta.TemplateField{
- {Name: "tableName", Label: "Table Name", Type: meta.FieldTypeString, Required: true, Current: tableName, Editable: &[]bool{false}[0]},
- {Name: "rowCount", Label: "Row Count", Type: meta.FieldTypeString, Required: false, Current: fmt.Sprintf("%d", rowCount), Editable: &[]bool{false}[0]},
- {Name: "confirmDelete", Label: "Confirm Delete", Type: meta.FieldTypeBool, Required: true, Help: "删除表将永久删除所有数据,此操作不可恢复"},
- },
- Notes: "警告:删除表将永久删除所有数据。此操作不可恢复。",
- }
- return tpl, nil
- default:
- return meta.ObjectTemplate{}, fmt.Errorf("不支持的 delete 类型: %s", objectType)
- }
- }
|