velocity-python 0.0.105__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 -792
- velocity/db/core/result.py +36 -22
- velocity/db/core/row.py +15 -3
- velocity/db/core/table.py +283 -44
- 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.105.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.105.dist-info/RECORD +0 -56
- {velocity_python-0.0.105.dist-info → velocity_python-0.0.155.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.105.dist-info → velocity_python-0.0.155.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.105.dist-info → velocity_python-0.0.155.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from velocity.db.servers import postgres, tablehelper
|
|
3
|
+
from velocity.db.core.table import Query
|
|
4
|
+
from common import TestSQLModule, engine
|
|
5
|
+
|
|
6
|
+
DO_ALL = True
|
|
7
|
+
|
|
8
|
+
print(f"DO_ALL: {DO_ALL}")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@engine.transaction
|
|
12
|
+
class TestLocal(TestSQLModule):
|
|
13
|
+
|
|
14
|
+
def test_table_helper(self, tx):
|
|
15
|
+
if not DO_ALL:
|
|
16
|
+
return
|
|
17
|
+
th = tablehelper.TableHelper(tx, "fk_child")
|
|
18
|
+
|
|
19
|
+
expected_output = (
|
|
20
|
+
"column_name IN (SELECT COLUMN_NAME\nFROM TABLE_NAME\nWHERE COLUMN_NAME = 1)",
|
|
21
|
+
None,
|
|
22
|
+
)
|
|
23
|
+
t = th.make_predicate(
|
|
24
|
+
"column_name",
|
|
25
|
+
Query("select column_name from table_name where column_name = 1"),
|
|
26
|
+
)
|
|
27
|
+
self.assertEqual(t, expected_output)
|
|
28
|
+
|
|
29
|
+
expected_output = ("column_name = %s", "value")
|
|
30
|
+
t = th.make_predicate("column_name", "value")
|
|
31
|
+
self.assertEqual(t, expected_output)
|
|
32
|
+
|
|
33
|
+
expected_output = ("column_name > %s", "value")
|
|
34
|
+
t = th.make_predicate(">column_name", "value")
|
|
35
|
+
self.assertEqual(t, expected_output)
|
|
36
|
+
|
|
37
|
+
expected_output = ("column_name <> %s", "value")
|
|
38
|
+
t = th.make_predicate("<>column_name", "value")
|
|
39
|
+
self.assertEqual(t, expected_output)
|
|
40
|
+
|
|
41
|
+
expected_output = ("B.parent_name <> %s", "value")
|
|
42
|
+
t = th.make_predicate("<>parent_id>parent_name", "value")
|
|
43
|
+
self.assertEqual(t, expected_output)
|
|
44
|
+
|
|
45
|
+
expected_output = ('B."table" <> %s', "value")
|
|
46
|
+
t = th.make_predicate("<>parent_id>table", "value")
|
|
47
|
+
self.assertEqual(t, expected_output)
|
|
48
|
+
|
|
49
|
+
expected_output = ("B.parent_name IS NOT NULL", None)
|
|
50
|
+
t = th.make_predicate("<>parent_id>parent_name", None)
|
|
51
|
+
self.assertEqual(t, expected_output)
|
|
52
|
+
|
|
53
|
+
columns = [
|
|
54
|
+
("parent_id", "parent_id"),
|
|
55
|
+
("parent_name", "parent_name"),
|
|
56
|
+
("parent_id>parent_name", "parent_id_parent_name"),
|
|
57
|
+
("quijibo", "quijibo"),
|
|
58
|
+
("doojibo", "doojibo"),
|
|
59
|
+
("symmetric", '"symmetric"'),
|
|
60
|
+
("table", '"table"'),
|
|
61
|
+
("parent_id>is_valid", "parent_id_is_valid"),
|
|
62
|
+
("sum_info", "sum_info"),
|
|
63
|
+
("max_hours", "max_hours"),
|
|
64
|
+
("parent_id>num_things", "parent_id_num_things"),
|
|
65
|
+
("parent_id>symmetric", "parent_id_symmetric"),
|
|
66
|
+
("parent_id>table", "parent_id_table"),
|
|
67
|
+
]
|
|
68
|
+
for key, val in columns:
|
|
69
|
+
column = th.resolve_references(key, options={"alias_only": True})
|
|
70
|
+
self.assertEqual(column, val)
|
|
71
|
+
columns = [
|
|
72
|
+
("parent_id", "parent_id"),
|
|
73
|
+
("parent_name", "parent_name"),
|
|
74
|
+
("parent_id>parent_name", "B.parent_name"),
|
|
75
|
+
("quijibo", "quijibo"),
|
|
76
|
+
("doojibo", "doojibo"),
|
|
77
|
+
("symmetric", '"symmetric"'),
|
|
78
|
+
("table", '"table"'),
|
|
79
|
+
("parent_id>is_valid", "B.is_valid"),
|
|
80
|
+
("sum_info", "sum_info"),
|
|
81
|
+
("max_hours", "max_hours"),
|
|
82
|
+
("parent_id>num_things", "B.num_things"),
|
|
83
|
+
("parent_id>symmetric", 'B."symmetric"'),
|
|
84
|
+
("parent_id>table", 'B."table"'),
|
|
85
|
+
]
|
|
86
|
+
for key, val in columns:
|
|
87
|
+
column = th.resolve_references(key, options={"alias_table": True})
|
|
88
|
+
self.assertEqual(column, val)
|
|
89
|
+
|
|
90
|
+
columns = [
|
|
91
|
+
("parent_id", "parent_id"),
|
|
92
|
+
("parent_name", "parent_name"),
|
|
93
|
+
("parent_id>parent_name", "B.parent_name as parent_id_parent_name"),
|
|
94
|
+
("quijibo", "quijibo"),
|
|
95
|
+
("doojibo", "doojibo"),
|
|
96
|
+
("symmetric", '"symmetric"'),
|
|
97
|
+
("table", '"table"'),
|
|
98
|
+
("parent_id>is_valid", "B.is_valid as parent_id_is_valid"),
|
|
99
|
+
("sum_info", "sum_info"),
|
|
100
|
+
("max_hours", "max_hours"),
|
|
101
|
+
("parent_id>num_things", "B.num_things as parent_id_num_things"),
|
|
102
|
+
("parent_id>symmetric", 'B."symmetric" as parent_id_symmetric'),
|
|
103
|
+
("parent_id>table", 'B."table" as parent_id_table'),
|
|
104
|
+
]
|
|
105
|
+
for key, val in columns:
|
|
106
|
+
column = th.resolve_references(
|
|
107
|
+
key, options={"alias_table": True, "alias_column": True}
|
|
108
|
+
)
|
|
109
|
+
self.assertEqual(column, val)
|
|
110
|
+
|
|
111
|
+
columns = [
|
|
112
|
+
("parent_id", "parent_id"),
|
|
113
|
+
("parent_name", "parent_name"),
|
|
114
|
+
("parent_id>parent_name", "fk_parent.parent_name as parent_id_parent_name"),
|
|
115
|
+
("quijibo", "quijibo"),
|
|
116
|
+
("doojibo", "doojibo"),
|
|
117
|
+
("symmetric", '"symmetric"'),
|
|
118
|
+
("table", '"table"'),
|
|
119
|
+
("parent_id>is_valid", "fk_parent.is_valid as parent_id_is_valid"),
|
|
120
|
+
("sum_info", "sum_info"),
|
|
121
|
+
("max_hours", "max_hours"),
|
|
122
|
+
("parent_id>num_things", "fk_parent.num_things as parent_id_num_things"),
|
|
123
|
+
("parent_id>symmetric", 'fk_parent."symmetric" as parent_id_symmetric'),
|
|
124
|
+
("parent_id>table", 'fk_parent."table" as parent_id_table'),
|
|
125
|
+
]
|
|
126
|
+
for key, val in columns:
|
|
127
|
+
column = th.resolve_references(
|
|
128
|
+
key, options={"alias_table": False, "alias_column": True}
|
|
129
|
+
)
|
|
130
|
+
self.assertEqual(column, val)
|
|
131
|
+
|
|
132
|
+
def test_select_snuffleupagus(self, tx):
|
|
133
|
+
if not DO_ALL:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
expected_sql = """SELECT parent_id,
|
|
137
|
+
parent_name,
|
|
138
|
+
B.parent_name AS parent_id_parent_name,
|
|
139
|
+
quijibo,
|
|
140
|
+
doojibo,
|
|
141
|
+
"symmetric",
|
|
142
|
+
"table",
|
|
143
|
+
B.is_valid AS parent_id_is_valid,
|
|
144
|
+
sum_info,
|
|
145
|
+
max_hours,
|
|
146
|
+
B.num_things AS parent_id_num_things,
|
|
147
|
+
B."symmetric" AS parent_id_symmetric,
|
|
148
|
+
B."table" AS parent_id_table
|
|
149
|
+
FROM fk_child AS A
|
|
150
|
+
LEFT JOIN fk_parent AS B ON A.parent_id = B.sys_id
|
|
151
|
+
WHERE B.parent_name = %s
|
|
152
|
+
AND B.is_valid IS TRUE
|
|
153
|
+
AND B.num_things = %s
|
|
154
|
+
AND B."symmetric" IS TRUE
|
|
155
|
+
AND sum_info = %s
|
|
156
|
+
AND max_hours = %s
|
|
157
|
+
GROUP BY parent_id_num_things,
|
|
158
|
+
parent_id_parent_name
|
|
159
|
+
HAVING B.num_things = %s
|
|
160
|
+
AND B."symmetric" IS TRUE
|
|
161
|
+
ORDER BY parent_id_num_things DESC,
|
|
162
|
+
parent_id_parent_name
|
|
163
|
+
OFFSET 99 ROWS FETCH NEXT 100 ROWS ONLY
|
|
164
|
+
FOR
|
|
165
|
+
UPDATE SKIP LOCKED"""
|
|
166
|
+
expected_vars = ("value", 1, "value", 1, 1)
|
|
167
|
+
|
|
168
|
+
sql, vars = tx.table("fk_child").select(
|
|
169
|
+
sql_only=True,
|
|
170
|
+
columns=[
|
|
171
|
+
"parent_id",
|
|
172
|
+
"parent_name",
|
|
173
|
+
"parent_id>parent_name",
|
|
174
|
+
"quijibo",
|
|
175
|
+
"doojibo",
|
|
176
|
+
"symmetric",
|
|
177
|
+
"table",
|
|
178
|
+
"parent_id>is_valid",
|
|
179
|
+
"sum_info",
|
|
180
|
+
"max_hours",
|
|
181
|
+
"parent_id>num_things",
|
|
182
|
+
"parent_id>symmetric",
|
|
183
|
+
"parent_id>table",
|
|
184
|
+
],
|
|
185
|
+
where={
|
|
186
|
+
"parent_id>parent_name": "value",
|
|
187
|
+
"parent_id>is_valid": True,
|
|
188
|
+
"parent_id>num_things": 1,
|
|
189
|
+
"parent_id>symmetric": True,
|
|
190
|
+
"sum_info": "value",
|
|
191
|
+
"max_hours": 1,
|
|
192
|
+
},
|
|
193
|
+
having={"parent_id>num_things": 1, "parent_id>symmetric": True},
|
|
194
|
+
orderby=["parent_id>num_things desc", "parent_id>parent_name"],
|
|
195
|
+
groupby=["parent_id>num_things", "parent_id>parent_name"],
|
|
196
|
+
lock=True,
|
|
197
|
+
skip_locked=True,
|
|
198
|
+
start=99,
|
|
199
|
+
qty=100,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
self.assertEqual(sql, expected_sql)
|
|
203
|
+
self.assertEqual(vars, expected_vars)
|
|
204
|
+
|
|
205
|
+
def test_create_index(self, tx):
|
|
206
|
+
if not DO_ALL:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
sql, vars = tx.table("weird_names_table").create_index(
|
|
210
|
+
sql_only=True,
|
|
211
|
+
unique=True,
|
|
212
|
+
columns=[
|
|
213
|
+
"sum_info",
|
|
214
|
+
"max_hours",
|
|
215
|
+
"order",
|
|
216
|
+
],
|
|
217
|
+
where={
|
|
218
|
+
"sum_info": "value",
|
|
219
|
+
"max_hours": 1,
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
expected_sql = """CREATE UNIQUE INDEX IDX__WEIRD_NAMES_TABLE__SUM_INFO_MAX_HOURS_ORDER ON weird_names_table (sum_info, max_hours, "order")
|
|
223
|
+
WHERE sum_info = %s
|
|
224
|
+
AND max_hours = %s
|
|
225
|
+
"""
|
|
226
|
+
expected_vars = ("value", 1)
|
|
227
|
+
self.assertEqual(
|
|
228
|
+
sql.split(),
|
|
229
|
+
expected_sql.split(),
|
|
230
|
+
)
|
|
231
|
+
self.assertEqual(vars, expected_vars)
|
|
232
|
+
|
|
233
|
+
def test_delete(self, tx):
|
|
234
|
+
if not DO_ALL:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
sql, vars = tx.table("weird_names_table").delete(
|
|
238
|
+
sql_only=True,
|
|
239
|
+
where={
|
|
240
|
+
"sum_info": "value",
|
|
241
|
+
"max_hours": 1,
|
|
242
|
+
},
|
|
243
|
+
)
|
|
244
|
+
expected_sql = """
|
|
245
|
+
DELETE FROM weird_names_table WHERE sum_info = %s AND max_hours = %s
|
|
246
|
+
"""
|
|
247
|
+
expected_vars = ("value", 1)
|
|
248
|
+
self.assertEqual(
|
|
249
|
+
sql.split(),
|
|
250
|
+
expected_sql.split(),
|
|
251
|
+
)
|
|
252
|
+
self.assertEqual(vars, expected_vars)
|
|
253
|
+
|
|
254
|
+
def test_missing(self, tx):
|
|
255
|
+
if not DO_ALL:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
sql, vars = tx.table("weird_names_table").missing(
|
|
259
|
+
sql_only=True,
|
|
260
|
+
list_=["a", "b", "c"],
|
|
261
|
+
where={
|
|
262
|
+
"sum_info": "value",
|
|
263
|
+
"max_hours": 1,
|
|
264
|
+
},
|
|
265
|
+
)
|
|
266
|
+
expected_sql = """
|
|
267
|
+
SELECT *
|
|
268
|
+
FROM UNNEST('{a,b,c}'::int[]) id
|
|
269
|
+
EXCEPT ALL
|
|
270
|
+
SELECT sys_id
|
|
271
|
+
FROM weird_names_table
|
|
272
|
+
WHERE sum_info = %s
|
|
273
|
+
AND max_hours = %s
|
|
274
|
+
"""
|
|
275
|
+
expected_vars = ("value", 1)
|
|
276
|
+
self.assertEqual(
|
|
277
|
+
sql.split(),
|
|
278
|
+
expected_sql.split(),
|
|
279
|
+
)
|
|
280
|
+
self.assertEqual(vars, expected_vars)
|
|
281
|
+
|
|
282
|
+
def test_insert(self, tx):
|
|
283
|
+
if not DO_ALL:
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
sql, vars = tx.table("weird_names_table").insert(
|
|
287
|
+
sql_only=True,
|
|
288
|
+
data={
|
|
289
|
+
"sum_info": "value",
|
|
290
|
+
"max_hours": 1,
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
expected_sql = """
|
|
294
|
+
INSERT INTO weird_names_table (sum_info, max_hours)
|
|
295
|
+
VALUES (%s,%s)
|
|
296
|
+
"""
|
|
297
|
+
expected_vars = ("value", 1)
|
|
298
|
+
self.assertEqual(
|
|
299
|
+
sql.split(),
|
|
300
|
+
expected_sql.split(),
|
|
301
|
+
)
|
|
302
|
+
self.assertEqual(vars, expected_vars)
|
|
303
|
+
|
|
304
|
+
def test_update(self, tx):
|
|
305
|
+
if not DO_ALL:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
sql, vars = tx.table("weird_names_table").update(
|
|
309
|
+
sql_only=True,
|
|
310
|
+
data={
|
|
311
|
+
"sum_info": "value",
|
|
312
|
+
"max_hours": 1,
|
|
313
|
+
"order": None,
|
|
314
|
+
},
|
|
315
|
+
where={
|
|
316
|
+
"peekaboo": "value",
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
expected_sql = """
|
|
320
|
+
UPDATE weird_names_table
|
|
321
|
+
SET sum_info = %s,
|
|
322
|
+
max_hours = %s,
|
|
323
|
+
"order" = %s
|
|
324
|
+
WHERE peekaboo = %s
|
|
325
|
+
"""
|
|
326
|
+
expected_vars = ("value", 1, None, "value")
|
|
327
|
+
|
|
328
|
+
self.assertEqual(
|
|
329
|
+
sql.split(),
|
|
330
|
+
expected_sql.split(),
|
|
331
|
+
)
|
|
332
|
+
self.assertEqual(vars, expected_vars)
|
|
333
|
+
|
|
334
|
+
def test_count(self, tx):
|
|
335
|
+
if not DO_ALL:
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
sql, vars = tx.table("weird_names_table").count(
|
|
339
|
+
sql_only=True,
|
|
340
|
+
where={
|
|
341
|
+
"peekaboo": "value",
|
|
342
|
+
},
|
|
343
|
+
)
|
|
344
|
+
expected_sql = """
|
|
345
|
+
SELECT count(*)
|
|
346
|
+
FROM weird_names_table
|
|
347
|
+
WHERE peekaboo = %s
|
|
348
|
+
"""
|
|
349
|
+
expected_vars = ("value",)
|
|
350
|
+
|
|
351
|
+
self.assertEqual(
|
|
352
|
+
sql.split(),
|
|
353
|
+
expected_sql.split(),
|
|
354
|
+
)
|
|
355
|
+
self.assertEqual(vars, expected_vars)
|
|
356
|
+
|
|
357
|
+
def test_extract_column_name(self, tx):
|
|
358
|
+
if not DO_ALL:
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
th = tablehelper.TableHelper(tx, "fk_child")
|
|
362
|
+
column = th.extract_column_name("parent_id>parent_name")
|
|
363
|
+
self.assertEqual(column, "parent_id>parent_name")
|
|
364
|
+
column = th.extract_column_name("count(parent_id>parent_name)")
|
|
365
|
+
self.assertEqual(column, "parent_id>parent_name")
|
|
366
|
+
column = th.extract_column_name("sum(count(parent_id>parent_name))")
|
|
367
|
+
self.assertEqual(column, "parent_id>parent_name")
|
|
368
|
+
column = th.extract_column_name("coalesce(parent_id>parent_name, 0)")
|
|
369
|
+
self.assertEqual(column, "parent_id>parent_name")
|
|
370
|
+
|
|
371
|
+
column = th.extract_column_name("count(pArent_id>pArent_nAme)")
|
|
372
|
+
self.assertEqual(column, "pArent_id>pArent_nAme")
|
|
373
|
+
|
|
374
|
+
column = th.extract_column_name('count("pArent_id>pArent_nAme")')
|
|
375
|
+
self.assertEqual(column, "pArent_id>pArent_nAme")
|
|
376
|
+
|
|
377
|
+
# Ensure balanced parentheses
|
|
378
|
+
|
|
379
|
+
self.assertRaises(
|
|
380
|
+
ValueError,
|
|
381
|
+
th.extract_column_name,
|
|
382
|
+
"sum(count(coalesce(parent_id>parent_name, 0)",
|
|
383
|
+
)
|
|
384
|
+
column = th.extract_column_name(
|
|
385
|
+
"sum(count(coalesce(parent_id>parent_name, 0)))"
|
|
386
|
+
)
|
|
387
|
+
self.assertEqual(column, "parent_id>parent_name")
|
|
388
|
+
|
|
389
|
+
column = th.extract_column_name("!=sum(parent_id>parent_name, 0)")
|
|
390
|
+
self.assertEqual(column, "parent_id>parent_name")
|
|
391
|
+
|
|
392
|
+
for op in th.operators:
|
|
393
|
+
column = th.extract_column_name(f"{op}coalesce(parent_id>parent_name, 0)")
|
|
394
|
+
self.assertEqual(column, "parent_id>parent_name")
|
|
395
|
+
|
|
396
|
+
def test_resolve_references(self, tx):
|
|
397
|
+
if not DO_ALL:
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
th = tablehelper.TableHelper(tx, "fk_child")
|
|
401
|
+
column = th.resolve_references("parent_id>parent_name")
|
|
402
|
+
self.assertEqual(column, "fk_parent.parent_name as parent_id_parent_name")
|
|
403
|
+
|
|
404
|
+
column = th.resolve_references("sum(parent_id>parent_name)")
|
|
405
|
+
self.assertEqual(column, "sum(fk_parent.parent_name) as parent_id_parent_name")
|
|
406
|
+
|
|
407
|
+
column = th.resolve_references('coalesce(parent_id>parent_name, "account")')
|
|
408
|
+
self.assertEqual(
|
|
409
|
+
column,
|
|
410
|
+
'coalesce(fk_parent.parent_name, "account") as parent_id_parent_name',
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
column = th.resolve_references('>coalesce(parent_id>parent_name, "account")')
|
|
414
|
+
self.assertEqual(
|
|
415
|
+
column,
|
|
416
|
+
'coalesce(fk_parent.parent_name, "account") as parent_id_parent_name',
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
for op in th.operators:
|
|
420
|
+
column = th.resolve_references(f"{op}coalesce(parent_id>parent_name, 0)")
|
|
421
|
+
self.assertEqual(
|
|
422
|
+
column, f"coalesce(fk_parent.parent_name, 0) as parent_id_parent_name"
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
def test_duplicate_rows(self, tx):
|
|
426
|
+
if not DO_ALL:
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
sql, vars = tx.table("weird_names_table").duplicate_rows(
|
|
430
|
+
sql_only=True, columns=["peekaboo"]
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
expected_sql = """
|
|
434
|
+
SELECT t.*
|
|
435
|
+
FROM weird_names_table t
|
|
436
|
+
JOIN (SELECT peekaboo
|
|
437
|
+
FROM weird_names_table
|
|
438
|
+
GROUP BY peekaboo
|
|
439
|
+
HAVING count(*) > %s) dup
|
|
440
|
+
ON t.peekaboo = dup.peekaboo
|
|
441
|
+
ORDER BY peekaboo
|
|
442
|
+
"""
|
|
443
|
+
expected_vars = (1,)
|
|
444
|
+
|
|
445
|
+
self.assertEqual(
|
|
446
|
+
sql.split(),
|
|
447
|
+
expected_sql.split(),
|
|
448
|
+
)
|
|
449
|
+
self.assertEqual(vars, expected_vars)
|
|
450
|
+
|
|
451
|
+
def test_select_strings(self, tx):
|
|
452
|
+
if not DO_ALL:
|
|
453
|
+
return
|
|
454
|
+
|
|
455
|
+
sql, vars = tx.table("weird_names_table").select(
|
|
456
|
+
sql_only=True,
|
|
457
|
+
columns="distinct parent_id, child_id",
|
|
458
|
+
where="where_value = 'value'",
|
|
459
|
+
having="having_value = 'value'",
|
|
460
|
+
)
|
|
461
|
+
expected_sql = """
|
|
462
|
+
SELECT DISTINCT parent_id, child_id
|
|
463
|
+
FROM weird_names_table
|
|
464
|
+
WHERE where_value = 'value'
|
|
465
|
+
HAVING having_value = 'value'
|
|
466
|
+
"""
|
|
467
|
+
expected_vars = ()
|
|
468
|
+
|
|
469
|
+
self.assertEqual(
|
|
470
|
+
sql.split(),
|
|
471
|
+
expected_sql.split(),
|
|
472
|
+
)
|
|
473
|
+
self.assertEqual(vars, expected_vars)
|
|
474
|
+
|
|
475
|
+
def test_function_in_predicate(self, tx):
|
|
476
|
+
if not DO_ALL:
|
|
477
|
+
return
|
|
478
|
+
th = tablehelper.TableHelper(tx, "fk_child")
|
|
479
|
+
columns = [
|
|
480
|
+
('coalesce(parent_id,"")', 'coalesce(parent_id,"")'),
|
|
481
|
+
('coalesce(parent_name,"")', 'coalesce(parent_name,"")'),
|
|
482
|
+
(
|
|
483
|
+
'coalesce(parent_id>parent_name,"")',
|
|
484
|
+
'coalesce(B.parent_name,"") as parent_id_parent_name',
|
|
485
|
+
),
|
|
486
|
+
('coalesce(quijibo,"")', 'coalesce(quijibo,"")'),
|
|
487
|
+
('coalesce(doojibo,"")', 'coalesce(doojibo,"")'),
|
|
488
|
+
('coalesce(symmetric,"")', 'coalesce("symmetric","")'),
|
|
489
|
+
('coalesce(table,"")', 'coalesce("table","")'),
|
|
490
|
+
(
|
|
491
|
+
'coalesce(parent_id>is_valid,"")',
|
|
492
|
+
'coalesce(B.is_valid,"") as parent_id_is_valid',
|
|
493
|
+
),
|
|
494
|
+
('coalesce(sum_info,"")', 'coalesce(sum_info,"")'),
|
|
495
|
+
('coalesce(max_hours,"")', 'coalesce(max_hours,"")'),
|
|
496
|
+
(
|
|
497
|
+
'coalesce(parent_id>num_things,"")',
|
|
498
|
+
'coalesce(B.num_things,"") as parent_id_num_things',
|
|
499
|
+
),
|
|
500
|
+
(
|
|
501
|
+
'coalesce(parent_id>symmetric,"")',
|
|
502
|
+
'coalesce(B."symmetric","") as parent_id_symmetric',
|
|
503
|
+
),
|
|
504
|
+
(
|
|
505
|
+
'sum(parent_id>table,"")',
|
|
506
|
+
'sum(B."table","") as parent_id_table',
|
|
507
|
+
),
|
|
508
|
+
]
|
|
509
|
+
for key, val in columns:
|
|
510
|
+
column = th.resolve_references(
|
|
511
|
+
key, options={"alias_table": True, "alias_column": True}
|
|
512
|
+
)
|
|
513
|
+
self.assertEqual(column, val)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
if __name__ == "__main__":
|
|
517
|
+
unittest.main()
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import Mock, MagicMock
|
|
3
|
+
from velocity.db.core.table import Table
|
|
4
|
+
from velocity.db.core.result import Result
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestCursorRowCountFix(unittest.TestCase):
|
|
8
|
+
"""
|
|
9
|
+
Test cases to verify that table methods handle None cursors gracefully
|
|
10
|
+
when accessing result.cursor.rowcount.
|
|
11
|
+
|
|
12
|
+
This addresses the AttributeError: 'NoneType' object has no attribute 'rowcount'
|
|
13
|
+
issue that can occur when the cursor is None due to connection errors or
|
|
14
|
+
other exceptional conditions.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def setUp(self):
|
|
18
|
+
"""Set up mock objects for testing."""
|
|
19
|
+
self.mock_tx = Mock()
|
|
20
|
+
self.mock_sql = Mock()
|
|
21
|
+
self.table = Table(self.mock_tx, "test_table")
|
|
22
|
+
self.table.sql = self.mock_sql
|
|
23
|
+
|
|
24
|
+
def test_insert_with_none_cursor(self):
|
|
25
|
+
"""Test that insert() returns 0 when result.cursor is None."""
|
|
26
|
+
# Mock the SQL generation
|
|
27
|
+
self.mock_sql.insert.return_value = ("INSERT SQL", ["values"])
|
|
28
|
+
|
|
29
|
+
# Create a mock result with cursor = None
|
|
30
|
+
mock_result = Mock(spec=Result)
|
|
31
|
+
mock_result.cursor = None
|
|
32
|
+
|
|
33
|
+
# Mock the execute method to return our mock result
|
|
34
|
+
self.mock_tx.execute.return_value = mock_result
|
|
35
|
+
|
|
36
|
+
# Mock the cursor method
|
|
37
|
+
mock_cursor = Mock()
|
|
38
|
+
self.table.cursor = Mock(return_value=mock_cursor)
|
|
39
|
+
|
|
40
|
+
# Call insert and verify it returns 0 instead of raising AttributeError
|
|
41
|
+
result = self.table.insert({"test_field": "test_value"})
|
|
42
|
+
self.assertEqual(result, 0)
|
|
43
|
+
|
|
44
|
+
def test_update_with_none_cursor(self):
|
|
45
|
+
"""Test that update() returns 0 when result.cursor is None."""
|
|
46
|
+
# Mock the SQL generation
|
|
47
|
+
self.mock_sql.update.return_value = ("UPDATE SQL", ["values"])
|
|
48
|
+
|
|
49
|
+
# Create a mock result with cursor = None
|
|
50
|
+
mock_result = Mock(spec=Result)
|
|
51
|
+
mock_result.cursor = None
|
|
52
|
+
|
|
53
|
+
# Mock the execute method to return our mock result
|
|
54
|
+
self.mock_tx.execute.return_value = mock_result
|
|
55
|
+
|
|
56
|
+
# Mock the cursor method
|
|
57
|
+
mock_cursor = Mock()
|
|
58
|
+
self.table.cursor = Mock(return_value=mock_cursor)
|
|
59
|
+
|
|
60
|
+
# Call update and verify it returns 0 instead of raising AttributeError
|
|
61
|
+
result = self.table.update({"test_field": "new_value"}, where={"id": 1})
|
|
62
|
+
self.assertEqual(result, 0)
|
|
63
|
+
|
|
64
|
+
def test_merge_with_none_cursor(self):
|
|
65
|
+
"""Test that merge() returns 0 when result.cursor is None."""
|
|
66
|
+
# Mock the SQL generation
|
|
67
|
+
self.mock_sql.merge.return_value = ("MERGE SQL", ["values"])
|
|
68
|
+
|
|
69
|
+
# Create a mock result with cursor = None
|
|
70
|
+
mock_result = Mock(spec=Result)
|
|
71
|
+
mock_result.cursor = None
|
|
72
|
+
|
|
73
|
+
# Mock the execute method to return our mock result
|
|
74
|
+
self.mock_tx.execute.return_value = mock_result
|
|
75
|
+
|
|
76
|
+
# Mock the cursor method
|
|
77
|
+
mock_cursor = Mock()
|
|
78
|
+
self.table.cursor = Mock(return_value=mock_cursor)
|
|
79
|
+
|
|
80
|
+
# Call merge and verify it returns 0 instead of raising AttributeError
|
|
81
|
+
result = self.table.merge({"test_field": "test_value"})
|
|
82
|
+
self.assertEqual(result, 0)
|
|
83
|
+
|
|
84
|
+
def test_delete_with_none_cursor(self):
|
|
85
|
+
"""Test that delete() returns 0 when result.cursor is None."""
|
|
86
|
+
# Mock the SQL generation
|
|
87
|
+
self.mock_sql.delete.return_value = ("DELETE SQL", ["values"])
|
|
88
|
+
|
|
89
|
+
# Create a mock result with cursor = None
|
|
90
|
+
mock_result = Mock(spec=Result)
|
|
91
|
+
mock_result.cursor = None
|
|
92
|
+
|
|
93
|
+
# Mock the execute method to return our mock result
|
|
94
|
+
self.mock_tx.execute.return_value = mock_result
|
|
95
|
+
|
|
96
|
+
# Call delete and verify it returns 0 instead of raising AttributeError
|
|
97
|
+
result = self.table.delete(where={"id": 1})
|
|
98
|
+
self.assertEqual(result, 0)
|
|
99
|
+
|
|
100
|
+
def test_insert_with_valid_cursor(self):
|
|
101
|
+
"""Test that insert() returns rowcount when result.cursor is valid."""
|
|
102
|
+
# Mock the SQL generation
|
|
103
|
+
self.mock_sql.insert.return_value = ("INSERT SQL", ["values"])
|
|
104
|
+
|
|
105
|
+
# Create a mock cursor with rowcount
|
|
106
|
+
mock_cursor = Mock()
|
|
107
|
+
mock_cursor.rowcount = 1
|
|
108
|
+
|
|
109
|
+
# Create a mock result with valid cursor
|
|
110
|
+
mock_result = Mock(spec=Result)
|
|
111
|
+
mock_result.cursor = mock_cursor
|
|
112
|
+
|
|
113
|
+
# Mock the execute method to return our mock result
|
|
114
|
+
self.mock_tx.execute.return_value = mock_result
|
|
115
|
+
|
|
116
|
+
# Mock the cursor method
|
|
117
|
+
table_cursor = Mock()
|
|
118
|
+
self.table.cursor = Mock(return_value=table_cursor)
|
|
119
|
+
|
|
120
|
+
# Call insert and verify it returns the actual rowcount
|
|
121
|
+
result = self.table.insert({"test_field": "test_value"})
|
|
122
|
+
self.assertEqual(result, 1)
|
|
123
|
+
|
|
124
|
+
def test_update_with_valid_cursor(self):
|
|
125
|
+
"""Test that update() returns rowcount when result.cursor is valid."""
|
|
126
|
+
# Mock the SQL generation
|
|
127
|
+
self.mock_sql.update.return_value = ("UPDATE SQL", ["values"])
|
|
128
|
+
|
|
129
|
+
# Create a mock cursor with rowcount
|
|
130
|
+
mock_cursor = Mock()
|
|
131
|
+
mock_cursor.rowcount = 2
|
|
132
|
+
|
|
133
|
+
# Create a mock result with valid cursor
|
|
134
|
+
mock_result = Mock(spec=Result)
|
|
135
|
+
mock_result.cursor = mock_cursor
|
|
136
|
+
|
|
137
|
+
# Mock the execute method to return our mock result
|
|
138
|
+
self.mock_tx.execute.return_value = mock_result
|
|
139
|
+
|
|
140
|
+
# Mock the cursor method
|
|
141
|
+
table_cursor = Mock()
|
|
142
|
+
self.table.cursor = Mock(return_value=table_cursor)
|
|
143
|
+
|
|
144
|
+
# Call update and verify it returns the actual rowcount
|
|
145
|
+
result = self.table.update({"test_field": "new_value"}, where={"id": 1})
|
|
146
|
+
self.assertEqual(result, 2)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
unittest.main()
|