@@ -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