Skip to content

Commit c3b6aab

Browse files
author
Eugene Shershen
committed
implement UUID parameter binding
1 parent 21eadc2 commit c3b6aab

1 file changed

Lines changed: 72 additions & 2 deletions

File tree

psqlpy_sqlalchemy/connection.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,17 @@ async def _prepare_execute(
6262
# Process parameters to ensure proper type conversion (especially for UUIDs)
6363
processed_parameters = self._process_parameters(parameters)
6464

65+
# Convert named parameters with casting syntax to positional parameters
66+
converted_query, converted_params = (
67+
self._convert_named_params_with_casting(
68+
querystring, processed_parameters
69+
)
70+
)
71+
6572
try:
6673
prepared_stmt = await self._connection.prepare(
67-
querystring=querystring,
68-
parameters=processed_parameters,
74+
querystring=converted_query,
75+
parameters=converted_params,
6976
)
7077

7178
self._description = [
@@ -146,6 +153,69 @@ def process_value(value):
146153
else:
147154
return process_value(parameters)
148155

156+
def _convert_named_params_with_casting(
157+
self,
158+
querystring: str,
159+
parameters: t.Union[
160+
t.Sequence[t.Any], t.Mapping[str, Any], None
161+
] = None,
162+
) -> t.Tuple[str, t.Union[t.Sequence[t.Any], t.Mapping[str, Any], None]]:
163+
"""Convert named parameters with PostgreSQL casting syntax to positional parameters.
164+
165+
Transforms queries like:
166+
'SELECT * FROM table WHERE col = :param::UUID LIMIT :limit'
167+
168+
To:
169+
'SELECT * FROM table WHERE col = $1::UUID LIMIT $2'
170+
171+
And converts the parameters dict to a list in the correct order.
172+
"""
173+
if parameters is None or not isinstance(parameters, dict):
174+
return querystring, parameters
175+
176+
import re
177+
178+
# Find all named parameters with optional casting syntax
179+
# Pattern matches :param_name optionally followed by ::TYPE
180+
param_pattern = r":([a-zA-Z_][a-zA-Z0-9_]*)(::[\w\[\]]+)?"
181+
182+
# Find all parameter references in the query
183+
matches = list(re.finditer(param_pattern, querystring))
184+
185+
if not matches:
186+
return querystring, parameters
187+
188+
# Build the conversion mapping and new parameter list
189+
param_order = []
190+
seen_params = set()
191+
192+
# Process matches to determine parameter order (first occurrence wins)
193+
for match in matches:
194+
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)
198+
199+
# Convert the query string by replacing each parameter with its positional equivalent
200+
converted_query = querystring
201+
202+
for i, param_name in enumerate(param_order, 1):
203+
# Replace all occurrences of this parameter with $N, preserving any casting
204+
param_pattern_specific = (
205+
f":({re.escape(param_name)})" + r"(::[\w\[\]]+)?"
206+
)
207+
replacement = f"${i}\\2" # $N + casting part (group 2)
208+
converted_query = re.sub(
209+
param_pattern_specific, replacement, converted_query
210+
)
211+
212+
# Convert parameters dict to list in the correct order
213+
converted_params = [
214+
parameters[param_name] for param_name in param_order
215+
]
216+
217+
return converted_query, converted_params
218+
149219
@property
150220
def description(self) -> "Optional[_DBAPICursorDescription]":
151221
return self._description

0 commit comments

Comments
 (0)