velocity-python 0.0.89__py3-none-any.whl → 0.0.92__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of velocity-python might be problematic. Click here for more details.
- velocity/__init__.py +1 -1
- velocity/db/servers/postgres/sql.py +107 -34
- velocity/db/servers/tablehelper.py +329 -75
- velocity_python-0.0.92.dist-info/METADATA +409 -0
- {velocity_python-0.0.89.dist-info → velocity_python-0.0.92.dist-info}/RECORD +8 -8
- velocity_python-0.0.89.dist-info/METADATA +0 -186
- {velocity_python-0.0.89.dist-info → velocity_python-0.0.92.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.89.dist-info → velocity_python-0.0.92.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.89.dist-info → velocity_python-0.0.92.dist-info}/top_level.txt +0 -0
velocity/__init__.py
CHANGED
|
@@ -12,10 +12,44 @@ from ..tablehelper import TableHelper
|
|
|
12
12
|
from collections.abc import Mapping, Sequence
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
# Configure TableHelper for PostgreSQL
|
|
15
16
|
TableHelper.reserved = reserved_words
|
|
16
17
|
TableHelper.operators = OPERATORS
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def _get_table_helper(tx, table):
|
|
21
|
+
"""
|
|
22
|
+
Utility function to create a TableHelper instance.
|
|
23
|
+
Ensures consistent configuration across all SQL methods.
|
|
24
|
+
"""
|
|
25
|
+
return TableHelper(tx, table)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _validate_table_name(table):
|
|
29
|
+
"""Validate table name format."""
|
|
30
|
+
if not table or not isinstance(table, str):
|
|
31
|
+
raise ValueError("Table name must be a non-empty string")
|
|
32
|
+
# Add more validation as needed
|
|
33
|
+
return table.strip()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _handle_predicate_errors(predicates, operation="WHERE"):
|
|
37
|
+
"""Process a list of predicates with error handling."""
|
|
38
|
+
sql_parts = []
|
|
39
|
+
vals = []
|
|
40
|
+
|
|
41
|
+
for pred, val in predicates:
|
|
42
|
+
sql_parts.append(pred)
|
|
43
|
+
if val is None:
|
|
44
|
+
pass
|
|
45
|
+
elif isinstance(val, tuple):
|
|
46
|
+
vals.extend(val)
|
|
47
|
+
else:
|
|
48
|
+
vals.append(val)
|
|
49
|
+
|
|
50
|
+
return sql_parts, vals
|
|
51
|
+
|
|
52
|
+
|
|
19
53
|
system_fields = [
|
|
20
54
|
"sys_id",
|
|
21
55
|
"sys_created",
|
|
@@ -72,9 +106,17 @@ class SQL:
|
|
|
72
106
|
lock=None,
|
|
73
107
|
skip_locked=None,
|
|
74
108
|
):
|
|
75
|
-
|
|
109
|
+
"""
|
|
110
|
+
Generate a PostgreSQL SELECT statement with proper table helper integration.
|
|
111
|
+
"""
|
|
76
112
|
if not table:
|
|
77
113
|
raise ValueError("Table name is required.")
|
|
114
|
+
|
|
115
|
+
# Validate pagination parameters
|
|
116
|
+
if start is not None and not isinstance(start, int):
|
|
117
|
+
raise ValueError("Start (OFFSET) must be an integer.")
|
|
118
|
+
if qty is not None and not isinstance(qty, int):
|
|
119
|
+
raise ValueError("Qty (FETCH) must be an integer.")
|
|
78
120
|
|
|
79
121
|
sql_parts = {
|
|
80
122
|
"SELECT": [],
|
|
@@ -88,8 +130,8 @@ class SQL:
|
|
|
88
130
|
sql = []
|
|
89
131
|
vals = []
|
|
90
132
|
|
|
91
|
-
#
|
|
92
|
-
th =
|
|
133
|
+
# Create table helper instance
|
|
134
|
+
th = _get_table_helper(tx, table)
|
|
93
135
|
|
|
94
136
|
# Handle columns and DISTINCT before aliasing
|
|
95
137
|
if columns is None:
|
|
@@ -97,25 +139,28 @@ class SQL:
|
|
|
97
139
|
columns = ["*"]
|
|
98
140
|
elif isinstance(columns, str):
|
|
99
141
|
columns = th.split_columns(columns)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
f"variable `columns` must be a sequence, but {type(columns)} was found"
|
|
142
|
+
elif not isinstance(columns, Sequence):
|
|
143
|
+
raise TypeError(
|
|
144
|
+
f"Columns must be a string, sequence, or None, but {type(columns)} was found"
|
|
104
145
|
)
|
|
105
146
|
|
|
106
|
-
|
|
147
|
+
# Clean and validate columns
|
|
148
|
+
columns = [c.strip() for c in columns if c.strip()] # Remove empty columns
|
|
149
|
+
if not columns:
|
|
150
|
+
raise ValueError("No valid columns specified")
|
|
151
|
+
|
|
107
152
|
distinct = False
|
|
108
153
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
): # Check if "distinct" exists in any entry
|
|
154
|
+
# Check for DISTINCT keyword in any column
|
|
155
|
+
if any("distinct" in c.lower() for c in columns):
|
|
112
156
|
distinct = True
|
|
113
157
|
columns = [re.sub(r"(?i)\bdistinct\b", "", c).strip() for c in columns]
|
|
114
158
|
|
|
159
|
+
# Process column references
|
|
115
160
|
processed_columns = []
|
|
116
161
|
for col in columns:
|
|
117
|
-
|
|
118
|
-
th.resolve_references(
|
|
162
|
+
try:
|
|
163
|
+
processed_col = th.resolve_references(
|
|
119
164
|
col,
|
|
120
165
|
options={
|
|
121
166
|
"alias_column": True,
|
|
@@ -123,40 +168,65 @@ class SQL:
|
|
|
123
168
|
"bypass_on_error": True,
|
|
124
169
|
},
|
|
125
170
|
)
|
|
126
|
-
|
|
171
|
+
processed_columns.append(processed_col)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
raise ValueError(f"Error processing column '{col}': {e}")
|
|
127
174
|
|
|
128
175
|
columns = processed_columns
|
|
129
176
|
|
|
130
|
-
# Handle WHERE conditions
|
|
177
|
+
# Handle WHERE conditions with better error handling
|
|
131
178
|
if isinstance(where, Mapping):
|
|
132
179
|
new_where = []
|
|
133
180
|
for key, val in where.items():
|
|
134
|
-
|
|
181
|
+
try:
|
|
182
|
+
new_where.append(th.make_predicate(key, val))
|
|
183
|
+
except Exception as e:
|
|
184
|
+
raise ValueError(f"Error processing WHERE condition '{key}': {e}")
|
|
135
185
|
where = new_where
|
|
136
186
|
|
|
187
|
+
# Handle ORDER BY with improved validation
|
|
137
188
|
new_orderby = []
|
|
138
189
|
if isinstance(orderby, str):
|
|
139
190
|
orderby = th.split_columns(orderby)
|
|
191
|
+
|
|
140
192
|
# Handle orderby references
|
|
141
|
-
if isinstance(orderby,
|
|
193
|
+
if isinstance(orderby, Sequence):
|
|
142
194
|
for column in orderby:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
195
|
+
try:
|
|
196
|
+
if " " in column:
|
|
197
|
+
parts = column.split(" ", 1)
|
|
198
|
+
if len(parts) == 2:
|
|
199
|
+
col_name, direction = parts
|
|
200
|
+
# Validate direction
|
|
201
|
+
direction = direction.upper()
|
|
202
|
+
if direction not in ("ASC", "DESC"):
|
|
203
|
+
raise ValueError(f"Invalid ORDER BY direction: {direction}")
|
|
204
|
+
col_name = th.resolve_references(
|
|
205
|
+
col_name.strip(), options={"alias_only": True}
|
|
206
|
+
)
|
|
207
|
+
new_orderby.append(f"{col_name} {direction}")
|
|
208
|
+
else:
|
|
209
|
+
raise ValueError(f"Invalid ORDER BY format: {column}")
|
|
210
|
+
else:
|
|
211
|
+
resolved_col = th.resolve_references(
|
|
152
212
|
column.strip(), options={"alias_only": True}
|
|
153
213
|
)
|
|
154
|
-
|
|
214
|
+
new_orderby.append(resolved_col)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
raise ValueError(f"Error processing ORDER BY column '{column}': {e}")
|
|
155
217
|
|
|
156
|
-
|
|
218
|
+
elif isinstance(orderby, Mapping):
|
|
157
219
|
for key, val in orderby.items():
|
|
158
|
-
|
|
159
|
-
|
|
220
|
+
try:
|
|
221
|
+
# Validate direction
|
|
222
|
+
direction = str(val).upper()
|
|
223
|
+
if direction not in ("ASC", "DESC"):
|
|
224
|
+
raise ValueError(f"Invalid ORDER BY direction: {direction}")
|
|
225
|
+
parsed_key = th.resolve_references(key, options={"alias_only": True})
|
|
226
|
+
new_orderby.append(f"{parsed_key} {direction}")
|
|
227
|
+
except Exception as e:
|
|
228
|
+
raise ValueError(f"Error processing ORDER BY key '{key}': {e}")
|
|
229
|
+
|
|
160
230
|
orderby = new_orderby
|
|
161
231
|
|
|
162
232
|
# Handle groupby
|
|
@@ -308,7 +378,7 @@ class SQL:
|
|
|
308
378
|
if not isinstance(data, Mapping) or not data:
|
|
309
379
|
raise ValueError("data must be a non-empty mapping of column-value pairs.")
|
|
310
380
|
|
|
311
|
-
th =
|
|
381
|
+
th = _get_table_helper(tx, table)
|
|
312
382
|
set_clauses = []
|
|
313
383
|
vals = []
|
|
314
384
|
|
|
@@ -390,12 +460,15 @@ class SQL:
|
|
|
390
460
|
"""
|
|
391
461
|
Generate an INSERT statement.
|
|
392
462
|
"""
|
|
393
|
-
|
|
463
|
+
# Create a temporary TableHelper instance for quoting
|
|
464
|
+
# Note: We pass None for tx since we only need quoting functionality
|
|
465
|
+
temp_helper = TableHelper(None, table)
|
|
466
|
+
|
|
394
467
|
keys = []
|
|
395
468
|
vals_placeholders = []
|
|
396
469
|
args = []
|
|
397
470
|
for key, val in data.items():
|
|
398
|
-
keys.append(
|
|
471
|
+
keys.append(temp_helper.quote(key.lower()))
|
|
399
472
|
if isinstance(val, str) and len(val) > 2 and val[:2] == "@@" and val[2:]:
|
|
400
473
|
vals_placeholders.append(val[2:])
|
|
401
474
|
else:
|
|
@@ -404,7 +477,7 @@ class SQL:
|
|
|
404
477
|
|
|
405
478
|
sql_parts = []
|
|
406
479
|
sql_parts.append("INSERT INTO")
|
|
407
|
-
sql_parts.append(
|
|
480
|
+
sql_parts.append(temp_helper.quote(table))
|
|
408
481
|
sql_parts.append("(")
|
|
409
482
|
sql_parts.append(",".join(keys))
|
|
410
483
|
sql_parts.append(")")
|