velocity-python 0.0.109__py3-none-any.whl → 0.0.155__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.
Files changed (120) hide show
  1. velocity/__init__.py +3 -1
  2. velocity/app/orders.py +3 -4
  3. velocity/app/tests/__init__.py +1 -0
  4. velocity/app/tests/test_email_processing.py +112 -0
  5. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  6. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  7. velocity/aws/__init__.py +3 -0
  8. velocity/aws/amplify.py +10 -6
  9. velocity/aws/handlers/__init__.py +2 -0
  10. velocity/aws/handlers/base_handler.py +248 -0
  11. velocity/aws/handlers/context.py +167 -2
  12. velocity/aws/handlers/exceptions.py +16 -0
  13. velocity/aws/handlers/lambda_handler.py +24 -85
  14. velocity/aws/handlers/mixins/__init__.py +16 -0
  15. velocity/aws/handlers/mixins/activity_tracker.py +181 -0
  16. velocity/aws/handlers/mixins/aws_session_mixin.py +192 -0
  17. velocity/aws/handlers/mixins/error_handler.py +192 -0
  18. velocity/aws/handlers/mixins/legacy_mixin.py +53 -0
  19. velocity/aws/handlers/mixins/standard_mixin.py +73 -0
  20. velocity/aws/handlers/response.py +1 -1
  21. velocity/aws/handlers/sqs_handler.py +28 -143
  22. velocity/aws/tests/__init__.py +1 -0
  23. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  24. velocity/aws/tests/test_response.py +163 -0
  25. velocity/db/__init__.py +16 -4
  26. velocity/db/core/decorators.py +20 -4
  27. velocity/db/core/engine.py +185 -839
  28. velocity/db/core/result.py +30 -24
  29. velocity/db/core/row.py +15 -3
  30. velocity/db/core/table.py +279 -40
  31. velocity/db/core/transaction.py +19 -11
  32. velocity/db/exceptions.py +42 -18
  33. velocity/db/servers/base/__init__.py +9 -0
  34. velocity/db/servers/base/initializer.py +70 -0
  35. velocity/db/servers/base/operators.py +98 -0
  36. velocity/db/servers/base/sql.py +503 -0
  37. velocity/db/servers/base/types.py +135 -0
  38. velocity/db/servers/mysql/__init__.py +73 -0
  39. velocity/db/servers/mysql/operators.py +54 -0
  40. velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
  41. velocity/db/servers/mysql/sql.py +718 -0
  42. velocity/db/servers/mysql/types.py +107 -0
  43. velocity/db/servers/postgres/__init__.py +59 -11
  44. velocity/db/servers/postgres/operators.py +34 -0
  45. velocity/db/servers/postgres/sql.py +474 -120
  46. velocity/db/servers/postgres/types.py +88 -2
  47. velocity/db/servers/sqlite/__init__.py +61 -0
  48. velocity/db/servers/sqlite/operators.py +52 -0
  49. velocity/db/servers/sqlite/reserved.py +20 -0
  50. velocity/db/servers/sqlite/sql.py +677 -0
  51. velocity/db/servers/sqlite/types.py +92 -0
  52. velocity/db/servers/sqlserver/__init__.py +73 -0
  53. velocity/db/servers/sqlserver/operators.py +47 -0
  54. velocity/db/servers/sqlserver/reserved.py +32 -0
  55. velocity/db/servers/sqlserver/sql.py +805 -0
  56. velocity/db/servers/sqlserver/types.py +114 -0
  57. velocity/db/servers/tablehelper.py +117 -91
  58. velocity/db/tests/__init__.py +1 -0
  59. velocity/db/tests/common_db_test.py +0 -0
  60. velocity/db/tests/postgres/__init__.py +1 -0
  61. velocity/db/tests/postgres/common.py +49 -0
  62. velocity/db/tests/postgres/test_column.py +29 -0
  63. velocity/db/tests/postgres/test_connections.py +25 -0
  64. velocity/db/tests/postgres/test_database.py +21 -0
  65. velocity/db/tests/postgres/test_engine.py +205 -0
  66. velocity/db/tests/postgres/test_general_usage.py +88 -0
  67. velocity/db/tests/postgres/test_imports.py +8 -0
  68. velocity/db/tests/postgres/test_result.py +19 -0
  69. velocity/db/tests/postgres/test_row.py +137 -0
  70. velocity/db/tests/postgres/test_row_comprehensive.py +720 -0
  71. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  72. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  73. velocity/db/tests/postgres/test_sequence.py +34 -0
  74. velocity/db/tests/postgres/test_sql_comprehensive.py +462 -0
  75. velocity/db/tests/postgres/test_table.py +101 -0
  76. velocity/db/tests/postgres/test_table_comprehensive.py +646 -0
  77. velocity/db/tests/postgres/test_transaction.py +106 -0
  78. velocity/db/tests/sql/__init__.py +1 -0
  79. velocity/db/tests/sql/common.py +177 -0
  80. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  81. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  82. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  83. velocity/db/tests/test_db_utils.py +221 -0
  84. velocity/db/tests/test_postgres.py +448 -0
  85. velocity/db/tests/test_postgres_unchanged.py +81 -0
  86. velocity/db/tests/test_process_error_robustness.py +292 -0
  87. velocity/db/tests/test_result_caching.py +279 -0
  88. velocity/db/tests/test_result_sql_aware.py +117 -0
  89. velocity/db/tests/test_row_get_missing_column.py +72 -0
  90. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  91. velocity/db/tests/test_schema_locking_simple.py +97 -0
  92. velocity/db/tests/test_sql_builder.py +165 -0
  93. velocity/db/tests/test_tablehelper.py +486 -0
  94. velocity/db/utils.py +62 -47
  95. velocity/misc/conv/__init__.py +2 -0
  96. velocity/misc/conv/iconv.py +5 -4
  97. velocity/misc/export.py +1 -4
  98. velocity/misc/merge.py +1 -1
  99. velocity/misc/tests/__init__.py +1 -0
  100. velocity/misc/tests/test_db.py +90 -0
  101. velocity/misc/tests/test_fix.py +78 -0
  102. velocity/misc/tests/test_format.py +64 -0
  103. velocity/misc/tests/test_iconv.py +203 -0
  104. velocity/misc/tests/test_merge.py +82 -0
  105. velocity/misc/tests/test_oconv.py +144 -0
  106. velocity/misc/tests/test_original_error.py +52 -0
  107. velocity/misc/tests/test_timer.py +74 -0
  108. velocity/misc/tools.py +0 -1
  109. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/METADATA +2 -2
  110. velocity_python-0.0.155.dist-info/RECORD +129 -0
  111. velocity/db/core/exceptions.py +0 -70
  112. velocity/db/servers/mysql.py +0 -641
  113. velocity/db/servers/sqlite.py +0 -968
  114. velocity/db/servers/sqlite_reserved.py +0 -208
  115. velocity/db/servers/sqlserver.py +0 -921
  116. velocity/db/servers/sqlserver_reserved.py +0 -314
  117. velocity_python-0.0.109.dist-info/RECORD +0 -56
  118. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/WHEEL +0 -0
  119. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/licenses/LICENSE +0 -0
  120. {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,720 @@
1
+ import unittest
2
+ import threading
3
+ import time
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from velocity.db.exceptions import DbColumnMissingError, DbDuplicateKeyError
6
+ from velocity.db.core.row import Row
7
+ from .common import CommonPostgresTest, engine, test_db
8
+
9
+
10
+ @engine.transaction
11
+ class TestRowComprehensive(CommonPostgresTest):
12
+ """Comprehensive tests for Row class including edge cases, race conditions, and error recovery."""
13
+
14
+ @classmethod
15
+ def create_test_tables(cls, tx):
16
+ """Create comprehensive test tables for Row testing."""
17
+ # Basic table for row operations
18
+ tx.table("row_test_basic").create(
19
+ columns={
20
+ "name": str,
21
+ "age": int,
22
+ "email": str,
23
+ "active": bool,
24
+ "score": float,
25
+ "metadata": str,
26
+ "created_at": str,
27
+ }
28
+ )
29
+
30
+ # Table with complex data types
31
+ tx.table("row_test_complex").create(
32
+ columns={
33
+ "title": str,
34
+ "description": str,
35
+ "tags": str, # JSON-like string
36
+ "priority": int,
37
+ "deadline": str,
38
+ "completed": bool,
39
+ "progress": float,
40
+ }
41
+ )
42
+
43
+ # Table for constraint testing
44
+ tx.table("row_test_constraints").create(
45
+ columns={
46
+ "username": str,
47
+ "email": str,
48
+ "phone": str,
49
+ "status": str,
50
+ }
51
+ )
52
+
53
+ # Create unique constraint
54
+ try:
55
+ tx.table("row_test_constraints").create_index("username", unique=True)
56
+ tx.table("row_test_constraints").create_index("email", unique=True)
57
+ except:
58
+ pass
59
+
60
+ # Parent-child tables for relationship testing
61
+ tx.table("row_test_parent").create(
62
+ columns={
63
+ "parent_name": str,
64
+ "category": str,
65
+ "priority": int,
66
+ }
67
+ )
68
+
69
+ tx.table("row_test_child").create(
70
+ columns={
71
+ "parent_id": int,
72
+ "child_name": str,
73
+ "value": float,
74
+ "notes": str,
75
+ }
76
+ )
77
+
78
+ tx.table("row_test_child").create_foreign_key("parent_id", "row_test_parent", "sys_id")
79
+
80
+ # Table for concurrent testing
81
+ tx.table("row_test_concurrent").create(
82
+ columns={
83
+ "counter": int,
84
+ "worker_id": str,
85
+ "last_updated": str,
86
+ "version": int,
87
+ }
88
+ )
89
+
90
+ # Insert initial test data
91
+ cls.insert_test_data(tx)
92
+
93
+ @classmethod
94
+ def insert_test_data(cls, tx):
95
+ """Insert comprehensive test data."""
96
+ # Basic table data
97
+ basic_data = [
98
+ {
99
+ "name": "Alice Johnson",
100
+ "age": 30,
101
+ "email": "alice@test.com",
102
+ "active": True,
103
+ "score": 95.5,
104
+ "metadata": '{"role": "admin", "department": "IT"}',
105
+ "created_at": "2023-01-01T10:00:00"
106
+ },
107
+ {
108
+ "name": "Bob Smith",
109
+ "age": 25,
110
+ "email": "bob@test.com",
111
+ "active": False,
112
+ "score": 87.2,
113
+ "metadata": '{"role": "user", "department": "Sales"}',
114
+ "created_at": "2023-01-02T11:00:00"
115
+ },
116
+ {
117
+ "name": "Charlie Brown",
118
+ "age": 35,
119
+ "email": "charlie@test.com",
120
+ "active": True,
121
+ "score": 92.8,
122
+ "metadata": '{"role": "manager", "department": "HR"}',
123
+ "created_at": "2023-01-03T12:00:00"
124
+ },
125
+ ]
126
+
127
+ for data in basic_data:
128
+ tx.table("row_test_basic").insert(data)
129
+
130
+ # Complex table data
131
+ complex_data = [
132
+ {
133
+ "title": "Project Alpha",
134
+ "description": "Main development project",
135
+ "tags": '["development", "priority", "Q1"]',
136
+ "priority": 1,
137
+ "deadline": "2023-03-31",
138
+ "completed": False,
139
+ "progress": 75.5
140
+ },
141
+ {
142
+ "title": "Project Beta",
143
+ "description": "Testing and QA project",
144
+ "tags": '["testing", "qa", "Q2"]',
145
+ "priority": 2,
146
+ "deadline": "2023-06-30",
147
+ "completed": True,
148
+ "progress": 100.0
149
+ },
150
+ ]
151
+
152
+ for data in complex_data:
153
+ tx.table("row_test_complex").insert(data)
154
+
155
+ # Constraints table data
156
+ constraint_data = [
157
+ {"username": "admin", "email": "admin@test.com", "phone": "555-0001", "status": "active"},
158
+ {"username": "user1", "email": "user1@test.com", "phone": "555-0002", "status": "pending"},
159
+ {"username": "user2", "email": "user2@test.com", "phone": "555-0003", "status": "suspended"},
160
+ ]
161
+
162
+ for data in constraint_data:
163
+ tx.table("row_test_constraints").insert(data)
164
+
165
+ # Parent-child data
166
+ parent_data = [
167
+ {"parent_name": "Department A", "category": "Engineering", "priority": 1},
168
+ {"parent_name": "Department B", "category": "Marketing", "priority": 2},
169
+ ]
170
+
171
+ parent_ids = []
172
+ for data in parent_data:
173
+ row = tx.table("row_test_parent").new(data)
174
+ parent_ids.append(row["sys_id"])
175
+
176
+ child_data = [
177
+ {"parent_id": parent_ids[0], "child_name": "Team 1", "value": 100.0, "notes": "Primary team"},
178
+ {"parent_id": parent_ids[0], "child_name": "Team 2", "value": 85.5, "notes": "Secondary team"},
179
+ {"parent_id": parent_ids[1], "child_name": "Team 3", "value": 92.3, "notes": "Marketing team"},
180
+ ]
181
+
182
+ for data in child_data:
183
+ tx.table("row_test_child").insert(data)
184
+
185
+ # Concurrent testing data
186
+ for i in range(10):
187
+ tx.table("row_test_concurrent").insert({
188
+ "counter": i,
189
+ "worker_id": "initial",
190
+ "last_updated": "2023-01-01T00:00:00",
191
+ "version": 1
192
+ })
193
+
194
+ def test_row_initialization(self, tx):
195
+ """Test row initialization with different key types."""
196
+ table = tx.table("row_test_basic")
197
+
198
+ # Test initialization with sys_id
199
+ first_row_data = table.select().one()
200
+ sys_id = first_row_data["sys_id"]
201
+
202
+ row_by_id = table.row(sys_id)
203
+ self.assertIsInstance(row_by_id, Row)
204
+ self.assertEqual(row_by_id["sys_id"], sys_id)
205
+
206
+ # Test initialization with dictionary key
207
+ row_by_dict = table.row({"name": "Alice Johnson"})
208
+ self.assertIsInstance(row_by_dict, Row)
209
+ self.assertEqual(row_by_dict["name"], "Alice Johnson")
210
+
211
+ # Test initialization with complex dictionary key
212
+ row_by_complex = table.row({"name": "Alice Johnson", "email": "alice@test.com"})
213
+ self.assertIsInstance(row_by_complex, Row)
214
+ self.assertEqual(row_by_complex["name"], "Alice Johnson")
215
+
216
+ def test_row_representation(self, tx):
217
+ """Test row string and representation methods."""
218
+ table = tx.table("row_test_basic")
219
+ row = table.select().one()
220
+
221
+ # Test __repr__
222
+ repr_str = repr(row)
223
+ self.assertIsInstance(repr_str, str)
224
+ self.assertIn("'name':", repr_str)
225
+
226
+ # Test __str__
227
+ str_repr = str(row)
228
+ self.assertIsInstance(str_repr, str)
229
+ self.assertIn("name", str_repr)
230
+
231
+ def test_row_length(self, tx):
232
+ """Test row length operations."""
233
+ table = tx.table("row_test_basic")
234
+ row = table.select().one()
235
+
236
+ # Test __len__ - should return 1 since it represents one row
237
+ length = len(row)
238
+ self.assertEqual(length, 1)
239
+
240
+ def test_row_item_access(self, tx):
241
+ """Test row item access operations."""
242
+ table = tx.table("row_test_basic")
243
+ row = table.select().one()
244
+
245
+ # Test __getitem__
246
+ name = row["name"]
247
+ self.assertIsInstance(name, str)
248
+
249
+ age = row["age"]
250
+ self.assertIsInstance(age, int)
251
+
252
+ # Test accessing sys_ columns
253
+ sys_id = row["sys_id"]
254
+ self.assertIsNotNone(sys_id)
255
+
256
+ # Test accessing non-existent column
257
+ with self.assertRaises((DbColumnMissingError, KeyError)):
258
+ _ = row["non_existent_column"]
259
+
260
+ def test_row_item_assignment(self, tx):
261
+ """Test row item assignment operations."""
262
+ table = tx.table("row_test_basic")
263
+ row = table.select().one()
264
+ original_name = row["name"]
265
+
266
+ # Test __setitem__
267
+ row["name"] = "Updated Name"
268
+ self.assertEqual(row["name"], "Updated Name")
269
+
270
+ # Test setting new column value
271
+ row["new_field"] = "new_value"
272
+ self.assertEqual(row["new_field"], "new_value")
273
+
274
+ # Test setting None value
275
+ row["metadata"] = None
276
+ self.assertIsNone(row["metadata"])
277
+
278
+ # Test that primary key cannot be updated
279
+ with self.assertRaises(Exception):
280
+ row["sys_id"] = 99999
281
+
282
+ def test_row_item_deletion(self, tx):
283
+ """Test row item deletion operations."""
284
+ table = tx.table("row_test_basic")
285
+ row = table.select().one()
286
+
287
+ # Test __delitem__
288
+ row["metadata"] = "test_data"
289
+ self.assertEqual(row["metadata"], "test_data")
290
+
291
+ del row["metadata"]
292
+ self.assertIsNone(row["metadata"])
293
+
294
+ # Test deleting non-existent field
295
+ del row["non_existent_field"] # Should not raise error
296
+
297
+ # Test that primary key cannot be deleted
298
+ with self.assertRaises(Exception):
299
+ del row["sys_id"]
300
+
301
+ def test_row_contains(self, tx):
302
+ """Test row membership testing."""
303
+ table = tx.table("row_test_basic")
304
+ row = table.select().one()
305
+
306
+ # Test __contains__
307
+ self.assertIn("name", row)
308
+ self.assertIn("age", row)
309
+ self.assertIn("sys_id", row)
310
+ self.assertNotIn("non_existent_column", row)
311
+
312
+ # Test case insensitivity
313
+ self.assertIn("NAME", row) # Should be case insensitive
314
+ self.assertIn("Age", row)
315
+
316
+ def test_row_clear(self, tx):
317
+ """Test row clear operation (delete from database)."""
318
+ table = tx.table("row_test_basic")
319
+
320
+ # Insert a test row to delete
321
+ test_row = table.new({"name": "To Delete", "age": 99, "email": "delete@test.com"})
322
+ test_id = test_row["sys_id"]
323
+
324
+ # Verify it exists
325
+ self.assertIsNotNone(table.find(test_id))
326
+
327
+ # Clear the row
328
+ test_row.clear()
329
+
330
+ # Verify it's deleted
331
+ self.assertIsNone(table.find(test_id))
332
+
333
+ def test_row_keys(self, tx):
334
+ """Test row keys operation."""
335
+ table = tx.table("row_test_basic")
336
+ row = table.select().one()
337
+
338
+ # Test keys()
339
+ keys = row.keys()
340
+ self.assertIsInstance(keys, list)
341
+ self.assertIn("name", keys)
342
+ self.assertIn("age", keys)
343
+ self.assertIn("sys_id", keys)
344
+ self.assertIn("name", keys)
345
+ self.assertIn("age", keys)
346
+ self.assertIn("sys_id", keys)
347
+ self.assertIn("sys_created_by", keys)
348
+ self.assertIn("sys_created_on", keys)
349
+
350
+ def test_row_values(self, tx):
351
+ """Test row values operation."""
352
+ table = tx.table("row_test_basic")
353
+ row = table.select().one()
354
+
355
+ # Test values() without arguments
356
+ values = row.values()
357
+ self.assertIsInstance(values, list)
358
+ self.assertGreater(len(values), 0)
359
+
360
+ # Test values() with specific columns
361
+ name_values = row.values("name")
362
+ self.assertIsInstance(name_values, list)
363
+ self.assertEqual(len(name_values), 1)
364
+ self.assertGreater(len(values), 0)
365
+
366
+ # Test values() with specific columns
367
+ specific_values = row.values("name", "age")
368
+ self.assertEqual(len(specific_values), 2)
369
+ self.assertEqual(specific_values[0], row["name"])
370
+ self.assertEqual(specific_values[1], row["age"])
371
+
372
+ def test_row_items(self, tx):
373
+ """Test row items operation."""
374
+ table = tx.table("row_test_basic")
375
+ row = table.select().one()
376
+
377
+ # Test items()
378
+ items = row.items()
379
+ self.assertIsInstance(items, list)
380
+ self.assertGreater(len(items), 0)
381
+
382
+ # Each item should be a tuple
383
+ for item in items:
384
+ self.assertIsInstance(item, tuple)
385
+ self.assertEqual(len(item), 2) # Verify items structure
386
+ for key, value in items:
387
+ self.assertIsInstance(key, str)
388
+ self.assertEqual(value, row[key])
389
+
390
+ def test_row_get_method(self, tx):
391
+ """Test row get method with default values."""
392
+ table = tx.table("row_test_basic")
393
+ row = table.select().one()
394
+
395
+ # Test get() with existing key
396
+ name = row.get("name")
397
+ self.assertEqual(name, row["name"])
398
+
399
+ # Test get() with non-existent key and default
400
+ default_value = row.get("non_existent", "default")
401
+ self.assertEqual(default_value, "default")
402
+
403
+ # Test get() with None value
404
+ row["metadata"] = None
405
+ metadata = row.get("metadata", "fallback")
406
+ self.assertEqual(metadata, "fallback")
407
+
408
+ # Test get() without default
409
+ non_existent = row.get("totally_missing")
410
+ self.assertIsNone(non_existent)
411
+
412
+ def test_row_dictionary_conversion(self, tx):
413
+ """Test converting row to dictionary."""
414
+ table = tx.table("row_test_basic")
415
+ row = table.select().one()
416
+
417
+ # Test to_dict() method if it exists
418
+ if hasattr(row, "to_dict"):
419
+ row_dict = row.to_dict()
420
+ self.assertIsInstance(row_dict, dict)
421
+ self.assertEqual(row_dict["name"], row["name"])
422
+
423
+ # Test dict() conversion
424
+ row_as_dict = dict(row)
425
+ self.assertIsInstance(row_as_dict, dict)
426
+ self.assertEqual(row_as_dict["name"], row["name"])
427
+
428
+ def test_row_update_operations(self, tx):
429
+ """Test various row update operations."""
430
+ table = tx.table("row_test_basic")
431
+ row = table.select().one()
432
+
433
+ # Test single field update
434
+ original_score = row["score"]
435
+ row["score"] = 99.9
436
+ self.assertEqual(row["score"], 99.9)
437
+
438
+ # Test multiple field updates
439
+ row["name"] = "Bulk Updated"
440
+ row["age"] = 999
441
+ row["active"] = not row["active"]
442
+
443
+ self.assertEqual(row["name"], "Bulk Updated")
444
+ self.assertEqual(row["age"], 999)
445
+
446
+ # Test updating with different data types
447
+ row["score"] = 100 # int to float column
448
+ self.assertEqual(row["score"], 100)
449
+
450
+ def test_row_constraint_violations(self, tx):
451
+ """Test row operations with constraint violations."""
452
+ table = tx.table("row_test_constraints")
453
+
454
+ # Get existing row
455
+ row = table.select().one()
456
+
457
+ # Test unique constraint violation
458
+ try:
459
+ # Try to update to existing username
460
+ row["username"] = "user1" # This should fail if user1 exists
461
+ except (DbDuplicateKeyError, Exception):
462
+ pass # Expected to fail
463
+
464
+ # Test that row is still usable after error
465
+ row["phone"] = "555-9999"
466
+ self.assertEqual(row["phone"], "555-9999")
467
+
468
+ def test_row_concurrent_updates(self, tx):
469
+ """Test row updates under concurrent access."""
470
+ table = tx.table("row_test_concurrent")
471
+
472
+ # Get a row for concurrent testing
473
+ test_row = table.select().one()
474
+ row_id = test_row["sys_id"]
475
+
476
+ def worker(worker_id, iterations=5):
477
+ """Worker function for concurrent row updates."""
478
+ results = []
479
+ for i in range(iterations):
480
+ try:
481
+ # Get fresh row instance
482
+ row = table.row(row_id)
483
+
484
+ # Update counter
485
+ current_counter = row["counter"] or 0
486
+ row["counter"] = current_counter + 1
487
+ row["worker_id"] = f"worker_{worker_id}"
488
+ row["last_updated"] = f"2023-01-01T{i:02d}:00:00"
489
+
490
+ results.append(("success", row["counter"]))
491
+ time.sleep(0.001) # Small delay
492
+
493
+ except Exception as e:
494
+ results.append(("error", str(e)))
495
+
496
+ return results
497
+
498
+ # Run concurrent workers
499
+ with ThreadPoolExecutor(max_workers=3) as executor:
500
+ futures = [executor.submit(worker, i, 3) for i in range(3)]
501
+ all_results = []
502
+ for future in as_completed(futures):
503
+ all_results.extend(future.result())
504
+
505
+ # Verify operations completed
506
+ self.assertGreater(len(all_results), 0)
507
+
508
+ # Check final state
509
+ final_row = table.row(row_id)
510
+ self.assertIsNotNone(final_row["counter"])
511
+
512
+ def test_row_relationship_operations(self, tx):
513
+ """Test row operations with foreign key relationships."""
514
+ parent_table = tx.table("row_test_parent")
515
+ child_table = tx.table("row_test_child")
516
+
517
+ # Get parent row
518
+ parent = parent_table.select().one()
519
+ parent_id = parent["sys_id"]
520
+
521
+ # Get related child rows
522
+ children = child_table.select(where={"parent_id": parent_id})
523
+ child_list = list(children)
524
+ self.assertGreater(len(child_list), 0)
525
+
526
+ # Update parent
527
+ parent["priority"] = 999
528
+ self.assertEqual(parent["priority"], 999)
529
+
530
+ # Update child
531
+ child = child_list[0]
532
+ child["value"] = 888.8
533
+ self.assertEqual(child["value"], 888.8)
534
+
535
+ # Test orphaning (if constraints allow)
536
+ try:
537
+ child["parent_id"] = 99999 # Non-existent parent
538
+ except Exception:
539
+ pass # Expected to fail due to foreign key constraint
540
+
541
+ def test_row_edge_cases_null_values(self, tx):
542
+ """Test row operations with NULL values."""
543
+ table = tx.table("row_test_basic")
544
+
545
+ # Insert row with NULL values
546
+ row = table.new({
547
+ "name": "NULL Test",
548
+ "age": None,
549
+ "email": None,
550
+ "active": None,
551
+ "score": None,
552
+ "metadata": None
553
+ })
554
+
555
+ # Test accessing NULL values
556
+ self.assertEqual(row["name"], "NULL Test")
557
+ self.assertIsNone(row["age"])
558
+ self.assertIsNone(row["email"])
559
+ self.assertIsNone(row["active"])
560
+ self.assertIsNone(row["score"])
561
+ self.assertIsNone(row["metadata"])
562
+
563
+ # Test updating NULL values
564
+ row["age"] = 25
565
+ self.assertEqual(row["age"], 25)
566
+
567
+ # Test setting back to NULL
568
+ row["age"] = None
569
+ self.assertIsNone(row["age"])
570
+
571
+ def test_row_edge_cases_unicode_data(self, tx):
572
+ """Test row operations with Unicode data."""
573
+ table = tx.table("row_test_basic")
574
+
575
+ # Insert row with Unicode data
576
+ unicode_data = {
577
+ "name": "José María 🚀",
578
+ "email": "josé@español.com",
579
+ "metadata": "Unicode test: αβγδε 中文 🎉"
580
+ }
581
+
582
+ row = table.new(unicode_data)
583
+
584
+ # Test accessing Unicode data
585
+ self.assertEqual(row["name"], "José María 🚀")
586
+ self.assertEqual(row["email"], "josé@español.com")
587
+ self.assertIn("αβγδε", row["metadata"])
588
+
589
+ # Test updating Unicode data
590
+ row["name"] = "Updated José 🎯"
591
+ self.assertEqual(row["name"], "Updated José 🎯")
592
+
593
+ def test_row_edge_cases_large_data(self, tx):
594
+ """Test row operations with large data."""
595
+ table = tx.table("row_test_basic")
596
+
597
+ # Insert row with large data
598
+ large_string = "x" * 10000 # 10KB string
599
+ row = table.new({
600
+ "name": "Large Data User",
601
+ "metadata": large_string
602
+ })
603
+
604
+ # Test accessing large data
605
+ self.assertEqual(row["name"], "Large Data User")
606
+ self.assertEqual(len(row["metadata"]), 10000)
607
+
608
+ # Test updating large data
609
+ even_larger = "y" * 20000 # 20KB string
610
+ row["metadata"] = even_larger
611
+ self.assertEqual(len(row["metadata"]), 20000)
612
+
613
+ def test_row_edge_cases_special_characters(self, tx):
614
+ """Test row operations with special characters."""
615
+ table = tx.table("row_test_basic")
616
+
617
+ # Special characters that might cause issues
618
+ special_data = {
619
+ "name": "Special'Char\"User",
620
+ "email": "test@example.com",
621
+ "metadata": "Data with 'quotes' and \"double quotes\" and \n newlines \t tabs"
622
+ }
623
+
624
+ row = table.new(special_data)
625
+
626
+ # Test accessing special character data
627
+ self.assertIn("'", row["name"])
628
+ self.assertIn('"', row["name"])
629
+ self.assertIn("'quotes'", row["metadata"])
630
+ self.assertIn('\n', row["metadata"])
631
+ self.assertIn('\t', row["metadata"])
632
+
633
+ def test_row_error_recovery(self, tx):
634
+ """Test row error recovery scenarios."""
635
+ table = tx.table("row_test_basic")
636
+ row = table.select().one()
637
+
638
+ # Test recovery from column access error
639
+ try:
640
+ _ = row["completely_invalid_column"]
641
+ except Exception:
642
+ # Should still be able to access valid columns
643
+ name = row["name"]
644
+ self.assertIsNotNone(name)
645
+
646
+ # Test recovery from update error
647
+ try:
648
+ row["invalid_column"] = "value"
649
+ except Exception:
650
+ # Should still be able to update valid columns
651
+ row["name"] = "Recovered User"
652
+ self.assertEqual(row["name"], "Recovered User")
653
+
654
+ def test_row_lock_operations(self, tx):
655
+ """Test row locking operations if supported."""
656
+ table = tx.table("row_test_basic")
657
+
658
+ # Test row with lock parameter
659
+ first_row_data = table.select().one()
660
+ sys_id = first_row_data["sys_id"]
661
+
662
+ try:
663
+ # Some databases support row locking
664
+ locked_row = table.row(sys_id, lock=True)
665
+ self.assertIsInstance(locked_row, Row)
666
+
667
+ # Test operations on locked row
668
+ locked_row["name"] = "Locked User"
669
+ self.assertEqual(locked_row["name"], "Locked User")
670
+
671
+ except Exception:
672
+ # Row locking might not be supported
673
+ pass
674
+
675
+ def test_row_key_column_operations(self, tx):
676
+ """Test row operations with different key column configurations."""
677
+ table = tx.table("row_test_basic")
678
+
679
+ # Test key_cols property
680
+ row = table.select().one()
681
+ if hasattr(row, "key_cols"):
682
+ key_cols = row.key_cols
683
+ self.assertIsInstance(key_cols, list)
684
+ self.assertIn("sys_id", key_cols)
685
+
686
+ def test_row_caching_behavior(self, tx):
687
+ """Test row caching and data consistency."""
688
+ table = tx.table("row_test_basic")
689
+ row1 = table.select().one()
690
+ row_id = row1["sys_id"]
691
+
692
+ # Get another instance of the same row
693
+ row2 = table.row(row_id)
694
+
695
+ # Update through first instance
696
+ row1["name"] = "Updated via row1"
697
+
698
+ # Check if second instance sees the change
699
+ # (behavior depends on caching implementation)
700
+ name_via_row2 = row2["name"]
701
+ # This test documents current behavior rather than asserting specific behavior
702
+
703
+ def test_row_data_type_coercion(self, tx):
704
+ """Test row data type coercion and validation."""
705
+ table = tx.table("row_test_basic")
706
+ row = table.select().one()
707
+
708
+ # Test type coercion
709
+ row["age"] = "30" # String to int
710
+ self.assertEqual(row["age"], 30)
711
+
712
+ row["score"] = "95.5" # String to float
713
+ self.assertEqual(row["score"], 95.5)
714
+
715
+ row["active"] = "true" # String to bool (if supported)
716
+ # Behavior may vary by implementation
717
+
718
+
719
+ if __name__ == "__main__":
720
+ unittest.main()