1use std::collections::HashMap;
9use std::sync::Arc;
10
11use tokio::io::{AsyncRead, AsyncWrite};
12use tokio::net::TcpListener;
13
14use super::catalog_views::translate_pg_catalog_query;
15use super::protocol::{
16 read_frame, read_startup, write_frame, write_raw_byte, BackendMessage, ColumnDescriptor,
17 DescribeTarget, FrontendMessage, PgWireError, TransactionStatus,
18};
19use super::types::{pg_param_to_param_value, value_to_pg_wire_bytes, PgOid};
20use crate::auth::Role;
21use crate::runtime::query_request::{
22 ParamValue, PreparedId, PreparedRegistry, QueryRequest, QueryRequestExecutor,
23};
24use crate::runtime::RedDBRuntime;
25use crate::storage::query::unified::{UnifiedRecord, UnifiedResult};
26use reddb_rql::ast::QueryExpr;
27use reddb_types::Value;
28
29#[derive(Debug, Clone)]
31pub struct PgWireConfig {
32 pub bind_addr: String,
35 pub server_version: String,
39 pub tls: Option<crate::wire::tls::WireTlsConfig>,
48}
49
50struct PgAuthContext {
57 username: String,
58 role: Role,
59 tenant: Option<String>,
60 _lease: crate::auth::store::ConnectionLease,
61}
62
63#[derive(Debug, Clone)]
64struct PgPreparedStatement {
65 sql: String,
66 prepared_id: PreparedId,
67 param_type_oids: Vec<u32>,
68}
69
70#[derive(Debug, Clone)]
71struct PgPortal {
72 sql: String,
73 prepared_id: PreparedId,
74 params: Vec<ParamValue>,
75 #[allow(dead_code)]
76 result_format_codes: Vec<i16>,
77 row_description_sent: bool,
78 result: Option<crate::runtime::OwnedQueryResult>,
79 row_offset: usize,
80}
81
82impl Default for PgWireConfig {
83 fn default() -> Self {
84 Self {
85 bind_addr: "127.0.0.1:5432".to_string(),
86 server_version: "15.0 (RedDB 3.1)".to_string(),
87 tls: None,
88 }
89 }
90}
91
92fn run_runtime_blocking<T>(f: impl FnOnce() -> T) -> T {
112 use tokio::runtime::{Handle, RuntimeFlavor};
113 match Handle::try_current().map(|h| h.runtime_flavor()) {
114 Ok(RuntimeFlavor::MultiThread) => tokio::task::block_in_place(f),
115 _ => f(),
116 }
117}
118
119fn pg_frame_requires_execution(frame: &FrontendMessage) -> bool {
120 matches!(
121 frame,
122 FrontendMessage::Query(_)
123 | FrontendMessage::Parse(_)
124 | FrontendMessage::Describe(_)
125 | FrontendMessage::Execute(_)
126 )
127}
128
129async fn acquire_pg_execution(
130 runtime: &RedDBRuntime,
131 latency_sensitive: bool,
132) -> Result<crate::transport::execution_admission::ExecutionPermit, &'static str> {
133 use crate::transport::execution_admission::ExecutionAdmissionError;
134
135 let admission = runtime.execution_admission();
136 match tokio::time::timeout(
137 runtime.execution_queue_timeout(),
138 admission.acquire_classified(latency_sensitive),
139 )
140 .await
141 {
142 Ok(Ok(permit)) => Ok(permit),
143 Ok(Err(ExecutionAdmissionError::QueueFull)) => {
144 Err("execution_queue_full: engine execution queue is full; retryable=true")
145 }
146 Ok(Err(ExecutionAdmissionError::Closed)) => {
147 Err("execution_admission_closed: engine execution admission is unavailable")
148 }
149 Err(_) => Err(
150 "execution_queue_timeout: deadline expired while waiting to execute; retryable=true",
151 ),
152 }
153}
154
155struct ExecutionResponseWriter<'a, S> {
160 inner: &'a mut S,
161 permit: Option<crate::transport::execution_admission::ExecutionPermit>,
162}
163
164impl<'a, S> ExecutionResponseWriter<'a, S> {
165 fn new(
166 inner: &'a mut S,
167 permit: Option<crate::transport::execution_admission::ExecutionPermit>,
168 ) -> Self {
169 Self { inner, permit }
170 }
171}
172
173impl<S: AsyncWrite + Unpin> AsyncWrite for ExecutionResponseWriter<'_, S> {
174 fn poll_write(
175 mut self: std::pin::Pin<&mut Self>,
176 cx: &mut std::task::Context<'_>,
177 buf: &[u8],
178 ) -> std::task::Poll<Result<usize, std::io::Error>> {
179 drop(self.permit.take());
180 std::pin::Pin::new(&mut *self.inner).poll_write(cx, buf)
181 }
182
183 fn poll_flush(
184 mut self: std::pin::Pin<&mut Self>,
185 cx: &mut std::task::Context<'_>,
186 ) -> std::task::Poll<Result<(), std::io::Error>> {
187 drop(self.permit.take());
188 std::pin::Pin::new(&mut *self.inner).poll_flush(cx)
189 }
190
191 fn poll_shutdown(
192 mut self: std::pin::Pin<&mut Self>,
193 cx: &mut std::task::Context<'_>,
194 ) -> std::task::Poll<Result<(), std::io::Error>> {
195 drop(self.permit.take());
196 std::pin::Pin::new(&mut *self.inner).poll_shutdown(cx)
197 }
198}
199
200fn execute_with_pg_auth_context<T>(
208 auth_context: Option<&PgAuthContext>,
209 f: impl FnOnce() -> T,
210) -> T {
211 let Some(auth_context) = auth_context else {
212 return f();
213 };
214 let _guard = PgAuthContextGuard::install(auth_context);
215 f()
216}
217
218struct PgAuthContextGuard {
222 previous_principal: Option<crate::auth::UserId>,
223 previous_identity: Option<(String, Role)>,
224 previous_tenant: Option<String>,
225}
226
227impl PgAuthContextGuard {
228 fn install(auth_context: &PgAuthContext) -> Self {
229 let previous_tenant = crate::runtime::execution_context::current_tenant();
230 let previous_principal = crate::runtime::execution_context::current_auth_principal();
231 let previous_identity = crate::runtime::execution_context::current_auth_identity();
232 match &auth_context.tenant {
233 Some(tenant) => crate::runtime::execution_context::set_current_tenant(tenant.clone()),
234 None => crate::runtime::execution_context::clear_current_tenant(),
235 }
236 crate::runtime::execution_context::set_current_auth_principal(
237 crate::auth::UserId::from_parts(auth_context.tenant.as_deref(), &auth_context.username),
238 auth_context.role,
239 );
240 Self {
241 previous_principal,
242 previous_identity,
243 previous_tenant,
244 }
245 }
246}
247
248impl Drop for PgAuthContextGuard {
249 fn drop(&mut self) {
250 match (
251 self.previous_principal.take(),
252 self.previous_identity.take(),
253 ) {
254 (Some(principal), Some((_, role))) => {
255 crate::runtime::execution_context::set_current_auth_principal(principal, role)
256 }
257 _ => crate::runtime::execution_context::clear_current_auth_identity(),
258 }
259 match self.previous_tenant.take() {
260 Some(tenant) => crate::runtime::execution_context::set_current_tenant(tenant),
261 None => crate::runtime::execution_context::clear_current_tenant(),
262 }
263 }
264}
265
266fn is_loopback_bind(bind_addr: &str) -> bool {
270 crate::transport::is_loopback_bind(bind_addr)
271}
272
273fn password_from_message(payload: &[u8]) -> Option<String> {
276 let nul = payload.iter().position(|&b| b == 0)?;
277 std::str::from_utf8(&payload[..nul])
278 .ok()
279 .map(|password| password.to_string())
280}
281
282pub async fn start_pg_wire_listener(
285 config: PgWireConfig,
286 runtime: Arc<RedDBRuntime>,
287) -> Result<(), Box<dyn std::error::Error>> {
288 let listener = TcpListener::bind(&config.bind_addr).await?;
289 start_pg_wire_listener_on(listener, config, runtime).await
290}
291
292pub async fn start_pg_wire_listener_on(
294 listener: TcpListener,
295 config: PgWireConfig,
296 runtime: Arc<RedDBRuntime>,
297) -> Result<(), Box<dyn std::error::Error>> {
298 let trust_active = runtime
303 .auth_store()
304 .map(|store| !store.is_enabled())
305 .unwrap_or(true)
306 && is_loopback_bind(&config.bind_addr);
307 if trust_active {
308 tracing::warn!(
309 transport = "pg-wire",
310 bind = %config.bind_addr,
311 "PG-Wire trust authentication is active (no auth store configured); \
312 loopback-only. Configure auth before exposing this listener."
313 );
314 }
315 let tls_acceptor = match config.tls.as_ref() {
321 Some(tls_cfg) => Some(crate::wire::tls::build_tls_acceptor(tls_cfg)?),
322 None => None,
323 };
324 tracing::info!(
325 transport = "pg-wire",
326 bind = %config.bind_addr,
327 tls = tls_acceptor.is_some(),
328 "listener online"
329 );
330 let cfg = Arc::new(config);
331 let admission = crate::wire::admission::ConnectionAdmission::new("pg-wire");
332 loop {
333 let (stream, peer) = listener.accept().await?;
334 let Some(permit) = admission.try_admit() else {
338 continue;
339 };
340 if let Err(error) = stream.set_nodelay(true) {
343 tracing::warn!(transport = "pg-wire", peer = %peer, err = %error,
344 "failed to configure connection");
345 continue;
346 }
347 let rt = Arc::clone(&runtime);
348 let cfg = Arc::clone(&cfg);
349 let acceptor = tls_acceptor.clone();
350 let peer_str = peer.to_string();
351 tokio::spawn(async move {
352 let _permit = permit;
353 if let Err(e) = handle_connection(stream, rt, cfg, acceptor).await {
354 tracing::warn!(
355 transport = "pg-wire",
356 peer = %peer_str,
357 err = %e,
358 "connection failed"
359 );
360 }
361 });
362 }
363}
364
365pub(crate) async fn handle_connection<S>(
377 mut stream: S,
378 runtime: Arc<RedDBRuntime>,
379 config: Arc<PgWireConfig>,
380 tls_acceptor: Option<tokio_rustls::TlsAcceptor>,
381) -> Result<(), PgWireError>
382where
383 S: AsyncRead + AsyncWrite + Unpin + Send,
384{
385 loop {
393 let startup = crate::wire::admission::with_handshake_deadline(read_startup(&mut stream))
394 .await
395 .map_err(PgWireError::from)??;
396 match startup {
397 FrontendMessage::SslRequest => {
398 if let Some(acceptor) = tls_acceptor.as_ref() {
399 write_raw_byte(&mut stream, b'S').await?;
402 let tls_stream =
403 crate::wire::admission::with_handshake_deadline(acceptor.accept(stream))
404 .await??;
405 return serve_session_after_tls(tls_stream, runtime, config).await;
406 }
407 write_raw_byte(&mut stream, b'N').await?;
410 continue;
411 }
412 FrontendMessage::GssEncRequest => {
413 write_raw_byte(&mut stream, b'N').await?;
416 continue;
417 }
418 FrontendMessage::Startup(params) => {
419 return run_pg_session(stream, runtime, config, false, params).await;
420 }
421 FrontendMessage::Unknown { .. } => {
422 return Ok(());
424 }
425 other => {
426 return Err(PgWireError::Protocol(format!(
427 "unexpected startup frame: {other:?}"
428 )));
429 }
430 }
431 }
432}
433
434async fn serve_session_after_tls<S>(
439 mut stream: tokio_rustls::server::TlsStream<S>,
440 runtime: Arc<RedDBRuntime>,
441 config: Arc<PgWireConfig>,
442) -> Result<(), PgWireError>
443where
444 S: AsyncRead + AsyncWrite + Unpin + Send,
445{
446 loop {
447 match read_startup(&mut stream).await? {
448 FrontendMessage::SslRequest | FrontendMessage::GssEncRequest => {
449 write_raw_byte(&mut stream, b'N').await?;
450 continue;
451 }
452 FrontendMessage::Startup(params) => {
453 return run_pg_session(stream, runtime, config, true, params).await;
454 }
455 FrontendMessage::Unknown { .. } => return Ok(()),
456 other => {
457 return Err(PgWireError::Protocol(format!(
458 "unexpected startup frame: {other:?}"
459 )));
460 }
461 }
462 }
463}
464
465async fn run_pg_session<S>(
469 mut stream: S,
470 runtime: Arc<RedDBRuntime>,
471 config: Arc<PgWireConfig>,
472 over_tls: bool,
473 params: super::protocol::StartupParams,
474) -> Result<(), PgWireError>
475where
476 S: AsyncRead + AsyncWrite + Unpin + Send,
477{
478 let auth_context =
479 match authenticate_startup(&mut stream, &runtime, &config, over_tls, ¶ms).await? {
480 Some(context) => context,
481 None => return Ok(()),
484 };
485
486 let mut prepared: HashMap<String, PgPreparedStatement> = HashMap::new();
487 let mut portals: HashMap<String, PgPortal> = HashMap::new();
488 let prepared_registry = PreparedRegistry::new();
489
490 loop {
492 let frame = match read_frame(&mut stream).await {
493 Ok(f) => f,
494 Err(PgWireError::Eof) => return Ok(()),
495 Err(e) => return Err(e),
496 };
497
498 let mut execution_permit = if pg_frame_requires_execution(&frame) {
504 let latency_sensitive = matches!(
505 &frame,
506 FrontendMessage::Query(sql)
507 if crate::server::is_trivial_scalar_sql(sql)
508 );
509 match acquire_pg_execution(&runtime, latency_sensitive).await {
510 Ok(permit) => Some(permit),
511 Err(message) => {
512 send_error(&mut stream, "53000", message).await?;
516 write_frame(
517 &mut stream,
518 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
519 )
520 .await?;
521 continue;
522 }
523 }
524 } else {
525 None
526 };
527
528 match frame {
529 FrontendMessage::Query(sql) => {
530 let mut response =
531 ExecutionResponseWriter::new(&mut stream, execution_permit.take());
532 handle_simple_query(&mut response, &runtime, auth_context.as_ref(), &sql).await?;
533 }
534 FrontendMessage::Parse(msg) => {
535 let mut response =
536 ExecutionResponseWriter::new(&mut stream, execution_permit.take());
537 handle_parse(
538 &mut response,
539 &runtime,
540 &prepared_registry,
541 &mut prepared,
542 &portals,
543 msg,
544 )
545 .await?;
546 }
547 FrontendMessage::Bind(msg) => {
548 handle_bind(
549 &mut stream,
550 &prepared_registry,
551 &prepared,
552 &mut portals,
553 msg,
554 )
555 .await?;
556 }
557 FrontendMessage::Describe(msg) => {
558 let mut response =
559 ExecutionResponseWriter::new(&mut stream, execution_permit.take());
560 handle_describe(
561 &mut response,
562 &runtime,
563 auth_context.as_ref(),
564 &prepared_registry,
565 &prepared,
566 &mut portals,
567 msg,
568 )
569 .await?;
570 }
571 FrontendMessage::Execute(msg) => {
572 let mut response =
573 ExecutionResponseWriter::new(&mut stream, execution_permit.take());
574 handle_execute(
575 &mut response,
576 &runtime,
577 auth_context.as_ref(),
578 &prepared_registry,
579 &mut portals,
580 msg,
581 )
582 .await?;
583 }
584 FrontendMessage::Close(msg) => {
585 handle_close(
586 &mut stream,
587 &prepared_registry,
588 &mut prepared,
589 &mut portals,
590 msg,
591 )
592 .await?;
593 }
594 FrontendMessage::Terminate => return Ok(()),
595 FrontendMessage::Flush => {
596 continue;
599 }
600 FrontendMessage::Sync => {
601 write_frame(
602 &mut stream,
603 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
604 )
605 .await?;
606 }
607 FrontendMessage::PasswordMessage(_) => {
608 continue;
610 }
611 FrontendMessage::Unknown { tag, .. } => {
612 send_error(
613 &mut stream,
614 "0A000",
615 &format!("unsupported frame tag 0x{tag:02x}"),
616 )
617 .await?;
618 write_frame(
619 &mut stream,
620 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
621 )
622 .await?;
623 }
624 other => {
625 send_error(
626 &mut stream,
627 "0A000",
628 &format!("unsupported frame {other:?}"),
629 )
630 .await?;
631 write_frame(
632 &mut stream,
633 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
634 )
635 .await?;
636 }
637 }
638 }
639}
640
641async fn handle_parse<S>(
642 stream: &mut S,
643 runtime: &RedDBRuntime,
644 prepared_registry: &PreparedRegistry,
645 prepared: &mut HashMap<String, PgPreparedStatement>,
646 portals: &HashMap<String, PgPortal>,
647 msg: super::protocol::ParseMessage,
648) -> Result<(), PgWireError>
649where
650 S: AsyncWrite + Unpin,
651{
652 let inferred_param_type_oids = infer_pg_cast_param_type_oids(&msg.query);
653 let sql = rewrite_pg_parameter_casts(&msg.query);
654 let prepared_query = match prepared_registry.prepare(runtime, &sql) {
655 Ok(prepared_query) => prepared_query,
656 Err(err) => {
657 send_error(stream, "42601", &err.to_string()).await?;
658 return Ok(());
659 }
660 };
661 let mut param_type_oids = msg.param_type_oids;
662 if param_type_oids.is_empty() {
663 param_type_oids.resize(prepared_query.parameter_count, PgOid::Unknown.as_u32());
664 }
665 for (idx, oid) in inferred_param_type_oids {
666 if idx >= param_type_oids.len() {
667 param_type_oids.resize(idx + 1, PgOid::Unknown.as_u32());
668 }
669 if param_type_oids[idx] == PgOid::Unknown.as_u32() {
670 param_type_oids[idx] = oid;
671 }
672 }
673 let replaced = prepared.insert(
674 msg.statement,
675 PgPreparedStatement {
676 sql,
677 prepared_id: prepared_query.id,
678 param_type_oids,
679 },
680 );
681 if let Some(replaced) = replaced {
682 release_prepared_if_unreferenced(
683 prepared_registry,
684 replaced.prepared_id,
685 prepared,
686 portals,
687 );
688 }
689 write_frame(stream, &BackendMessage::ParseComplete).await
690}
691
692async fn handle_bind<S>(
693 stream: &mut S,
694 prepared_registry: &PreparedRegistry,
695 prepared: &HashMap<String, PgPreparedStatement>,
696 portals: &mut HashMap<String, PgPortal>,
697 msg: super::protocol::BindMessage,
698) -> Result<(), PgWireError>
699where
700 S: AsyncWrite + Unpin,
701{
702 let Some(stmt) = prepared.get(&msg.statement) else {
703 send_error(
704 stream,
705 "26000",
706 &format!("prepared statement {:?} does not exist", msg.statement),
707 )
708 .await?;
709 return Ok(());
710 };
711 let params = match bind_pg_params(stmt, &msg) {
712 Ok(params) => params,
713 Err(err) => {
714 send_error(stream, "22023", &err).await?;
715 return Ok(());
716 }
717 };
718 let replaced = portals.insert(
719 msg.portal,
720 PgPortal {
721 sql: stmt.sql.clone(),
722 prepared_id: stmt.prepared_id,
723 params,
724 result_format_codes: msg.result_format_codes,
725 row_description_sent: false,
726 result: None,
727 row_offset: 0,
728 },
729 );
730 if let Some(replaced) = replaced {
731 release_prepared_if_unreferenced(
732 prepared_registry,
733 replaced.prepared_id,
734 prepared,
735 portals,
736 );
737 }
738 write_frame(stream, &BackendMessage::BindComplete).await
739}
740
741async fn handle_describe<S>(
742 stream: &mut S,
743 runtime: &RedDBRuntime,
744 auth_context: Option<&PgAuthContext>,
745 prepared_registry: &PreparedRegistry,
746 prepared: &HashMap<String, PgPreparedStatement>,
747 portals: &mut HashMap<String, PgPortal>,
748 msg: super::protocol::DescribeMessage,
749) -> Result<(), PgWireError>
750where
751 S: AsyncWrite + Unpin,
752{
753 match msg.target {
754 DescribeTarget::Statement => {
755 let Some(stmt) = prepared.get(&msg.name) else {
756 send_error(
757 stream,
758 "26000",
759 &format!("prepared statement {:?} does not exist", msg.name),
760 )
761 .await?;
762 return Ok(());
763 };
764 write_frame(
765 stream,
766 &BackendMessage::ParameterDescription(stmt.param_type_oids.clone()),
767 )
768 .await?;
769 if is_ask_query(&stmt.sql) {
770 emit_ask_row_description(stream).await
771 } else {
772 write_frame(stream, &BackendMessage::NoData).await
773 }
774 }
775 DescribeTarget::Portal => {
776 let Some(portal) = portals.get_mut(&msg.name) else {
777 send_error(
778 stream,
779 "34000",
780 &format!("portal {:?} does not exist", msg.name),
781 )
782 .await?;
783 return Ok(());
784 };
785 if is_ask_query(&portal.sql) {
786 emit_ask_row_description(stream).await?;
787 portal.row_description_sent = true;
788 Ok(())
789 } else if is_row_returning_query(&portal.sql) {
790 let result = match execute_pg_query_result(
791 runtime,
792 auth_context,
793 prepared_registry,
794 portal.prepared_id,
795 &portal.sql,
796 &portal.params,
797 ) {
798 Ok(result) => result,
799 Err(err) => {
800 let code = classify_sqlstate(&err);
801 send_error(stream, code, &err).await?;
802 return Ok(());
803 }
804 };
805 emit_row_description_for_result(stream, &result).await?;
806 portal.row_description_sent = true;
807 portal.result = Some(result);
808 portal.row_offset = 0;
809 Ok(())
810 } else {
811 write_frame(stream, &BackendMessage::NoData).await
812 }
813 }
814 }
815}
816
817async fn handle_execute<S>(
818 stream: &mut S,
819 runtime: &RedDBRuntime,
820 auth_context: Option<&PgAuthContext>,
821 prepared_registry: &PreparedRegistry,
822 portals: &mut HashMap<String, PgPortal>,
823 msg: super::protocol::ExecuteMessage,
824) -> Result<(), PgWireError>
825where
826 S: AsyncWrite + Unpin,
827{
828 let Some(portal) = portals.get_mut(&msg.portal) else {
829 send_error(
830 stream,
831 "34000",
832 &format!("portal {:?} does not exist", msg.portal),
833 )
834 .await?;
835 return Ok(());
836 };
837 match prepared_registry.ddl_epoch(portal.prepared_id) {
838 Some(epoch) if epoch != runtime.ddl_epoch() => {
839 send_error(stream, "XX000", "prepared_needs_replan").await?;
840 return Ok(());
841 }
842 Some(_) => {}
843 None => {
844 send_error(stream, "26000", "prepared statement does not exist").await?;
845 return Ok(());
846 }
847 }
848 if portal.result.is_none() {
849 match execute_pg_query_result(
850 runtime,
851 auth_context,
852 prepared_registry,
853 portal.prepared_id,
854 &portal.sql,
855 &portal.params,
856 ) {
857 Ok(result) => {
858 portal.result = Some(result);
859 portal.row_offset = 0;
860 }
861 Err(err) => {
862 let code = classify_sqlstate(&err);
863 send_error(stream, code, &err).await?;
864 return Ok(());
865 }
866 }
867 }
868
869 let result = portal.result.as_ref().expect("portal result populated");
870 match emit_portal_execution(
871 stream,
872 result,
873 portal.row_description_sent,
874 portal.row_offset,
875 msg.max_rows,
876 )
877 .await
878 {
879 Ok(PortalExecution::Complete) => {
880 portal.result = None;
881 portal.row_offset = 0;
882 portal.row_description_sent = false;
883 Ok(())
884 }
885 Ok(PortalExecution::Suspended { next_row_offset }) => {
886 portal.row_offset = next_row_offset;
887 portal.row_description_sent = true;
888 Ok(())
889 }
890 Err(err) => {
891 let code = classify_sqlstate(&err);
892 send_error(stream, code, &err).await
893 }
894 }
895}
896
897async fn handle_close<S>(
898 stream: &mut S,
899 prepared_registry: &PreparedRegistry,
900 prepared: &mut HashMap<String, PgPreparedStatement>,
901 portals: &mut HashMap<String, PgPortal>,
902 msg: super::protocol::CloseMessage,
903) -> Result<(), PgWireError>
904where
905 S: AsyncWrite + Unpin,
906{
907 let prepared_id = match msg.target {
908 DescribeTarget::Statement => prepared.remove(&msg.name).map(|stmt| stmt.prepared_id),
909 DescribeTarget::Portal => portals.remove(&msg.name).map(|portal| portal.prepared_id),
910 };
911 if let Some(prepared_id) = prepared_id {
912 release_prepared_if_unreferenced(prepared_registry, prepared_id, prepared, portals);
913 }
914 write_frame(stream, &BackendMessage::CloseComplete).await
915}
916
917fn release_prepared_if_unreferenced(
918 prepared_registry: &PreparedRegistry,
919 prepared_id: PreparedId,
920 prepared: &HashMap<String, PgPreparedStatement>,
921 portals: &HashMap<String, PgPortal>,
922) {
923 let statement_references_id = prepared
924 .values()
925 .any(|statement| statement.prepared_id == prepared_id);
926 let portal_references_id = portals
927 .values()
928 .any(|portal| portal.prepared_id == prepared_id);
929 if !statement_references_id && !portal_references_id {
930 prepared_registry.release(prepared_id);
931 }
932}
933
934fn bind_pg_params(
935 stmt: &PgPreparedStatement,
936 msg: &super::protocol::BindMessage,
937) -> Result<Vec<ParamValue>, String> {
938 if !matches!(msg.param_format_codes.len(), 0 | 1)
939 && msg.param_format_codes.len() != msg.params.len()
940 {
941 return Err("Bind format count must be 0, 1, or match parameter count".to_string());
942 }
943 msg.params
944 .iter()
945 .enumerate()
946 .map(|(idx, param)| {
947 let oid = stmt
948 .param_type_oids
949 .get(idx)
950 .copied()
951 .unwrap_or(PgOid::Unknown.as_u32());
952 let format_code = match msg.param_format_codes.as_slice() {
953 [] => 0,
954 [format] => *format,
955 formats => formats[idx],
956 };
957 pg_param_to_param_value(oid, format_code, param.as_deref())
958 })
959 .collect()
960}
961
962fn execute_pg_query_result(
963 runtime: &RedDBRuntime,
964 auth_context: Option<&PgAuthContext>,
965 prepared_registry: &PreparedRegistry,
966 prepared_id: PreparedId,
967 sql: &str,
968 params: &[ParamValue],
969) -> Result<crate::runtime::OwnedQueryResult, String> {
970 let executor = QueryRequestExecutor::new(runtime, prepared_registry);
971 let bound = executor
972 .bind(QueryRequest::prepared(prepared_id, params.to_vec()))
973 .map_err(|err| err.to_string())?;
974 if pg_scalar_select_param_index(sql).is_some() {
975 let values = params.iter().cloned().map(Value::from).collect::<Vec<_>>();
976 if let Some(result) = try_execute_pg_scalar_select(sql, &values) {
977 return runtime
978 .own_query_result(result)
979 .map_err(|error| error.to_string());
980 }
981 }
982 if params.is_empty() {
983 return match translate_pg_catalog_query(runtime, sql) {
984 Ok(Some(result)) => runtime
985 .own_query_result(crate::runtime::RuntimeQueryResult {
986 query: sql.to_string(),
987 mode: reddb_rql::modes::QueryMode::Sql,
988 statement: "select",
989 engine: "pg-catalog",
990 result,
991 affected_rows: 0,
992 statement_type: "select",
993 bookmark: None,
994 notice: None,
995 })
996 .map_err(|error| error.to_string()),
997 Ok(None) => run_runtime_blocking(|| {
998 execute_with_pg_auth_context(auth_context, || {
999 if matches!(
1003 bound.prepared_expr(),
1004 Some(
1005 QueryExpr::Function(_)
1006 | QueryExpr::Table(_)
1007 | QueryExpr::Graph(_)
1008 | QueryExpr::Path(_)
1009 | QueryExpr::Join(_)
1010 | QueryExpr::Vector(_)
1011 | QueryExpr::Hybrid(_)
1012 | QueryExpr::Insert(_)
1013 | QueryExpr::Update(_)
1014 | QueryExpr::Delete(_)
1015 | QueryExpr::QueueCommand(_)
1016 | QueryExpr::KvCommand(_)
1017 | QueryExpr::SearchCommand(_)
1018 | QueryExpr::Ask(_)
1019 )
1020 ) {
1021 executor.execute_bound(bound)
1022 } else {
1023 runtime
1024 .execute_query(sql)
1025 .and_then(|result| runtime.own_query_result(result))
1026 }
1027 })
1028 })
1029 .map_err(|err| err.to_string()),
1030 Err(err) => Err(err.to_string()),
1031 };
1032 }
1033
1034 run_runtime_blocking(|| {
1035 execute_with_pg_auth_context(auth_context, || executor.execute_bound(bound))
1036 })
1037 .map_err(|err| err.to_string())
1038}
1039
1040fn try_execute_pg_scalar_select(
1041 sql: &str,
1042 params: &[Value],
1043) -> Option<crate::runtime::RuntimeQueryResult> {
1044 let index = pg_scalar_select_param_index(sql)?;
1045 let value = params.get(index)?.clone();
1046 let mut result = UnifiedResult::with_columns(vec!["?column?".to_string()]);
1047 let mut record = UnifiedRecord::new();
1048 record.set("?column?", value);
1049 result.push(record);
1050 Some(crate::runtime::RuntimeQueryResult {
1051 query: sql.to_string(),
1052 mode: reddb_rql::modes::QueryMode::Sql,
1053 statement: "select",
1054 engine: "pg-wire",
1055 result,
1056 affected_rows: 0,
1057 statement_type: "select",
1058 bookmark: None,
1059 notice: None,
1060 })
1061}
1062
1063fn pg_scalar_select_param_index(sql: &str) -> Option<usize> {
1064 let trimmed = sql.trim().trim_end_matches(';').trim();
1065 let lower = trimmed.to_ascii_lowercase();
1066 let body = lower.strip_prefix("select ")?;
1067 let param = if let Some(inner) = body.strip_prefix("cast(") {
1068 let end = inner.find(" as ")?;
1069 &inner[..end]
1070 } else {
1071 body.split_whitespace().next()?
1072 };
1073 let digits = param.strip_prefix('$')?;
1074 let n = digits.parse::<usize>().ok()?;
1075 n.checked_sub(1)
1076}
1077
1078fn rewrite_pg_parameter_casts(sql: &str) -> String {
1079 let mut out = String::with_capacity(sql.len());
1080 let bytes = sql.as_bytes();
1081 let mut cursor = 0;
1082 let mut pos = 0;
1083 while pos < bytes.len() {
1084 if bytes[pos] != b'$' {
1085 pos += 1;
1086 continue;
1087 }
1088 let param_start = pos;
1089 pos += 1;
1090 let digits_start = pos;
1091 while pos < bytes.len() && bytes[pos].is_ascii_digit() {
1092 pos += 1;
1093 }
1094 if digits_start == pos {
1095 continue;
1096 }
1097 if pos + 2 <= bytes.len() && &bytes[pos..pos + 2] == b"::" {
1098 let param_end = pos;
1099 pos += 2;
1100 let type_start = pos;
1101 while pos < bytes.len()
1102 && (bytes[pos].is_ascii_alphanumeric() || matches!(bytes[pos], b'_' | b'.'))
1103 {
1104 pos += 1;
1105 }
1106 if type_start != pos {
1107 out.push_str(&sql[cursor..param_start]);
1108 out.push_str(&sql[param_start..param_end]);
1109 cursor = pos;
1110 continue;
1111 }
1112 }
1113 }
1114 out.push_str(&sql[cursor..]);
1115 out
1116}
1117
1118fn infer_pg_cast_param_type_oids(sql: &str) -> Vec<(usize, u32)> {
1119 let mut out = Vec::new();
1120 let bytes = sql.as_bytes();
1121 let mut pos = 0;
1122 while pos < bytes.len() {
1123 if bytes[pos] != b'$' {
1124 pos += 1;
1125 continue;
1126 }
1127 pos += 1;
1128 let digits_start = pos;
1129 while pos < bytes.len() && bytes[pos].is_ascii_digit() {
1130 pos += 1;
1131 }
1132 if digits_start == pos {
1133 continue;
1134 }
1135 let Some(param_index) = sql[digits_start..pos]
1136 .parse::<usize>()
1137 .ok()
1138 .and_then(|idx| idx.checked_sub(1))
1139 else {
1140 continue;
1141 };
1142 if pos + 2 > bytes.len() || &bytes[pos..pos + 2] != b"::" {
1143 continue;
1144 }
1145 pos += 2;
1146 let type_start = pos;
1147 while pos < bytes.len()
1148 && (bytes[pos].is_ascii_alphanumeric() || matches!(bytes[pos], b'_' | b'.'))
1149 {
1150 pos += 1;
1151 }
1152 if type_start == pos {
1153 continue;
1154 }
1155 if let Some(oid) = pg_cast_type_oid(&sql[type_start..pos]) {
1156 out.push((param_index, oid));
1157 }
1158 }
1159 out
1160}
1161
1162fn pg_cast_type_oid(ty: &str) -> Option<u32> {
1163 let lower = ty.to_ascii_lowercase();
1164 let short = lower.rsplit('.').next().unwrap_or(lower.as_str());
1165 let oid = match short {
1166 "bool" | "boolean" => PgOid::Bool,
1167 "int2" | "smallint" => PgOid::Int2,
1168 "int" | "int4" | "integer" => PgOid::Int4,
1169 "int8" | "bigint" => PgOid::Int8,
1170 "float4" | "real" => PgOid::Float4,
1171 "float8" | "double" | "doubleprecision" => PgOid::Float8,
1172 "numeric" | "decimal" => PgOid::Numeric,
1173 "bytea" => PgOid::Bytea,
1174 "json" => PgOid::Json,
1175 "jsonb" => PgOid::Jsonb,
1176 "text" => PgOid::Text,
1177 "varchar" | "character varying" => PgOid::Varchar,
1178 "uuid" => PgOid::Uuid,
1179 "timestamp" => PgOid::Timestamp,
1180 "timestamptz" | "timestampz" => PgOid::TimestampTz,
1181 "vector" => PgOid::Vector,
1182 _ => return None,
1183 };
1184 Some(oid.as_u32())
1185}
1186
1187fn is_row_returning_query(sql: &str) -> bool {
1188 let trimmed = sql.trim_start().to_ascii_lowercase();
1189 trimmed.starts_with("select")
1190 || trimmed.starts_with("with")
1191 || trimmed.starts_with("ask")
1192 || trimmed.starts_with("search")
1193 || trimmed.starts_with("vector")
1194 || trimmed.starts_with("hybrid")
1195}
1196
1197fn is_ask_query(sql: &str) -> bool {
1198 sql.trim_start().to_ascii_lowercase().starts_with("ask")
1199}
1200
1201async fn authenticate_startup<S>(
1228 stream: &mut S,
1229 runtime: &RedDBRuntime,
1230 config: &PgWireConfig,
1231 over_tls: bool,
1232 params: &super::protocol::StartupParams,
1233) -> Result<Option<Option<PgAuthContext>>, PgWireError>
1234where
1235 S: AsyncRead + AsyncWrite + Unpin,
1236{
1237 let auth_store = runtime.auth_store().filter(|store| store.is_enabled());
1238 let Some(auth_store) = auth_store else {
1239 if is_loopback_bind(&config.bind_addr) {
1241 send_startup_ready(stream, config, params).await?;
1242 return Ok(Some(None));
1243 }
1244 send_error(stream, "28P01", "password authentication failed").await?;
1245 return Ok(None);
1246 };
1247
1248 if !over_tls && !is_loopback_bind(&config.bind_addr) {
1253 send_error(
1254 stream,
1255 "28000",
1256 "cleartext password authentication requires TLS on non-loopback binds",
1257 )
1258 .await?;
1259 return Ok(None);
1260 }
1261
1262 write_frame(stream, &BackendMessage::AuthenticationCleartextPassword).await?;
1263 let password = match read_frame(stream).await? {
1264 FrontendMessage::PasswordMessage(payload) => password_from_message(&payload),
1265 _ => None,
1268 };
1269 let username = params.get("user").unwrap_or("");
1270 let tenant = params.get("tenant");
1271 let Some(password) = password else {
1272 send_error(stream, "28P01", "password authentication failed").await?;
1273 return Ok(None);
1274 };
1275 let (session, lease) = match auth_store.authenticate_connection(tenant, username, &password) {
1278 Ok(authenticated) => authenticated,
1279 Err(_) => {
1280 send_error(stream, "28P01", "password authentication failed").await?;
1281 return Ok(None);
1282 }
1283 };
1284
1285 send_startup_ready(stream, config, params).await?;
1286 Ok(Some(Some(PgAuthContext {
1287 username: session.username,
1288 role: session.role,
1289 tenant: session.tenant_id,
1290 _lease: lease,
1291 })))
1292}
1293
1294async fn send_startup_ready<S>(
1295 stream: &mut S,
1296 config: &PgWireConfig,
1297 params: &super::protocol::StartupParams,
1298) -> Result<(), PgWireError>
1299where
1300 S: AsyncWrite + Unpin,
1301{
1302 write_frame(stream, &BackendMessage::AuthenticationOk).await?;
1304
1305 for (name, value) in [
1307 ("server_version", config.server_version.as_str()),
1308 ("server_encoding", "UTF8"),
1309 ("client_encoding", "UTF8"),
1310 ("DateStyle", "ISO, MDY"),
1311 ("TimeZone", "UTC"),
1312 ("integer_datetimes", "on"),
1313 ("standard_conforming_strings", "on"),
1314 (
1315 "application_name",
1316 params.get("application_name").unwrap_or(""),
1317 ),
1318 ] {
1319 write_frame(
1320 stream,
1321 &BackendMessage::ParameterStatus {
1322 name: name.to_string(),
1323 value: value.to_string(),
1324 },
1325 )
1326 .await?;
1327 }
1328
1329 write_frame(
1334 stream,
1335 &BackendMessage::BackendKeyData {
1336 pid: std::process::id(),
1337 key: random_cancel_key(),
1338 },
1339 )
1340 .await?;
1341
1342 write_frame(
1343 stream,
1344 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
1345 )
1346 .await?;
1347 Ok(())
1348}
1349
1350async fn handle_simple_query<S>(
1351 stream: &mut S,
1352 runtime: &RedDBRuntime,
1353 auth_context: Option<&PgAuthContext>,
1354 sql: &str,
1355) -> Result<(), PgWireError>
1356where
1357 S: AsyncWrite + Unpin,
1358{
1359 if sql.trim().is_empty() {
1362 write_frame(stream, &BackendMessage::EmptyQueryResponse).await?;
1363 write_frame(
1364 stream,
1365 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
1366 )
1367 .await?;
1368 return Ok(());
1369 }
1370
1371 if let Some(tag) = pg_session_compat_command_tag(sql) {
1372 write_frame(stream, &BackendMessage::CommandComplete(tag.to_string())).await?;
1373 write_frame(
1374 stream,
1375 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
1376 )
1377 .await?;
1378 return Ok(());
1379 }
1380
1381 let query_result = match translate_pg_catalog_query(runtime, sql) {
1382 Ok(Some(result)) => runtime.own_query_result(crate::runtime::RuntimeQueryResult {
1383 query: sql.to_string(),
1384 mode: reddb_rql::modes::QueryMode::Sql,
1385 statement: "select",
1386 engine: "pg-catalog",
1387 result,
1388 affected_rows: 0,
1389 statement_type: "select",
1390 bookmark: None,
1391 notice: None,
1392 }),
1393 Ok(None) => run_runtime_blocking(|| {
1394 execute_with_pg_auth_context(auth_context, || {
1395 runtime
1396 .execute_query(sql)
1397 .and_then(|result| runtime.own_query_result(result))
1398 })
1399 }),
1400 Err(err) => Err(err),
1401 };
1402
1403 match query_result {
1404 Ok(result) => {
1405 emit_success_result(stream, &result).await?;
1406 }
1407 Err(err) => {
1408 let code = classify_sqlstate(&err.to_string());
1412 send_error(stream, code, &crate::api::client_facing_message(&err)).await?;
1413 }
1414 }
1415
1416 write_frame(
1417 stream,
1418 &BackendMessage::ReadyForQuery(TransactionStatus::Idle),
1419 )
1420 .await?;
1421 Ok(())
1422}
1423
1424fn pg_session_compat_command_tag(sql: &str) -> Option<&'static str> {
1425 let lower = sql.trim().trim_end_matches(';').to_ascii_lowercase();
1426 if lower.starts_with("set ") {
1427 return Some("SET");
1428 }
1429 None
1430}
1431
1432async fn emit_success_result<S>(
1433 stream: &mut S,
1434 result: &crate::runtime::RuntimeQueryResult,
1435) -> Result<(), PgWireError>
1436where
1437 S: AsyncWrite + Unpin,
1438{
1439 if result.statement == "ask" {
1440 emit_ask_result_row(stream, result).await?;
1441 write_frame(
1442 stream,
1443 &BackendMessage::CommandComplete("SELECT 1".to_string()),
1444 )
1445 .await?;
1446 } else if result_returns_rows(result) {
1447 emit_result_rows(stream, &result.result).await?;
1448 write_frame(
1449 stream,
1450 &BackendMessage::CommandComplete(format!("SELECT {}", result.result.records.len())),
1451 )
1452 .await?;
1453 } else {
1454 let tag = match result.statement_type {
1459 "insert" => format!("INSERT 0 {}", result.affected_rows),
1460 "update" => format!("UPDATE {}", result.affected_rows),
1461 "delete" => format!("DELETE {}", result.affected_rows),
1462 other => other.to_uppercase(),
1463 };
1464 write_frame(stream, &BackendMessage::CommandComplete(tag)).await?;
1465 }
1466 Ok(())
1467}
1468
1469async fn emit_success_result_without_row_description<S>(
1470 stream: &mut S,
1471 result: &crate::runtime::RuntimeQueryResult,
1472) -> Result<(), PgWireError>
1473where
1474 S: AsyncWrite + Unpin,
1475{
1476 if result.statement == "ask" {
1477 let row = ask_query_result_to_pg_wire_row(result)
1478 .ok_or_else(|| PgWireError::Protocol("ASK result missing row body".to_string()))?;
1479 write_frame(stream, &BackendMessage::DataRow(row.cells)).await?;
1480 write_frame(
1481 stream,
1482 &BackendMessage::CommandComplete("SELECT 1".to_string()),
1483 )
1484 .await?;
1485 } else if result_returns_rows(result) {
1486 emit_result_data_rows(stream, &result.result).await?;
1487 write_frame(
1488 stream,
1489 &BackendMessage::CommandComplete(format!("SELECT {}", result.result.records.len())),
1490 )
1491 .await?;
1492 } else {
1493 let tag = match result.statement_type {
1494 "insert" => format!("INSERT 0 {}", result.affected_rows),
1495 "update" => format!("UPDATE {}", result.affected_rows),
1496 "delete" => format!("DELETE {}", result.affected_rows),
1497 other => other.to_uppercase(),
1498 };
1499 write_frame(stream, &BackendMessage::CommandComplete(tag)).await?;
1500 }
1501 Ok(())
1502}
1503
1504enum PortalExecution {
1505 Complete,
1506 Suspended { next_row_offset: usize },
1507}
1508
1509async fn emit_portal_execution<S>(
1510 stream: &mut S,
1511 result: &crate::runtime::RuntimeQueryResult,
1512 row_description_sent: bool,
1513 row_offset: usize,
1514 max_rows: u32,
1515) -> Result<PortalExecution, String>
1516where
1517 S: AsyncWrite + Unpin,
1518{
1519 if result.statement == "ask" && row_description_sent {
1520 emit_success_result_without_row_description(stream, result)
1521 .await
1522 .map_err(|err| err.to_string())?;
1523 Ok(PortalExecution::Complete)
1524 } else if result.statement == "ask" {
1525 emit_success_result(stream, result)
1526 .await
1527 .map_err(|err| err.to_string())?;
1528 Ok(PortalExecution::Complete)
1529 } else if result_returns_rows(result) {
1530 emit_row_returning_portal_execution(
1531 stream,
1532 result,
1533 row_description_sent,
1534 row_offset,
1535 max_rows,
1536 )
1537 .await
1538 } else if row_description_sent {
1539 emit_success_result_without_row_description(stream, result)
1540 .await
1541 .map_err(|err| err.to_string())?;
1542 Ok(PortalExecution::Complete)
1543 } else {
1544 emit_success_result(stream, result)
1545 .await
1546 .map_err(|err| err.to_string())?;
1547 Ok(PortalExecution::Complete)
1548 }
1549}
1550
1551async fn emit_row_returning_portal_execution<S>(
1552 stream: &mut S,
1553 result: &crate::runtime::RuntimeQueryResult,
1554 row_description_sent: bool,
1555 row_offset: usize,
1556 max_rows: u32,
1557) -> Result<PortalExecution, String>
1558where
1559 S: AsyncWrite + Unpin,
1560{
1561 if !row_description_sent {
1562 emit_result_row_description(stream, &result.result)
1563 .await
1564 .map_err(|err| err.to_string())?;
1565 }
1566
1567 let total_rows = result.result.records.len();
1568 let remaining_rows = total_rows.saturating_sub(row_offset);
1569 let rows_to_emit = if max_rows == 0 {
1570 remaining_rows
1571 } else {
1572 remaining_rows.min(max_rows as usize)
1573 };
1574 emit_result_data_rows_range(stream, &result.result, row_offset, rows_to_emit)
1575 .await
1576 .map_err(|err| err.to_string())?;
1577
1578 let next_row_offset = row_offset + rows_to_emit;
1579 if next_row_offset < total_rows {
1580 write_frame(stream, &BackendMessage::PortalSuspended)
1581 .await
1582 .map_err(|err| err.to_string())?;
1583 Ok(PortalExecution::Suspended { next_row_offset })
1584 } else {
1585 write_frame(
1586 stream,
1587 &BackendMessage::CommandComplete(format!("SELECT {}", total_rows)),
1588 )
1589 .await
1590 .map_err(|err| err.to_string())?;
1591 Ok(PortalExecution::Complete)
1592 }
1593}
1594
1595async fn emit_row_description_for_result<S>(
1596 stream: &mut S,
1597 result: &crate::runtime::RuntimeQueryResult,
1598) -> Result<(), PgWireError>
1599where
1600 S: AsyncWrite + Unpin,
1601{
1602 if result.statement == "ask" {
1603 emit_ask_row_description(stream).await
1604 } else if result_returns_rows(result) {
1605 emit_result_row_description(stream, &result.result).await
1606 } else {
1607 write_frame(stream, &BackendMessage::NoData).await
1608 }
1609}
1610
1611fn result_returns_rows(result: &crate::runtime::RuntimeQueryResult) -> bool {
1612 result.statement_type == "select"
1613}
1614
1615async fn emit_result_rows<S>(
1616 stream: &mut S,
1617 result: &crate::storage::query::unified::UnifiedResult,
1618) -> Result<(), PgWireError>
1619where
1620 S: AsyncWrite + Unpin,
1621{
1622 emit_result_row_description(stream, result).await?;
1623 emit_result_data_rows(stream, result).await
1624}
1625
1626async fn emit_result_row_description<S>(
1627 stream: &mut S,
1628 result: &crate::storage::query::unified::UnifiedResult,
1629) -> Result<(), PgWireError>
1630where
1631 S: AsyncWrite + Unpin,
1632{
1633 let columns: Vec<String> = if !result.columns.is_empty() {
1637 result.columns.clone()
1638 } else if let Some(first) = result.records.first() {
1639 record_field_names(first)
1640 } else {
1641 Vec::new()
1642 };
1643
1644 let type_oids: Vec<PgOid> = columns
1648 .iter()
1649 .map(|col| {
1650 result
1651 .records
1652 .first()
1653 .and_then(|r| record_get(r, col))
1654 .map(PgOid::from_value)
1655 .unwrap_or(PgOid::Text)
1656 })
1657 .collect();
1658
1659 let descriptors: Vec<ColumnDescriptor> = columns
1660 .iter()
1661 .zip(type_oids.iter())
1662 .map(|(name, oid)| ColumnDescriptor {
1663 name: name.clone(),
1664 table_oid: 0,
1665 column_attr: 0,
1666 type_oid: oid.as_u32(),
1667 type_size: -1,
1668 type_mod: -1,
1669 format: 0,
1670 })
1671 .collect();
1672
1673 write_frame(stream, &BackendMessage::RowDescription(descriptors)).await
1674}
1675
1676async fn emit_result_data_rows<S>(
1677 stream: &mut S,
1678 result: &crate::storage::query::unified::UnifiedResult,
1679) -> Result<(), PgWireError>
1680where
1681 S: AsyncWrite + Unpin,
1682{
1683 emit_result_data_rows_range(stream, result, 0, result.records.len()).await
1684}
1685
1686async fn emit_result_data_rows_range<S>(
1687 stream: &mut S,
1688 result: &crate::storage::query::unified::UnifiedResult,
1689 row_offset: usize,
1690 row_count: usize,
1691) -> Result<(), PgWireError>
1692where
1693 S: AsyncWrite + Unpin,
1694{
1695 let columns: Vec<String> = if !result.columns.is_empty() {
1696 result.columns.clone()
1697 } else if let Some(first) = result.records.first() {
1698 record_field_names(first)
1699 } else {
1700 Vec::new()
1701 };
1702 for record in result.records.iter().skip(row_offset).take(row_count) {
1703 let fields: Vec<Option<Vec<u8>>> = columns
1704 .iter()
1705 .map(|col| record_get(record, col).and_then(value_to_pg_wire_bytes))
1706 .collect();
1707 write_frame(stream, &BackendMessage::DataRow(fields)).await?;
1708 }
1709
1710 Ok(())
1711}
1712
1713async fn emit_ask_result_row<S>(
1714 stream: &mut S,
1715 result: &crate::runtime::RuntimeQueryResult,
1716) -> Result<(), PgWireError>
1717where
1718 S: AsyncWrite + Unpin,
1719{
1720 let row = ask_query_result_to_pg_wire_row(result)
1721 .ok_or_else(|| PgWireError::Protocol("ASK result missing row body".to_string()))?;
1722
1723 emit_ask_row_description(stream).await?;
1724 write_frame(stream, &BackendMessage::DataRow(row.cells)).await?;
1725 Ok(())
1726}
1727
1728async fn emit_ask_row_description<S>(stream: &mut S) -> Result<(), PgWireError>
1729where
1730 S: AsyncWrite + Unpin,
1731{
1732 let descriptors: Vec<ColumnDescriptor> = crate::runtime::ai::pg_wire_ask_row_encoder::columns()
1733 .iter()
1734 .map(|col| ColumnDescriptor {
1735 name: col.name.to_string(),
1736 table_oid: 0,
1737 column_attr: 0,
1738 type_oid: col.oid.as_u32(),
1739 type_size: -1,
1740 type_mod: -1,
1741 format: 0,
1742 })
1743 .collect();
1744 write_frame(stream, &BackendMessage::RowDescription(descriptors)).await
1745}
1746
1747fn ask_query_result_to_pg_wire_row(
1748 result: &crate::runtime::RuntimeQueryResult,
1749) -> Option<crate::runtime::ai::pg_wire_ask_row_encoder::AskRow> {
1750 if result.statement != "ask" {
1751 return None;
1752 }
1753 let ask = crate::presentation::query_result::ask_result_from_unified_result(&result.result)?;
1754
1755 Some(crate::runtime::ai::pg_wire_ask_row_encoder::encode(&ask))
1756}
1757
1758fn record_get<'a>(record: &'a UnifiedRecord, key: &str) -> Option<&'a Value> {
1762 record.get(key)
1763}
1764
1765fn record_field_names(record: &UnifiedRecord) -> Vec<String> {
1774 record
1778 .column_names()
1779 .into_iter()
1780 .map(|k| k.to_string())
1781 .collect()
1782}
1783
1784async fn send_error<S>(stream: &mut S, code: &str, message: &str) -> Result<(), PgWireError>
1785where
1786 S: AsyncWrite + Unpin,
1787{
1788 write_frame(
1789 stream,
1790 &BackendMessage::ErrorResponse {
1791 severity: "ERROR".to_string(),
1792 code: code.to_string(),
1793 message: message.to_string(),
1794 },
1795 )
1796 .await
1797}
1798
1799fn classify_sqlstate(msg: &str) -> &'static str {
1803 let lower = msg.to_ascii_lowercase();
1804 if lower.contains("not found") || lower.contains("does not exist") {
1805 "42P01"
1807 } else if lower.contains("parse") || lower.contains("expected") || lower.contains("syntax") {
1808 "42601"
1809 } else if lower.contains("already exists") {
1810 "42P07"
1811 } else if lower.contains("permission") || lower.contains("auth") {
1812 "28000"
1813 } else {
1814 "XX000"
1815 }
1816}
1817
1818#[cfg(test)]
1819mod tests {
1820 use super::*;
1821 use crate::api::RedDBOptions;
1822 use crate::auth::{AuthConfig, AuthStore, UserAttributes, UserId};
1823 use crate::runtime::RuntimeQueryResult;
1824 use crate::storage::query::unified::UnifiedResult;
1825 use reddb_rql::modes::QueryMode;
1826 use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
1827
1828 async fn assert_pg_password_rejected(auth: Arc<AuthStore>, username: &str, password: &str) {
1829 let runtime = Arc::new(RedDBRuntime::with_options(RedDBOptions::in_memory()).unwrap());
1830 runtime.set_auth_store(auth);
1831 let config = Arc::new(PgWireConfig::default());
1832 let (server_io, mut client_io) = tokio::io::duplex(64 * 1024);
1833 let server = tokio::spawn(async move {
1834 handle_connection(server_io, runtime, config, None)
1835 .await
1836 .unwrap();
1837 });
1838
1839 write_startup_params(&mut client_io, &[("user", username)]).await;
1840 let (tag, body) = read_backend_frame(&mut client_io).await;
1841 assert_eq!(tag, b'R');
1842 assert_eq!(i32::from_be_bytes(body[..4].try_into().unwrap()), 3);
1843 write_frontend_frame(&mut client_io, b'p', cstring_body(password)).await;
1844 let (tag, body) = read_backend_frame(&mut client_io).await;
1845 assert_eq!(tag, b'E');
1846 assert_eq!(error_response_field(&body, b'C'), Some("28P01"));
1847
1848 let mut eof = [0u8; 1];
1849 assert_eq!(client_io.read(&mut eof).await.unwrap(), 0);
1850 server.await.unwrap();
1851 }
1852
1853 #[tokio::test]
1854 async fn response_writer_releases_execution_slot_before_socket_write() {
1855 let runtime = RedDBRuntime::with_options(RedDBOptions::in_memory()).unwrap();
1856 runtime.configure_execution_admission(1, 0, 100);
1857 let admission = runtime.execution_admission();
1858 let permit = admission.acquire().await.expect("execution permit");
1859 assert_eq!(admission.active(), 1);
1860
1861 let (mut server_io, mut client_io) = tokio::io::duplex(64);
1862 let mut writer = ExecutionResponseWriter::new(&mut server_io, Some(permit));
1863 writer.write_all(b"ok").await.unwrap();
1864 assert_eq!(admission.active(), 0);
1865
1866 let mut received = [0_u8; 2];
1867 client_io.read_exact(&mut received).await.unwrap();
1868 assert_eq!(&received, b"ok");
1869 }
1870
1871 #[tokio::test]
1872 async fn enabled_auth_store_rejects_bad_pgwire_password_with_28p01() {
1873 let runtime = Arc::new(RedDBRuntime::with_options(RedDBOptions::in_memory()).unwrap());
1874 let auth = Arc::new(AuthStore::new(AuthConfig {
1875 enabled: true,
1876 require_auth: true,
1877 ..AuthConfig::default()
1878 }));
1879 auth.create_user("alice", "secret", Role::Write).unwrap();
1880 runtime.set_auth_store(auth);
1881
1882 let config = Arc::new(PgWireConfig::default());
1883 let (server_io, mut client_io) = tokio::io::duplex(64 * 1024);
1884 let server = tokio::spawn(async move {
1885 handle_connection(server_io, runtime, config, None)
1886 .await
1887 .unwrap();
1888 });
1889
1890 write_startup_params(&mut client_io, &[("user", "alice")]).await;
1891 let (tag, body) = read_backend_frame(&mut client_io).await;
1893 assert_eq!(tag, b'R');
1894 assert_eq!(i32::from_be_bytes(body[..4].try_into().unwrap()), 3);
1895
1896 write_frontend_frame(&mut client_io, b'p', cstring_body("wrong")).await;
1897 let (tag, body) = read_backend_frame(&mut client_io).await;
1898 assert_eq!(tag, b'E');
1899 assert_eq!(error_response_field(&body, b'C'), Some("28P01"));
1900
1901 let mut eof = [0u8; 1];
1903 assert_eq!(client_io.read(&mut eof).await.unwrap(), 0);
1904 server.await.unwrap();
1905 }
1906
1907 #[tokio::test]
1908 async fn pgwire_rejects_expired_accounts() {
1909 let auth = Arc::new(AuthStore::new(AuthConfig {
1910 enabled: true,
1911 require_auth: true,
1912 ..AuthConfig::default()
1913 }));
1914 auth.create_user("alice", "secret", Role::Write).unwrap();
1915 auth.set_user_attributes(
1916 &UserId::platform("alice"),
1917 UserAttributes {
1918 valid_until: Some(1),
1919 ..UserAttributes::default()
1920 },
1921 )
1922 .unwrap();
1923
1924 assert_pg_password_rejected(auth, "alice", "secret").await;
1925 }
1926
1927 #[tokio::test]
1928 async fn pgwire_rejects_accounts_at_connection_limit() {
1929 let auth = Arc::new(AuthStore::new(AuthConfig {
1930 enabled: true,
1931 require_auth: true,
1932 ..AuthConfig::default()
1933 }));
1934 auth.create_user("alice", "secret", Role::Write).unwrap();
1935 let id = UserId::platform("alice");
1936 auth.set_user_attributes(
1937 &id,
1938 UserAttributes {
1939 connection_limit: Some(1),
1940 ..UserAttributes::default()
1941 },
1942 )
1943 .unwrap();
1944 let _held = auth
1945 .acquire_connection_lease(&id)
1946 .expect("occupy the only connection slot");
1947
1948 assert_pg_password_rejected(auth, "alice", "secret").await;
1949 }
1950
1951 #[tokio::test]
1952 async fn pgwire_password_auth_installs_statement_identity() {
1953 let runtime = Arc::new(RedDBRuntime::with_options(RedDBOptions::in_memory()).unwrap());
1954 let auth = Arc::new(AuthStore::new(AuthConfig {
1955 enabled: true,
1956 require_auth: true,
1957 ..AuthConfig::default()
1958 }));
1959 auth.create_user_in_tenant(Some("acme"), "alice", "secret", Role::Write)
1960 .unwrap();
1961 runtime.set_auth_store(auth);
1962
1963 let config = Arc::new(PgWireConfig::default());
1964 let (server_io, mut client_io) = tokio::io::duplex(64 * 1024);
1965 let server = tokio::spawn(async move {
1966 handle_connection(server_io, runtime, config, None)
1967 .await
1968 .unwrap();
1969 });
1970
1971 write_startup_params(&mut client_io, &[("user", "alice"), ("tenant", "acme")]).await;
1972 let (tag, body) = read_backend_frame(&mut client_io).await;
1973 assert_eq!(tag, b'R');
1974 assert_eq!(i32::from_be_bytes(body[..4].try_into().unwrap()), 3);
1975 write_frontend_frame(&mut client_io, b'p', cstring_body("secret")).await;
1976 read_until_ready(&mut client_io).await;
1977
1978 write_frontend_frame(
1982 &mut client_io,
1983 b'Q',
1984 cstring_body("SELECT CURRENT_USER(), CURRENT_ROLE(), CURRENT_TENANT()"),
1985 )
1986 .await;
1987 let frames = read_until_ready(&mut client_io).await;
1988 assert_eq!(
1989 frames.iter().map(|(tag, _)| *tag).collect::<Vec<_>>(),
1990 b"TDCZ"
1991 );
1992 let cells = decode_data_row(&frames[1].1);
1993 assert_eq!(cells[0].as_deref(), Some(b"alice".as_slice()));
1994 assert_eq!(cells[1].as_deref(), Some(b"write".as_slice()));
1995 assert_eq!(cells[2].as_deref(), Some(b"acme".as_slice()));
1996
1997 write_frontend_frame(&mut client_io, b'X', Vec::new()).await;
1998 server.await.unwrap();
1999 }
2000
2001 #[tokio::test]
2002 async fn extended_parse_bind_execute_returns_rows() {
2003 let runtime = Arc::new(RedDBRuntime::with_options(RedDBOptions::in_memory()).unwrap());
2004 let config = Arc::new(PgWireConfig::default());
2005 let (server_io, mut client_io) = tokio::io::duplex(64 * 1024);
2006 let server = tokio::spawn(async move {
2007 handle_connection(server_io, runtime, config, None)
2008 .await
2009 .unwrap();
2010 });
2011
2012 write_startup(&mut client_io).await;
2013 read_until_ready(&mut client_io).await;
2014
2015 write_frontend_frame(
2016 &mut client_io,
2017 b'P',
2018 parse_body("", "SELECT $1::int", &[PgOid::Int4.as_u32()]),
2019 )
2020 .await;
2021 write_frontend_frame(
2022 &mut client_io,
2023 b'B',
2024 bind_body("", "", &[0], &[Some(b"42".as_slice())], &[]),
2025 )
2026 .await;
2027 write_frontend_frame(&mut client_io, b'D', describe_body(b'P', "")).await;
2028 write_frontend_frame(&mut client_io, b'E', execute_body("", 0)).await;
2029 write_frontend_frame(&mut client_io, b'S', Vec::new()).await;
2030
2031 let frames = read_until_ready(&mut client_io).await;
2032 assert_eq!(
2033 frames.iter().map(|(tag, _)| *tag).collect::<Vec<_>>(),
2034 b"12TDCZ"
2035 );
2036 let columns = decode_row_description(&frames[2].1);
2037 assert_eq!(columns.len(), 1);
2038 let cells = decode_data_row(&frames[3].1);
2039 assert_eq!(cells.len(), 1);
2040 assert_eq!(cells[0].as_deref(), Some(b"42".as_slice()));
2041 assert_eq!(decode_command_complete(&frames[4].1), "SELECT 1");
2042
2043 write_frontend_frame(&mut client_io, b'X', Vec::new()).await;
2044 server.await.unwrap();
2045 }
2046
2047 #[test]
2048 fn prepared_pg_utility_statements_preserve_ddl_and_transactions() {
2049 let runtime = RedDBRuntime::in_memory().expect("runtime");
2050 let registry = PreparedRegistry::new();
2051 let execute = |sql: &str, params: Vec<ParamValue>| {
2052 let prepared = registry
2053 .prepare(&runtime, sql)
2054 .expect("prepare utility or DML");
2055 execute_pg_query_result(&runtime, None, ®istry, prepared.id, sql, ¶ms)
2056 .unwrap_or_else(|error| panic!("{sql}: {error}"))
2057 };
2058 execute("CREATE TABLE pg_utilities (id INT)", Vec::new());
2059 execute("BEGIN", Vec::new());
2060 execute(
2061 "INSERT INTO pg_utilities (id) VALUES ($1)",
2062 vec![ParamValue::Int64(1)],
2063 );
2064 execute("SAVEPOINT before_second", Vec::new());
2065 execute(
2066 "INSERT INTO pg_utilities (id) VALUES ($1)",
2067 vec![ParamValue::Int64(2)],
2068 );
2069 execute("ROLLBACK TO SAVEPOINT before_second", Vec::new());
2070 execute("COMMIT", Vec::new());
2071 let result = execute("SELECT id FROM pg_utilities", Vec::new());
2072 assert_eq!(result.result.records.len(), 1);
2073 assert_eq!(result.result.records[0].get("id"), Some(&Value::Integer(1)));
2074 execute("DROP TABLE pg_utilities", Vec::new());
2075 assert!(runtime
2076 .db()
2077 .store()
2078 .get_collection("pg_utilities")
2079 .is_none());
2080 }
2081
2082 #[tokio::test]
2083 async fn extended_repeated_execute_parses_prepared_shape_once() {
2084 let runtime = RedDBRuntime::with_options(RedDBOptions::in_memory()).unwrap();
2085 runtime
2086 .execute_query("CREATE TABLE pg_parse_once (id INT)")
2087 .unwrap();
2088 runtime
2089 .execute_query("INSERT INTO pg_parse_once (id) VALUES (42)")
2090 .unwrap();
2091 let prepared_registry = PreparedRegistry::new();
2092 let mut prepared = HashMap::new();
2093 let mut portals = HashMap::new();
2094 let (mut server_io, _client_io) = tokio::io::duplex(64 * 1024);
2095
2096 handle_parse(
2097 &mut server_io,
2098 &runtime,
2099 &prepared_registry,
2100 &mut prepared,
2101 &portals,
2102 super::super::protocol::ParseMessage {
2103 statement: "parse_once_stmt".to_string(),
2104 query: "SELECT id FROM pg_parse_once WHERE id = $1".to_string(),
2105 param_type_oids: vec![PgOid::Int4.as_u32()],
2106 },
2107 )
2108 .await
2109 .unwrap();
2110 assert_eq!(prepared_registry.parse_count(), 1);
2111
2112 handle_bind(
2113 &mut server_io,
2114 &prepared_registry,
2115 &prepared,
2116 &mut portals,
2117 super::super::protocol::BindMessage {
2118 portal: "parse_once_portal".to_string(),
2119 statement: "parse_once_stmt".to_string(),
2120 param_format_codes: vec![0],
2121 params: vec![Some(b"42".to_vec())],
2122 result_format_codes: Vec::new(),
2123 },
2124 )
2125 .await
2126 .unwrap();
2127
2128 for _ in 0..2 {
2129 handle_execute(
2130 &mut server_io,
2131 &runtime,
2132 None,
2133 &prepared_registry,
2134 &mut portals,
2135 super::super::protocol::ExecuteMessage {
2136 portal: "parse_once_portal".to_string(),
2137 max_rows: 0,
2138 },
2139 )
2140 .await
2141 .unwrap();
2142 }
2143 assert_eq!(prepared_registry.parse_count(), 1);
2144 }
2145
2146 #[tokio::test]
2147 async fn non_loopback_plaintext_refuses_cleartext_password_without_tls() {
2148 let runtime = Arc::new(RedDBRuntime::with_options(RedDBOptions::in_memory()).unwrap());
2155 let auth = Arc::new(AuthStore::new(AuthConfig {
2156 enabled: true,
2157 require_auth: true,
2158 ..AuthConfig::default()
2159 }));
2160 auth.create_user("alice", "secret", Role::Write).unwrap();
2161 runtime.set_auth_store(auth);
2162
2163 let config = Arc::new(PgWireConfig {
2165 bind_addr: "0.0.0.0:5432".to_string(),
2166 ..PgWireConfig::default()
2167 });
2168 let (server_io, mut client_io) = tokio::io::duplex(64 * 1024);
2169 let server = tokio::spawn(async move {
2170 handle_connection(server_io, runtime, config, None)
2171 .await
2172 .unwrap();
2173 });
2174
2175 write_startup_params(&mut client_io, &[("user", "alice")]).await;
2176 let (tag, body) = read_backend_frame(&mut client_io).await;
2178 assert_eq!(tag, b'E', "expected an ErrorResponse, not a challenge");
2179 assert_eq!(error_response_field(&body, b'C'), Some("28000"));
2180
2181 let mut eof = [0u8; 1];
2183 assert_eq!(client_io.read(&mut eof).await.unwrap(), 0);
2184 server.await.unwrap();
2185 }
2186
2187 #[test]
2188 fn infer_pg_cast_param_type_oids_from_parameter_casts() {
2189 assert_eq!(
2190 infer_pg_cast_param_type_oids("INSERT INTO t (id, name) VALUES ($1::int, $2::text)"),
2191 vec![(0, PgOid::Int4.as_u32()), (1, PgOid::Text.as_u32())]
2192 );
2193 assert_eq!(
2194 infer_pg_cast_param_type_oids("SEARCH SIMILAR [1.0] COLLECTION v LIMIT $1::int8"),
2195 vec![(0, PgOid::Int8.as_u32())]
2196 );
2197 }
2198
2199 #[test]
2200 fn pg_session_compat_accepts_driver_setup_set_commands() {
2201 assert_eq!(
2202 pg_session_compat_command_tag("SET extra_float_digits = 3"),
2203 Some("SET")
2204 );
2205 assert_eq!(
2206 pg_session_compat_command_tag("SET application_name = 'pgjdbc'"),
2207 Some("SET")
2208 );
2209 assert_eq!(pg_session_compat_command_tag("SELECT 1"), None);
2210 }
2211
2212 #[tokio::test]
2213 async fn ask_success_result_uses_canonical_pg_wire_row_shape() {
2214 let mut result = UnifiedResult::with_columns(vec![
2215 "answer".into(),
2216 "provider".into(),
2217 "model".into(),
2218 "prompt_tokens".into(),
2219 "completion_tokens".into(),
2220 "sources_count".into(),
2221 "sources_flat".into(),
2222 "citations".into(),
2223 "validation".into(),
2224 ]);
2225 let mut record = UnifiedRecord::new();
2226 record.set("answer", Value::text("Deploy failed [^1]."));
2227 record.set("provider", Value::text("openai"));
2228 record.set("model", Value::text("gpt-4o-mini"));
2229 record.set("prompt_tokens", Value::Integer(11));
2230 record.set("completion_tokens", Value::Integer(7));
2231 record.set(
2232 "sources_flat",
2233 Value::Json(
2234 br#"[{"urn":"urn:reddb:row:deployments:1","kind":"row","collection":"deployments","id":"1"}]"#
2235 .to_vec(),
2236 ),
2237 );
2238 record.set(
2239 "citations",
2240 Value::Json(br#"[{"marker":1,"urn":"urn:reddb:row:deployments:1"}]"#.to_vec()),
2241 );
2242 record.set(
2243 "validation",
2244 Value::Json(br#"{"ok":true,"warnings":[],"errors":[]}"#.to_vec()),
2245 );
2246 result.push(record);
2247
2248 let qr = RuntimeQueryResult {
2249 query: "ASK 'why did deploy fail?'".to_string(),
2250 mode: QueryMode::Sql,
2251 statement: "ask",
2252 engine: "runtime-ai",
2253 result,
2254 affected_rows: 0,
2255 statement_type: "select",
2256 bookmark: None,
2257 notice: None,
2258 };
2259
2260 let mut out = Vec::new();
2261 emit_success_result(&mut out, &qr).await.unwrap();
2262 let frames = decode_frames(&out);
2263
2264 assert_eq!(
2265 frames.iter().map(|(tag, _)| *tag).collect::<Vec<_>>(),
2266 b"TDC"
2267 );
2268
2269 let columns = decode_row_description(frames[0].1);
2270 assert_eq!(
2271 columns,
2272 vec![
2273 ("answer".to_string(), PgOid::Text.as_u32()),
2274 ("cache_hit".to_string(), PgOid::Bool.as_u32()),
2275 ("citations".to_string(), PgOid::Jsonb.as_u32()),
2276 ("completion_tokens".to_string(), PgOid::Int8.as_u32()),
2277 ("cost_usd".to_string(), PgOid::Numeric.as_u32()),
2278 ("mode".to_string(), PgOid::Text.as_u32()),
2279 ("model".to_string(), PgOid::Text.as_u32()),
2280 ("prompt_tokens".to_string(), PgOid::Int8.as_u32()),
2281 ("provider".to_string(), PgOid::Text.as_u32()),
2282 ("retry_count".to_string(), PgOid::Int8.as_u32()),
2283 ("sources_flat".to_string(), PgOid::Jsonb.as_u32()),
2284 ("validation".to_string(), PgOid::Jsonb.as_u32()),
2285 ]
2286 );
2287
2288 let cells = decode_data_row(frames[1].1);
2289 assert_eq!(cells.len(), 12);
2290 assert_eq!(cells[0].as_deref(), Some(b"Deploy failed [^1].".as_slice()));
2291 assert_eq!(cells[1].as_deref(), Some(b"f".as_slice()));
2292 assert_eq!(cells[4].as_deref(), Some(b"0".as_slice()));
2293 assert_eq!(cells[5].as_deref(), Some(b"strict".as_slice()));
2294 assert_eq!(cells[9].as_deref(), Some(b"0".as_slice()));
2295 assert!(std::str::from_utf8(cells[10].as_deref().unwrap())
2296 .unwrap()
2297 .contains(r#""payload""#));
2298 assert_eq!(decode_command_complete(frames[2].1), "SELECT 1");
2299 }
2300
2301 fn decode_frames(bytes: &[u8]) -> Vec<(u8, &[u8])> {
2302 let mut pos = 0;
2303 let mut frames = Vec::new();
2304 while pos < bytes.len() {
2305 let tag = bytes[pos];
2306 let len = u32::from_be_bytes([
2307 bytes[pos + 1],
2308 bytes[pos + 2],
2309 bytes[pos + 3],
2310 bytes[pos + 4],
2311 ]) as usize;
2312 let body_start = pos + 5;
2313 let body_end = pos + 1 + len;
2314 frames.push((tag, &bytes[body_start..body_end]));
2315 pos = body_end;
2316 }
2317 frames
2318 }
2319
2320 fn decode_row_description(body: &[u8]) -> Vec<(String, u32)> {
2321 let count = i16::from_be_bytes([body[0], body[1]]) as usize;
2322 let mut pos = 2;
2323 let mut columns = Vec::with_capacity(count);
2324 for _ in 0..count {
2325 let end = body[pos..].iter().position(|&b| b == 0).unwrap() + pos;
2326 let name = std::str::from_utf8(&body[pos..end]).unwrap().to_string();
2327 pos = end + 1;
2328 pos += 4; pos += 2; let oid = u32::from_be_bytes([body[pos], body[pos + 1], body[pos + 2], body[pos + 3]]);
2331 pos += 4;
2332 pos += 2; pos += 4; pos += 2; columns.push((name, oid));
2336 }
2337 columns
2338 }
2339
2340 fn decode_data_row(body: &[u8]) -> Vec<Option<Vec<u8>>> {
2341 let count = i16::from_be_bytes([body[0], body[1]]) as usize;
2342 let mut pos = 2;
2343 let mut cells = Vec::with_capacity(count);
2344 for _ in 0..count {
2345 let len = i32::from_be_bytes([body[pos], body[pos + 1], body[pos + 2], body[pos + 3]]);
2346 pos += 4;
2347 if len < 0 {
2348 cells.push(None);
2349 } else {
2350 let len = len as usize;
2351 cells.push(Some(body[pos..pos + len].to_vec()));
2352 pos += len;
2353 }
2354 }
2355 cells
2356 }
2357
2358 fn decode_command_complete(body: &[u8]) -> &str {
2359 let nul = body.iter().position(|&b| b == 0).unwrap_or(body.len());
2360 std::str::from_utf8(&body[..nul]).unwrap()
2361 }
2362
2363 async fn write_startup<W: AsyncWrite + Unpin>(stream: &mut W) {
2364 write_startup_params(stream, &[("user", "reddb")]).await;
2365 }
2366
2367 async fn write_startup_params<W: AsyncWrite + Unpin>(stream: &mut W, params: &[(&str, &str)]) {
2368 let mut payload = Vec::new();
2369 payload.extend_from_slice(&crate::wire::postgres::protocol::PG_PROTOCOL_V3.to_be_bytes());
2370 for (name, value) in params {
2371 push_pg_cstring(&mut payload, name);
2372 push_pg_cstring(&mut payload, value);
2373 }
2374 payload.push(0);
2375 let len = (payload.len() + 4) as u32;
2376 stream.write_all(&len.to_be_bytes()).await.unwrap();
2377 stream.write_all(&payload).await.unwrap();
2378 }
2379
2380 fn cstring_body(value: &str) -> Vec<u8> {
2381 let mut out = Vec::new();
2382 push_pg_cstring(&mut out, value);
2383 out
2384 }
2385
2386 fn error_response_field(body: &[u8], field_tag: u8) -> Option<&str> {
2387 let mut pos = 0;
2388 while pos < body.len() && body[pos] != 0 {
2389 let tag = body[pos];
2390 pos += 1;
2391 let end = body[pos..].iter().position(|&b| b == 0)? + pos;
2392 if tag == field_tag {
2393 return std::str::from_utf8(&body[pos..end]).ok();
2394 }
2395 pos = end + 1;
2396 }
2397 None
2398 }
2399
2400 async fn write_frontend_frame<W: AsyncWrite + Unpin>(
2401 stream: &mut W,
2402 tag: u8,
2403 payload: Vec<u8>,
2404 ) {
2405 stream.write_all(&[tag]).await.unwrap();
2406 stream
2407 .write_all(&((payload.len() + 4) as u32).to_be_bytes())
2408 .await
2409 .unwrap();
2410 stream.write_all(&payload).await.unwrap();
2411 }
2412
2413 async fn read_backend_frame<R: AsyncRead + Unpin>(stream: &mut R) -> (u8, Vec<u8>) {
2414 let mut tag = [0u8; 1];
2415 stream.read_exact(&mut tag).await.unwrap();
2416 let mut len = [0u8; 4];
2417 stream.read_exact(&mut len).await.unwrap();
2418 let len = u32::from_be_bytes(len) as usize;
2419 let mut body = vec![0u8; len - 4];
2420 stream.read_exact(&mut body).await.unwrap();
2421 (tag[0], body)
2422 }
2423
2424 async fn read_until_ready<R: AsyncRead + Unpin>(stream: &mut R) -> Vec<(u8, Vec<u8>)> {
2425 let mut frames = Vec::new();
2426 loop {
2427 let frame = read_backend_frame(stream).await;
2428 let done = frame.0 == b'Z';
2429 frames.push(frame);
2430 if done {
2431 return frames;
2432 }
2433 }
2434 }
2435
2436 fn parse_body(statement: &str, query: &str, oids: &[u32]) -> Vec<u8> {
2437 let mut out = Vec::new();
2438 push_pg_cstring(&mut out, statement);
2439 push_pg_cstring(&mut out, query);
2440 out.extend_from_slice(&(oids.len() as i16).to_be_bytes());
2441 for oid in oids {
2442 out.extend_from_slice(&oid.to_be_bytes());
2443 }
2444 out
2445 }
2446
2447 fn bind_body(
2448 portal: &str,
2449 statement: &str,
2450 formats: &[i16],
2451 params: &[Option<&[u8]>],
2452 result_formats: &[i16],
2453 ) -> Vec<u8> {
2454 let mut out = Vec::new();
2455 push_pg_cstring(&mut out, portal);
2456 push_pg_cstring(&mut out, statement);
2457 out.extend_from_slice(&(formats.len() as i16).to_be_bytes());
2458 for format in formats {
2459 out.extend_from_slice(&format.to_be_bytes());
2460 }
2461 out.extend_from_slice(&(params.len() as i16).to_be_bytes());
2462 for param in params {
2463 match param {
2464 Some(bytes) => {
2465 out.extend_from_slice(&(bytes.len() as i32).to_be_bytes());
2466 out.extend_from_slice(bytes);
2467 }
2468 None => out.extend_from_slice(&(-1i32).to_be_bytes()),
2469 }
2470 }
2471 out.extend_from_slice(&(result_formats.len() as i16).to_be_bytes());
2472 for format in result_formats {
2473 out.extend_from_slice(&format.to_be_bytes());
2474 }
2475 out
2476 }
2477
2478 fn describe_body(target: u8, name: &str) -> Vec<u8> {
2479 let mut out = vec![target];
2480 push_pg_cstring(&mut out, name);
2481 out
2482 }
2483
2484 fn execute_body(portal: &str, max_rows: u32) -> Vec<u8> {
2485 let mut out = Vec::new();
2486 push_pg_cstring(&mut out, portal);
2487 out.extend_from_slice(&max_rows.to_be_bytes());
2488 out
2489 }
2490
2491 fn push_pg_cstring(out: &mut Vec<u8>, value: &str) {
2492 out.extend_from_slice(value.as_bytes());
2493 out.push(0);
2494 }
2495}
2496
2497fn random_cancel_key() -> u32 {
2502 let mut bytes = [0u8; 4];
2503 if crate::crypto::os_random::fill_bytes(&mut bytes).is_ok() {
2504 return u32::from_le_bytes(bytes);
2505 }
2506 let nanos = std::time::SystemTime::now()
2507 .duration_since(std::time::UNIX_EPOCH)
2508 .map(|d| d.subsec_nanos())
2509 .unwrap_or(0);
2510 std::process::id() ^ nanos
2511}