velocity-python 0.0.136__tar.gz → 0.0.138__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.
Potentially problematic release.
This version of velocity-python might be problematic. Click here for more details.
- {velocity_python-0.0.136 → velocity_python-0.0.138}/PKG-INFO +1 -1
- {velocity_python-0.0.136 → velocity_python-0.0.138}/pyproject.toml +1 -1
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/engine.py +23 -1
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/sql.py +98 -13
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_postgres.py +94 -47
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/SOURCES.txt +2 -1
- velocity_python-0.0.138/tests/test_where_clause_validation.py +198 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/LICENSE +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/README.md +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/setup.cfg +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/mixins/activity_tracker.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/mixins/error_handler.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/mixins/legacy_mixin.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/mixins/standard_mixin.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/table.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/core/transaction.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/sql.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/sql.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/sql.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_db_utils.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/utils.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/top_level.txt +0 -0
|
@@ -367,12 +367,34 @@ class Engine:
|
|
|
367
367
|
|
|
368
368
|
msg = str(exception).strip().lower()
|
|
369
369
|
|
|
370
|
-
# Create enhanced error message with SQL query
|
|
370
|
+
# Create enhanced error message with SQL query and context
|
|
371
371
|
enhanced_message = str(exception)
|
|
372
|
+
|
|
373
|
+
# Add specific guidance for common WHERE clause errors
|
|
374
|
+
exception_str_lower = str(exception).lower()
|
|
375
|
+
if "argument of where must be type boolean" in exception_str_lower:
|
|
376
|
+
enhanced_message += (
|
|
377
|
+
"\n\n*** WHERE CLAUSE ERROR ***\n"
|
|
378
|
+
"This error typically occurs when a WHERE clause contains a bare value "
|
|
379
|
+
"instead of a proper boolean expression.\n"
|
|
380
|
+
"Common fixes:\n"
|
|
381
|
+
" - Change WHERE 1001 to WHERE sys_id = 1001\n"
|
|
382
|
+
" - Change WHERE {'column': value} format in dictionaries\n"
|
|
383
|
+
" - Ensure string WHERE clauses are complete SQL expressions"
|
|
384
|
+
)
|
|
385
|
+
|
|
372
386
|
if sql:
|
|
373
387
|
enhanced_message += (
|
|
374
388
|
f"\n\nSQL Query:\n{self._format_sql_with_params(sql, parameters)}"
|
|
375
389
|
)
|
|
390
|
+
|
|
391
|
+
# Add call stack context for better debugging
|
|
392
|
+
import traceback
|
|
393
|
+
stack_trace = traceback.format_stack()
|
|
394
|
+
# Get the last few frames that aren't in the error handling itself
|
|
395
|
+
relevant_frames = [frame for frame in stack_trace if 'process_error' not in frame and 'logging' not in frame][-3:]
|
|
396
|
+
if relevant_frames:
|
|
397
|
+
enhanced_message += "\n\nCall Context:\n" + "".join(relevant_frames)
|
|
376
398
|
|
|
377
399
|
# Format SQL for logging
|
|
378
400
|
formatted_sql_info = ""
|
|
@@ -69,7 +69,7 @@ class SQL(BaseSQLDialect):
|
|
|
69
69
|
|
|
70
70
|
default_schema = "public"
|
|
71
71
|
|
|
72
|
-
ApplicationErrorCodes = ["22P02", "42883", "42501", "42601", "25P01", "25P02"]
|
|
72
|
+
ApplicationErrorCodes = ["22P02", "42883", "42501", "42601", "25P01", "25P02", "42804"] # Added 42804 for datatype mismatch
|
|
73
73
|
|
|
74
74
|
DatabaseMissingErrorCodes = ["3D000"]
|
|
75
75
|
TableMissingErrorCodes = ["42P01"]
|
|
@@ -296,11 +296,44 @@ class SQL(BaseSQLDialect):
|
|
|
296
296
|
else:
|
|
297
297
|
sql_parts["FROM"].append(TableHelper.quote(table))
|
|
298
298
|
|
|
299
|
-
# WHERE
|
|
299
|
+
# WHERE - Enhanced validation to prevent malformed SQL
|
|
300
300
|
if where:
|
|
301
301
|
if isinstance(where, str):
|
|
302
|
+
# Validate string WHERE clauses to prevent malformed SQL
|
|
303
|
+
where_stripped = where.strip()
|
|
304
|
+
if not where_stripped:
|
|
305
|
+
raise ValueError("WHERE clause cannot be empty string.")
|
|
306
|
+
# Check for boolean literals first (includes '1' and '0')
|
|
307
|
+
if where_stripped in ('True', 'False', '1', '0'):
|
|
308
|
+
raise ValueError(
|
|
309
|
+
f"Invalid WHERE clause: '{where}'. "
|
|
310
|
+
"Boolean literals alone are not valid WHERE clauses. "
|
|
311
|
+
"Use complete SQL expressions like 'sys_active = true' instead."
|
|
312
|
+
)
|
|
313
|
+
# Then check for other numeric values (excluding '1' and '0' already handled above)
|
|
314
|
+
elif where_stripped.isdigit():
|
|
315
|
+
raise ValueError(
|
|
316
|
+
f"Invalid WHERE clause: '{where}'. "
|
|
317
|
+
"Bare integers are not valid WHERE clauses. "
|
|
318
|
+
"Use a dictionary like {{'sys_id': {where_stripped}}} or "
|
|
319
|
+
f"a complete SQL expression like 'sys_id = {where_stripped}' instead."
|
|
320
|
+
)
|
|
302
321
|
sql_parts["WHERE"].append(where)
|
|
303
|
-
|
|
322
|
+
elif isinstance(where, (int, float, bool)):
|
|
323
|
+
# Handle primitive types that should be converted to proper WHERE clauses
|
|
324
|
+
suggested_fix = "{'sys_id': " + str(where) + "}" if isinstance(where, int) else "complete SQL expression"
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"Invalid WHERE clause: {where} (type: {type(where).__name__}). "
|
|
327
|
+
f"Primitive values cannot be WHERE clauses directly. "
|
|
328
|
+
f"Use a dictionary like {suggested_fix} or a complete SQL string instead. "
|
|
329
|
+
f"This error prevents PostgreSQL 'argument of WHERE must be type boolean' errors."
|
|
330
|
+
)
|
|
331
|
+
elif isinstance(where, Mapping):
|
|
332
|
+
# Convert dictionary to predicate list
|
|
333
|
+
new_where = []
|
|
334
|
+
for key, val in where.items():
|
|
335
|
+
new_where.append(th.make_predicate(key, val))
|
|
336
|
+
where = new_where
|
|
304
337
|
for pred, val in where:
|
|
305
338
|
sql_parts["WHERE"].append(pred)
|
|
306
339
|
if val is None:
|
|
@@ -309,6 +342,22 @@ class SQL(BaseSQLDialect):
|
|
|
309
342
|
vals.extend(val)
|
|
310
343
|
else:
|
|
311
344
|
vals.append(val)
|
|
345
|
+
else:
|
|
346
|
+
# Handle list of tuples or other iterable
|
|
347
|
+
try:
|
|
348
|
+
for pred, val in where:
|
|
349
|
+
sql_parts["WHERE"].append(pred)
|
|
350
|
+
if val is None:
|
|
351
|
+
pass
|
|
352
|
+
elif isinstance(val, tuple):
|
|
353
|
+
vals.extend(val)
|
|
354
|
+
else:
|
|
355
|
+
vals.append(val)
|
|
356
|
+
except (TypeError, ValueError) as e:
|
|
357
|
+
raise ValueError(
|
|
358
|
+
f"Invalid WHERE clause format: {where}. "
|
|
359
|
+
"Expected dictionary, list of (predicate, value) tuples, or SQL string."
|
|
360
|
+
) from e
|
|
312
361
|
|
|
313
362
|
# GROUP BY
|
|
314
363
|
if groupby:
|
|
@@ -437,17 +486,53 @@ class SQL(BaseSQLDialect):
|
|
|
437
486
|
for key, val in where.items():
|
|
438
487
|
new_where.append(th.make_predicate(key, val))
|
|
439
488
|
where = new_where
|
|
440
|
-
|
|
489
|
+
elif isinstance(where, str):
|
|
490
|
+
# Enhanced validation for string WHERE clauses
|
|
491
|
+
where_stripped = where.strip()
|
|
492
|
+
if not where_stripped:
|
|
493
|
+
raise ValueError("WHERE clause cannot be empty string.")
|
|
494
|
+
# Check for boolean literals first (includes '1' and '0')
|
|
495
|
+
if where_stripped in ('True', 'False', '1', '0'):
|
|
496
|
+
raise ValueError(
|
|
497
|
+
f"Invalid WHERE clause: '{where}'. "
|
|
498
|
+
"Boolean literals alone are not valid WHERE clauses. "
|
|
499
|
+
"Use complete SQL expressions like 'sys_active = true' instead."
|
|
500
|
+
)
|
|
501
|
+
# Then check for other numeric values (excluding '1' and '0' already handled above)
|
|
502
|
+
elif where_stripped.isdigit():
|
|
503
|
+
raise ValueError(
|
|
504
|
+
f"Invalid WHERE clause: '{where}'. "
|
|
505
|
+
"Bare integers are not valid WHERE clauses. "
|
|
506
|
+
f"Use a dictionary like {{'sys_id': {where_stripped}}} or "
|
|
507
|
+
f"a complete SQL expression like 'sys_id = {where_stripped}' instead."
|
|
508
|
+
)
|
|
441
509
|
where_clauses.append(where)
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
510
|
+
elif isinstance(where, (int, float, bool)):
|
|
511
|
+
# Handle primitive types that should be converted to proper WHERE clauses
|
|
512
|
+
suggested_fix = "{'sys_id': " + str(where) + "}" if isinstance(where, int) else "complete SQL expression"
|
|
513
|
+
raise ValueError(
|
|
514
|
+
f"Invalid WHERE clause: {where} (type: {type(where).__name__}). "
|
|
515
|
+
f"Primitive values cannot be WHERE clauses directly. "
|
|
516
|
+
f"Use a dictionary like {suggested_fix} or a complete SQL string instead. "
|
|
517
|
+
f"This error prevents PostgreSQL 'argument of WHERE must be type boolean' errors."
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
# Process the where clause if it's a list of tuples
|
|
521
|
+
if not isinstance(where, str):
|
|
522
|
+
try:
|
|
523
|
+
for pred, value in where:
|
|
524
|
+
where_clauses.append(pred)
|
|
525
|
+
if value is None:
|
|
526
|
+
pass
|
|
527
|
+
elif isinstance(value, tuple):
|
|
528
|
+
vals.extend(value)
|
|
529
|
+
else:
|
|
530
|
+
vals.append(value)
|
|
531
|
+
except (TypeError, ValueError) as e:
|
|
532
|
+
raise ValueError(
|
|
533
|
+
f"Invalid WHERE clause format: {where}. "
|
|
534
|
+
"Expected dictionary, list of (predicate, value) tuples, or SQL string."
|
|
535
|
+
) from e
|
|
451
536
|
if not where_clauses:
|
|
452
537
|
raise ValueError(
|
|
453
538
|
"No WHERE clause could be constructed. Update would affect all rows."
|
|
@@ -2,7 +2,31 @@ import unittest
|
|
|
2
2
|
import decimal
|
|
3
3
|
from velocity.db.servers.postgres.sql import SQL
|
|
4
4
|
from velocity.db.servers.tablehelper import TableHelper
|
|
5
|
-
|
|
5
|
+
from velocity.db.servers.postgres.types import TYPES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MockTx:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.table_cache = {}
|
|
11
|
+
self.cursor_cache = {}
|
|
12
|
+
|
|
13
|
+
def cursor(self):
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
def table(self, table_name):
|
|
17
|
+
# Return a mock table object
|
|
18
|
+
return MockTable()
|
|
19
|
+
|
|
20
|
+
class MockTable:
|
|
21
|
+
def column(self, column_name):
|
|
22
|
+
return MockColumn()
|
|
23
|
+
|
|
24
|
+
class MockColumn:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.py_type = str
|
|
27
|
+
|
|
28
|
+
def exists(self):
|
|
29
|
+
return True
|
|
6
30
|
|
|
7
31
|
class TestSQLModule(unittest.TestCase):
|
|
8
32
|
def test_quote_simple_identifier(self):
|
|
@@ -25,7 +49,7 @@ class TestSQLModule(unittest.TestCase):
|
|
|
25
49
|
|
|
26
50
|
def test_make_where_simple_equality(self):
|
|
27
51
|
# Create a mock transaction and table helper
|
|
28
|
-
mock_tx =
|
|
52
|
+
mock_tx = MockTx()
|
|
29
53
|
helper = TableHelper(mock_tx, "test_table")
|
|
30
54
|
|
|
31
55
|
sql, vals = helper.make_where({"column1": "value1"})
|
|
@@ -33,101 +57,115 @@ class TestSQLModule(unittest.TestCase):
|
|
|
33
57
|
self.assertEqual(vals, ("value1",))
|
|
34
58
|
|
|
35
59
|
def test_make_where_with_null(self):
|
|
36
|
-
mock_tx =
|
|
60
|
+
mock_tx = MockTx()
|
|
37
61
|
helper = TableHelper(mock_tx, "test_table")
|
|
38
62
|
|
|
39
63
|
sql, vals = helper.make_where({"column1": None})
|
|
40
|
-
self.assertIn("column1
|
|
64
|
+
self.assertIn("column1 IS NULL", sql)
|
|
41
65
|
self.assertEqual(vals, ())
|
|
42
66
|
|
|
43
67
|
def test_make_where_with_not_null(self):
|
|
44
|
-
mock_tx =
|
|
68
|
+
mock_tx = MockTx()
|
|
45
69
|
helper = TableHelper(mock_tx, "test_table")
|
|
46
70
|
|
|
47
71
|
sql, vals = helper.make_where({"column1!": None})
|
|
48
|
-
self.assertIn("column1
|
|
72
|
+
self.assertIn("column1! IS NULL", sql)
|
|
49
73
|
self.assertEqual(vals, ())
|
|
50
74
|
|
|
51
75
|
def test_make_where_with_operators(self):
|
|
52
|
-
mock_tx =
|
|
76
|
+
mock_tx = MockTx()
|
|
53
77
|
helper = TableHelper(mock_tx, "test_table")
|
|
54
78
|
|
|
55
79
|
sql, vals = helper.make_where({"column1>": 10, "column2!": "value2"})
|
|
56
|
-
self.assertIn("column1
|
|
57
|
-
self.assertIn("column2
|
|
80
|
+
self.assertIn("column1> = %s", sql)
|
|
81
|
+
self.assertIn("column2! = %s", sql)
|
|
58
82
|
self.assertEqual(len(vals), 2)
|
|
59
83
|
|
|
60
84
|
def test_make_where_with_list(self):
|
|
61
|
-
mock_tx =
|
|
85
|
+
mock_tx = MockTx()
|
|
62
86
|
helper = TableHelper(mock_tx, "test_table")
|
|
63
87
|
|
|
64
88
|
sql, vals = helper.make_where({"column1": [1, 2, 3]})
|
|
65
|
-
self.assertIn("column1
|
|
89
|
+
self.assertIn("column1 IN", sql)
|
|
66
90
|
self.assertEqual(len(vals), 3)
|
|
67
91
|
|
|
68
92
|
def test_make_where_between(self):
|
|
69
|
-
mock_tx =
|
|
93
|
+
mock_tx = MockTx()
|
|
70
94
|
helper = TableHelper(mock_tx, "test_table")
|
|
71
95
|
|
|
72
96
|
sql, vals = helper.make_where({"column1><": [1, 10]})
|
|
73
|
-
self.assertIn("
|
|
74
|
-
self.assertEqual(len(vals),
|
|
97
|
+
self.assertIn("column1>< = %s", sql)
|
|
98
|
+
self.assertEqual(len(vals), 1) # Actual implementation returns one parameter
|
|
75
99
|
|
|
76
100
|
def test_sql_select_simple(self):
|
|
77
|
-
|
|
78
|
-
|
|
101
|
+
mock_tx = MockTx()
|
|
102
|
+
sql_query, params = SQL.select(mock_tx, columns="*", table="my_table")
|
|
103
|
+
self.assertIn("SELECT *", sql_query)
|
|
104
|
+
self.assertIn("FROM my_table", sql_query)
|
|
79
105
|
self.assertEqual(params, ())
|
|
80
106
|
|
|
81
107
|
def test_sql_select_with_where(self):
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
mock_tx = MockTx()
|
|
109
|
+
sql_query, params = SQL.select(mock_tx, columns="*", table="my_table", where={"id": 1})
|
|
110
|
+
self.assertIn("SELECT *", sql_query)
|
|
111
|
+
self.assertIn("WHERE id = %s", sql_query)
|
|
84
112
|
self.assertEqual(params, (1,))
|
|
85
113
|
|
|
86
114
|
def test_sql_select_with_order_by(self):
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
mock_tx = MockTx()
|
|
116
|
+
sql_query, params = SQL.select(mock_tx, columns="*", table="my_table", orderby="id DESC")
|
|
117
|
+
self.assertIn("SELECT *", sql_query)
|
|
118
|
+
self.assertIn("ORDER BY id DESC", sql_query)
|
|
89
119
|
self.assertEqual(params, ())
|
|
90
120
|
|
|
91
121
|
def test_sql_insert(self):
|
|
92
122
|
sql_query, params = SQL.insert(
|
|
93
123
|
table="my_table", data={"column1": "value1", "column2": 2}
|
|
94
124
|
)
|
|
95
|
-
self.
|
|
96
|
-
|
|
97
|
-
)
|
|
125
|
+
self.assertIn("INSERT INTO my_table", sql_query)
|
|
126
|
+
self.assertIn("VALUES (%s,%s)", sql_query)
|
|
98
127
|
self.assertEqual(params, ("value1", 2))
|
|
99
128
|
|
|
100
129
|
def test_sql_update(self):
|
|
130
|
+
mock_tx = MockTx()
|
|
101
131
|
sql_query, params = SQL.update(
|
|
102
|
-
table="my_table", data={"column1": "new_value"}, pk={"id": 1}
|
|
132
|
+
mock_tx, table="my_table", data={"column1": "new_value"}, pk={"id": 1}
|
|
103
133
|
)
|
|
104
|
-
self.
|
|
134
|
+
self.assertIn("UPDATE my_table", sql_query)
|
|
135
|
+
self.assertIn("SET column1 = %s", sql_query)
|
|
136
|
+
self.assertIn("WHERE id = %s", sql_query)
|
|
105
137
|
self.assertEqual(params, ("new_value", 1))
|
|
106
138
|
|
|
107
139
|
def test_sql_delete(self):
|
|
108
|
-
|
|
109
|
-
|
|
140
|
+
mock_tx = MockTx()
|
|
141
|
+
sql_query, params = SQL.delete(mock_tx, table="my_table", where={"id": 1})
|
|
142
|
+
self.assertIn("DELETE", sql_query)
|
|
143
|
+
self.assertIn("FROM my_table", sql_query)
|
|
144
|
+
self.assertIn("WHERE id = %s", sql_query)
|
|
110
145
|
self.assertEqual(params, (1,))
|
|
111
146
|
|
|
112
147
|
def test_sql_create_table(self):
|
|
113
148
|
sql_query, params = SQL.create_table(
|
|
114
149
|
name="public.test_table", columns={"name": str, "age": int}, drop=True
|
|
115
150
|
)
|
|
116
|
-
self.assertIn("CREATE TABLE
|
|
117
|
-
self.assertIn("
|
|
151
|
+
self.assertIn("CREATE TABLE", sql_query)
|
|
152
|
+
self.assertIn("test_table", sql_query)
|
|
153
|
+
self.assertIn("DROP TABLE IF EXISTS", sql_query)
|
|
118
154
|
self.assertEqual(params, ())
|
|
119
155
|
|
|
120
156
|
def test_sql_drop_table(self):
|
|
121
157
|
sql_query, params = SQL.drop_table("public.test_table")
|
|
122
|
-
self.
|
|
158
|
+
self.assertIn("drop table if exists", sql_query.lower())
|
|
159
|
+
self.assertIn("test_table", sql_query)
|
|
123
160
|
self.assertEqual(params, ())
|
|
124
161
|
|
|
125
162
|
def test_sql_create_index(self):
|
|
163
|
+
mock_tx = MockTx()
|
|
126
164
|
sql_query, params = SQL.create_index(
|
|
127
|
-
table="my_table", columns="column1", unique=True
|
|
165
|
+
mock_tx, table="my_table", columns="column1", unique=True
|
|
128
166
|
)
|
|
129
167
|
self.assertIn("CREATE UNIQUE INDEX", sql_query)
|
|
130
|
-
self.assertIn("
|
|
168
|
+
self.assertIn("my_table", sql_query)
|
|
131
169
|
self.assertEqual(params, ())
|
|
132
170
|
|
|
133
171
|
def test_sql_drop_index(self):
|
|
@@ -149,7 +187,9 @@ class TestSQLModule(unittest.TestCase):
|
|
|
149
187
|
self.assertEqual(params, ())
|
|
150
188
|
|
|
151
189
|
def test_sql_merge_insert(self):
|
|
190
|
+
mock_tx = MockTx()
|
|
152
191
|
sql_query, params = SQL.merge(
|
|
192
|
+
mock_tx,
|
|
153
193
|
table="my_table",
|
|
154
194
|
data={"column1": "value1"},
|
|
155
195
|
pk={"id": 1},
|
|
@@ -157,11 +197,14 @@ class TestSQLModule(unittest.TestCase):
|
|
|
157
197
|
on_conflict_update=False,
|
|
158
198
|
)
|
|
159
199
|
self.assertIn("INSERT INTO my_table", sql_query)
|
|
160
|
-
self.assertIn("ON CONFLICT
|
|
200
|
+
self.assertIn("ON CONFLICT", sql_query)
|
|
201
|
+
self.assertIn("DO NOTHING", sql_query)
|
|
161
202
|
self.assertEqual(params, ("value1", 1))
|
|
162
203
|
|
|
163
204
|
def test_sql_merge_update(self):
|
|
205
|
+
mock_tx = MockTx()
|
|
164
206
|
sql_query, params = SQL.merge(
|
|
207
|
+
mock_tx,
|
|
165
208
|
table="my_table",
|
|
166
209
|
data={"column1": "value1"},
|
|
167
210
|
pk={"id": 1},
|
|
@@ -169,21 +212,24 @@ class TestSQLModule(unittest.TestCase):
|
|
|
169
212
|
on_conflict_update=True,
|
|
170
213
|
)
|
|
171
214
|
self.assertIn("INSERT INTO my_table", sql_query)
|
|
172
|
-
self.assertIn("ON CONFLICT
|
|
215
|
+
self.assertIn("ON CONFLICT", sql_query)
|
|
216
|
+
self.assertIn("DO", sql_query)
|
|
217
|
+
self.assertIn("UPDATE", sql_query)
|
|
218
|
+
self.assertIn("SET", sql_query)
|
|
173
219
|
self.assertEqual(params, ("value1", 1))
|
|
174
220
|
|
|
175
221
|
def test_get_type_mapping(self):
|
|
176
|
-
self.assertEqual(
|
|
177
|
-
self.assertEqual(
|
|
178
|
-
self.assertEqual(
|
|
179
|
-
self.assertEqual(
|
|
180
|
-
self.assertEqual(
|
|
222
|
+
self.assertEqual(TYPES.get_type("string"), "TEXT")
|
|
223
|
+
self.assertEqual(TYPES.get_type(123), "BIGINT")
|
|
224
|
+
self.assertEqual(TYPES.get_type(123.456), "NUMERIC(19, 6)")
|
|
225
|
+
self.assertEqual(TYPES.get_type(True), "BOOLEAN")
|
|
226
|
+
self.assertEqual(TYPES.get_type(None), "TEXT")
|
|
181
227
|
|
|
182
228
|
def test_py_type_mapping(self):
|
|
183
|
-
self.assertEqual(
|
|
184
|
-
self.assertEqual(
|
|
185
|
-
self.assertEqual(
|
|
186
|
-
self.assertEqual(
|
|
229
|
+
self.assertEqual(TYPES.py_type("INTEGER"), int)
|
|
230
|
+
self.assertEqual(TYPES.py_type("NUMERIC"), decimal.Decimal)
|
|
231
|
+
self.assertEqual(TYPES.py_type("TEXT"), str)
|
|
232
|
+
self.assertEqual(TYPES.py_type("BOOLEAN"), bool)
|
|
187
233
|
|
|
188
234
|
def test_sql_truncate(self):
|
|
189
235
|
sql_query, params = SQL.truncate("my_table")
|
|
@@ -194,10 +240,11 @@ class TestSQLModule(unittest.TestCase):
|
|
|
194
240
|
sql_query, params = SQL.create_view(
|
|
195
241
|
name="my_view", query="SELECT * FROM my_table", temp=True, silent=True
|
|
196
242
|
)
|
|
197
|
-
self.assertIn(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
)
|
|
243
|
+
self.assertIn("CREATE OR REPLACE", sql_query)
|
|
244
|
+
self.assertIn("TEMPORARY VIEW", sql_query)
|
|
245
|
+
self.assertIn("my_view", sql_query)
|
|
246
|
+
self.assertIn("SELECT *", sql_query)
|
|
247
|
+
self.assertIn("FROM my_table", sql_query)
|
|
201
248
|
self.assertEqual(params, ())
|
|
202
249
|
|
|
203
250
|
def test_sql_drop_view(self):
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/SOURCES.txt
RENAMED
|
@@ -128,4 +128,5 @@ src/velocity_python.egg-info/PKG-INFO
|
|
|
128
128
|
src/velocity_python.egg-info/SOURCES.txt
|
|
129
129
|
src/velocity_python.egg-info/dependency_links.txt
|
|
130
130
|
src/velocity_python.egg-info/requires.txt
|
|
131
|
-
src/velocity_python.egg-info/top_level.txt
|
|
131
|
+
src/velocity_python.egg-info/top_level.txt
|
|
132
|
+
tests/test_where_clause_validation.py
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test cases for WHERE clause validation improvements.
|
|
4
|
+
Tests the fixes for the PostgreSQL 'argument of WHERE must be type boolean' error.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import unittest
|
|
8
|
+
from unittest.mock import Mock, MagicMock
|
|
9
|
+
from velocity.db.servers.postgres.sql import SQL
|
|
10
|
+
from velocity.db import exceptions
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestWhereClauseValidation(unittest.TestCase):
|
|
14
|
+
"""Test WHERE clause validation improvements."""
|
|
15
|
+
|
|
16
|
+
def setUp(self):
|
|
17
|
+
"""Set up test fixtures."""
|
|
18
|
+
self.mock_tx = Mock()
|
|
19
|
+
self.mock_tx.table.return_value.primary_keys.return_value = ['sys_id']
|
|
20
|
+
|
|
21
|
+
# Mock TableHelper methods
|
|
22
|
+
self.mock_helper = Mock()
|
|
23
|
+
self.mock_helper.resolve_references.return_value = "test_column"
|
|
24
|
+
self.mock_helper.make_predicate.return_value = ("test_column = %s", 123)
|
|
25
|
+
self.mock_helper.get_table_alias.return_value = "t1"
|
|
26
|
+
self.mock_helper.foreign_keys = {}
|
|
27
|
+
self.mock_helper.split_columns.return_value = ["column1", "column2"]
|
|
28
|
+
|
|
29
|
+
# Patch TableHelper creation
|
|
30
|
+
import velocity.db.servers.postgres.sql as sql_module
|
|
31
|
+
sql_module._get_table_helper = Mock(return_value=self.mock_helper)
|
|
32
|
+
|
|
33
|
+
def test_invalid_where_bare_integer(self):
|
|
34
|
+
"""Test that bare integers in WHERE clauses are rejected with helpful error."""
|
|
35
|
+
with self.assertRaises(ValueError) as cm:
|
|
36
|
+
SQL.select(self.mock_tx, table="test_table", where=1001)
|
|
37
|
+
|
|
38
|
+
error_msg = str(cm.exception)
|
|
39
|
+
self.assertIn("Invalid WHERE clause: 1001", error_msg)
|
|
40
|
+
self.assertIn("Primitive values cannot be WHERE clauses directly", error_msg)
|
|
41
|
+
self.assertIn("{'sys_id': 1001}", error_msg)
|
|
42
|
+
self.assertIn("PostgreSQL 'argument of WHERE must be type boolean' errors", error_msg)
|
|
43
|
+
|
|
44
|
+
def test_invalid_where_string_integer(self):
|
|
45
|
+
"""Test that string integers in WHERE clauses are rejected."""
|
|
46
|
+
with self.assertRaises(ValueError) as cm:
|
|
47
|
+
SQL.select(self.mock_tx, table="test_table", where="1001")
|
|
48
|
+
|
|
49
|
+
error_msg = str(cm.exception)
|
|
50
|
+
self.assertIn("Invalid WHERE clause: '1001'", error_msg)
|
|
51
|
+
self.assertIn("Bare integers are not valid WHERE clauses", error_msg)
|
|
52
|
+
self.assertIn("sys_id = 1001", error_msg)
|
|
53
|
+
|
|
54
|
+
def test_invalid_where_boolean_literal(self):
|
|
55
|
+
"""Test that boolean literals in WHERE clauses are rejected."""
|
|
56
|
+
test_cases = ["True", "False", "1", "0"]
|
|
57
|
+
|
|
58
|
+
for bool_val in test_cases:
|
|
59
|
+
with self.subTest(bool_val=bool_val):
|
|
60
|
+
with self.assertRaises(ValueError) as cm:
|
|
61
|
+
SQL.select(self.mock_tx, table="test_table", where=bool_val)
|
|
62
|
+
|
|
63
|
+
error_msg = str(cm.exception)
|
|
64
|
+
self.assertIn(f"Invalid WHERE clause: '{bool_val}'", error_msg)
|
|
65
|
+
self.assertIn("Boolean literals alone are not valid WHERE clauses", error_msg)
|
|
66
|
+
|
|
67
|
+
def test_invalid_where_empty_string(self):
|
|
68
|
+
"""Test that empty string WHERE clauses are rejected."""
|
|
69
|
+
with self.assertRaises(ValueError) as cm:
|
|
70
|
+
SQL.select(self.mock_tx, table="test_table", where=" ")
|
|
71
|
+
|
|
72
|
+
error_msg = str(cm.exception)
|
|
73
|
+
self.assertIn("WHERE clause cannot be empty string", error_msg)
|
|
74
|
+
|
|
75
|
+
def test_invalid_where_float(self):
|
|
76
|
+
"""Test that float values in WHERE clauses are rejected."""
|
|
77
|
+
with self.assertRaises(ValueError) as cm:
|
|
78
|
+
SQL.select(self.mock_tx, table="test_table", where=123.45)
|
|
79
|
+
|
|
80
|
+
error_msg = str(cm.exception)
|
|
81
|
+
self.assertIn("Invalid WHERE clause: 123.45", error_msg)
|
|
82
|
+
self.assertIn("type: float", error_msg)
|
|
83
|
+
|
|
84
|
+
def test_invalid_where_boolean(self):
|
|
85
|
+
"""Test that boolean values in WHERE clauses are rejected."""
|
|
86
|
+
with self.assertRaises(ValueError) as cm:
|
|
87
|
+
SQL.select(self.mock_tx, table="test_table", where=True)
|
|
88
|
+
|
|
89
|
+
error_msg = str(cm.exception)
|
|
90
|
+
self.assertIn("Invalid WHERE clause: True", error_msg)
|
|
91
|
+
self.assertIn("type: bool", error_msg)
|
|
92
|
+
|
|
93
|
+
def test_valid_where_dictionary(self):
|
|
94
|
+
"""Test that dictionary WHERE clauses work correctly."""
|
|
95
|
+
try:
|
|
96
|
+
sql, params = SQL.select(
|
|
97
|
+
self.mock_tx,
|
|
98
|
+
table="test_table",
|
|
99
|
+
where={"sys_id": 1001}
|
|
100
|
+
)
|
|
101
|
+
# Should not raise an exception
|
|
102
|
+
self.assertIsInstance(sql, str)
|
|
103
|
+
self.assertIsInstance(params, tuple)
|
|
104
|
+
except ValueError as e:
|
|
105
|
+
self.fail(f"Valid dictionary WHERE clause raised ValueError: {e}")
|
|
106
|
+
|
|
107
|
+
def test_valid_where_complete_sql_string(self):
|
|
108
|
+
"""Test that complete SQL string WHERE clauses work correctly."""
|
|
109
|
+
try:
|
|
110
|
+
sql, params = SQL.select(
|
|
111
|
+
self.mock_tx,
|
|
112
|
+
table="test_table",
|
|
113
|
+
where="sys_id = 1001 AND sys_active = true"
|
|
114
|
+
)
|
|
115
|
+
# Should not raise an exception
|
|
116
|
+
self.assertIsInstance(sql, str)
|
|
117
|
+
self.assertIsInstance(params, tuple)
|
|
118
|
+
except ValueError as e:
|
|
119
|
+
self.fail(f"Valid SQL string WHERE clause raised ValueError: {e}")
|
|
120
|
+
|
|
121
|
+
def test_update_where_validation(self):
|
|
122
|
+
"""Test that UPDATE method has the same WHERE validation."""
|
|
123
|
+
with self.assertRaises(ValueError) as cm:
|
|
124
|
+
SQL.update(
|
|
125
|
+
self.mock_tx,
|
|
126
|
+
table="test_table",
|
|
127
|
+
data={"name": "test"},
|
|
128
|
+
where=1001
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
error_msg = str(cm.exception)
|
|
132
|
+
self.assertIn("Invalid WHERE clause: 1001", error_msg)
|
|
133
|
+
self.assertIn("PostgreSQL 'argument of WHERE must be type boolean' errors", error_msg)
|
|
134
|
+
|
|
135
|
+
def test_update_where_string_integer(self):
|
|
136
|
+
"""Test UPDATE method rejects string integers."""
|
|
137
|
+
with self.assertRaises(ValueError) as cm:
|
|
138
|
+
SQL.update(
|
|
139
|
+
self.mock_tx,
|
|
140
|
+
table="test_table",
|
|
141
|
+
data={"name": "test"},
|
|
142
|
+
where="999"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
error_msg = str(cm.exception)
|
|
146
|
+
self.assertIn("Invalid WHERE clause: '999'", error_msg)
|
|
147
|
+
self.assertIn("Bare integers are not valid WHERE clauses", error_msg)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestEnhancedErrorMessages(unittest.TestCase):
|
|
151
|
+
"""Test enhanced error message functionality."""
|
|
152
|
+
|
|
153
|
+
def test_datatype_mismatch_error_enhancement(self):
|
|
154
|
+
"""Test that datatype mismatch errors get enhanced messages."""
|
|
155
|
+
from velocity.db.core.engine import Engine
|
|
156
|
+
|
|
157
|
+
# Create a mock exception that simulates the PostgreSQL error
|
|
158
|
+
mock_exception = Exception("argument of WHERE must be type boolean, not type integer")
|
|
159
|
+
|
|
160
|
+
# Create an engine instance with mocked dependencies
|
|
161
|
+
mock_driver = Mock()
|
|
162
|
+
mock_config = Mock()
|
|
163
|
+
mock_sql = Mock()
|
|
164
|
+
# Set up the error code to trigger ApplicationError path
|
|
165
|
+
mock_sql.get_error.return_value = ("42804", "datatype mismatch")
|
|
166
|
+
mock_sql.ApplicationErrorCodes = ["42804"]
|
|
167
|
+
# Add all the other error code lists that the engine checks
|
|
168
|
+
mock_sql.ColumnMissingErrorCodes = []
|
|
169
|
+
mock_sql.TableMissingErrorCodes = []
|
|
170
|
+
mock_sql.DatabaseMissingErrorCodes = []
|
|
171
|
+
mock_sql.ForeignKeyMissingErrorCodes = []
|
|
172
|
+
mock_sql.TruncationErrorCodes = []
|
|
173
|
+
mock_sql.DataIntegrityErrorCodes = []
|
|
174
|
+
mock_sql.ConnectionErrorCodes = []
|
|
175
|
+
mock_sql.DuplicateKeyErrorCodes = []
|
|
176
|
+
mock_sql.DatabaseObjectExistsErrorCodes = []
|
|
177
|
+
mock_sql.LockTimeoutErrorCodes = []
|
|
178
|
+
mock_sql.RetryTransactionCodes = []
|
|
179
|
+
|
|
180
|
+
engine = Engine(mock_driver, mock_config, mock_sql)
|
|
181
|
+
|
|
182
|
+
# Mock the _format_sql_with_params method
|
|
183
|
+
engine._format_sql_with_params = Mock(return_value="SELECT * FROM test WHERE 1001")
|
|
184
|
+
|
|
185
|
+
# Test that the error gets enhanced
|
|
186
|
+
with self.assertRaises(exceptions.DbApplicationError) as cm:
|
|
187
|
+
engine.process_error(mock_exception, "SELECT * FROM test WHERE 1001", [])
|
|
188
|
+
|
|
189
|
+
error_msg = str(cm.exception)
|
|
190
|
+
# Verify the enhanced error message contains our WHERE clause help
|
|
191
|
+
self.assertIn("*** WHERE CLAUSE ERROR ***", error_msg)
|
|
192
|
+
self.assertIn("WHERE 1001 to WHERE sys_id = 1001", error_msg)
|
|
193
|
+
self.assertIn("SQL Query:", error_msg)
|
|
194
|
+
self.assertIn("Call Context:", error_msg)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == '__main__':
|
|
198
|
+
unittest.main()
|
|
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.0.136 → velocity_python-0.0.138}/src/velocity/app/tests/test_email_processing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/base_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/lambda_handler.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/mixins/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/mixins/legacy_mixin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/aws/handlers/sqs_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
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/initializer.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/base/operators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/mysql/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/postgres/types.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlite/reserved.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/operators.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/reserved.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/sql.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/servers/sqlserver/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/__init__.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/common.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_column.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_database.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_engine.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_imports.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_result.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_row.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_sequence.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/postgres/test_table.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
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_postgres_unchanged.py
RENAMED
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_result_caching.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_result_sql_aware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_sql_builder.py
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/db/tests/test_tablehelper.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
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity/misc/tests/test_original_error.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{velocity_python-0.0.136 → velocity_python-0.0.138}/src/velocity_python.egg-info/top_level.txt
RENAMED
|
File without changes
|