1use super::connection::PooledConnection;
4use super::lifecycle::MAX_HOT_STATEMENTS;
5use crate::driver::{
6 PgConnection, PgError, PgResult, ResultFormat,
7 extended_flow::{ExtendedFlowConfig, ExtendedFlowTracker},
8 is_ignorable_session_message, unexpected_backend_message,
9};
10use std::sync::Arc;
11
12#[inline]
13fn rollback_cache_miss_statement_registration(
14 conn: &mut PgConnection,
15 is_cache_miss: bool,
16 sql_hash: u64,
17 stmt_name: &str,
18) {
19 if is_cache_miss {
20 conn.stmt_cache.remove(&sql_hash);
21 conn.prepared_statements.remove(stmt_name);
22 conn.column_info_cache.remove(&sql_hash);
23 }
24}
25
26#[inline]
27fn register_hot_statement_after_parse_success(
28 pool: &super::lifecycle::PgPoolInner,
29 sql_hash: u64,
30 stmt_name: &str,
31 sql: &str,
32) {
33 if let Ok(mut hot) = pool.hot_statements.write()
34 && (hot.contains_key(&sql_hash) || hot.len() < MAX_HOT_STATEMENTS)
35 {
36 hot.insert(sql_hash, (stmt_name.to_string(), sql.to_string()));
37 }
38}
39
40#[inline]
41fn return_with_desync<T>(conn: &mut PgConnection, err: PgError) -> PgResult<T> {
42 if matches!(
43 err,
44 PgError::Protocol(_) | PgError::Connection(_) | PgError::Timeout(_)
45 ) {
46 conn.mark_io_desynced();
47 }
48 Err(err)
49}
50
51#[inline]
52fn encoded_sql_str(sql_buf: &[u8]) -> PgResult<&str> {
53 std::str::from_utf8(sql_buf)
54 .map_err(|e| PgError::Encode(format!("encoded SQL is not UTF-8: {}", e)))
55}
56
57async fn drain_extended_responses_after_rls_setup_error(conn: &mut PgConnection) -> PgResult<()> {
58 loop {
59 let msg = conn.recv().await?;
60 match msg {
61 crate::protocol::BackendMessage::ReadyForQuery(_) => return Ok(()),
62 crate::protocol::BackendMessage::ErrorResponse(_) => {}
63 msg if is_ignorable_session_message(&msg) => {}
64 _ => {}
66 }
67 }
68}
69
70fn copy_export_table_sql(table: &str, columns: &[String]) -> PgResult<String> {
71 let cols: Vec<String> = columns
72 .iter()
73 .map(|c| crate::driver::copy::quote_copy_column_ident(c))
74 .collect::<PgResult<_>>()?;
75
76 Ok(format!(
77 "COPY {} ({}) TO STDOUT",
78 crate::driver::copy::quote_copy_table_ref(table)?,
79 cols.join(", ")
80 ))
81}
82
83impl PooledConnection {
84 pub async fn fetch_all_uncached(
87 &mut self,
88 cmd: &qail_core::ast::Qail,
89 ) -> PgResult<Vec<crate::driver::PgRow>> {
90 self.fetch_all_uncached_with_format(cmd, ResultFormat::Text)
91 .await
92 }
93
94 pub async fn query_raw_with_params(
102 &mut self,
103 sql: &str,
104 params: &[Option<Vec<u8>>],
105 ) -> PgResult<Vec<Vec<Option<Vec<u8>>>>> {
106 let conn = self.conn_mut()?;
107 conn.query(sql, params).await
108 }
109
110 pub async fn query_rows_with_params(
116 &mut self,
117 sql: &str,
118 params: &[Option<Vec<u8>>],
119 ) -> PgResult<Vec<crate::driver::PgRow>> {
120 self.query_rows_with_params_with_format(sql, params, ResultFormat::Text)
121 .await
122 }
123
124 pub async fn query_rows_with_params_with_format(
127 &mut self,
128 sql: &str,
129 params: &[Option<Vec<u8>>],
130 result_format: ResultFormat,
131 ) -> PgResult<Vec<crate::driver::PgRow>> {
132 let conn = self.conn_mut()?;
133 conn.query_rows_with_result_format(sql, params, result_format.as_wire_code())
134 .await
135 }
136
137 pub async fn query_rows_with_param_types_with_format(
140 &mut self,
141 sql: &str,
142 param_types: &[u32],
143 params: &[Option<Vec<u8>>],
144 result_format: ResultFormat,
145 ) -> PgResult<Vec<crate::driver::PgRow>> {
146 let conn = self.conn_mut()?;
147 conn.query_rows_with_param_types_and_result_format(
148 sql,
149 param_types,
150 params,
151 result_format.as_wire_code(),
152 )
153 .await
154 }
155
156 pub async fn probe_query_with_param_types(
159 &mut self,
160 sql: &str,
161 param_types: &[u32],
162 params: &[Option<Vec<u8>>],
163 ) -> PgResult<()> {
164 let conn = self.conn_mut()?;
165 conn.probe_query_with_param_types(sql, param_types, params)
166 .await
167 }
168
169 pub async fn copy_export(&mut self, cmd: &qail_core::ast::Qail) -> PgResult<Vec<Vec<String>>> {
171 self.conn_mut()?.copy_export(cmd).await
172 }
173
174 pub async fn copy_export_stream_raw<F, Fut>(
176 &mut self,
177 cmd: &qail_core::ast::Qail,
178 on_chunk: F,
179 ) -> PgResult<()>
180 where
181 F: FnMut(Vec<u8>) -> Fut,
182 Fut: std::future::Future<Output = PgResult<()>>,
183 {
184 self.conn_mut()?.copy_export_stream_raw(cmd, on_chunk).await
185 }
186
187 pub async fn copy_export_stream_rows<F>(
189 &mut self,
190 cmd: &qail_core::ast::Qail,
191 on_row: F,
192 ) -> PgResult<()>
193 where
194 F: FnMut(Vec<String>) -> PgResult<()>,
195 {
196 self.conn_mut()?.copy_export_stream_rows(cmd, on_row).await
197 }
198
199 pub async fn copy_export_table(
201 &mut self,
202 table: &str,
203 columns: &[String],
204 ) -> PgResult<Vec<u8>> {
205 let sql = copy_export_table_sql(table, columns)?;
206 self.conn_mut()?.copy_out_raw(&sql).await
207 }
208
209 pub async fn copy_export_table_stream<F, Fut>(
211 &mut self,
212 table: &str,
213 columns: &[String],
214 on_chunk: F,
215 ) -> PgResult<()>
216 where
217 F: FnMut(Vec<u8>) -> Fut,
218 Fut: std::future::Future<Output = PgResult<()>>,
219 {
220 let sql = copy_export_table_sql(table, columns)?;
221 self.conn_mut()?.copy_out_raw_stream(&sql, on_chunk).await
222 }
223
224 pub async fn execute(&mut self, cmd: &qail_core::ast::Qail) -> PgResult<u64> {
229 use crate::protocol::AstEncoder;
230
231 let conn = self.conn_mut()?;
232
233 AstEncoder::encode_cmd_reuse_into(
234 cmd,
235 &mut conn.sql_buf,
236 &mut conn.params_buf,
237 &mut conn.write_buf,
238 )
239 .map_err(|e| PgError::Encode(e.to_string()))?;
240
241 conn.flush_write_buf().await?;
242
243 let mut affected = 0u64;
244 let mut error: Option<PgError> = None;
245 let mut flow =
246 ExtendedFlowTracker::new(ExtendedFlowConfig::parse_bind_describe_portal_execute());
247
248 loop {
249 let msg = conn.recv().await?;
250 if let Err(err) = flow.validate(&msg, "pool execute mutation", error.is_some()) {
251 return return_with_desync(conn, err);
252 }
253 match msg {
254 crate::protocol::BackendMessage::ParseComplete
255 | crate::protocol::BackendMessage::BindComplete => {}
256 crate::protocol::BackendMessage::RowDescription(_) => {}
257 crate::protocol::BackendMessage::DataRow(_) => {}
258 crate::protocol::BackendMessage::NoData => {}
259 crate::protocol::BackendMessage::CommandComplete(tag) => {
260 if error.is_none() {
261 match crate::driver::parse_affected_rows(&tag) {
262 Ok(parsed) => affected = parsed,
263 Err(err) => return return_with_desync(conn, err),
264 }
265 }
266 }
267 crate::protocol::BackendMessage::ReadyForQuery(_) => {
268 if let Some(err) = error {
269 return Err(err);
270 }
271 return Ok(affected);
272 }
273 crate::protocol::BackendMessage::ErrorResponse(err) => {
274 if error.is_none() {
275 error = Some(PgError::QueryServer(err.into()));
276 }
277 }
278 msg if is_ignorable_session_message(&msg) => {}
279 other => {
280 return return_with_desync(
281 conn,
282 unexpected_backend_message("pool execute mutation", &other),
283 );
284 }
285 }
286 }
287 }
288
289 pub async fn fetch_all_uncached_with_format(
291 &mut self,
292 cmd: &qail_core::ast::Qail,
293 result_format: ResultFormat,
294 ) -> PgResult<Vec<crate::driver::PgRow>> {
295 use crate::driver::ColumnInfo;
296 use crate::protocol::AstEncoder;
297
298 let conn = self.conn_mut()?;
299
300 AstEncoder::encode_cmd_reuse_into_with_result_format(
301 cmd,
302 &mut conn.sql_buf,
303 &mut conn.params_buf,
304 &mut conn.write_buf,
305 result_format.as_wire_code(),
306 )
307 .map_err(|e| PgError::Encode(e.to_string()))?;
308
309 conn.flush_write_buf().await?;
310
311 let mut rows: Vec<crate::driver::PgRow> = Vec::new();
312 let mut column_info: Option<Arc<ColumnInfo>> = None;
313 let mut error: Option<PgError> = None;
314 let mut flow =
315 ExtendedFlowTracker::new(ExtendedFlowConfig::parse_bind_describe_portal_execute());
316
317 loop {
318 let msg = conn.recv().await?;
319 if let Err(err) = flow.validate(&msg, "pool fetch_all execute", error.is_some()) {
320 return return_with_desync(conn, err);
321 }
322 match msg {
323 crate::protocol::BackendMessage::ParseComplete
324 | crate::protocol::BackendMessage::BindComplete => {}
325 crate::protocol::BackendMessage::RowDescription(fields) => {
326 column_info = Some(Arc::new(ColumnInfo::from_fields(&fields)));
327 }
328 crate::protocol::BackendMessage::DataRow(data) => {
329 if error.is_none() {
330 rows.push(crate::driver::PgRow {
331 columns: data,
332 column_info: column_info.clone(),
333 });
334 }
335 }
336 crate::protocol::BackendMessage::NoData => {}
337 crate::protocol::BackendMessage::CommandComplete(_) => {}
338 crate::protocol::BackendMessage::ReadyForQuery(_) => {
339 if let Some(err) = error {
340 return Err(err);
341 }
342 return Ok(rows);
343 }
344 crate::protocol::BackendMessage::ErrorResponse(err) => {
345 if error.is_none() {
346 error = Some(PgError::QueryServer(err.into()));
347 }
348 }
349 msg if is_ignorable_session_message(&msg) => {}
350 other => {
351 return return_with_desync(
352 conn,
353 unexpected_backend_message("pool fetch_all execute", &other),
354 );
355 }
356 }
357 }
358 }
359
360 pub async fn fetch_all_fast(
364 &mut self,
365 cmd: &qail_core::ast::Qail,
366 ) -> PgResult<Vec<crate::driver::PgRow>> {
367 self.fetch_all_fast_with_format(cmd, ResultFormat::Text)
368 .await
369 }
370
371 pub async fn fetch_all_fast_with_format(
373 &mut self,
374 cmd: &qail_core::ast::Qail,
375 result_format: ResultFormat,
376 ) -> PgResult<Vec<crate::driver::PgRow>> {
377 use crate::protocol::AstEncoder;
378
379 let conn = self.conn_mut()?;
380
381 AstEncoder::encode_cmd_reuse_into_with_result_format(
382 cmd,
383 &mut conn.sql_buf,
384 &mut conn.params_buf,
385 &mut conn.write_buf,
386 result_format.as_wire_code(),
387 )
388 .map_err(|e| PgError::Encode(e.to_string()))?;
389
390 conn.flush_write_buf().await?;
391
392 let mut rows: Vec<crate::driver::PgRow> = Vec::with_capacity(32);
393 let mut error: Option<PgError> = None;
394 let mut flow = ExtendedFlowTracker::new(ExtendedFlowConfig::parse_bind_execute(true));
395
396 loop {
397 let res = conn.recv_with_data_fast().await;
398 match res {
399 Ok((msg_type, data)) => {
400 if let Err(err) = flow.validate_msg_type(
401 msg_type,
402 "pool fetch_all_fast execute",
403 error.is_some(),
404 ) {
405 return return_with_desync(conn, err);
406 }
407 match msg_type {
408 b'D' => {
409 if error.is_none()
410 && let Some(columns) = data
411 {
412 rows.push(crate::driver::PgRow {
413 columns,
414 column_info: None,
415 });
416 }
417 }
418 b'Z' => {
419 if let Some(err) = error {
420 return Err(err);
421 }
422 return Ok(rows);
423 }
424 _ => {}
425 }
426 }
427 Err(e) => {
428 if matches!(&e, PgError::QueryServer(_)) {
429 if error.is_none() {
430 error = Some(e);
431 }
432 continue;
433 }
434 return Err(e);
435 }
436 }
437 }
438 }
439
440 pub async fn fetch_all_cached(
445 &mut self,
446 cmd: &qail_core::ast::Qail,
447 ) -> PgResult<Vec<crate::driver::PgRow>> {
448 self.fetch_all_cached_with_format(cmd, ResultFormat::Text)
449 .await
450 }
451
452 pub async fn fetch_all_cached_with_format(
454 &mut self,
455 cmd: &qail_core::ast::Qail,
456 result_format: ResultFormat,
457 ) -> PgResult<Vec<crate::driver::PgRow>> {
458 let mut retried = false;
459 loop {
460 match self
461 .fetch_all_cached_with_format_once(cmd, result_format)
462 .await
463 {
464 Ok(rows) => return Ok(rows),
465 Err(err)
466 if !retried
467 && (err.is_prepared_statement_retryable()
468 || err.is_prepared_statement_already_exists()) =>
469 {
470 retried = true;
471 if err.is_prepared_statement_retryable()
472 && let Some(conn) = self.conn.as_mut()
473 {
474 conn.clear_prepared_statement_state();
475 }
476 }
477 Err(err) => return Err(err),
478 }
479 }
480 }
481
482 pub async fn fetch_typed<T: crate::driver::row::QailRow>(
484 &mut self,
485 cmd: &qail_core::ast::Qail,
486 ) -> PgResult<Vec<T>> {
487 self.fetch_typed_with_format(cmd, ResultFormat::Text).await
488 }
489
490 pub async fn fetch_typed_with_format<T: crate::driver::row::QailRow>(
495 &mut self,
496 cmd: &qail_core::ast::Qail,
497 result_format: ResultFormat,
498 ) -> PgResult<Vec<T>> {
499 let rows = self
500 .fetch_all_cached_with_format(cmd, result_format)
501 .await?;
502 Ok(rows.iter().map(T::from_row).collect())
503 }
504
505 pub async fn fetch_one_typed<T: crate::driver::row::QailRow>(
507 &mut self,
508 cmd: &qail_core::ast::Qail,
509 ) -> PgResult<Option<T>> {
510 self.fetch_one_typed_with_format(cmd, ResultFormat::Text)
511 .await
512 }
513
514 pub async fn fetch_one_typed_with_format<T: crate::driver::row::QailRow>(
516 &mut self,
517 cmd: &qail_core::ast::Qail,
518 result_format: ResultFormat,
519 ) -> PgResult<Option<T>> {
520 let rows = self
521 .fetch_all_cached_with_format(cmd, result_format)
522 .await?;
523 Ok(rows.first().map(T::from_row))
524 }
525
526 async fn fetch_all_cached_with_format_once(
527 &mut self,
528 cmd: &qail_core::ast::Qail,
529 result_format: ResultFormat,
530 ) -> PgResult<Vec<crate::driver::PgRow>> {
531 use crate::driver::ColumnInfo;
532 use std::collections::hash_map::DefaultHasher;
533 use std::hash::{Hash, Hasher};
534
535 let pool = std::sync::Arc::clone(&self.pool);
536 let conn = self.conn.as_mut().ok_or_else(|| {
537 PgError::Connection("Connection already released back to pool".into())
538 })?;
539
540 conn.sql_buf.clear();
541 conn.params_buf.clear();
542
543 match cmd.action {
545 qail_core::ast::Action::Get | qail_core::ast::Action::With => {
546 crate::protocol::ast_encoder::dml::encode_select(
547 cmd,
548 &mut conn.sql_buf,
549 &mut conn.params_buf,
550 )?;
551 }
552 qail_core::ast::Action::Add => {
553 crate::protocol::ast_encoder::dml::encode_insert(
554 cmd,
555 &mut conn.sql_buf,
556 &mut conn.params_buf,
557 )?;
558 }
559 qail_core::ast::Action::Set => {
560 crate::protocol::ast_encoder::dml::encode_update(
561 cmd,
562 &mut conn.sql_buf,
563 &mut conn.params_buf,
564 )?;
565 }
566 qail_core::ast::Action::Del => {
567 crate::protocol::ast_encoder::dml::encode_delete(
568 cmd,
569 &mut conn.sql_buf,
570 &mut conn.params_buf,
571 )?;
572 }
573 _ => {
574 return self
576 .fetch_all_uncached_with_format(cmd, result_format)
577 .await;
578 }
579 }
580
581 let mut hasher = DefaultHasher::new();
582 conn.sql_buf.hash(&mut hasher);
583 let sql_hash = hasher.finish();
584
585 let is_cache_miss = !conn.stmt_cache.contains(&sql_hash);
586
587 conn.write_buf.clear();
588
589 let stmt_name = if let Some(name) = conn.stmt_cache.get(&sql_hash) {
590 name
591 } else {
592 let name = format!("qail_{:x}", sql_hash);
593
594 conn.evict_prepared_if_full();
595
596 let sql_str = encoded_sql_str(&conn.sql_buf)?;
597
598 use crate::protocol::PgEncoder;
599 let parse_msg = PgEncoder::try_encode_parse(&name, sql_str, &[])?;
600 let describe_msg = PgEncoder::try_encode_describe(false, &name)?;
601 conn.write_buf.extend_from_slice(&parse_msg);
602 conn.write_buf.extend_from_slice(&describe_msg);
603
604 conn.stmt_cache.put(sql_hash, name.clone());
605 conn.prepared_statements
606 .insert(name.clone(), sql_str.to_string());
607
608 name
609 };
610
611 use crate::protocol::PgEncoder;
612 if let Err(e) = PgEncoder::encode_bind_to_with_result_format(
613 &mut conn.write_buf,
614 &stmt_name,
615 &conn.params_buf,
616 result_format.as_wire_code(),
617 ) {
618 if is_cache_miss {
619 conn.stmt_cache.remove(&sql_hash);
620 conn.prepared_statements.remove(&stmt_name);
621 conn.column_info_cache.remove(&sql_hash);
622 }
623 return Err(PgError::Encode(e.to_string()));
624 }
625 PgEncoder::encode_execute_to(&mut conn.write_buf);
626 PgEncoder::encode_sync_to(&mut conn.write_buf);
627
628 if let Err(err) = conn.flush_write_buf().await {
629 if is_cache_miss {
630 conn.stmt_cache.remove(&sql_hash);
631 conn.prepared_statements.remove(&stmt_name);
632 conn.column_info_cache.remove(&sql_hash);
633 }
634 return Err(err);
635 }
636
637 let cached_column_info = conn.column_info_cache.get(&sql_hash).cloned();
638
639 let mut rows: Vec<crate::driver::PgRow> = Vec::with_capacity(32);
640 let mut column_info: Option<Arc<ColumnInfo>> = cached_column_info;
641 let mut error: Option<PgError> = None;
642 let mut flow = ExtendedFlowTracker::new(
643 ExtendedFlowConfig::parse_describe_statement_bind_execute(is_cache_miss),
644 );
645
646 loop {
647 let msg = match conn.recv().await {
648 Ok(msg) => msg,
649 Err(err) => {
650 if is_cache_miss && !flow.saw_parse_complete() {
651 conn.stmt_cache.remove(&sql_hash);
652 conn.prepared_statements.remove(&stmt_name);
653 conn.column_info_cache.remove(&sql_hash);
654 }
655 return Err(err);
656 }
657 };
658 if let Err(err) = flow.validate(&msg, "pool fetch_all_cached execute", error.is_some())
659 {
660 if is_cache_miss && !flow.saw_parse_complete() {
661 conn.stmt_cache.remove(&sql_hash);
662 conn.prepared_statements.remove(&stmt_name);
663 conn.column_info_cache.remove(&sql_hash);
664 }
665 return return_with_desync(conn, err);
666 }
667 match msg {
668 crate::protocol::BackendMessage::ParseComplete => {}
669 crate::protocol::BackendMessage::BindComplete => {}
670 crate::protocol::BackendMessage::ParameterDescription(_) => {}
671 crate::protocol::BackendMessage::RowDescription(fields) => {
672 let info = Arc::new(ColumnInfo::from_fields(&fields));
673 if is_cache_miss {
674 conn.column_info_cache.insert(sql_hash, Arc::clone(&info));
675 }
676 column_info = Some(info);
677 }
678 crate::protocol::BackendMessage::DataRow(data) => {
679 if error.is_none() {
680 rows.push(crate::driver::PgRow {
681 columns: data,
682 column_info: column_info.clone(),
683 });
684 }
685 }
686 crate::protocol::BackendMessage::CommandComplete(_) => {}
687 crate::protocol::BackendMessage::ReadyForQuery(_) => {
688 if let Some(err) = error {
689 if is_cache_miss
690 && !flow.saw_parse_complete()
691 && !err.is_prepared_statement_already_exists()
692 {
693 conn.stmt_cache.remove(&sql_hash);
694 conn.prepared_statements.remove(&stmt_name);
695 conn.column_info_cache.remove(&sql_hash);
696 }
697 return Err(err);
698 }
699 if is_cache_miss && !flow.saw_parse_complete() {
700 conn.stmt_cache.remove(&sql_hash);
701 conn.prepared_statements.remove(&stmt_name);
702 conn.column_info_cache.remove(&sql_hash);
703 return return_with_desync(
704 conn,
705 PgError::Protocol(
706 "Cache miss query reached ReadyForQuery without ParseComplete"
707 .to_string(),
708 ),
709 );
710 }
711 if is_cache_miss && let Some(sql) = conn.prepared_statements.get(&stmt_name) {
712 register_hot_statement_after_parse_success(
713 &pool, sql_hash, &stmt_name, sql,
714 );
715 }
716 return Ok(rows);
717 }
718 crate::protocol::BackendMessage::ErrorResponse(err) => {
719 if error.is_none() {
720 error = Some(PgError::QueryServer(err.into()));
721 }
722 }
723 msg if is_ignorable_session_message(&msg) => {}
724 other => {
725 if is_cache_miss && !flow.saw_parse_complete() {
726 conn.stmt_cache.remove(&sql_hash);
727 conn.prepared_statements.remove(&stmt_name);
728 conn.column_info_cache.remove(&sql_hash);
729 }
730 return return_with_desync(
731 conn,
732 unexpected_backend_message("pool fetch_all_cached execute", &other),
733 );
734 }
735 }
736 }
737 }
738
739 pub async fn fetch_all_with_rls(
758 &mut self,
759 cmd: &qail_core::ast::Qail,
760 rls_sql: &str,
761 ) -> PgResult<Vec<crate::driver::PgRow>> {
762 self.fetch_all_with_rls_with_format(cmd, rls_sql, ResultFormat::Text)
763 .await
764 }
765
766 pub async fn fetch_all_with_rls_with_format(
768 &mut self,
769 cmd: &qail_core::ast::Qail,
770 rls_sql: &str,
771 result_format: ResultFormat,
772 ) -> PgResult<Vec<crate::driver::PgRow>> {
773 let mut retried = false;
774 loop {
775 match self
776 .fetch_all_with_rls_with_format_once(cmd, rls_sql, result_format)
777 .await
778 {
779 Ok(rows) => return Ok(rows),
780 Err(err)
781 if !retried
782 && (err.is_prepared_statement_retryable()
783 || err.is_prepared_statement_already_exists()) =>
784 {
785 retried = true;
786 if let Some(conn) = self.conn.as_mut() {
787 if err.is_prepared_statement_retryable() {
788 conn.clear_prepared_statement_state();
789 }
790 let _ = conn.execute_simple("ROLLBACK").await;
793 }
794 self.rls_dirty = false;
795 }
796 Err(err) => return Err(err),
797 }
798 }
799 }
800
801 async fn fetch_all_with_rls_with_format_once(
802 &mut self,
803 cmd: &qail_core::ast::Qail,
804 rls_sql: &str,
805 result_format: ResultFormat,
806 ) -> PgResult<Vec<crate::driver::PgRow>> {
807 use crate::driver::ColumnInfo;
808 use std::collections::hash_map::DefaultHasher;
809 use std::hash::{Hash, Hasher};
810
811 let pool = std::sync::Arc::clone(&self.pool);
812 let conn = self.conn.as_mut().ok_or_else(|| {
813 PgError::Connection("Connection already released back to pool".into())
814 })?;
815
816 if !crate::protocol::AstEncoder::encode_cacheable_cmd_sql_to(
817 cmd,
818 &mut conn.sql_buf,
819 &mut conn.params_buf,
820 )? {
821 conn.execute_simple(rls_sql).await?;
823 self.rls_dirty = true;
824 return self
825 .fetch_all_uncached_with_format(cmd, result_format)
826 .await;
827 }
828
829 let mut hasher = DefaultHasher::new();
830 conn.sql_buf.hash(&mut hasher);
831 let sql_hash = hasher.finish();
832
833 let is_cache_miss = !conn.stmt_cache.contains(&sql_hash);
834
835 conn.write_buf.clear();
836
837 let rls_msg = crate::protocol::PgEncoder::try_encode_query_string(rls_sql)?;
842 conn.write_buf.extend_from_slice(&rls_msg);
843
844 let stmt_name = if let Some(name) = conn.stmt_cache.get(&sql_hash) {
846 name
847 } else {
848 let name = format!("qail_{:x}", sql_hash);
849
850 conn.evict_prepared_if_full();
851
852 let sql_str = encoded_sql_str(&conn.sql_buf)?;
853
854 use crate::protocol::PgEncoder;
855 let parse_msg = PgEncoder::try_encode_parse(&name, sql_str, &[])?;
856 let describe_msg = PgEncoder::try_encode_describe(false, &name)?;
857 conn.write_buf.extend_from_slice(&parse_msg);
858 conn.write_buf.extend_from_slice(&describe_msg);
859
860 conn.stmt_cache.put(sql_hash, name.clone());
861 conn.prepared_statements
862 .insert(name.clone(), sql_str.to_string());
863
864 name
865 };
866
867 use crate::protocol::PgEncoder;
868 if let Err(e) = PgEncoder::encode_bind_to_with_result_format(
869 &mut conn.write_buf,
870 &stmt_name,
871 &conn.params_buf,
872 result_format.as_wire_code(),
873 ) {
874 rollback_cache_miss_statement_registration(conn, is_cache_miss, sql_hash, &stmt_name);
875 return Err(PgError::Encode(e.to_string()));
876 }
877 PgEncoder::encode_execute_to(&mut conn.write_buf);
878 PgEncoder::encode_sync_to(&mut conn.write_buf);
879
880 if let Err(err) = conn.flush_write_buf().await {
882 rollback_cache_miss_statement_registration(conn, is_cache_miss, sql_hash, &stmt_name);
883 return Err(err);
884 }
885
886 self.rls_dirty = true;
888
889 let mut rls_error: Option<PgError> = None;
893 loop {
894 let msg = match conn.recv().await {
895 Ok(msg) => msg,
896 Err(err) => {
897 rollback_cache_miss_statement_registration(
898 conn,
899 is_cache_miss,
900 sql_hash,
901 &stmt_name,
902 );
903 return Err(err);
904 }
905 };
906 match msg {
907 crate::protocol::BackendMessage::ReadyForQuery(_) => {
908 if let Some(err) = rls_error {
910 rollback_cache_miss_statement_registration(
911 conn,
912 is_cache_miss,
913 sql_hash,
914 &stmt_name,
915 );
916 if let Err(drain_err) =
917 drain_extended_responses_after_rls_setup_error(conn).await
918 {
919 tracing::warn!(
920 error = %drain_err,
921 "failed to drain pipelined extended responses after RLS setup error"
922 );
923 }
924 return Err(err);
925 }
926 break;
927 }
928 crate::protocol::BackendMessage::ErrorResponse(err) => {
929 if rls_error.is_none() {
930 rls_error = Some(PgError::QueryServer(err.into()));
931 }
932 }
933 crate::protocol::BackendMessage::CommandComplete(_)
935 | crate::protocol::BackendMessage::DataRow(_)
936 | crate::protocol::BackendMessage::RowDescription(_)
937 | crate::protocol::BackendMessage::ParseComplete
938 | crate::protocol::BackendMessage::BindComplete => {}
939 msg if is_ignorable_session_message(&msg) => {}
940 other => {
941 rollback_cache_miss_statement_registration(
942 conn,
943 is_cache_miss,
944 sql_hash,
945 &stmt_name,
946 );
947 return return_with_desync(
948 conn,
949 unexpected_backend_message("pool rls setup", &other),
950 );
951 }
952 }
953 }
954
955 let cached_column_info = conn.column_info_cache.get(&sql_hash).cloned();
957
958 let mut rows: Vec<crate::driver::PgRow> = Vec::with_capacity(32);
959 let mut column_info: Option<std::sync::Arc<ColumnInfo>> = cached_column_info;
960 let mut error: Option<PgError> = None;
961 let mut flow = ExtendedFlowTracker::new(
962 ExtendedFlowConfig::parse_describe_statement_bind_execute(is_cache_miss),
963 );
964
965 loop {
966 let msg = match conn.recv().await {
967 Ok(msg) => msg,
968 Err(err) => {
969 if is_cache_miss && !flow.saw_parse_complete() {
970 rollback_cache_miss_statement_registration(
971 conn,
972 is_cache_miss,
973 sql_hash,
974 &stmt_name,
975 );
976 }
977 return Err(err);
978 }
979 };
980 if let Err(err) =
981 flow.validate(&msg, "pool fetch_all_with_rls execute", error.is_some())
982 {
983 if is_cache_miss && !flow.saw_parse_complete() {
984 rollback_cache_miss_statement_registration(
985 conn,
986 is_cache_miss,
987 sql_hash,
988 &stmt_name,
989 );
990 }
991 return return_with_desync(conn, err);
992 }
993 match msg {
994 crate::protocol::BackendMessage::ParseComplete => {}
995 crate::protocol::BackendMessage::BindComplete => {}
996 crate::protocol::BackendMessage::ParameterDescription(_) => {}
997 crate::protocol::BackendMessage::RowDescription(fields) => {
998 let info = std::sync::Arc::new(ColumnInfo::from_fields(&fields));
999 if is_cache_miss {
1000 conn.column_info_cache
1001 .insert(sql_hash, std::sync::Arc::clone(&info));
1002 }
1003 column_info = Some(info);
1004 }
1005 crate::protocol::BackendMessage::DataRow(data) => {
1006 if error.is_none() {
1007 rows.push(crate::driver::PgRow {
1008 columns: data,
1009 column_info: column_info.clone(),
1010 });
1011 }
1012 }
1013 crate::protocol::BackendMessage::CommandComplete(_) => {}
1014 crate::protocol::BackendMessage::ReadyForQuery(_) => {
1015 if let Some(err) = error {
1016 if is_cache_miss
1017 && !flow.saw_parse_complete()
1018 && !err.is_prepared_statement_already_exists()
1019 {
1020 rollback_cache_miss_statement_registration(
1021 conn,
1022 is_cache_miss,
1023 sql_hash,
1024 &stmt_name,
1025 );
1026 }
1027 return Err(err);
1028 }
1029 if is_cache_miss && !flow.saw_parse_complete() {
1030 rollback_cache_miss_statement_registration(
1031 conn,
1032 is_cache_miss,
1033 sql_hash,
1034 &stmt_name,
1035 );
1036 return return_with_desync(
1037 conn,
1038 PgError::Protocol(
1039 "Cache miss query reached ReadyForQuery without ParseComplete"
1040 .to_string(),
1041 ),
1042 );
1043 }
1044 if is_cache_miss && let Some(sql) = conn.prepared_statements.get(&stmt_name) {
1045 register_hot_statement_after_parse_success(
1046 &pool, sql_hash, &stmt_name, sql,
1047 );
1048 }
1049 return Ok(rows);
1050 }
1051 crate::protocol::BackendMessage::ErrorResponse(err) => {
1052 if error.is_none() {
1053 error = Some(PgError::QueryServer(err.into()));
1054 }
1055 }
1056 msg if is_ignorable_session_message(&msg) => {}
1057 other => {
1058 if is_cache_miss && !flow.saw_parse_complete() {
1059 rollback_cache_miss_statement_registration(
1060 conn,
1061 is_cache_miss,
1062 sql_hash,
1063 &stmt_name,
1064 );
1065 }
1066 return return_with_desync(
1067 conn,
1068 unexpected_backend_message("pool fetch_all_with_rls execute", &other),
1069 );
1070 }
1071 }
1072 }
1073 }
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use super::{copy_export_table_sql, encoded_sql_str, return_with_desync};
1079
1080 #[cfg(unix)]
1081 fn test_conn() -> crate::driver::PgConnection {
1082 use crate::driver::connection::StatementCache;
1083 use crate::driver::stream::PgStream;
1084 use bytes::BytesMut;
1085 use std::collections::{HashMap, VecDeque};
1086 use std::num::NonZeroUsize;
1087 use tokio::net::UnixStream;
1088
1089 let (unix_stream, _peer) = UnixStream::pair().expect("unix stream pair");
1090 crate::driver::PgConnection {
1091 stream: PgStream::Unix(unix_stream),
1092 buffer: BytesMut::with_capacity(1024),
1093 write_buf: BytesMut::with_capacity(1024),
1094 sql_buf: BytesMut::with_capacity(256),
1095 params_buf: Vec::new(),
1096 prepared_statements: HashMap::new(),
1097 stmt_cache: StatementCache::new(NonZeroUsize::new(2).expect("non-zero")),
1098 column_info_cache: HashMap::new(),
1099 process_id: 0,
1100 cancel_key_bytes: Vec::new(),
1101 requested_protocol_minor: crate::driver::PgConnection::default_protocol_minor(),
1102 negotiated_protocol_minor: crate::driver::PgConnection::default_protocol_minor(),
1103 notifications: VecDeque::new(),
1104 replication_stream_active: false,
1105 replication_mode_enabled: false,
1106 last_replication_wal_end: None,
1107 io_desynced: false,
1108 pending_statement_closes: Vec::new(),
1109 draining_statement_closes: false,
1110 }
1111 }
1112
1113 #[test]
1114 fn pool_copy_export_table_sql_preserves_schema_qualified_table() {
1115 let sql = copy_export_table_sql(
1116 "tenant_a.users",
1117 &["id".to_string(), "display\"name".to_string()],
1118 )
1119 .unwrap();
1120
1121 assert_eq!(
1122 sql,
1123 "COPY \"tenant_a\".\"users\" (\"id\", \"display\"\"name\") TO STDOUT"
1124 );
1125 }
1126
1127 #[test]
1128 fn pool_copy_export_table_sql_rejects_nul_bytes() {
1129 assert!(copy_export_table_sql("tenant\0.users", &["id".to_string()]).is_err());
1130 assert!(copy_export_table_sql("users", &["id\0".to_string()]).is_err());
1131 }
1132
1133 #[test]
1134 fn pool_encoded_sql_str_rejects_invalid_utf8() {
1135 let err = encoded_sql_str(&[0xff]).expect_err("invalid SQL UTF-8 must fail");
1136 assert!(err.to_string().contains("encoded SQL is not UTF-8"));
1137 }
1138
1139 #[cfg(unix)]
1140 #[tokio::test]
1141 async fn pool_return_with_desync_marks_protocol_error() {
1142 let mut conn = test_conn();
1143
1144 let err = return_with_desync::<()>(
1145 &mut conn,
1146 crate::driver::PgError::Protocol("bad response ordering".to_string()),
1147 )
1148 .expect_err("protocol error must be returned");
1149
1150 assert!(err.to_string().contains("bad response ordering"));
1151 assert!(conn.is_io_desynced());
1152 }
1153}