meta.go 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963
  1. package v8
  2. import (
  3. "context"
  4. "database/sql"
  5. "dbview/service/internal/common/databases/meta"
  6. "encoding/json"
  7. "fmt"
  8. "reflect"
  9. "sort"
  10. "strings"
  11. "time"
  12. )
  13. // GetRootObjects 获取MySQL实例下的所有数据库(根对象)
  14. // connInfo 仅用于错误提示/上下文,不用于连接建立(连接由外部 db 实例提供)
  15. func (q *MySQLDriver) GetRootObjects(ctx context.Context, connID string, connInfo string, page, pageSize int, fetch bool) ([]meta.GenericObject, int64, error) {
  16. db := q.db
  17. // 2. 查询总数(分页用)
  18. var total int64
  19. if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA").Scan(&total); err != nil {
  20. return nil, 0, fmt.Errorf("查询数据库实例下的 schema 总数失败(INFORMATION_SCHEMA.SCHEMATA):%w", err)
  21. }
  22. // 3. 分页查询库列表
  23. offset := (page - 1) * pageSize
  24. // 某些 MySQL 版本或驱动对在 LIMIT/OFFSET 中使用占位符兼容性不好,改为直接插入整数常量(page,pageSize 为内部控制,非外部注入)
  25. query := fmt.Sprintf(
  26. "SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA LIMIT %d OFFSET %d",
  27. pageSize, offset)
  28. // 如果不需要取具体数据(fetch==false),只返回总数和 root 的 type 信息(前端可用来决定后续请求)
  29. if !fetch {
  30. // root 层的类型通常为 database
  31. // 为类型占位对象添加 Attrs,包含前端可用的请求模板(占位符形式);Description 已移到结构体字段
  32. types := []meta.GenericObject{{
  33. ID: fmt.Sprintf("%s.type-database", connID),
  34. Name: "database",
  35. Description: "数据库(root)类型,占位用于导航;前端可据此请求数据库下的子类型/条目",
  36. Type: "database",
  37. ParentID: connInfo,
  38. DBType: "mysql",
  39. Attrs: map[string]string{},
  40. }}
  41. return types, total, nil
  42. }
  43. rows, err := db.QueryContext(ctx, query)
  44. if err != nil {
  45. return nil, 0, fmt.Errorf("分页查询 schema 列表失败(conn=%s,page=%d,pageSize=%d, sql=%s):%w", connInfo, page, pageSize, query, err)
  46. }
  47. defer rows.Close()
  48. // 4. 映射为GenericObject(根对象类型为"database")
  49. var objects []meta.GenericObject
  50. for rows.Next() {
  51. var dbName sql.NullString
  52. var charset sql.NullString
  53. if err := rows.Scan(&dbName, &charset); err != nil {
  54. // 忽略该行并继续
  55. continue
  56. }
  57. objects = append(objects, meta.GenericObject{
  58. ID: fmt.Sprintf("%s.db-%s", connID, dbName.String), // ID格式:connID.db-库名
  59. Name: dbName.String,
  60. Type: "database", // 根对象类型为数据库
  61. ParentID: connInfo, // 父对象为实例(connInfo)
  62. DBType: "mysql",
  63. Attrs: map[string]string{
  64. "charset": charset.String, // MySQL库特有属性:默认字符集
  65. "createTime": "",
  66. },
  67. Children: []meta.GenericObject{}, // 子对象暂不加载
  68. })
  69. }
  70. if err := rows.Err(); err != nil {
  71. return nil, 0, fmt.Errorf("遍历 schema 结果集出错:%w", err)
  72. }
  73. return objects, total, nil
  74. }
  75. // GetChildObjects 获取父对象下的子对象(使用结构化路径定位父对象)
  76. // path: 从根到父对象的完整路径(见 meta.ObjectPath),最后一项为父对象自身
  77. // childType: 可选,若指定仅返回该子类型(例如 "column"、"index")
  78. func (q *MySQLDriver) GetChildObjects(ctx context.Context, path meta.ObjectPath, childType string, page, pageSize int, fetch bool) ([]meta.GenericObject, int64, error) {
  79. db := q.db
  80. // path 必须至少包含父对象自身
  81. if len(path) == 0 {
  82. return nil, 0, fmt.Errorf("empty path")
  83. }
  84. parentEntry := path[len(path)-1]
  85. // parentEntry.Name 在库级场景下为库名(TABLE_SCHEMA)
  86. dbName := parentEntry.Name
  87. // 解析筛选类型(默认 table),优先使用 childType 参数
  88. objectType := strings.ToLower(childType)
  89. if objectType == "" {
  90. objectType = "table"
  91. }
  92. // 当不需要拉取具体数据时,返回可用类型清单(前端据此发起具体类型的请求)
  93. if !fetch {
  94. // 目前不再支持传入表级 parent 来列出表级子类型(请使用 GetObjectDetails 获取表的列/索引等信息)
  95. // 为每种类型占位添加 Attrs;Description 已移到结构体字段
  96. types := []meta.GenericObject{
  97. {ID: fmt.Sprintf("%s.type-table", dbName), Name: "table", Description: "库级的表类型占位;前端可据此请求 /database/metadata/children 获取具体表列表", Type: "table", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
  98. {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{}},
  99. {ID: fmt.Sprintf("%s.type-index", dbName), Name: "index", Description: "索引(index)类型占位;可用于列出库级索引或提示按表查看索引", Type: "index", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
  100. {ID: fmt.Sprintf("%s.type-procedure", dbName), Name: "procedure", Description: "存储过程(procedure)类型占位;前端可据此请求对应类型列表", Type: "procedure", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
  101. {ID: fmt.Sprintf("%s.type-trigger", dbName), Name: "trigger", Description: "触发器(trigger)类型占位;前端可据此请求对应触发器列表", Type: "trigger", ParentID: dbName, DBType: "mysql", Attrs: map[string]string{}},
  102. }
  103. return types, 0, nil
  104. }
  105. switch objectType {
  106. case "table":
  107. tableType := "BASE TABLE"
  108. objType := "table"
  109. // count
  110. var total int64
  111. if err := db.QueryRowContext(ctx, `
  112. SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
  113. WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
  114. `, dbName, tableType).Scan(&total); err != nil {
  115. return nil, 0, fmt.Errorf("查询库 %s 中 %s 数量失败:%w", dbName, tableType, err)
  116. }
  117. // page
  118. offset := (page - 1) * pageSize
  119. rows, err := db.QueryContext(ctx, `
  120. SELECT TABLE_NAME, ENGINE, CREATE_TIME
  121. FROM INFORMATION_SCHEMA.TABLES
  122. WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
  123. LIMIT ? OFFSET ?
  124. `, dbName, tableType, pageSize, offset)
  125. if err != nil {
  126. return nil, 0, fmt.Errorf("分页查询库 %s 的 %s 列表失败(page=%d,pageSize=%d):%w", dbName, tableType, page, pageSize, err)
  127. }
  128. defer rows.Close()
  129. var objects []meta.GenericObject
  130. for rows.Next() {
  131. var tName sql.NullString
  132. var engine sql.NullString
  133. var createTime sql.NullString
  134. if err := rows.Scan(&tName, &engine, &createTime); err != nil {
  135. continue
  136. }
  137. objects = append(objects, meta.GenericObject{
  138. ID: fmt.Sprintf("%s.table-%s", dbName, tName.String),
  139. Name: tName.String,
  140. Type: objType,
  141. ParentID: dbName,
  142. DBType: "mysql",
  143. Attrs: map[string]string{
  144. "engine": engine.String,
  145. "createTime": createTime.String,
  146. },
  147. })
  148. }
  149. if err := rows.Err(); err != nil {
  150. return nil, 0, fmt.Errorf("遍历表结果集出错(%s.%s):%w", dbName, tableType, err)
  151. }
  152. return objects, total, nil
  153. case "view":
  154. tableType := "VIEW"
  155. objType := "view"
  156. // count
  157. var total int64
  158. if err := db.QueryRowContext(ctx, `
  159. SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
  160. WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
  161. `, dbName, tableType).Scan(&total); err != nil {
  162. return nil, 0, fmt.Errorf("查询库 %s 中 %s 数量失败:%w", dbName, tableType, err)
  163. }
  164. // page
  165. offset := (page - 1) * pageSize
  166. rows, err := db.QueryContext(ctx, `
  167. SELECT TABLE_NAME, NULL AS ENGINE, CREATE_TIME
  168. FROM INFORMATION_SCHEMA.TABLES
  169. WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = ?
  170. LIMIT ? OFFSET ?
  171. `, dbName, tableType, pageSize, offset)
  172. if err != nil {
  173. return nil, 0, fmt.Errorf("分页查询库 %s 的 %s 列表失败(page=%d,pageSize=%d):%w", dbName, tableType, page, pageSize, err)
  174. }
  175. defer rows.Close()
  176. var objects []meta.GenericObject
  177. for rows.Next() {
  178. var tName sql.NullString
  179. var engine sql.NullString
  180. var createTime sql.NullString
  181. if err := rows.Scan(&tName, &engine, &createTime); err != nil {
  182. continue
  183. }
  184. objects = append(objects, meta.GenericObject{
  185. ID: fmt.Sprintf("%s.table-%s", dbName, tName.String),
  186. Name: tName.String,
  187. Type: objType,
  188. ParentID: dbName,
  189. DBType: "mysql",
  190. Attrs: map[string]string{
  191. "engine": engine.String,
  192. "createTime": createTime.String,
  193. },
  194. })
  195. }
  196. if err := rows.Err(); err != nil {
  197. return nil, 0, fmt.Errorf("遍历视图结果集出错(%s.%s):%w", dbName, tableType, err)
  198. }
  199. return objects, total, nil
  200. case "index":
  201. // 仅支持库级索引列出(表级索引请通过 GetObjectDetails 读取表详情以获取索引)
  202. var total int64
  203. if err := db.QueryRowContext(ctx, `
  204. SELECT COUNT(DISTINCT CONCAT(TABLE_NAME,'::',INDEX_NAME))
  205. FROM INFORMATION_SCHEMA.STATISTICS
  206. WHERE TABLE_SCHEMA = ?
  207. `, dbName).Scan(&total); err != nil {
  208. return nil, 0, err
  209. }
  210. offset := (page - 1) * pageSize
  211. rows, err := db.QueryContext(ctx, `
  212. SELECT DISTINCT INDEX_NAME, TABLE_NAME, NON_UNIQUE
  213. FROM INFORMATION_SCHEMA.STATISTICS
  214. WHERE TABLE_SCHEMA = ?
  215. LIMIT ? OFFSET ?
  216. `, dbName, pageSize, offset)
  217. if err != nil {
  218. return nil, 0, err
  219. }
  220. defer rows.Close()
  221. var objects []meta.GenericObject
  222. for rows.Next() {
  223. var idxName sql.NullString
  224. var tName sql.NullString
  225. var nonUnique sql.NullInt64
  226. if err := rows.Scan(&idxName, &tName, &nonUnique); err != nil {
  227. continue
  228. }
  229. id := fmt.Sprintf("%s.index-%s-%s", dbName, tName.String, idxName.String)
  230. objects = append(objects, meta.GenericObject{
  231. ID: id,
  232. Name: idxName.String,
  233. Type: "index",
  234. ParentID: dbName,
  235. DBType: "mysql",
  236. Attrs: map[string]string{
  237. "table": tName.String,
  238. "nonUnique": fmt.Sprintf("%d", nonUnique.Int64),
  239. },
  240. })
  241. }
  242. if err := rows.Err(); err != nil {
  243. return nil, 0, fmt.Errorf("遍历索引结果集出错(库级,%s):%w", dbName, err)
  244. }
  245. return objects, total, nil
  246. case "procedure", "proc":
  247. var total int64
  248. if err := db.QueryRowContext(ctx, `
  249. SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES
  250. WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE'
  251. `, dbName).Scan(&total); err != nil {
  252. return nil, 0, fmt.Errorf("查询库 %s 存储过程数量失败:%w", dbName, err)
  253. }
  254. offset := (page - 1) * pageSize
  255. rows, err := db.QueryContext(ctx, `
  256. SELECT ROUTINE_NAME, ROUTINE_DEFINITION, CREATED
  257. FROM INFORMATION_SCHEMA.ROUTINES
  258. WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE'
  259. LIMIT ? OFFSET ?
  260. `, dbName, pageSize, offset)
  261. if err != nil {
  262. return nil, 0, fmt.Errorf("分页查询库 %s 存储过程列表失败(page=%d,pageSize=%d):%w", dbName, page, pageSize, err)
  263. }
  264. defer rows.Close()
  265. var objects []meta.GenericObject
  266. for rows.Next() {
  267. var rName sql.NullString
  268. var def sql.NullString
  269. var created sql.NullString
  270. if err := rows.Scan(&rName, &def, &created); err != nil {
  271. continue
  272. }
  273. id := fmt.Sprintf("%s.proc-%s", dbName, rName.String)
  274. objects = append(objects, meta.GenericObject{
  275. ID: id,
  276. Name: rName.String,
  277. Type: "procedure",
  278. ParentID: dbName,
  279. DBType: "mysql",
  280. Attrs: map[string]string{
  281. "definition": def.String,
  282. "created": created.String,
  283. },
  284. })
  285. }
  286. if err := rows.Err(); err != nil {
  287. return nil, 0, fmt.Errorf("遍历存储过程结果出错(%s):%w", dbName, err)
  288. }
  289. return objects, total, nil
  290. case "trigger":
  291. // 列出库中的触发器(不再支持表级 parentName)
  292. var total int64
  293. if err := db.QueryRowContext(ctx, `
  294. SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS
  295. WHERE TRIGGER_SCHEMA = ?
  296. `, dbName).Scan(&total); err != nil {
  297. return nil, 0, fmt.Errorf("查询库 %s 触发器数量失败:%w", dbName, err)
  298. }
  299. offset := (page - 1) * pageSize
  300. rows, err := db.QueryContext(ctx, `
  301. SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT
  302. FROM INFORMATION_SCHEMA.TRIGGERS
  303. WHERE TRIGGER_SCHEMA = ?
  304. LIMIT ? OFFSET ?
  305. `, dbName, pageSize, offset)
  306. if err != nil {
  307. return nil, 0, fmt.Errorf("分页查询库 %s 触发器列表失败(page=%d,pageSize=%d):%w", dbName, page, pageSize, err)
  308. }
  309. defer rows.Close()
  310. var objects []meta.GenericObject
  311. for rows.Next() {
  312. var tName sql.NullString
  313. var event sql.NullString
  314. var objTable sql.NullString
  315. var timing sql.NullString
  316. var stmt sql.NullString
  317. if err := rows.Scan(&tName, &event, &objTable, &timing, &stmt); err != nil {
  318. continue
  319. }
  320. id := fmt.Sprintf("%s.trigger-%s", dbName, tName.String)
  321. objects = append(objects, meta.GenericObject{
  322. ID: id,
  323. Name: tName.String,
  324. Type: "trigger",
  325. ParentID: dbName,
  326. DBType: "mysql",
  327. Attrs: map[string]string{
  328. "event": event.String,
  329. "table": objTable.String,
  330. "timing": timing.String,
  331. "statement": stmt.String,
  332. },
  333. })
  334. }
  335. if err := rows.Err(); err != nil {
  336. return nil, 0, fmt.Errorf("遍历触发器结果出错(%s):%w", dbName, err)
  337. }
  338. return objects, total, nil
  339. default:
  340. return nil, 0, fmt.Errorf("不支持的类型: %s", objectType)
  341. }
  342. }
  343. // GetObjectDetails 获取对象详情(含子对象,如表的字段、索引)
  344. // path: 从根到目标对象的完整路径(见 meta.ObjectPath),最后一项为目标对象
  345. // 注意:不再兼容旧的 dotted objectID 格式,调用方应传入结构化路径
  346. func (q *MySQLDriver) GetObjectDetails(ctx context.Context, path meta.ObjectPath, fetch bool) (meta.GenericObject, error) {
  347. if len(path) == 0 {
  348. return meta.GenericObject{}, fmt.Errorf("empty path")
  349. }
  350. last := path[len(path)-1]
  351. t := strings.ToLower(last.Type)
  352. switch t {
  353. case "table", "view":
  354. tableName := last.Name
  355. var dbName string
  356. if len(path) >= 2 {
  357. dbName = path[len(path)-2].Name
  358. } else {
  359. dbName = ""
  360. }
  361. // 构造表对象的 ID 与 parentID
  362. parentID := dbName
  363. objectID := fmt.Sprintf("%s.table-%s", parentID, tableName)
  364. // 获取表基本信息
  365. tableObj, err := q.getTableBaseInfo(ctx, dbName, tableName, parentID)
  366. if err != nil {
  367. return meta.GenericObject{}, fmt.Errorf("获取表 %s.%s 的基础信息失败:%w", dbName, tableName, err)
  368. }
  369. // 如果不需要拉取子对象细节(如列/索引),返回可用子类型清单(供前端按类型按需加载)
  370. if !fetch {
  371. var types []meta.GenericObject
  372. // columns
  373. types = append(types, meta.GenericObject{
  374. ID: fmt.Sprintf("%s.type-column", objectID),
  375. Name: "column",
  376. Description: "列(column)类型占位;调用同一对象详情接口并设置 fetch=true 可获取列列表",
  377. Type: "column",
  378. ParentID: objectID,
  379. DBType: "mysql",
  380. Attrs: map[string]string{},
  381. })
  382. // indexes
  383. types = append(types, meta.GenericObject{
  384. ID: fmt.Sprintf("%s.type-index", objectID),
  385. Name: "index",
  386. Description: "索引(index)类型占位;调用对象详情接口并设置 fetch=true 可获取索引信息",
  387. Type: "index",
  388. ParentID: objectID,
  389. DBType: "mysql",
  390. Attrs: map[string]string{},
  391. })
  392. // 将 types 作为 Children 返回(空的 Children 表示未展开,带 types 表示可按类型加载)
  393. tableObj.Children = types
  394. return tableObj, nil
  395. }
  396. // 查询字段(子对象)
  397. columns, err := q.getTableColumns(ctx, dbName, tableName, objectID)
  398. if err != nil {
  399. return meta.GenericObject{}, fmt.Errorf("获取表 %s.%s 列信息失败:%w", dbName, tableName, err)
  400. }
  401. tableObj.Children = append(tableObj.Children, columns...)
  402. // 查询索引(子对象)
  403. indexes, err := q.getTableIndexes(ctx, dbName, tableName, objectID)
  404. if err != nil {
  405. return meta.GenericObject{}, fmt.Errorf("获取表 %s.%s 索引信息失败:%w", dbName, tableName, err)
  406. }
  407. tableObj.Children = append(tableObj.Children, indexes...)
  408. return tableObj, nil
  409. default:
  410. return meta.GenericObject{}, fmt.Errorf("不支持的对象类型: %s", last.Type)
  411. }
  412. }
  413. // getTableBaseInfo 返回表的基础信息,用于构造 table 对象
  414. func (q *MySQLDriver) getTableBaseInfo(ctx context.Context, dbName, tableName, parentName string) (meta.GenericObject, error) {
  415. db := q.db
  416. var engine sql.NullString
  417. var tableType sql.NullString
  418. var createTime sql.NullString
  419. if err := db.QueryRowContext(ctx, `
  420. SELECT ENGINE, TABLE_TYPE, CREATE_TIME FROM INFORMATION_SCHEMA.TABLES
  421. WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
  422. `, dbName, tableName).Scan(&engine, &tableType, &createTime); err != nil {
  423. return meta.GenericObject{}, fmt.Errorf("查询表 %s.%s 的基础信息失败:%w", dbName, tableName, err)
  424. }
  425. objType := "table"
  426. if strings.EqualFold(tableType.String, "VIEW") {
  427. objType = "view"
  428. }
  429. parentID := parentName
  430. objectID := fmt.Sprintf("%s.table-%s", parentName, tableName)
  431. return meta.GenericObject{
  432. ID: objectID,
  433. Name: tableName,
  434. Type: objType,
  435. ParentID: parentID,
  436. DBType: "mysql",
  437. Attrs: map[string]string{
  438. "engine": engine.String,
  439. "createTime": createTime.String,
  440. },
  441. Children: []meta.GenericObject{},
  442. }, nil
  443. }
  444. // getTableColumns 返回表的列信息(作为子对象)
  445. func (q *MySQLDriver) getTableColumns(ctx context.Context, dbName, tableName, objectID string) ([]meta.GenericObject, error) {
  446. db := q.db
  447. rows, err := db.QueryContext(ctx, `
  448. SELECT COLUMN_NAME, COLUMN_TYPE, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, CHARACTER_MAXIMUM_LENGTH, ORDINAL_POSITION
  449. FROM INFORMATION_SCHEMA.COLUMNS
  450. WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
  451. ORDER BY ORDINAL_POSITION
  452. `, dbName, tableName)
  453. if err != nil {
  454. return nil, fmt.Errorf("查询表 %s.%s 的列信息失败:%w", dbName, tableName, err)
  455. }
  456. defer rows.Close()
  457. var cols []meta.GenericObject
  458. for rows.Next() {
  459. var colName sql.NullString
  460. var columnType sql.NullString
  461. var dataType sql.NullString
  462. var isNullable sql.NullString
  463. var colDefault sql.NullString
  464. var charMax sql.NullInt64
  465. var ord sql.NullInt64
  466. if err := rows.Scan(&colName, &columnType, &dataType, &isNullable, &colDefault, &charMax, &ord); err != nil {
  467. continue
  468. }
  469. id := fmt.Sprintf("%s.column-%s", objectID, colName.String)
  470. cols = append(cols, meta.GenericObject{
  471. ID: id,
  472. Name: colName.String,
  473. Type: "column",
  474. ParentID: objectID,
  475. DBType: "mysql",
  476. Attrs: map[string]string{
  477. "columnType": columnType.String,
  478. "dataType": dataType.String,
  479. "nullable": isNullable.String,
  480. "default": colDefault.String,
  481. "charMax": fmt.Sprintf("%d", charMax.Int64),
  482. "position": fmt.Sprintf("%d", ord.Int64),
  483. },
  484. })
  485. }
  486. if err := rows.Err(); err != nil {
  487. return nil, fmt.Errorf("遍历列结果集出错(%s.%s):%w", dbName, tableName, err)
  488. }
  489. return cols, nil
  490. }
  491. // getTableIndexes 返回表的索引信息(作为子对象)
  492. func (q *MySQLDriver) getTableIndexes(ctx context.Context, dbName, tableName, objectID string) ([]meta.GenericObject, error) {
  493. db := q.db
  494. rows, err := db.QueryContext(ctx, `
  495. SELECT INDEX_NAME, NON_UNIQUE, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS columns
  496. FROM INFORMATION_SCHEMA.STATISTICS
  497. WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
  498. GROUP BY INDEX_NAME, NON_UNIQUE
  499. `, dbName, tableName)
  500. if err != nil {
  501. return nil, fmt.Errorf("查询表 %s.%s 的索引信息失败:%w", dbName, tableName, err)
  502. }
  503. defer rows.Close()
  504. var idxs []meta.GenericObject
  505. for rows.Next() {
  506. var idxName sql.NullString
  507. var nonUnique sql.NullInt64
  508. var cols sql.NullString
  509. if err := rows.Scan(&idxName, &nonUnique, &cols); err != nil {
  510. continue
  511. }
  512. id := fmt.Sprintf("%s.index-%s", objectID, idxName.String)
  513. idxs = append(idxs, meta.GenericObject{
  514. ID: id,
  515. Name: idxName.String,
  516. Type: "index",
  517. ParentID: objectID,
  518. DBType: "mysql",
  519. Attrs: map[string]string{
  520. "columns": cols.String,
  521. "nonUnique": fmt.Sprintf("%d", nonUnique.Int64),
  522. },
  523. })
  524. }
  525. if err := rows.Err(); err != nil {
  526. return nil, fmt.Errorf("遍历索引结果集出错(%s.%s):%w", dbName, tableName, err)
  527. }
  528. return idxs, nil
  529. }
  530. // DeleteRootObjects:根据前端传入的 rootName(支持通配符 * 或 SQL %)、typeName(如 database/table/view)
  531. // 返回匹配到的对象列表与总数(注:默认不直接执行 DROP,仅列出匹配项)
  532. func (q *MySQLDriver) DeleteRootObjects(ctx context.Context, req meta.ObjectOperationRequest) (meta.ObjectOperationResponse, error) {
  533. var resp meta.ObjectOperationResponse
  534. db := q.db
  535. rootName := req.Object.Name
  536. typeName := req.Object.Type
  537. t := strings.ToLower(typeName)
  538. // 处理通配符
  539. pattern := rootName
  540. if strings.Contains(pattern, "*") {
  541. pattern = strings.ReplaceAll(pattern, "*", "%")
  542. }
  543. useLike := strings.Contains(pattern, "%") || strings.Contains(pattern, "_")
  544. switch t {
  545. case "database", "schema":
  546. var rows *sql.Rows
  547. var err error
  548. if pattern == "" {
  549. rows, err = db.QueryContext(ctx, `SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA`)
  550. } else if useLike {
  551. rows, err = db.QueryContext(ctx, `SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME LIKE ?`, pattern)
  552. } else {
  553. rows, err = db.QueryContext(ctx, `SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`, pattern)
  554. }
  555. if err != nil {
  556. return resp, fmt.Errorf("查询 schema 列表以供删除匹配失败:%w", err)
  557. }
  558. defer rows.Close()
  559. var objs []meta.GenericObject
  560. var total int64
  561. for rows.Next() {
  562. var name sql.NullString
  563. var charset sql.NullString
  564. if err := rows.Scan(&name, &charset); err != nil {
  565. continue
  566. }
  567. total++
  568. objs = append(objs, meta.GenericObject{
  569. ID: fmt.Sprintf("db-%s", name.String),
  570. Name: name.String,
  571. Type: "database",
  572. ParentID: "",
  573. DBType: "mysql",
  574. Attrs: map[string]string{
  575. "charset": charset.String,
  576. },
  577. })
  578. }
  579. if err := rows.Err(); err != nil {
  580. return resp, fmt.Errorf("遍历 schema 结果出错:%w", err)
  581. }
  582. resp.Affected = total
  583. resp.ObjectID = ""
  584. // 将匹配对象放入 Options 供前端显示(不能放在 Object 因为可能是多个)
  585. respMap := map[string]interface{}{"matches": objs}
  586. if respMapBytes, err := json.Marshal(respMap); err == nil {
  587. // 当仅生成 SQL(不执行)时,把匹配对象的 JSON 放到 Sql 字段供前端查看;
  588. // 若 Execute==true,则我们会尝试执行对应的 DROP 语句并在 Sql 中也返回执行的语句列表。
  589. resp.Sql = string(respMapBytes)
  590. }
  591. // 如果请求不要求执行,直接返回列出匹配项
  592. if !req.Execute {
  593. return resp, nil
  594. }
  595. // Execute==true: 执行删除操作(注意:对 DDL 的事务语义依赖于具体数据库;MySQL 的 DROP 在多数情况下不可回滚)
  596. tx, err := db.BeginTx(ctx, nil)
  597. if err != nil {
  598. return resp, fmt.Errorf("开始事务失败:%w", err)
  599. }
  600. var execCount int64
  601. var execSQLs []string
  602. for _, o := range objs {
  603. // 对不同类型生成对应的 DROP 语句
  604. switch o.Type {
  605. case "database":
  606. sqlStr := fmt.Sprintf("DROP DATABASE `%s`", o.Name)
  607. if _, err := tx.ExecContext(ctx, sqlStr); err != nil {
  608. _ = tx.Rollback()
  609. return resp, fmt.Errorf("执行 SQL 失败:%s, err: %w", sqlStr, err)
  610. }
  611. execSQLs = append(execSQLs, sqlStr)
  612. execCount++
  613. default:
  614. // 其他 root 类型目前不支持直接删除,记录并继续
  615. }
  616. }
  617. if err := tx.Commit(); err != nil {
  618. _ = tx.Rollback()
  619. return resp, fmt.Errorf("提交事务失败:%w", err)
  620. }
  621. resp.Affected = execCount
  622. if b, err := json.Marshal(execSQLs); err == nil {
  623. resp.Sql = string(b)
  624. }
  625. return resp, nil
  626. case "table", "view":
  627. tableType := "BASE TABLE"
  628. if t == "view" {
  629. tableType = "VIEW"
  630. }
  631. var rows *sql.Rows
  632. var err error
  633. if pattern == "" {
  634. rows, err = db.QueryContext(ctx, `SELECT TABLE_SCHEMA, TABLE_NAME, NULL AS ENGINE, CREATE_TIME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ?`, tableType)
  635. } else if useLike {
  636. 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)
  637. } else {
  638. 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)
  639. }
  640. if err != nil {
  641. return resp, fmt.Errorf("查询表/视图以供删除匹配失败:%w", err)
  642. }
  643. defer rows.Close()
  644. var objs []meta.GenericObject
  645. var total int64
  646. for rows.Next() {
  647. var schema sql.NullString
  648. var tname sql.NullString
  649. var engine sql.NullString
  650. var ctime sql.NullString
  651. if err := rows.Scan(&schema, &tname, &engine, &ctime); err != nil {
  652. continue
  653. }
  654. total++
  655. pid := fmt.Sprintf("db-%s", schema.String)
  656. id := fmt.Sprintf("%s.table-%s", pid, tname.String)
  657. objs = append(objs, meta.GenericObject{
  658. ID: id,
  659. Name: tname.String,
  660. Type: t,
  661. ParentID: pid,
  662. DBType: "mysql",
  663. Attrs: map[string]string{
  664. "engine": engine.String,
  665. "createTime": ctime.String,
  666. },
  667. })
  668. }
  669. if err := rows.Err(); err != nil {
  670. return resp, fmt.Errorf("遍历表/视图结果出错:%w", err)
  671. }
  672. resp.Affected = total
  673. matchMap := map[string]interface{}{"matches": objs}
  674. if b, err := json.Marshal(matchMap); err == nil {
  675. resp.Sql = string(b)
  676. }
  677. return resp, nil
  678. default:
  679. return resp, fmt.Errorf("不支持的 root 类型: %s", typeName)
  680. }
  681. }
  682. // DeleteChildObjects:根据 parentID(如 db-xxx 或 conn.db-xxx)和 filter(type/name)返回匹配的子对象列表与总数
  683. func (q *MySQLDriver) DeleteChildObjects(ctx context.Context, req meta.ObjectOperationRequest) (meta.ObjectOperationResponse, error) {
  684. var resp meta.ObjectOperationResponse
  685. parentID := req.Object.ParentID
  686. filter := map[string]string{}
  687. if req.Object.Name != "" {
  688. filter["name"] = req.Object.Name
  689. }
  690. if req.Object.Type != "" {
  691. filter["type"] = req.Object.Type
  692. }
  693. // reuse previous logic but adapt to return ObjectOperationResponse
  694. parts := strings.Split(parentID, ".")
  695. var dbPart string
  696. if len(parts) >= 2 {
  697. dbPart = parts[1]
  698. } else {
  699. dbPart = parentID
  700. }
  701. dbName := strings.TrimPrefix(dbPart, "db-")
  702. objectType := strings.ToLower(filter["type"])
  703. namePattern := filter["name"]
  704. if strings.Contains(namePattern, "*") {
  705. namePattern = strings.ReplaceAll(namePattern, "*", "%")
  706. }
  707. useLike := strings.Contains(namePattern, "%") || strings.Contains(namePattern, "_")
  708. db := q.db
  709. switch objectType {
  710. case "index":
  711. // 表级或库级索引
  712. var rows *sql.Rows
  713. var err error
  714. if namePattern == "" {
  715. 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)
  716. } else if useLike {
  717. 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)
  718. } else {
  719. 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)
  720. }
  721. if err != nil {
  722. return resp, fmt.Errorf("查询索引以供删除匹配失败:%w", err)
  723. }
  724. defer rows.Close()
  725. var res []meta.GenericObject
  726. var total int64
  727. for rows.Next() {
  728. var idx sql.NullString
  729. var tname sql.NullString
  730. var nonUnique sql.NullInt64
  731. if err := rows.Scan(&idx, &tname, &nonUnique); err != nil {
  732. continue
  733. }
  734. total++
  735. id := fmt.Sprintf("%s.table-%s.index-%s", parentID, tname.String, idx.String)
  736. res = append(res, meta.GenericObject{
  737. ID: id,
  738. Name: idx.String,
  739. Type: "index",
  740. ParentID: fmt.Sprintf("%s.table-%s", parentID, tname.String),
  741. DBType: "mysql",
  742. Attrs: map[string]string{
  743. "table": tname.String,
  744. "nonUnique": fmt.Sprintf("%d", nonUnique.Int64),
  745. },
  746. })
  747. }
  748. if err := rows.Err(); err != nil {
  749. return resp, fmt.Errorf("遍历索引结果出错:%w", err)
  750. }
  751. resp.Affected = total
  752. m := map[string]interface{}{"matches": res}
  753. if b, err := json.Marshal(m); err == nil {
  754. resp.Sql = string(b)
  755. }
  756. return resp, nil
  757. case "procedure", "proc":
  758. var rows *sql.Rows
  759. var err error
  760. if namePattern == "" {
  761. rows, err = db.QueryContext(ctx, `SELECT ROUTINE_NAME, ROUTINE_DEFINITION, CREATED FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = ? AND ROUTINE_TYPE = 'PROCEDURE'`, dbName)
  762. } else if useLike {
  763. 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)
  764. } else {
  765. 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)
  766. }
  767. if err != nil {
  768. return resp, fmt.Errorf("查询存储过程以供删除匹配失败:%w", err)
  769. }
  770. defer rows.Close()
  771. var res []meta.GenericObject
  772. var total int64
  773. for rows.Next() {
  774. var rName sql.NullString
  775. var def sql.NullString
  776. var created sql.NullString
  777. if err := rows.Scan(&rName, &def, &created); err != nil {
  778. continue
  779. }
  780. total++
  781. id := fmt.Sprintf("%s.proc-%s", parentID, rName.String)
  782. res = append(res, meta.GenericObject{
  783. ID: id,
  784. Name: rName.String,
  785. Type: "procedure",
  786. ParentID: parentID,
  787. DBType: "mysql",
  788. Attrs: map[string]string{
  789. "definition": def.String,
  790. "created": created.String,
  791. },
  792. })
  793. }
  794. if err := rows.Err(); err != nil {
  795. return resp, fmt.Errorf("遍历存储过程结果出错:%w", err)
  796. }
  797. resp.Affected = total
  798. m := map[string]interface{}{"matches": res}
  799. if b, err := json.Marshal(m); err == nil {
  800. resp.Sql = string(b)
  801. }
  802. return resp, nil
  803. case "trigger":
  804. var rows *sql.Rows
  805. var err error
  806. if namePattern == "" {
  807. 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)
  808. } else if useLike {
  809. 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)
  810. } else {
  811. 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)
  812. }
  813. if err != nil {
  814. return resp, fmt.Errorf("查询触发器以供删除匹配失败:%w", err)
  815. }
  816. defer rows.Close()
  817. var res []meta.GenericObject
  818. var total int64
  819. for rows.Next() {
  820. var tName sql.NullString
  821. var event sql.NullString
  822. var objTable sql.NullString
  823. var timing sql.NullString
  824. var stmt sql.NullString
  825. if err := rows.Scan(&tName, &event, &objTable, &timing, &stmt); err != nil {
  826. continue
  827. }
  828. total++
  829. id := fmt.Sprintf("%s.trigger-%s", parentID, tName.String)
  830. res = append(res, meta.GenericObject{
  831. ID: id,
  832. Name: tName.String,
  833. Type: "trigger",
  834. ParentID: parentID,
  835. DBType: "mysql",
  836. Attrs: map[string]string{
  837. "event": event.String,
  838. "table": objTable.String,
  839. "timing": timing.String,
  840. "statement": stmt.String,
  841. },
  842. })
  843. }
  844. if err := rows.Err(); err != nil {
  845. return resp, fmt.Errorf("遍历触发器结果出错:%w", err)
  846. }
  847. resp.Affected = total
  848. m := map[string]interface{}{"matches": res}
  849. if b, err := json.Marshal(m); err == nil {
  850. resp.Sql = string(b)
  851. }
  852. return resp, nil
  853. default:
  854. return resp, fmt.Errorf("不支持的 child 类型: %s", objectType)
  855. }
  856. }
  857. // DescribeCreateTemplate 返回创建指定类型对象的表单模板(供前端渲染)
  858. // getDatabaseTemplateFields 返回数据库相关的模板字段
  859. func getDatabaseTemplateFields(isUpdate bool, currentValues map[string]string) []meta.TemplateField {
  860. fields := []meta.TemplateField{
  861. {Name: "databaseName", Label: "Database Name", Type: meta.FieldTypeString, Required: true},
  862. {Name: "charset", Label: "Character Set", Type: meta.FieldTypeEnum, Required: false, Default: "utf8mb4", EnumOptions: []string{"utf8mb4", "utf8", "latin1", "ascii", "ucs2", "utf16", "utf32", "big5", "gbk"}},
  863. {Name: "collation", Label: "Collation", Type: meta.FieldTypeString, Required: false},
  864. }
  865. if !isUpdate {
  866. // 创建模式:添加ifNotExists字段
  867. fields = append(fields, meta.TemplateField{Name: "ifNotExists", Label: "If Not Exists", Type: meta.FieldTypeBool, Required: false, Default: "false"})
  868. fields[0].Help = "要创建的数据库名称,不含前缀"
  869. } else {
  870. // 修改模式:设置当前值和可编辑性(currentValues 采用 map[string]string)
  871. if currentValues != nil {
  872. if dbName, ok := currentValues["databaseName"]; ok {
  873. fields[0].Current = dbName
  874. fields[0].Editable = &[]bool{false}[0] // 数据库名通常不可修改
  875. }
  876. if charset, ok := currentValues["charset"]; ok {
  877. fields[1].Current = charset
  878. }
  879. if collation, ok := currentValues["collation"]; ok {
  880. fields[2].Current = collation
  881. }
  882. }
  883. }
  884. return fields
  885. }
  886. // getTableTemplateFields 返回表相关的模板字段
  887. func getTableTemplateFields(isUpdate bool, currentValues map[string]string) []meta.TemplateField {
  888. fields := []meta.TemplateField{
  889. {Name: "tableName", Label: "Table Name", Type: meta.FieldTypeString, Required: true},
  890. {Name: "engine", Label: "Engine", Type: meta.FieldTypeEnum, Required: false, EnumOptions: []string{"InnoDB", "MyISAM"}, Default: "InnoDB"},
  891. {Name: "charset", Label: "Charset", Type: meta.FieldTypeEnum, Required: false, Default: "utf8mb4", EnumOptions: []string{"utf8mb4", "utf8", "latin1", "ascii", "ucs2", "utf16", "utf32", "big5", "gbk"}},
  892. {Name: "collation", Label: "Collation", Type: meta.FieldTypeString, Required: false},
  893. }
  894. if !isUpdate {
  895. // 创建模式:添加ifNotExists和columns字段
  896. fields = append(fields, meta.TemplateField{Name: "ifNotExists", Label: "If Not Exists", Type: meta.FieldTypeBool, Required: false, Default: "false"})
  897. fields = append(fields, meta.TemplateField{
  898. Name: "columns", Label: "Columns", Type: meta.FieldTypeList, Required: true, NestedFields: []meta.TemplateField{
  899. {Name: "name", Label: "Column Name", Type: meta.FieldTypeString, Required: true},
  900. {Name: "type", Label: "Data Type", Type: meta.FieldTypeString, Required: true},
  901. {Name: "nullable", Label: "Nullable", Type: meta.FieldTypeBool, Required: false, Default: "false"},
  902. {Name: "default", Label: "Default", Type: meta.FieldTypeString, Required: false},
  903. {Name: "autoIncrement", Label: "Auto Increment", Type: meta.FieldTypeBool, Required: false, Default: "false"},
  904. }})
  905. } else {
  906. // 修改模式:设置当前值和可编辑性(currentValues 采用 map[string]string)
  907. if currentValues != nil {
  908. if tableName, ok := currentValues["tableName"]; ok {
  909. fields[0].Current = tableName
  910. fields[0].Editable = &[]bool{false}[0] // 表名修改需要重命名
  911. }
  912. if engine, ok := currentValues["engine"]; ok {
  913. fields[1].Current = engine
  914. }
  915. if charset, ok := currentValues["charset"]; ok {
  916. fields[2].Current = charset
  917. }
  918. if collation, ok := currentValues["collation"]; ok {
  919. fields[3].Current = collation
  920. }
  921. }
  922. }
  923. return fields
  924. }
  925. // getIndexTemplateFields 返回索引相关的模板字段
  926. func getIndexTemplateFields(isUpdate bool, currentValues map[string]string) []meta.TemplateField {
  927. fields := []meta.TemplateField{
  928. {Name: "indexName", Label: "Index Name", Type: meta.FieldTypeString, Required: true},
  929. {Name: "columns", Label: "Columns (comma separated)", Type: meta.FieldTypeString, Required: true},
  930. {Name: "unique", Label: "Unique", Type: meta.FieldTypeBool, Required: false, Default: "false"},
  931. }
  932. if isUpdate && currentValues != nil {
  933. // 修改模式:设置当前值(currentValues 为 map[string]string)
  934. if indexName, ok := currentValues["indexName"]; ok {
  935. fields[0].Current = indexName
  936. }
  937. if columns, ok := currentValues["columns"]; ok {
  938. fields[1].Current = columns
  939. }
  940. if uniqueStr, ok := currentValues["unique"]; ok {
  941. fields[2].Current = uniqueStr
  942. }
  943. }
  944. return fields
  945. }
  946. // getCurrentDatabaseInfo 获取数据库的当前信息
  947. func (q *MySQLDriver) getCurrentDatabaseInfo(ctx context.Context, dbName string) (map[string]string, error) {
  948. var charset, collation sql.NullString
  949. query := "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?"
  950. err := q.db.QueryRowContext(ctx, query, dbName).Scan(&charset, &collation)
  951. if err != nil {
  952. return nil, fmt.Errorf("获取数据库信息失败: %w", err)
  953. }
  954. return map[string]string{
  955. "databaseName": dbName,
  956. "charset": charset.String,
  957. "collation": collation.String,
  958. }, nil
  959. }
  960. // getCurrentTableInfo 获取表的当前信息
  961. func (q *MySQLDriver) getCurrentTableInfo(ctx context.Context, dbName, tableName string) (map[string]string, error) {
  962. var engine, charset, collation sql.NullString
  963. query := "SELECT ENGINE, TABLE_COLLATION, SUBSTRING_INDEX(TABLE_COLLATION, '_', 1) AS TABLE_CHARSET FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?"
  964. err := q.db.QueryRowContext(ctx, query, dbName, tableName).Scan(&engine, &collation, &charset)
  965. if err != nil {
  966. return nil, fmt.Errorf("获取表信息失败: %w", err)
  967. }
  968. return map[string]string{
  969. "tableName": tableName,
  970. "engine": engine.String,
  971. "charset": charset.String,
  972. "collation": collation.String,
  973. }, nil
  974. }
  975. func (q *MySQLDriver) DescribeCreateTemplate(ctx context.Context, path meta.ObjectPath) (meta.ObjectTemplate, error) {
  976. // 从路径中提取对象类型和父名称
  977. var objectType, parentName string
  978. if len(path) > 0 {
  979. lastEntry := path[len(path)-1]
  980. objectType = strings.ToLower(lastEntry.Type)
  981. // 对于创建操作,最后一个元素是待创建的对象类型
  982. // 前面的元素构成父上下文
  983. if len(path) > 1 {
  984. parentEntry := path[len(path)-2] // 父对象是倒数第二个元素
  985. parentName = parentEntry.Name
  986. }
  987. } else {
  988. return meta.ObjectTemplate{}, fmt.Errorf("创建模板需要指定对象类型路径")
  989. }
  990. switch objectType {
  991. case "database":
  992. // 示例值均以字符串形式表示
  993. ex := map[string]string{"databaseName": "mydb", "charset": "utf8mb4", "ifNotExists": "true"}
  994. tpl := meta.ObjectTemplate{
  995. Operation: "create",
  996. ObjectType: "database",
  997. ParentHint: "",
  998. Fields: getDatabaseTemplateFields(false, nil),
  999. Example: ex,
  1000. }
  1001. return tpl, nil
  1002. case "table":
  1003. parentHint := "parentName should be the database name"
  1004. if parentName != "" {
  1005. parentHint = "database: " + parentName
  1006. }
  1007. // 将复杂的 columns 示例编码为 JSON 字符串以便前端展示/解析
  1008. colsExample := []map[string]interface{}{{"name": "id", "type": "INT", "nullable": false}}
  1009. colsB, _ := json.Marshal(colsExample)
  1010. ex := map[string]string{"tableName": "users", "columns": string(colsB), "engine": "InnoDB"}
  1011. tpl := meta.ObjectTemplate{
  1012. Operation: "create",
  1013. ObjectType: "table",
  1014. ParentHint: parentHint,
  1015. Fields: getTableTemplateFields(false, nil),
  1016. Example: ex,
  1017. }
  1018. return tpl, nil
  1019. case "index":
  1020. parentHint := "parentName can be table name"
  1021. if parentName != "" {
  1022. parentHint = "table: " + parentName
  1023. }
  1024. ex := map[string]string{"indexName": "idx_users_email", "columns": "email", "unique": "true"}
  1025. tpl := meta.ObjectTemplate{
  1026. Operation: "create",
  1027. ObjectType: "index",
  1028. ParentHint: parentHint,
  1029. Fields: getIndexTemplateFields(false, nil),
  1030. Example: ex,
  1031. }
  1032. return tpl, nil
  1033. default:
  1034. return meta.ObjectTemplate{}, fmt.Errorf("不支持的 create 类型: %s", objectType)
  1035. }
  1036. }
  1037. // GetMetadataInfo 返回数据库的元信息(关键字、字段类型、能力等)
  1038. func (q *MySQLDriver) GetMetadataInfo(ctx context.Context) (meta.MetadataCapabilities, error) {
  1039. var caps meta.MetadataCapabilities
  1040. // 1) 尝试从 INFORMATION_SCHEMA.COLUMNS 获取字段类型
  1041. rows, err := q.db.QueryContext(ctx, "SELECT DISTINCT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS")
  1042. if err == nil {
  1043. defer rows.Close()
  1044. for rows.Next() {
  1045. var dt sql.NullString
  1046. if err := rows.Scan(&dt); err != nil {
  1047. continue
  1048. }
  1049. if dt.Valid && dt.String != "" {
  1050. caps.FieldTypes = append(caps.FieldTypes, strings.ToUpper(dt.String))
  1051. }
  1052. }
  1053. } else {
  1054. // 回退到一组常见类型
  1055. caps.FieldTypes = []string{"INT", "BIGINT", "VARCHAR", "TEXT", "DATETIME", "TIMESTAMP", "DATE", "CHAR", "FLOAT", "DOUBLE", "DECIMAL", "BOOLEAN"}
  1056. }
  1057. // 2) 尝试从 mysql.help_keyword 获取关键字(部分 MySQL 安装提供)
  1058. rows2, err2 := q.db.QueryContext(ctx, "SELECT DISTINCT word FROM mysql.help_keyword")
  1059. if err2 == nil {
  1060. defer rows2.Close()
  1061. for rows2.Next() {
  1062. var kw sql.NullString
  1063. if err := rows2.Scan(&kw); err != nil {
  1064. continue
  1065. }
  1066. if kw.Valid && kw.String != "" {
  1067. caps.Keywords = append(caps.Keywords, kw.String)
  1068. }
  1069. }
  1070. } else {
  1071. // 3) 尝试 INFORMATION_SCHEMA.KEYWORDS(部分版本可用)
  1072. rows3, err3 := q.db.QueryContext(ctx, "SELECT DISTINCT keyword FROM INFORMATION_SCHEMA.KEYWORDS")
  1073. if err3 == nil {
  1074. defer rows3.Close()
  1075. for rows3.Next() {
  1076. var kw sql.NullString
  1077. if err := rows3.Scan(&kw); err != nil {
  1078. continue
  1079. }
  1080. if kw.Valid && kw.String != "" {
  1081. caps.Keywords = append(caps.Keywords, kw.String)
  1082. }
  1083. }
  1084. }
  1085. }
  1086. // 4) 去重与排序
  1087. if len(caps.FieldTypes) > 0 {
  1088. m := map[string]struct{}{}
  1089. for _, v := range caps.FieldTypes {
  1090. m[v] = struct{}{}
  1091. }
  1092. caps.FieldTypes = caps.FieldTypes[:0]
  1093. for k := range m {
  1094. caps.FieldTypes = append(caps.FieldTypes, k)
  1095. }
  1096. sort.Strings(caps.FieldTypes)
  1097. }
  1098. if len(caps.Keywords) > 0 {
  1099. m := map[string]struct{}{}
  1100. for _, v := range caps.Keywords {
  1101. m[v] = struct{}{}
  1102. }
  1103. caps.Keywords = caps.Keywords[:0]
  1104. for k := range m {
  1105. caps.Keywords = append(caps.Keywords, k)
  1106. }
  1107. sort.Strings(caps.Keywords)
  1108. }
  1109. // 5) 常见能力标记
  1110. caps.Capabilities = map[string]bool{
  1111. "supportsTransactions": true,
  1112. "supportsDDLTransaction": false,
  1113. }
  1114. return caps, nil
  1115. }
  1116. // CreateObject 仅实现 preview(execute==false)路径:校验输入并生成 SQL,execute=true 尚未实现
  1117. func (q *MySQLDriver) CreateObject(ctx context.Context, req meta.CreateObjectRequest) (meta.CreateObjectResponse, error) {
  1118. var resp meta.CreateObjectResponse
  1119. t := strings.ToLower(req.ObjectType)
  1120. // Basic validation
  1121. if req.ObjectType == "" {
  1122. return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: objectType")
  1123. }
  1124. switch t {
  1125. case "database":
  1126. props := req.Properties
  1127. nameI, ok := props["databaseName"]
  1128. if !ok {
  1129. return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: databaseName")
  1130. }
  1131. name, _ := nameI.(string)
  1132. if name == "" {
  1133. return meta.CreateObjectResponse{}, fmt.Errorf("databaseName 不能为空")
  1134. }
  1135. if req.Execute {
  1136. return meta.CreateObjectResponse{}, fmt.Errorf("execute 路径未实现(当前仅支持预览 execute=false)")
  1137. }
  1138. charset := "utf8mb4"
  1139. if cs, ok := props["charset"].(string); ok && cs != "" {
  1140. charset = cs
  1141. }
  1142. ifNot := false
  1143. if v, ok := props["ifNotExists"].(bool); ok {
  1144. ifNot = v
  1145. }
  1146. sql := "CREATE DATABASE"
  1147. if ifNot {
  1148. sql += " IF NOT EXISTS"
  1149. }
  1150. sql = fmt.Sprintf("%s `%s` DEFAULT CHARACTER SET = %s;", sql, name, charset)
  1151. resp.GeneratedSQL = []string{sql}
  1152. return resp, nil
  1153. case "table":
  1154. props := req.Properties
  1155. tnameI, ok := props["tableName"]
  1156. if !ok {
  1157. return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: tableName")
  1158. }
  1159. tname, _ := tnameI.(string)
  1160. if tname == "" {
  1161. return meta.CreateObjectResponse{}, fmt.Errorf("tableName 不能为空")
  1162. }
  1163. colsI, ok := props["columns"]
  1164. if !ok {
  1165. return meta.CreateObjectResponse{}, fmt.Errorf("缺少必填字段: columns")
  1166. }
  1167. colsSlice, ok := colsI.([]interface{})
  1168. if !ok {
  1169. return meta.CreateObjectResponse{}, fmt.Errorf("columns 格式无效,期望为数组类型的列定义,例如 [{\"name\":\"id\",\"type\":\"INT\"}]")
  1170. }
  1171. if req.Execute {
  1172. return meta.CreateObjectResponse{}, fmt.Errorf("execute 路径未实现(当前仅支持预览 execute=false)")
  1173. }
  1174. engine := "InnoDB"
  1175. if e, ok := props["engine"].(string); ok && e != "" {
  1176. engine = e
  1177. }
  1178. charset := "utf8mb4"
  1179. if cs, ok := props["charset"].(string); ok && cs != "" {
  1180. charset = cs
  1181. }
  1182. ifNot := false
  1183. if v, ok := props["ifNotExists"].(bool); ok {
  1184. ifNot = v
  1185. }
  1186. // build column definitions
  1187. var colDefs []string
  1188. for _, ci := range colsSlice {
  1189. m, ok := ci.(map[string]interface{})
  1190. if !ok {
  1191. continue
  1192. }
  1193. cname, _ := m["name"].(string)
  1194. ctype, _ := m["type"].(string)
  1195. if cname == "" || ctype == "" {
  1196. // skip invalid
  1197. continue
  1198. }
  1199. nullable := true
  1200. if n, ok := m["nullable"].(bool); ok {
  1201. nullable = n
  1202. }
  1203. autoInc := false
  1204. if a, ok := m["autoIncrement"].(bool); ok {
  1205. autoInc = a
  1206. }
  1207. defStr := ""
  1208. if d, ok := m["default"]; ok && d != nil {
  1209. // simple formatting: quote strings, otherwise use fmt.Sprint
  1210. switch v := d.(type) {
  1211. case string:
  1212. defStr = fmt.Sprintf(" DEFAULT '%s'", strings.ReplaceAll(v, "'", "\\'"))
  1213. default:
  1214. defStr = fmt.Sprintf(" DEFAULT %v", v)
  1215. }
  1216. }
  1217. col := fmt.Sprintf("`%s` %s", cname, ctype)
  1218. if !nullable {
  1219. col += " NOT NULL"
  1220. }
  1221. if autoInc {
  1222. col += " AUTO_INCREMENT"
  1223. }
  1224. col += defStr
  1225. colDefs = append(colDefs, col)
  1226. }
  1227. if len(colDefs) == 0 {
  1228. return meta.CreateObjectResponse{}, fmt.Errorf("未找到有效的列定义,请检查 properties.columns 字段,示例格式: [{\"name\":\"id\",\"type\":\"INT\"}]")
  1229. }
  1230. // build create statement (include parent/db if provided)
  1231. createStmt := "CREATE TABLE"
  1232. if ifNot {
  1233. createStmt += " IF NOT EXISTS"
  1234. }
  1235. // If parent provided, include database qualifier
  1236. if req.ParentName != "" {
  1237. createStmt = fmt.Sprintf("%s `%s`.`%s` ( %s ) ENGINE=%s DEFAULT CHARSET=%s;", createStmt, req.ParentName, tname, strings.Join(colDefs, ", "), engine, charset)
  1238. } else {
  1239. createStmt = fmt.Sprintf("%s `%s` ( %s ) ENGINE=%s DEFAULT CHARSET=%s;", createStmt, tname, strings.Join(colDefs, ", "), engine, charset)
  1240. }
  1241. resp.GeneratedSQL = []string{createStmt}
  1242. return resp, nil
  1243. case "index":
  1244. props := req.Properties
  1245. iname, _ := props["indexName"].(string)
  1246. cols, _ := props["columns"].(string)
  1247. unique := false
  1248. if u, ok := props["unique"].(bool); ok {
  1249. unique = u
  1250. }
  1251. if iname == "" || cols == "" {
  1252. return meta.CreateObjectResponse{}, fmt.Errorf("indexName 与 columns 为必填字段")
  1253. }
  1254. if req.Execute {
  1255. return meta.CreateObjectResponse{}, fmt.Errorf("execute 路径未实现(当前仅支持预览 execute=false)")
  1256. }
  1257. uq := ""
  1258. if unique {
  1259. uq = "UNIQUE "
  1260. }
  1261. // parentName ideally should be table name
  1262. tableRef := iname
  1263. if req.ParentName != "" {
  1264. tableRef = req.ParentName
  1265. }
  1266. stmt := fmt.Sprintf("CREATE %sINDEX `%s` ON `%s` (%s);", uq, iname, tableRef, cols)
  1267. resp.GeneratedSQL = []string{stmt}
  1268. return resp, nil
  1269. default:
  1270. return meta.CreateObjectResponse{}, fmt.Errorf("不支持的 create 类型: %s", req.ObjectType)
  1271. }
  1272. }
  1273. // QueryData 执行数据查询(实现 DataReader 接口)
  1274. func (q *MySQLDriver) QueryData(ctx context.Context, path meta.ObjectPath, req meta.DataQueryRequest) (meta.QueryResult, error) {
  1275. db := q.db
  1276. // 解析路径:path 应为 [{"type":"database","name":"db"},{"type":"table","name":"table"}]
  1277. if len(path) < 2 {
  1278. return meta.QueryResult{}, fmt.Errorf("invalid path: need at least database and table")
  1279. }
  1280. dbName := path[0].Name
  1281. tableName := path[1].Name
  1282. // 切换到指定数据库(如果需要)
  1283. if _, err := db.ExecContext(ctx, fmt.Sprintf("USE `%s`", dbName)); err != nil {
  1284. return meta.QueryResult{}, fmt.Errorf("failed to switch to database %s: %w", dbName, err)
  1285. }
  1286. // 构建列信息
  1287. var columns []meta.DataMeta
  1288. var columnNames []string
  1289. if len(req.Columns) == 0 {
  1290. // 查询所有列
  1291. 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)
  1292. rows, err := db.QueryContext(ctx, query)
  1293. if err != nil {
  1294. return meta.QueryResult{}, fmt.Errorf("failed to query columns: %w", err)
  1295. }
  1296. defer rows.Close()
  1297. for rows.Next() {
  1298. var colName, dataType, nullable sql.NullString
  1299. if err := rows.Scan(&colName, &dataType, &nullable); err != nil {
  1300. continue
  1301. }
  1302. columns = append(columns, meta.DataMeta{
  1303. Name: colName.String,
  1304. Type: "column",
  1305. DBType: dataType.String,
  1306. Nullable: nullable.String == "YES",
  1307. })
  1308. columnNames = append(columnNames, colName.String)
  1309. }
  1310. } else {
  1311. // 指定列
  1312. columnNames = req.Columns
  1313. for _, col := range req.Columns {
  1314. columns = append(columns, meta.DataMeta{
  1315. Name: col,
  1316. Type: "column",
  1317. })
  1318. }
  1319. }
  1320. // 构建 SELECT 语句
  1321. selectClause := "*"
  1322. if len(columnNames) > 0 {
  1323. selectClause = "`" + strings.Join(columnNames, "`, `") + "`"
  1324. }
  1325. sql := fmt.Sprintf("SELECT %s FROM `%s`", selectClause, tableName)
  1326. // 添加 WHERE 子句
  1327. if len(req.Filters) > 0 {
  1328. var conditions []string
  1329. for col, val := range req.Filters {
  1330. conditions = append(conditions, fmt.Sprintf("`%s` = '%s'", col, val)) // 简单等值,注意 SQL 注入风险(生产环境需参数化)
  1331. }
  1332. sql += " WHERE " + strings.Join(conditions, " AND ")
  1333. }
  1334. // 添加 ORDER BY
  1335. if len(req.Order) > 0 {
  1336. var orders []string
  1337. for _, o := range req.Order {
  1338. dir := "ASC"
  1339. if o.Desc {
  1340. dir = "DESC"
  1341. }
  1342. orders = append(orders, fmt.Sprintf("`%s` %s", o.Column, dir))
  1343. }
  1344. sql += " ORDER BY " + strings.Join(orders, ", ")
  1345. }
  1346. // 添加 LIMIT
  1347. if req.Limit > 0 {
  1348. sql += fmt.Sprintf(" LIMIT %d", req.Limit)
  1349. if req.Offset > 0 {
  1350. sql += fmt.Sprintf(" OFFSET %d", req.Offset)
  1351. }
  1352. }
  1353. // 如果 Fetch=false,仅返回 SQL,不执行查询,但计算总数
  1354. if !req.Fetch {
  1355. var total int64
  1356. countSQL := fmt.Sprintf("SELECT COUNT(*) FROM `%s`", tableName)
  1357. if len(req.Filters) > 0 {
  1358. var conditions []string
  1359. for col, val := range req.Filters {
  1360. conditions = append(conditions, fmt.Sprintf("`%s` = '%s'", col, val))
  1361. }
  1362. countSQL += " WHERE " + strings.Join(conditions, " AND ")
  1363. }
  1364. if err := db.QueryRowContext(ctx, countSQL).Scan(&total); err != nil {
  1365. return meta.QueryResult{}, fmt.Errorf("failed to count total: %w", err)
  1366. }
  1367. return meta.QueryResult{
  1368. Columns: columns,
  1369. Rows: []meta.DataMeta{},
  1370. Total: &total,
  1371. Returned: int(total),
  1372. Truncated: false,
  1373. Stats: map[string]string{"sql": sql},
  1374. }, nil
  1375. }
  1376. // 执行查询
  1377. rows, err := db.QueryContext(ctx, sql)
  1378. if err != nil {
  1379. return meta.QueryResult{}, fmt.Errorf("failed to execute query: %w", err)
  1380. }
  1381. defer rows.Close()
  1382. // 获取列信息(如果未指定列)
  1383. if len(req.Columns) == 0 {
  1384. colTypes, err := rows.ColumnTypes()
  1385. if err != nil {
  1386. return meta.QueryResult{}, fmt.Errorf("failed to get column types: %w", err)
  1387. }
  1388. for i, ct := range colTypes {
  1389. nullable, _ := ct.Nullable()
  1390. columns[i].DBType = ct.DatabaseTypeName()
  1391. columns[i].Nullable = nullable
  1392. }
  1393. }
  1394. // 读取数据
  1395. var resultRows []meta.DataMeta
  1396. for rows.Next() {
  1397. values := make([]interface{}, len(columns))
  1398. valuePtrs := make([]interface{}, len(columns))
  1399. for i := range values {
  1400. valuePtrs[i] = &values[i]
  1401. }
  1402. if err := rows.Scan(valuePtrs...); err != nil {
  1403. return meta.QueryResult{}, fmt.Errorf("failed to scan row: %w", err)
  1404. }
  1405. var children []meta.DataMeta
  1406. for i, val := range values {
  1407. // 安全检查:确保val是有效的interface{}
  1408. if i >= len(columns) {
  1409. continue // 跳过超出列数的字段
  1410. }
  1411. // 处理 []byte 类型,转为 string 以正确显示中文
  1412. if val != nil {
  1413. if b, ok := val.([]byte); ok {
  1414. val = string(b)
  1415. }
  1416. }
  1417. // 根据值类型设置 ValueType
  1418. var valueType meta.FieldType
  1419. if val == nil {
  1420. valueType = meta.FieldTypeString // nil值当作字符串处理
  1421. } else {
  1422. // 根据值类型设置 ValueType
  1423. switch reflect.TypeOf(val).Kind() {
  1424. case reflect.String:
  1425. valueType = meta.FieldTypeString
  1426. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  1427. valueType = meta.FieldTypeInt
  1428. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  1429. valueType = meta.FieldTypeInt
  1430. case reflect.Float32, reflect.Float64:
  1431. valueType = meta.FieldTypeNumber
  1432. case reflect.Bool:
  1433. valueType = meta.FieldTypeBool
  1434. default:
  1435. valueType = meta.FieldTypeString // 默认当作字符串
  1436. }
  1437. }
  1438. children = append(children, meta.DataMeta{
  1439. Name: columns[i].Name,
  1440. Type: "value",
  1441. Value: val, // 现在 val 是原生类型,但 []byte 已转为 string
  1442. ValueType: valueType,
  1443. })
  1444. }
  1445. resultRows = append(resultRows, meta.DataMeta{
  1446. Name: getRowName(children), // 设置行 name 为第一个列的值(通常为主键或 ID)
  1447. Type: "row",
  1448. RowID: "",
  1449. Children: children,
  1450. })
  1451. }
  1452. // 计算总数(如果请求)
  1453. var total *int64
  1454. if req.IncludeTotal {
  1455. countSQL := fmt.Sprintf("SELECT COUNT(*) FROM `%s`", tableName)
  1456. if len(req.Filters) > 0 {
  1457. var conditions []string
  1458. for col, val := range req.Filters {
  1459. conditions = append(conditions, fmt.Sprintf("`%s` = '%s'", col, val))
  1460. }
  1461. countSQL += " WHERE " + strings.Join(conditions, " AND ")
  1462. }
  1463. var t int64
  1464. if err := db.QueryRowContext(ctx, countSQL).Scan(&t); err != nil {
  1465. return meta.QueryResult{}, fmt.Errorf("failed to count total: %w", err)
  1466. }
  1467. total = &t
  1468. }
  1469. return meta.QueryResult{
  1470. Columns: columns,
  1471. Rows: resultRows,
  1472. Total: total,
  1473. Returned: len(resultRows),
  1474. Truncated: req.Limit > 0 && len(resultRows) == req.Limit,
  1475. Stats: map[string]string{"query": sql},
  1476. }, nil
  1477. }
  1478. // ExecuteSQL 执行SQL语句并返回执行结果
  1479. func (q *MySQLDriver) ExecuteSQL(ctx context.Context, path meta.ObjectPath, sql string, params []interface{}) (meta.ExecuteResult, error) {
  1480. startTime := time.Now()
  1481. result := meta.ExecuteResult{
  1482. Success: false,
  1483. ExecutionTime: 0,
  1484. }
  1485. // 清理和标准化SQL
  1486. sql = strings.TrimSpace(sql)
  1487. if sql == "" {
  1488. result.ErrorMessage = "SQL语句不能为空:请提供有效的SQL查询或执行语句"
  1489. return result, nil
  1490. }
  1491. // 分析SQL类型
  1492. sqlType := q.analyzeSQLType(sql)
  1493. db := q.db
  1494. // 对于SELECT语句,使用Query执行
  1495. if strings.HasPrefix(strings.ToUpper(sqlType), "SELECT") {
  1496. rows, err := db.QueryContext(ctx, sql, params...)
  1497. if err != nil {
  1498. result.ErrorMessage = fmt.Sprintf("SELECT查询执行失败:SQL语法错误或数据库连接问题。原始错误:%v", err)
  1499. result.ExecutionTime = time.Since(startTime).Milliseconds()
  1500. return result, nil
  1501. }
  1502. defer rows.Close()
  1503. // 获取列信息
  1504. columns, err := rows.Columns()
  1505. if err != nil {
  1506. result.ErrorMessage = fmt.Sprintf("获取查询结果列信息失败:无法解析结果集结构。原始错误:%v", err)
  1507. result.ExecutionTime = time.Since(startTime).Milliseconds()
  1508. return result, nil
  1509. }
  1510. // 构建列元信息
  1511. var columnMetas []meta.DataMeta
  1512. columnTypes, err := rows.ColumnTypes()
  1513. if err == nil {
  1514. for i, col := range columns {
  1515. dbType := ""
  1516. if i < len(columnTypes) {
  1517. dbType = columnTypes[i].DatabaseTypeName()
  1518. }
  1519. columnMetas = append(columnMetas, meta.DataMeta{
  1520. Name: col,
  1521. Type: "column",
  1522. DBType: dbType,
  1523. ValueType: "string", // 简化处理
  1524. })
  1525. }
  1526. }
  1527. // 读取数据
  1528. var resultRows []meta.DataMeta
  1529. rowCount := 0
  1530. for rows.Next() && rowCount < 1000 { // 限制最大行数
  1531. values := make([]interface{}, len(columns))
  1532. valuePtrs := make([]interface{}, len(columns))
  1533. for i := range values {
  1534. valuePtrs[i] = &values[i]
  1535. }
  1536. if err := rows.Scan(valuePtrs...); err != nil {
  1537. result.ErrorMessage = fmt.Sprintf("扫描查询结果行数据失败:数据类型不匹配或结果集损坏。原始错误:%v", err)
  1538. result.ExecutionTime = time.Since(startTime).Milliseconds()
  1539. return result, nil
  1540. }
  1541. // 构建行数据
  1542. var rowValues []meta.DataMeta
  1543. for i, val := range values {
  1544. colName := ""
  1545. if i < len(columns) {
  1546. colName = columns[i]
  1547. }
  1548. // 转换值为字符串
  1549. var strVal string
  1550. if val != nil {
  1551. strVal = fmt.Sprintf("%v", val)
  1552. }
  1553. rowValues = append(rowValues, meta.DataMeta{
  1554. Name: colName,
  1555. Type: "value",
  1556. Value: strVal,
  1557. })
  1558. }
  1559. resultRows = append(resultRows, meta.DataMeta{
  1560. Type: "row",
  1561. RowID: "",
  1562. Children: rowValues,
  1563. })
  1564. rowCount++
  1565. }
  1566. // 构建查询结果
  1567. queryResult := &meta.QueryResult{
  1568. Columns: columnMetas,
  1569. Rows: resultRows,
  1570. Returned: len(resultRows),
  1571. Truncated: rowCount >= 1000,
  1572. }
  1573. result.Success = true
  1574. result.SQLType = sqlType
  1575. result.Data = queryResult
  1576. result.ExecutionTime = time.Since(startTime).Milliseconds()
  1577. result.RowsReturned = len(resultRows)
  1578. // 对于非SELECT语句,使用Exec执行
  1579. execResult, err := db.ExecContext(ctx, sql, params...)
  1580. if err != nil {
  1581. result.ErrorMessage = fmt.Sprintf("%s语句执行失败:SQL语法错误、权限不足或数据库约束冲突。原始错误:%v", sqlType, err)
  1582. result.ExecutionTime = time.Since(startTime).Milliseconds()
  1583. return result, nil
  1584. }
  1585. // 获取影响行数
  1586. affectedRows, _ := execResult.RowsAffected()
  1587. result.AffectedRows = affectedRows
  1588. // 获取最后插入ID(如果适用)
  1589. if strings.HasPrefix(strings.ToUpper(sqlType), "INSERT") {
  1590. lastInsertID, _ := execResult.LastInsertId()
  1591. result.LastInsertID = lastInsertID
  1592. }
  1593. result.Success = true
  1594. result.SQLType = sqlType
  1595. result.ExecutionTime = time.Since(startTime).Milliseconds()
  1596. }
  1597. return result, nil
  1598. }
  1599. // analyzeSQLType 分析SQL语句类型
  1600. func (q *MySQLDriver) analyzeSQLType(sql string) string {
  1601. upperSQL := strings.ToUpper(strings.TrimSpace(sql))
  1602. switch {
  1603. case strings.HasPrefix(upperSQL, "SELECT"):
  1604. return "SELECT"
  1605. case strings.HasPrefix(upperSQL, "INSERT"):
  1606. return "INSERT"
  1607. case strings.HasPrefix(upperSQL, "UPDATE"):
  1608. return "UPDATE"
  1609. case strings.HasPrefix(upperSQL, "DELETE"):
  1610. return "DELETE"
  1611. case strings.HasPrefix(upperSQL, "CREATE"):
  1612. if strings.Contains(upperSQL, "TABLE") {
  1613. return "CREATE TABLE"
  1614. } else if strings.Contains(upperSQL, "DATABASE") {
  1615. return "CREATE DATABASE"
  1616. } else if strings.Contains(upperSQL, "INDEX") {
  1617. return "CREATE INDEX"
  1618. }
  1619. return "CREATE"
  1620. case strings.HasPrefix(upperSQL, "DROP"):
  1621. if strings.Contains(upperSQL, "TABLE") {
  1622. return "DROP TABLE"
  1623. } else if strings.Contains(upperSQL, "DATABASE") {
  1624. return "DROP DATABASE"
  1625. } else if strings.Contains(upperSQL, "INDEX") {
  1626. return "DROP INDEX"
  1627. }
  1628. return "DROP"
  1629. case strings.HasPrefix(upperSQL, "ALTER"):
  1630. return "ALTER"
  1631. case strings.HasPrefix(upperSQL, "TRUNCATE"):
  1632. return "TRUNCATE"
  1633. default:
  1634. return "UNKNOWN"
  1635. }
  1636. }
  1637. // getRowName 安全地获取行名,避免空指针引用
  1638. func getRowName(children []meta.DataMeta) string {
  1639. if len(children) == 0 {
  1640. return ""
  1641. }
  1642. if children[0].Value == nil {
  1643. return "<NULL>"
  1644. }
  1645. return fmt.Sprintf("%v", children[0].Value)
  1646. }
  1647. // DescribeUpdateTemplate 返回指定对象的修改模板,用于前端动态生成表单
  1648. func (q *MySQLDriver) DescribeUpdateTemplate(ctx context.Context, path meta.ObjectPath) (meta.ObjectTemplate, error) {
  1649. if len(path) == 0 {
  1650. return meta.ObjectTemplate{}, fmt.Errorf("路径不能为空")
  1651. }
  1652. lastEntry := path[len(path)-1]
  1653. objectType := strings.ToLower(lastEntry.Type)
  1654. objectName := lastEntry.Name
  1655. switch objectType {
  1656. case "database":
  1657. // 获取数据库的当前信息
  1658. currentValues, err := q.getCurrentDatabaseInfo(ctx, objectName)
  1659. if err != nil {
  1660. return meta.ObjectTemplate{}, err
  1661. }
  1662. tpl := meta.ObjectTemplate{
  1663. Operation: "update",
  1664. ObjectType: "database",
  1665. ParentHint: "",
  1666. Fields: getDatabaseTemplateFields(true, currentValues),
  1667. Current: currentValues,
  1668. Example: map[string]string{"charset": "utf8mb4"},
  1669. }
  1670. return tpl, nil
  1671. case "table":
  1672. if len(path) < 2 {
  1673. return meta.ObjectTemplate{}, fmt.Errorf("表路径需要包含数据库信息")
  1674. }
  1675. dbName := path[0].Name
  1676. tableName := objectName
  1677. // 获取表的当前信息
  1678. currentValues, err := q.getCurrentTableInfo(ctx, dbName, tableName)
  1679. if err != nil {
  1680. return meta.ObjectTemplate{}, err
  1681. }
  1682. tpl := meta.ObjectTemplate{
  1683. Operation: "update",
  1684. ObjectType: "table",
  1685. ParentHint: "database: " + dbName,
  1686. Fields: getTableTemplateFields(true, currentValues),
  1687. Current: currentValues,
  1688. Example: map[string]string{"engine": "InnoDB", "charset": "utf8mb4"},
  1689. }
  1690. return tpl, nil
  1691. default:
  1692. return meta.ObjectTemplate{}, fmt.Errorf("不支持的 update 类型: %s", objectType)
  1693. }
  1694. }
  1695. // UpdateObject 执行对象的修改操作
  1696. func (q *MySQLDriver) UpdateObject(ctx context.Context, req meta.UpdateObjectRequest) (meta.UpdateObjectResponse, error) {
  1697. if len(req.Path) == 0 {
  1698. return meta.UpdateObjectResponse{}, fmt.Errorf("路径不能为空")
  1699. }
  1700. lastEntry := req.Path[len(req.Path)-1]
  1701. objectType := strings.ToLower(lastEntry.Type)
  1702. objectName := lastEntry.Name
  1703. var sqls []string
  1704. switch objectType {
  1705. case "database":
  1706. dbName := objectName
  1707. charset, charsetOk := req.Properties["charset"].(string)
  1708. collation, collationOk := req.Properties["collation"].(string)
  1709. if charsetOk && charset != "" {
  1710. sql := fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET %s", dbName, charset)
  1711. sqls = append(sqls, sql)
  1712. }
  1713. if collationOk && collation != "" {
  1714. sql := fmt.Sprintf("ALTER DATABASE `%s` COLLATE %s", dbName, collation)
  1715. sqls = append(sqls, sql)
  1716. }
  1717. case "table":
  1718. if len(req.Path) < 2 {
  1719. return meta.UpdateObjectResponse{}, fmt.Errorf("表路径需要包含数据库信息")
  1720. }
  1721. dbName := req.Path[0].Name
  1722. tableName := objectName
  1723. engine, engineOk := req.Properties["engine"].(string)
  1724. charset, charsetOk := req.Properties["charset"].(string)
  1725. collation, collationOk := req.Properties["collation"].(string)
  1726. if engineOk && engine != "" {
  1727. sql := fmt.Sprintf("ALTER TABLE `%s`.`%s` ENGINE = %s", dbName, tableName, engine)
  1728. sqls = append(sqls, sql)
  1729. }
  1730. if charsetOk && charset != "" {
  1731. sql := fmt.Sprintf("ALTER TABLE `%s`.`%s` CONVERT TO CHARACTER SET %s", dbName, tableName, charset)
  1732. sqls = append(sqls, sql)
  1733. }
  1734. if collationOk && collation != "" {
  1735. sql := fmt.Sprintf("ALTER TABLE `%s`.`%s` COLLATE %s", dbName, tableName, collation)
  1736. sqls = append(sqls, sql)
  1737. }
  1738. default:
  1739. return meta.UpdateObjectResponse{}, fmt.Errorf("不支持的 update 类型: %s", objectType)
  1740. }
  1741. if !req.Execute {
  1742. // 预览模式,只返回生成的SQL
  1743. return meta.UpdateObjectResponse{
  1744. GeneratedSQL: sqls,
  1745. }, nil
  1746. }
  1747. // 执行模式
  1748. for _, sql := range sqls {
  1749. if _, err := q.db.ExecContext(ctx, sql); err != nil {
  1750. return meta.UpdateObjectResponse{}, fmt.Errorf("执行SQL失败: %s, 错误: %v", sql, err)
  1751. }
  1752. }
  1753. return meta.UpdateObjectResponse{
  1754. GeneratedSQL: sqls,
  1755. }, nil
  1756. }
  1757. // DescribeDeleteTemplate 返回指定对象的删除模板,用于前端显示删除确认和影响预览
  1758. func (q *MySQLDriver) DescribeDeleteTemplate(ctx context.Context, path meta.ObjectPath) (meta.ObjectTemplate, error) {
  1759. if len(path) == 0 {
  1760. return meta.ObjectTemplate{}, fmt.Errorf("路径不能为空")
  1761. }
  1762. lastEntry := path[len(path)-1]
  1763. objectType := strings.ToLower(lastEntry.Type)
  1764. objectName := lastEntry.Name
  1765. switch objectType {
  1766. case "database":
  1767. dbName := objectName
  1768. // 查询数据库中的表数量
  1769. var tableCount int64
  1770. err := q.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?", dbName).Scan(&tableCount)
  1771. if err != nil {
  1772. return meta.ObjectTemplate{}, fmt.Errorf("获取数据库表数量失败: %w", err)
  1773. }
  1774. tpl := meta.ObjectTemplate{
  1775. Operation: "delete",
  1776. ObjectType: "database",
  1777. ParentHint: "",
  1778. Current: map[string]string{
  1779. "databaseName": dbName,
  1780. "tableCount": fmt.Sprintf("%d", tableCount),
  1781. },
  1782. Fields: []meta.TemplateField{
  1783. {Name: "databaseName", Label: "Database Name", Type: meta.FieldTypeString, Required: true, Current: dbName, Editable: &[]bool{false}[0]},
  1784. {Name: "tableCount", Label: "Tables Count", Type: meta.FieldTypeString, Required: false, Current: fmt.Sprintf("%d", tableCount), Editable: &[]bool{false}[0]},
  1785. {Name: "confirmDelete", Label: "Confirm Delete", Type: meta.FieldTypeBool, Required: true, Help: "删除数据库将同时删除所有表和数据,此操作不可恢复"},
  1786. },
  1787. Notes: "警告:删除数据库将永久删除所有表、数据和相关对象。此操作不可恢复。",
  1788. }
  1789. return tpl, nil
  1790. case "table":
  1791. if len(path) < 2 {
  1792. return meta.ObjectTemplate{}, fmt.Errorf("表路径需要包含数据库信息")
  1793. }
  1794. dbName := path[0].Name
  1795. tableName := objectName
  1796. // 查询表中的行数
  1797. var rowCount int64
  1798. query := fmt.Sprintf("SELECT COUNT(*) FROM `%s`.`%s`", dbName, tableName)
  1799. err := q.db.QueryRowContext(ctx, query).Scan(&rowCount)
  1800. if err != nil {
  1801. // 如果查询失败,可能表不存在或权限问题
  1802. rowCount = -1
  1803. }
  1804. tpl := meta.ObjectTemplate{
  1805. Operation: "delete",
  1806. ObjectType: "table",
  1807. ParentHint: "database: " + dbName,
  1808. Current: map[string]string{
  1809. "tableName": tableName,
  1810. "rowCount": fmt.Sprintf("%d", rowCount),
  1811. },
  1812. Fields: []meta.TemplateField{
  1813. {Name: "tableName", Label: "Table Name", Type: meta.FieldTypeString, Required: true, Current: tableName, Editable: &[]bool{false}[0]},
  1814. {Name: "rowCount", Label: "Row Count", Type: meta.FieldTypeString, Required: false, Current: fmt.Sprintf("%d", rowCount), Editable: &[]bool{false}[0]},
  1815. {Name: "confirmDelete", Label: "Confirm Delete", Type: meta.FieldTypeBool, Required: true, Help: "删除表将永久删除所有数据,此操作不可恢复"},
  1816. },
  1817. Notes: "警告:删除表将永久删除所有数据。此操作不可恢复。",
  1818. }
  1819. return tpl, nil
  1820. default:
  1821. return meta.ObjectTemplate{}, fmt.Errorf("不支持的 delete 类型: %s", objectType)
  1822. }
  1823. }