@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.
- package/.dockerignore +1 -0
- package/.gitattributes +18 -0
- package/.github/workflows/codeql.yml +2 -10
- package/.github/workflows/docker-publish.yml +15 -13
- package/CHANGELOG.md +287 -1
- package/DOCKER_README.md +100 -265
- package/Dockerfile +5 -0
- package/README.md +124 -59
- package/VERSION +1 -1
- package/dist/__tests__/mocks/adapter.d.ts.map +1 -1
- package/dist/__tests__/mocks/adapter.js +2 -0
- package/dist/__tests__/mocks/adapter.js.map +1 -1
- package/dist/adapters/DatabaseAdapter.d.ts.map +1 -1
- package/dist/adapters/DatabaseAdapter.js +50 -9
- package/dist/adapters/DatabaseAdapter.js.map +1 -1
- package/dist/adapters/mysql/MySQLAdapter.d.ts +6 -0
- package/dist/adapters/mysql/MySQLAdapter.d.ts.map +1 -1
- package/dist/adapters/mysql/MySQLAdapter.js +8 -0
- package/dist/adapters/mysql/MySQLAdapter.js.map +1 -1
- package/dist/adapters/mysql/SchemaManager.js +16 -15
- package/dist/adapters/mysql/SchemaManager.js.map +1 -1
- package/dist/adapters/mysql/prompts/index.js +10 -20
- package/dist/adapters/mysql/prompts/index.js.map +1 -1
- package/dist/adapters/mysql/prompts/proxysqlSetup.js +1 -1
- package/dist/adapters/mysql/resources/docstore.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/docstore.js +10 -7
- package/dist/adapters/mysql/resources/docstore.js.map +1 -1
- package/dist/adapters/mysql/resources/events.js +11 -8
- package/dist/adapters/mysql/resources/events.js.map +1 -1
- package/dist/adapters/mysql/resources/indexes.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/indexes.js +12 -15
- package/dist/adapters/mysql/resources/indexes.js.map +1 -1
- package/dist/adapters/mysql/resources/innodb.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/innodb.js +20 -17
- package/dist/adapters/mysql/resources/innodb.js.map +1 -1
- package/dist/adapters/mysql/resources/locks.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/locks.js +9 -6
- package/dist/adapters/mysql/resources/locks.js.map +1 -1
- package/dist/adapters/mysql/resources/performance.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/performance.js +15 -15
- package/dist/adapters/mysql/resources/performance.js.map +1 -1
- package/dist/adapters/mysql/resources/spatial.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/spatial.js +9 -6
- package/dist/adapters/mysql/resources/spatial.js.map +1 -1
- package/dist/adapters/mysql/resources/sysschema.d.ts.map +1 -1
- package/dist/adapters/mysql/resources/sysschema.js +12 -9
- package/dist/adapters/mysql/resources/sysschema.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/backup.js +170 -121
- package/dist/adapters/mysql/tools/admin/backup.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/maintenance.js +106 -57
- package/dist/adapters/mysql/tools/admin/maintenance.js.map +1 -1
- package/dist/adapters/mysql/tools/admin/monitoring.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/admin/monitoring.js +183 -101
- package/dist/adapters/mysql/tools/admin/monitoring.js.map +1 -1
- package/dist/adapters/mysql/tools/cluster/group-replication.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/cluster/group-replication.js +164 -120
- package/dist/adapters/mysql/tools/cluster/group-replication.js.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js +212 -145
- package/dist/adapters/mysql/tools/cluster/innodb-cluster.js.map +1 -1
- package/dist/adapters/mysql/tools/codemode/index.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/codemode/index.js +6 -4
- package/dist/adapters/mysql/tools/codemode/index.js.map +1 -1
- package/dist/adapters/mysql/tools/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/core.js +152 -29
- package/dist/adapters/mysql/tools/core.js.map +1 -1
- package/dist/adapters/mysql/tools/docstore.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/docstore.js +340 -163
- package/dist/adapters/mysql/tools/docstore.js.map +1 -1
- package/dist/adapters/mysql/tools/events.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/events.js +284 -198
- package/dist/adapters/mysql/tools/events.js.map +1 -1
- package/dist/adapters/mysql/tools/json/core.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/core.js +11 -39
- package/dist/adapters/mysql/tools/json/core.js.map +1 -1
- package/dist/adapters/mysql/tools/json/enhanced.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/enhanced.js +15 -33
- package/dist/adapters/mysql/tools/json/enhanced.js.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/json/helpers.js +13 -24
- package/dist/adapters/mysql/tools/json/helpers.js.map +1 -1
- package/dist/adapters/mysql/tools/partitioning.js +3 -0
- package/dist/adapters/mysql/tools/partitioning.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/analysis.js +89 -60
- package/dist/adapters/mysql/tools/performance/analysis.js.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/performance/optimization.js +151 -127
- package/dist/adapters/mysql/tools/performance/optimization.js.map +1 -1
- package/dist/adapters/mysql/tools/proxysql.d.ts +1 -1
- package/dist/adapters/mysql/tools/proxysql.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/proxysql.js +289 -176
- package/dist/adapters/mysql/tools/proxysql.js.map +1 -1
- package/dist/adapters/mysql/tools/replication.js +75 -49
- package/dist/adapters/mysql/tools/replication.js.map +1 -1
- package/dist/adapters/mysql/tools/roles.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/roles.js +224 -182
- package/dist/adapters/mysql/tools/roles.js.map +1 -1
- package/dist/adapters/mysql/tools/router.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/router.js +168 -67
- package/dist/adapters/mysql/tools/router.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/constraints.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/constraints.js +21 -3
- package/dist/adapters/mysql/tools/schema/constraints.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/management.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/management.js +61 -14
- package/dist/adapters/mysql/tools/schema/management.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/routines.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/routines.js +27 -4
- package/dist/adapters/mysql/tools/schema/routines.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/scheduled_events.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/scheduled_events.js +24 -3
- package/dist/adapters/mysql/tools/schema/scheduled_events.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/triggers.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/triggers.js +23 -2
- package/dist/adapters/mysql/tools/schema/triggers.js.map +1 -1
- package/dist/adapters/mysql/tools/schema/views.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/schema/views.js +47 -7
- package/dist/adapters/mysql/tools/schema/views.js.map +1 -1
- package/dist/adapters/mysql/tools/security/audit.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/audit.js +102 -34
- package/dist/adapters/mysql/tools/security/audit.js.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/data-protection.js +264 -205
- package/dist/adapters/mysql/tools/security/data-protection.js.map +1 -1
- package/dist/adapters/mysql/tools/security/encryption.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/security/encryption.js +137 -104
- package/dist/adapters/mysql/tools/security/encryption.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/backup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/backup.js +71 -59
- package/dist/adapters/mysql/tools/shell/backup.js.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/shell/restore.js +61 -47
- package/dist/adapters/mysql/tools/shell/restore.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/geometry.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/geometry.js +19 -5
- package/dist/adapters/mysql/tools/spatial/geometry.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/operations.js +42 -17
- package/dist/adapters/mysql/tools/spatial/operations.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/queries.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/queries.js +109 -57
- package/dist/adapters/mysql/tools/spatial/queries.js.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/spatial/setup.js +103 -50
- package/dist/adapters/mysql/tools/spatial/setup.js.map +1 -1
- package/dist/adapters/mysql/tools/stats/comparative.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/stats/comparative.js +128 -79
- package/dist/adapters/mysql/tools/stats/comparative.js.map +1 -1
- package/dist/adapters/mysql/tools/stats/descriptive.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/stats/descriptive.js +174 -102
- package/dist/adapters/mysql/tools/stats/descriptive.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/activity.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/activity.js +50 -25
- package/dist/adapters/mysql/tools/sysschema/activity.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/performance.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/performance.js +121 -66
- package/dist/adapters/mysql/tools/sysschema/performance.js.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/sysschema/resources.js +101 -64
- package/dist/adapters/mysql/tools/sysschema/resources.js.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/text/fulltext.js +18 -32
- package/dist/adapters/mysql/tools/text/fulltext.js.map +1 -1
- package/dist/adapters/mysql/tools/transactions.d.ts.map +1 -1
- package/dist/adapters/mysql/tools/transactions.js +48 -23
- package/dist/adapters/mysql/tools/transactions.js.map +1 -1
- package/dist/adapters/mysql/types/proxysql-types.d.ts +15 -0
- package/dist/adapters/mysql/types/proxysql-types.d.ts.map +1 -1
- package/dist/adapters/mysql/types/proxysql-types.js +33 -1
- package/dist/adapters/mysql/types/proxysql-types.js.map +1 -1
- package/dist/adapters/mysql/types/router-types.d.ts +1 -1
- package/dist/adapters/mysql/types/router-types.js +1 -1
- package/dist/adapters/mysql/types/router-types.js.map +1 -1
- package/dist/adapters/mysql/types/shell-types.js +2 -2
- package/dist/adapters/mysql/types/shell-types.js.map +1 -1
- package/dist/adapters/mysql/types.d.ts +485 -21
- package/dist/adapters/mysql/types.d.ts.map +1 -1
- package/dist/adapters/mysql/types.js +546 -19
- package/dist/adapters/mysql/types.js.map +1 -1
- package/dist/auth/scopes.js +1 -1
- package/dist/auth/scopes.js.map +1 -1
- package/dist/codemode/api.d.ts +3 -2
- package/dist/codemode/api.d.ts.map +1 -1
- package/dist/codemode/api.js +80 -5
- package/dist/codemode/api.js.map +1 -1
- package/dist/codemode/sandbox-factory.js +1 -1
- package/dist/codemode/sandbox-factory.js.map +1 -1
- package/dist/codemode/types.d.ts +26 -0
- package/dist/codemode/types.d.ts.map +1 -1
- package/dist/codemode/types.js +2 -0
- package/dist/codemode/types.js.map +1 -1
- package/dist/codemode/worker-sandbox.d.ts +4 -2
- package/dist/codemode/worker-sandbox.d.ts.map +1 -1
- package/dist/codemode/worker-sandbox.js +66 -7
- package/dist/codemode/worker-sandbox.js.map +1 -1
- package/dist/codemode/worker-script.d.ts +3 -0
- package/dist/codemode/worker-script.d.ts.map +1 -1
- package/dist/codemode/worker-script.js +128 -75
- package/dist/codemode/worker-script.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +37 -31
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/filtering/ToolConstants.d.ts +1 -1
- package/dist/filtering/ToolConstants.d.ts.map +1 -1
- package/dist/filtering/ToolConstants.js +1 -2
- package/dist/filtering/ToolConstants.js.map +1 -1
- package/dist/pool/ConnectionPool.d.ts.map +1 -1
- package/dist/pool/ConnectionPool.js.map +1 -1
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +6 -0
- package/dist/transports/http.js.map +1 -1
- package/dist/utils/validators.d.ts +1 -1
- package/dist/utils/validators.d.ts.map +1 -1
- package/dist/utils/validators.js.map +1 -1
- package/package.json +4 -4
- package/releases/v2.3.0-release-notes.md +20 -20
- package/releases/v2.3.1-release-notes.md +34 -0
- package/releases/v3.0.0-release-notes.md +81 -0
- package/src/__tests__/mocks/adapter.ts +3 -0
- package/src/__tests__/perf.test.ts +6 -6
- package/src/adapters/DatabaseAdapter.ts +58 -9
- package/src/adapters/__tests__/DatabaseAdapter.test.ts +89 -8
- package/src/adapters/mysql/MySQLAdapter.ts +17 -2
- package/src/adapters/mysql/SchemaManager.ts +21 -21
- package/src/adapters/mysql/__tests__/MySQLAdapter.test.ts +1 -1
- package/src/adapters/mysql/prompts/index.ts +12 -22
- package/src/adapters/mysql/prompts/proxysqlSetup.ts +1 -1
- package/src/adapters/mysql/resources/docstore.ts +13 -10
- package/src/adapters/mysql/resources/events.ts +12 -12
- package/src/adapters/mysql/resources/indexes.ts +17 -19
- package/src/adapters/mysql/resources/innodb.ts +23 -22
- package/src/adapters/mysql/resources/locks.ts +9 -7
- package/src/adapters/mysql/resources/performance.ts +23 -18
- package/src/adapters/mysql/resources/spatial.ts +9 -7
- package/src/adapters/mysql/resources/sysschema.ts +12 -11
- package/src/adapters/mysql/tools/__tests__/core.test.ts +126 -55
- package/src/adapters/mysql/tools/__tests__/docstore.test.ts +459 -88
- package/src/adapters/mysql/tools/__tests__/events.test.ts +281 -103
- package/src/adapters/mysql/tools/__tests__/proxysql.test.ts +128 -28
- package/src/adapters/mysql/tools/__tests__/replication.test.ts +48 -2
- package/src/adapters/mysql/tools/__tests__/roles.test.ts +15 -18
- package/src/adapters/mysql/tools/__tests__/router.test.ts +32 -5
- package/src/adapters/mysql/tools/__tests__/security.test.ts +126 -2
- package/src/adapters/mysql/tools/__tests__/security_injection.test.ts +84 -76
- package/src/adapters/mysql/tools/__tests__/security_integration.test.ts +47 -50
- package/src/adapters/mysql/tools/__tests__/spatial.test.ts +11 -10
- package/src/adapters/mysql/tools/__tests__/spatial_handler.test.ts +54 -38
- package/src/adapters/mysql/tools/__tests__/stats.test.ts +285 -152
- package/src/adapters/mysql/tools/__tests__/transactions.test.ts +13 -13
- package/src/adapters/mysql/tools/admin/__tests__/backup.test.ts +171 -25
- package/src/adapters/mysql/tools/admin/__tests__/maintenance.test.ts +240 -4
- package/src/adapters/mysql/tools/admin/__tests__/monitoring-summary.test.ts +274 -0
- package/src/adapters/mysql/tools/admin/__tests__/monitoring.test.ts +94 -5
- package/src/adapters/mysql/tools/admin/backup.ts +193 -143
- package/src/adapters/mysql/tools/admin/maintenance.ts +118 -69
- package/src/adapters/mysql/tools/admin/monitoring.ts +201 -125
- package/src/adapters/mysql/tools/cluster/__tests__/group-replication.test.ts +69 -0
- package/src/adapters/mysql/tools/cluster/__tests__/innodb-cluster.test.ts +141 -0
- package/src/adapters/mysql/tools/cluster/group-replication.ts +172 -132
- package/src/adapters/mysql/tools/cluster/innodb-cluster.ts +231 -157
- package/src/adapters/mysql/tools/codemode/__tests__/codemode-tool.test.ts +227 -0
- package/src/adapters/mysql/tools/codemode/index.ts +5 -3
- package/src/adapters/mysql/tools/core.ts +152 -38
- package/src/adapters/mysql/tools/docstore.ts +422 -205
- package/src/adapters/mysql/tools/events.ts +334 -233
- package/src/adapters/mysql/tools/json/__tests__/core.test.ts +20 -0
- package/src/adapters/mysql/tools/json/__tests__/enhanced.test.ts +82 -50
- package/src/adapters/mysql/tools/json/__tests__/helpers.test.ts +42 -3
- package/src/adapters/mysql/tools/json/core.ts +21 -42
- package/src/adapters/mysql/tools/json/enhanced.ts +22 -37
- package/src/adapters/mysql/tools/json/helpers.ts +21 -25
- package/src/adapters/mysql/tools/partitioning.ts +3 -0
- package/src/adapters/mysql/tools/performance/__tests__/analysis.test.ts +98 -5
- package/src/adapters/mysql/tools/performance/__tests__/optimization-coverage.test.ts +515 -0
- package/src/adapters/mysql/tools/performance/__tests__/optimization.test.ts +187 -0
- package/src/adapters/mysql/tools/performance/analysis.ts +95 -69
- package/src/adapters/mysql/tools/performance/optimization.ts +182 -153
- package/src/adapters/mysql/tools/proxysql.ts +314 -209
- package/src/adapters/mysql/tools/replication.ts +84 -57
- package/src/adapters/mysql/tools/roles.ts +274 -226
- package/src/adapters/mysql/tools/router.ts +181 -85
- package/src/adapters/mysql/tools/schema/__tests__/constraints.test.ts +13 -0
- package/src/adapters/mysql/tools/schema/__tests__/management.test.ts +60 -25
- package/src/adapters/mysql/tools/schema/__tests__/scheduled_events.test.ts +11 -0
- package/src/adapters/mysql/tools/schema/__tests__/triggers.test.ts +25 -4
- package/src/adapters/mysql/tools/schema/__tests__/views.test.ts +46 -14
- package/src/adapters/mysql/tools/schema/constraints.ts +22 -3
- package/src/adapters/mysql/tools/schema/management.ts +60 -15
- package/src/adapters/mysql/tools/schema/routines.ts +26 -4
- package/src/adapters/mysql/tools/schema/scheduled_events.ts +25 -3
- package/src/adapters/mysql/tools/schema/triggers.ts +27 -2
- package/src/adapters/mysql/tools/schema/views.ts +46 -8
- package/src/adapters/mysql/tools/security/__tests__/audit.test.ts +90 -4
- package/src/adapters/mysql/tools/security/audit.ts +113 -39
- package/src/adapters/mysql/tools/security/data-protection.ts +293 -233
- package/src/adapters/mysql/tools/security/encryption.ts +172 -139
- package/src/adapters/mysql/tools/shell/__tests__/backup.test.ts +29 -0
- package/src/adapters/mysql/tools/shell/backup.ts +90 -73
- package/src/adapters/mysql/tools/shell/restore.ts +62 -48
- package/src/adapters/mysql/tools/spatial/__tests__/operations.test.ts +22 -14
- package/src/adapters/mysql/tools/spatial/__tests__/queries.test.ts +65 -51
- package/src/adapters/mysql/tools/spatial/geometry.ts +23 -7
- package/src/adapters/mysql/tools/spatial/operations.ts +60 -31
- package/src/adapters/mysql/tools/spatial/queries.ts +142 -65
- package/src/adapters/mysql/tools/spatial/setup.ts +121 -55
- package/src/adapters/mysql/tools/stats/__tests__/comparative.test.ts +12 -10
- package/src/adapters/mysql/tools/stats/comparative.ts +150 -98
- package/src/adapters/mysql/tools/stats/descriptive.ts +204 -127
- package/src/adapters/mysql/tools/sysschema/__tests__/error-paths.test.ts +222 -0
- package/src/adapters/mysql/tools/sysschema/__tests__/performance.test.ts +45 -0
- package/src/adapters/mysql/tools/sysschema/__tests__/resources.test.ts +6 -3
- package/src/adapters/mysql/tools/sysschema/activity.ts +52 -27
- package/src/adapters/mysql/tools/sysschema/performance.ts +132 -68
- package/src/adapters/mysql/tools/sysschema/resources.ts +105 -67
- package/src/adapters/mysql/tools/text/__tests__/fulltext.test.ts +45 -17
- package/src/adapters/mysql/tools/text/fulltext.ts +27 -38
- package/src/adapters/mysql/tools/transactions.ts +49 -24
- package/src/adapters/mysql/types/proxysql-types.ts +38 -1
- package/src/adapters/mysql/types/router-types.ts +1 -1
- package/src/adapters/mysql/types/shell-types.ts +2 -2
- package/src/adapters/mysql/types.ts +632 -19
- package/src/auth/__tests__/scopes.test.ts +2 -2
- package/src/auth/scopes.ts +1 -1
- package/src/codemode/__tests__/api.test.ts +417 -0
- package/src/codemode/__tests__/sandbox-factory.test.ts +158 -0
- package/src/codemode/__tests__/sandbox.test.ts +301 -0
- package/src/codemode/__tests__/security.test.ts +368 -0
- package/src/codemode/__tests__/worker-sandbox.test.ts +179 -0
- package/src/codemode/__tests__/worker-script.test.ts +226 -0
- package/src/codemode/api.ts +89 -5
- package/src/codemode/sandbox-factory.ts +1 -1
- package/src/codemode/types.ts +34 -0
- package/src/codemode/worker-sandbox.ts +74 -7
- package/src/codemode/worker-script.ts +157 -86
- package/src/constants/ServerInstructions.ts +37 -31
- package/src/filtering/ToolConstants.ts +1 -2
- package/src/filtering/__tests__/ToolFilter.test.ts +9 -9
- package/src/pool/ConnectionPool.ts +4 -1
- package/src/transports/__tests__/http.test.ts +15 -3
- package/src/transports/http.ts +12 -0
- package/src/utils/validators.ts +2 -1
- package/vitest.config.ts +3 -1
- 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
|
-
|
|
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
|
+
});
|