@neverinfamous/mysql-mcp 2.3.1 → 3.0.1

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 (345) hide show
  1. package/.dockerignore +1 -0
  2. package/.gitattributes +18 -0
  3. package/.github/workflows/codeql.yml +2 -2
  4. package/.github/workflows/docker-publish.yml +5 -5
  5. package/CHANGELOG.md +348 -122
  6. package/DOCKER_README.md +81 -40
  7. package/README.md +87 -46
  8. package/VERSION +1 -1
  9. package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
  10. package/dist/__tests__/mocks/adapter.js +2 -0
  11. package/dist/__tests__/mocks/adapter.js.map +1 -1
  12. package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
  13. package/dist/adapters/DatabaseAdapter.js +50 -9
  14. package/dist/adapters/DatabaseAdapter.js.map +1 -1
  15. package/dist/adapters/mysql/MySQLAdapter.d.ts +6 -0
  16. package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
  17. package/dist/adapters/mysql/MySQLAdapter.js +8 -0
  18. package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
  19. package/dist/adapters/mysql/SchemaManager.js +16 -15
  20. package/dist/adapters/mysql/SchemaManager.js.map +1 -1
  21. package/dist/adapters/mysql/prompts/index.js +10 -20
  22. package/dist/adapters/mysql/prompts/index.js.map +1 -1
  23. package/dist/adapters/mysql/prompts/proxysqlSetup.js +1 -1
  24. package/dist/adapters/mysql/resources/docstore.d.ts.map +1 -1
  25. package/dist/adapters/mysql/resources/docstore.js +10 -7
  26. package/dist/adapters/mysql/resources/docstore.js.map +1 -1
  27. package/dist/adapters/mysql/resources/events.js +11 -8
  28. package/dist/adapters/mysql/resources/events.js.map +1 -1
  29. package/dist/adapters/mysql/resources/indexes.d.ts.map +1 -1
  30. package/dist/adapters/mysql/resources/indexes.js +12 -15
  31. package/dist/adapters/mysql/resources/indexes.js.map +1 -1
  32. package/dist/adapters/mysql/resources/innodb.d.ts.map +1 -1
  33. package/dist/adapters/mysql/resources/innodb.js +20 -17
  34. package/dist/adapters/mysql/resources/innodb.js.map +1 -1
  35. package/dist/adapters/mysql/resources/locks.d.ts.map +1 -1
  36. package/dist/adapters/mysql/resources/locks.js +9 -6
  37. package/dist/adapters/mysql/resources/locks.js.map +1 -1
  38. package/dist/adapters/mysql/resources/performance.d.ts.map +1 -1
  39. package/dist/adapters/mysql/resources/performance.js +15 -15
  40. package/dist/adapters/mysql/resources/performance.js.map +1 -1
  41. package/dist/adapters/mysql/resources/spatial.d.ts.map +1 -1
  42. package/dist/adapters/mysql/resources/spatial.js +9 -6
  43. package/dist/adapters/mysql/resources/spatial.js.map +1 -1
  44. package/dist/adapters/mysql/resources/sysschema.d.ts.map +1 -1
  45. package/dist/adapters/mysql/resources/sysschema.js +12 -9
  46. package/dist/adapters/mysql/resources/sysschema.js.map +1 -1
  47. package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
  48. package/dist/adapters/mysql/tools/admin/backup.js +170 -121
  49. package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
  50. package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
  51. package/dist/adapters/mysql/tools/admin/maintenance.js +106 -57
  52. package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
  53. package/dist/adapters/mysql/tools/admin/monitoring.d.ts.map +1 -1
  54. package/dist/adapters/mysql/tools/admin/monitoring.js +183 -101
  55. package/dist/adapters/mysql/tools/admin/monitoring.js.map +1 -1
  56. package/dist/adapters/mysql/tools/cluster/group-replication.d.ts.map +1 -1
  57. package/dist/adapters/mysql/tools/cluster/group-replication.js +164 -120
  58. package/dist/adapters/mysql/tools/cluster/group-replication.js.map +1 -1
  59. package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
  60. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +212 -145
  61. package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
  62. package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -1
  63. package/dist/adapters/mysql/tools/codemode/index.js +6 -4
  64. package/dist/adapters/mysql/tools/codemode/index.js.map +1 -1
  65. package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
  66. package/dist/adapters/mysql/tools/core.js +152 -29
  67. package/dist/adapters/mysql/tools/core.js.map +1 -1
  68. package/dist/adapters/mysql/tools/docstore.d.ts.map +1 -1
  69. package/dist/adapters/mysql/tools/docstore.js +340 -163
  70. package/dist/adapters/mysql/tools/docstore.js.map +1 -1
  71. package/dist/adapters/mysql/tools/events.d.ts.map +1 -1
  72. package/dist/adapters/mysql/tools/events.js +284 -198
  73. package/dist/adapters/mysql/tools/events.js.map +1 -1
  74. package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
  75. package/dist/adapters/mysql/tools/json/core.js +11 -39
  76. package/dist/adapters/mysql/tools/json/core.js.map +1 -1
  77. package/dist/adapters/mysql/tools/json/enhanced.d.ts.map +1 -1
  78. package/dist/adapters/mysql/tools/json/enhanced.js +15 -33
  79. package/dist/adapters/mysql/tools/json/enhanced.js.map +1 -1
  80. package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
  81. package/dist/adapters/mysql/tools/json/helpers.js +13 -24
  82. package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
  83. package/dist/adapters/mysql/tools/partitioning.js +3 -0
  84. package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
  85. package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
  86. package/dist/adapters/mysql/tools/performance/analysis.js +89 -60
  87. package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
  88. package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
  89. package/dist/adapters/mysql/tools/performance/optimization.js +151 -127
  90. package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
  91. package/dist/adapters/mysql/tools/proxysql.d.ts +1 -1
  92. package/dist/adapters/mysql/tools/proxysql.d.ts.map +1 -1
  93. package/dist/adapters/mysql/tools/proxysql.js +289 -176
  94. package/dist/adapters/mysql/tools/proxysql.js.map +1 -1
  95. package/dist/adapters/mysql/tools/replication.js +75 -49
  96. package/dist/adapters/mysql/tools/replication.js.map +1 -1
  97. package/dist/adapters/mysql/tools/roles.d.ts.map +1 -1
  98. package/dist/adapters/mysql/tools/roles.js +224 -182
  99. package/dist/adapters/mysql/tools/roles.js.map +1 -1
  100. package/dist/adapters/mysql/tools/router.d.ts.map +1 -1
  101. package/dist/adapters/mysql/tools/router.js +168 -67
  102. package/dist/adapters/mysql/tools/router.js.map +1 -1
  103. package/dist/adapters/mysql/tools/schema/constraints.d.ts.map +1 -1
  104. package/dist/adapters/mysql/tools/schema/constraints.js +21 -3
  105. package/dist/adapters/mysql/tools/schema/constraints.js.map +1 -1
  106. package/dist/adapters/mysql/tools/schema/management.d.ts.map +1 -1
  107. package/dist/adapters/mysql/tools/schema/management.js +61 -14
  108. package/dist/adapters/mysql/tools/schema/management.js.map +1 -1
  109. package/dist/adapters/mysql/tools/schema/routines.d.ts.map +1 -1
  110. package/dist/adapters/mysql/tools/schema/routines.js +27 -4
  111. package/dist/adapters/mysql/tools/schema/routines.js.map +1 -1
  112. package/dist/adapters/mysql/tools/schema/scheduled_events.d.ts.map +1 -1
  113. package/dist/adapters/mysql/tools/schema/scheduled_events.js +24 -3
  114. package/dist/adapters/mysql/tools/schema/scheduled_events.js.map +1 -1
  115. package/dist/adapters/mysql/tools/schema/triggers.d.ts.map +1 -1
  116. package/dist/adapters/mysql/tools/schema/triggers.js +23 -2
  117. package/dist/adapters/mysql/tools/schema/triggers.js.map +1 -1
  118. package/dist/adapters/mysql/tools/schema/views.d.ts.map +1 -1
  119. package/dist/adapters/mysql/tools/schema/views.js +47 -7
  120. package/dist/adapters/mysql/tools/schema/views.js.map +1 -1
  121. package/dist/adapters/mysql/tools/security/audit.d.ts.map +1 -1
  122. package/dist/adapters/mysql/tools/security/audit.js +102 -34
  123. package/dist/adapters/mysql/tools/security/audit.js.map +1 -1
  124. package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
  125. package/dist/adapters/mysql/tools/security/data-protection.js +264 -205
  126. package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
  127. package/dist/adapters/mysql/tools/security/encryption.d.ts.map +1 -1
  128. package/dist/adapters/mysql/tools/security/encryption.js +137 -104
  129. package/dist/adapters/mysql/tools/security/encryption.js.map +1 -1
  130. package/dist/adapters/mysql/tools/shell/backup.d.ts.map +1 -1
  131. package/dist/adapters/mysql/tools/shell/backup.js +71 -59
  132. package/dist/adapters/mysql/tools/shell/backup.js.map +1 -1
  133. package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
  134. package/dist/adapters/mysql/tools/shell/restore.js +61 -47
  135. package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
  136. package/dist/adapters/mysql/tools/spatial/geometry.d.ts.map +1 -1
  137. package/dist/adapters/mysql/tools/spatial/geometry.js +19 -5
  138. package/dist/adapters/mysql/tools/spatial/geometry.js.map +1 -1
  139. package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
  140. package/dist/adapters/mysql/tools/spatial/operations.js +42 -17
  141. package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
  142. package/dist/adapters/mysql/tools/spatial/queries.d.ts.map +1 -1
  143. package/dist/adapters/mysql/tools/spatial/queries.js +109 -57
  144. package/dist/adapters/mysql/tools/spatial/queries.js.map +1 -1
  145. package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
  146. package/dist/adapters/mysql/tools/spatial/setup.js +103 -50
  147. package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
  148. package/dist/adapters/mysql/tools/stats/comparative.d.ts.map +1 -1
  149. package/dist/adapters/mysql/tools/stats/comparative.js +128 -79
  150. package/dist/adapters/mysql/tools/stats/comparative.js.map +1 -1
  151. package/dist/adapters/mysql/tools/stats/descriptive.d.ts.map +1 -1
  152. package/dist/adapters/mysql/tools/stats/descriptive.js +174 -102
  153. package/dist/adapters/mysql/tools/stats/descriptive.js.map +1 -1
  154. package/dist/adapters/mysql/tools/sysschema/activity.d.ts.map +1 -1
  155. package/dist/adapters/mysql/tools/sysschema/activity.js +50 -25
  156. package/dist/adapters/mysql/tools/sysschema/activity.js.map +1 -1
  157. package/dist/adapters/mysql/tools/sysschema/performance.d.ts.map +1 -1
  158. package/dist/adapters/mysql/tools/sysschema/performance.js +121 -66
  159. package/dist/adapters/mysql/tools/sysschema/performance.js.map +1 -1
  160. package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
  161. package/dist/adapters/mysql/tools/sysschema/resources.js +101 -64
  162. package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
  163. package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
  164. package/dist/adapters/mysql/tools/text/fulltext.js +18 -32
  165. package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
  166. package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
  167. package/dist/adapters/mysql/tools/transactions.js +48 -23
  168. package/dist/adapters/mysql/tools/transactions.js.map +1 -1
  169. package/dist/adapters/mysql/types/proxysql-types.d.ts +15 -0
  170. package/dist/adapters/mysql/types/proxysql-types.d.ts.map +1 -1
  171. package/dist/adapters/mysql/types/proxysql-types.js +33 -1
  172. package/dist/adapters/mysql/types/proxysql-types.js.map +1 -1
  173. package/dist/adapters/mysql/types/router-types.d.ts +1 -1
  174. package/dist/adapters/mysql/types/router-types.js +1 -1
  175. package/dist/adapters/mysql/types/router-types.js.map +1 -1
  176. package/dist/adapters/mysql/types/shell-types.js +2 -2
  177. package/dist/adapters/mysql/types/shell-types.js.map +1 -1
  178. package/dist/adapters/mysql/types.d.ts +485 -21
  179. package/dist/adapters/mysql/types.d.ts.map +1 -1
  180. package/dist/adapters/mysql/types.js +546 -19
  181. package/dist/adapters/mysql/types.js.map +1 -1
  182. package/dist/auth/scopes.js +1 -1
  183. package/dist/auth/scopes.js.map +1 -1
  184. package/dist/codemode/api.d.ts +3 -2
  185. package/dist/codemode/api.d.ts.map +1 -1
  186. package/dist/codemode/api.js +80 -5
  187. package/dist/codemode/api.js.map +1 -1
  188. package/dist/codemode/sandbox-factory.js +1 -1
  189. package/dist/codemode/sandbox-factory.js.map +1 -1
  190. package/dist/codemode/types.d.ts +26 -0
  191. package/dist/codemode/types.d.ts.map +1 -1
  192. package/dist/codemode/types.js +2 -0
  193. package/dist/codemode/types.js.map +1 -1
  194. package/dist/codemode/worker-sandbox.d.ts +4 -2
  195. package/dist/codemode/worker-sandbox.d.ts.map +1 -1
  196. package/dist/codemode/worker-sandbox.js +66 -7
  197. package/dist/codemode/worker-sandbox.js.map +1 -1
  198. package/dist/codemode/worker-script.d.ts +3 -0
  199. package/dist/codemode/worker-script.d.ts.map +1 -1
  200. package/dist/codemode/worker-script.js +128 -75
  201. package/dist/codemode/worker-script.js.map +1 -1
  202. package/dist/constants/ServerInstructions.d.ts +1 -1
  203. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  204. package/dist/constants/ServerInstructions.js +37 -31
  205. package/dist/constants/ServerInstructions.js.map +1 -1
  206. package/dist/filtering/ToolConstants.d.ts +1 -1
  207. package/dist/filtering/ToolConstants.d.ts.map +1 -1
  208. package/dist/filtering/ToolConstants.js +1 -2
  209. package/dist/filtering/ToolConstants.js.map +1 -1
  210. package/dist/pool/ConnectionPool.d.ts.map +1 -1
  211. package/dist/pool/ConnectionPool.js.map +1 -1
  212. package/dist/transports/http.d.ts.map +1 -1
  213. package/dist/transports/http.js +6 -0
  214. package/dist/transports/http.js.map +1 -1
  215. package/dist/utils/validators.d.ts +1 -1
  216. package/dist/utils/validators.d.ts.map +1 -1
  217. package/dist/utils/validators.js.map +1 -1
  218. package/package.json +4 -4
  219. package/releases/v3.0.0-release-notes.md +81 -0
  220. package/releases/v3.0.1-release-notes.md +20 -0
  221. package/src/__tests__/mocks/adapter.ts +3 -0
  222. package/src/__tests__/perf.test.ts +6 -6
  223. package/src/adapters/DatabaseAdapter.ts +58 -9
  224. package/src/adapters/__tests__/DatabaseAdapter.test.ts +89 -8
  225. package/src/adapters/mysql/MySQLAdapter.ts +17 -2
  226. package/src/adapters/mysql/SchemaManager.ts +21 -21
  227. package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
  228. package/src/adapters/mysql/prompts/index.ts +12 -22
  229. package/src/adapters/mysql/prompts/proxysqlSetup.ts +1 -1
  230. package/src/adapters/mysql/resources/docstore.ts +13 -10
  231. package/src/adapters/mysql/resources/events.ts +12 -12
  232. package/src/adapters/mysql/resources/indexes.ts +17 -19
  233. package/src/adapters/mysql/resources/innodb.ts +23 -22
  234. package/src/adapters/mysql/resources/locks.ts +9 -7
  235. package/src/adapters/mysql/resources/performance.ts +23 -18
  236. package/src/adapters/mysql/resources/spatial.ts +9 -7
  237. package/src/adapters/mysql/resources/sysschema.ts +12 -11
  238. package/src/adapters/mysql/tools/__tests__/core.test.ts +126 -55
  239. package/src/adapters/mysql/tools/__tests__/docstore.test.ts +459 -88
  240. package/src/adapters/mysql/tools/__tests__/events.test.ts +281 -103
  241. package/src/adapters/mysql/tools/__tests__/proxysql.test.ts +128 -28
  242. package/src/adapters/mysql/tools/__tests__/replication.test.ts +48 -2
  243. package/src/adapters/mysql/tools/__tests__/roles.test.ts +15 -18
  244. package/src/adapters/mysql/tools/__tests__/router.test.ts +32 -5
  245. package/src/adapters/mysql/tools/__tests__/security.test.ts +126 -2
  246. package/src/adapters/mysql/tools/__tests__/security_injection.test.ts +84 -76
  247. package/src/adapters/mysql/tools/__tests__/security_integration.test.ts +47 -51
  248. package/src/adapters/mysql/tools/__tests__/spatial.test.ts +11 -10
  249. package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +54 -38
  250. package/src/adapters/mysql/tools/__tests__/stats.test.ts +285 -152
  251. package/src/adapters/mysql/tools/__tests__/transactions.test.ts +13 -13
  252. package/src/adapters/mysql/tools/admin/__tests__/backup.test.ts +171 -25
  253. package/src/adapters/mysql/tools/admin/__tests__/maintenance.test.ts +240 -4
  254. package/src/adapters/mysql/tools/admin/__tests__/monitoring-summary.test.ts +274 -0
  255. package/src/adapters/mysql/tools/admin/__tests__/monitoring.test.ts +94 -5
  256. package/src/adapters/mysql/tools/admin/backup.ts +193 -143
  257. package/src/adapters/mysql/tools/admin/maintenance.ts +118 -69
  258. package/src/adapters/mysql/tools/admin/monitoring.ts +201 -125
  259. package/src/adapters/mysql/tools/cluster/__tests__/group-replication.test.ts +69 -0
  260. package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +141 -0
  261. package/src/adapters/mysql/tools/cluster/group-replication.ts +172 -132
  262. package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +231 -157
  263. package/src/adapters/mysql/tools/codemode/__tests__/codemode-tool.test.ts +227 -0
  264. package/src/adapters/mysql/tools/codemode/index.ts +5 -3
  265. package/src/adapters/mysql/tools/core.ts +152 -38
  266. package/src/adapters/mysql/tools/docstore.ts +422 -205
  267. package/src/adapters/mysql/tools/events.ts +334 -233
  268. package/src/adapters/mysql/tools/json/__tests__/core.test.ts +20 -0
  269. package/src/adapters/mysql/tools/json/__tests__/enhanced.test.ts +82 -50
  270. package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +42 -3
  271. package/src/adapters/mysql/tools/json/core.ts +21 -42
  272. package/src/adapters/mysql/tools/json/enhanced.ts +22 -37
  273. package/src/adapters/mysql/tools/json/helpers.ts +21 -25
  274. package/src/adapters/mysql/tools/partitioning.ts +3 -0
  275. package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +98 -5
  276. package/src/adapters/mysql/tools/performance/__tests__/optimization-coverage.test.ts +515 -0
  277. package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +187 -0
  278. package/src/adapters/mysql/tools/performance/analysis.ts +95 -69
  279. package/src/adapters/mysql/tools/performance/optimization.ts +182 -153
  280. package/src/adapters/mysql/tools/proxysql.ts +314 -209
  281. package/src/adapters/mysql/tools/replication.ts +84 -57
  282. package/src/adapters/mysql/tools/roles.ts +274 -226
  283. package/src/adapters/mysql/tools/router.ts +181 -85
  284. package/src/adapters/mysql/tools/schema/__tests__/constraints.test.ts +13 -0
  285. package/src/adapters/mysql/tools/schema/__tests__/management.test.ts +60 -25
  286. package/src/adapters/mysql/tools/schema/__tests__/scheduled_events.test.ts +11 -0
  287. package/src/adapters/mysql/tools/schema/__tests__/triggers.test.ts +25 -4
  288. package/src/adapters/mysql/tools/schema/__tests__/views.test.ts +46 -14
  289. package/src/adapters/mysql/tools/schema/constraints.ts +22 -3
  290. package/src/adapters/mysql/tools/schema/management.ts +60 -15
  291. package/src/adapters/mysql/tools/schema/routines.ts +26 -4
  292. package/src/adapters/mysql/tools/schema/scheduled_events.ts +25 -3
  293. package/src/adapters/mysql/tools/schema/triggers.ts +27 -2
  294. package/src/adapters/mysql/tools/schema/views.ts +46 -8
  295. package/src/adapters/mysql/tools/security/__tests__/audit.test.ts +90 -4
  296. package/src/adapters/mysql/tools/security/audit.ts +113 -39
  297. package/src/adapters/mysql/tools/security/data-protection.ts +293 -233
  298. package/src/adapters/mysql/tools/security/encryption.ts +172 -139
  299. package/src/adapters/mysql/tools/shell/__tests__/backup.test.ts +29 -0
  300. package/src/adapters/mysql/tools/shell/backup.ts +90 -73
  301. package/src/adapters/mysql/tools/shell/restore.ts +62 -48
  302. package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +22 -14
  303. package/src/adapters/mysql/tools/spatial/__tests__/queries.test.ts +65 -51
  304. package/src/adapters/mysql/tools/spatial/geometry.ts +23 -7
  305. package/src/adapters/mysql/tools/spatial/operations.ts +60 -31
  306. package/src/adapters/mysql/tools/spatial/queries.ts +142 -65
  307. package/src/adapters/mysql/tools/spatial/setup.ts +121 -55
  308. package/src/adapters/mysql/tools/stats/__tests__/comparative.test.ts +12 -10
  309. package/src/adapters/mysql/tools/stats/comparative.ts +150 -98
  310. package/src/adapters/mysql/tools/stats/descriptive.ts +204 -127
  311. package/src/adapters/mysql/tools/sysschema/__tests__/error-paths.test.ts +222 -0
  312. package/src/adapters/mysql/tools/sysschema/__tests__/performance.test.ts +45 -0
  313. package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +6 -3
  314. package/src/adapters/mysql/tools/sysschema/activity.ts +52 -27
  315. package/src/adapters/mysql/tools/sysschema/performance.ts +132 -68
  316. package/src/adapters/mysql/tools/sysschema/resources.ts +105 -67
  317. package/src/adapters/mysql/tools/text/__tests__/fulltext.test.ts +45 -17
  318. package/src/adapters/mysql/tools/text/fulltext.ts +27 -38
  319. package/src/adapters/mysql/tools/transactions.ts +49 -24
  320. package/src/adapters/mysql/types/proxysql-types.ts +38 -1
  321. package/src/adapters/mysql/types/router-types.ts +1 -1
  322. package/src/adapters/mysql/types/shell-types.ts +2 -2
  323. package/src/adapters/mysql/types.ts +632 -19
  324. package/src/auth/__tests__/scopes.test.ts +2 -2
  325. package/src/auth/scopes.ts +1 -1
  326. package/src/codemode/__tests__/api.test.ts +417 -0
  327. package/src/codemode/__tests__/sandbox-factory.test.ts +158 -0
  328. package/src/codemode/__tests__/sandbox.test.ts +301 -0
  329. package/src/codemode/__tests__/security.test.ts +368 -0
  330. package/src/codemode/__tests__/worker-sandbox.test.ts +179 -0
  331. package/src/codemode/__tests__/worker-script.test.ts +226 -0
  332. package/src/codemode/api.ts +89 -5
  333. package/src/codemode/sandbox-factory.ts +1 -1
  334. package/src/codemode/types.ts +34 -0
  335. package/src/codemode/worker-sandbox.ts +74 -7
  336. package/src/codemode/worker-script.ts +157 -86
  337. package/src/constants/ServerInstructions.ts +37 -31
  338. package/src/filtering/ToolConstants.ts +1 -2
  339. package/src/filtering/__tests__/ToolFilter.test.ts +9 -9
  340. package/src/pool/ConnectionPool.ts +4 -1
  341. package/src/transports/__tests__/http.test.ts +15 -3
  342. package/src/transports/http.ts +12 -0
  343. package/src/utils/validators.ts +2 -1
  344. package/vitest.config.ts +3 -1
  345. 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
+ });