mcp-dbutils 0.20.0__tar.gz → 0.20.1__tar.gz
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.
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/CHANGELOG.md +7 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/PKG-INFO +1 -1
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/pyproject.toml +1 -1
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/mysql/handler.py +2 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/postgres/handler.py +2 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_mysql_handler.py +207 -110
- mcp_dbutils-0.20.1/tests/unit/test_postgres_handler.py +268 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.coveragerc +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/bug_report_ar.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/bug_report_en.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/bug_report_es.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/bug_report_fr.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/bug_report_ru.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/bug_report_zh.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/documentation_improvement_ar.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/documentation_improvement_en.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/documentation_improvement_es.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/documentation_improvement_fr.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/documentation_improvement_ru.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/documentation_improvement_zh.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/feature_request_ar.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/feature_request_en.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/feature_request_es.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/feature_request_fr.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/feature_request_ru.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/feature_request_zh.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/performance_issue_ar.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/performance_issue_en.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/performance_issue_es.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/performance_issue_fr.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/performance_issue_ru.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/performance_issue_zh.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/security_vulnerability_ar.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/security_vulnerability_en.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/security_vulnerability_es.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/security_vulnerability_fr.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/security_vulnerability_ru.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/ISSUE_TEMPLATE/security_vulnerability_zh.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/workflows/code-style.yml +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/workflows/issue-translator.yml +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/workflows/quality-assurance.yml +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.github/workflows/release.yml +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.gitignore +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.pre-commit-config.yaml +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/.releaserc.json +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/Dockerfile +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/LICENSE +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/README.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/README_AR.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/README_EN.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/README_ES.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/README_FR.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/README_RU.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/config.yaml.example +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/configuration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/examples/README.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/examples/mysql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/installation-platform-specific.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/installation.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/technical/architecture.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/technical/development.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/technical/security.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/technical/testing.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ar/usage.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/document-consistency-check.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/document-version-history.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/configuration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/examples/mysql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/installation-platform-specific.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/installation.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/technical/architecture.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/technical/development.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/technical/security.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/technical/testing.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/en/usage.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/configuration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/examples/README.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/examples/mysql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/installation-platform-specific.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/installation.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/technical/architecture.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/technical/development.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/technical/security.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/technical/testing.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/es/usage.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/configuration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/examples/README.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/examples/mysql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/installation-platform-specific.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/installation.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/technical/architecture.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/technical/development.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/technical/security.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/technical/testing.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/fr/usage.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/index.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/configuration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/examples/README.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/examples/mysql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/installation-platform-specific.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/installation.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/technical/architecture.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/technical/development.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/technical/security.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/technical/testing.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/ru/usage.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/configuration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/examples/mysql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/installation-platform-specific.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/installation.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/technical/architecture.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/technical/development.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/technical/security.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/technical/testing.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/docs/zh/usage.md +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/check_docs_consistency.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/check_zh_docs.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/fix_en_nav_links.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/fix_imports.sh +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/fix_remaining_issues.sh +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/fix_zh_nav_links.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/run_sonar_analysis.sh +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/scripts/sonar-ai-fix.fish +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/smithery.yaml +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/sonar-project.properties +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/__init__.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/base.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/config.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/log.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/mysql/__init__.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/mysql/config.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/mysql/server.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/postgres/__init__.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/postgres/config.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/postgres/server.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/sqlite/__init__.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/sqlite/config.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/sqlite/handler.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/sqlite/server.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/src/mcp_dbutils/stats.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/conftest.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/__init__.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/conftest.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/fixtures.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_list_connections.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_logging.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_monitoring.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_monitoring_enhanced.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_mysql.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_mysql_config.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_mysql_config_helpers.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_mysql_handler_extended.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_postgres.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_postgres_config.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_prompts.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_sqlite.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_sqlite_config.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_sqlite_handler_extended.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_tools.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/integration/test_tools_advanced.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_base.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_base_handlers.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_base_helpers.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_base_server.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_log.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_mysql_server.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_postgres_server.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_sqlite_server.py +0 -0
- {mcp_dbutils-0.20.0 → mcp_dbutils-0.20.1}/tests/unit/test_stats.py +0 -0
@@ -1,3 +1,10 @@
|
|
1
|
+
## [0.20.1](https://github.com/donghao1393/mcp-dbutils/compare/v0.20.0...v0.20.1) (2025-04-30)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* 修复MySQL和PostgreSQL处理器中变量作用域问题 ([#78](https://github.com/donghao1393/mcp-dbutils/issues/78)) ([9479e67](https://github.com/donghao1393/mcp-dbutils/commit/9479e677fb78346b6ef34ad474055f4df787a059)), closes [#75](https://github.com/donghao1393/mcp-dbutils/issues/75) [#75](https://github.com/donghao1393/mcp-dbutils/issues/75)
|
7
|
+
|
1
8
|
# [0.20.0](https://github.com/donghao1393/mcp-dbutils/compare/v0.19.0...v0.20.0) (2025-04-30)
|
2
9
|
|
3
10
|
|
@@ -62,6 +62,7 @@ class MySQLHandler(ConnectionHandler):
|
|
62
62
|
|
63
63
|
async def get_tables(self) -> list[types.Resource]:
|
64
64
|
"""Get all table resources"""
|
65
|
+
conn = None
|
65
66
|
try:
|
66
67
|
conn_params = self.config.get_connection_params()
|
67
68
|
conn = mysql.connector.connect(**conn_params)
|
@@ -92,6 +93,7 @@ class MySQLHandler(ConnectionHandler):
|
|
92
93
|
|
93
94
|
async def get_schema(self, table_name: str) -> str:
|
94
95
|
"""Get table schema information"""
|
96
|
+
conn = None
|
95
97
|
try:
|
96
98
|
conn_params = self.config.get_connection_params()
|
97
99
|
conn = mysql.connector.connect(**conn_params)
|
@@ -33,6 +33,7 @@ class PostgreSQLHandler(ConnectionHandler):
|
|
33
33
|
|
34
34
|
async def get_tables(self) -> list[types.Resource]:
|
35
35
|
"""Get all table resources"""
|
36
|
+
conn = None
|
36
37
|
try:
|
37
38
|
conn_params = self.config.get_connection_params()
|
38
39
|
conn = psycopg2.connect(**conn_params)
|
@@ -66,6 +67,7 @@ class PostgreSQLHandler(ConnectionHandler):
|
|
66
67
|
|
67
68
|
async def get_schema(self, table_name: str) -> str:
|
68
69
|
"""Get table schema information"""
|
70
|
+
conn = None
|
69
71
|
try:
|
70
72
|
conn_params = self.config.get_connection_params()
|
71
73
|
conn = psycopg2.connect(**conn_params)
|
@@ -61,17 +61,17 @@ class TestMySQLHandler:
|
|
61
61
|
{'table_name': 'users', 'description': 'User table'},
|
62
62
|
{'table_name': 'orders', 'description': None}
|
63
63
|
]
|
64
|
-
|
64
|
+
|
65
65
|
# Call the method
|
66
66
|
result = await handler.get_tables()
|
67
|
-
|
67
|
+
|
68
68
|
# Verify connection was made with correct parameters
|
69
69
|
mock_connect.assert_called_once()
|
70
|
-
|
70
|
+
|
71
71
|
# Verify the cursor was used correctly
|
72
72
|
mock_conn.cursor().__enter__().execute.assert_called_once()
|
73
73
|
mock_conn.cursor().__enter__().fetchall.assert_called_once()
|
74
|
-
|
74
|
+
|
75
75
|
# Verify the result format
|
76
76
|
assert isinstance(result, list)
|
77
77
|
assert len(result) == 2
|
@@ -80,35 +80,21 @@ class TestMySQLHandler:
|
|
80
80
|
assert result[0].description == 'User table'
|
81
81
|
assert result[1].name == 'orders schema'
|
82
82
|
assert result[1].description is None
|
83
|
-
|
83
|
+
|
84
84
|
# Verify connection was closed
|
85
85
|
mock_conn.close.assert_called_once()
|
86
86
|
|
87
87
|
@pytest.mark.asyncio
|
88
|
-
async def test_get_tables_error(self, handler
|
89
|
-
"""Test error handling when getting tables"""
|
90
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
# Replace with a method that raises the expected exception
|
94
|
-
async def mock_get_tables():
|
95
|
-
handler.stats.record_error.return_value = None
|
96
|
-
handler.stats.record_error("Error")
|
97
|
-
raise ConnectionHandlerError("Failed to get tables: Connection failed")
|
98
|
-
|
99
|
-
# Set the mock method
|
100
|
-
handler.get_tables = mock_get_tables
|
101
|
-
|
102
|
-
try:
|
88
|
+
async def test_get_tables_error(self, handler):
|
89
|
+
"""Test error handling when getting tables with connection failure"""
|
90
|
+
# Mock the connector.connect function to raise an exception
|
91
|
+
with patch('mysql.connector.connect', side_effect=mysql.connector.Error('Connection failed')):
|
103
92
|
# Call the method and expect an exception
|
104
93
|
with pytest.raises(ConnectionHandlerError, match="Failed to get tables"):
|
105
94
|
await handler.get_tables()
|
106
|
-
|
95
|
+
|
107
96
|
# Verify error was recorded
|
108
97
|
handler.stats.record_error.assert_called_once()
|
109
|
-
finally:
|
110
|
-
# Restore original method
|
111
|
-
handler.get_tables = original_get_tables
|
112
98
|
|
113
99
|
@pytest.mark.asyncio
|
114
100
|
async def test_get_schema(self, handler, mock_conn):
|
@@ -123,53 +109,39 @@ class TestMySQLHandler:
|
|
123
109
|
constraints = [
|
124
110
|
{'constraint_name': 'PRIMARY', 'constraint_type': 'PRIMARY KEY'}
|
125
111
|
]
|
126
|
-
|
112
|
+
|
127
113
|
# Set up the mock cursor to return different data for different queries
|
128
114
|
mock_cursor = mock_conn.cursor().__enter__()
|
129
115
|
mock_cursor.fetchall.side_effect = [columns, constraints]
|
130
|
-
|
116
|
+
|
131
117
|
# Call the method
|
132
118
|
result = await handler.get_schema('users')
|
133
|
-
|
119
|
+
|
134
120
|
# Verify connection was made with correct parameters
|
135
121
|
mock_connect.assert_called_once()
|
136
|
-
|
122
|
+
|
137
123
|
# Verify the cursor was used correctly for both queries
|
138
124
|
assert mock_cursor.execute.call_count == 2
|
139
|
-
|
125
|
+
|
140
126
|
# Verify the result format (it should be a string representation of dict)
|
141
127
|
assert isinstance(result, str)
|
142
128
|
assert "'columns':" in result
|
143
129
|
assert "'constraints':" in result
|
144
|
-
|
130
|
+
|
145
131
|
# Verify connection was closed
|
146
132
|
mock_conn.close.assert_called_once()
|
147
133
|
|
148
134
|
@pytest.mark.asyncio
|
149
|
-
async def test_get_schema_error(self, handler
|
150
|
-
"""Test error handling when getting schema"""
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
# Replace with a method that raises the expected exception
|
155
|
-
async def mock_get_schema(table_name):
|
156
|
-
handler.stats.record_error.return_value = None
|
157
|
-
handler.stats.record_error("Error")
|
158
|
-
raise ConnectionHandlerError("Failed to read table schema: Connection failed")
|
159
|
-
|
160
|
-
# Set the mock method
|
161
|
-
handler.get_schema = mock_get_schema
|
162
|
-
|
163
|
-
try:
|
135
|
+
async def test_get_schema_error(self, handler):
|
136
|
+
"""Test error handling when getting schema with connection failure"""
|
137
|
+
# Mock the connector.connect function to raise an exception
|
138
|
+
with patch('mysql.connector.connect', side_effect=mysql.connector.Error('Connection failed')):
|
164
139
|
# Call the method and expect an exception
|
165
140
|
with pytest.raises(ConnectionHandlerError, match="Failed to read table schema"):
|
166
141
|
await handler.get_schema('users')
|
167
|
-
|
142
|
+
|
168
143
|
# Verify error was recorded
|
169
144
|
handler.stats.record_error.assert_called_once()
|
170
|
-
finally:
|
171
|
-
# Restore original method
|
172
|
-
handler.get_schema = original_get_schema
|
173
145
|
|
174
146
|
@pytest.mark.asyncio
|
175
147
|
async def test_execute_query(self, handler, mock_conn):
|
@@ -183,22 +155,22 @@ class TestMySQLHandler:
|
|
183
155
|
{'id': 1, 'name': 'Test User'},
|
184
156
|
{'id': 2, 'name': 'Another User'}
|
185
157
|
]
|
186
|
-
|
158
|
+
|
187
159
|
# Call the method
|
188
160
|
result = await handler._execute_query('SELECT * FROM users')
|
189
|
-
|
161
|
+
|
190
162
|
# Verify connection was made
|
191
163
|
mock_connect.assert_called_once()
|
192
|
-
|
164
|
+
|
193
165
|
# Verify the cursor was used correctly
|
194
166
|
assert mock_cursor.execute.call_count == 2 # SET TRANSACTION + Query (不再需要ROLLBACK)
|
195
167
|
mock_cursor.fetchall.assert_called_once()
|
196
|
-
|
168
|
+
|
197
169
|
# Verify the result format
|
198
170
|
assert isinstance(result, str)
|
199
171
|
assert "columns" in result
|
200
172
|
assert "rows" in result
|
201
|
-
|
173
|
+
|
202
174
|
# Verify connection was closed
|
203
175
|
mock_conn.close.assert_called_once()
|
204
176
|
|
@@ -210,11 +182,11 @@ class TestMySQLHandler:
|
|
210
182
|
# Mock cursor to raise an exception when executing the query
|
211
183
|
mock_cursor = mock_conn.cursor().__enter__()
|
212
184
|
mock_cursor.execute.side_effect = [None, mysql.connector.Error('Query failed'), None]
|
213
|
-
|
185
|
+
|
214
186
|
# Call the method and expect an exception
|
215
187
|
with pytest.raises(ConnectionHandlerError, match="Query failed"):
|
216
188
|
await handler._execute_query('SELECT * FROM users')
|
217
|
-
|
189
|
+
|
218
190
|
# Verify connection was closed even after an error
|
219
191
|
mock_conn.close.assert_called_once()
|
220
192
|
|
@@ -244,13 +216,13 @@ class TestMySQLHandler:
|
|
244
216
|
]
|
245
217
|
mock_cursor.fetchone.side_effect = [{'count': 1}, table_info]
|
246
218
|
mock_cursor.fetchall.return_value = column_info
|
247
|
-
|
219
|
+
|
248
220
|
# Call the method
|
249
221
|
result = await handler.get_table_description('users')
|
250
|
-
|
222
|
+
|
251
223
|
# Verify connection was made
|
252
224
|
mock_connect.assert_called_once()
|
253
|
-
|
225
|
+
|
254
226
|
# Verify the result format
|
255
227
|
assert isinstance(result, str)
|
256
228
|
assert "Table: users" in result
|
@@ -258,7 +230,7 @@ class TestMySQLHandler:
|
|
258
230
|
assert "id" in result
|
259
231
|
assert "name" in result
|
260
232
|
assert "varchar" in result
|
261
|
-
|
233
|
+
|
262
234
|
# Verify connection was closed
|
263
235
|
mock_conn.close.assert_called_once()
|
264
236
|
|
@@ -270,7 +242,7 @@ class TestMySQLHandler:
|
|
270
242
|
# Call the method and expect an exception
|
271
243
|
with pytest.raises(ConnectionHandlerError, match="Failed to get table description"):
|
272
244
|
await handler.get_table_description('users')
|
273
|
-
|
245
|
+
|
274
246
|
# Verify error was recorded
|
275
247
|
handler.stats.record_error.assert_called_once()
|
276
248
|
|
@@ -284,20 +256,20 @@ class TestMySQLHandler:
|
|
284
256
|
mock_cursor.fetchone.return_value = {
|
285
257
|
'Create Table': 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))'
|
286
258
|
}
|
287
|
-
|
259
|
+
|
288
260
|
# Call the method
|
289
261
|
result = await handler.get_table_ddl('users')
|
290
|
-
|
262
|
+
|
291
263
|
# Verify connection was made
|
292
264
|
mock_connect.assert_called_once()
|
293
|
-
|
265
|
+
|
294
266
|
# Verify the cursor was used correctly
|
295
267
|
mock_cursor.execute.assert_called_once_with('SHOW CREATE TABLE users')
|
296
268
|
mock_cursor.fetchone.assert_called_once()
|
297
|
-
|
269
|
+
|
298
270
|
# Verify the result format
|
299
271
|
assert result == 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))'
|
300
|
-
|
272
|
+
|
301
273
|
# Verify connection was closed
|
302
274
|
mock_conn.close.assert_called_once()
|
303
275
|
|
@@ -309,13 +281,13 @@ class TestMySQLHandler:
|
|
309
281
|
# Mock cursor to return no DDL
|
310
282
|
mock_cursor = mock_conn.cursor().__enter__()
|
311
283
|
mock_cursor.fetchone.return_value = None
|
312
|
-
|
284
|
+
|
313
285
|
# Call the method
|
314
286
|
result = await handler.get_table_ddl('users')
|
315
|
-
|
287
|
+
|
316
288
|
# Verify the result format
|
317
289
|
assert result == 'Failed to get DDL for table users'
|
318
|
-
|
290
|
+
|
319
291
|
# Verify connection was closed
|
320
292
|
mock_conn.close.assert_called_once()
|
321
293
|
|
@@ -327,7 +299,7 @@ class TestMySQLHandler:
|
|
327
299
|
# Call the method and expect an exception
|
328
300
|
with pytest.raises(ConnectionHandlerError, match="Failed to get table DDL"):
|
329
301
|
await handler.get_table_ddl('users')
|
330
|
-
|
302
|
+
|
331
303
|
# Verify error was recorded
|
332
304
|
handler.stats.record_error.assert_called_once()
|
333
305
|
|
@@ -353,28 +325,28 @@ class TestMySQLHandler:
|
|
353
325
|
'index_comment': 'Name index'
|
354
326
|
}
|
355
327
|
]
|
356
|
-
|
328
|
+
|
357
329
|
mock_cursor = mock_conn.cursor().__enter__()
|
358
330
|
# 首先模拟表存在性检查
|
359
331
|
mock_cursor.fetchone.side_effect = [{'count': 1}]
|
360
332
|
mock_cursor.fetchall.return_value = indexes
|
361
|
-
|
333
|
+
|
362
334
|
# Call the method
|
363
335
|
result = await handler.get_table_indexes('users')
|
364
|
-
|
336
|
+
|
365
337
|
# Verify connection was made
|
366
338
|
mock_connect.assert_called_once()
|
367
|
-
|
339
|
+
|
368
340
|
# Verify the cursor was used correctly
|
369
341
|
assert mock_cursor.execute.call_count == 2 # 表存在性检查 + 索引查询
|
370
|
-
|
342
|
+
|
371
343
|
# Verify the result format
|
372
344
|
assert isinstance(result, str)
|
373
345
|
assert "Index: PRIMARY" in result
|
374
346
|
assert "Type: UNIQUE" in result
|
375
347
|
assert "Method: BTREE" in result
|
376
348
|
assert "Columns:" in result
|
377
|
-
|
349
|
+
|
378
350
|
# Verify connection was closed
|
379
351
|
mock_conn.close.assert_called_once()
|
380
352
|
|
@@ -388,13 +360,13 @@ class TestMySQLHandler:
|
|
388
360
|
# 首先模拟表存在性检查返回成功
|
389
361
|
mock_cursor.fetchone.side_effect = [{'count': 1}]
|
390
362
|
mock_cursor.fetchall.return_value = []
|
391
|
-
|
363
|
+
|
392
364
|
# Call the method
|
393
365
|
result = await handler.get_table_indexes('users')
|
394
|
-
|
366
|
+
|
395
367
|
# Verify the result format
|
396
368
|
assert result == "No indexes found on table users"
|
397
|
-
|
369
|
+
|
398
370
|
# Verify connection was closed
|
399
371
|
mock_conn.close.assert_called_once()
|
400
372
|
|
@@ -406,7 +378,7 @@ class TestMySQLHandler:
|
|
406
378
|
# Call the method and expect an exception
|
407
379
|
with pytest.raises(ConnectionHandlerError, match="Failed to get index information"):
|
408
380
|
await handler.get_table_indexes('users')
|
409
|
-
|
381
|
+
|
410
382
|
# Verify error was recorded
|
411
383
|
handler.stats.record_error.assert_called_once()
|
412
384
|
|
@@ -427,29 +399,29 @@ class TestMySQLHandler:
|
|
427
399
|
{'column_name': 'id', 'data_type': 'int', 'column_type': 'int(11)'},
|
428
400
|
{'column_name': 'name', 'data_type': 'varchar', 'column_type': 'varchar(255)'}
|
429
401
|
]
|
430
|
-
|
402
|
+
|
431
403
|
# Set up the mock cursor to return different data for different queries
|
432
404
|
mock_cursor = mock_conn.cursor().__enter__()
|
433
405
|
# 首先模拟表存在性检查返回成功
|
434
406
|
mock_cursor.fetchone.side_effect = [{'count': 1}, table_stats]
|
435
407
|
mock_cursor.fetchall.return_value = columns
|
436
|
-
|
408
|
+
|
437
409
|
# Call the method
|
438
410
|
result = await handler.get_table_stats('users')
|
439
|
-
|
411
|
+
|
440
412
|
# Verify connection was made
|
441
413
|
mock_connect.assert_called_once()
|
442
|
-
|
414
|
+
|
443
415
|
# Verify the cursor was used correctly
|
444
416
|
assert mock_cursor.execute.call_count == 3 # 表存在性检查 + 表统计查询 + 列信息查询
|
445
|
-
|
417
|
+
|
446
418
|
# Verify the result format
|
447
419
|
assert isinstance(result, str)
|
448
420
|
assert "Table Statistics for users:" in result
|
449
421
|
assert "Estimated Row Count: 1,000" in result
|
450
422
|
assert "Data Length: 100,000" in result
|
451
423
|
assert "Average Row Length: 100" in result
|
452
|
-
|
424
|
+
|
453
425
|
# Verify connection was closed
|
454
426
|
mock_conn.close.assert_called_once()
|
455
427
|
|
@@ -462,13 +434,13 @@ class TestMySQLHandler:
|
|
462
434
|
mock_cursor = mock_conn.cursor().__enter__()
|
463
435
|
# 首先模拟表存在性检查返回成功,但表统计查询返回空
|
464
436
|
mock_cursor.fetchone.side_effect = [{'count': 1}, None]
|
465
|
-
|
437
|
+
|
466
438
|
# Call the method
|
467
439
|
result = await handler.get_table_stats('users')
|
468
|
-
|
440
|
+
|
469
441
|
# Verify the result format
|
470
442
|
assert result == 'No statistics found for table users'
|
471
|
-
|
443
|
+
|
472
444
|
# Verify connection was closed
|
473
445
|
mock_conn.close.assert_called_once()
|
474
446
|
|
@@ -480,7 +452,7 @@ class TestMySQLHandler:
|
|
480
452
|
# Call the method and expect an exception
|
481
453
|
with pytest.raises(ConnectionHandlerError, match="Failed to get table statistics"):
|
482
454
|
await handler.get_table_stats('users')
|
483
|
-
|
455
|
+
|
484
456
|
# Verify error was recorded
|
485
457
|
handler.stats.record_error.assert_called_once()
|
486
458
|
|
@@ -506,21 +478,21 @@ class TestMySQLHandler:
|
|
506
478
|
'referenced_column_name': 'id'
|
507
479
|
}
|
508
480
|
]
|
509
|
-
|
481
|
+
|
510
482
|
mock_cursor = mock_conn.cursor().__enter__()
|
511
483
|
# 首先模拟表存在性检查返回成功
|
512
484
|
mock_cursor.fetchone.side_effect = [{'count': 1}]
|
513
485
|
mock_cursor.fetchall.return_value = constraints
|
514
|
-
|
486
|
+
|
515
487
|
# Call the method
|
516
488
|
result = await handler.get_table_constraints('users')
|
517
|
-
|
489
|
+
|
518
490
|
# Verify connection was made
|
519
491
|
mock_connect.assert_called_once()
|
520
|
-
|
492
|
+
|
521
493
|
# Verify the cursor was used correctly
|
522
494
|
assert mock_cursor.execute.call_count == 2 # 表存在性检查 + 约束查询
|
523
|
-
|
495
|
+
|
524
496
|
# Verify the result format
|
525
497
|
assert isinstance(result, str)
|
526
498
|
assert "Constraints for users:" in result
|
@@ -528,7 +500,7 @@ class TestMySQLHandler:
|
|
528
500
|
assert "FOREIGN KEY" in result
|
529
501
|
assert "fk_order" in result
|
530
502
|
assert "orders.id" in result
|
531
|
-
|
503
|
+
|
532
504
|
# Verify connection was closed
|
533
505
|
mock_conn.close.assert_called_once()
|
534
506
|
|
@@ -542,13 +514,13 @@ class TestMySQLHandler:
|
|
542
514
|
# 首先模拟表存在性检查返回成功
|
543
515
|
mock_cursor.fetchone.side_effect = [{'count': 1}]
|
544
516
|
mock_cursor.fetchall.return_value = []
|
545
|
-
|
517
|
+
|
546
518
|
# Call the method
|
547
519
|
result = await handler.get_table_constraints('users')
|
548
|
-
|
520
|
+
|
549
521
|
# Verify the result format
|
550
522
|
assert result == 'No constraints found on table users'
|
551
|
-
|
523
|
+
|
552
524
|
# Verify connection was closed
|
553
525
|
mock_conn.close.assert_called_once()
|
554
526
|
|
@@ -560,7 +532,7 @@ class TestMySQLHandler:
|
|
560
532
|
# Call the method and expect an exception
|
561
533
|
with pytest.raises(ConnectionHandlerError, match="Failed to get constraint information"):
|
562
534
|
await handler.get_table_constraints('users')
|
563
|
-
|
535
|
+
|
564
536
|
# Verify error was recorded
|
565
537
|
handler.stats.record_error.assert_called_once()
|
566
538
|
|
@@ -572,22 +544,22 @@ class TestMySQLHandler:
|
|
572
544
|
# Mock cursor to return explain results
|
573
545
|
explain_result = [{'EXPLAIN': 'Table scan on users'}]
|
574
546
|
analyze_result = [{'EXPLAIN': 'Table scan on users (actual time=0.1..0.2 rows=100)'}]
|
575
|
-
|
547
|
+
|
576
548
|
# Set up the mock cursor to return different data for different queries
|
577
549
|
mock_cursor = mock_conn.cursor().__enter__()
|
578
550
|
mock_cursor.fetchall.side_effect = [explain_result, analyze_result]
|
579
|
-
|
551
|
+
|
580
552
|
# Call the method
|
581
553
|
result = await handler.explain_query('SELECT * FROM users')
|
582
|
-
|
554
|
+
|
583
555
|
# Verify connection was made
|
584
556
|
mock_connect.assert_called_once()
|
585
|
-
|
557
|
+
|
586
558
|
# Verify the cursor was used correctly
|
587
559
|
assert mock_cursor.execute.call_count == 2
|
588
560
|
mock_cursor.execute.assert_any_call('EXPLAIN FORMAT=TREE SELECT * FROM users')
|
589
561
|
mock_cursor.execute.assert_any_call('EXPLAIN ANALYZE SELECT * FROM users')
|
590
|
-
|
562
|
+
|
591
563
|
# Verify the result format
|
592
564
|
assert isinstance(result, str)
|
593
565
|
assert "Query Execution Plan:" in result
|
@@ -595,7 +567,7 @@ class TestMySQLHandler:
|
|
595
567
|
assert "Table scan on users" in result
|
596
568
|
assert "Actual Plan (ANALYZE):" in result
|
597
569
|
assert "Table scan on users (actual time=0.1..0.2 rows=100)" in result
|
598
|
-
|
570
|
+
|
599
571
|
# Verify connection was closed
|
600
572
|
mock_conn.close.assert_called_once()
|
601
573
|
|
@@ -607,7 +579,7 @@ class TestMySQLHandler:
|
|
607
579
|
# Call the method and expect an exception
|
608
580
|
with pytest.raises(ConnectionHandlerError, match="Failed to explain query"):
|
609
581
|
await handler.explain_query('SELECT * FROM users')
|
610
|
-
|
582
|
+
|
611
583
|
# Verify error was recorded
|
612
584
|
handler.stats.record_error.assert_called_once()
|
613
585
|
|
@@ -616,9 +588,134 @@ class TestMySQLHandler:
|
|
616
588
|
"""Test cleanup method"""
|
617
589
|
# Mock the handler.stats.to_dict method
|
618
590
|
handler.stats.to_dict.return_value = {'queries': 10, 'errors': 0}
|
619
|
-
|
591
|
+
|
620
592
|
# Call the method
|
621
593
|
await handler.cleanup()
|
622
|
-
|
594
|
+
|
623
595
|
# Verify log was called
|
624
|
-
handler.log.assert_called_once_with('info', 'Final MySQL handler stats: {\'queries\': 10, \'errors\': 0}')
|
596
|
+
handler.log.assert_called_once_with('info', 'Final MySQL handler stats: {\'queries\': 10, \'errors\': 0}')
|
597
|
+
|
598
|
+
@pytest.mark.asyncio
|
599
|
+
async def test_special_character_password(self):
|
600
|
+
"""Test handling of special characters in password"""
|
601
|
+
# Create a handler with a password containing special characters
|
602
|
+
with patch('os.path.exists', return_value=True), \
|
603
|
+
patch('builtins.open', MagicMock()), \
|
604
|
+
patch('yaml.safe_load', return_value={
|
605
|
+
'connections': {
|
606
|
+
'test_mysql': {
|
607
|
+
'type': 'mysql',
|
608
|
+
'host': 'localhost',
|
609
|
+
'port': 3306,
|
610
|
+
'user': 'testuser',
|
611
|
+
'password': 'test?pass!@#$%^&*()', # Password with special characters
|
612
|
+
'database': 'testdb'
|
613
|
+
}
|
614
|
+
}
|
615
|
+
}):
|
616
|
+
special_handler = MySQLHandler('config.yaml', 'test_mysql')
|
617
|
+
special_handler.log = MagicMock()
|
618
|
+
special_handler.stats = MagicMock()
|
619
|
+
|
620
|
+
# Mock successful connection
|
621
|
+
mock_conn = MagicMock()
|
622
|
+
mock_cursor = MagicMock()
|
623
|
+
mock_cursor.__enter__.return_value = mock_cursor
|
624
|
+
mock_cursor.fetchall.return_value = [
|
625
|
+
{'table_name': 'users', 'description': 'User table'}
|
626
|
+
]
|
627
|
+
mock_conn.cursor.return_value = mock_cursor
|
628
|
+
|
629
|
+
with patch('mysql.connector.connect', return_value=mock_conn) as mock_connect:
|
630
|
+
# Test get_tables method
|
631
|
+
result = await special_handler.get_tables()
|
632
|
+
|
633
|
+
# Verify connection was made with correct parameters including special character password
|
634
|
+
mock_connect.assert_called_once()
|
635
|
+
call_args = mock_connect.call_args[1]
|
636
|
+
assert 'password' in call_args
|
637
|
+
assert call_args['password'] == 'test?pass!@#$%^&*()'
|
638
|
+
|
639
|
+
# Verify the result
|
640
|
+
assert len(result) == 1
|
641
|
+
assert result[0].name == 'users schema'
|
642
|
+
|
643
|
+
# Verify connection was closed
|
644
|
+
mock_conn.close.assert_called_once()
|
645
|
+
|
646
|
+
@pytest.mark.asyncio
|
647
|
+
async def test_special_character_password_connection_error(self):
|
648
|
+
"""Test error handling with special characters in password"""
|
649
|
+
# Create a handler with a password containing special characters
|
650
|
+
with patch('os.path.exists', return_value=True), \
|
651
|
+
patch('builtins.open', MagicMock()), \
|
652
|
+
patch('yaml.safe_load', return_value={
|
653
|
+
'connections': {
|
654
|
+
'test_mysql': {
|
655
|
+
'type': 'mysql',
|
656
|
+
'host': 'localhost',
|
657
|
+
'port': 3306,
|
658
|
+
'user': 'testuser',
|
659
|
+
'password': 'test?pass!@#$%^&*()', # Password with special characters
|
660
|
+
'database': 'testdb'
|
661
|
+
}
|
662
|
+
}
|
663
|
+
}):
|
664
|
+
special_handler = MySQLHandler('config.yaml', 'test_mysql')
|
665
|
+
special_handler.log = MagicMock()
|
666
|
+
special_handler.stats = MagicMock()
|
667
|
+
|
668
|
+
# Mock connection failure
|
669
|
+
with patch('mysql.connector.connect', side_effect=mysql.connector.Error('Connection failed')):
|
670
|
+
# Test get_tables method with connection failure
|
671
|
+
with pytest.raises(ConnectionHandlerError, match="Failed to get tables"):
|
672
|
+
await special_handler.get_tables()
|
673
|
+
|
674
|
+
# Verify error was recorded
|
675
|
+
special_handler.stats.record_error.assert_called_once()
|
676
|
+
|
677
|
+
@pytest.mark.asyncio
|
678
|
+
async def test_variable_scope_handling(self):
|
679
|
+
"""Test proper handling of variable scope in connection methods"""
|
680
|
+
# Create a handler
|
681
|
+
with patch('os.path.exists', return_value=True), \
|
682
|
+
patch('builtins.open', MagicMock()), \
|
683
|
+
patch('yaml.safe_load', return_value={
|
684
|
+
'connections': {
|
685
|
+
'test_mysql': {
|
686
|
+
'type': 'mysql',
|
687
|
+
'host': 'localhost',
|
688
|
+
'port': 3306,
|
689
|
+
'user': 'testuser',
|
690
|
+
'password': 'testpass',
|
691
|
+
'database': 'testdb'
|
692
|
+
}
|
693
|
+
}
|
694
|
+
}):
|
695
|
+
handler = MySQLHandler('config.yaml', 'test_mysql')
|
696
|
+
handler.log = MagicMock()
|
697
|
+
handler.stats = MagicMock()
|
698
|
+
|
699
|
+
# Test all methods that use connection handling with try/finally blocks
|
700
|
+
methods_to_test = [
|
701
|
+
('get_tables', []),
|
702
|
+
('get_schema', ['users']),
|
703
|
+
('get_table_description', ['users']),
|
704
|
+
('get_table_ddl', ['users']),
|
705
|
+
('get_table_indexes', ['users']),
|
706
|
+
('get_table_stats', ['users']),
|
707
|
+
('get_table_constraints', ['users']),
|
708
|
+
('explain_query', ['SELECT * FROM users'])
|
709
|
+
]
|
710
|
+
|
711
|
+
for method_name, args in methods_to_test:
|
712
|
+
# Mock connection failure
|
713
|
+
with patch('mysql.connector.connect', side_effect=mysql.connector.Error('Connection failed')):
|
714
|
+
method = getattr(handler, method_name)
|
715
|
+
|
716
|
+
# Call the method and expect an exception
|
717
|
+
with pytest.raises(ConnectionHandlerError):
|
718
|
+
await method(*args)
|
719
|
+
|
720
|
+
# Verify error was recorded
|
721
|
+
handler.stats.record_error.assert_called()
|