velocity-python 0.1.2__tar.gz → 0.1.4__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.2 → velocity_python-0.1.4}/PKG-INFO +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.4}/pyproject.toml +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/column.py +2 -1
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/decorators.py +6 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/engine.py +68 -5
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/table.py +24 -3
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/transaction.py +85 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/base/sql.py +10 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/sql.py +5 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/sql.py +5 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity_python.egg-info/SOURCES.txt +2 -0
- velocity_python-0.1.4/tests/test_observability.py +443 -0
- velocity_python-0.1.4/tests/test_security_hardening.py +256 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/LICENSE +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/README.md +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/setup.cfg +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/formbuilder/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/formbuilder/reshaper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/validators/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/app/validators/formbuilder_template.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/demo_profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_formbuilder_reshaper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_formbuilder_template_validator.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_payment_demo_profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_payment_profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_payment_router.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.4}/tests/test_where_clause_validation.py +0 -0
|
@@ -125,8 +125,9 @@ class Column:
|
|
|
125
125
|
Returns the MAX() of this column, or 0 if table/column is missing.
|
|
126
126
|
"""
|
|
127
127
|
try:
|
|
128
|
+
qcol = self.sql.quote_identifier(self.name)
|
|
128
129
|
sql, vals = self.sql.select(
|
|
129
|
-
columns=f"max({
|
|
130
|
+
columns=f"max({qcol})", table=self.table.name, where=where
|
|
130
131
|
)
|
|
131
132
|
return self.tx.execute(sql, vals).scalar()
|
|
132
133
|
except (exceptions.DbTableMissingError, exceptions.DbColumnMissingError):
|
|
@@ -121,6 +121,12 @@ def return_default(
|
|
|
121
121
|
e,
|
|
122
122
|
)
|
|
123
123
|
|
|
124
|
+
# R12 — Increment per-transaction counter for swallowed exceptions.
|
|
125
|
+
try:
|
|
126
|
+
self.tx._return_default_count = getattr(self.tx, "_return_default_count", 0) + 1
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
124
130
|
# Capture swallowed exceptions for upstream diagnostics.
|
|
125
131
|
# This decorator intentionally returns a default value instead of
|
|
126
132
|
# raising, but consumers (e.g. API handlers) may still want to
|
|
@@ -7,7 +7,7 @@ from contextlib import contextmanager
|
|
|
7
7
|
from functools import wraps
|
|
8
8
|
from velocity.db import exceptions
|
|
9
9
|
from velocity.db.core.transaction import Transaction
|
|
10
|
-
from velocity.db.utils import mask_config_for_display
|
|
10
|
+
from velocity.db.utils import mask_config_for_display, mask_sensitive_in_string
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
|
|
@@ -77,6 +77,7 @@ class ConnectionPool:
|
|
|
77
77
|
if self._closed:
|
|
78
78
|
raise exceptions.DbConnectionError("Connection pool is closed")
|
|
79
79
|
|
|
80
|
+
t0 = time.perf_counter()
|
|
80
81
|
self._available.acquire() # blocks when maxconn reached
|
|
81
82
|
|
|
82
83
|
with self._lock:
|
|
@@ -84,7 +85,11 @@ class ConnectionPool:
|
|
|
84
85
|
while self._pool:
|
|
85
86
|
conn = self._pool.pop()
|
|
86
87
|
if self._is_alive(conn):
|
|
87
|
-
|
|
88
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
89
|
+
logger.debug(
|
|
90
|
+
"Pool: reusing idle connection (pool_size=%d, wait=%.1f ms)",
|
|
91
|
+
len(self._pool), elapsed_ms,
|
|
92
|
+
)
|
|
88
93
|
return conn
|
|
89
94
|
# Dead connection — close it silently and create a fresh one.
|
|
90
95
|
logger.debug("Pool: discarding dead idle connection")
|
|
@@ -95,7 +100,11 @@ class ConnectionPool:
|
|
|
95
100
|
try:
|
|
96
101
|
conn = self._connect_fn()
|
|
97
102
|
self._total_created += 1
|
|
98
|
-
|
|
103
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
104
|
+
logger.debug(
|
|
105
|
+
"Pool: created new connection (total=%d, wait=%.1f ms)",
|
|
106
|
+
self._total_created, elapsed_ms,
|
|
107
|
+
)
|
|
99
108
|
return conn
|
|
100
109
|
except Exception:
|
|
101
110
|
self._available.release()
|
|
@@ -277,10 +286,12 @@ class Engine:
|
|
|
277
286
|
If pooling is enabled, borrows from the pool.
|
|
278
287
|
If the database is missing, tries to create it, then reconnect.
|
|
279
288
|
"""
|
|
289
|
+
t0 = time.perf_counter()
|
|
280
290
|
if self.__pool:
|
|
281
291
|
try:
|
|
282
292
|
conn = self.__pool.getconn()
|
|
283
|
-
|
|
293
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
294
|
+
logger.debug("Engine.connect: obtained pooled connection (%.1f ms)", elapsed_ms)
|
|
284
295
|
return conn
|
|
285
296
|
except exceptions.DbDatabaseMissingError:
|
|
286
297
|
self.create_database()
|
|
@@ -291,7 +302,8 @@ class Engine:
|
|
|
291
302
|
|
|
292
303
|
try:
|
|
293
304
|
conn = self._raw_connect()
|
|
294
|
-
|
|
305
|
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
|
306
|
+
logger.debug("Engine.connect: created direct connection (%.1f ms)", elapsed_ms)
|
|
295
307
|
except exceptions.DbDatabaseMissingError:
|
|
296
308
|
self.create_database()
|
|
297
309
|
conn = self._raw_connect()
|
|
@@ -496,6 +508,53 @@ class Engine:
|
|
|
496
508
|
# if depth == 0:
|
|
497
509
|
# delattr(_tx, "_exec_function_depth")
|
|
498
510
|
|
|
511
|
+
def perf_log(self, func):
|
|
512
|
+
"""
|
|
513
|
+
Decorator that logs wall time, query count, and total query time
|
|
514
|
+
for a ``@engine.transaction``-wrapped function.
|
|
515
|
+
|
|
516
|
+
Usage::
|
|
517
|
+
|
|
518
|
+
@engine.perf_log
|
|
519
|
+
@engine.transaction
|
|
520
|
+
def some_work(tx):
|
|
521
|
+
...
|
|
522
|
+
|
|
523
|
+
The ``tx`` parameter **must** be a keyword argument or the first
|
|
524
|
+
positional ``Transaction`` so we can read its counters.
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
@wraps(func)
|
|
528
|
+
def wrapper(*args, **kwds):
|
|
529
|
+
t0 = time.perf_counter()
|
|
530
|
+
result = func(*args, **kwds)
|
|
531
|
+
wall_ms = (time.perf_counter() - t0) * 1000
|
|
532
|
+
|
|
533
|
+
# Find the Transaction among the arguments.
|
|
534
|
+
tx = kwds.get("tx")
|
|
535
|
+
if tx is None:
|
|
536
|
+
for a in args:
|
|
537
|
+
if isinstance(a, Transaction):
|
|
538
|
+
tx = a
|
|
539
|
+
break
|
|
540
|
+
|
|
541
|
+
qcount = getattr(tx, "_query_count", 0) if tx else 0
|
|
542
|
+
qtime = getattr(tx, "_query_time_ms", 0.0) if tx else 0.0
|
|
543
|
+
|
|
544
|
+
logger.info(
|
|
545
|
+
"perf_log %s: wall=%.1f ms, queries=%d, query_time=%.1f ms",
|
|
546
|
+
getattr(func, "__qualname__", func.__name__),
|
|
547
|
+
wall_ms, qcount, qtime,
|
|
548
|
+
extra={
|
|
549
|
+
"wall_ms": round(wall_ms, 1),
|
|
550
|
+
"query_count": qcount,
|
|
551
|
+
"query_time_ms": round(qtime, 1),
|
|
552
|
+
},
|
|
553
|
+
)
|
|
554
|
+
return result
|
|
555
|
+
|
|
556
|
+
return wrapper
|
|
557
|
+
|
|
499
558
|
@property
|
|
500
559
|
def driver(self):
|
|
501
560
|
return self.__driver
|
|
@@ -714,6 +773,10 @@ class Engine:
|
|
|
714
773
|
if relevant_frames:
|
|
715
774
|
enhanced_message += "\n\nCall Context:\n" + "".join(relevant_frames)
|
|
716
775
|
|
|
776
|
+
# Mask any credentials that may have leaked into driver error messages
|
|
777
|
+
# (e.g. connection strings containing password=...).
|
|
778
|
+
enhanced_message = mask_sensitive_in_string(enhanced_message)
|
|
779
|
+
|
|
717
780
|
# Note: SQL formatting for logging is available via _format_sql_with_params,
|
|
718
781
|
# but we intentionally avoid eager logging here.
|
|
719
782
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
3
|
import warnings
|
|
3
4
|
import sqlparse
|
|
@@ -12,6 +13,8 @@ from velocity.db.core.decorators import (
|
|
|
12
13
|
reset_id_on_dup_key,
|
|
13
14
|
)
|
|
14
15
|
|
|
16
|
+
_ddl_logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
15
18
|
|
|
16
19
|
class Query:
|
|
17
20
|
"""
|
|
@@ -252,6 +255,7 @@ class Table:
|
|
|
252
255
|
)
|
|
253
256
|
if kwds.get("sql_only", False):
|
|
254
257
|
return sql, vals
|
|
258
|
+
_ddl_logger.warning("DDL CREATE INDEX on %s columns=%s unique=%s", self.name, columns, unique)
|
|
255
259
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
256
260
|
|
|
257
261
|
def create_indexes(self, indexes, **kwds):
|
|
@@ -317,6 +321,7 @@ class Table:
|
|
|
317
321
|
sql, vals = self.sql.drop_index(self.name, columns)
|
|
318
322
|
if kwds.get("sql_only", False):
|
|
319
323
|
return sql, vals
|
|
324
|
+
_ddl_logger.warning("DDL DROP INDEX on %s columns=%s", self.name, columns)
|
|
320
325
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
321
326
|
|
|
322
327
|
@return_default(None)
|
|
@@ -325,6 +330,7 @@ class Table:
|
|
|
325
330
|
Drops a column from this table.
|
|
326
331
|
"""
|
|
327
332
|
sql, vals = self.sql.drop_column(self.name, column)
|
|
333
|
+
_ddl_logger.warning("DDL DROP COLUMN %s on %s", column, self.name)
|
|
328
334
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
329
335
|
|
|
330
336
|
def create(self, columns=None, drop=False):
|
|
@@ -334,6 +340,7 @@ class Table:
|
|
|
334
340
|
"""
|
|
335
341
|
columns = columns or {}
|
|
336
342
|
sql, vals = self.sql.create_table(self.name, columns, drop)
|
|
343
|
+
_ddl_logger.warning("DDL CREATE TABLE %s columns=%s drop=%s", self.name, list(columns.keys()), drop)
|
|
337
344
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
338
345
|
|
|
339
346
|
def drop(self):
|
|
@@ -341,6 +348,7 @@ class Table:
|
|
|
341
348
|
Drops this table if it exists.
|
|
342
349
|
"""
|
|
343
350
|
sql, vals = self.sql.drop_table(self.name)
|
|
351
|
+
_ddl_logger.warning("DDL DROP TABLE %s", self.name)
|
|
344
352
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
345
353
|
|
|
346
354
|
def exists(self):
|
|
@@ -625,6 +633,7 @@ class Table:
|
|
|
625
633
|
)
|
|
626
634
|
if kwds.get("sql_only", False):
|
|
627
635
|
return sql, vals
|
|
636
|
+
_ddl_logger.warning("DDL CREATE FOREIGN KEY on %s columns=%s ref=%s(%s)", self.name, columns, key_to_table, key_to_columns)
|
|
628
637
|
return self.tx.execute(sql, vals, cursor=self.cursor())
|
|
629
638
|
|
|
630
639
|
def drop_foreign_key(self, columns, key_to_table, key_to_columns="sys_id", **kwds):
|
|
@@ -636,6 +645,7 @@ class Table:
|
|
|
636
645
|
)
|
|
637
646
|
if kwds.get("sql_only", False):
|
|
638
647
|
return sql, vals
|
|
648
|
+
_ddl_logger.warning("DDL DROP FOREIGN KEY on %s columns=%s ref=%s(%s)", self.name, columns, key_to_table, key_to_columns)
|
|
639
649
|
return self.tx.execute(sql, vals, cursor=self.cursor())
|
|
640
650
|
|
|
641
651
|
def rename(self, name, **kwds):
|
|
@@ -645,6 +655,7 @@ class Table:
|
|
|
645
655
|
sql, vals = self.sql.rename_table(self.name, name)
|
|
646
656
|
if kwds.get("sql_only", False):
|
|
647
657
|
return sql, vals
|
|
658
|
+
_ddl_logger.warning("DDL RENAME TABLE %s to %s", self.name, name)
|
|
648
659
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
649
660
|
self.name = name
|
|
650
661
|
|
|
@@ -784,6 +795,7 @@ class Table:
|
|
|
784
795
|
return statements[0]
|
|
785
796
|
return statements
|
|
786
797
|
|
|
798
|
+
_ddl_logger.warning("DDL ALTER TABLE %s columns=%s mode=%s", self.name, list(columns.keys()), mode)
|
|
787
799
|
for sql, vals in statements:
|
|
788
800
|
if not sql:
|
|
789
801
|
continue
|
|
@@ -808,6 +820,7 @@ class Table:
|
|
|
808
820
|
)
|
|
809
821
|
if kwds.get("sql_only", False):
|
|
810
822
|
return sql, vals
|
|
823
|
+
_ddl_logger.warning("DDL ALTER COLUMN TYPE on %s column=%s", self.name, column)
|
|
811
824
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
812
825
|
|
|
813
826
|
@create_missing
|
|
@@ -1069,9 +1082,10 @@ class Table:
|
|
|
1069
1082
|
"""
|
|
1070
1083
|
Returns the sum of the given column across rows matching `where`.
|
|
1071
1084
|
"""
|
|
1085
|
+
qcol = self.sql.quote_identifier(column)
|
|
1072
1086
|
sql, vals = self.sql.select(
|
|
1073
1087
|
self.tx,
|
|
1074
|
-
columns=f"coalesce(sum(coalesce({
|
|
1088
|
+
columns=f"coalesce(sum(coalesce({qcol},0)),0)",
|
|
1075
1089
|
table=self.name,
|
|
1076
1090
|
where=where,
|
|
1077
1091
|
)
|
|
@@ -1322,6 +1336,7 @@ class Table:
|
|
|
1322
1336
|
)
|
|
1323
1337
|
if kwds.get("sql_only", False):
|
|
1324
1338
|
return sql, vals
|
|
1339
|
+
_ddl_logger.warning("DDL CREATE VIEW %s temp=%s", name, temp)
|
|
1325
1340
|
return self.tx.execute(sql, vals)
|
|
1326
1341
|
|
|
1327
1342
|
def drop_view(self, name, silent=True, **kwds):
|
|
@@ -1331,6 +1346,7 @@ class Table:
|
|
|
1331
1346
|
sql, vals = self.sql.drop_view(name=name, silent=silent)
|
|
1332
1347
|
if kwds.get("sql_only", False):
|
|
1333
1348
|
return sql, vals
|
|
1349
|
+
_ddl_logger.warning("DDL DROP VIEW %s", name)
|
|
1334
1350
|
return self.tx.execute(sql, vals)
|
|
1335
1351
|
|
|
1336
1352
|
def alter_trigger(self, name="USER", state="ENABLE", **kwds):
|
|
@@ -1340,6 +1356,7 @@ class Table:
|
|
|
1340
1356
|
sql, vals = self.sql.alter_trigger(table=self.name, state=state, name=name)
|
|
1341
1357
|
if kwds.get("sql_only", False):
|
|
1342
1358
|
return sql, vals
|
|
1359
|
+
_ddl_logger.warning("DDL ALTER TRIGGER %s on %s state=%s", name, self.name, state)
|
|
1343
1360
|
return self.tx.execute(sql, vals)
|
|
1344
1361
|
|
|
1345
1362
|
def rename_column(self, orig, new, **kwds):
|
|
@@ -1349,6 +1366,7 @@ class Table:
|
|
|
1349
1366
|
sql, vals = self.sql.rename_column(table=self.name, orig=orig, new=new)
|
|
1350
1367
|
if kwds.get("sql_only", False):
|
|
1351
1368
|
return sql, vals
|
|
1369
|
+
_ddl_logger.warning("DDL RENAME COLUMN on %s %s -> %s", self.name, orig, new)
|
|
1352
1370
|
return self.tx.execute(sql, vals)
|
|
1353
1371
|
|
|
1354
1372
|
def set_sequence(self, next_value=1000, **kwds):
|
|
@@ -1358,6 +1376,7 @@ class Table:
|
|
|
1358
1376
|
sql, vals = self.sql.set_sequence(table=self.name, next_value=next_value)
|
|
1359
1377
|
if kwds.get("sql_only", False):
|
|
1360
1378
|
return sql, vals
|
|
1379
|
+
_ddl_logger.warning("DDL SET SEQUENCE on %s next_value=%s", self.name, next_value)
|
|
1361
1380
|
return self.tx.execute(sql, vals).scalar()
|
|
1362
1381
|
|
|
1363
1382
|
def get_sequence(self, **kwds):
|
|
@@ -1396,8 +1415,9 @@ class Table:
|
|
|
1396
1415
|
"""
|
|
1397
1416
|
Returns the MAX() of the specified column.
|
|
1398
1417
|
"""
|
|
1418
|
+
qcol = self.sql.quote_identifier(column)
|
|
1399
1419
|
sql, vals = self.sql.select(
|
|
1400
|
-
self.tx, columns=f"max({
|
|
1420
|
+
self.tx, columns=f"max({qcol})", table=self.name, where=where
|
|
1401
1421
|
)
|
|
1402
1422
|
if kwds.get("sql_only", False):
|
|
1403
1423
|
return sql, vals
|
|
@@ -1408,8 +1428,9 @@ class Table:
|
|
|
1408
1428
|
"""
|
|
1409
1429
|
Returns the MIN() of the specified column.
|
|
1410
1430
|
"""
|
|
1431
|
+
qcol = self.sql.quote_identifier(column)
|
|
1411
1432
|
sql, vals = self.sql.select(
|
|
1412
|
-
self.tx, columns=f"min({
|
|
1433
|
+
self.tx, columns=f"min({qcol})", table=self.name, where=where
|
|
1413
1434
|
)
|
|
1414
1435
|
if kwds.get("sql_only", False):
|
|
1415
1436
|
return sql, vals
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
3
|
+
import time as _time
|
|
2
4
|
import traceback
|
|
3
5
|
from collections import OrderedDict
|
|
4
6
|
|
|
@@ -14,9 +16,60 @@ from velocity.misc.db import randomword
|
|
|
14
16
|
|
|
15
17
|
debug = False
|
|
16
18
|
|
|
19
|
+
_logger = logging.getLogger("velocity.db.transaction")
|
|
20
|
+
|
|
17
21
|
# Default maximum number of cached query results per transaction.
|
|
18
22
|
_DEFAULT_QUERY_CACHE_SIZE = int(os.environ.get("VELOCITY_QUERY_CACHE_SIZE", "100"))
|
|
19
23
|
|
|
24
|
+
# Slow-query threshold in milliseconds (0 = disabled).
|
|
25
|
+
_SLOW_QUERY_MS = int(os.environ.get("VELOCITY_SLOW_QUERY_MS", "500"))
|
|
26
|
+
|
|
27
|
+
_SQL_OP_PREFIXES = {
|
|
28
|
+
"select": "SELECT",
|
|
29
|
+
"insert": "INSERT",
|
|
30
|
+
"update": "UPDATE",
|
|
31
|
+
"delete": "DELETE",
|
|
32
|
+
"create": "DDL",
|
|
33
|
+
"alter": "DDL",
|
|
34
|
+
"drop": "DDL",
|
|
35
|
+
"set": "SET",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _classify_sql(sql):
|
|
40
|
+
"""Return a short operation label (SELECT, INSERT, …) from a SQL string."""
|
|
41
|
+
if not sql:
|
|
42
|
+
return "OTHER"
|
|
43
|
+
first = sql.lstrip().split(None, 1)[0].lower() if sql.strip() else ""
|
|
44
|
+
return _SQL_OP_PREFIXES.get(first, "OTHER")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _extract_table_name(sql):
|
|
48
|
+
"""Best-effort extraction of the main table name from a SQL statement."""
|
|
49
|
+
if not sql:
|
|
50
|
+
return None
|
|
51
|
+
upper = sql.strip().upper()
|
|
52
|
+
lowered = sql.strip()
|
|
53
|
+
try:
|
|
54
|
+
if upper.startswith("SELECT"):
|
|
55
|
+
idx = upper.find(" FROM ")
|
|
56
|
+
if idx != -1:
|
|
57
|
+
rest = lowered[idx + 6:].strip()
|
|
58
|
+
return rest.split()[0].strip('"').strip("'") if rest else None
|
|
59
|
+
elif upper.startswith("INSERT"):
|
|
60
|
+
idx = upper.find(" INTO ")
|
|
61
|
+
if idx != -1:
|
|
62
|
+
rest = lowered[idx + 6:].strip()
|
|
63
|
+
return rest.split()[0].strip('"').strip("'") if rest else None
|
|
64
|
+
elif upper.startswith(("UPDATE", "DELETE FROM")):
|
|
65
|
+
parts = lowered.split()
|
|
66
|
+
if len(parts) >= 2:
|
|
67
|
+
token = parts[2] if upper.startswith("DELETE") and len(parts) > 2 else parts[1]
|
|
68
|
+
return token.strip('"').strip("'")
|
|
69
|
+
except (IndexError, ValueError):
|
|
70
|
+
pass
|
|
71
|
+
return None
|
|
72
|
+
|
|
20
73
|
|
|
21
74
|
class Transaction:
|
|
22
75
|
"""
|
|
@@ -32,6 +85,10 @@ class Transaction:
|
|
|
32
85
|
# R5 — Transaction-scoped query cache (opt-in via cache=True on select).
|
|
33
86
|
self.__query_cache: OrderedDict = OrderedDict()
|
|
34
87
|
self.__query_cache_max = _DEFAULT_QUERY_CACHE_SIZE
|
|
88
|
+
# R12 — Observability counters.
|
|
89
|
+
self._query_count = 0
|
|
90
|
+
self._query_time_ms = 0.0
|
|
91
|
+
self._return_default_count = 0
|
|
35
92
|
|
|
36
93
|
def __str__(self):
|
|
37
94
|
config = mask_config_for_display(self.engine.config)
|
|
@@ -119,6 +176,7 @@ class Transaction:
|
|
|
119
176
|
if prepare is None:
|
|
120
177
|
prepare = getattr(self.engine, "prepare_enabled", False)
|
|
121
178
|
|
|
179
|
+
t0 = _time.perf_counter()
|
|
122
180
|
try:
|
|
123
181
|
if parms:
|
|
124
182
|
cursor.execute(sql, parms, prepare=prepare)
|
|
@@ -136,6 +194,24 @@ class Transaction:
|
|
|
136
194
|
except Exception as e:
|
|
137
195
|
raise self.engine.process_error(e, sql, parms)
|
|
138
196
|
|
|
197
|
+
elapsed_ms = (_time.perf_counter() - t0) * 1000
|
|
198
|
+
self._query_count += 1
|
|
199
|
+
self._query_time_ms += elapsed_ms
|
|
200
|
+
|
|
201
|
+
# R12 — Slow query logging.
|
|
202
|
+
if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
|
|
203
|
+
op = _classify_sql(sql)
|
|
204
|
+
tbl = _extract_table_name(sql)
|
|
205
|
+
_logger.warning(
|
|
206
|
+
"Slow query (%s): %.1f ms table=%s",
|
|
207
|
+
op, elapsed_ms, tbl,
|
|
208
|
+
extra={
|
|
209
|
+
"query_duration_ms": round(elapsed_ms, 1),
|
|
210
|
+
"table_name": tbl,
|
|
211
|
+
"operation": op,
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
|
|
139
215
|
if single:
|
|
140
216
|
self.connection.autocommit = False
|
|
141
217
|
|
|
@@ -195,6 +271,15 @@ class Transaction:
|
|
|
195
271
|
if debug:
|
|
196
272
|
print(f"{id(self)} --- connection commit.")
|
|
197
273
|
self.connection.commit()
|
|
274
|
+
if self._query_count:
|
|
275
|
+
_logger.debug(
|
|
276
|
+
"Transaction commit: %d queries in %.1f ms",
|
|
277
|
+
self._query_count, self._query_time_ms,
|
|
278
|
+
extra={
|
|
279
|
+
"query_count": self._query_count,
|
|
280
|
+
"query_time_ms": round(self._query_time_ms, 1),
|
|
281
|
+
},
|
|
282
|
+
)
|
|
198
283
|
|
|
199
284
|
def rollback(self):
|
|
200
285
|
"""
|
|
@@ -38,6 +38,16 @@ class BaseSQLDialect(ABC):
|
|
|
38
38
|
DatabaseObjectExistsErrorCodes: List[str] = []
|
|
39
39
|
DataIntegrityErrorCodes: List[str] = []
|
|
40
40
|
|
|
41
|
+
@classmethod
|
|
42
|
+
def quote_identifier(cls, name: str) -> str:
|
|
43
|
+
"""Always-quote a single SQL identifier to prevent injection.
|
|
44
|
+
|
|
45
|
+
Uses standard SQL double-quoting. Dialect subclasses override for
|
|
46
|
+
MySQL (backticks) and SQL Server (brackets).
|
|
47
|
+
"""
|
|
48
|
+
escaped = name.replace('"', '""')
|
|
49
|
+
return f'"{escaped}"'
|
|
50
|
+
|
|
41
51
|
@classmethod
|
|
42
52
|
@abstractmethod
|
|
43
53
|
def get_error(cls, e: Exception) -> Optional[str]:
|
|
@@ -48,6 +48,11 @@ class SQL(BaseSQLDialect):
|
|
|
48
48
|
type_column_identifier = "DATA_TYPE"
|
|
49
49
|
is_nullable = "IS_NULLABLE"
|
|
50
50
|
|
|
51
|
+
@classmethod
|
|
52
|
+
def quote_identifier(cls, name: str) -> str:
|
|
53
|
+
escaped = name.replace('`', '``')
|
|
54
|
+
return f'`{escaped}`'
|
|
55
|
+
|
|
51
56
|
default_schema = ""
|
|
52
57
|
|
|
53
58
|
ApplicationErrorCodes = []
|
|
@@ -48,6 +48,11 @@ class SQL(BaseSQLDialect):
|
|
|
48
48
|
type_column_identifier = "DATA_TYPE"
|
|
49
49
|
is_nullable = "IS_NULLABLE"
|
|
50
50
|
|
|
51
|
+
@classmethod
|
|
52
|
+
def quote_identifier(cls, name: str) -> str:
|
|
53
|
+
escaped = name.replace(']', ']]')
|
|
54
|
+
return f'[{escaped}]'
|
|
55
|
+
|
|
51
56
|
default_schema = "dbo"
|
|
52
57
|
|
|
53
58
|
# SQL Server error numbers
|
|
@@ -159,6 +159,7 @@ tests/test_iconv_money_to_cents.py
|
|
|
159
159
|
tests/test_lambda_handler.py
|
|
160
160
|
tests/test_lambda_handler_auth.py
|
|
161
161
|
tests/test_mixins_import.py
|
|
162
|
+
tests/test_observability.py
|
|
162
163
|
tests/test_payment_braintree_adapter.py
|
|
163
164
|
tests/test_payment_demo_profiles.py
|
|
164
165
|
tests/test_payment_profiles.py
|
|
@@ -169,6 +170,7 @@ tests/test_psycopg3_upgrade.py
|
|
|
169
170
|
tests/test_query_cache.py
|
|
170
171
|
tests/test_row_batch_update.py
|
|
171
172
|
tests/test_row_cache_staleness.py
|
|
173
|
+
tests/test_security_hardening.py
|
|
172
174
|
tests/test_sqs_per_record_transactions.py
|
|
173
175
|
tests/test_sys_modified_count_postgres_demo.py
|
|
174
176
|
tests/test_table_alter.py
|