@neverinfamous/mysql-mcp 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/.dockerignore +1 -0
  2. package/.gitattributes +18 -0
  3. package/.github/workflows/codeql.yml +2 -10
  4. package/.github/workflows/docker-publish.yml +15 -13
  5. package/CHANGELOG.md +287 -1
  6. package/DOCKER_README.md +100 -265
  7. package/Dockerfile +5 -0
  8. package/README.md +124 -59
  9. package/VERSION +1 -1
  10. package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
  11. package/dist/__tests__/mocks/adapter.js +2 -0
  12. package/dist/__tests__/mocks/adapter.js.map +1 -1
  13. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
  14. package/dist/adapters/DatabaseAdapter.js +50 -9
  15. package/dist/adapters/DatabaseAdapter.js.map +1 -1
  16. package/dist/adapters/mysql/MySQLAdapter.d.ts +6 -0
  17. package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
  18. package/dist/adapters/mysql/MySQLAdapter.js +8 -0
  19. package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
  20. package/dist/adapters/mysql/SchemaManager.js +16 -15
  21. package/dist/adapters/mysql/SchemaManager.js.map +1 -1
  22. package/dist/adapters/mysql/prompts/index.js +10 -20
  23. package/dist/adapters/mysql/prompts/index.js.map +1 -1
  24. package/dist/adapters/mysql/prompts/proxysqlSetup.js +1 -1
  25. package/dist/adapters/mysql/resources/docstore.d.ts.map +1 -1
  26. package/dist/adapters/mysql/resources/docstore.js +10 -7
  27. package/dist/adapters/mysql/resources/docstore.js.map +1 -1
  28. package/dist/adapters/mysql/resources/events.js +11 -8
  29. package/dist/adapters/mysql/resources/events.js.map +1 -1
  30. package/dist/adapters/mysql/resources/indexes.d.ts.map +1 -1
  31. package/dist/adapters/mysql/resources/indexes.js +12 -15
  32. package/dist/adapters/mysql/resources/indexes.js.map +1 -1
  33. package/dist/adapters/mysql/resources/innodb.d.ts.map +1 -1
  34. package/dist/adapters/mysql/resources/innodb.js +20 -17
  35. package/dist/adapters/mysql/resources/innodb.js.map +1 -1
  36. package/dist/adapters/mysql/resources/locks.d.ts.map +1 -1
  37. package/dist/adapters/mysql/resources/locks.js +9 -6
  38. package/dist/adapters/mysql/resources/locks.js.map +1 -1
  39. package/dist/adapters/mysql/resources/performance.d.ts.map +1 -1
  40. package/dist/adapters/mysql/resources/performance.js +15 -15
  41. package/dist/adapters/mysql/resources/performance.js.map +1 -1
  42. package/dist/adapters/mysql/resources/spatial.d.ts.map +1 -1
  43. package/dist/adapters/mysql/resources/spatial.js +9 -6
  44. package/dist/adapters/mysql/resources/spatial.js.map +1 -1
  45. package/dist/adapters/mysql/resources/sysschema.d.ts.map +1 -1
  46. package/dist/adapters/mysql/resources/sysschema.js +12 -9
  47. package/dist/adapters/mysql/resources/sysschema.js.map +1 -1
  48. package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
  49. package/dist/adapters/mysql/tools/admin/backup.js +170 -121
  50. package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
  51. package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
  52. package/dist/adapters/mysql/tools/admin/maintenance.js +106 -57
  53. package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
  54. package/dist/adapters/mysql/tools/admin/monitoring.d.ts.map +1 -1
  55. package/dist/adapters/mysql/tools/admin/monitoring.js +183 -101
  56. package/dist/adapters/mysql/tools/admin/monitoring.js.map +1 -1
  57. package/dist/adapters/mysql/tools/cluster/group-replication.d.ts.map +1 -1
  58. package/dist/adapters/mysql/tools/cluster/group-replication.js +164 -120
  59. package/dist/adapters/mysql/tools/cluster/group-replication.js.map +1 -1
  60. package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
  61. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +212 -145
  62. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
  63. package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -1
  64. package/dist/adapters/mysql/tools/codemode/index.js +6 -4
  65. package/dist/adapters/mysql/tools/codemode/index.js.map +1 -1
  66. package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
  67. package/dist/adapters/mysql/tools/core.js +152 -29
  68. package/dist/adapters/mysql/tools/core.js.map +1 -1
  69. package/dist/adapters/mysql/tools/docstore.d.ts.map +1 -1
  70. package/dist/adapters/mysql/tools/docstore.js +340 -163
  71. package/dist/adapters/mysql/tools/docstore.js.map +1 -1
  72. package/dist/adapters/mysql/tools/events.d.ts.map +1 -1
  73. package/dist/adapters/mysql/tools/events.js +284 -198
  74. package/dist/adapters/mysql/tools/events.js.map +1 -1
  75. package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
  76. package/dist/adapters/mysql/tools/json/core.js +11 -39
  77. package/dist/adapters/mysql/tools/json/core.js.map +1 -1
  78. package/dist/adapters/mysql/tools/json/enhanced.d.ts.map +1 -1
  79. package/dist/adapters/mysql/tools/json/enhanced.js +15 -33
  80. package/dist/adapters/mysql/tools/json/enhanced.js.map +1 -1
  81. package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
  82. package/dist/adapters/mysql/tools/json/helpers.js +13 -24
  83. package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
  84. package/dist/adapters/mysql/tools/partitioning.js +3 -0
  85. package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
  86. package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
  87. package/dist/adapters/mysql/tools/performance/analysis.js +89 -60
  88. package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
  89. package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
  90. package/dist/adapters/mysql/tools/performance/optimization.js +151 -127
  91. package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
  92. package/dist/adapters/mysql/tools/proxysql.d.ts +1 -1
  93. package/dist/adapters/mysql/tools/proxysql.d.ts.map +1 -1
  94. package/dist/adapters/mysql/tools/proxysql.js +289 -176
  95. package/dist/adapters/mysql/tools/proxysql.js.map +1 -1
  96. package/dist/adapters/mysql/tools/replication.js +75 -49
  97. package/dist/adapters/mysql/tools/replication.js.map +1 -1
  98. package/dist/adapters/mysql/tools/roles.d.ts.map +1 -1
  99. package/dist/adapters/mysql/tools/roles.js +224 -182
  100. package/dist/adapters/mysql/tools/roles.js.map +1 -1
  101. package/dist/adapters/mysql/tools/router.d.ts.map +1 -1
  102. package/dist/adapters/mysql/tools/router.js +168 -67
  103. package/dist/adapters/mysql/tools/router.js.map +1 -1
  104. package/dist/adapters/mysql/tools/schema/constraints.d.ts.map +1 -1
  105. package/dist/adapters/mysql/tools/schema/constraints.js +21 -3
  106. package/dist/adapters/mysql/tools/schema/constraints.js.map +1 -1
  107. package/dist/adapters/mysql/tools/schema/management.d.ts.map +1 -1
  108. package/dist/adapters/mysql/tools/schema/management.js +61 -14
  109. package/dist/adapters/mysql/tools/schema/management.js.map +1 -1
  110. package/dist/adapters/mysql/tools/schema/routines.d.ts.map +1 -1
  111. package/dist/adapters/mysql/tools/schema/routines.js +27 -4
  112. package/dist/adapters/mysql/tools/schema/routines.js.map +1 -1
  113. package/dist/adapters/mysql/tools/schema/scheduled_events.d.ts.map +1 -1
  114. package/dist/adapters/mysql/tools/schema/scheduled_events.js +24 -3
  115. package/dist/adapters/mysql/tools/schema/scheduled_events.js.map +1 -1
  116. package/dist/adapters/mysql/tools/schema/triggers.d.ts.map +1 -1
  117. package/dist/adapters/mysql/tools/schema/triggers.js +23 -2
  118. package/dist/adapters/mysql/tools/schema/triggers.js.map +1 -1
  119. package/dist/adapters/mysql/tools/schema/views.d.ts.map +1 -1
  120. package/dist/adapters/mysql/tools/schema/views.js +47 -7
  121. package/dist/adapters/mysql/tools/schema/views.js.map +1 -1
  122. package/dist/adapters/mysql/tools/security/audit.d.ts.map +1 -1
  123. package/dist/adapters/mysql/tools/security/audit.js +102 -34
  124. package/dist/adapters/mysql/tools/security/audit.js.map +1 -1
  125. package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
  126. package/dist/adapters/mysql/tools/security/data-protection.js +264 -205
  127. package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
  128. package/dist/adapters/mysql/tools/security/encryption.d.ts.map +1 -1
  129. package/dist/adapters/mysql/tools/security/encryption.js +137 -104
  130. package/dist/adapters/mysql/tools/security/encryption.js.map +1 -1
  131. package/dist/adapters/mysql/tools/shell/backup.d.ts.map +1 -1
  132. package/dist/adapters/mysql/tools/shell/backup.js +71 -59
  133. package/dist/adapters/mysql/tools/shell/backup.js.map +1 -1
  134. package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
  135. package/dist/adapters/mysql/tools/shell/restore.js +61 -47
  136. package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
  137. package/dist/adapters/mysql/tools/spatial/geometry.d.ts.map +1 -1
  138. package/dist/adapters/mysql/tools/spatial/geometry.js +19 -5
  139. package/dist/adapters/mysql/tools/spatial/geometry.js.map +1 -1
  140. package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
  141. package/dist/adapters/mysql/tools/spatial/operations.js +42 -17
  142. package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
  143. package/dist/adapters/mysql/tools/spatial/queries.d.ts.map +1 -1
  144. package/dist/adapters/mysql/tools/spatial/queries.js +109 -57
  145. package/dist/adapters/mysql/tools/spatial/queries.js.map +1 -1
  146. package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
  147. package/dist/adapters/mysql/tools/spatial/setup.js +103 -50
  148. package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
  149. package/dist/adapters/mysql/tools/stats/comparative.d.ts.map +1 -1
  150. package/dist/adapters/mysql/tools/stats/comparative.js +128 -79
  151. package/dist/adapters/mysql/tools/stats/comparative.js.map +1 -1
  152. package/dist/adapters/mysql/tools/stats/descriptive.d.ts.map +1 -1
  153. package/dist/adapters/mysql/tools/stats/descriptive.js +174 -102
  154. package/dist/adapters/mysql/tools/stats/descriptive.js.map +1 -1
  155. package/dist/adapters/mysql/tools/sysschema/activity.d.ts.map +1 -1
  156. package/dist/adapters/mysql/tools/sysschema/activity.js +50 -25
  157. package/dist/adapters/mysql/tools/sysschema/activity.js.map +1 -1
  158. package/dist/adapters/mysql/tools/sysschema/performance.d.ts.map +1 -1
  159. package/dist/adapters/mysql/tools/sysschema/performance.js +121 -66
  160. package/dist/adapters/mysql/tools/sysschema/performance.js.map +1 -1
  161. package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
  162. package/dist/adapters/mysql/tools/sysschema/resources.js +101 -64
  163. package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
  164. package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
  165. package/dist/adapters/mysql/tools/text/fulltext.js +18 -32
  166. package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
  167. package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
  168. package/dist/adapters/mysql/tools/transactions.js +48 -23
  169. package/dist/adapters/mysql/tools/transactions.js.map +1 -1
  170. package/dist/adapters/mysql/types/proxysql-types.d.ts +15 -0
  171. package/dist/adapters/mysql/types/proxysql-types.d.ts.map +1 -1
  172. package/dist/adapters/mysql/types/proxysql-types.js +33 -1
  173. package/dist/adapters/mysql/types/proxysql-types.js.map +1 -1
  174. package/dist/adapters/mysql/types/router-types.d.ts +1 -1
  175. package/dist/adapters/mysql/types/router-types.js +1 -1
  176. package/dist/adapters/mysql/types/router-types.js.map +1 -1
  177. package/dist/adapters/mysql/types/shell-types.js +2 -2
  178. package/dist/adapters/mysql/types/shell-types.js.map +1 -1
  179. package/dist/adapters/mysql/types.d.ts +485 -21
  180. package/dist/adapters/mysql/types.d.ts.map +1 -1
  181. package/dist/adapters/mysql/types.js +546 -19
  182. package/dist/adapters/mysql/types.js.map +1 -1
  183. package/dist/auth/scopes.js +1 -1
  184. package/dist/auth/scopes.js.map +1 -1
  185. package/dist/codemode/api.d.ts +3 -2
  186. package/dist/codemode/api.d.ts.map +1 -1
  187. package/dist/codemode/api.js +80 -5
  188. package/dist/codemode/api.js.map +1 -1
  189. package/dist/codemode/sandbox-factory.js +1 -1
  190. package/dist/codemode/sandbox-factory.js.map +1 -1
  191. package/dist/codemode/types.d.ts +26 -0
  192. package/dist/codemode/types.d.ts.map +1 -1
  193. package/dist/codemode/types.js +2 -0
  194. package/dist/codemode/types.js.map +1 -1
  195. package/dist/codemode/worker-sandbox.d.ts +4 -2
  196. package/dist/codemode/worker-sandbox.d.ts.map +1 -1
  197. package/dist/codemode/worker-sandbox.js +66 -7
  198. package/dist/codemode/worker-sandbox.js.map +1 -1
  199. package/dist/codemode/worker-script.d.ts +3 -0
  200. package/dist/codemode/worker-script.d.ts.map +1 -1
  201. package/dist/codemode/worker-script.js +128 -75
  202. package/dist/codemode/worker-script.js.map +1 -1
  203. package/dist/constants/ServerInstructions.d.ts +1 -1
  204. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  205. package/dist/constants/ServerInstructions.js +37 -31
  206. package/dist/constants/ServerInstructions.js.map +1 -1
  207. package/dist/filtering/ToolConstants.d.ts +1 -1
  208. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  209. package/dist/filtering/ToolConstants.js +1 -2
  210. package/dist/filtering/ToolConstants.js.map +1 -1
  211. package/dist/pool/ConnectionPool.d.ts.map +1 -1
  212. package/dist/pool/ConnectionPool.js.map +1 -1
  213. package/dist/transports/http.d.ts.map +1 -1
  214. package/dist/transports/http.js +6 -0
  215. package/dist/transports/http.js.map +1 -1
  216. package/dist/utils/validators.d.ts +1 -1
  217. package/dist/utils/validators.d.ts.map +1 -1
  218. package/dist/utils/validators.js.map +1 -1
  219. package/package.json +4 -4
  220. package/releases/v2.3.0-release-notes.md +20 -20
  221. package/releases/v2.3.1-release-notes.md +34 -0
  222. package/releases/v3.0.0-release-notes.md +81 -0
  223. package/src/__tests__/mocks/adapter.ts +3 -0
  224. package/src/__tests__/perf.test.ts +6 -6
  225. package/src/adapters/DatabaseAdapter.ts +58 -9
  226. package/src/adapters/__tests__/DatabaseAdapter.test.ts +89 -8
  227. package/src/adapters/mysql/MySQLAdapter.ts +17 -2
  228. package/src/adapters/mysql/SchemaManager.ts +21 -21
  229. package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
  230. package/src/adapters/mysql/prompts/index.ts +12 -22
  231. package/src/adapters/mysql/prompts/proxysqlSetup.ts +1 -1
  232. package/src/adapters/mysql/resources/docstore.ts +13 -10
  233. package/src/adapters/mysql/resources/events.ts +12 -12
  234. package/src/adapters/mysql/resources/indexes.ts +17 -19
  235. package/src/adapters/mysql/resources/innodb.ts +23 -22
  236. package/src/adapters/mysql/resources/locks.ts +9 -7
  237. package/src/adapters/mysql/resources/performance.ts +23 -18
  238. package/src/adapters/mysql/resources/spatial.ts +9 -7
  239. package/src/adapters/mysql/resources/sysschema.ts +12 -11
  240. package/src/adapters/mysql/tools/__tests__/core.test.ts +126 -55
  241. package/src/adapters/mysql/tools/__tests__/docstore.test.ts +459 -88
  242. package/src/adapters/mysql/tools/__tests__/events.test.ts +281 -103
  243. package/src/adapters/mysql/tools/__tests__/proxysql.test.ts +128 -28
  244. package/src/adapters/mysql/tools/__tests__/replication.test.ts +48 -2
  245. package/src/adapters/mysql/tools/__tests__/roles.test.ts +15 -18
  246. package/src/adapters/mysql/tools/__tests__/router.test.ts +32 -5
  247. package/src/adapters/mysql/tools/__tests__/security.test.ts +126 -2
  248. package/src/adapters/mysql/tools/__tests__/security_injection.test.ts +84 -76
  249. package/src/adapters/mysql/tools/__tests__/security_integration.test.ts +47 -50
  250. package/src/adapters/mysql/tools/__tests__/spatial.test.ts +11 -10
  251. package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +54 -38
  252. package/src/adapters/mysql/tools/__tests__/stats.test.ts +285 -152
  253. package/src/adapters/mysql/tools/__tests__/transactions.test.ts +13 -13
  254. package/src/adapters/mysql/tools/admin/__tests__/backup.test.ts +171 -25
  255. package/src/adapters/mysql/tools/admin/__tests__/maintenance.test.ts +240 -4
  256. package/src/adapters/mysql/tools/admin/__tests__/monitoring-summary.test.ts +274 -0
  257. package/src/adapters/mysql/tools/admin/__tests__/monitoring.test.ts +94 -5
  258. package/src/adapters/mysql/tools/admin/backup.ts +193 -143
  259. package/src/adapters/mysql/tools/admin/maintenance.ts +118 -69
  260. package/src/adapters/mysql/tools/admin/monitoring.ts +201 -125
  261. package/src/adapters/mysql/tools/cluster/__tests__/group-replication.test.ts +69 -0
  262. package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +141 -0
  263. package/src/adapters/mysql/tools/cluster/group-replication.ts +172 -132
  264. package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +231 -157
  265. package/src/adapters/mysql/tools/codemode/__tests__/codemode-tool.test.ts +227 -0
  266. package/src/adapters/mysql/tools/codemode/index.ts +5 -3
  267. package/src/adapters/mysql/tools/core.ts +152 -38
  268. package/src/adapters/mysql/tools/docstore.ts +422 -205
  269. package/src/adapters/mysql/tools/events.ts +334 -233
  270. package/src/adapters/mysql/tools/json/__tests__/core.test.ts +20 -0
  271. package/src/adapters/mysql/tools/json/__tests__/enhanced.test.ts +82 -50
  272. package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +42 -3
  273. package/src/adapters/mysql/tools/json/core.ts +21 -42
  274. package/src/adapters/mysql/tools/json/enhanced.ts +22 -37
  275. package/src/adapters/mysql/tools/json/helpers.ts +21 -25
  276. package/src/adapters/mysql/tools/partitioning.ts +3 -0
  277. package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +98 -5
  278. package/src/adapters/mysql/tools/performance/__tests__/optimization-coverage.test.ts +515 -0
  279. package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +187 -0
  280. package/src/adapters/mysql/tools/performance/analysis.ts +95 -69
  281. package/src/adapters/mysql/tools/performance/optimization.ts +182 -153
  282. package/src/adapters/mysql/tools/proxysql.ts +314 -209
  283. package/src/adapters/mysql/tools/replication.ts +84 -57
  284. package/src/adapters/mysql/tools/roles.ts +274 -226
  285. package/src/adapters/mysql/tools/router.ts +181 -85
  286. package/src/adapters/mysql/tools/schema/__tests__/constraints.test.ts +13 -0
  287. package/src/adapters/mysql/tools/schema/__tests__/management.test.ts +60 -25
  288. package/src/adapters/mysql/tools/schema/__tests__/scheduled_events.test.ts +11 -0
  289. package/src/adapters/mysql/tools/schema/__tests__/triggers.test.ts +25 -4
  290. package/src/adapters/mysql/tools/schema/__tests__/views.test.ts +46 -14
  291. package/src/adapters/mysql/tools/schema/constraints.ts +22 -3
  292. package/src/adapters/mysql/tools/schema/management.ts +60 -15
  293. package/src/adapters/mysql/tools/schema/routines.ts +26 -4
  294. package/src/adapters/mysql/tools/schema/scheduled_events.ts +25 -3
  295. package/src/adapters/mysql/tools/schema/triggers.ts +27 -2
  296. package/src/adapters/mysql/tools/schema/views.ts +46 -8
  297. package/src/adapters/mysql/tools/security/__tests__/audit.test.ts +90 -4
  298. package/src/adapters/mysql/tools/security/audit.ts +113 -39
  299. package/src/adapters/mysql/tools/security/data-protection.ts +293 -233
  300. package/src/adapters/mysql/tools/security/encryption.ts +172 -139
  301. package/src/adapters/mysql/tools/shell/__tests__/backup.test.ts +29 -0
  302. package/src/adapters/mysql/tools/shell/backup.ts +90 -73
  303. package/src/adapters/mysql/tools/shell/restore.ts +62 -48
  304. package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +22 -14
  305. package/src/adapters/mysql/tools/spatial/__tests__/queries.test.ts +65 -51
  306. package/src/adapters/mysql/tools/spatial/geometry.ts +23 -7
  307. package/src/adapters/mysql/tools/spatial/operations.ts +60 -31
  308. package/src/adapters/mysql/tools/spatial/queries.ts +142 -65
  309. package/src/adapters/mysql/tools/spatial/setup.ts +121 -55
  310. package/src/adapters/mysql/tools/stats/__tests__/comparative.test.ts +12 -10
  311. package/src/adapters/mysql/tools/stats/comparative.ts +150 -98
  312. package/src/adapters/mysql/tools/stats/descriptive.ts +204 -127
  313. package/src/adapters/mysql/tools/sysschema/__tests__/error-paths.test.ts +222 -0
  314. package/src/adapters/mysql/tools/sysschema/__tests__/performance.test.ts +45 -0
  315. package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +6 -3
  316. package/src/adapters/mysql/tools/sysschema/activity.ts +52 -27
  317. package/src/adapters/mysql/tools/sysschema/performance.ts +132 -68
  318. package/src/adapters/mysql/tools/sysschema/resources.ts +105 -67
  319. package/src/adapters/mysql/tools/text/__tests__/fulltext.test.ts +45 -17
  320. package/src/adapters/mysql/tools/text/fulltext.ts +27 -38
  321. package/src/adapters/mysql/tools/transactions.ts +49 -24
  322. package/src/adapters/mysql/types/proxysql-types.ts +38 -1
  323. package/src/adapters/mysql/types/router-types.ts +1 -1
  324. package/src/adapters/mysql/types/shell-types.ts +2 -2
  325. package/src/adapters/mysql/types.ts +632 -19
  326. package/src/auth/__tests__/scopes.test.ts +2 -2
  327. package/src/auth/scopes.ts +1 -1
  328. package/src/codemode/__tests__/api.test.ts +417 -0
  329. package/src/codemode/__tests__/sandbox-factory.test.ts +158 -0
  330. package/src/codemode/__tests__/sandbox.test.ts +301 -0
  331. package/src/codemode/__tests__/security.test.ts +368 -0
  332. package/src/codemode/__tests__/worker-sandbox.test.ts +179 -0
  333. package/src/codemode/__tests__/worker-script.test.ts +226 -0
  334. package/src/codemode/api.ts +89 -5
  335. package/src/codemode/sandbox-factory.ts +1 -1
  336. package/src/codemode/types.ts +34 -0
  337. package/src/codemode/worker-sandbox.ts +74 -7
  338. package/src/codemode/worker-script.ts +157 -86
  339. package/src/constants/ServerInstructions.ts +37 -31
  340. package/src/filtering/ToolConstants.ts +1 -2
  341. package/src/filtering/__tests__/ToolFilter.test.ts +9 -9
  342. package/src/pool/ConnectionPool.ts +4 -1
  343. package/src/transports/__tests__/http.test.ts +15 -3
  344. package/src/transports/http.ts +12 -0
  345. package/src/utils/validators.ts +2 -1
  346. package/vitest.config.ts +3 -1
  347. package/CODE_MODE.md +0 -245
