@@ -175,41 +175,105 @@ public async Task<IReadOnlyList<SqlRow>> QueryAsync(string sql, IReadOnlyList<Sq
175175 {
176176 await _lock . WaitAsync ( ct ) . ConfigureAwait ( false ) ;
177177 try {
178- _writer ! . Write ( PgQueryMessage . Build ( sql ) ) ;
179- await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
180-
181- var rows = new List < SqlRow > ( ) ;
182- List < SqlColumn > ? columns = null ;
183- while ( true )
178+ if ( parameters is { Count : > 0 } )
184179 {
185- var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
186- if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
187- if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
188- if ( type == ( char ) PgBackendType . RowDescription ) columns = PgDecoder . ParseRowDescription ( payload ) ;
189- if ( type == ( char ) PgBackendType . DataRow && columns != null ) rows . Add ( PgDecoder . ParseDataRow ( payload , columns ) ) ;
180+ // Extended Query Protocol
181+ sql = MapPlaceholders ( sql , parameters ) ;
182+ _writer ! . Write ( PgParseMessage . Build ( "" , sql , new int [ parameters . Count ] ) ) ; // 0 OIDs = infer
183+ _writer . Write ( PgBindMessage . Build ( "" , "" , parameters ) ) ;
184+ _writer . Write ( PgDescribeMessage . Build ( 'P' , "" ) ) ;
185+ _writer . Write ( PgExecuteMessage . Build ( "" ) ) ;
186+ _writer . Write ( PgSyncMessage . Build ( ) ) ;
187+ await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
188+
189+ var rows = new List < SqlRow > ( ) ;
190+ List < SqlColumn > ? columns = null ;
191+ while ( true )
192+ {
193+ var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
194+ if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
195+ if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
196+ if ( type == ( char ) PgBackendType . RowDescription ) columns = PgDecoder . ParseRowDescription ( payload ) ;
197+ if ( type == ( char ) PgBackendType . DataRow && columns != null ) rows . Add ( PgDecoder . ParseDataRow ( payload , columns ) ) ;
198+ }
199+ return rows ;
200+ }
201+ else
202+ {
203+ // Simple Query Protocol
204+ _writer ! . Write ( PgQueryMessage . Build ( sql ) ) ;
205+ await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
206+
207+ var rows = new List < SqlRow > ( ) ;
208+ List < SqlColumn > ? columns = null ;
209+ while ( true )
210+ {
211+ var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
212+ if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
213+ if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
214+ if ( type == ( char ) PgBackendType . RowDescription ) columns = PgDecoder . ParseRowDescription ( payload ) ;
215+ if ( type == ( char ) PgBackendType . DataRow && columns != null ) rows . Add ( PgDecoder . ParseDataRow ( payload , columns ) ) ;
216+ }
217+ return rows ;
190218 }
191- return rows ;
192219 } finally { _lock . Release ( ) ; }
193220 }
194221
222+ private static string MapPlaceholders ( string sql , IReadOnlyList < SqlParameter > parameters )
223+ {
224+ // Simple heuristic: map @p1, @p2... or just sequential @?
225+ // Most Cosmo usage follows @name. We'll map them by order of appearance or ordinal if named @p1
226+ for ( int i = 0 ; i < parameters . Count ; i ++ )
227+ {
228+ var p = parameters [ i ] ;
229+ var name = p . Name . StartsWith ( "@" ) ? p . Name : "@" + p . Name ;
230+ // This is a naive replacement, but sufficient for standard Cosmo usage
231+ sql = sql . Replace ( name , "$" + ( i + 1 ) ) ;
232+ }
233+ return sql ;
234+ }
235+
195236 public Task < IReadOnlyList < T > > QueryAsync < T > ( string sql , IReadOnlyList < SqlParameter > ? parameters = null , CancellationToken ct = default ) where T : new ( )
196237 => QueryAsync ( sql , parameters , ct ) . ContinueWith ( t => ( IReadOnlyList < T > ) t . Result . Select ( r => new SqlRowDecoder ( ) . Decode < T > ( r ) ) . ToList ( ) ) ;
197238
198239 public async IAsyncEnumerable < SqlRow > QueryStreamAsync ( string sql , IReadOnlyList < SqlParameter > ? parameters = null , [ System . Runtime . CompilerServices . EnumeratorCancellation ] CancellationToken ct = default )
199240 {
200241 await _lock . WaitAsync ( ct ) . ConfigureAwait ( false ) ;
201242 try {
202- _writer ! . Write ( PgQueryMessage . Build ( sql ) ) ;
203- await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
204-
205- List < SqlColumn > ? columns = null ;
206- while ( true )
243+ if ( parameters is { Count : > 0 } )
244+ {
245+ sql = MapPlaceholders ( sql , parameters ) ;
246+ _writer ! . Write ( PgParseMessage . Build ( "" , sql , new int [ parameters . Count ] ) ) ;
247+ _writer . Write ( PgBindMessage . Build ( "" , "" , parameters ) ) ;
248+ _writer . Write ( PgDescribeMessage . Build ( 'P' , "" ) ) ;
249+ _writer . Write ( PgExecuteMessage . Build ( "" ) ) ;
250+ _writer . Write ( PgSyncMessage . Build ( ) ) ;
251+ await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
252+
253+ List < SqlColumn > ? columns = null ;
254+ while ( true )
255+ {
256+ var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
257+ if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
258+ if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
259+ if ( type == ( char ) PgBackendType . RowDescription ) columns = PgDecoder . ParseRowDescription ( payload ) ;
260+ if ( type == ( char ) PgBackendType . DataRow && columns != null ) yield return PgDecoder . ParseDataRow ( payload , columns ) ;
261+ }
262+ }
263+ else
207264 {
208- var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
209- if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
210- if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
211- if ( type == ( char ) PgBackendType . RowDescription ) columns = PgDecoder . ParseRowDescription ( payload ) ;
212- if ( type == ( char ) PgBackendType . DataRow && columns != null ) yield return PgDecoder . ParseDataRow ( payload , columns ) ;
265+ _writer ! . Write ( PgQueryMessage . Build ( sql ) ) ;
266+ await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
267+
268+ List < SqlColumn > ? columns = null ;
269+ while ( true )
270+ {
271+ var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
272+ if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
273+ if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
274+ if ( type == ( char ) PgBackendType . RowDescription ) columns = PgDecoder . ParseRowDescription ( payload ) ;
275+ if ( type == ( char ) PgBackendType . DataRow && columns != null ) yield return PgDecoder . ParseDataRow ( payload , columns ) ;
276+ }
213277 }
214278 } finally { _lock . Release ( ) ; }
215279 }
@@ -218,18 +282,40 @@ public async Task<int> ExecuteAsync(string sql, IReadOnlyList<SqlParameter>? par
218282 {
219283 await _lock . WaitAsync ( ct ) . ConfigureAwait ( false ) ;
220284 try {
221- _writer ! . Write ( PgQueryMessage . Build ( sql ) ) ;
222- await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
223-
224- int affected = 0 ;
225- while ( true )
285+ if ( parameters is { Count : > 0 } )
226286 {
227- var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
228- if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
229- if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
230- if ( type == ( char ) PgBackendType . CommandComplete ) affected = ( int ) PgDecoder . ParseCommandComplete ( payload ) . rowCount ;
287+ sql = MapPlaceholders ( sql , parameters ) ;
288+ _writer ! . Write ( PgParseMessage . Build ( "" , sql , new int [ parameters . Count ] ) ) ;
289+ _writer . Write ( PgBindMessage . Build ( "" , "" , parameters ) ) ;
290+ _writer . Write ( PgExecuteMessage . Build ( "" ) ) ;
291+ _writer . Write ( PgSyncMessage . Build ( ) ) ;
292+ await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
293+
294+ int affected = 0 ;
295+ while ( true )
296+ {
297+ var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
298+ if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
299+ if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
300+ if ( type == ( char ) PgBackendType . CommandComplete ) affected = ( int ) PgDecoder . ParseCommandComplete ( payload ) . rowCount ;
301+ }
302+ return affected ;
303+ }
304+ else
305+ {
306+ _writer ! . Write ( PgQueryMessage . Build ( sql ) ) ;
307+ await _writer . FlushAsync ( ct ) . ConfigureAwait ( false ) ;
308+
309+ int affected = 0 ;
310+ while ( true )
311+ {
312+ var ( type , payload ) = await ReceiveMessageAsync ( ct ) . ConfigureAwait ( false ) ;
313+ if ( type == ( char ) PgBackendType . ReadyForQuery ) break ;
314+ if ( type == ( char ) PgBackendType . ErrorResponse ) throw SqlException . Query ( PgDecoder . ParseErrorResponse ( payload ) ) ;
315+ if ( type == ( char ) PgBackendType . CommandComplete ) affected = ( int ) PgDecoder . ParseCommandComplete ( payload ) . rowCount ;
316+ }
317+ return affected ;
231318 }
232- return affected ;
233319 } finally { _lock . Release ( ) ; }
234320 }
235321
0 commit comments