meta.go 69 KB

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