@@ -301,12 +301,9 @@ describe("Performance Analysis Tools", () => {
301
301
  );
302
302
  await tool.handler({ limit: 10, minTime: 0.5 }, mockContext);
303
303
 
304
+ // 0.5 sec = 500,000,000,000 picoseconds (AVG_TIMER_WAIT is in picoseconds)
304
305
  const call = mockAdapter.executeReadQuery.mock.calls[0][0] as string;
305
- // 0.5 sec = 500,000,000 picoseconds? No, performance_schema uses picoseconds usually,
306
- // the code multiplies by 1,000,000,000 (10^9), suggesting nanoseconds or something.
307
- // Wait, TIMER_WAIT is usually picoseconds (10^-12). 10^9 conversion suggests seconds?
308
- // Code: AVG_TIMER_WAIT > ${minTime * 1000000000}
309
- expect(call).toContain("AVG_TIMER_WAIT > 500000000");
306
+ expect(call).toContain("AVG_TIMER_WAIT > 500000000000");
310
307
  });
311
308
 
312
309
  it("should clamp overflowed timer values to -1 with overflow flag", async () => {
@@ -414,6 +411,23 @@ describe("Performance Analysis Tools", () => {
414
411
  expect(typeof result.slowQueries[0]["total_time_ms"]).toBe("number");
415
412
  expect(result.slowQueries[0]["overflow"]).toBeUndefined();
416
413
  });
414
+
415
+ it("should return structured error on query failure", async () => {
416
+ mockAdapter.executeReadQuery.mockRejectedValue(
417
+ new Error("Access denied for performance_schema"),
418
+ );
419
+
420
+ const tool = createSlowQueriesTool(
421
+ mockAdapter as unknown as MySQLAdapter,
422
+ );
423
+ const result = (await tool.handler({ limit: 10 }, mockContext)) as {
424
+ success: boolean;
425
+ error: string;
426
+ };
427
+
428
+ expect(result.success).toBe(false);
429
+ expect(result.error).toContain("Access denied");
430
+ });
417
431
  });
