queryapigate 0.48.0__tar.gz → 0.49.0__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.
- {queryapigate-0.48.0/queryapigate.egg-info → queryapigate-0.49.0}/PKG-INFO +1 -1
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/__init__.py +1 -1
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/experimental.py +2 -8
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/masking.py +4 -1
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/runstore.py +1 -1
- {queryapigate-0.48.0 → queryapigate-0.49.0/queryapigate.egg-info}/PKG-INFO +1 -1
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_db.py +4 -1
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_events_server.py +1 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_experimental.py +2 -1
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_history.py +1 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_masking.py +11 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_rolling_upgrade.py +19 -2
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_upgrades.py +98 -11
- {queryapigate-0.48.0 → queryapigate-0.49.0}/LICENSE +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/NOTICE +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/README.md +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/pyproject.toml +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/__main__.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/accesslog.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/adminroles.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/admins.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/agentwrites.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/alerts.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/apikeys.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/app.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/auditchain.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/auditstream.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/aws.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/broadcast.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/bundle.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/cache.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/cli.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/collection_admin.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/config.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/configcode.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/AccessMapPage-CvmP9Sfw.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/AdministratorsPage-B1G7Dgxv.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/AgentsPage-ex_lhxAd.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/AlertsPage-CkFfLPBu.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/ApiKeyDetailPage-DluS3aag.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/ApiKeysPage-D3xeaQlS.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/ApprovalsPage-BYL5hXGT.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/AuditLogPage-TAMz5LIn.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/CachingPage-8OhOUgxW.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/CodeBox-Cwh5KHLp.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/ConnectionsPage-mHkn5I8c.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/DesignerPage-CPZneqCQ.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/DestinationsPage-Dy6NYnv3.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/Experimental-B3dnaeV7.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/ExportsPage-BzDOR2fh.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/HelpPage-BV_YH8GV.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/HomePage-BShpQIil.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/KeyActivity-DuTyhSGT.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/Managed-CQ0VXNNZ.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/McpPage-JMpMLJIz.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/MetricsPage-BOUZqgao.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/QueryInfo-n4U1MkL1.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/RepositoryPage-Cj_ZwMQj.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/Results-6QW3JK2B.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/RolesPage-Dq83u-am.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/SchemaBrowser-BzZ6DZjO.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/SettingsPage-CpjtKQ1P.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/StatTile-Cft4kJPH.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/Time-DurHvFXj.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/WorkspacesPage-Bfe_wYYe.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/api-CgyYL3VS.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/client-ZHNiMG8H.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/forms-DR_JyNks.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/forms-DdFqq646.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/icons-CFXE8hAz.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/index-0v-rImZs.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/index-Cv64XKNs.css +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/maskRules-CvKQIPgN.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/reach-BAOLd75Y.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/shared-D8E1i8vU.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/state-O13cl7cO.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/useMutation-D4M562eQ.js +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/favicon.svg +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/index.html +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/cors.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/databases.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/dataplane.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/db.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/definitions.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/deprecations.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/descriptions.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/destinations.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/duckfiles.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/embeddings.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/engine.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/errors.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/events.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/examples.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/exporting.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/exports.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/formats.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/governance.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/gunicorn_conf.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/history.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/instances.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/jwtauth.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/lib/h2-2.2.224.jar +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/limits.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/logging_setup.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/mcp_server.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/metacache.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/metrics.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/mongodocs.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/mongotools.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/oidc.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/openapi.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/params.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/parquet.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/pool.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/postman.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/quotas.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/ratelimit.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/rediscache.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/rekey.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/runners.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/schema.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/scope.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/secheaders.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/secretrefs.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/__init__.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/access.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/administrators.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/audit.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/collections.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/connections.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/descriptions.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/destinations.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/exports.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/mcp.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/queries.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/services/workspaces.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/sqlflow.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/sqltools.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/sso.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/store.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/tableguard.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/tracing.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/usage.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/v1.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/v1_spec.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/vault.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/workspaces.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate.egg-info/SOURCES.txt +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate.egg-info/dependency_links.txt +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate.egg-info/entry_points.txt +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate.egg-info/requires.txt +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate.egg-info/top_level.txt +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/setup.cfg +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_adhoc_history.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_adminroles.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_admins.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_agent_writes.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_alerts.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_api_administrators.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_api_keys.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_api_v1.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_audit_chain.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_audit_log.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_audit_siem.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_audit_stream.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_aws.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_backup_restore.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_broadcast.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_cli.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_collection_bundle.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_collection_postman.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_collections.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_configcode.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_connection_encryption.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_console.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_cors_and_rate_limit.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_data_protection.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_databases.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_dataplane.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_dependency_floors.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_deprecations.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_descriptions.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_destinations.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_docs_site.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_duckdb.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_duckdb_files.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_embeddings.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_errors.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_events.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_examples.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_export_security.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_exporting.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_exports.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_fips.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_helm.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_howto.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_instances.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_integration.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_jdbc.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_jwt.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_key_rotation.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_limits.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_masking_requests.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_mcp.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_mcp_keycloak.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_mcp_oauth.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_metacache.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_mongo_streaming.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_mongotools.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_multinode.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_observability.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_oidc.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_params.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_parquet.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_pool.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_publishing.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_query_window.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_queryapigate.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_quotas.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_rekey.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_release_check.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_response_cache.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_roles.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_runstore.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_runstore_differential.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_secretrefs.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_security_headers.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_settings.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_shared_rate_limit.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_sql_guard_fuzz.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_sql_guard_writes.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_sqlflow.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_sso.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_sso_keycloak.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_supply_chain.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_tableguard.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_tracing.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_usage.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_vault.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_workspace_config_cli.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_workspace_isolation.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_workspace_observability.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_workspace_store.py +0 -0
- {queryapigate-0.48.0 → queryapigate-0.49.0}/tests/test_workspaces_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queryapigate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.49.0
|
|
4
4
|
Summary: Turn SQL into governed REST APIs over MySQL, PostgreSQL, ClickHouse, SQLite, DuckDB, H2, MongoDB, and Parquet/CSV/JSON files - JSON, CSV, Parquet or Excel out, with scoped keys, an admin Console and MCP for AI agents.
|
|
5
5
|
Author-email: Anantha Raju C <arcswdev@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -12,7 +12,8 @@ added - when it meets every one of GRADUATION. Alerts, remote files through Duck
|
|
|
12
12
|
Console sign-in through SSO in 0.30 (ADR 0017), with the Helm chart; the audit log streamed to a SIEM in 0.32, once
|
|
13
13
|
tested against real receivers (ADR 0019); live events, and secrets from files and Vault, in 0.34 (ADR 0021) - AWS's
|
|
14
14
|
stay, until tested against AWS. The connection types have their own terms (databases.GRADUATION): MongoDB met them
|
|
15
|
-
in 0.37, with streaming exports (ADR 0022).
|
|
15
|
+
in 0.37, with streaming exports (ADR 0022). The ClickHouse run store graduated in 0.49 (ADR 0029), once upgrades from
|
|
16
|
+
each release that had it were tested.
|
|
16
17
|
"""
|
|
17
18
|
from typing import Any
|
|
18
19
|
|
|
@@ -75,13 +76,6 @@ FEATURES: dict[str, dict[str, Any]] = {
|
|
|
75
76
|
'settings': ('QUERYAPIGATE_DATAPLANE_SECRET',),
|
|
76
77
|
'docs': ('documentation/DEPLOYMENT.md', 'A Rust data plane in front of /q/'),
|
|
77
78
|
},
|
|
78
|
-
'run_store': {
|
|
79
|
-
'name': 'A ClickHouse run store for history, the event log and usage',
|
|
80
|
-
'why': 'a prototype (ADR 0029, decided go): its tables, its event ids and how it is switched on may change '
|
|
81
|
-
'before it graduates',
|
|
82
|
-
'settings': ('QUERYAPIGATE_RUN_STORE_URL',),
|
|
83
|
-
'docs': ('documentation/DEPLOYMENT.md', 'A ClickHouse run store (prototype)'),
|
|
84
|
-
},
|
|
85
79
|
}
|
|
86
80
|
|
|
87
81
|
DB_TYPES = frozenset(t for f in FEATURES.values() for t in f.get('db_types', ()))
|
|
@@ -404,7 +404,10 @@ def _store_hash_key():
|
|
|
404
404
|
def _hash(value, key):
|
|
405
405
|
if value is None:
|
|
406
406
|
return None
|
|
407
|
-
text
|
|
407
|
+
# Binary values as their text, as JSON shows them - a memoryview too, which psycopg2 hands over for bytea: str() of
|
|
408
|
+
# one is its address, not its value
|
|
409
|
+
text = bytes(value).decode('utf-8', 'replace') if isinstance(value, (bytes, bytearray, memoryview)) \
|
|
410
|
+
else str(value)
|
|
408
411
|
return hmac.new(key, text.encode('utf-8'), hashlib.sha256).hexdigest()[:16]
|
|
409
412
|
|
|
410
413
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The run store (ADR 0029
|
|
1
|
+
"""The run store (ADR 0029): run history, the event log and usage rollups in ClickHouse.
|
|
2
2
|
|
|
3
3
|
With QUERYAPIGATE_RUN_STORE_URL set, history.py's writer thread sends its batches here instead of to the metadata
|
|
4
4
|
store, and everything that reads runs or events - GET /api/v1/history, the Console's runs per version, usage
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queryapigate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.49.0
|
|
4
4
|
Summary: Turn SQL into governed REST APIs over MySQL, PostgreSQL, ClickHouse, SQLite, DuckDB, H2, MongoDB, and Parquet/CSV/JSON files - JSON, CSV, Parquet or Excel out, with scoped keys, an admin Console and MCP for AI agents.
|
|
5
5
|
Author-email: Anantha Raju C <arcswdev@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -15,7 +15,7 @@ from unittest import mock
|
|
|
15
15
|
|
|
16
16
|
from queryapigate import config, db, events, history
|
|
17
17
|
from queryapigate.store import now as store_now
|
|
18
|
-
from tests import TEST_DATABASE_URL
|
|
18
|
+
from tests import TEST_DATABASE_URL, TEST_RUN_STORE_URL
|
|
19
19
|
from tests.helpers import save_query, write_connections
|
|
20
20
|
|
|
21
21
|
sqlite_only = unittest.skipIf(TEST_DATABASE_URL, 'inspects the SQLite file itself')
|
|
@@ -316,6 +316,7 @@ class PostgresDbTests(unittest.TestCase):
|
|
|
316
316
|
self.assertIs(db.connection(), parent)
|
|
317
317
|
self.assertEqual(parent.execute('SELECT 1').fetchone()[0], 1) # the parent's is still usable
|
|
318
318
|
|
|
319
|
+
@unittest.skipIf(TEST_RUN_STORE_URL, 'a run store keeps history by time, not by count')
|
|
319
320
|
def test_concurrent_history_writes_are_all_recorded_then_trimmed(self):
|
|
320
321
|
"""Runs recorded from many threads at once (queued, then batched by history.py, whose writes skip the
|
|
321
322
|
store-wide lock on Postgres) must all land, and the per-version cap must still hold afterwards."""
|
|
@@ -456,6 +457,7 @@ class MigrateToPostgresTests(unittest.TestCase):
|
|
|
456
457
|
db.close()
|
|
457
458
|
return snapshot
|
|
458
459
|
|
|
460
|
+
@unittest.skipIf(TEST_RUN_STORE_URL, "the SQLite store's runs are in the run store, which the copy leaves there")
|
|
459
461
|
def test_copies_everything_and_the_api_sees_the_same_store(self):
|
|
460
462
|
before = os.path.getmtime(self.sqlite_path), os.path.getsize(self.sqlite_path)
|
|
461
463
|
copied = db.migrate_sqlite_to_postgres(self.sqlite_path)
|
|
@@ -467,6 +469,7 @@ class MigrateToPostgresTests(unittest.TestCase):
|
|
|
467
469
|
self.assertEqual((os.path.getmtime(self.sqlite_path), os.path.getsize(self.sqlite_path)), before)
|
|
468
470
|
self.assertEqual(copied['events'], 3)
|
|
469
471
|
|
|
472
|
+
@unittest.skipIf(TEST_RUN_STORE_URL, "the SQLite store's events are in the run store, which the copy leaves there")
|
|
470
473
|
def test_event_ids_and_their_sequence_come_across(self):
|
|
471
474
|
"""An id handed out and since swept is not handed out again: the sequence is SQLite's, not MAX(id)."""
|
|
472
475
|
source = sqlite3.connect(self.sqlite_path)
|
|
@@ -247,6 +247,7 @@ class DeliveryTests(EventServerTestCase):
|
|
|
247
247
|
self.assertTrue(stream.nothing_more())
|
|
248
248
|
|
|
249
249
|
@unittest.skipUnless(TEST_DATABASE_URL, 'set QUERYAPIGATE_TEST_DATABASE_URL to run')
|
|
250
|
+
@unittest.skipIf(TEST_RUN_STORE_URL, "the metadata store's NOTIFY: with a run store, the events server tails it")
|
|
250
251
|
def test_postgres_notify_delivers_without_waiting_for_the_poll(self):
|
|
251
252
|
self.start_server(poll_interval=30) # only a NOTIFY can wake this one in time
|
|
252
253
|
stream = self.open('admin')
|
|
@@ -80,7 +80,8 @@ class MarkedEverywhereTests(unittest.TestCase):
|
|
|
80
80
|
('documentation/MCP.md', 'Sign-in for AI clients (OAuth)', '0.30'),
|
|
81
81
|
('documentation/MCP.md', 'Write tools: agents that write, approved by a person',
|
|
82
82
|
'0.30'),
|
|
83
|
-
('documentation/DEPLOYMENT.md', 'Kubernetes (Helm)', '0.30')
|
|
83
|
+
('documentation/DEPLOYMENT.md', 'Kubernetes (Helm)', '0.30'),
|
|
84
|
+
('documentation/DEPLOYMENT.md', 'A ClickHouse run store', '0.49')):
|
|
84
85
|
with self.subTest(heading=heading):
|
|
85
86
|
text = read(path)
|
|
86
87
|
match = re.search(rf'^#+ {re.escape(heading)}\n+(.+)', text, re.M)
|
|
@@ -239,6 +239,7 @@ class RetentionTests(HistoryTestCase):
|
|
|
239
239
|
self.assertEqual(self.stored(), 2)
|
|
240
240
|
|
|
241
241
|
@unittest.skipUnless(TEST_DATABASE_URL, 'several instances only share a PostgreSQL store')
|
|
242
|
+
@unittest.skipIf(TEST_RUN_STORE_URL, "a run store's TTL, not a sweep, keeps its retention")
|
|
242
243
|
def test_while_another_instance_sweeps_this_one_skips(self):
|
|
243
244
|
with mock.patch.dict(os.environ, {'QUERYAPIGATE_HISTORY_RETENTION_DAYS': '7'}):
|
|
244
245
|
now = datetime(2026, 3, 1, 12, 0, 0)
|
|
@@ -221,6 +221,17 @@ class ApplyTests(unittest.TestCase):
|
|
|
221
221
|
with mock.patch.dict(os.environ, {'QUERYAPIGATE_MASK_KEY': 'k2'}):
|
|
222
222
|
self.assertNotEqual(hashed('jane@example.com'), before)
|
|
223
223
|
|
|
224
|
+
def test_binary_values_hash_by_their_value_whatever_the_driver_hands_over(self):
|
|
225
|
+
# psycopg2 gives a memoryview for bytea, mysql-connector bytes for a BLOB: the same value, the same hash -
|
|
226
|
+
# and two memoryviews of one value too, which str() would have told apart by their addresses
|
|
227
|
+
def hashed(value):
|
|
228
|
+
return list(masking.apply(['v'], [(value,)], ['hash'])[1])[0][0]
|
|
229
|
+
value = b'\x48\x65\x6c\x6c\x6f'
|
|
230
|
+
self.assertEqual(hashed(memoryview(value)), hashed(value))
|
|
231
|
+
self.assertEqual(hashed(memoryview(value)), hashed(memoryview(bytes(value))))
|
|
232
|
+
self.assertEqual(hashed(bytearray(value)), hashed('Hello'))
|
|
233
|
+
self.assertNotEqual(hashed(memoryview(b'Hello')), hashed(memoryview(b'World')))
|
|
234
|
+
|
|
224
235
|
def test_mask(self):
|
|
225
236
|
for value, expected in (('jane@example.com', 'j***@example.com'), ('@x.org', '***@x.org'), ('Bo', 'B***'),
|
|
226
237
|
('', None), (12345, None), (None, None)):
|
|
@@ -7,7 +7,9 @@ What it can't do is start: a release refuses a store a newer one has upgraded (d
|
|
|
7
7
|
restarts mid-upgrade must come back on the new version.
|
|
8
8
|
|
|
9
9
|
This runs the real previous release, installed from PyPI - QUERYAPIGATE_TEST_OLD_BIN is its `queryapigate` - beside
|
|
10
|
-
the current code, on one store. Runs when that and QUERYAPIGATE_TEST_DATABASE_URL are set (CI's PostgreSQL job).
|
|
10
|
+
the current code, on one store. Runs when that and QUERYAPIGATE_TEST_DATABASE_URL are set (CI's PostgreSQL job). With
|
|
11
|
+
QUERYAPIGATE_TEST_RUN_STORE_URL set too (CI's run store job on PostgreSQL), both keep their runs in one ClickHouse run
|
|
12
|
+
store (ADR 0029): each node's runs land there, with ids of its own, whichever release it is.
|
|
11
13
|
|
|
12
14
|
The one exception: 0.38's store change (schema 17, workspaces - ADR 0023) isn't additive, so a 0.37 instance can't
|
|
13
15
|
write to a store 0.38 has upgraded, and that upgrade stops every instance first (CHANGELOG). Against a previous release
|
|
@@ -23,6 +25,7 @@ import uuid
|
|
|
23
25
|
from tests.test_multinode import ADMIN, call, eventually
|
|
24
26
|
|
|
25
27
|
DATABASE_URL = os.environ.get('QUERYAPIGATE_TEST_DATABASE_URL')
|
|
28
|
+
RUN_STORE_URL = os.environ.get('QUERYAPIGATE_TEST_RUN_STORE_URL', '').strip() or None
|
|
26
29
|
OLD_BIN = os.environ.get('QUERYAPIGATE_TEST_OLD_BIN')
|
|
27
30
|
NOT_ADDITIVE_UNTIL = (0, 38, 0) # the release whose store change wasn't additive: one before it can't serve beside it
|
|
28
31
|
|
|
@@ -58,8 +61,10 @@ class RollingUpgradeTests(unittest.TestCase):
|
|
|
58
61
|
separator = '&' if '?' in DATABASE_URL else '?'
|
|
59
62
|
self.env = {**os.environ, 'QUERYAPIGATE_API_KEY': ADMIN, 'QUERYAPIGATE_HISTORY_FLUSH_INTERVAL': '0.1',
|
|
60
63
|
'QUERYAPIGATE_DATABASE_URL': f'{DATABASE_URL}{separator}options=-csearch_path%3D{self.schema}'}
|
|
61
|
-
for name in ('QUERYAPIGATE_TEST_DATABASE_URL', 'QUERYAPIGATE_REDIS_URL'):
|
|
64
|
+
for name in ('QUERYAPIGATE_TEST_DATABASE_URL', 'QUERYAPIGATE_TEST_RUN_STORE_URL', 'QUERYAPIGATE_REDIS_URL'):
|
|
62
65
|
self.env.pop(name, None)
|
|
66
|
+
if RUN_STORE_URL: # a database of its own, named as the schema is
|
|
67
|
+
self.env['QUERYAPIGATE_RUN_STORE_URL'] = f'{RUN_STORE_URL.rstrip("/")}/{self.schema}'
|
|
63
68
|
self.processes = []
|
|
64
69
|
self.data = os.path.join(self.tmp.name, 'data.db')
|
|
65
70
|
import sqlite3
|
|
@@ -82,6 +87,9 @@ class RollingUpgradeTests(unittest.TestCase):
|
|
|
82
87
|
conn.autocommit = True
|
|
83
88
|
conn.cursor().execute(f'DROP SCHEMA IF EXISTS {self.schema} CASCADE')
|
|
84
89
|
conn.close()
|
|
90
|
+
if RUN_STORE_URL:
|
|
91
|
+
from queryapigate import runstore
|
|
92
|
+
runstore.execute(f'DROP DATABASE IF EXISTS {self.schema} SYNC', db_name='default')
|
|
85
93
|
self.tmp.cleanup()
|
|
86
94
|
|
|
87
95
|
def start(self, command, name):
|
|
@@ -146,6 +154,15 @@ class RollingUpgradeTests(unittest.TestCase):
|
|
|
146
154
|
self.assertEqual(status, 201, body)
|
|
147
155
|
self.assertEqual(call(old, 'GET', '/q/total', key=body['secret'])[1], [{'s': 30}])
|
|
148
156
|
self.assertTrue(eventually(lambda: len(call(new, 'GET', '/api/v1/history?query=total')[1]['items']) >= 3))
|
|
157
|
+
if RUN_STORE_URL:
|
|
158
|
+
from queryapigate import runstore
|
|
159
|
+
self.assertEqual(runstore.query('SELECT max(version) AS v FROM run_store_schema', db_name=self.schema),
|
|
160
|
+
[{'v': runstore._SCHEMA_VERSION}])
|
|
161
|
+
[runs] = runstore.query("SELECT count() AS n, uniqExact(id) AS ids, groupUniqArray(key_name) AS keys "
|
|
162
|
+
"FROM events WHERE query_name = 'total'", db_name=self.schema)
|
|
163
|
+
self.assertGreaterEqual(runs['n'], 3)
|
|
164
|
+
self.assertEqual(runs['ids'], runs['n']) # the two releases' minters never handed out one id twice
|
|
165
|
+
self.assertEqual(sorted(runs['keys']), ['admin', 'made-by-new', 'made-by-old'])
|
|
149
166
|
|
|
150
167
|
# A node that restarts mid-upgrade comes back on the old release only while the store is still at that
|
|
151
168
|
# release's schema; once the new code has moved it on, the old release refuses, saying why. (Right after a
|
|
@@ -9,10 +9,17 @@ A new release adds its fixture; this test then covers it with no change here.
|
|
|
9
9
|
Releases with the PostgreSQL store (0.12.0 on) also have <version>.postgres.tar.gz: the same estate in a PostgreSQL
|
|
10
10
|
schema, dumped as plain SQL. When the suite runs against PostgreSQL, each is restored into the test's own schema and
|
|
11
11
|
checked the same way - which is also a test of restoring a pg_dump (BACKLOG #71).
|
|
12
|
+
|
|
13
|
+
Releases with the ClickHouse run store (0.45.0 on, ADR 0029) also have <version>.runstore.tar.gz: the home, whose
|
|
14
|
+
metadata store says its runs are in ClickHouse, and runstore/, those tables and their rows. When the suite runs with
|
|
15
|
+
a run store, each is loaded into the test's own ClickHouse database and checked the same way, and its tables must be
|
|
16
|
+
at this code's run store schema: 0.45.0's are at version 1, so this tests that migration on a real release's tables.
|
|
12
17
|
"""
|
|
13
18
|
import glob
|
|
19
|
+
import hashlib
|
|
14
20
|
import json
|
|
15
21
|
import os
|
|
22
|
+
import re
|
|
16
23
|
import shutil
|
|
17
24
|
import sqlite3
|
|
18
25
|
import tarfile
|
|
@@ -20,7 +27,7 @@ import tempfile
|
|
|
20
27
|
import unittest
|
|
21
28
|
from unittest import mock
|
|
22
29
|
|
|
23
|
-
from queryapigate import create_app, db, events, history
|
|
30
|
+
from queryapigate import create_app, db, events, history, runstore
|
|
24
31
|
from tests import TEST_DATABASE_URL, TEST_RUN_STORE_URL
|
|
25
32
|
from tests.helpers import needs_sqlite_335
|
|
26
33
|
|
|
@@ -52,6 +59,34 @@ def restore_postgres(sql):
|
|
|
52
59
|
conn.close()
|
|
53
60
|
|
|
54
61
|
|
|
62
|
+
def restore_run_store(home):
|
|
63
|
+
"""Load a fixture's run store (runstore/ in its home) into this test's own ClickHouse database: the tables, their
|
|
64
|
+
rows, then the views that write to them, so no row is counted twice. TTL merges stay stopped on it: the fixture's
|
|
65
|
+
events are older than the event log keeps, and ClickHouse would drop them partway through a test. The metadata
|
|
66
|
+
store's marker names the ClickHouse the fixture was made on; it's pointed at this one, as the connection is
|
|
67
|
+
pointed at this home's data.db."""
|
|
68
|
+
name = runstore.database()
|
|
69
|
+
runstore.execute(f'DROP DATABASE IF EXISTS {name} SYNC', db_name='default')
|
|
70
|
+
runstore.execute(f'CREATE DATABASE {name}', db_name='default')
|
|
71
|
+
with open(os.path.join(home, 'runstore', 'tables.json')) as f:
|
|
72
|
+
tables = json.load(f)
|
|
73
|
+
for table in tables:
|
|
74
|
+
ddl = table['ddl'].replace('qag_fixture.', name + '.')
|
|
75
|
+
runstore.execute(re.sub(r'DEFINER = \S+', 'DEFINER = CURRENT_USER', ddl), db_name=name)
|
|
76
|
+
if table['engine'].endswith('MergeTree'):
|
|
77
|
+
runstore.execute(f'SYSTEM STOP TTL MERGES {name}.{table["name"]}', db_name=name)
|
|
78
|
+
rows = os.path.join(home, 'runstore', f'{table["name"]}.native')
|
|
79
|
+
if os.path.exists(rows):
|
|
80
|
+
with open(rows, 'rb') as f:
|
|
81
|
+
runstore._client().request({'database': name, 'query': f'INSERT INTO {table["name"]} FORMAT Native'},
|
|
82
|
+
f.read())
|
|
83
|
+
conn = sqlite3.connect(os.path.join(home, 'queryapigate.db'))
|
|
84
|
+
conn.execute('UPDATE run_store SET location = ?', (runstore.location(),))
|
|
85
|
+
conn.commit()
|
|
86
|
+
conn.close()
|
|
87
|
+
return name
|
|
88
|
+
|
|
89
|
+
|
|
55
90
|
class FixtureTestCase(unittest.TestCase):
|
|
56
91
|
def open_home(self, version, kind=''):
|
|
57
92
|
"""A fresh home holding that version's store, with the test's QUERYAPIGATE_HOME pointed at it."""
|
|
@@ -69,9 +104,22 @@ class FixtureTestCase(unittest.TestCase):
|
|
|
69
104
|
|
|
70
105
|
|
|
71
106
|
class StoreChecks(FixtureTestCase):
|
|
72
|
-
"""What every fixture must read back, whichever store it is in - run by the
|
|
107
|
+
"""What every fixture must read back, whichever store it is in - run by the three classes below."""
|
|
73
108
|
kind = ''
|
|
74
109
|
|
|
110
|
+
def restore(self, home):
|
|
111
|
+
"""Put a fixture's PostgreSQL dump or run store where this run's settings look for them."""
|
|
112
|
+
if self.kind == '.postgres':
|
|
113
|
+
with open(os.path.join(home, 'store.sql')) as f:
|
|
114
|
+
restore_postgres(f.read())
|
|
115
|
+
elif self.kind == '.runstore':
|
|
116
|
+
name = restore_run_store(home)
|
|
117
|
+
|
|
118
|
+
def drop():
|
|
119
|
+
history.flush() # this test's last runs first, or they'd be written to a database just dropped
|
|
120
|
+
runstore.execute(f'DROP DATABASE IF EXISTS {name} SYNC', db_name='default')
|
|
121
|
+
self.addCleanup(drop)
|
|
122
|
+
|
|
75
123
|
def check_every_release(self):
|
|
76
124
|
for version in fixture_versions(self.kind):
|
|
77
125
|
with self.subTest(version=version):
|
|
@@ -91,9 +139,7 @@ class StoreChecks(FixtureTestCase):
|
|
|
91
139
|
create_app()
|
|
92
140
|
return
|
|
93
141
|
self.assertEqual(expected['skipped'], [])
|
|
94
|
-
|
|
95
|
-
with open(os.path.join(home, 'store.sql')) as f:
|
|
96
|
-
restore_postgres(f.read())
|
|
142
|
+
self.restore(home)
|
|
97
143
|
client = create_app().test_client()
|
|
98
144
|
|
|
99
145
|
connection = self.get(client, '/api/v1/connections/films')
|
|
@@ -121,16 +167,36 @@ class StoreChecks(FixtureTestCase):
|
|
|
121
167
|
self.assertEqual([row['title'] for row in res.get_json()], ['Alpha', 'Gamma'])
|
|
122
168
|
|
|
123
169
|
self.assertTrue(self.get(client, '/api/v1/audit')['items'])
|
|
170
|
+
if self.kind == '.runstore':
|
|
171
|
+
self.check_run_store(client, expected)
|
|
172
|
+
|
|
173
|
+
def check_run_store(self, client, expected):
|
|
174
|
+
"""A run store's tables at this code's schema, whichever release made them, and nothing lost on the way."""
|
|
175
|
+
history.flush()
|
|
176
|
+
self.assertEqual(runstore.query('SELECT max(version) AS v FROM run_store_schema')[0]['v'],
|
|
177
|
+
runstore._SCHEMA_VERSION)
|
|
178
|
+
# The ad-hoc run's statement, in the hourly rollups as its hash whether the release kept the text (0.45) or not
|
|
179
|
+
sql = "SELECT title FROM film WHERE rating = 'R'"
|
|
180
|
+
self.assertEqual([row['statement'] for row in runstore.query(
|
|
181
|
+
'SELECT DISTINCT statement FROM run_hours WHERE query_name IS NULL')],
|
|
182
|
+
['sha:' + hashlib.sha256(sql.encode()).hexdigest()])
|
|
183
|
+
self.assertEqual(sum(row['runs'] for row in runstore.query('SELECT runs FROM run_hours')),
|
|
184
|
+
expected['run_store']['rows']['events'] + 1)
|
|
185
|
+
runs = self.get(client, '/api/v1/history')['items']
|
|
186
|
+
self.assertEqual(sorted(r['kind'] for r in runs), ['adhoc', 'saved', 'saved']) # and the run just made
|
|
187
|
+
self.assertEqual([r['sql'] for r in runs if r['kind'] == 'adhoc'], [sql])
|
|
124
188
|
|
|
125
189
|
def check_event_ids(self, version):
|
|
126
190
|
"""ADR 0002 §3, ADR 0020 §4: the run's history row id was its event id, and stays so. A client holding the id
|
|
127
191
|
before it resumes with the run; the next event's id is above every id the old release could have sent."""
|
|
128
192
|
home, _ = self.open_home(version, self.kind)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
193
|
+
self.restore(home)
|
|
194
|
+
if self.kind == '.runstore':
|
|
195
|
+
newest = runstore.query('SELECT max(id) AS id FROM events')[0]['id']
|
|
196
|
+
else:
|
|
197
|
+
newest = db.connection().execute('SELECT MAX(rowid) FROM execution_history').fetchone()[0]
|
|
198
|
+
# However old the run: eleven years, within ClickHouse's DateTime, which ends in 2106
|
|
199
|
+
with mock.patch.dict(os.environ, {'QUERYAPIGATE_EVENTS_RETENTION_HOURS': '100000'}):
|
|
134
200
|
client = create_app().test_client()
|
|
135
201
|
[(event_id, payload_json)] = events._replay_page(newest - 1, None, {})
|
|
136
202
|
payload = json.loads(payload_json)
|
|
@@ -140,7 +206,11 @@ class StoreChecks(FixtureTestCase):
|
|
|
140
206
|
self.assertEqual(res.status_code, 200, res.get_data(as_text=True))
|
|
141
207
|
self.assertEqual(client.get('/q/films_by_rating?rating=PG', headers=ADMIN).status_code, 200)
|
|
142
208
|
history.flush()
|
|
143
|
-
self.
|
|
209
|
+
if self.kind == '.runstore': # a run store's ids are minted from the clock: above the old one, not next to it
|
|
210
|
+
[(after, _)] = events._replay_page(newest, None, {}, upto=runstore.MAX_ID)
|
|
211
|
+
self.assertGreater(after, newest)
|
|
212
|
+
else:
|
|
213
|
+
self.assertEqual([row[0] for row in events._replay_page(newest, None, {})], [newest + 1])
|
|
144
214
|
|
|
145
215
|
|
|
146
216
|
@unittest.skipIf(TEST_DATABASE_URL, 'the SQLite stores and JSON homes')
|
|
@@ -175,6 +245,23 @@ class PostgresUpgradeTests(StoreChecks):
|
|
|
175
245
|
self.check_event_ids(version)
|
|
176
246
|
|
|
177
247
|
|
|
248
|
+
@unittest.skipIf(TEST_DATABASE_URL, "the fixtures' metadata stores are SQLite")
|
|
249
|
+
@unittest.skipUnless(TEST_RUN_STORE_URL, 'set QUERYAPIGATE_TEST_RUN_STORE_URL to run')
|
|
250
|
+
class RunStoreUpgradeTests(StoreChecks):
|
|
251
|
+
kind = '.runstore'
|
|
252
|
+
|
|
253
|
+
def test_there_is_a_fixture_for_every_release_with_the_run_store(self):
|
|
254
|
+
self.assertLessEqual({'0.45.0', '0.46.0', '0.47.0', '0.48.0'}, set(fixture_versions(self.kind)))
|
|
255
|
+
|
|
256
|
+
def test_every_release_store_starts_with_its_data_intact(self):
|
|
257
|
+
self.check_every_release()
|
|
258
|
+
|
|
259
|
+
def test_every_release_store_keeps_its_event_ids(self):
|
|
260
|
+
for version in fixture_versions(self.kind):
|
|
261
|
+
with self.subTest(version=version):
|
|
262
|
+
self.check_event_ids(version)
|
|
263
|
+
|
|
264
|
+
|
|
178
265
|
@unittest.skipIf(TEST_DATABASE_URL, 'the fixtures are SQLite stores')
|
|
179
266
|
class RefusalTests(FixtureTestCase):
|
|
180
267
|
"""A store this version can't run on stops startup with a message saying what is wrong and what to do - before
|
|
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
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/AgentsPage-ex_lhxAd.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/AlertsPage-CkFfLPBu.js
RENAMED
|
File without changes
|
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/ApiKeysPage-D3xeaQlS.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/CachingPage-8OhOUgxW.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/CodeBox-Cwh5KHLp.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/ExportsPage-BzDOR2fh.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/HelpPage-BV_YH8GV.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/HomePage-BShpQIil.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/KeyActivity-DuTyhSGT.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/Managed-CQ0VXNNZ.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/McpPage-JMpMLJIz.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/MetricsPage-BOUZqgao.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/QueryInfo-n4U1MkL1.js
RENAMED
|
File without changes
|
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/Results-6QW3JK2B.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/RolesPage-Dq83u-am.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/StatTile-Cft4kJPH.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/Time-DurHvFXj.js
RENAMED
|
File without changes
|
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/api-CgyYL3VS.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/client-ZHNiMG8H.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/forms-DR_JyNks.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/forms-DdFqq646.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/icons-CFXE8hAz.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/index-0v-rImZs.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/index-Cv64XKNs.css
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/maskRules-CvKQIPgN.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/reach-BAOLd75Y.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/shared-D8E1i8vU.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/state-O13cl7cO.js
RENAMED
|
File without changes
|
{queryapigate-0.48.0 → queryapigate-0.49.0}/queryapigate/console_dist/assets/useMutation-D4M562eQ.js
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
|