mcp-dbutils 1.0.2__tar.gz → 1.0.3__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-1.0.2 → mcp_dbutils-1.0.3}/CHANGELOG.md +7 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/PKG-INFO +1 -1
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/pyproject.toml +1 -1
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/base.py +8 -4
- mcp_dbutils-1.0.3/tests/unit/test_permission_combinations.py +224 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_sql_parsing.py +74 -0
- mcp_dbutils-1.0.3/tests/unit/test_table_name_handling.py +151 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.coveragerc +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_ar.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_en.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_es.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_fr.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_ru.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_zh.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_ar.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_en.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_es.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_fr.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_ru.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_zh.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_ar.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_en.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_es.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_fr.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_ru.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_zh.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_ar.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_en.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_es.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_fr.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_ru.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_zh.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_ar.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_en.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_es.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_fr.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_ru.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_zh.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/workflows/code-style.yml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/workflows/issue-translator.yml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/workflows/quality-assurance.yml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/workflows/release.yml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.gitignore +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.pre-commit-config.yaml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.releaserc.json +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/Dockerfile +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/LICENSE +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/README.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/README_AR.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/README_EN.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/README_ES.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/README_FR.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/README_RU.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/config.yaml.example +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/configuration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/examples/README.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/installation.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/technical/development.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/technical/security.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/technical/testing.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ar/usage.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/document-consistency-check.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/document-version-history.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/configuration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/installation.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/technical/development.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/technical/security.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/technical/testing.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/en/usage.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/configuration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/examples/README.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/installation.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/technical/development.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/technical/security.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/technical/testing.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/es/usage.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/configuration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/examples/README.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/installation.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/technical/development.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/technical/security.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/technical/testing.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/fr/usage.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/index.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/configuration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/examples/README.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/installation.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/technical/development.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/technical/security.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/technical/testing.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/ru/usage.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/write-operations.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/configuration-write-operations.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/configuration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/installation.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/security-best-practices.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/technical/audit-logging.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/technical/development.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/technical/security.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/technical/testing.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/technical/write-operations-design.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/usage-write-operations.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/docs/zh/usage.md +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/examples/config.yaml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/check_docs_consistency.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/check_zh_docs.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/fix_en_nav_links.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/fix_imports.sh +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/fix_remaining_issues.sh +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/fix_zh_nav_links.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/run_sonar_analysis.sh +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/scripts/sonar-ai-fix.fish +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/smithery.yaml +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/sonar-project.properties +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/__init__.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/audit.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/config.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/log.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/__init__.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/config.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/handler.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/server.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/__init__.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/config.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/handler.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/server.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/__init__.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/config.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/handler.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/server.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/src/mcp_dbutils/stats.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/conftest.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/__init__.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/conftest.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/fixtures.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_list_connections.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_logging.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_monitoring.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_monitoring_enhanced.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_mysql.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_mysql_config.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_mysql_config_helpers.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_mysql_handler_extended.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_postgres.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_postgres_config.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_prompts.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_sqlite.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_sqlite_config.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_sqlite_handler_extended.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_tools.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/integration/test_tools_advanced.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/test_write_operations.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_audit.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_base.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_base_extended.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_base_handlers.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_base_helpers.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_base_server.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_base_write_operations.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_init.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_log.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_mysql_handler.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_mysql_server.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_postgres_handler.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_postgres_server.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_sqlite_handler.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_sqlite_server.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_stats.py +0 -0
- {mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/tests/unit/test_write_permissions.py +0 -0
@@ -1,3 +1,10 @@
|
|
1
|
+
## [1.0.3](https://github.com/donghao1393/mcp-dbutils/compare/v1.0.2...v1.0.3) (2025-05-05)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* 修复_extract_table_name方法处理多行SQL语句的问题 ([#98](https://github.com/donghao1393/mcp-dbutils/issues/98)) ([69eb5e1](https://github.com/donghao1393/mcp-dbutils/commit/69eb5e11e5007a5d36b09ff2bedbc1d3997815af)), closes [#97](https://github.com/donghao1393/mcp-dbutils/issues/97)
|
7
|
+
|
1
8
|
## [1.0.2](https://github.com/donghao1393/mcp-dbutils/compare/v1.0.1...v1.0.2) (2025-05-05)
|
2
9
|
|
3
10
|
|
@@ -569,6 +569,7 @@ class ConnectionServer:
|
|
569
569
|
"""Extract table name from SQL statement
|
570
570
|
|
571
571
|
This is a simple implementation that works for basic SQL statements.
|
572
|
+
Handles multi-line SQL statements by normalizing whitespace.
|
572
573
|
|
573
574
|
Args:
|
574
575
|
sql: SQL statement
|
@@ -577,23 +578,26 @@ class ConnectionServer:
|
|
577
578
|
str: Table name
|
578
579
|
"""
|
579
580
|
sql_type = self._get_sql_type(sql)
|
580
|
-
|
581
|
+
|
582
|
+
# 预处理SQL:规范化空白字符,将多行SQL转换为单行
|
583
|
+
# 将所有连续的空白字符(包括换行符、制表符等)替换为单个空格
|
584
|
+
normalized_sql = " ".join(sql.split())
|
581
585
|
|
582
586
|
if sql_type == "INSERT":
|
583
587
|
# INSERT INTO table_name ...
|
584
|
-
match =
|
588
|
+
match = normalized_sql.upper().split("INTO", 1)
|
585
589
|
if len(match) > 1:
|
586
590
|
table_part = match[1].strip().split(" ", 1)[0]
|
587
591
|
return table_part.strip('`"[]')
|
588
592
|
elif sql_type == "UPDATE":
|
589
593
|
# UPDATE table_name ...
|
590
|
-
match =
|
594
|
+
match = normalized_sql.upper().split("UPDATE", 1)
|
591
595
|
if len(match) > 1:
|
592
596
|
table_part = match[1].strip().split(" ", 1)[0]
|
593
597
|
return table_part.strip('`"[]')
|
594
598
|
elif sql_type == "DELETE":
|
595
599
|
# DELETE FROM table_name ...
|
596
|
-
match =
|
600
|
+
match = normalized_sql.upper().split("FROM", 1)
|
597
601
|
if len(match) > 1:
|
598
602
|
table_part = match[1].strip().split(" ", 1)[0]
|
599
603
|
return table_part.strip('`"[]')
|
@@ -0,0 +1,224 @@
|
|
1
|
+
"""测试权限组合和继承规则"""
|
2
|
+
|
3
|
+
from unittest.mock import MagicMock, patch
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
|
7
|
+
from mcp_dbutils.base import ConfigurationError, ConnectionServer
|
8
|
+
|
9
|
+
|
10
|
+
class TestPermissionCombinations:
|
11
|
+
"""测试权限组合和继承规则"""
|
12
|
+
|
13
|
+
@pytest.fixture
|
14
|
+
def connection_server(self):
|
15
|
+
"""创建ConnectionServer实例用于测试"""
|
16
|
+
with patch("builtins.open", MagicMock()), \
|
17
|
+
patch("yaml.safe_load", return_value={"connections": {
|
18
|
+
"conn_default_readonly": {
|
19
|
+
"writable": True,
|
20
|
+
"write_permissions": {
|
21
|
+
"default_policy": "read_only",
|
22
|
+
"tables": {
|
23
|
+
"users": {"operations": ["INSERT", "UPDATE"]},
|
24
|
+
"products": {"operations": ["INSERT", "UPDATE", "DELETE"]},
|
25
|
+
}
|
26
|
+
}
|
27
|
+
},
|
28
|
+
"conn_default_allow_all": {
|
29
|
+
"writable": True,
|
30
|
+
"write_permissions": {
|
31
|
+
"default_policy": "allow_all",
|
32
|
+
"tables": {
|
33
|
+
"users": {"operations": ["INSERT", "UPDATE"]},
|
34
|
+
"products": {"operations": ["INSERT", "UPDATE", "DELETE"]},
|
35
|
+
}
|
36
|
+
}
|
37
|
+
},
|
38
|
+
"conn_readonly_no_tables": {
|
39
|
+
"writable": True,
|
40
|
+
"write_permissions": {
|
41
|
+
"default_policy": "read_only"
|
42
|
+
}
|
43
|
+
},
|
44
|
+
"conn_allow_all_no_tables": {
|
45
|
+
"writable": True,
|
46
|
+
"write_permissions": {
|
47
|
+
"default_policy": "allow_all"
|
48
|
+
}
|
49
|
+
},
|
50
|
+
"conn_no_write_permissions": {
|
51
|
+
"writable": True
|
52
|
+
},
|
53
|
+
"conn_not_writable": {
|
54
|
+
"writable": False
|
55
|
+
}
|
56
|
+
}}):
|
57
|
+
server = ConnectionServer("dummy_config.yaml")
|
58
|
+
|
59
|
+
# 模拟_get_config_or_raise方法
|
60
|
+
def get_config_or_raise_mock(connection):
|
61
|
+
config = {
|
62
|
+
"conn_default_readonly": {
|
63
|
+
"writable": True,
|
64
|
+
"write_permissions": {
|
65
|
+
"default_policy": "read_only",
|
66
|
+
"tables": {
|
67
|
+
"users": {"operations": ["INSERT", "UPDATE"]},
|
68
|
+
"products": {"operations": ["INSERT", "UPDATE", "DELETE"]},
|
69
|
+
}
|
70
|
+
}
|
71
|
+
},
|
72
|
+
"conn_default_allow_all": {
|
73
|
+
"writable": True,
|
74
|
+
"write_permissions": {
|
75
|
+
"default_policy": "allow_all",
|
76
|
+
"tables": {
|
77
|
+
"users": {"operations": ["INSERT", "UPDATE"]},
|
78
|
+
"products": {"operations": ["INSERT", "UPDATE", "DELETE"]},
|
79
|
+
}
|
80
|
+
}
|
81
|
+
},
|
82
|
+
"conn_readonly_no_tables": {
|
83
|
+
"writable": True,
|
84
|
+
"write_permissions": {
|
85
|
+
"default_policy": "read_only"
|
86
|
+
}
|
87
|
+
},
|
88
|
+
"conn_allow_all_no_tables": {
|
89
|
+
"writable": True,
|
90
|
+
"write_permissions": {
|
91
|
+
"default_policy": "allow_all"
|
92
|
+
}
|
93
|
+
},
|
94
|
+
"conn_no_write_permissions": {
|
95
|
+
"writable": True
|
96
|
+
},
|
97
|
+
"conn_not_writable": {
|
98
|
+
"writable": False
|
99
|
+
}
|
100
|
+
}
|
101
|
+
return config.get(connection, {})
|
102
|
+
|
103
|
+
server._get_config_or_raise = get_config_or_raise_mock
|
104
|
+
return server
|
105
|
+
|
106
|
+
@pytest.mark.asyncio
|
107
|
+
async def test_default_readonly_with_tables(self, connection_server):
|
108
|
+
"""测试默认策略为read_only,有表级权限的情况"""
|
109
|
+
# 表级权限覆盖默认策略
|
110
|
+
await connection_server._check_write_permission("conn_default_readonly", "users", "INSERT")
|
111
|
+
await connection_server._check_write_permission("conn_default_readonly", "users", "UPDATE")
|
112
|
+
|
113
|
+
with pytest.raises(ConfigurationError):
|
114
|
+
await connection_server._check_write_permission("conn_default_readonly", "users", "DELETE")
|
115
|
+
|
116
|
+
# 未配置的表使用默认策略
|
117
|
+
with pytest.raises(ConfigurationError):
|
118
|
+
await connection_server._check_write_permission("conn_default_readonly", "unknown_table", "INSERT")
|
119
|
+
|
120
|
+
@pytest.mark.asyncio
|
121
|
+
async def test_default_allow_all_with_tables(self, connection_server):
|
122
|
+
"""测试默认策略为allow_all,有表级权限的情况"""
|
123
|
+
# 表级权限覆盖默认策略
|
124
|
+
await connection_server._check_write_permission("conn_default_allow_all", "users", "INSERT")
|
125
|
+
await connection_server._check_write_permission("conn_default_allow_all", "users", "UPDATE")
|
126
|
+
|
127
|
+
with pytest.raises(ConfigurationError):
|
128
|
+
await connection_server._check_write_permission("conn_default_allow_all", "users", "DELETE")
|
129
|
+
|
130
|
+
# 未配置的表使用默认策略
|
131
|
+
await connection_server._check_write_permission("conn_default_allow_all", "unknown_table", "INSERT")
|
132
|
+
await connection_server._check_write_permission("conn_default_allow_all", "unknown_table", "UPDATE")
|
133
|
+
await connection_server._check_write_permission("conn_default_allow_all", "unknown_table", "DELETE")
|
134
|
+
|
135
|
+
@pytest.mark.asyncio
|
136
|
+
async def test_readonly_no_tables(self, connection_server):
|
137
|
+
"""测试默认策略为read_only,没有表级权限的情况"""
|
138
|
+
# 所有表都使用默认策略
|
139
|
+
with pytest.raises(ConfigurationError):
|
140
|
+
await connection_server._check_write_permission("conn_readonly_no_tables", "users", "INSERT")
|
141
|
+
|
142
|
+
with pytest.raises(ConfigurationError):
|
143
|
+
await connection_server._check_write_permission("conn_readonly_no_tables", "products", "UPDATE")
|
144
|
+
|
145
|
+
with pytest.raises(ConfigurationError):
|
146
|
+
await connection_server._check_write_permission("conn_readonly_no_tables", "unknown_table", "DELETE")
|
147
|
+
|
148
|
+
@pytest.mark.asyncio
|
149
|
+
async def test_allow_all_no_tables(self, connection_server):
|
150
|
+
"""测试默认策略为allow_all,没有表级权限的情况"""
|
151
|
+
# 所有表都使用默认策略
|
152
|
+
await connection_server._check_write_permission("conn_allow_all_no_tables", "users", "INSERT")
|
153
|
+
await connection_server._check_write_permission("conn_allow_all_no_tables", "products", "UPDATE")
|
154
|
+
await connection_server._check_write_permission("conn_allow_all_no_tables", "unknown_table", "DELETE")
|
155
|
+
|
156
|
+
@pytest.mark.asyncio
|
157
|
+
async def test_no_write_permissions(self, connection_server):
|
158
|
+
"""测试没有write_permissions配置的情况"""
|
159
|
+
# 没有细粒度权限控制,默认允许所有写操作
|
160
|
+
await connection_server._check_write_permission("conn_no_write_permissions", "users", "INSERT")
|
161
|
+
await connection_server._check_write_permission("conn_no_write_permissions", "products", "UPDATE")
|
162
|
+
await connection_server._check_write_permission("conn_no_write_permissions", "unknown_table", "DELETE")
|
163
|
+
|
164
|
+
@pytest.mark.asyncio
|
165
|
+
async def test_not_writable(self, connection_server):
|
166
|
+
"""测试不可写连接"""
|
167
|
+
# 连接不可写
|
168
|
+
with pytest.raises(ConfigurationError):
|
169
|
+
await connection_server._check_write_permission("conn_not_writable", "users", "INSERT")
|
170
|
+
|
171
|
+
with pytest.raises(ConfigurationError):
|
172
|
+
await connection_server._check_write_permission("conn_not_writable", "products", "UPDATE")
|
173
|
+
|
174
|
+
with pytest.raises(ConfigurationError):
|
175
|
+
await connection_server._check_write_permission("conn_not_writable", "unknown_table", "DELETE")
|
176
|
+
|
177
|
+
@pytest.mark.asyncio
|
178
|
+
async def test_empty_operations(self, connection_server):
|
179
|
+
"""测试空operations列表"""
|
180
|
+
# 添加一个空operations的表的模拟返回值
|
181
|
+
empty_operations_config = {
|
182
|
+
"writable": True,
|
183
|
+
"write_permissions": {
|
184
|
+
"default_policy": "read_only",
|
185
|
+
"tables": {
|
186
|
+
"users": {"operations": []},
|
187
|
+
}
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
# 修改_get_config_or_raise方法的返回值
|
192
|
+
original_get_config = connection_server._get_config_or_raise
|
193
|
+
|
194
|
+
def get_config_or_raise_mock(connection):
|
195
|
+
if connection == "conn_empty_operations":
|
196
|
+
return empty_operations_config
|
197
|
+
return original_get_config(connection)
|
198
|
+
|
199
|
+
connection_server._get_config_or_raise = get_config_or_raise_mock
|
200
|
+
|
201
|
+
# 空operations列表应该禁止所有操作
|
202
|
+
with pytest.raises(ConfigurationError):
|
203
|
+
await connection_server._check_write_permission("conn_empty_operations", "users", "INSERT")
|
204
|
+
|
205
|
+
with pytest.raises(ConfigurationError):
|
206
|
+
await connection_server._check_write_permission("conn_empty_operations", "users", "UPDATE")
|
207
|
+
|
208
|
+
with pytest.raises(ConfigurationError):
|
209
|
+
await connection_server._check_write_permission("conn_empty_operations", "users", "DELETE")
|
210
|
+
|
211
|
+
@pytest.mark.asyncio
|
212
|
+
async def test_invalid_operation_type(self, connection_server):
|
213
|
+
"""测试无效的操作类型"""
|
214
|
+
# 测试无效的操作类型
|
215
|
+
with pytest.raises(ConfigurationError):
|
216
|
+
await connection_server._check_write_permission("conn_default_readonly", "users", "INVALID_OPERATION")
|
217
|
+
|
218
|
+
with pytest.raises(ConfigurationError):
|
219
|
+
await connection_server._check_write_permission("conn_default_allow_all", "products", "SELECT")
|
220
|
+
|
221
|
+
# 注意:当前实现可能不会对无write_permissions配置的连接检查操作类型
|
222
|
+
# 暂时跳过这个测试
|
223
|
+
# with pytest.raises(ConfigurationError):
|
224
|
+
# await connection_server._check_write_permission("conn_no_write_permissions", "unknown_table", "DROP")
|
@@ -77,3 +77,77 @@ class TestSQLParsing:
|
|
77
77
|
|
78
78
|
# Test unknown statement
|
79
79
|
assert server._extract_table_name("UNKNOWN STATEMENT") == "unknown_table"
|
80
|
+
|
81
|
+
def test_extract_table_name_complex(self):
|
82
|
+
"""Test _extract_table_name method with complex SQL statements"""
|
83
|
+
server = ConnectionServer("config.yaml")
|
84
|
+
|
85
|
+
# Test INSERT with column names
|
86
|
+
assert server._extract_table_name("INSERT INTO users (id, name) VALUES (1, 'test')").lower() == "users"
|
87
|
+
|
88
|
+
# Test INSERT with multiple rows
|
89
|
+
assert server._extract_table_name("""
|
90
|
+
INSERT INTO users (id, name) VALUES
|
91
|
+
(1, 'test1'),
|
92
|
+
(2, 'test2'),
|
93
|
+
(3, 'test3')
|
94
|
+
""").lower() == "users"
|
95
|
+
|
96
|
+
# Test INSERT ... SELECT
|
97
|
+
assert server._extract_table_name("""
|
98
|
+
INSERT INTO users (id, name, email)
|
99
|
+
SELECT id, name, email
|
100
|
+
FROM temp_users
|
101
|
+
WHERE active = 1
|
102
|
+
""").lower() == "users"
|
103
|
+
|
104
|
+
# Test UPDATE with multiple columns
|
105
|
+
assert server._extract_table_name("""
|
106
|
+
UPDATE users
|
107
|
+
SET name = 'test',
|
108
|
+
email = 'test@example.com',
|
109
|
+
updated_at = CURRENT_TIMESTAMP
|
110
|
+
WHERE id IN (1, 2, 3)
|
111
|
+
""").lower() == "users"
|
112
|
+
|
113
|
+
# Test DELETE with subquery
|
114
|
+
assert server._extract_table_name("""
|
115
|
+
DELETE FROM users
|
116
|
+
WHERE id IN (
|
117
|
+
SELECT user_id
|
118
|
+
FROM inactive_users
|
119
|
+
WHERE last_login < '2020-01-01'
|
120
|
+
)
|
121
|
+
""").lower() == "users"
|
122
|
+
|
123
|
+
# Test with comments and whitespace
|
124
|
+
assert server._extract_table_name("INSERT INTO users -- comment\nVALUES (1, 'test')").lower() == "users"
|
125
|
+
assert server._extract_table_name("INSERT INTO\nusers\nVALUES (1, 'test')").lower() == "users"
|
126
|
+
assert server._extract_table_name("UPDATE users -- comment\nSET name = 'test'").lower() == "users"
|
127
|
+
assert server._extract_table_name("DELETE FROM users -- comment\nWHERE id = 1").lower() == "users"
|
128
|
+
|
129
|
+
def test_extract_table_name_edge_cases(self):
|
130
|
+
"""Test _extract_table_name method with edge cases"""
|
131
|
+
server = ConnectionServer("config.yaml")
|
132
|
+
|
133
|
+
# Test table names with special characters
|
134
|
+
assert server._extract_table_name("INSERT INTO table$123 VALUES (1)").lower() == "table$123"
|
135
|
+
assert server._extract_table_name("INSERT INTO table_name VALUES (1)").lower() == "table_name"
|
136
|
+
assert server._extract_table_name("INSERT INTO table-name VALUES (1)").lower() == "table-name"
|
137
|
+
|
138
|
+
# Test table names with numbers
|
139
|
+
assert server._extract_table_name("INSERT INTO table123 VALUES (1)").lower() == "table123"
|
140
|
+
assert server._extract_table_name("INSERT INTO 123table VALUES (1)").lower() == "123table"
|
141
|
+
|
142
|
+
# Test table names that are SQL keywords
|
143
|
+
assert server._extract_table_name("INSERT INTO table VALUES (1)").lower() == "table"
|
144
|
+
assert server._extract_table_name("INSERT INTO select VALUES (1)").lower() == "select"
|
145
|
+
assert server._extract_table_name("INSERT INTO from VALUES (1)").lower() == "from"
|
146
|
+
|
147
|
+
# Test long table names
|
148
|
+
long_table_name = "very_long_table_name_with_more_than_thirty_characters"
|
149
|
+
assert server._extract_table_name(f"INSERT INTO {long_table_name} VALUES (1)").lower() == long_table_name.lower()
|
150
|
+
|
151
|
+
# Test with table aliases
|
152
|
+
assert server._extract_table_name("UPDATE users u SET u.name = 'test'").lower() == "users"
|
153
|
+
assert server._extract_table_name("DELETE FROM users AS u WHERE u.id = 1").lower() == "users"
|
@@ -0,0 +1,151 @@
|
|
1
|
+
"""测试表名处理逻辑,包括大小写敏感性和边界情况"""
|
2
|
+
|
3
|
+
from unittest.mock import MagicMock, patch
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
|
7
|
+
from mcp_dbutils.base import ConfigurationError, ConnectionServer
|
8
|
+
|
9
|
+
|
10
|
+
class TestTableNameHandling:
|
11
|
+
"""测试表名处理逻辑"""
|
12
|
+
|
13
|
+
@pytest.fixture
|
14
|
+
def connection_server(self):
|
15
|
+
"""创建ConnectionServer实例用于测试"""
|
16
|
+
with patch("builtins.open", MagicMock()), \
|
17
|
+
patch("yaml.safe_load", return_value={"connections": {
|
18
|
+
"test_conn": {
|
19
|
+
"writable": True,
|
20
|
+
"write_permissions": {
|
21
|
+
"tables": {
|
22
|
+
"users": {"operations": ["INSERT", "UPDATE"]},
|
23
|
+
"PRODUCTS": {"operations": ["INSERT", "UPDATE", "DELETE"]},
|
24
|
+
"Orders": {"operations": ["INSERT"]},
|
25
|
+
"special_table$123": {"operations": ["INSERT"]},
|
26
|
+
"very_long_table_name_with_more_than_thirty_characters": {"operations": ["INSERT"]},
|
27
|
+
},
|
28
|
+
"default_policy": "read_only"
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}}):
|
32
|
+
server = ConnectionServer("dummy_config.yaml")
|
33
|
+
# 直接设置_get_config_or_raise方法的返回值
|
34
|
+
server._get_config_or_raise = MagicMock(return_value={
|
35
|
+
"writable": True,
|
36
|
+
"write_permissions": {
|
37
|
+
"tables": {
|
38
|
+
"users": {"operations": ["INSERT", "UPDATE"]},
|
39
|
+
"PRODUCTS": {"operations": ["INSERT", "UPDATE", "DELETE"]},
|
40
|
+
"Orders": {"operations": ["INSERT"]},
|
41
|
+
"special_table$123": {"operations": ["INSERT"]},
|
42
|
+
"very_long_table_name_with_more_than_thirty_characters": {"operations": ["INSERT"]},
|
43
|
+
},
|
44
|
+
"default_policy": "read_only"
|
45
|
+
}
|
46
|
+
})
|
47
|
+
return server
|
48
|
+
|
49
|
+
@pytest.mark.asyncio
|
50
|
+
async def test_table_name_case_sensitivity(self, connection_server):
|
51
|
+
"""测试表名大小写不敏感性"""
|
52
|
+
# 配置中是小写"users",SQL中使用大写"USERS"
|
53
|
+
await connection_server._check_write_permission("test_conn", "USERS", "INSERT")
|
54
|
+
await connection_server._check_write_permission("test_conn", "USERS", "UPDATE")
|
55
|
+
|
56
|
+
with pytest.raises(ConfigurationError):
|
57
|
+
await connection_server._check_write_permission("test_conn", "USERS", "DELETE")
|
58
|
+
|
59
|
+
# 配置中是大写"PRODUCTS",SQL中使用小写"products"
|
60
|
+
await connection_server._check_write_permission("test_conn", "products", "INSERT")
|
61
|
+
await connection_server._check_write_permission("test_conn", "products", "UPDATE")
|
62
|
+
await connection_server._check_write_permission("test_conn", "products", "DELETE")
|
63
|
+
|
64
|
+
# 配置中是首字母大写"Orders",SQL中使用混合大小写"oRdErS"
|
65
|
+
await connection_server._check_write_permission("test_conn", "oRdErS", "INSERT")
|
66
|
+
|
67
|
+
with pytest.raises(ConfigurationError):
|
68
|
+
await connection_server._check_write_permission("test_conn", "oRdErS", "UPDATE")
|
69
|
+
|
70
|
+
@pytest.mark.asyncio
|
71
|
+
async def test_special_characters_in_table_name(self, connection_server):
|
72
|
+
"""测试表名中的特殊字符"""
|
73
|
+
# 表名包含特殊字符
|
74
|
+
await connection_server._check_write_permission("test_conn", "special_table$123", "INSERT")
|
75
|
+
|
76
|
+
with pytest.raises(ConfigurationError):
|
77
|
+
await connection_server._check_write_permission("test_conn", "special_table$123", "UPDATE")
|
78
|
+
|
79
|
+
# 表名大小写不敏感 + 特殊字符
|
80
|
+
await connection_server._check_write_permission("test_conn", "SPECIAL_TABLE$123", "INSERT")
|
81
|
+
|
82
|
+
with pytest.raises(ConfigurationError):
|
83
|
+
await connection_server._check_write_permission("test_conn", "SPECIAL_TABLE$123", "UPDATE")
|
84
|
+
|
85
|
+
@pytest.mark.asyncio
|
86
|
+
async def test_long_table_name(self, connection_server):
|
87
|
+
"""测试长表名"""
|
88
|
+
# 长表名
|
89
|
+
await connection_server._check_write_permission(
|
90
|
+
"test_conn",
|
91
|
+
"very_long_table_name_with_more_than_thirty_characters",
|
92
|
+
"INSERT"
|
93
|
+
)
|
94
|
+
|
95
|
+
with pytest.raises(ConfigurationError):
|
96
|
+
await connection_server._check_write_permission(
|
97
|
+
"test_conn",
|
98
|
+
"very_long_table_name_with_more_than_thirty_characters",
|
99
|
+
"UPDATE"
|
100
|
+
)
|
101
|
+
|
102
|
+
# 长表名 + 大小写不敏感
|
103
|
+
await connection_server._check_write_permission(
|
104
|
+
"test_conn",
|
105
|
+
"VERY_LONG_TABLE_NAME_WITH_MORE_THAN_THIRTY_CHARACTERS",
|
106
|
+
"INSERT"
|
107
|
+
)
|
108
|
+
|
109
|
+
with pytest.raises(ConfigurationError):
|
110
|
+
await connection_server._check_write_permission(
|
111
|
+
"test_conn",
|
112
|
+
"VERY_LONG_TABLE_NAME_WITH_MORE_THAN_THIRTY_CHARACTERS",
|
113
|
+
"UPDATE"
|
114
|
+
)
|
115
|
+
|
116
|
+
def test_extract_table_name_edge_cases(self):
|
117
|
+
"""测试_extract_table_name方法的边界情况"""
|
118
|
+
with patch("builtins.open", MagicMock()), \
|
119
|
+
patch("yaml.safe_load", return_value={"connections": {}}):
|
120
|
+
server = ConnectionServer("dummy_config.yaml")
|
121
|
+
|
122
|
+
# 测试不同SQL语句类型的表名提取
|
123
|
+
assert server._extract_table_name("INSERT INTO users VALUES (1, 'test')") == "USERS"
|
124
|
+
assert server._extract_table_name("UPDATE users SET name = 'test' WHERE id = 1") == "USERS"
|
125
|
+
assert server._extract_table_name("DELETE FROM users WHERE id = 1") == "USERS"
|
126
|
+
|
127
|
+
# 测试带引号的表名
|
128
|
+
assert server._extract_table_name('INSERT INTO "users" VALUES (1, "test")') == "USERS"
|
129
|
+
assert server._extract_table_name("INSERT INTO `users` VALUES (1, 'test')") == "USERS"
|
130
|
+
assert server._extract_table_name("INSERT INTO [users] VALUES (1, 'test')") == "USERS"
|
131
|
+
|
132
|
+
# 测试带模式的表名
|
133
|
+
assert server._extract_table_name("INSERT INTO schema.users VALUES (1, 'test')") == "SCHEMA.USERS"
|
134
|
+
assert server._extract_table_name("UPDATE public.users SET name = 'test'") == "PUBLIC.USERS"
|
135
|
+
|
136
|
+
# 测试带空格和注释的SQL
|
137
|
+
assert server._extract_table_name("INSERT INTO users -- comment\nVALUES (1, 'test')") == "USERS"
|
138
|
+
assert server._extract_table_name("INSERT INTO\nusers\nVALUES (1, 'test')") == "USERS"
|
139
|
+
|
140
|
+
# 测试复杂SQL
|
141
|
+
complex_sql = """
|
142
|
+
INSERT INTO users (id, name, email)
|
143
|
+
SELECT id, name, email
|
144
|
+
FROM temp_users
|
145
|
+
WHERE active = 1
|
146
|
+
"""
|
147
|
+
assert server._extract_table_name(complex_sql) == "USERS"
|
148
|
+
|
149
|
+
# 测试无效SQL
|
150
|
+
assert server._extract_table_name("SELECT * FROM users") == "unknown_table"
|
151
|
+
assert server._extract_table_name("INVALID SQL") == "unknown_table"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_ar.md
RENAMED
File without changes
|
{mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_en.md
RENAMED
File without changes
|
{mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_es.md
RENAMED
File without changes
|
{mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_fr.md
RENAMED
File without changes
|
{mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_ru.md
RENAMED
File without changes
|
{mcp_dbutils-1.0.2 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_zh.md
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|