418
432
 
419
433
  describe("createQueryStatsTool", () => {
@@ -543,6 +557,21 @@ describe("Performance Analysis Tools", () => {
543
557
  expect(typeof result.queries[0]["total_time_ms"]).toBe("number");
544
558
  expect(result.queries[0]["overflow"]).toBeUndefined();
545
559
  });
560
+
561
+ it("should return structured error on query failure", async () => {
562
+ mockAdapter.executeReadQuery.mockRejectedValue(
563
+ new Error("Access denied for performance_schema"),
564
+ );
565
+
566
+ const tool = createQueryStatsTool(mockAdapter as unknown as MySQLAdapter);
567
+ const result = (await tool.handler({}, mockContext)) as {
568
+ success: boolean;
569
+ error: string;
570
+ };
571
+
572
+ expect(result.success).toBe(false);
573
+ expect(result.error).toContain("Access denied");
574
+ });
546
575
  });
547
576
 
548
577
  describe("createIndexUsageTool", () => {
@@ -609,6 +638,21 @@ describe("Performance Analysis Tools", () => {
609
638
  const call = mockAdapter.executeReadQuery.mock.calls[0][0] as string;
610
639
  expect(call).toContain("LIMIT 10");
611
640
  });
641
+
642
+ it("should return structured error on query failure", async () => {
643
+ mockAdapter.executeReadQuery.mockRejectedValue(
644
+ new Error("Access denied for performance_schema"),
645
+ );
646
+
647
+ const tool = createIndexUsageTool(mockAdapter as unknown as MySQLAdapter);
648
+ const result = (await tool.handler({}, mockContext)) as {
649
+ success: boolean;
650
+ error: string;
651
+ };
652
+
653
+ expect(result.success).toBe(false);
654
+ expect(result.error).toContain("Access denied");
655
+ });
612
656
  });
