mcp-dbutils 1.0.1__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.1 → mcp_dbutils-1.0.3}/CHANGELOG.md +14 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/PKG-INFO +1 -1
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/write-operations.md +22 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/configuration-write-operations.md +29 -6
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/pyproject.toml +1 -1
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/base.py +17 -7
- mcp_dbutils-1.0.3/tests/unit/test_permission_combinations.py +224 -0
- {mcp_dbutils-1.0.1 → 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.1 → mcp_dbutils-1.0.3}/tests/unit/test_write_permissions.py +15 -0
- mcp_dbutils-1.0.1/coverage.xml +0 -1928
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.coveragerc +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_ar.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_en.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_es.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_fr.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_ru.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/bug_report_zh.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_ar.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_en.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_es.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_fr.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_ru.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/documentation_improvement_zh.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_ar.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_en.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_es.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_fr.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_ru.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/feature_request_zh.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_ar.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_en.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_es.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_fr.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_ru.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/performance_issue_zh.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_ar.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_en.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_es.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_fr.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_ru.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/ISSUE_TEMPLATE/security_vulnerability_zh.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/workflows/code-style.yml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/workflows/issue-translator.yml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/workflows/quality-assurance.yml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.github/workflows/release.yml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.gitignore +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.pre-commit-config.yaml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/.releaserc.json +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/Dockerfile +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/LICENSE +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/README.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/README_AR.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/README_EN.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/README_ES.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/README_FR.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/README_RU.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/config.yaml.example +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/configuration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/examples/README.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/installation.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/technical/development.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/technical/security.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/technical/testing.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ar/usage.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/document-consistency-check.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/document-version-history.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/configuration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/installation.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/technical/development.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/technical/security.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/technical/testing.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/en/usage.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/configuration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/examples/README.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/installation.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/technical/development.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/technical/security.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/technical/testing.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/es/usage.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/configuration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/examples/README.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/installation.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/technical/development.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/technical/security.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/technical/testing.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/fr/usage.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/index.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/configuration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/examples/README.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/installation.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/technical/development.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/technical/security.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/technical/testing.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/ru/usage.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/configuration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/examples/advanced-llm-interactions.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/examples/mysql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/examples/postgresql-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/examples/sqlite-examples.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/installation-platform-specific.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/installation.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/security-best-practices.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/technical/architecture.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/technical/audit-logging.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/technical/development.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/technical/security.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/technical/sonarcloud-integration.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/technical/testing.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/technical/write-operations-design.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/usage-write-operations.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/docs/zh/usage.md +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/examples/config.yaml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/check_docs_consistency.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/check_zh_docs.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/fix_en_nav_links.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/fix_imports.sh +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/fix_remaining_issues.sh +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/fix_zh_nav_links.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/run_sonar_analysis.sh +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/scripts/sonar-ai-fix.fish +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/smithery.yaml +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/sonar-project.properties +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/__init__.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/audit.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/config.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/log.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/__init__.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/config.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/handler.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/mysql/server.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/__init__.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/config.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/handler.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/postgres/server.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/__init__.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/config.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/handler.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/sqlite/server.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/src/mcp_dbutils/stats.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/conftest.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/__init__.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/conftest.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/fixtures.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_list_connections.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_logging.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_monitoring.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_monitoring_enhanced.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_mysql.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_mysql_config.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_mysql_config_helpers.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_mysql_handler_extended.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_postgres.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_postgres_config.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_prompts.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_sqlite.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_sqlite_config.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_sqlite_handler_extended.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_tools.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/integration/test_tools_advanced.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/test_write_operations.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_audit.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_base.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_base_extended.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_base_handlers.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_base_helpers.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_base_server.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_base_write_operations.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_init.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_log.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_mysql_handler.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_mysql_server.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_postgres_handler.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_postgres_server.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_sqlite_handler.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_sqlite_server.py +0 -0
- {mcp_dbutils-1.0.1 → mcp_dbutils-1.0.3}/tests/unit/test_stats.py +0 -0
@@ -1,3 +1,17 @@
|
|
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
|
+
|
8
|
+
## [1.0.2](https://github.com/donghao1393/mcp-dbutils/compare/v1.0.1...v1.0.2) (2025-05-05)
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* 修复表名大小写敏感性问题 ([#95](https://github.com/donghao1393/mcp-dbutils/issues/95)) ([2986101](https://github.com/donghao1393/mcp-dbutils/commit/2986101656d3f6d8d70ee21c051e4d140c0c9eee)), closes [#92](https://github.com/donghao1393/mcp-dbutils/issues/92)
|
14
|
+
|
1
15
|
## [1.0.1](https://github.com/donghao1393/mcp-dbutils/compare/v1.0.0...v1.0.1) (2025-05-04)
|
2
16
|
|
3
17
|
|
@@ -41,6 +41,28 @@ connections:
|
|
41
41
|
- `default_policy`:默认策略,可选值为 `read_only`(只读)或 `allow_all`(允许所有写操作)。
|
42
42
|
- `tables`:表级权限配置,可以为每个表指定允许的操作类型。
|
43
43
|
|
44
|
+
### 表名大小写处理
|
45
|
+
|
46
|
+
MCP-DBUtils 在处理表名时采用大小写不敏感的比较策略:
|
47
|
+
|
48
|
+
- 在配置文件中,表名可以使用任意大小写(如 `users`、`Users` 或 `USERS`)。
|
49
|
+
- 在 SQL 语句中,表名同样可以使用任意大小写。
|
50
|
+
- 系统会自动将表名转换为小写进行比较,确保大小写不影响权限检查。
|
51
|
+
|
52
|
+
例如,以下配置和 SQL 语句都能正确匹配:
|
53
|
+
|
54
|
+
```yaml
|
55
|
+
# 配置文件中使用小写表名
|
56
|
+
tables:
|
57
|
+
users:
|
58
|
+
operations: ["INSERT", "UPDATE"]
|
59
|
+
```
|
60
|
+
|
61
|
+
```sql
|
62
|
+
-- SQL 语句中使用大写表名,仍然能正确匹配权限
|
63
|
+
INSERT INTO USERS (id, name) VALUES (1, 'test');
|
64
|
+
```
|
65
|
+
|
44
66
|
## 使用写操作工具
|
45
67
|
|
46
68
|
MCP-DBUtils 提供了 `dbutils-execute-write` 工具用于执行写操作:
|
@@ -92,6 +92,28 @@ tables:
|
|
92
92
|
|
93
93
|
如果未指定`operations`,则默认允许所有写操作(INSERT、UPDATE、DELETE)。
|
94
94
|
|
95
|
+
### 2.4 表名大小写处理
|
96
|
+
|
97
|
+
MCP-DBUtils 在处理表名时采用大小写不敏感的比较策略:
|
98
|
+
|
99
|
+
- 在配置文件中,表名可以使用任意大小写(如 `users`、`Users` 或 `USERS`)。
|
100
|
+
- 在 SQL 语句中,表名同样可以使用任意大小写。
|
101
|
+
- 系统会自动将表名转换为小写进行比较,确保大小写不影响权限检查。
|
102
|
+
|
103
|
+
例如,以下配置和 SQL 语句都能正确匹配:
|
104
|
+
|
105
|
+
```yaml
|
106
|
+
# 配置文件中使用小写表名
|
107
|
+
tables:
|
108
|
+
users:
|
109
|
+
operations: [INSERT, UPDATE]
|
110
|
+
```
|
111
|
+
|
112
|
+
```sql
|
113
|
+
-- SQL 语句中使用大写表名,仍然能正确匹配权限
|
114
|
+
INSERT INTO USERS (id, name) VALUES (1, 'test');
|
115
|
+
```
|
116
|
+
|
95
117
|
## 3. 完整配置示例
|
96
118
|
|
97
119
|
以下是一个包含多种配置场景的完整示例:
|
@@ -103,7 +125,7 @@ connections:
|
|
103
125
|
type: sqlite
|
104
126
|
database: ":memory:"
|
105
127
|
# 未指定writable,默认为false(只读)
|
106
|
-
|
128
|
+
|
107
129
|
# 示例2:可写连接,无细粒度控制
|
108
130
|
simple_writable_mysql:
|
109
131
|
type: mysql
|
@@ -114,7 +136,7 @@ connections:
|
|
114
136
|
password: secret
|
115
137
|
writable: true
|
116
138
|
# 未指定write_permissions,所有表都可写
|
117
|
-
|
139
|
+
|
118
140
|
# 示例3:可写连接,有表级和操作级控制
|
119
141
|
controlled_postgres:
|
120
142
|
type: postgres
|
@@ -124,7 +146,7 @@ connections:
|
|
124
146
|
user: postgres
|
125
147
|
password: postgres
|
126
148
|
writable: true
|
127
|
-
|
149
|
+
|
128
150
|
write_permissions:
|
129
151
|
# 表级权限
|
130
152
|
tables:
|
@@ -134,10 +156,10 @@ connections:
|
|
134
156
|
operations: [INSERT] # 只允许插入
|
135
157
|
temp_data:
|
136
158
|
operations: [INSERT, UPDATE, DELETE] # 允许所有写操作
|
137
|
-
|
159
|
+
|
138
160
|
# 默认策略
|
139
161
|
default_policy: read_only # 未指定的表默认只读
|
140
|
-
|
162
|
+
|
141
163
|
# 示例4:可写连接,只有默认策略
|
142
164
|
all_writable_postgres:
|
143
165
|
type: postgres
|
@@ -147,7 +169,7 @@ connections:
|
|
147
169
|
user: postgres
|
148
170
|
password: postgres
|
149
171
|
writable: true
|
150
|
-
|
172
|
+
|
151
173
|
write_permissions:
|
152
174
|
default_policy: allow_all # 所有表默认可写
|
153
175
|
```
|
@@ -210,6 +232,7 @@ connections:
|
|
210
232
|
| "Connection is not configured for write operations" | 连接未设置`writable: true` | 在配置文件中添加`writable: true` |
|
211
233
|
| "No permission to perform [操作] on table [表名]" | 表未被允许执行该操作 | 在`write_permissions.tables`中添加表和操作 |
|
212
234
|
| "Operation not confirmed" | 未提供正确的确认参数 | 在工具调用中添加`confirmation: "CONFIRM_WRITE"` |
|
235
|
+
| "No permission to perform [操作] on table [大写表名]" | SQL语句中使用了大写表名,但配置中使用小写表名 | 系统已支持大小写不敏感的表名比较,此问题在v1.0.1及以上版本已修复 |
|
213
236
|
|
214
237
|
## 6. 配置迁移
|
215
238
|
|
@@ -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('`"[]')
|
@@ -625,6 +629,9 @@ class ConnectionServer:
|
|
625
629
|
# 没有细粒度权限控制,默认允许所有写操作
|
626
630
|
return
|
627
631
|
|
632
|
+
# 将表名转换为小写,用于大小写不敏感的比较
|
633
|
+
table_name_lower = table_name.lower()
|
634
|
+
|
628
635
|
# 检查表级权限
|
629
636
|
tables = write_permissions.get("tables", {})
|
630
637
|
if not tables:
|
@@ -638,9 +645,12 @@ class ConnectionServer:
|
|
638
645
|
operation=operation_type, table=table_name
|
639
646
|
))
|
640
647
|
|
641
|
-
#
|
642
|
-
|
643
|
-
|
648
|
+
# 创建表名到配置的映射,支持大小写不敏感的比较
|
649
|
+
tables_lower = {k.lower(): v for k, v in tables.items()}
|
650
|
+
|
651
|
+
# 检查特定表的权限(大小写不敏感)
|
652
|
+
if table_name_lower in tables_lower:
|
653
|
+
table_config = tables_lower[table_name_lower]
|
644
654
|
operations = table_config.get("operations", ["INSERT", "UPDATE", "DELETE"])
|
645
655
|
if operation_type in operations:
|
646
656
|
return
|
@@ -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"
|