velocity-python 0.0.134__py3-none-any.whl → 0.0.135__py3-none-any.whl

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.

@@ -0,0 +1,471 @@
1
+ import unittest
2
+ import psycopg2
3
+ from velocity.db.core.transaction import Transaction
4
+ from velocity.db.exceptions import DbSyntaxError, DbTableMissingError, DbColumnMissingError
5
+ from .common import CommonPostgresTest, engine, test_db
6
+
7
+
8
+ @engine.transaction
9
+ class TestPostgreSQLModule(CommonPostgresTest):
10
+ """Comprehensive tests for PostgreSQL SQL module including edge cases and error conditions."""
11
+
12
+ @classmethod
13
+ def create_test_tables(cls, tx):
14
+ """Create comprehensive test tables for SQL module testing."""
15
+ # Basic table for general operations
16
+ tx.table("sql_test_basic").create(
17
+ columns={
18
+ "name": str,
19
+ "age": int,
20
+ "email": str,
21
+ "active": bool,
22
+ "score": float,
23
+ }
24
+ )
25
+
26
+ # Table with special characters and reserved words
27
+ tx.table("sql_test_special").create(
28
+ columns={
29
+ "order": str, # reserved keyword
30
+ "user": str, # reserved keyword
31
+ "select": str, # reserved keyword
32
+ "column_with_spaces": str,
33
+ "column-with-dashes": str,
34
+ }
35
+ )
36
+
37
+ # Table for foreign key testing
38
+ tx.table("sql_test_parent").create(
39
+ columns={
40
+ "parent_name": str,
41
+ "category": str,
42
+ }
43
+ )
44
+
45
+ tx.table("sql_test_child").create(
46
+ columns={
47
+ "parent_id": int,
48
+ "child_name": str,
49
+ "value": float,
50
+ }
51
+ )
52
+
53
+ # Create foreign key relationship
54
+ tx.table("sql_test_child").create_foreign_key("parent_id", "sql_test_parent", "sys_id")
55
+
56
+ # Large table for performance testing
57
+ tx.table("sql_test_large").create(
58
+ columns={
59
+ "data": str,
60
+ "index_col": int,
61
+ "timestamp_col": str,
62
+ }
63
+ )
64
+
65
+ # Insert test data
66
+ cls.insert_test_data(tx)
67
+
68
+ @classmethod
69
+ def insert_test_data(cls, tx):
70
+ """Insert comprehensive test data."""
71
+ # Basic table data
72
+ basic_data = [
73
+ {"name": "Alice", "age": 30, "email": "alice@example.com", "active": True, "score": 95.5},
74
+ {"name": "Bob", "age": 25, "email": "bob@example.com", "active": False, "score": 87.2},
75
+ {"name": "Charlie", "age": 35, "email": "charlie@example.com", "active": True, "score": 92.8},
76
+ {"name": "Diana", "age": 28, "email": "diana@example.com", "active": True, "score": 88.9},
77
+ {"name": "Eve", "age": 32, "email": "eve@example.com", "active": False, "score": 91.1},
78
+ ]
79
+
80
+ for data in basic_data:
81
+ tx.table("sql_test_basic").insert(data)
82
+
83
+ # Special characters data
84
+ special_data = [
85
+ {"order": "first", "user": "admin", "select": "all", "column_with_spaces": "test value", "column-with-dashes": "test-value"},
86
+ {"order": "second", "user": "user1", "select": "some", "column_with_spaces": "another value", "column-with-dashes": "another-value"},
87
+ ]
88
+
89
+ for data in special_data:
90
+ tx.table("sql_test_special").insert(data)
91
+
92
+ # Parent-child data
93
+ parent_data = [
94
+ {"parent_name": "Parent A", "category": "type1"},
95
+ {"parent_name": "Parent B", "category": "type2"},
96
+ {"parent_name": "Parent C", "category": "type1"},
97
+ ]
98
+
99
+ parent_ids = []
100
+ for data in parent_data:
101
+ row = tx.table("sql_test_parent").insert(data)
102
+ parent_ids.append(row["sys_id"])
103
+
104
+ child_data = [
105
+ {"parent_id": parent_ids[0], "child_name": "Child A1", "value": 10.5},
106
+ {"parent_id": parent_ids[0], "child_name": "Child A2", "value": 20.3},
107
+ {"parent_id": parent_ids[1], "child_name": "Child B1", "value": 15.7},
108
+ {"parent_id": parent_ids[2], "child_name": "Child C1", "value": 30.9},
109
+ ]
110
+
111
+ for data in child_data:
112
+ tx.table("sql_test_child").insert(data)
113
+
114
+ # Large table data for performance testing
115
+ for i in range(100):
116
+ tx.table("sql_test_large").insert({
117
+ "data": f"test_data_{i}",
118
+ "index_col": i,
119
+ "timestamp_col": f"2023-01-{(i % 28) + 1:02d}",
120
+ })
121
+
122
+ def test_select_basic(self, tx):
123
+ """Test basic SELECT operations."""
124
+ # Simple select all
125
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic")
126
+ self.assertIn("SELECT", sql.upper())
127
+ self.assertIn("sql_test_basic", sql)
128
+
129
+ # Select with where clause
130
+ where = {"name": "Alice"}
131
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
132
+ self.assertIn("WHERE", sql.upper())
133
+ self.assertEqual(vals, ("Alice",))
134
+
135
+ # Select with multiple conditions
136
+ where = {"active": True, "age": 30}
137
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
138
+ self.assertIn("WHERE", sql.upper())
139
+ self.assertIn("AND", sql.upper())
140
+
141
+ def test_select_with_operators(self, tx):
142
+ """Test SELECT with various operators."""
143
+ # Greater than
144
+ where = {"age__gt": 30}
145
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
146
+ self.assertIn(">", sql)
147
+ self.assertEqual(vals, (30,))
148
+
149
+ # Less than or equal
150
+ where = {"score__lte": 90.0}
151
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
152
+ self.assertIn("<=", sql)
153
+
154
+ # IN operator
155
+ where = {"name__in": ["Alice", "Bob"]}
156
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
157
+ self.assertIn("IN", sql.upper())
158
+
159
+ # LIKE operator
160
+ where = {"email__contains": "example"}
161
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
162
+ self.assertIn("LIKE", sql.upper() if "LIKE" in sql.upper() else "ILIKE", sql.upper())
163
+
164
+ def test_select_ordering(self, tx):
165
+ """Test SELECT with ordering."""
166
+ # Ascending order
167
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", orderby="name")
168
+ self.assertIn("ORDER BY", sql.upper())
169
+ self.assertIn("name", sql)
170
+
171
+ # Descending order
172
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", orderby="-age")
173
+ self.assertIn("ORDER BY", sql.upper())
174
+ self.assertIn("DESC", sql.upper())
175
+
176
+ # Multiple ordering
177
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", orderby=["name", "-age"])
178
+ order_by_count = sql.upper().count("ORDER BY")
179
+ self.assertEqual(order_by_count, 1)
180
+ self.assertIn(",", sql)
181
+
182
+ def test_select_limits(self, tx):
183
+ """Test SELECT with limits and offsets."""
184
+ # Limit only
185
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", limit=3)
186
+ self.assertIn("LIMIT", sql.upper())
187
+
188
+ # Limit with offset
189
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", limit=3, offset=2)
190
+ self.assertIn("LIMIT", sql.upper())
191
+ self.assertIn("OFFSET", sql.upper())
192
+
193
+ def test_insert_operations(self, tx):
194
+ """Test INSERT operations."""
195
+ # Basic insert
196
+ data = {"name": "Test User", "age": 25, "email": "test@example.com"}
197
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
198
+ self.assertIn("INSERT", sql.upper())
199
+ self.assertIn("VALUES", sql.upper())
200
+
201
+ # Insert with None values
202
+ data = {"name": "Test User 2", "age": None, "email": "test2@example.com"}
203
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
204
+ self.assertIn("NULL", sql.upper())
205
+
206
+ def test_update_operations(self, tx):
207
+ """Test UPDATE operations."""
208
+ # Basic update
209
+ data = {"name": "Updated User"}
210
+ where = {"sys_id": 1}
211
+ sql, vals = tx.engine.sql.update(tx, table="sql_test_basic", data=data, where=where)
212
+ self.assertIn("UPDATE", sql.upper())
213
+ self.assertIn("SET", sql.upper())
214
+ self.assertIn("WHERE", sql.upper())
215
+
216
+ # Update multiple columns
217
+ data = {"name": "Updated User", "age": 99}
218
+ where = {"sys_id": 1}
219
+ sql, vals = tx.engine.sql.update(tx, table="sql_test_basic", data=data, where=where)
220
+ set_count = sql.upper().count("SET")
221
+ self.assertEqual(set_count, 1)
222
+ self.assertIn(",", sql)
223
+
224
+ def test_delete_operations(self, tx):
225
+ """Test DELETE operations."""
226
+ # Basic delete
227
+ where = {"sys_id": 1}
228
+ sql, vals = tx.engine.sql.delete(tx, table="sql_test_basic", where=where)
229
+ self.assertIn("DELETE", sql.upper())
230
+ self.assertIn("FROM", sql.upper())
231
+ self.assertIn("WHERE", sql.upper())
232
+
233
+ # Delete with multiple conditions
234
+ where = {"active": False, "age": 25}
235
+ sql, vals = tx.engine.sql.delete(tx, table="sql_test_basic", where=where)
236
+ self.assertIn("WHERE", sql.upper())
237
+ self.assertIn("AND", sql.upper())
238
+
239
+ def test_reserved_words_handling(self, tx):
240
+ """Test handling of reserved words as column names."""
241
+ # Select with reserved word columns
242
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_special")
243
+ self.assertIn("sql_test_special", sql)
244
+
245
+ # Insert with reserved words
246
+ data = {"order": "third", "user": "testuser", "select": "none"}
247
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_special", data=data)
248
+ self.assertIn("INSERT", sql.upper())
249
+
250
+ # Update with reserved words
251
+ data = {"order": "updated"}
252
+ where = {"user": "testuser"}
253
+ sql, vals = tx.engine.sql.update(tx, table="sql_test_special", data=data, where=where)
254
+ self.assertIn("UPDATE", sql.upper())
255
+
256
+ def test_special_characters_in_columns(self, tx):
257
+ """Test handling of special characters in column names."""
258
+ # Test columns with spaces and dashes
259
+ data = {"column_with_spaces": "space test", "column-with-dashes": "dash test"}
260
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_special", data=data)
261
+ self.assertIn("INSERT", sql.upper())
262
+
263
+ def test_join_operations(self, tx):
264
+ """Test JOIN operations."""
265
+ # Inner join
266
+ sql, vals = tx.engine.sql.select(
267
+ tx,
268
+ table="sql_test_child",
269
+ join="sql_test_parent",
270
+ on="sql_test_child.parent_id = sql_test_parent.sys_id"
271
+ )
272
+ self.assertIn("JOIN", sql.upper())
273
+ self.assertIn("ON", sql.upper())
274
+
275
+ def test_aggregate_functions(self, tx):
276
+ """Test aggregate function support."""
277
+ # Count
278
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", columns=["COUNT(*)"])
279
+ self.assertIn("COUNT", sql.upper())
280
+
281
+ # Aggregate with grouping
282
+ sql, vals = tx.engine.sql.select(
283
+ tx,
284
+ table="sql_test_basic",
285
+ columns=["active", "COUNT(*)"],
286
+ groupby="active"
287
+ )
288
+ self.assertIn("GROUP BY", sql.upper())
289
+
290
+ def test_create_table_operations(self, tx):
291
+ """Test CREATE TABLE operations."""
292
+ columns = {
293
+ "test_name": str,
294
+ "test_age": int,
295
+ "test_active": bool,
296
+ }
297
+ sql, vals = tx.engine.sql.create_table(tx, table="test_create_table", columns=columns)
298
+ self.assertIn("CREATE TABLE", sql.upper())
299
+ self.assertIn("test_create_table", sql)
300
+
301
+ def test_drop_table_operations(self, tx):
302
+ """Test DROP TABLE operations."""
303
+ sql, vals = tx.engine.sql.drop_table(tx, table="test_drop_table")
304
+ self.assertIn("DROP TABLE", sql.upper())
305
+ self.assertIn("test_drop_table", sql)
306
+
307
+ def test_alter_table_operations(self, tx):
308
+ """Test ALTER TABLE operations."""
309
+ # Add column
310
+ sql, vals = tx.engine.sql.add_column(tx, table="sql_test_basic", column="new_column", datatype=str)
311
+ self.assertIn("ALTER TABLE", sql.upper())
312
+ self.assertIn("ADD COLUMN", sql.upper())
313
+
314
+ def test_index_operations(self, tx):
315
+ """Test INDEX operations."""
316
+ # Create index
317
+ sql, vals = tx.engine.sql.create_index(tx, table="sql_test_basic", column="name")
318
+ self.assertIn("CREATE INDEX", sql.upper())
319
+
320
+ def test_foreign_key_operations(self, tx):
321
+ """Test FOREIGN KEY operations."""
322
+ sql, vals = tx.engine.sql.create_foreign_key(
323
+ tx,
324
+ table="sql_test_child",
325
+ column="parent_id",
326
+ reference_table="sql_test_parent",
327
+ reference_column="sys_id"
328
+ )
329
+ self.assertIn("FOREIGN KEY", sql.upper())
330
+ self.assertIn("REFERENCES", sql.upper())
331
+
332
+ def test_transaction_operations(self, tx):
333
+ """Test transaction-related SQL."""
334
+ # Begin transaction
335
+ sql, vals = tx.engine.sql.begin_transaction()
336
+ self.assertIn("BEGIN", sql.upper())
337
+
338
+ # Commit transaction
339
+ sql, vals = tx.engine.sql.commit_transaction()
340
+ self.assertIn("COMMIT", sql.upper())
341
+
342
+ # Rollback transaction
343
+ sql, vals = tx.engine.sql.rollback_transaction()
344
+ self.assertIn("ROLLBACK", sql.upper())
345
+
346
+ def test_error_handling_invalid_table(self, tx):
347
+ """Test error handling for invalid table names."""
348
+ with self.assertRaises((DbTableMissingError, Exception)):
349
+ sql, vals = tx.engine.sql.select(tx, table="nonexistent_table")
350
+ tx.execute(sql, vals)
351
+
352
+ def test_error_handling_invalid_column(self, tx):
353
+ """Test error handling for invalid column names."""
354
+ with self.assertRaises((DbColumnMissingError, Exception)):
355
+ where = {"nonexistent_column": "value"}
356
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
357
+ tx.execute(sql, vals)
358
+
359
+ def test_error_handling_syntax_errors(self, tx):
360
+ """Test handling of SQL syntax errors."""
361
+ # This should be handled gracefully by the SQL builder
362
+ with self.assertRaises((DbSyntaxError, Exception)):
363
+ # Try to create invalid SQL
364
+ tx.execute("INVALID SQL STATEMENT")
365
+
366
+ def test_sql_injection_prevention(self, tx):
367
+ """Test SQL injection prevention."""
368
+ # Test with malicious input
369
+ malicious_input = "'; DROP TABLE sql_test_basic; --"
370
+ where = {"name": malicious_input}
371
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
372
+
373
+ # The input should be parameterized, not embedded directly
374
+ self.assertNotIn("DROP TABLE", sql.upper())
375
+ self.assertIn("?", sql) or self.assertIn("%s", sql) or self.assertIn("$", sql)
376
+
377
+ def test_performance_large_dataset(self, tx):
378
+ """Test performance with large datasets."""
379
+ # Test selecting from large table
380
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_large", limit=10)
381
+ result = tx.execute(sql, vals)
382
+ self.assertIsNotNone(result)
383
+
384
+ # Test with complex where clause on large table
385
+ where = {"index_col__gte": 50, "index_col__lt": 60}
386
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_large", where=where)
387
+ result = tx.execute(sql, vals)
388
+ self.assertIsNotNone(result)
389
+
390
+ def test_concurrent_operations(self, tx):
391
+ """Test concurrent operation support."""
392
+ # Test that SQL generation is thread-safe
393
+ import threading
394
+ import time
395
+
396
+ results = []
397
+ errors = []
398
+
399
+ def worker():
400
+ try:
401
+ for i in range(10):
402
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where={"age__gt": i})
403
+ results.append((sql, vals))
404
+ time.sleep(0.001) # Small delay to encourage race conditions
405
+ except Exception as e:
406
+ errors.append(e)
407
+
408
+ threads = [threading.Thread(target=worker) for _ in range(3)]
409
+ for thread in threads:
410
+ thread.start()
411
+ for thread in threads:
412
+ thread.join()
413
+
414
+ # Should have completed without errors
415
+ self.assertEqual(len(errors), 0)
416
+ self.assertGreater(len(results), 0)
417
+
418
+ def test_edge_cases_empty_data(self, tx):
419
+ """Test edge cases with empty data."""
420
+ # Empty where clause
421
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where={})
422
+ self.assertIn("SELECT", sql.upper())
423
+
424
+ # Empty data for insert (should handle gracefully)
425
+ with self.assertRaises(Exception):
426
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data={})
427
+
428
+ def test_edge_cases_null_values(self, tx):
429
+ """Test edge cases with NULL values."""
430
+ # Insert with None values
431
+ data = {"name": None, "age": None, "email": None}
432
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
433
+ self.assertIn("NULL", sql.upper() if "NULL" in sql.upper() else str(vals))
434
+
435
+ # Where clause with None
436
+ where = {"name": None}
437
+ sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
438
+ self.assertIn("IS NULL", sql.upper())
439
+
440
+ def test_edge_cases_unicode_data(self, tx):
441
+ """Test edge cases with Unicode data."""
442
+ # Unicode strings
443
+ data = {"name": "José María", "email": "josé@español.com"}
444
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
445
+ self.assertIn("INSERT", sql.upper())
446
+
447
+ # Emoji and special Unicode
448
+ data = {"name": "User 🚀", "email": "test@example.com"}
449
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
450
+ self.assertIn("INSERT", sql.upper())
451
+
452
+ def test_data_type_handling(self, tx):
453
+ """Test proper handling of different data types."""
454
+ # Boolean values
455
+ data = {"name": "Test", "active": True}
456
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
457
+ self.assertIn("INSERT", sql.upper())
458
+
459
+ # Float values
460
+ data = {"name": "Test", "score": 95.67}
461
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
462
+ self.assertIn("INSERT", sql.upper())
463
+
464
+ # Large integers
465
+ data = {"name": "Test", "age": 999999999}
466
+ sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
467
+ self.assertIn("INSERT", sql.upper())
468
+
469
+
470
+ if __name__ == "__main__":
471
+ unittest.main()