Skip to content

Commit 2b82857

Browse files
author
Eugene Shershen
committed
implement UUID parameter binding
1 parent 2b00db6 commit 2b82857

1 file changed

Lines changed: 55 additions & 4 deletions

File tree

psqlpy_sqlalchemy/connection.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,25 @@ def _convert_named_params_with_casting(
188188
# Build the conversion mapping and new parameter list
189189
param_order = []
190190
seen_params = set()
191+
missing_params = []
191192

192193
# Process matches to determine parameter order (first occurrence wins)
193194
for match in matches:
194195
param_name = match.group(1)
195-
if param_name not in seen_params and param_name in parameters:
196-
param_order.append(param_name)
197-
seen_params.add(param_name)
196+
if param_name not in seen_params:
197+
if param_name in parameters:
198+
param_order.append(param_name)
199+
seen_params.add(param_name)
200+
else:
201+
missing_params.append(param_name)
202+
203+
# Defensive check: ensure all parameters found in query are available
204+
if missing_params:
205+
raise ValueError(
206+
f"Missing parameters in query: {missing_params}. "
207+
f"Query contains parameters {[m.group(1) for m in matches]} "
208+
f"but parameters dict only has {list(parameters.keys())}"
209+
)
198210

199211
# Convert the query string by replacing each parameter with its positional equivalent
200212
converted_query = querystring
@@ -205,15 +217,54 @@ def _convert_named_params_with_casting(
205217
f":({re.escape(param_name)})" + r"(::[\w\[\]]+)?"
206218
)
207219
replacement = f"${i}\\2" # $N + casting part (group 2)
208-
converted_query = re.sub(
220+
221+
# Perform replacement and verify it worked
222+
new_query = re.sub(
209223
param_pattern_specific, replacement, converted_query
210224
)
211225

226+
# Defensive check: ensure replacement actually occurred
227+
if (
228+
new_query == converted_query
229+
and f":{param_name}" in converted_query
230+
):
231+
raise RuntimeError(
232+
f"Failed to replace parameter '{param_name}' in query. "
233+
f"Pattern: {param_pattern_specific}, Query: {converted_query}"
234+
)
235+
236+
converted_query = new_query
237+
212238
# Convert parameters dict to list in the correct order
213239
converted_params = [
214240
parameters[param_name] for param_name in param_order
215241
]
216242

243+
# Final defensive check: ensure no named parameters remain in the converted query
244+
# Look for the original parameter pattern, but exclude matches that are part of casting syntax
245+
remaining_matches = []
246+
for match in re.finditer(param_pattern, converted_query):
247+
full_match = match.group(0)
248+
param_name = match.group(1)
249+
# Check if this looks like a real parameter (not casting syntax)
250+
# Real parameters should not be preceded by a positional parameter like $1, $2, etc.
251+
start_pos = match.start()
252+
if start_pos > 0:
253+
# Look at the characters before the match
254+
preceding_text = converted_query[
255+
max(0, start_pos - 3) : start_pos
256+
]
257+
# If preceded by $N, this is likely casting syntax, not a parameter
258+
if re.search(r"\$\d+$", preceding_text):
259+
continue
260+
remaining_matches.append(full_match)
261+
262+
if remaining_matches:
263+
raise RuntimeError(
264+
f"Conversion incomplete: named parameters still present in query: {remaining_matches}. "
265+
f"Converted query: {converted_query}, Original query: {querystring}"
266+
)
267+
217268
return converted_query, converted_params
218269

219270
@property

0 commit comments

Comments
 (0)