velocity-python 0.1.68__tar.gz → 0.1.70__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.
- {velocity_python-0.1.68 → velocity_python-0.1.70}/PKG-INFO +1 -1
- {velocity_python-0.1.68 → velocity_python-0.1.70}/pyproject.toml +1 -1
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/engine.py +26 -4
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/result.py +0 -10
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/row.py +35 -77
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/table.py +48 -37
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/transaction.py +36 -9
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/base/sql.py +56 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/mysql/sql.py +3 -2
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/postgres/sql.py +8 -3
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlite/sql.py +57 -14
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlite/types.py +10 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlserver/sql.py +2 -2
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_postgres.py +7 -24
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity_python.egg-info/SOURCES.txt +2 -1
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_json_columns.py +131 -21
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_row_batch_update.py +0 -2
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_row_cache_staleness.py +0 -2
- velocity_python-0.1.70/tests/test_sqlite_backend.py +182 -0
- velocity_python-0.1.70/tests/test_transaction_class_wrapping.py +90 -0
- velocity_python-0.1.68/tests/test_row_dirty_tracking.py +0 -193
- {velocity_python-0.1.68 → velocity_python-0.1.70}/LICENSE +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/README.md +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/setup.cfg +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/assets/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/assets/backfill.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/assets/indexing.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/assets/references.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/assets/service.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/assets/usage_index.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/dirty_pipeline.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/masquerade.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/s3.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/ssm_config.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/async_support.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/jsonproxy.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/migrations.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/conftest.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/pdf.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/authorizenet_mirror.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/braintree_mirror.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity/payment/stripe_mirror.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity_python.egg-info/entry_points.txt +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_asset_indexing.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_asset_references.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_assets_service.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_async_support.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_context_job_descriptions.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_db_credentials_ssm_cascade.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_dirty_pipeline_fast_path.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_get_cognito_user_provider.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_http_handler_rollback.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_identifier_injection_guard.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_jsonb_dict_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_lambda_handler_masquerade.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_masquerade_grant.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_n_plus_one.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_observability.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_payment_authorizenet_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_payment_braintree_mirror.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_pdf.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_restricted_direct_tables.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_retry_side_effect_guard.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_return_default_safety.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_schema_migrations.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_security_hardening.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_server_cursor.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_single_autocommit_safety.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_ssm_config.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_where_clause_validation.py +0 -0
- {velocity_python-0.1.68 → velocity_python-0.1.70}/tests/test_write_hook_create_flow.py +0 -0
|
@@ -548,10 +548,32 @@ class Engine:
|
|
|
548
548
|
if attr_name.startswith("__") and attr_name.endswith("__"):
|
|
549
549
|
continue
|
|
550
550
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
551
|
+
# Inspect the attribute *as defined* (without triggering the
|
|
552
|
+
# descriptor) so that @staticmethod / @classmethod keep their
|
|
553
|
+
# nature. Plain getattr() resolves a staticmethod to its bare
|
|
554
|
+
# function; re-setting that as a class attribute turns it into an
|
|
555
|
+
# instance method, silently injecting `self` as the first
|
|
556
|
+
# positional arg and breaking every static/class method on the
|
|
557
|
+
# wrapped class. Wrap the underlying function but re-apply the
|
|
558
|
+
# original descriptor so the call signature is preserved.
|
|
559
|
+
raw = inspect.getattr_static(func_or_cls, attr_name, None)
|
|
560
|
+
|
|
561
|
+
if isinstance(raw, staticmethod):
|
|
562
|
+
setattr(
|
|
563
|
+
NewCls,
|
|
564
|
+
attr_name,
|
|
565
|
+
staticmethod(self.transaction(raw.__func__)),
|
|
566
|
+
)
|
|
567
|
+
elif isinstance(raw, classmethod):
|
|
568
|
+
setattr(
|
|
569
|
+
NewCls,
|
|
570
|
+
attr_name,
|
|
571
|
+
classmethod(self.transaction(raw.__func__)),
|
|
572
|
+
)
|
|
573
|
+
else:
|
|
574
|
+
attr = getattr(func_or_cls, attr_name)
|
|
575
|
+
if callable(attr):
|
|
576
|
+
setattr(NewCls, attr_name, self.transaction(attr))
|
|
555
577
|
|
|
556
578
|
return NewCls
|
|
557
579
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import warnings
|
|
2
1
|
from velocity.misc.format import to_json
|
|
3
2
|
from velocity.db.core.row import Row
|
|
4
3
|
|
|
@@ -288,15 +287,6 @@ class Result:
|
|
|
288
287
|
self.transform = lambda row: list(zip(self.headers, row))
|
|
289
288
|
return self
|
|
290
289
|
|
|
291
|
-
def as_named_tuple(self):
|
|
292
|
-
"""Deprecated: use as_pairs() instead."""
|
|
293
|
-
warnings.warn(
|
|
294
|
-
"Result.as_named_tuple() is deprecated, use Result.as_pairs() instead.",
|
|
295
|
-
DeprecationWarning,
|
|
296
|
-
stacklevel=2,
|
|
297
|
-
)
|
|
298
|
-
return self.as_pairs()
|
|
299
|
-
|
|
300
290
|
def as_list(self):
|
|
301
291
|
"""
|
|
302
292
|
Transform each row into a list of values.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json as _json
|
|
1
2
|
import pprint
|
|
2
3
|
import time as _time
|
|
3
4
|
from collections.abc import MutableMapping
|
|
@@ -9,7 +10,7 @@ from velocity.db.core import jsonproxy
|
|
|
9
10
|
# intercepted by __getattr__ / __setattr__.
|
|
10
11
|
_INTERNAL_ATTRS = frozenset({
|
|
11
12
|
"table", "pk", "_cache", "_column_set", "_batching", "_pending",
|
|
12
|
-
"_cache_ttl", "_cache_time", "_no_cache",
|
|
13
|
+
"_cache_ttl", "_cache_time", "_no_cache",
|
|
13
14
|
})
|
|
14
15
|
|
|
15
16
|
|
|
@@ -25,7 +26,7 @@ class Row(MutableMapping):
|
|
|
25
26
|
write-through (immediate UPDATE) and also update the local cache.
|
|
26
27
|
"""
|
|
27
28
|
|
|
28
|
-
def __init__(self, table, key, lock=None, cache_ttl=None, no_cache=False
|
|
29
|
+
def __init__(self, table, key, lock=None, cache_ttl=None, no_cache=False):
|
|
29
30
|
if isinstance(table, str):
|
|
30
31
|
raise Exception("Table parameter must be a `table` instance.")
|
|
31
32
|
object.__setattr__(self, "table", table)
|
|
@@ -48,8 +49,6 @@ class Row(MutableMapping):
|
|
|
48
49
|
object.__setattr__(self, "_cache_ttl", cache_ttl)
|
|
49
50
|
object.__setattr__(self, "_cache_time", None)
|
|
50
51
|
object.__setattr__(self, "_no_cache", no_cache)
|
|
51
|
-
object.__setattr__(self, "_dirty_tracking", dirty_tracking)
|
|
52
|
-
object.__setattr__(self, "_dirty", {})
|
|
53
52
|
if lock:
|
|
54
53
|
self.lock()
|
|
55
54
|
|
|
@@ -80,8 +79,6 @@ class Row(MutableMapping):
|
|
|
80
79
|
object.__setattr__(row, "_cache_ttl", None)
|
|
81
80
|
object.__setattr__(row, "_cache_time", _time.monotonic())
|
|
82
81
|
object.__setattr__(row, "_no_cache", False)
|
|
83
|
-
object.__setattr__(row, "_dirty_tracking", False)
|
|
84
|
-
object.__setattr__(row, "_dirty", {})
|
|
85
82
|
return row
|
|
86
83
|
|
|
87
84
|
# ------------------------------------------------------------------
|
|
@@ -129,29 +126,46 @@ class Row(MutableMapping):
|
|
|
129
126
|
# MutableMapping protocol
|
|
130
127
|
# ------------------------------------------------------------------
|
|
131
128
|
|
|
129
|
+
def _bind_json(self, key, val):
|
|
130
|
+
"""
|
|
131
|
+
Wrap a JSON column value in a live proxy so in-place mutation writes
|
|
132
|
+
the whole value back. On backends whose driver returns JSON as text
|
|
133
|
+
(everything except PostgreSQL), decode the string first when *key* is a
|
|
134
|
+
known JSON column. Non-JSON values pass through unchanged.
|
|
135
|
+
"""
|
|
136
|
+
if isinstance(val, (jsonproxy.BoundDict, jsonproxy.BoundList)):
|
|
137
|
+
return val
|
|
138
|
+
if isinstance(val, str):
|
|
139
|
+
# Possible JSON text from a non-native backend. Decode only for
|
|
140
|
+
# columns the table reports as JSON; tables that don't implement
|
|
141
|
+
# json_columns() (mocks, external Table-likes) skip decoding.
|
|
142
|
+
json_cols = getattr(self.table, "json_columns", None)
|
|
143
|
+
if json_cols is None:
|
|
144
|
+
return val
|
|
145
|
+
try:
|
|
146
|
+
if key in json_cols():
|
|
147
|
+
val = _json.loads(val)
|
|
148
|
+
else:
|
|
149
|
+
return val
|
|
150
|
+
except (ValueError, TypeError):
|
|
151
|
+
return val
|
|
152
|
+
if isinstance(val, (dict, list)):
|
|
153
|
+
return jsonproxy.bind(
|
|
154
|
+
val, lambda plain, _k=key: self.__setitem__(_k, plain)
|
|
155
|
+
)
|
|
156
|
+
return val
|
|
157
|
+
|
|
132
158
|
def __getitem__(self, key):
|
|
133
159
|
if key in self.pk:
|
|
134
160
|
return self.pk[key]
|
|
135
161
|
cache = self._ensure_cache()
|
|
136
162
|
if key in cache:
|
|
137
|
-
val = cache[key]
|
|
138
|
-
|
|
139
|
-
# proxies so in-place mutation writes the whole value back.
|
|
140
|
-
if isinstance(val, (dict, list)) and not isinstance(
|
|
141
|
-
val, (jsonproxy.BoundDict, jsonproxy.BoundList)
|
|
142
|
-
):
|
|
143
|
-
val = jsonproxy.bind(
|
|
144
|
-
val, lambda plain, _k=key: self.__setitem__(_k, plain)
|
|
145
|
-
)
|
|
163
|
+
val = self._bind_json(key, cache[key])
|
|
164
|
+
if isinstance(val, (jsonproxy.BoundDict, jsonproxy.BoundList)):
|
|
146
165
|
cache[key] = val
|
|
147
166
|
return val
|
|
148
167
|
# Fall back to a direct DB fetch for columns not in the initial SELECT
|
|
149
|
-
|
|
150
|
-
if isinstance(val, (dict, list)):
|
|
151
|
-
val = jsonproxy.bind(
|
|
152
|
-
val, lambda plain, _k=key: self.__setitem__(_k, plain)
|
|
153
|
-
)
|
|
154
|
-
return val
|
|
168
|
+
return self._bind_json(key, self.table.get_value(key, self.pk))
|
|
155
169
|
|
|
156
170
|
def __setitem__(self, key, val):
|
|
157
171
|
if key in self.pk:
|
|
@@ -163,13 +177,6 @@ class Row(MutableMapping):
|
|
|
163
177
|
self._cache[key] = val
|
|
164
178
|
object.__setattr__(self, "_column_set", None)
|
|
165
179
|
return
|
|
166
|
-
if self._dirty_tracking:
|
|
167
|
-
self._dirty[key] = val
|
|
168
|
-
# Update local cache optimistically so reads see the dirty value
|
|
169
|
-
if self._cache is not None:
|
|
170
|
-
self._cache[key] = val
|
|
171
|
-
object.__setattr__(self, "_column_set", None)
|
|
172
|
-
return
|
|
173
180
|
self.table.update_or_insert({key: val}, pk=self.pk)
|
|
174
181
|
# Invalidate cache so trigger-computed columns are re-fetched
|
|
175
182
|
object.__setattr__(self, "_cache", None)
|
|
@@ -420,55 +427,6 @@ class Row(MutableMapping):
|
|
|
420
427
|
"""
|
|
421
428
|
return _BatchContext(self)
|
|
422
429
|
|
|
423
|
-
def save(self):
|
|
424
|
-
"""Flush accumulated dirty-tracking changes to the database.
|
|
425
|
-
|
|
426
|
-
When ``dirty_tracking=True``, assignments to the row accumulate
|
|
427
|
-
in memory instead of writing through immediately. Call
|
|
428
|
-
``.save()`` to flush them all in a single UPDATE::
|
|
429
|
-
|
|
430
|
-
row = Row(table, 1, dirty_tracking=True)
|
|
431
|
-
row["name"] = "John"
|
|
432
|
-
row["email"] = "john@example.com"
|
|
433
|
-
row.save() # Single UPDATE with both columns
|
|
434
|
-
|
|
435
|
-
Returns ``self`` for chaining.
|
|
436
|
-
|
|
437
|
-
Raises:
|
|
438
|
-
RuntimeError: If dirty tracking is not enabled on this row.
|
|
439
|
-
"""
|
|
440
|
-
if not self._dirty_tracking:
|
|
441
|
-
raise RuntimeError(
|
|
442
|
-
"save() requires dirty_tracking=True. "
|
|
443
|
-
"Use Row(table, key, dirty_tracking=True) or row.update({...})."
|
|
444
|
-
)
|
|
445
|
-
dirty = self._dirty
|
|
446
|
-
object.__setattr__(self, "_dirty", {})
|
|
447
|
-
if dirty:
|
|
448
|
-
self.table.update_or_insert(dirty, pk=self.pk)
|
|
449
|
-
# Invalidate cache so trigger-computed columns are re-fetched
|
|
450
|
-
object.__setattr__(self, "_cache", None)
|
|
451
|
-
object.__setattr__(self, "_column_set", None)
|
|
452
|
-
return self
|
|
453
|
-
|
|
454
|
-
@property
|
|
455
|
-
def is_dirty(self):
|
|
456
|
-
"""Return True if there are unsaved dirty-tracking changes."""
|
|
457
|
-
return bool(self._dirty)
|
|
458
|
-
|
|
459
|
-
def discard(self):
|
|
460
|
-
"""Discard accumulated dirty-tracking changes without writing to DB.
|
|
461
|
-
|
|
462
|
-
Also invalidates the cache so the next read re-fetches from the
|
|
463
|
-
database, undoing any optimistic cache updates.
|
|
464
|
-
|
|
465
|
-
Returns ``self`` for chaining.
|
|
466
|
-
"""
|
|
467
|
-
object.__setattr__(self, "_dirty", {})
|
|
468
|
-
object.__setattr__(self, "_cache", None)
|
|
469
|
-
object.__setattr__(self, "_column_set", None)
|
|
470
|
-
return self
|
|
471
|
-
|
|
472
430
|
def touch(self):
|
|
473
431
|
"""
|
|
474
432
|
Update sys_modified to current timestamp.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
|
-
import warnings
|
|
4
3
|
import sqlparse
|
|
5
4
|
from collections import defaultdict
|
|
6
5
|
from collections.abc import Iterable, Mapping, Sequence
|
|
@@ -106,6 +105,11 @@ def _is_nullable_flag(value):
|
|
|
106
105
|
return None
|
|
107
106
|
|
|
108
107
|
|
|
108
|
+
_COLUMN_SPEC_OPTION_KEYS = frozenset(
|
|
109
|
+
{"type", "value", "add_value", "nullable", "sql", "using"}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
109
113
|
def _parse_column_spec(spec, default_nullable):
|
|
110
114
|
"""Normalize user-provided column specification into a common structure."""
|
|
111
115
|
|
|
@@ -121,7 +125,11 @@ def _parse_column_spec(spec, default_nullable):
|
|
|
121
125
|
base, override_options = spec
|
|
122
126
|
|
|
123
127
|
options = {}
|
|
124
|
-
|
|
128
|
+
# A Mapping base is an options spec (e.g. {"type": "TEXT", "nullable": False})
|
|
129
|
+
# only when every key is a recognized option keyword. A plain JSON data
|
|
130
|
+
# value that happens to be a dict (e.g. {"k": 1}) must be kept as the value
|
|
131
|
+
# so its column types as JSON/JSONB — not silently mis-read as options.
|
|
132
|
+
if isinstance(base, Mapping) and base and set(base) <= _COLUMN_SPEC_OPTION_KEYS:
|
|
125
133
|
options.update(base)
|
|
126
134
|
base = options.get("type", options.get("value"))
|
|
127
135
|
|
|
@@ -590,7 +598,8 @@ class Table:
|
|
|
590
598
|
if kwds.get("sql_only", False):
|
|
591
599
|
return sql, vals
|
|
592
600
|
result = self.tx.execute(sql, vals, cursor=self.cursor())
|
|
593
|
-
|
|
601
|
+
name_idx = self.sql.columns_name_index
|
|
602
|
+
return [x[name_idx] for x in result.as_tuple()]
|
|
594
603
|
|
|
595
604
|
def columns(self):
|
|
596
605
|
"""
|
|
@@ -723,9 +732,12 @@ class Table:
|
|
|
723
732
|
Optionally drops any existing table first.
|
|
724
733
|
"""
|
|
725
734
|
columns = columns or {}
|
|
726
|
-
sql, vals = self.sql.create_table(self.name, columns, drop)
|
|
727
735
|
_ddl_logger.warning("DDL CREATE TABLE %s columns=%s drop=%s", self.name, list(columns.keys()), drop)
|
|
728
|
-
|
|
736
|
+
# Execute statement-by-statement: SQLite's driver rejects
|
|
737
|
+
# multi-statement execute() calls. Backends whose CREATE TABLE is a
|
|
738
|
+
# single (possibly multi-statement) string return a one-element list.
|
|
739
|
+
for stmt in self.sql.create_table_statements(self.name, columns, drop):
|
|
740
|
+
self.tx.execute(stmt, tuple(), cursor=self.cursor())
|
|
729
741
|
|
|
730
742
|
def _ensure_schema_unlocked(self, operation):
|
|
731
743
|
if self.tx.engine.schema_locked:
|
|
@@ -1921,8 +1933,16 @@ class Table:
|
|
|
1921
1933
|
null_statements.append((sql, vals))
|
|
1922
1934
|
|
|
1923
1935
|
if to_add:
|
|
1924
|
-
sql, vals
|
|
1925
|
-
|
|
1936
|
+
# alter_add_statements returns one (sql, vals) per column so SQLite
|
|
1937
|
+
# (single-statement execute) works; other dialects return a single
|
|
1938
|
+
# combined statement. Fall back to alter_add for SQL implementations
|
|
1939
|
+
# that predate the per-statement contract.
|
|
1940
|
+
add_stmts_fn = getattr(self.sql, "alter_add_statements", None)
|
|
1941
|
+
if add_stmts_fn is not None:
|
|
1942
|
+
statements.extend(add_stmts_fn(self.name, to_add, default_nullable))
|
|
1943
|
+
else:
|
|
1944
|
+
sql, vals = self.sql.alter_add(self.name, to_add, default_nullable)
|
|
1945
|
+
statements.append((sql, vals))
|
|
1926
1946
|
|
|
1927
1947
|
for column_name, spec in add_specs.items():
|
|
1928
1948
|
if (
|
|
@@ -1984,6 +2004,26 @@ class Table:
|
|
|
1984
2004
|
_ddl_logger.warning("DDL ALTER COLUMN TYPE on %s column=%s", self.name, column)
|
|
1985
2005
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
1986
2006
|
|
|
2007
|
+
def json_columns(self):
|
|
2008
|
+
"""
|
|
2009
|
+
Return the (cached) set of column names on this table that hold JSON
|
|
2010
|
+
values and need Row-layer decoding on read.
|
|
2011
|
+
|
|
2012
|
+
Empty on backends whose driver round-trips JSON natively (PostgreSQL),
|
|
2013
|
+
so the Row read path stays zero-cost there.
|
|
2014
|
+
"""
|
|
2015
|
+
if self.sql.json_round_trips_natively:
|
|
2016
|
+
return frozenset()
|
|
2017
|
+
cache = self.tx._json_columns_cache
|
|
2018
|
+
names = cache.get(self.name)
|
|
2019
|
+
if names is None:
|
|
2020
|
+
try:
|
|
2021
|
+
names = self.sql.json_column_names(self.tx, self.name)
|
|
2022
|
+
except Exception:
|
|
2023
|
+
names = frozenset()
|
|
2024
|
+
cache[self.name] = names
|
|
2025
|
+
return names
|
|
2026
|
+
|
|
1987
2027
|
def _adapt_data(self, data):
|
|
1988
2028
|
"""
|
|
1989
2029
|
Apply the dialect's data-value adaptation (e.g. dict/list -> JSONB on
|
|
@@ -2177,9 +2217,7 @@ class Table:
|
|
|
2177
2217
|
else:
|
|
2178
2218
|
exists_where = pk
|
|
2179
2219
|
|
|
2180
|
-
ins_builder = getattr(self.sql, "insert_if_not_exists", None)
|
|
2181
|
-
self.sql, "insnx", None
|
|
2182
|
-
)
|
|
2220
|
+
ins_builder = getattr(self.sql, "insert_if_not_exists", None)
|
|
2183
2221
|
if ins_builder is None:
|
|
2184
2222
|
raise NotImplementedError(
|
|
2185
2223
|
"Current SQL dialect does not support insert-if-not-exists operations."
|
|
@@ -2193,15 +2231,6 @@ class Table:
|
|
|
2193
2231
|
result = self.tx.execute(sql, vals, cursor=self.cursor())
|
|
2194
2232
|
return result.cursor.rowcount if result.cursor else 0
|
|
2195
2233
|
|
|
2196
|
-
@property
|
|
2197
|
-
def updins(self):
|
|
2198
|
-
warnings.warn(
|
|
2199
|
-
"Table.updins is deprecated, use Table.update_or_insert instead.",
|
|
2200
|
-
DeprecationWarning,
|
|
2201
|
-
stacklevel=2,
|
|
2202
|
-
)
|
|
2203
|
-
return self.update_or_insert
|
|
2204
|
-
|
|
2205
2234
|
@create_missing
|
|
2206
2235
|
def insert_if_not_exists(self, data, where=None, **kwds):
|
|
2207
2236
|
"""
|
|
@@ -2223,26 +2252,8 @@ class Table:
|
|
|
2223
2252
|
result = self.tx.execute(sql, vals, cursor=self.cursor())
|
|
2224
2253
|
return result.cursor.rowcount if result.cursor else 0
|
|
2225
2254
|
|
|
2226
|
-
@property
|
|
2227
|
-
def insnx(self):
|
|
2228
|
-
warnings.warn(
|
|
2229
|
-
"Table.insnx is deprecated, use Table.insert_if_not_exists instead.",
|
|
2230
|
-
DeprecationWarning,
|
|
2231
|
-
stacklevel=2,
|
|
2232
|
-
)
|
|
2233
|
-
return self.insert_if_not_exists
|
|
2234
|
-
|
|
2235
2255
|
upsert = merge
|
|
2236
2256
|
|
|
2237
|
-
@property
|
|
2238
|
-
def indate(self):
|
|
2239
|
-
warnings.warn(
|
|
2240
|
-
"Table.indate is deprecated, use Table.merge (or Table.upsert) instead.",
|
|
2241
|
-
DeprecationWarning,
|
|
2242
|
-
stacklevel=2,
|
|
2243
|
-
)
|
|
2244
|
-
return self.merge
|
|
2245
|
-
|
|
2246
2257
|
@return_default(0)
|
|
2247
2258
|
def count(self, where=None, **kwds):
|
|
2248
2259
|
"""
|
|
@@ -176,6 +176,10 @@ class Transaction:
|
|
|
176
176
|
# R14 — N+1 detection: per-table SELECT counts.
|
|
177
177
|
self._table_select_counts: dict[str, int] = {}
|
|
178
178
|
self._n1_warned: set[str] = set()
|
|
179
|
+
# JSON-column name cache (per table) for backends whose driver returns
|
|
180
|
+
# JSON columns as text and need Row-layer decoding. None = not yet
|
|
181
|
+
# looked up; invalidated by invalidate_cache().
|
|
182
|
+
self._json_columns_cache: dict[str, frozenset] = {}
|
|
179
183
|
# R21 — set once an irreversible external side effect (e.g. a payment
|
|
180
184
|
# charge/capture) has happened in this transaction. The engine's
|
|
181
185
|
# auto-retry envelope must NOT re-run a function after this, or a
|
|
@@ -268,15 +272,32 @@ class Transaction:
|
|
|
268
272
|
if prepare is None:
|
|
269
273
|
prepare = getattr(self.engine, "prepare_enabled", False)
|
|
270
274
|
|
|
275
|
+
# Only psycopg3 accepts the 'prepare' keyword. Once a driver rejects
|
|
276
|
+
# it we remember that on the engine so we don't raise+catch a TypeError
|
|
277
|
+
# on every subsequent statement (sqlite/mysql/sqlserver).
|
|
278
|
+
supports_prepare_kw = getattr(self.engine, "_supports_prepare_kw", True)
|
|
279
|
+
|
|
271
280
|
t0 = _time.perf_counter()
|
|
272
281
|
try:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
282
|
+
if supports_prepare_kw:
|
|
283
|
+
try:
|
|
284
|
+
if parms:
|
|
285
|
+
cursor.execute(sql, parms, prepare=prepare)
|
|
286
|
+
else:
|
|
287
|
+
cursor.execute(sql, prepare=prepare)
|
|
288
|
+
except TypeError:
|
|
289
|
+
# Driver doesn't support 'prepare' keyword (non-psycopg3).
|
|
290
|
+
self.engine._supports_prepare_kw = False
|
|
291
|
+
try:
|
|
292
|
+
if parms:
|
|
293
|
+
cursor.execute(sql, parms)
|
|
294
|
+
else:
|
|
295
|
+
cursor.execute(sql)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
raise self.engine.process_error(e, sql, parms)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
raise self.engine.process_error(e, sql, parms)
|
|
300
|
+
else:
|
|
280
301
|
try:
|
|
281
302
|
if parms:
|
|
282
303
|
cursor.execute(sql, parms)
|
|
@@ -284,8 +305,6 @@ class Transaction:
|
|
|
284
305
|
cursor.execute(sql)
|
|
285
306
|
except Exception as e:
|
|
286
307
|
raise self.engine.process_error(e, sql, parms)
|
|
287
|
-
except Exception as e:
|
|
288
|
-
raise self.engine.process_error(e, sql, parms)
|
|
289
308
|
|
|
290
309
|
elapsed_ms = (_time.perf_counter() - t0) * 1000
|
|
291
310
|
self._query_count += 1
|
|
@@ -481,10 +500,16 @@ class Transaction:
|
|
|
481
500
|
rolls back. Use this to serialize DDL or other operations across
|
|
482
501
|
concurrent Lambda containers.
|
|
483
502
|
|
|
503
|
+
No-op on non-PostgreSQL backends (advisory locks are a PostgreSQL
|
|
504
|
+
feature) — emitting the ``pg_advisory_xact_lock`` statement elsewhere
|
|
505
|
+
would raise a syntax error that callers must then swallow.
|
|
506
|
+
|
|
484
507
|
Args:
|
|
485
508
|
key: An integer lock key, or a string that will be hashed to
|
|
486
509
|
a 64-bit integer.
|
|
487
510
|
"""
|
|
511
|
+
if not getattr(self.engine.sql, "supports_advisory_lock", False):
|
|
512
|
+
return
|
|
488
513
|
if isinstance(key, str):
|
|
489
514
|
import hashlib
|
|
490
515
|
# Use first 8 bytes of SHA-256 as a signed 64-bit int.
|
|
@@ -531,7 +556,9 @@ class Transaction:
|
|
|
531
556
|
"""
|
|
532
557
|
if table_name is None:
|
|
533
558
|
self.__query_cache.clear()
|
|
559
|
+
self._json_columns_cache.clear()
|
|
534
560
|
return
|
|
561
|
+
self._json_columns_cache.pop(table_name, None)
|
|
535
562
|
# Simple substring match on the SQL text (first element of the key).
|
|
536
563
|
tbl_lower = table_name.lower()
|
|
537
564
|
to_remove = [
|
|
@@ -22,9 +22,19 @@ class BaseSQLDialect(ABC):
|
|
|
22
22
|
type_column_identifier: str = ""
|
|
23
23
|
is_nullable: str = ""
|
|
24
24
|
|
|
25
|
+
# Position of the column NAME within each row returned by columns().
|
|
26
|
+
# Most dialects put it first; SQLite's PRAGMA table_info has cid at 0 and
|
|
27
|
+
# the name at 1.
|
|
28
|
+
columns_name_index: int = 0
|
|
29
|
+
|
|
25
30
|
# Default schema name for this database
|
|
26
31
|
default_schema: str = ""
|
|
27
32
|
|
|
33
|
+
# PostgreSQL-style transaction advisory locks (pg_advisory_xact_lock).
|
|
34
|
+
# False elsewhere so Transaction.advisory_lock() is a clean no-op instead
|
|
35
|
+
# of emitting a statement the backend can't parse.
|
|
36
|
+
supports_advisory_lock: bool = False
|
|
37
|
+
|
|
28
38
|
# Error code classifications - must be set by subclasses
|
|
29
39
|
ApplicationErrorCodes: List[str] = []
|
|
30
40
|
DatabaseMissingErrorCodes: List[str] = []
|
|
@@ -54,6 +64,52 @@ class BaseSQLDialect(ABC):
|
|
|
54
64
|
return json.dumps(val)
|
|
55
65
|
return val
|
|
56
66
|
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# JSON-column read support
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# When True, the driver returns JSON columns already decoded into
|
|
71
|
+
# Python dict/list (PostgreSQL/psycopg). When False, JSON columns come
|
|
72
|
+
# back as text and the Row layer decodes them — see ``json_column_names``.
|
|
73
|
+
json_round_trips_natively: bool = False
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def json_column_names(cls, tx, table):
|
|
77
|
+
"""
|
|
78
|
+
Return the set of column names on *table* that hold JSON values, used
|
|
79
|
+
by the Row layer to decode JSON text on backends where the driver does
|
|
80
|
+
not round-trip JSON natively.
|
|
81
|
+
|
|
82
|
+
Default: empty set (no decoding). Backends that store JSON in a
|
|
83
|
+
distinguishable declared type override this.
|
|
84
|
+
"""
|
|
85
|
+
return frozenset()
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def create_table_statements(cls, name, columns=None, drop=False):
|
|
89
|
+
"""
|
|
90
|
+
Return CREATE TABLE as a list of individual SQL statements.
|
|
91
|
+
|
|
92
|
+
Most dialects build one (possibly multi-statement) string that the
|
|
93
|
+
driver executes in a single call; the default wraps that string as a
|
|
94
|
+
one-element list. SQLite overrides this because ``sqlite3`` rejects
|
|
95
|
+
multi-statement ``execute()`` calls, so each statement must run
|
|
96
|
+
separately.
|
|
97
|
+
"""
|
|
98
|
+
sql, _vals = cls.create_table(name, columns or {}, drop)
|
|
99
|
+
return [sql]
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def alter_add_statements(cls, table, columns, null_allowed=True):
|
|
103
|
+
"""
|
|
104
|
+
Return ADD COLUMN DDL as a list of ``(sql, params)`` statements.
|
|
105
|
+
|
|
106
|
+
Default wraps the dialect's single ``alter_add`` string as one element;
|
|
107
|
+
SQLite overrides this so each column is its own statement (its driver
|
|
108
|
+
rejects multi-statement ``execute()``).
|
|
109
|
+
"""
|
|
110
|
+
sql, vals = cls.alter_add(table, columns, null_allowed)
|
|
111
|
+
return [(sql, vals)]
|
|
112
|
+
|
|
57
113
|
@classmethod
|
|
58
114
|
def quote_identifier(cls, name: str) -> str:
|
|
59
115
|
"""Always-quote a single SQL identifier to prevent injection.
|
|
@@ -588,11 +588,12 @@ END;
|
|
|
588
588
|
|
|
589
589
|
@classmethod
|
|
590
590
|
def columns(cls, name):
|
|
591
|
-
|
|
591
|
+
# SHOW COLUMNS rows: (Field, Type, Null, Key, Default, Extra) — name at 0.
|
|
592
|
+
return f"SHOW COLUMNS FROM {quote(name)}", tuple()
|
|
592
593
|
|
|
593
594
|
@classmethod
|
|
594
595
|
def column_info(cls, table, name):
|
|
595
|
-
return f"SHOW COLUMNS FROM {quote(table)} LIKE '{name}'"
|
|
596
|
+
return f"SHOW COLUMNS FROM {quote(table)} LIKE '{name}'", tuple()
|
|
596
597
|
|
|
597
598
|
@classmethod
|
|
598
599
|
def drop_column(cls, table, name, cascade=True):
|
|
@@ -37,6 +37,13 @@ class SQL(BaseSQLDialect):
|
|
|
37
37
|
|
|
38
38
|
default_schema = "public"
|
|
39
39
|
|
|
40
|
+
# psycopg decodes JSONB/JSON into dict/list automatically — no Row-layer
|
|
41
|
+
# decode needed (and the Row layer skips all JSON metadata lookups).
|
|
42
|
+
json_round_trips_natively = True
|
|
43
|
+
|
|
44
|
+
# PostgreSQL supports pg_advisory_xact_lock (see Transaction.advisory_lock).
|
|
45
|
+
supports_advisory_lock = True
|
|
46
|
+
|
|
40
47
|
ApplicationErrorCodes = [
|
|
41
48
|
"22P02",
|
|
42
49
|
"42883",
|
|
@@ -907,7 +914,7 @@ class SQL(BaseSQLDialect):
|
|
|
907
914
|
return sql, args_list, values_template
|
|
908
915
|
|
|
909
916
|
@classmethod
|
|
910
|
-
def
|
|
917
|
+
def insert_if_not_exists(cls, tx, table, data, where=None):
|
|
911
918
|
"""Insert only when the supplied predicate finds no existing row."""
|
|
912
919
|
if not table:
|
|
913
920
|
raise ValueError("Table name is required.")
|
|
@@ -1001,8 +1008,6 @@ class SQL(BaseSQLDialect):
|
|
|
1001
1008
|
)
|
|
1002
1009
|
return final_sql, tuple(vals)
|
|
1003
1010
|
|
|
1004
|
-
insert_if_not_exists = insnx
|
|
1005
|
-
|
|
1006
1011
|
@classmethod
|
|
1007
1012
|
def version(cls):
|
|
1008
1013
|
return "select version()", tuple()
|