Нема описа
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

get_common_query.go 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. package dbs
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "regexp"
  6. "strings"
  7. "time"
  8. "git.x2erp.com/qdy/go-svc-mcp/internal/mcp"
  9. )
  10. func init() {
  11. mcp.Register("get_common_query", "通用数据库查询工具,支持参数化查询和结果列自定义",
  12. map[string]interface{}{
  13. "type": "object",
  14. "properties": map[string]interface{}{
  15. "query_sql": map[string]interface{}{
  16. "type": "string",
  17. "description": "基础SQL查询语句(可包含WHERE、ORDER BY,或使用where_clause和order_by参数)",
  18. },
  19. "where_clause": map[string]interface{}{
  20. "type": "string",
  21. "description": "WHERE条件语句(可选,如果query_sql中已包含则不需要)",
  22. "default": "",
  23. },
  24. "order_by": map[string]interface{}{
  25. "type": "string",
  26. "description": "ORDER BY排序语句(可选,如果query_sql中已包含则不需要)",
  27. "default": "",
  28. },
  29. "params": map[string]interface{}{
  30. "type": "object",
  31. "description": "查询参数键值对,用于参数化查询(例如 {\"status\": \"ACTIVE\", \"min_age\": 18})",
  32. "default": map[string]interface{}{},
  33. },
  34. "columns_config": map[string]interface{}{
  35. "type": "array",
  36. "description": "执行查询返回的列配置信息,定义字段显示名称和宽度",
  37. "items": map[string]interface{}{
  38. "type": "object",
  39. "properties": map[string]interface{}{
  40. "field_name": map[string]interface{}{
  41. "type": "string",
  42. "description": "查询返回的字段名称",
  43. },
  44. "display_name": map[string]interface{}{
  45. "type": "string",
  46. "description": "字段显示名称(中文)",
  47. "default": "",
  48. },
  49. "width": map[string]interface{}{
  50. "type": "integer",
  51. "description": "前端显示宽度(像素)",
  52. "default": 100,
  53. },
  54. "align": map[string]interface{}{
  55. "type": "string",
  56. "description": "对齐方式(left/center/right)",
  57. "default": "left",
  58. },
  59. },
  60. "required": []string{"field_name"},
  61. },
  62. "default": []interface{}{},
  63. },
  64. "page": map[string]interface{}{
  65. "type": "integer",
  66. "description": "页码(从1开始,0表示不分页)",
  67. "default": 0,
  68. "minimum": 0,
  69. },
  70. "page_size": map[string]interface{}{
  71. "type": "integer",
  72. "description": "每页记录数(最大100条)",
  73. "default": 10,
  74. "minimum": 1,
  75. "maximum": 100,
  76. },
  77. "schema": map[string]interface{}{
  78. "type": "string",
  79. "description": "数据库模式/名称(可选,部分数据库需要)",
  80. "default": "",
  81. },
  82. "include_total_count": map[string]interface{}{
  83. "type": "boolean",
  84. "description": "是否包含总记录数",
  85. "default": true,
  86. },
  87. "database_key": map[string]interface{}{
  88. "type": "string",
  89. "description": "数据库配置键名:warehouse(仓库数据库)或 business(业务数据库),可选,默认使用主数据库",
  90. "enum": []string{"warehouse", "business"},
  91. "default": "",
  92. },
  93. },
  94. "required": []string{"query_sql"},
  95. },
  96. func(input json.RawMessage, deps *mcp.ToolDependencies) (interface{}, error) {
  97. var params struct {
  98. QuerySQL string `json:"query_sql"`
  99. WhereClause string `json:"where_clause"`
  100. OrderBy string `json:"order_by"`
  101. Params map[string]interface{} `json:"params"`
  102. ColumnsConfig []map[string]interface{} `json:"columns_config"`
  103. Page int `json:"page"`
  104. PageSize int `json:"page_size"`
  105. Schema string `json:"schema"`
  106. IncludeTotalCount bool `json:"include_total_count"`
  107. DatabaseKey string `json:"database_key"`
  108. }
  109. if len(input) > 0 {
  110. if err := json.Unmarshal(input, &params); err != nil {
  111. return nil, err
  112. }
  113. }
  114. // 设置默认值
  115. if params.PageSize == 0 {
  116. params.PageSize = 10
  117. }
  118. if params.PageSize > 100 {
  119. params.PageSize = 100
  120. }
  121. // 获取数据库工厂
  122. dbFactory, err := GetDBFactory(params.DatabaseKey, deps)
  123. if err != nil {
  124. return nil, err
  125. }
  126. // 获取数据库类型
  127. dbType := dbFactory.GetDBType()
  128. // 构建完整SQL
  129. finalSQL := params.QuerySQL
  130. if params.WhereClause != "" {
  131. // 检查query_sql是否已包含WHERE关键字
  132. if strings.Contains(strings.ToUpper(finalSQL), "WHERE") {
  133. finalSQL += " AND " + params.WhereClause
  134. } else {
  135. finalSQL += " WHERE " + params.WhereClause
  136. }
  137. }
  138. if params.OrderBy != "" {
  139. finalSQL += " ORDER BY " + params.OrderBy
  140. }
  141. // 根据数据库类型处理参数绑定
  142. processedSQL, queryParams, err := processParameters(finalSQL, params.Params, dbType)
  143. if err != nil {
  144. return nil, fmt.Errorf("参数处理失败: %v", err)
  145. }
  146. // 执行查询
  147. results, err := dbFactory.QuerySliceMapWithParams(processedSQL, queryParams...)
  148. if err != nil {
  149. return nil, fmt.Errorf("查询执行失败: %v", err)
  150. }
  151. // 处理列配置
  152. columnsMeta := processColumnsMetadata(results, params.ColumnsConfig)
  153. // 计算总记录数(如果需要)
  154. totalCount := int64(0)
  155. if params.IncludeTotalCount && params.Page > 0 {
  156. countSQL, countParams, err := buildCountSQL(finalSQL, params.Params, dbType)
  157. if err != nil {
  158. return nil, fmt.Errorf("构建计数SQL失败: %v", err)
  159. }
  160. countResults, err := dbFactory.QuerySliceMapWithParams(countSQL, countParams...)
  161. if err == nil && len(countResults) > 0 {
  162. if count, ok := countResults[0]["total_count"].(int64); ok {
  163. totalCount = count
  164. }
  165. }
  166. }
  167. // 应用分页(如果启用)
  168. var pagedResults []map[string]interface{}
  169. if params.Page > 0 && params.PageSize > 0 {
  170. offset := (params.Page - 1) * params.PageSize
  171. if offset < len(results) {
  172. end := offset + params.PageSize
  173. if end > len(results) {
  174. end = len(results)
  175. }
  176. pagedResults = results[offset:end]
  177. }
  178. } else {
  179. pagedResults = results
  180. }
  181. // 构建响应
  182. response := map[string]interface{}{
  183. "tenant_id": deps.ReqCtx.TenantID,
  184. "user_id": deps.ReqCtx.UserID,
  185. "database_type": dbType,
  186. "database_name": dbFactory.GetDatabaseName(),
  187. "schema": params.Schema,
  188. "original_sql": params.QuerySQL,
  189. "processed_sql": processedSQL,
  190. "parameters": params.Params,
  191. "columns_metadata": columnsMeta,
  192. "data": pagedResults,
  193. "data_count": len(pagedResults),
  194. "total_count": totalCount,
  195. "page": params.Page,
  196. "page_size": params.PageSize,
  197. "include_total_count": params.IncludeTotalCount,
  198. "timestamp": time.Now().Format(time.RFC3339),
  199. }
  200. // 如果启用了分页,添加分页信息
  201. if params.Page > 0 {
  202. totalPages := 0
  203. if totalCount > 0 {
  204. totalPages = int((totalCount + int64(params.PageSize) - 1) / int64(params.PageSize))
  205. }
  206. response["total_pages"] = totalPages
  207. response["has_more"] = int64(params.Page*params.PageSize) < totalCount
  208. }
  209. return response, nil
  210. },
  211. )
  212. }
  213. // processParameters 处理SQL参数绑定,根据数据库类型转换命名参数
  214. func processParameters(sql string, params map[string]interface{}, dbType string) (string, []interface{}, error) {
  215. if len(params) == 0 {
  216. return sql, []interface{}{}, nil
  217. }
  218. // 根据数据库类型选择参数处理器
  219. switch dbType {
  220. case "mysql", "doris":
  221. return processMySQLParameters(sql, params)
  222. case "postgresql":
  223. return processPostgreSQLParameters(sql, params)
  224. case "oracle":
  225. return processOracleParameters(sql, params)
  226. case "sqlserver":
  227. return processSQLServerParameters(sql, params)
  228. default:
  229. return sql, []interface{}{}, fmt.Errorf("不支持的数据库类型: %s", dbType)
  230. }
  231. }
  232. // parseNamedParameters 解析命名参数,返回替换后的SQL和有序参数值
  233. func parseNamedParameters(sql string, params map[string]interface{}, placeholderFunc func(int) string) (string, []interface{}, error) {
  234. // 正则匹配 :paramName 格式的参数
  235. re := regexp.MustCompile(`:([a-zA-Z_][a-zA-Z0-9_]*)`)
  236. matches := re.FindAllStringSubmatch(sql, -1)
  237. if len(matches) == 0 {
  238. return sql, []interface{}{}, nil
  239. }
  240. // 确定参数名到索引的映射(按第一次出现的顺序)
  241. paramIndex := make(map[string]int)
  242. var paramOrder []string
  243. for _, match := range matches {
  244. paramName := match[1]
  245. if _, exists := paramIndex[paramName]; !exists {
  246. paramIndex[paramName] = len(paramOrder)
  247. paramOrder = append(paramOrder, paramName)
  248. }
  249. }
  250. // 构建参数值列表(按索引顺序)
  251. paramValues := make([]interface{}, len(paramOrder))
  252. for i, paramName := range paramOrder {
  253. value, exists := params[paramName]
  254. if !exists {
  255. return "", nil, fmt.Errorf("参数 '%s' 未提供", paramName)
  256. }
  257. paramValues[i] = value
  258. }
  259. // 替换占位符
  260. replacedSQL := re.ReplaceAllStringFunc(sql, func(match string) string {
  261. paramName := match[1:]
  262. index := paramIndex[paramName]
  263. return placeholderFunc(index + 1) // 通常占位符从1开始
  264. })
  265. return replacedSQL, paramValues, nil
  266. }
  267. // processColumnsMetadata 处理列元数据,合并查询结果和列配置
  268. func processColumnsMetadata(results []map[string]interface{}, columnsConfig []map[string]interface{}) []map[string]interface{} {
  269. if len(results) == 0 {
  270. return []map[string]interface{}{}
  271. }
  272. // 从第一条结果中提取列名
  273. var columns []map[string]interface{}
  274. if len(results) > 0 {
  275. for fieldName := range results[0] {
  276. columnMeta := map[string]interface{}{
  277. "field_name": fieldName,
  278. "display_name": fieldName,
  279. "width": 100,
  280. "align": "left",
  281. }
  282. // 查找列配置
  283. for _, config := range columnsConfig {
  284. if configFieldName, ok := config["field_name"].(string); ok && configFieldName == fieldName {
  285. if displayName, ok := config["display_name"].(string); ok && displayName != "" {
  286. columnMeta["display_name"] = displayName
  287. }
  288. if width, ok := config["width"].(float64); ok && width > 0 {
  289. columnMeta["width"] = int(width)
  290. }
  291. if align, ok := config["align"].(string); ok && align != "" {
  292. columnMeta["align"] = align
  293. }
  294. break
  295. }
  296. }
  297. columns = append(columns, columnMeta)
  298. }
  299. }
  300. return columns
  301. }
  302. // buildCountSQL 构建计数SQL
  303. func buildCountSQL(originalSQL string, params map[string]interface{}, dbType string) (string, []interface{}, error) {
  304. // 移除ORDER BY子句
  305. countSQL := regexp.MustCompile(`(?i)\s+ORDER BY\s+.*$`).ReplaceAllString(originalSQL, "")
  306. // 构建COUNT查询
  307. countSQL = fmt.Sprintf("SELECT COUNT(*) as total_count FROM (%s) as count_query", countSQL)
  308. // 处理参数
  309. return processParameters(countSQL, params, dbType)
  310. }
  311. // processMySQLParameters 处理MySQL/Doris数据库参数绑定
  312. func processMySQLParameters(sql string, params map[string]interface{}) (string, []interface{}, error) {
  313. if len(params) == 0 {
  314. return sql, []interface{}{}, nil
  315. }
  316. // 正则匹配 :paramName 格式的参数
  317. re := regexp.MustCompile(`:([a-zA-Z_][a-zA-Z0-9_]*)`)
  318. matches := re.FindAllStringSubmatch(sql, -1)
  319. if len(matches) == 0 {
  320. return sql, []interface{}{}, nil
  321. }
  322. // 按出现顺序收集参数值(允许重复)
  323. var paramValues []interface{}
  324. replacedSQL := re.ReplaceAllStringFunc(sql, func(match string) string {
  325. paramName := match[1:]
  326. value, exists := params[paramName]
  327. if !exists {
  328. // 如果参数未提供,保留原占位符(后续会报错)
  329. return match
  330. }
  331. paramValues = append(paramValues, value)
  332. return "?"
  333. })
  334. // 检查是否有参数未提供
  335. if len(paramValues) != len(matches) {
  336. // 找出未提供的参数名
  337. for _, match := range matches {
  338. paramName := match[1]
  339. if _, exists := params[paramName]; !exists {
  340. return "", nil, fmt.Errorf("参数 '%s' 未提供", paramName)
  341. }
  342. }
  343. }
  344. return replacedSQL, paramValues, nil
  345. }
  346. // processPostgreSQLParameters 处理PostgreSQL数据库参数绑定
  347. func processPostgreSQLParameters(sql string, params map[string]interface{}) (string, []interface{}, error) {
  348. if len(params) == 0 {
  349. return sql, []interface{}{}, nil
  350. }
  351. // 正则匹配 :paramName 格式的参数
  352. re := regexp.MustCompile(`:([a-zA-Z_][a-zA-Z0-9_]*)`)
  353. matches := re.FindAllStringSubmatch(sql, -1)
  354. if len(matches) == 0 {
  355. return sql, []interface{}{}, nil
  356. }
  357. // 确定参数名到索引的映射(按第一次出现的顺序)
  358. paramIndex := make(map[string]int)
  359. var paramOrder []string
  360. for _, match := range matches {
  361. paramName := match[1]
  362. if _, exists := paramIndex[paramName]; !exists {
  363. paramIndex[paramName] = len(paramOrder)
  364. paramOrder = append(paramOrder, paramName)
  365. }
  366. }
  367. // 构建参数值列表(按索引顺序)
  368. paramValues := make([]interface{}, len(paramOrder))
  369. for i, paramName := range paramOrder {
  370. value, exists := params[paramName]
  371. if !exists {
  372. return "", nil, fmt.Errorf("参数 '%s' 未提供", paramName)
  373. }
  374. paramValues[i] = value
  375. }
  376. // 替换占位符为 $1, $2, ...
  377. replacedSQL := re.ReplaceAllStringFunc(sql, func(match string) string {
  378. paramName := match[1:]
  379. index := paramIndex[paramName]
  380. return fmt.Sprintf("$%d", index+1)
  381. })
  382. return replacedSQL, paramValues, nil
  383. }
  384. // processOracleParameters 处理Oracle数据库参数绑定
  385. func processOracleParameters(sql string, params map[string]interface{}) (string, []interface{}, error) {
  386. if len(params) == 0 {
  387. return sql, []interface{}{}, nil
  388. }
  389. // 正则匹配 :paramName 格式的参数
  390. re := regexp.MustCompile(`:([a-zA-Z_][a-zA-Z0-9_]*)`)
  391. matches := re.FindAllStringSubmatch(sql, -1)
  392. if len(matches) == 0 {
  393. return sql, []interface{}{}, nil
  394. }
  395. // 确定参数名到索引的映射(按第一次出现的顺序)
  396. paramIndex := make(map[string]int)
  397. var paramOrder []string
  398. for _, match := range matches {
  399. paramName := match[1]
  400. if _, exists := paramIndex[paramName]; !exists {
  401. paramIndex[paramName] = len(paramOrder)
  402. paramOrder = append(paramOrder, paramName)
  403. }
  404. }
  405. // 构建参数值列表(按索引顺序)
  406. paramValues := make([]interface{}, len(paramOrder))
  407. for i, paramName := range paramOrder {
  408. value, exists := params[paramName]
  409. if !exists {
  410. return "", nil, fmt.Errorf("参数 '%s' 未提供", paramName)
  411. }
  412. paramValues[i] = value
  413. }
  414. // 替换占位符为 :1, :2, ...(Oracle支持数字占位符)
  415. replacedSQL := re.ReplaceAllStringFunc(sql, func(match string) string {
  416. paramName := match[1:]
  417. index := paramIndex[paramName]
  418. return fmt.Sprintf(":%d", index+1)
  419. })
  420. return replacedSQL, paramValues, nil
  421. }
  422. // processSQLServerParameters 处理SQL Server数据库参数绑定
  423. func processSQLServerParameters(sql string, params map[string]interface{}) (string, []interface{}, error) {
  424. if len(params) == 0 {
  425. return sql, []interface{}{}, nil
  426. }
  427. // 正则匹配 :paramName 格式的参数
  428. re := regexp.MustCompile(`:([a-zA-Z_][a-zA-Z0-9_]*)`)
  429. matches := re.FindAllStringSubmatch(sql, -1)
  430. if len(matches) == 0 {
  431. return sql, []interface{}{}, nil
  432. }
  433. // 确定参数名到索引的映射(按第一次出现的顺序)
  434. paramIndex := make(map[string]int)
  435. var paramOrder []string
  436. for _, match := range matches {
  437. paramName := match[1]
  438. if _, exists := paramIndex[paramName]; !exists {
  439. paramIndex[paramName] = len(paramOrder)
  440. paramOrder = append(paramOrder, paramName)
  441. }
  442. }
  443. // 构建参数值列表(按索引顺序)
  444. paramValues := make([]interface{}, len(paramOrder))
  445. for i, paramName := range paramOrder {
  446. value, exists := params[paramName]
  447. if !exists {
  448. return "", nil, fmt.Errorf("参数 '%s' 未提供", paramName)
  449. }
  450. paramValues[i] = value
  451. }
  452. // 替换占位符为 @p1, @p2, ...(SQL Server支持命名参数)
  453. replacedSQL := re.ReplaceAllStringFunc(sql, func(match string) string {
  454. paramName := match[1:]
  455. index := paramIndex[paramName]
  456. return fmt.Sprintf("@p%d", index+1)
  457. })
  458. return replacedSQL, paramValues, nil
  459. }