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