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