velocity-python 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {velocity_python-0.1.2 → velocity_python-0.1.3}/PKG-INFO +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.3}/pyproject.toml +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/__init__.py +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/column.py +2 -1
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/engine.py +5 -1
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/table.py +24 -3
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/base/sql.py +10 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/sql.py +5 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/sql.py +5 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity_python.egg-info/SOURCES.txt +1 -0
- velocity_python-0.1.3/tests/test_security_hardening.py +256 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/LICENSE +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/README.md +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/setup.cfg +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/formbuilder/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/formbuilder/reshaper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/validators/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/validators/formbuilder_template.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/amplify_build.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/context_factory.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/perf.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/core/view.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/sql.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/tests/test_view_helper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/logging.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/__init__.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/authorizenet_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/base_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/braintree_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/charge_rules.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/demo_profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/router.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/payment/stripe_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_amplify_build.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_batch_operations.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_concurrency_safety.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_connection_pool.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_connection_resilience.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_decorators.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_formbuilder_reshaper.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_formbuilder_template_validator.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_iconv_money_to_cents.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_lambda_handler.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_lambda_handler_auth.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_mixins_import.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_payment_braintree_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_payment_demo_profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_payment_profiles.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_payment_router.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_payment_stripe_adapter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_prepared_statements.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_psycopg3_upgrade.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_query_cache.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_row_batch_update.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_row_cache_staleness.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_sqs_per_record_transactions.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_sys_modified_count_postgres_demo.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/tests/test_table_alter.py +0 -0
- {velocity_python-0.1.2 → velocity_python-0.1.3}/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):
|
|
@@ -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
|
|
|
@@ -714,6 +714,10 @@ class Engine:
|
|
|
714
714
|
if relevant_frames:
|
|
715
715
|
enhanced_message += "\n\nCall Context:\n" + "".join(relevant_frames)
|
|
716
716
|
|
|
717
|
+
# Mask any credentials that may have leaked into driver error messages
|
|
718
|
+
# (e.g. connection strings containing password=...).
|
|
719
|
+
enhanced_message = mask_sensitive_in_string(enhanced_message)
|
|
720
|
+
|
|
717
721
|
# Note: SQL formatting for logging is available via _format_sql_with_params,
|
|
718
722
|
# but we intentionally avoid eager logging here.
|
|
719
723
|
|
|
@@ -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
|
|
@@ -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
|
|
@@ -169,6 +169,7 @@ tests/test_psycopg3_upgrade.py
|
|
|
169
169
|
tests/test_query_cache.py
|
|
170
170
|
tests/test_row_batch_update.py
|
|
171
171
|
tests/test_row_cache_staleness.py
|
|
172
|
+
tests/test_security_hardening.py
|
|
172
173
|
tests/test_sqs_per_record_transactions.py
|
|
173
174
|
tests/test_sys_modified_count_postgres_demo.py
|
|
174
175
|
tests/test_table_alter.py
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for R10 — Security Hardening: column quoting, DDL logging, credential masking.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import pytest
|
|
8
|
+
from unittest.mock import MagicMock, patch, call
|
|
9
|
+
|
|
10
|
+
from velocity.db.servers.base.sql import BaseSQLDialect
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
14
|
+
# quote_identifier tests
|
|
15
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestQuoteIdentifier:
|
|
19
|
+
"""Verify quote_identifier on each dialect."""
|
|
20
|
+
|
|
21
|
+
def test_base_dialect_double_quotes(self):
|
|
22
|
+
assert BaseSQLDialect.quote_identifier("amount") == '"amount"'
|
|
23
|
+
|
|
24
|
+
def test_base_dialect_escapes_internal_quotes(self):
|
|
25
|
+
assert BaseSQLDialect.quote_identifier('col"name') == '"col""name"'
|
|
26
|
+
|
|
27
|
+
def test_base_dialect_prevents_injection(self):
|
|
28
|
+
malicious = 'sys_id); DROP TABLE users; --'
|
|
29
|
+
quoted = BaseSQLDialect.quote_identifier(malicious)
|
|
30
|
+
# Must be a single quoted identifier — entire payload wrapped in quotes
|
|
31
|
+
assert quoted.startswith('"')
|
|
32
|
+
assert quoted.endswith('"')
|
|
33
|
+
assert quoted == '"sys_id); DROP TABLE users; --"'
|
|
34
|
+
# When embedded in SQL like max("sys_id); DROP TABLE users; --"),
|
|
35
|
+
# the DB treats the whole thing as one (non-existent) column name
|
|
36
|
+
|
|
37
|
+
def test_postgres_inherits_default(self):
|
|
38
|
+
from velocity.db.servers.postgres.sql import SQL as PgSQL
|
|
39
|
+
assert PgSQL.quote_identifier("balance") == '"balance"'
|
|
40
|
+
|
|
41
|
+
def test_mysql_uses_backticks(self):
|
|
42
|
+
from velocity.db.servers.mysql.sql import SQL as MySqlSQL
|
|
43
|
+
assert MySqlSQL.quote_identifier("balance") == "`balance`"
|
|
44
|
+
|
|
45
|
+
def test_mysql_escapes_backtick(self):
|
|
46
|
+
from velocity.db.servers.mysql.sql import SQL as MySqlSQL
|
|
47
|
+
assert MySqlSQL.quote_identifier("col`name") == "`col``name`"
|
|
48
|
+
|
|
49
|
+
def test_sqlserver_uses_brackets(self):
|
|
50
|
+
from velocity.db.servers.sqlserver.sql import SQL as SsSQL
|
|
51
|
+
assert SsSQL.quote_identifier("balance") == "[balance]"
|
|
52
|
+
|
|
53
|
+
def test_sqlserver_escapes_bracket(self):
|
|
54
|
+
from velocity.db.servers.sqlserver.sql import SQL as SsSQL
|
|
55
|
+
assert SsSQL.quote_identifier("col]name") == "[col]]name]"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
59
|
+
# Aggregate column quoting integration tests
|
|
60
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _make_table():
|
|
64
|
+
"""Create a Table with a mocked transaction and Postgres SQL dialect."""
|
|
65
|
+
from velocity.db.core.table import Table
|
|
66
|
+
from velocity.db.servers.postgres.sql import SQL as PgSQL
|
|
67
|
+
|
|
68
|
+
tx = MagicMock()
|
|
69
|
+
tx.engine.sql = PgSQL
|
|
70
|
+
table = Table(tx, "accounts")
|
|
71
|
+
table._cursor_obj = MagicMock()
|
|
72
|
+
return table
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TestAggregateQuoting:
|
|
76
|
+
"""Verify aggregate SQL uses quoted column names."""
|
|
77
|
+
|
|
78
|
+
def test_sum_quotes_column(self):
|
|
79
|
+
table = _make_table()
|
|
80
|
+
sql, _ = table.sum("balance", sql_only=True)
|
|
81
|
+
# Column should be double-quoted inside the aggregate
|
|
82
|
+
assert '"balance"' in sql
|
|
83
|
+
assert 'sum(coalesce("balance"' in sql
|
|
84
|
+
|
|
85
|
+
def test_max_quotes_column(self):
|
|
86
|
+
table = _make_table()
|
|
87
|
+
sql, _ = table.max("amount", sql_only=True)
|
|
88
|
+
assert 'max("amount")' in sql
|
|
89
|
+
|
|
90
|
+
def test_min_quotes_column(self):
|
|
91
|
+
table = _make_table()
|
|
92
|
+
sql, _ = table.min("amount", sql_only=True)
|
|
93
|
+
assert 'min("amount")' in sql
|
|
94
|
+
|
|
95
|
+
def test_sum_injection_prevented(self):
|
|
96
|
+
table = _make_table()
|
|
97
|
+
malicious = "balance); DROP TABLE users; --"
|
|
98
|
+
sql, _ = table.sum(malicious, sql_only=True)
|
|
99
|
+
# The malicious payload should be inside quotes, not executable
|
|
100
|
+
assert "DROP TABLE" not in sql.split('"')[-1] # not outside quotes
|
|
101
|
+
|
|
102
|
+
def test_max_injection_prevented(self):
|
|
103
|
+
table = _make_table()
|
|
104
|
+
malicious = "x); DELETE FROM accounts; --"
|
|
105
|
+
sql, _ = table.max(malicious, sql_only=True)
|
|
106
|
+
assert "DELETE" not in sql.replace('"', "").split(")")[0]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
110
|
+
# DDL audit logging tests
|
|
111
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestDDLAuditLogging:
|
|
115
|
+
"""Verify DDL operations emit WARNING log messages."""
|
|
116
|
+
|
|
117
|
+
def _make_table_with_sql(self):
|
|
118
|
+
from velocity.db.core.table import Table
|
|
119
|
+
tx = MagicMock()
|
|
120
|
+
sql_dialect = MagicMock()
|
|
121
|
+
sql_dialect.create_table.return_value = ("CREATE TABLE ...", [])
|
|
122
|
+
sql_dialect.drop_table.return_value = ("DROP TABLE ...", [])
|
|
123
|
+
sql_dialect.create_index.return_value = ("CREATE INDEX ...", [])
|
|
124
|
+
sql_dialect.drop_index.return_value = ("DROP INDEX ...", [])
|
|
125
|
+
sql_dialect.drop_column.return_value = ("ALTER TABLE DROP COLUMN ...", [])
|
|
126
|
+
sql_dialect.create_foreign_key.return_value = ("ALTER TABLE ADD FK ...", [])
|
|
127
|
+
sql_dialect.rename_table.return_value = ("ALTER TABLE RENAME ...", [])
|
|
128
|
+
sql_dialect.rename_column.return_value = ("ALTER TABLE RENAME COLUMN ...", [])
|
|
129
|
+
sql_dialect.alter_trigger.return_value = ("ALTER TABLE TRIGGER ...", [])
|
|
130
|
+
sql_dialect.create_view.return_value = ("CREATE VIEW ...", [])
|
|
131
|
+
sql_dialect.drop_view.return_value = ("DROP VIEW ...", [])
|
|
132
|
+
sql_dialect.set_sequence.return_value = ("ALTER SEQUENCE ...", [])
|
|
133
|
+
sql_dialect.alter_column_by_type.return_value = ("ALTER COLUMN ...", [])
|
|
134
|
+
tx.engine.sql = sql_dialect
|
|
135
|
+
tx.execute.return_value = MagicMock(cursor=MagicMock(), scalar=MagicMock(return_value=0))
|
|
136
|
+
table = Table(tx, "test_table")
|
|
137
|
+
table._cursor_obj = MagicMock()
|
|
138
|
+
return table
|
|
139
|
+
|
|
140
|
+
def test_create_table_logs_warning(self, caplog):
|
|
141
|
+
table = self._make_table_with_sql()
|
|
142
|
+
with caplog.at_level(logging.WARNING, logger="velocity.db.core.table"):
|
|
143
|
+
table.create(columns={"name": "text", "age": "int"})
|
|
144
|
+
assert any("DDL CREATE TABLE test_table" in r.message for r in caplog.records)
|
|
145
|
+
assert any("name" in r.message and "age" in r.message for r in caplog.records)
|
|
146
|
+
|
|
147
|
+
def test_drop_table_logs_warning(self, caplog):
|
|
148
|
+
table = self._make_table_with_sql()
|
|
149
|
+
with caplog.at_level(logging.WARNING, logger="velocity.db.core.table"):
|
|
150
|
+
table.drop()
|
|
151
|
+
assert any("DDL DROP TABLE test_table" in r.message for r in caplog.records)
|
|
152
|
+
|
|
153
|
+
def test_create_index_logs_warning(self, caplog):
|
|
154
|
+
table = self._make_table_with_sql()
|
|
155
|
+
with caplog.at_level(logging.WARNING, logger="velocity.db.core.table"):
|
|
156
|
+
table.create_index(columns=["email"], unique=True)
|
|
157
|
+
assert any("DDL CREATE INDEX" in r.message and "unique=True" in r.message for r in caplog.records)
|
|
158
|
+
|
|
159
|
+
def test_drop_column_logs_warning(self, caplog):
|
|
160
|
+
table = self._make_table_with_sql()
|
|
161
|
+
with caplog.at_level(logging.WARNING, logger="velocity.db.core.table"):
|
|
162
|
+
table.drop_column("old_col")
|
|
163
|
+
assert any("DDL DROP COLUMN old_col" in r.message for r in caplog.records)
|
|
164
|
+
|
|
165
|
+
def test_rename_column_logs_warning(self, caplog):
|
|
166
|
+
table = self._make_table_with_sql()
|
|
167
|
+
with caplog.at_level(logging.WARNING, logger="velocity.db.core.table"):
|
|
168
|
+
table.rename_column("old", "new")
|
|
169
|
+
assert any("DDL RENAME COLUMN" in r.message and "old -> new" in r.message for r in caplog.records)
|
|
170
|
+
|
|
171
|
+
def test_sql_only_does_not_log(self, caplog):
|
|
172
|
+
"""sql_only=True should return SQL without logging."""
|
|
173
|
+
table = self._make_table_with_sql()
|
|
174
|
+
with caplog.at_level(logging.WARNING, logger="velocity.db.core.table"):
|
|
175
|
+
result = table.create_index(columns=["email"], sql_only=True)
|
|
176
|
+
assert not any("DDL" in r.message for r in caplog.records)
|
|
177
|
+
assert result is not None # Should return the SQL tuple
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
181
|
+
# Credential masking in error messages
|
|
182
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TestCredentialMasking:
|
|
186
|
+
"""Verify credentials are masked in error messages raised by process_error."""
|
|
187
|
+
|
|
188
|
+
def test_password_masked_in_connection_error(self):
|
|
189
|
+
from velocity.db.core.engine import Engine
|
|
190
|
+
from velocity.db import exceptions
|
|
191
|
+
|
|
192
|
+
engine = MagicMock(spec=Engine)
|
|
193
|
+
engine.sql = MagicMock()
|
|
194
|
+
engine.sql.get_error.return_value = (None, "connection refused")
|
|
195
|
+
engine.sql.ConnectionErrorCodes = []
|
|
196
|
+
engine.sql.ApplicationErrorCodes = []
|
|
197
|
+
engine.sql.ColumnMissingErrorCodes = []
|
|
198
|
+
engine.sql.TableMissingErrorCodes = []
|
|
199
|
+
engine.sql.DatabaseMissingErrorCodes = []
|
|
200
|
+
engine.sql.ForeignKeyMissingErrorCodes = []
|
|
201
|
+
engine.sql.TruncationErrorCodes = []
|
|
202
|
+
engine.sql.DataIntegrityErrorCodes = []
|
|
203
|
+
engine.sql.DuplicateKeyErrorCodes = []
|
|
204
|
+
engine.sql.DatabaseObjectExistsErrorCodes = []
|
|
205
|
+
engine.sql.LockTimeoutErrorCodes = []
|
|
206
|
+
engine.sql.RetryTransactionCodes = []
|
|
207
|
+
engine.sql.is_connection_error_message.return_value = False
|
|
208
|
+
|
|
209
|
+
# Simulate a driver error that includes a password in its message
|
|
210
|
+
exc = Exception("connection to host=db.example.com password=s3cret123 failed")
|
|
211
|
+
|
|
212
|
+
with pytest.raises(Exception) as exc_info:
|
|
213
|
+
Engine.process_error(engine, exc)
|
|
214
|
+
|
|
215
|
+
msg = str(exc_info.value)
|
|
216
|
+
assert "s3cret123" not in msg
|
|
217
|
+
assert "*****" in msg
|
|
218
|
+
|
|
219
|
+
def test_url_credentials_masked(self):
|
|
220
|
+
from velocity.db.core.engine import Engine
|
|
221
|
+
|
|
222
|
+
engine = MagicMock(spec=Engine)
|
|
223
|
+
engine.sql = MagicMock()
|
|
224
|
+
engine.sql.get_error.return_value = (None, "fail")
|
|
225
|
+
engine.sql.ConnectionErrorCodes = []
|
|
226
|
+
engine.sql.ApplicationErrorCodes = []
|
|
227
|
+
engine.sql.ColumnMissingErrorCodes = []
|
|
228
|
+
engine.sql.TableMissingErrorCodes = []
|
|
229
|
+
engine.sql.DatabaseMissingErrorCodes = []
|
|
230
|
+
engine.sql.ForeignKeyMissingErrorCodes = []
|
|
231
|
+
engine.sql.TruncationErrorCodes = []
|
|
232
|
+
engine.sql.DataIntegrityErrorCodes = []
|
|
233
|
+
engine.sql.DuplicateKeyErrorCodes = []
|
|
234
|
+
engine.sql.DatabaseObjectExistsErrorCodes = []
|
|
235
|
+
engine.sql.LockTimeoutErrorCodes = []
|
|
236
|
+
engine.sql.RetryTransactionCodes = []
|
|
237
|
+
engine.sql.is_connection_error_message.return_value = False
|
|
238
|
+
|
|
239
|
+
exc = Exception("could not connect to postgresql://admin:supersecret@db:5432/mydb")
|
|
240
|
+
|
|
241
|
+
with pytest.raises(Exception) as exc_info:
|
|
242
|
+
Engine.process_error(engine, exc)
|
|
243
|
+
|
|
244
|
+
msg = str(exc_info.value)
|
|
245
|
+
assert "supersecret" not in msg
|
|
246
|
+
assert "*****" in msg
|
|
247
|
+
|
|
248
|
+
def test_mask_sensitive_in_string_function(self):
|
|
249
|
+
from velocity.db.utils import mask_sensitive_in_string
|
|
250
|
+
|
|
251
|
+
# password= pattern
|
|
252
|
+
assert "s3cret" not in mask_sensitive_in_string("password=s3cret host=db")
|
|
253
|
+
# URL pattern
|
|
254
|
+
assert "mypass" not in mask_sensitive_in_string("postgresql://user:mypass@host/db")
|
|
255
|
+
# No credentials — unchanged
|
|
256
|
+
assert mask_sensitive_in_string("normal error text") == "normal error text"
|
|
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
|
{velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/tests/test_email_processing.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/tests/test_spreadsheet_functions.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/app/validators/formbuilder_template.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/context_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/data_service.py
RENAMED
|
File without changes
|
{velocity_python-0.1.2 → velocity_python-0.1.3}/src/velocity/aws/handlers/mixins/web_handler.py
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
|