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.
- velocity/__init__.py +3 -1
- velocity/app/orders.py +3 -4
- velocity/app/tests/__init__.py +1 -0
- velocity/app/tests/test_email_processing.py +112 -0
- velocity/app/tests/test_payment_profile_sorting.py +191 -0
- velocity/app/tests/test_spreadsheet_functions.py +124 -0
- velocity/aws/__init__.py +3 -0
- velocity/aws/amplify.py +10 -6
- velocity/aws/handlers/__init__.py +2 -0
- velocity/aws/handlers/base_handler.py +248 -0
- velocity/aws/handlers/context.py +167 -2
- velocity/aws/handlers/exceptions.py +16 -0
- velocity/aws/handlers/lambda_handler.py +24 -85
- velocity/aws/handlers/mixins/__init__.py +16 -0
- velocity/aws/handlers/mixins/activity_tracker.py +181 -0
- velocity/aws/handlers/mixins/aws_session_mixin.py +192 -0
- velocity/aws/handlers/mixins/error_handler.py +192 -0
- velocity/aws/handlers/mixins/legacy_mixin.py +53 -0
- velocity/aws/handlers/mixins/standard_mixin.py +73 -0
- velocity/aws/handlers/response.py +1 -1
- velocity/aws/handlers/sqs_handler.py +28 -143
- velocity/aws/tests/__init__.py +1 -0
- velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
- velocity/aws/tests/test_response.py +163 -0
- velocity/db/__init__.py +16 -4
- velocity/db/core/decorators.py +20 -4
- velocity/db/core/engine.py +185 -839
- velocity/db/core/result.py +30 -24
- velocity/db/core/row.py +15 -3
- velocity/db/core/table.py +279 -40
- velocity/db/core/transaction.py +19 -11
- velocity/db/exceptions.py +42 -18
- velocity/db/servers/base/__init__.py +9 -0
- velocity/db/servers/base/initializer.py +70 -0
- velocity/db/servers/base/operators.py +98 -0
- velocity/db/servers/base/sql.py +503 -0
- velocity/db/servers/base/types.py +135 -0
- velocity/db/servers/mysql/__init__.py +73 -0
- velocity/db/servers/mysql/operators.py +54 -0
- velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
- velocity/db/servers/mysql/sql.py +718 -0
- velocity/db/servers/mysql/types.py +107 -0
- velocity/db/servers/postgres/__init__.py +59 -11
- velocity/db/servers/postgres/operators.py +34 -0
- velocity/db/servers/postgres/sql.py +474 -120
- velocity/db/servers/postgres/types.py +88 -2
- velocity/db/servers/sqlite/__init__.py +61 -0
- velocity/db/servers/sqlite/operators.py +52 -0
- velocity/db/servers/sqlite/reserved.py +20 -0
- velocity/db/servers/sqlite/sql.py +677 -0
- velocity/db/servers/sqlite/types.py +92 -0
- velocity/db/servers/sqlserver/__init__.py +73 -0
- velocity/db/servers/sqlserver/operators.py +47 -0
- velocity/db/servers/sqlserver/reserved.py +32 -0
- velocity/db/servers/sqlserver/sql.py +805 -0
- velocity/db/servers/sqlserver/types.py +114 -0
- velocity/db/servers/tablehelper.py +117 -91
- velocity/db/tests/__init__.py +1 -0
- velocity/db/tests/common_db_test.py +0 -0
- velocity/db/tests/postgres/__init__.py +1 -0
- velocity/db/tests/postgres/common.py +49 -0
- velocity/db/tests/postgres/test_column.py +29 -0
- velocity/db/tests/postgres/test_connections.py +25 -0
- velocity/db/tests/postgres/test_database.py +21 -0
- velocity/db/tests/postgres/test_engine.py +205 -0
- velocity/db/tests/postgres/test_general_usage.py +88 -0
- velocity/db/tests/postgres/test_imports.py +8 -0
- velocity/db/tests/postgres/test_result.py +19 -0
- velocity/db/tests/postgres/test_row.py +137 -0
- velocity/db/tests/postgres/test_row_comprehensive.py +720 -0
- velocity/db/tests/postgres/test_schema_locking.py +335 -0
- velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
- velocity/db/tests/postgres/test_sequence.py +34 -0
- velocity/db/tests/postgres/test_sql_comprehensive.py +462 -0
- velocity/db/tests/postgres/test_table.py +101 -0
- velocity/db/tests/postgres/test_table_comprehensive.py +646 -0
- velocity/db/tests/postgres/test_transaction.py +106 -0
- velocity/db/tests/sql/__init__.py +1 -0
- velocity/db/tests/sql/common.py +177 -0
- velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
- velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
- velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
- velocity/db/tests/test_db_utils.py +221 -0
- velocity/db/tests/test_postgres.py +448 -0
- velocity/db/tests/test_postgres_unchanged.py +81 -0
- velocity/db/tests/test_process_error_robustness.py +292 -0
- velocity/db/tests/test_result_caching.py +279 -0
- velocity/db/tests/test_result_sql_aware.py +117 -0
- velocity/db/tests/test_row_get_missing_column.py +72 -0
- velocity/db/tests/test_schema_locking_initializers.py +226 -0
- velocity/db/tests/test_schema_locking_simple.py +97 -0
- velocity/db/tests/test_sql_builder.py +165 -0
- velocity/db/tests/test_tablehelper.py +486 -0
- velocity/db/utils.py +62 -47
- velocity/misc/conv/__init__.py +2 -0
- velocity/misc/conv/iconv.py +5 -4
- velocity/misc/export.py +1 -4
- velocity/misc/merge.py +1 -1
- velocity/misc/tests/__init__.py +1 -0
- velocity/misc/tests/test_db.py +90 -0
- velocity/misc/tests/test_fix.py +78 -0
- velocity/misc/tests/test_format.py +64 -0
- velocity/misc/tests/test_iconv.py +203 -0
- velocity/misc/tests/test_merge.py +82 -0
- velocity/misc/tests/test_oconv.py +144 -0
- velocity/misc/tests/test_original_error.py +52 -0
- velocity/misc/tests/test_timer.py +74 -0
- velocity/misc/tools.py +0 -1
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/METADATA +2 -2
- velocity_python-0.0.155.dist-info/RECORD +129 -0
- velocity/db/core/exceptions.py +0 -70
- velocity/db/servers/mysql.py +0 -641
- velocity/db/servers/sqlite.py +0 -968
- velocity/db/servers/sqlite_reserved.py +0 -208
- velocity/db/servers/sqlserver.py +0 -921
- velocity/db/servers/sqlserver_reserved.py +0 -314
- velocity_python-0.0.109.dist-info/RECORD +0 -56
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/licenses/LICENSE +0 -0
- {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()
|