613
657
 
614
658
  describe("createTableStatsTool", () => {
@@ -650,6 +694,21 @@ describe("Performance Analysis Tools", () => {
650
694
  expect(result.exists).toBe(false);
651
695
  expect(result.table).toBe("nonexistent");
652
696
  });
697
+
698
+ it("should return structured error on query failure", async () => {
699
+ mockAdapter.executeReadQuery.mockRejectedValue(
700
+ new Error("Access denied for information_schema"),
701
+ );
702
+
703
+ const tool = createTableStatsTool(mockAdapter as unknown as MySQLAdapter);
704
+ const result = (await tool.handler({ table: "users" }, mockContext)) as {
705
+ success: boolean;
706
+ error: string;
707
+ };
708
+
709
+ expect(result.success).toBe(false);
710
+ expect(result.error).toContain("Access denied");
711
+ });
653
712
  });
654
713
 
655
714
  describe("createBufferPoolStatsTool", () => {
@@ -683,6 +742,23 @@ describe("Performance Analysis Tools", () => {
683
742
  expect(call).not.toContain("SELECT *");
684
743
  expect(result).toHaveProperty("bufferPoolStats");
685
744
  });
745
+
746
+ it("should return structured error on query failure", async () => {
747
+ mockAdapter.executeReadQuery.mockRejectedValue(
748
+ new Error("Access denied for information_schema"),
749
+ );
750
+
751
+ const tool = createBufferPoolStatsTool(
752
+ mockAdapter as unknown as MySQLAdapter,
753
+ );
754
+ const result = (await tool.handler({}, mockContext)) as {
755
+ success: boolean;
756
+ error: string;
757
+ };
758
+
759
+ expect(result.success).toBe(false);
760
+ expect(result.error).toContain("Access denied");
761
+ });
686
762
  });
