@@ -238,34 +238,44 @@ export class GrpcFlightClient {
238238 /**
239239 * Substitutes parameters into a SQL query using positional placeholders ($1, $2, etc.)
240240 * This is a client-side substitution - the server will receive the final SQL.
241+ * Parameters are processed in reverse order to avoid $1 matching the '1' in $10.
241242 */
242243 private substitutePositionalParameters (
243244 queryText : string ,
244245 parameters : any [ ] ,
245246 ) : string {
246247 let result = queryText ;
247- for ( let i = 0 ; i < parameters . length ; i ++ ) {
248- const placeholder = `$${ i + 1 } ` ;
248+ // Process parameters in reverse order (highest to lowest) to avoid
249+ // $1 replacing the '1' in $10, $11, etc.
250+ for ( let i = parameters . length - 1 ; i >= 0 ; i -- ) {
249251 const value = this . formatParameterValue ( parameters [ i ] ) ;
250- // Replace all occurrences of this placeholder
251- result = result . split ( placeholder ) . join ( value ) ;
252+ // Use regex to match $N not followed by another digit
253+ const regex = new RegExp ( `\\$${ i + 1 } (?![0-9])` , 'g' ) ;
254+ result = result . replace ( regex , value ) ;
252255 }
253256 return result ;
254257 }
255258
256259 /**
257- * Substitutes named parameters into a SQL query (: name style)
260+ * Substitutes named parameters into a SQL query ($ name style)
258261 * This is a client-side substitution - the server will receive the final SQL.
262+ * Uses PostgreSQL-style $param_name syntax for consistency with positional $1, $2 placeholders.
259263 */
260264 private substituteNamedParameters (
261265 queryText : string ,
262266 parameters : Record < string , any > ,
263267 ) : string {
264268 let result = queryText ;
265- for ( const [ name , value ] of Object . entries ( parameters ) ) {
266- // Match :name but not ::type (PostgreSQL cast syntax)
267- // Use regex to match :name followed by non-alphanumeric or end of string
268- const regex = new RegExp ( `:${ name } (?![a-zA-Z0-9_])` , 'g' ) ;
269+ // Sort parameter names by length (descending) to avoid partial matches
270+ // e.g., $name should be replaced before $n
271+ const sortedNames = Object . keys ( parameters ) . sort (
272+ ( a , b ) => b . length - a . length ,
273+ ) ;
274+ for ( const name of sortedNames ) {
275+ const value = parameters [ name ] ;
276+ // Match $name followed by non-alphanumeric or end of string
277+ // This ensures $name_extra won't match when looking for $name
278+ const regex = new RegExp ( `\\$${ name } (?![a-zA-Z0-9_])` , 'g' ) ;
269279 result = result . replace ( regex , this . formatParameterValue ( value ) ) ;
270280 }
271281 return result ;
@@ -337,7 +347,7 @@ export class GrpcFlightClient {
337347 // Positional parameters: $1, $2, etc.
338348 finalQuery = this . substitutePositionalParameters ( queryText , parameters ) ;
339349 } else {
340- // Named parameters: : name, :value , etc.
350+ // Named parameters: $ name, $param , etc.
341351 finalQuery = this . substituteNamedParameters ( queryText , parameters ) ;
342352 }
343353 }
0 commit comments