velocity-python 0.0.109__py3-none-any.whl → 0.0.161__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 +251 -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 +48 -13
- velocity/db/core/engine.py +187 -840
- velocity/db/core/result.py +33 -25
- velocity/db/core/row.py +15 -3
- velocity/db/core/table.py +493 -50
- velocity/db/core/transaction.py +28 -15
- 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 +270 -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 +129 -51
- 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.161.dist-info}/METADATA +2 -2
- velocity_python-0.0.161.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.161.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.161.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.161.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import psycopg2
|
|
3
|
+
from velocity.db.core.transaction import Transaction
|
|
4
|
+
from velocity.db.exceptions import DbQueryError, 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").new(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>": 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<=": 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": ["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%": "example"}
|
|
161
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
|
|
162
|
+
self.assertIn("LIKE", 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 DESC")
|
|
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 DESC")
|
|
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", qty=3)
|
|
186
|
+
self.assertIn("LIMIT", sql.upper())
|
|
187
|
+
|
|
188
|
+
# Limit with offset
|
|
189
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", qty=3, start=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("sql_test_basic", 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
|
+
# Test select with complex where (simulating join condition)
|
|
266
|
+
where = {"parent_id": 1}
|
|
267
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_child", where=where)
|
|
268
|
+
self.assertIn("WHERE", sql.upper())
|
|
269
|
+
|
|
270
|
+
def test_aggregate_functions(self, tx):
|
|
271
|
+
"""Test aggregate function support."""
|
|
272
|
+
# Count
|
|
273
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", columns=["COUNT(*)"])
|
|
274
|
+
self.assertIn("COUNT", sql.upper())
|
|
275
|
+
|
|
276
|
+
# Aggregate with grouping
|
|
277
|
+
sql, vals = tx.engine.sql.select(
|
|
278
|
+
tx,
|
|
279
|
+
table="sql_test_basic",
|
|
280
|
+
columns=["active", "COUNT(*)"],
|
|
281
|
+
groupby="active"
|
|
282
|
+
)
|
|
283
|
+
self.assertIn("GROUP BY", sql.upper())
|
|
284
|
+
|
|
285
|
+
def test_create_table_operations(self, tx):
|
|
286
|
+
"""Test CREATE TABLE operations."""
|
|
287
|
+
columns = {
|
|
288
|
+
"test_name": str,
|
|
289
|
+
"test_age": int,
|
|
290
|
+
"test_active": bool,
|
|
291
|
+
}
|
|
292
|
+
sql, vals = tx.engine.sql.create_table("test_create_table", columns=columns)
|
|
293
|
+
self.assertIn("CREATE TABLE", sql.upper())
|
|
294
|
+
self.assertIn("test_create_table", sql)
|
|
295
|
+
|
|
296
|
+
def test_drop_table_operations(self, tx):
|
|
297
|
+
"""Test DROP TABLE operations."""
|
|
298
|
+
sql, vals = tx.engine.sql.drop_table("test_drop_table")
|
|
299
|
+
self.assertIn("DROP TABLE", sql.upper())
|
|
300
|
+
self.assertIn("test_drop_table", sql)
|
|
301
|
+
|
|
302
|
+
def test_alter_table_operations(self, tx):
|
|
303
|
+
"""Test ALTER TABLE operations."""
|
|
304
|
+
# Add column - not implemented in SQL class
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
def test_index_operations(self, tx):
|
|
308
|
+
"""Test INDEX operations."""
|
|
309
|
+
# Create index
|
|
310
|
+
sql, vals = tx.engine.sql.create_index(tx, table="sql_test_basic", columns=["name"])
|
|
311
|
+
self.assertIn("CREATE INDEX", sql.upper())
|
|
312
|
+
|
|
313
|
+
def test_foreign_key_operations(self, tx):
|
|
314
|
+
"""Test FOREIGN KEY operations."""
|
|
315
|
+
sql, vals = tx.engine.sql.create_foreign_key(
|
|
316
|
+
"sql_test_child",
|
|
317
|
+
columns="parent_id",
|
|
318
|
+
key_to_table="sql_test_parent",
|
|
319
|
+
key_to_columns="sys_id"
|
|
320
|
+
)
|
|
321
|
+
self.assertIn("FOREIGN KEY", sql.upper())
|
|
322
|
+
self.assertIn("REFERENCES", sql.upper())
|
|
323
|
+
|
|
324
|
+
def test_transaction_operations(self, tx):
|
|
325
|
+
"""Test transaction-related SQL."""
|
|
326
|
+
# Begin transaction - not implemented in SQL class
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
# Commit transaction
|
|
330
|
+
sql, vals = tx.engine.sql.commit_transaction()
|
|
331
|
+
self.assertIn("COMMIT", sql.upper())
|
|
332
|
+
|
|
333
|
+
# Rollback transaction
|
|
334
|
+
sql, vals = tx.engine.sql.rollback_transaction()
|
|
335
|
+
self.assertIn("ROLLBACK", sql.upper())
|
|
336
|
+
|
|
337
|
+
def test_error_handling_invalid_table(self, tx):
|
|
338
|
+
"""Test error handling for invalid table names."""
|
|
339
|
+
with self.assertRaises((DbTableMissingError, Exception)):
|
|
340
|
+
sql, vals = tx.engine.sql.select(tx, table="nonexistent_table")
|
|
341
|
+
tx.execute(sql, vals)
|
|
342
|
+
|
|
343
|
+
def test_error_handling_invalid_column(self, tx):
|
|
344
|
+
"""Test error handling for invalid column names."""
|
|
345
|
+
with self.assertRaises((DbColumnMissingError, Exception)):
|
|
346
|
+
where = {"nonexistent_column": "value"}
|
|
347
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
|
|
348
|
+
tx.execute(sql, vals)
|
|
349
|
+
|
|
350
|
+
def test_error_handling_syntax_errors(self, tx):
|
|
351
|
+
"""Test handling of SQL syntax errors."""
|
|
352
|
+
# This should be handled gracefully by the SQL builder
|
|
353
|
+
with self.assertRaises((DbQueryError, Exception)):
|
|
354
|
+
# Try to create invalid SQL
|
|
355
|
+
tx.execute("INVALID SQL STATEMENT")
|
|
356
|
+
|
|
357
|
+
def test_sql_injection_prevention(self, tx):
|
|
358
|
+
"""Test SQL injection prevention."""
|
|
359
|
+
# Test with malicious input
|
|
360
|
+
malicious_input = "'; DROP TABLE sql_test_basic; --"
|
|
361
|
+
where = {"name": malicious_input}
|
|
362
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
|
|
363
|
+
|
|
364
|
+
# The input should be parameterized, not embedded directly
|
|
365
|
+
self.assertNotIn("DROP TABLE", sql.upper())
|
|
366
|
+
self.assertIn("$", sql) # PostgreSQL uses $1, $2, etc. for parameters
|
|
367
|
+
|
|
368
|
+
def test_performance_large_dataset(self, tx):
|
|
369
|
+
"""Test performance with large datasets."""
|
|
370
|
+
# Test selecting from large table
|
|
371
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_large", qty=10)
|
|
372
|
+
result = tx.execute(sql, vals)
|
|
373
|
+
self.assertIsNotNone(result)
|
|
374
|
+
|
|
375
|
+
# Test with complex where clause on large table
|
|
376
|
+
where = {"index_col__gte": 50, "index_col__lt": 60}
|
|
377
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_large", where=where)
|
|
378
|
+
result = tx.execute(sql, vals)
|
|
379
|
+
self.assertIsNotNone(result)
|
|
380
|
+
|
|
381
|
+
def test_concurrent_operations(self, tx):
|
|
382
|
+
"""Test concurrent operation support."""
|
|
383
|
+
# Test that SQL generation is thread-safe
|
|
384
|
+
import threading
|
|
385
|
+
import time
|
|
386
|
+
|
|
387
|
+
results = []
|
|
388
|
+
errors = []
|
|
389
|
+
|
|
390
|
+
def worker():
|
|
391
|
+
try:
|
|
392
|
+
for i in range(10):
|
|
393
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where={"age__gt": i})
|
|
394
|
+
results.append((sql, vals))
|
|
395
|
+
time.sleep(0.001) # Small delay to encourage race conditions
|
|
396
|
+
except Exception as e:
|
|
397
|
+
errors.append(e)
|
|
398
|
+
|
|
399
|
+
threads = [threading.Thread(target=worker) for _ in range(3)]
|
|
400
|
+
for thread in threads:
|
|
401
|
+
thread.start()
|
|
402
|
+
for thread in threads:
|
|
403
|
+
thread.join()
|
|
404
|
+
|
|
405
|
+
# Should have completed without errors
|
|
406
|
+
self.assertEqual(len(errors), 0)
|
|
407
|
+
self.assertGreater(len(results), 0)
|
|
408
|
+
|
|
409
|
+
def test_edge_cases_empty_data(self, tx):
|
|
410
|
+
"""Test edge cases with empty data."""
|
|
411
|
+
# Empty where clause
|
|
412
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where={})
|
|
413
|
+
self.assertIn("SELECT", sql.upper())
|
|
414
|
+
|
|
415
|
+
# Empty data for insert (should handle gracefully)
|
|
416
|
+
with self.assertRaises(Exception):
|
|
417
|
+
sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data={})
|
|
418
|
+
|
|
419
|
+
def test_edge_cases_null_values(self, tx):
|
|
420
|
+
"""Test edge cases with NULL values."""
|
|
421
|
+
# Insert with None values
|
|
422
|
+
data = {"name": None, "age": None, "email": None}
|
|
423
|
+
sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
|
|
424
|
+
self.assertIn("NULL", sql.upper() if "NULL" in sql.upper() else str(vals))
|
|
425
|
+
|
|
426
|
+
# Where clause with None
|
|
427
|
+
where = {"name": None}
|
|
428
|
+
sql, vals = tx.engine.sql.select(tx, table="sql_test_basic", where=where)
|
|
429
|
+
self.assertIn("IS NULL", sql.upper())
|
|
430
|
+
|
|
431
|
+
def test_edge_cases_unicode_data(self, tx):
|
|
432
|
+
"""Test edge cases with Unicode data."""
|
|
433
|
+
# Unicode strings
|
|
434
|
+
data = {"name": "José María", "email": "josé@español.com"}
|
|
435
|
+
sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
|
|
436
|
+
self.assertIn("INSERT", sql.upper())
|
|
437
|
+
|
|
438
|
+
# Emoji and special Unicode
|
|
439
|
+
data = {"name": "User 🚀", "email": "test@example.com"}
|
|
440
|
+
sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
|
|
441
|
+
self.assertIn("INSERT", sql.upper())
|
|
442
|
+
|
|
443
|
+
def test_data_type_handling(self, tx):
|
|
444
|
+
"""Test proper handling of different data types."""
|
|
445
|
+
# Boolean values
|
|
446
|
+
data = {"name": "Test", "active": True}
|
|
447
|
+
sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
|
|
448
|
+
self.assertIn("INSERT", sql.upper())
|
|
449
|
+
|
|
450
|
+
# Float values
|
|
451
|
+
data = {"name": "Test", "score": 95.67}
|
|
452
|
+
sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
|
|
453
|
+
self.assertIn("INSERT", sql.upper())
|
|
454
|
+
|
|
455
|
+
# Large integers
|
|
456
|
+
data = {"name": "Test", "age": 999999999}
|
|
457
|
+
sql, vals = tx.engine.sql.insert(tx, table="sql_test_basic", data=data)
|
|
458
|
+
self.assertIn("INSERT", sql.upper())
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
if __name__ == "__main__":
|
|
462
|
+
unittest.main()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import unittest
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
from .common import CommonPostgresTest, engine, test_db
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@engine.transaction
|
|
9
|
+
class TestTable(CommonPostgresTest):
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def create_test_tables(cls, tx):
|
|
13
|
+
"""No special tables needed for table tests."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def test_init(self, tx):
|
|
17
|
+
t = tx.table("test_table")
|
|
18
|
+
assert t.exists() == False
|
|
19
|
+
assert tx.tables() == []
|
|
20
|
+
assert (
|
|
21
|
+
str(t)
|
|
22
|
+
== """Table: test_table
|
|
23
|
+
(table exists) False
|
|
24
|
+
Columns: 0
|
|
25
|
+
Rows: 0
|
|
26
|
+
"""
|
|
27
|
+
)
|
|
28
|
+
self.assertTrue(re.match(r"PostGreSQL\.transaction\(.*:test_db_postgres\)", str(tx)))
|
|
29
|
+
|
|
30
|
+
def test_rollback(self, tx):
|
|
31
|
+
t = tx.table("test_table")
|
|
32
|
+
for i in range(10):
|
|
33
|
+
t.insert(
|
|
34
|
+
{
|
|
35
|
+
"fname": "test",
|
|
36
|
+
"lname": "test",
|
|
37
|
+
"email": "test@test.com",
|
|
38
|
+
"age": 1,
|
|
39
|
+
"address": "test",
|
|
40
|
+
"city": "test",
|
|
41
|
+
"state": "test",
|
|
42
|
+
"zipcode": "test",
|
|
43
|
+
"country": "test",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
assert (
|
|
47
|
+
str(t)
|
|
48
|
+
== """Table: test_table
|
|
49
|
+
(table exists) True
|
|
50
|
+
Columns: 10
|
|
51
|
+
Rows: 10
|
|
52
|
+
"""
|
|
53
|
+
)
|
|
54
|
+
tx.rollback()
|
|
55
|
+
assert (
|
|
56
|
+
str(t)
|
|
57
|
+
== """Table: test_table
|
|
58
|
+
(table exists) False
|
|
59
|
+
Columns: 0
|
|
60
|
+
Rows: 0
|
|
61
|
+
"""
|
|
62
|
+
)
|
|
63
|
+
tx.table("test_table").drop()
|
|
64
|
+
|
|
65
|
+
def test_drop(self, tx):
|
|
66
|
+
t = tx.table("test_table")
|
|
67
|
+
for i in range(10):
|
|
68
|
+
t.insert(
|
|
69
|
+
{
|
|
70
|
+
"fname": "test",
|
|
71
|
+
"lname": "test",
|
|
72
|
+
"email": "test@test.com",
|
|
73
|
+
"age": 1,
|
|
74
|
+
"address": "test",
|
|
75
|
+
"city": "test",
|
|
76
|
+
"state": "test",
|
|
77
|
+
"zipcode": "test",
|
|
78
|
+
"country": "test",
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
assert (
|
|
82
|
+
str(t)
|
|
83
|
+
== """Table: test_table
|
|
84
|
+
(table exists) True
|
|
85
|
+
Columns: 10
|
|
86
|
+
Rows: 10
|
|
87
|
+
"""
|
|
88
|
+
)
|
|
89
|
+
tx.table("test_table").drop()
|
|
90
|
+
assert (
|
|
91
|
+
str(t)
|
|
92
|
+
== """Table: test_table
|
|
93
|
+
(table exists) False
|
|
94
|
+
Columns: 0
|
|
95
|
+
Rows: 0
|
|
96
|
+
"""
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
unittest.main()
|