687
763
 
688
764
  describe("createThreadStatsTool", () => {
@@ -713,5 +789,22 @@ describe("Performance Analysis Tools", () => {
713
789
  expect(call).toContain("performance_schema.threads");
714
790
  expect(result).toHaveProperty("threads");
715
791
  });
792
+
793
+ it("should return structured error on query failure", async () => {
794
+ mockAdapter.executeReadQuery.mockRejectedValue(
795
+ new Error("Access denied for performance_schema"),
796
+ );
797
+
798
+ const tool = createThreadStatsTool(
799
+ mockAdapter as unknown as MySQLAdapter,
800
+ );
801
+ const result = (await tool.handler({}, mockContext)) as {
802
+ success: boolean;
803
+ error: string;
804
+ };
805
+
806
+ expect(result.success).toBe(false);
807
+ expect(result.error).toContain("Access denied");
808
+ });
716
809
  });
717
810
  });
@@ -0,0 +1,515 @@
1
+ /**
2
+ * mysql-mcp - Optimization Trace Summary & Error Path Tests
3
+ *
4
+ * Tests for extractTraceSummary (via optimizer_trace summary=true)
5
+ * and error paths in optimization tools.
6
+ * Targets lines 40-157, 308, 314, 321, 487 in optimization.ts.
7
+ */
8
+
9
+ import { describe, it, expect, vi, beforeEach } from "vitest";
10
+ import {
11
+ createIndexRecommendationTool,
12
+ createQueryRewriteTool,
13
+ createForceIndexTool,
14
+ createOptimizerTraceTool,
15
+ } from "../optimization.js";
16
+ import type { MySQLAdapter } from "../../../MySQLAdapter.js";
17
+ import {
18
+ createMockMySQLAdapter,
19
+ createMockRequestContext,
20
+ createMockQueryResult,
21
+ } from "../../../../../__tests__/mocks/index.js";
22
+
23
+ describe("Optimization Tools — Summary & Error Paths", () => {
24
+ let mockAdapter: ReturnType<typeof createMockMySQLAdapter>;
25
+ let mockContext: ReturnType<typeof createMockRequestContext>;
26
+
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ mockAdapter = createMockMySQLAdapter();
30
+ mockContext = createMockRequestContext();
31
+ });
32
+
33
+ // ===========================================================================
34
+ // extractTraceSummary (via optimizer trace with summary=true)
35
+ // ===========================================================================
36
+ describe("optimizer trace summary mode", () => {
37
+ it("should return error when no trace data", async () => {
38
+ // Enable tracing
39
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
40
+ // Execute query
41
+ mockAdapter.executeReadQuery
42
+ .mockResolvedValueOnce(createMockQueryResult([])) // user query
43
+ .mockResolvedValueOnce(createMockQueryResult([])); // OPTIMIZER_TRACE empty
44
+
45
+ const tool = createOptimizerTraceTool(
46
+ mockAdapter as unknown as MySQLAdapter,
47
+ );
48
+ const result = (await tool.handler(
49
+ { query: "SELECT 1", summary: true },
50
+ mockContext,
51
+ )) as { error?: string; decisions?: unknown[] };
52
+
53
+ expect(result.error).toContain("No trace data");
54
+ });
55
+
56
+ it("should extract index selection decisions from trace", async () => {
57
+ const traceJson = JSON.stringify({
58
+ steps: [
59
+ {
60
+ join_optimization: {
61
+ steps: [
62
+ {
63
+ rows_estimation: [
64
+ {
65
+ table: "users",
66
+ range_analysis: {
67
+ chosen_range_access_summary: {
68
+ chosen: true,
69
+ range_access_plan: {
70
+ type: "range",
71
+ index: "idx_email",
72
+ rows: 10,
73
+ },
74
+ cost_for_plan: 5.5,
75
+ },
76
+ },
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ },
82
+ },
83
+ ],
84
+ });
85
+
86
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
87
+ mockAdapter.executeReadQuery
88
+ .mockResolvedValueOnce(createMockQueryResult([])) // user query
89
+ .mockResolvedValueOnce(createMockQueryResult([{ TRACE: traceJson }]));
90
+
91
+ const tool = createOptimizerTraceTool(
92
+ mockAdapter as unknown as MySQLAdapter,
93
+ );
94
+ const result = (await tool.handler(
95
+ { query: "SELECT * FROM users WHERE email = 'test'", summary: true },
96
+ mockContext,
97
+ )) as { decisions: { type: string; index?: string }[] };
98
+
99
+ expect(result.decisions).toHaveLength(1);
100
+ expect(result.decisions[0].type).toBe("index_selection");
101
+ expect(result.decisions[0].index).toBe("idx_email");
102
+ });
103
+
104
+ it("should extract table scan decisions", async () => {
105
+ const traceJson = JSON.stringify({
106
+ steps: [
107
+ {
108
+ join_optimization: {
109
+ steps: [
110
+ {
111
+ rows_estimation: [
112
+ {
113
+ table: "orders",
114
+ range_analysis: {
115
+ table_scan: { rows: 1000, cost: 200 },
116
+ },
117
+ },
118
+ ],
119
+ },
120
+ ],
121
+ },
122
+ },
123
+ ],
124
+ });
125
+
126
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
127
+ mockAdapter.executeReadQuery
128
+ .mockResolvedValueOnce(createMockQueryResult([]))
129
+ .mockResolvedValueOnce(createMockQueryResult([{ TRACE: traceJson }]));
130
+
131
+ const tool = createOptimizerTraceTool(
132
+ mockAdapter as unknown as MySQLAdapter,
133
+ );
134
+ const result = (await tool.handler(
135
+ { query: "SELECT * FROM orders", summary: true },
136
+ mockContext,
137
+ )) as { decisions: { type: string; table?: string }[] };
138
+
139
+ expect(result.decisions).toHaveLength(1);
140
+ expect(result.decisions[0].type).toBe("table_scan");
141
+ expect(result.decisions[0].table).toBe("orders");
142
+ });
143
+
144
+ it("should extract access path decisions", async () => {
145
+ const traceJson = JSON.stringify({
146
+ steps: [
147
+ {
148
+ join_optimization: {
149
+ steps: [
150
+ {
151
+ considered_execution_plans: [
152
+ {
153
+ table: "products",
154
+ best_access_path: {
155
+ considered_access_paths: [
156
+ {
157
+ access_type: "ref",
158
+ index: "idx_category",
159
+ rows: 50,
160
+ cost: 10.5,
161
+ chosen: true,
162
+ },
163
+ {
164
+ access_type: "full_scan",
165
+ rows: 1000,
166
+ cost: 200,
167
+ chosen: false,
168
+ },
169
+ ],
170
+ },
171
+ },
172
+ ],
173
+ },
174
+ ],
175
+ },
176
+ },
177
+ ],
178
+ });
179
+
180
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
181
+ mockAdapter.executeReadQuery
182
+ .mockResolvedValueOnce(createMockQueryResult([]))
183
+ .mockResolvedValueOnce(createMockQueryResult([{ TRACE: traceJson }]));
184
+
185
+ const tool = createOptimizerTraceTool(
186
+ mockAdapter as unknown as MySQLAdapter,
187
+ );
188
+ const result = (await tool.handler(
189
+ {
190
+ query: "SELECT * FROM products WHERE category_id = 1",
191
+ summary: true,
192
+ },
193
+ mockContext,
194
+ )) as { decisions: { type: string; accessType?: string }[] };
195
+
196
+ expect(result.decisions).toHaveLength(1);
197
+ expect(result.decisions[0].type).toBe("access_path");
198
+ expect(result.decisions[0].accessType).toBe("ref");
199
+ });
200
+
201
+ it("should handle invalid trace JSON", async () => {
202
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
203
+ mockAdapter.executeReadQuery
204
+ .mockResolvedValueOnce(createMockQueryResult([]))
205
+ .mockResolvedValueOnce(
206
+ createMockQueryResult([{ TRACE: "not valid json {{{" }]),
207
+ );
208
+
209
+ const tool = createOptimizerTraceTool(
210
+ mockAdapter as unknown as MySQLAdapter,
211
+ );
212
+ const result = (await tool.handler(
213
+ { query: "SELECT 1", summary: true },
214
+ mockContext,
215
+ )) as { error?: string };
216
+
217
+ expect(result.error).toContain("Failed to parse trace");
218
+ });
219
+
220
+ it("should handle TRACE column with non-string value", async () => {
221
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
222
+ mockAdapter.executeReadQuery
223
+ .mockResolvedValueOnce(createMockQueryResult([]))
224
+ .mockResolvedValueOnce(createMockQueryResult([{ TRACE: 12345 }]));
225
+
226
+ const tool = createOptimizerTraceTool(
227
+ mockAdapter as unknown as MySQLAdapter,
228
+ );
229
+ const result = (await tool.handler(
230
+ { query: "SELECT 1", summary: true },
231
+ mockContext,
232
+ )) as { error?: string };
233
+
234
+ expect(result.error).toContain("Invalid trace format");
235
+ });
236
+
237
+ it("should handle query execution failure in trace mode", async () => {
238
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
239
+ mockAdapter.executeReadQuery.mockRejectedValueOnce(
240
+ new Error("Table 'nonexistent' doesn't exist"),
241
+ );
242
+
243
+ const tool = createOptimizerTraceTool(
244
+ mockAdapter as unknown as MySQLAdapter,
245
+ );
246
+ const result = (await tool.handler(
247
+ { query: "SELECT * FROM nonexistent", summary: true },
248
+ mockContext,
249
+ )) as { error?: string; decisions?: unknown[] };
250
+
251
+ expect(result.error).toContain("doesn't exist");
252
+ expect(result.decisions).toEqual([]);
253
+ });
254
+
255
+ it("should handle query execution failure in non-summary mode", async () => {
256
+ mockAdapter.executeQuery.mockResolvedValue(createMockQueryResult([]));
257
+ mockAdapter.executeReadQuery.mockRejectedValueOnce(
258
+ new Error("Query failed: Table error"),
259
+ );
260
+
261
+ const tool = createOptimizerTraceTool(
262
+ mockAdapter as unknown as MySQLAdapter,
263
+ );
264
+ const result = (await tool.handler(
265
+ { query: "SELECT * FROM nonexistent" },
266
+ mockContext,
267
+ )) as { error?: string; trace?: unknown };
268
+
269
+ expect(result.error).toBeDefined();
270
+ expect(result.trace).toBeNull();
271
+ });
272
+ });
273
+
274
+ // ===========================================================================
275
+ // Query Rewrite edge cases
276
+ // ===========================================================================
277
+ describe("query rewrite edge cases", () => {
278
+ it("should detect OR in WHERE clause", async () => {
279
+ mockAdapter.executeReadQuery.mockResolvedValue(createMockQueryResult([]));
280
+
281
+ const tool = createQueryRewriteTool(
282
+ mockAdapter as unknown as MySQLAdapter,
283
+ );
284
+ const result = (await tool.handler(
285
+ { query: "SELECT * FROM users WHERE id = 1 OR name = 'test'" },
286
+ mockContext,
287
+ )) as { suggestions: string[] };
288
+
289
+ expect(
290
+ result.suggestions.some((s: string) => s.includes("OR conditions")),
291
+ ).toBe(true);
292
+ });
293
+
294
+ it("should detect NOT IN pattern", async () => {
295
+ mockAdapter.executeReadQuery.mockResolvedValue(createMockQueryResult([]));
296
+
297
+ const tool = createQueryRewriteTool(
298
+ mockAdapter as unknown as MySQLAdapter,
299
+ );
300
+ const result = (await tool.handler(
301
+ { query: "SELECT * FROM users WHERE id NOT IN (1,2,3)" },
302
+ mockContext,
303
+ )) as { suggestions: string[] };
304
+
305
+ expect(result.suggestions.some((s: string) => s.includes("NOT IN"))).toBe(
306
+ true,
307
+ );
308
+ });
309
+
310
+ it("should detect ORDER BY without LIMIT", async () => {
311
+ mockAdapter.executeReadQuery.mockResolvedValue(createMockQueryResult([]));
312
+
313
+ const tool = createQueryRewriteTool(
314
+ mockAdapter as unknown as MySQLAdapter,
315
+ );
316
+ const result = (await tool.handler(
317
+ { query: "SELECT id FROM users ORDER BY name" },
318
+ mockContext,
319
+ )) as { suggestions: string[] };
320
+
321
+ expect(
322
+ result.suggestions.some((s: string) =>
323
+ s.includes("ORDER BY without LIMIT"),
324
+ ),
325
+ ).toBe(true);
326
+ });
327
+
328
+ it("should detect leading wildcard in LIKE", async () => {
329
+ mockAdapter.executeReadQuery.mockResolvedValue(createMockQueryResult([]));
330
+
331
+ const tool = createQueryRewriteTool(
332
+ mockAdapter as unknown as MySQLAdapter,
333
+ );
334
+ const result = (await tool.handler(
335
+ { query: "SELECT * FROM users WHERE name LIKE '%test'" },
336
+ mockContext,
337
+ )) as { suggestions: string[] };
338
+
339
+ expect(
340
+ result.suggestions.some((s: string) => s.includes("Leading wildcard")),
341
+ ).toBe(true);
342
+ });
343
+
344
+ it("should handle EXPLAIN returning JSON", async () => {
345
+ mockAdapter.executeReadQuery.mockResolvedValue(
346
+ createMockQueryResult([
347
+ { EXPLAIN: '{"query_block": {"select_id": 1}}' },
348
+ ]),
349
+ );
350
+
351
+ const tool = createQueryRewriteTool(
352
+ mockAdapter as unknown as MySQLAdapter,
353
+ );
354
+ const result = (await tool.handler(
355
+ { query: "SELECT 1 FROM dual LIMIT 1" },
356
+ mockContext,
357
+ )) as { explainPlan: unknown };
358
+
359
+ expect(result.explainPlan).toBeDefined();
360
+ });
361
+
362
+ it("should handle EXPLAIN failure gracefully", async () => {
363
+ mockAdapter.executeReadQuery.mockRejectedValue(
364
+ new Error("Query failed: Unknown table"),
365
+ );
366
+
367
+ const tool = createQueryRewriteTool(
368
+ mockAdapter as unknown as MySQLAdapter,
369
+ );
370
+ const result = (await tool.handler(
371
+ { query: "SELECT * FROM nonexistent LIMIT 1" },
372
+ mockContext,
373
+ )) as { explainError: string; explainPlan: unknown };
374
+
375
+ expect(result.explainError).toBeDefined();
376
+ expect(result.explainPlan).toBeNull();
377
+ });
378
+
379
+ it("should use sql alias for query", async () => {
380
+ mockAdapter.executeReadQuery.mockResolvedValue(createMockQueryResult([]));
381
+
382
+ const tool = createQueryRewriteTool(
383
+ mockAdapter as unknown as MySQLAdapter,
384
+ );
385
+ const result = (await tool.handler(
386
+ { sql: "SELECT 1 FROM dual LIMIT 1" },
387
+ mockContext,
388
+ )) as { originalQuery: string };
389
+
390
+ expect(result.originalQuery).toBe("SELECT 1 FROM dual LIMIT 1");
391
+ });
392
+
393
+ it("should return error when no query provided", async () => {
394
+ const tool = createQueryRewriteTool(
395
+ mockAdapter as unknown as MySQLAdapter,
396
+ );
397
+ const result = (await tool.handler({}, mockContext)) as {
398
+ success: boolean;
399
+ error: string;
400
+ };
401
+
402
+ expect(result.success).toBe(false);
403
+ });
404
+ });
405
+
406
+ // ===========================================================================
407
+ // Index Recommendation edge cases
408
+ // ===========================================================================
409
+ describe("index recommendation edge cases", () => {
410
+ it("should suggest indexes for timestamp columns", async () => {
411
+ mockAdapter.describeTable.mockResolvedValue({
412
+ columns: [
413
+ { name: "id", type: "int", nullable: false },
414
+ { name: "created_at", type: "datetime", nullable: true },
415
+ ],
416
+ });
417
+ mockAdapter.getTableIndexes.mockResolvedValue([
418
+ { name: "PRIMARY", columns: ["id"], unique: true },
419
+ ]);
420
+
421
+ const tool = createIndexRecommendationTool(
422
+ mockAdapter as unknown as MySQLAdapter,
423
+ );
424
+ const result = (await tool.handler({ table: "users" }, mockContext)) as {
425
+ recommendations: { column: string; reason: string }[];
426
+ };
427
+
428
+ expect(result.recommendations).toHaveLength(1);
429
+ expect(result.recommendations[0].column).toBe("created_at");
430
+ expect(result.recommendations[0].reason).toContain("Timestamp");
431
+ });
432
+
433
+ it("should suggest indexes for status columns", async () => {
434
+ mockAdapter.describeTable.mockResolvedValue({
435
+ columns: [
436
+ { name: "id", type: "int", nullable: false },
437
+ { name: "status", type: "varchar", nullable: true },
438
+ ],
439
+ });
440
+ mockAdapter.getTableIndexes.mockResolvedValue([
441
+ { name: "PRIMARY", columns: ["id"], unique: true },
442
+ ]);
443
+
444
+ const tool = createIndexRecommendationTool(
445
+ mockAdapter as unknown as MySQLAdapter,
446
+ );
447
+ const result = (await tool.handler({ table: "orders" }, mockContext)) as {
448
+ recommendations: { column: string; reason: string }[];
449
+ };
450
+
451
+ const statusRec = result.recommendations.find(
452
+ (r) => r.column === "status",
453
+ );
454
+ expect(statusRec).toBeDefined();
455
+ expect(statusRec!.reason).toContain("Status/type");
456
+ });
457
+
458
+ it("should handle nonexistent table", async () => {
459
+ mockAdapter.describeTable.mockResolvedValue({ columns: [] });
460
+
461
+ const tool = createIndexRecommendationTool(
462
+ mockAdapter as unknown as MySQLAdapter,
463
+ );
464
+ const result = (await tool.handler(
465
+ { table: "nonexistent" },
466
+ mockContext,
467
+ )) as { exists: boolean };
468
+
469
+ expect(result.exists).toBe(false);
470
+ });
471
+ });
472
+
473
+ // ===========================================================================
474
+ // Force Index edge cases
475
+ // ===========================================================================
476
+ describe("force index edge cases", () => {
477
+ it("should warn when index doesn't exist", async () => {
478
+ mockAdapter.describeTable.mockResolvedValue({
479
+ columns: [{ name: "id", type: "int", nullable: false }],
480
+ });
481
+ mockAdapter.getTableIndexes.mockResolvedValue([
482
+ { name: "PRIMARY", columns: ["id"], unique: true },
483
+ ]);
484
+
485
+ const tool = createForceIndexTool(mockAdapter as unknown as MySQLAdapter);
486
+ const result = (await tool.handler(
487
+ {
488
+ table: "users",
489
+ query: "SELECT * FROM users WHERE id = 1",
490
+ indexName: "nonexistent_idx",
491
+ },
492
+ mockContext,
493
+ )) as { warning?: string; rewrittenQuery: string };
494
+
495
+ expect(result.warning).toContain("not found");
496
+ expect(result.rewrittenQuery).toContain("FORCE INDEX");
497
+ });
498
+
499
+ it("should handle nonexistent table in force index", async () => {
500
+ mockAdapter.describeTable.mockResolvedValue({ columns: [] });
501
+
502
+ const tool = createForceIndexTool(mockAdapter as unknown as MySQLAdapter);
503
+ const result = (await tool.handler(
504
+ {
505
+ table: "nonexistent",
506
+ query: "SELECT * FROM nonexistent",
507
+ indexName: "idx1",
508
+ },
509
+ mockContext,
510
+ )) as { exists: boolean };
511
+
512
+ expect(result.exists).toBe(false);
513
+ });
514
+ });
515
+ });