diff --git a/crates/pgsrv/src/codec.rs b/crates/pgsrv/src/codec.rs index d7d406dd3..938458ab8 100644 --- a/crates/pgsrv/src/codec.rs +++ b/crates/pgsrv/src/codec.rs @@ -180,6 +180,7 @@ impl Encoder for PgCodec { BackendMessage::AuthenticationOk => b'R', BackendMessage::AuthenticationCleartextPassword => b'R', BackendMessage::EmptyQueryResponse => b'I', + BackendMessage::ParameterStatus { .. } => b'S', BackendMessage::ReadyForQuery(_) => b'Z', BackendMessage::CommandComplete { .. } => b'C', BackendMessage::RowDescription(_) => b'T', @@ -197,6 +198,10 @@ impl Encoder for PgCodec { BackendMessage::AuthenticationOk => dst.put_i32(0), BackendMessage::AuthenticationCleartextPassword => dst.put_i32(3), BackendMessage::EmptyQueryResponse => (), + BackendMessage::ParameterStatus { key, val } => { + dst.put_cstring(&key); + dst.put_cstring(&val); + } BackendMessage::ReadyForQuery(status) => match status { TransactionStatus::Idle => dst.put_u8(b'I'), TransactionStatus::InBlock => dst.put_u8(b'T'), @@ -279,7 +284,7 @@ impl Decoder for PgCodec { let msg_len = i32::from_be_bytes(src[1..5].try_into().unwrap()) as usize; // Not enough bytes to read the full message yet. - if src.len() < msg_len - 1 { + if src.len() < msg_len { src.reserve(msg_len - src.len()); return Ok(None); } diff --git a/crates/pgsrv/src/errors.rs b/crates/pgsrv/src/errors.rs index dc70d9c30..03d4fe8c2 100644 --- a/crates/pgsrv/src/errors.rs +++ b/crates/pgsrv/src/errors.rs @@ -21,7 +21,7 @@ pub enum PgSrvError { #[error("missing null byte")] MissingNullByte, - #[error("invalid message type: {0}")] + #[error("invalid message type: {}", *.0 as char)] // Easier to debug character representation. InvalidMsgType(u8), #[error(transparent)] diff --git a/crates/pgsrv/src/handler.rs b/crates/pgsrv/src/handler.rs index 58f781d81..a59e22148 100644 --- a/crates/pgsrv/src/handler.rs +++ b/crates/pgsrv/src/handler.rs @@ -15,6 +15,15 @@ use std::collections::HashMap; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use tracing::trace; +/// Default parameters to send to the frontend on startup. Existing postgres +/// drivers may expect these in the server response on startup. +/// +/// See https://www.postgresql.org/docs/current/runtime-config-preset.html for +/// other parameters we may want to provide. +/// +/// Some parameters will eventually be provided at runtime. +const DEFAULT_READ_ONLY_PARAMS: &[(&str, &str)] = &[("server_version", "0.0.0")]; + /// A wrapper around a sqlengine that implements the Postgres frontend/backend /// protocol. pub struct Handler { @@ -56,7 +65,7 @@ impl Handler { } } StartupMessage::CancelRequest { .. } => { - trace!("recieved cancel request"); + trace!("received cancel request"); // TODO: Properly handle requests to cancel sessions. // Note that we should not respond to this request. @@ -103,6 +112,16 @@ impl Handler { } }; + // Send server parameters. + for (key, val) in DEFAULT_READ_ONLY_PARAMS { + framed + .send(BackendMessage::ParameterStatus { + key: key.to_string(), + val: val.to_string(), + }) + .await?; + } + let cs = ClientSession::new(sess, framed); cs.run().await } diff --git a/crates/pgsrv/src/messages.rs b/crates/pgsrv/src/messages.rs index 58e987d32..ad11d7e23 100644 --- a/crates/pgsrv/src/messages.rs +++ b/crates/pgsrv/src/messages.rs @@ -46,6 +46,7 @@ pub enum BackendMessage { NoticeResponse(NoticeResponse), AuthenticationOk, AuthenticationCleartextPassword, + ParameterStatus { key: String, val: String }, EmptyQueryResponse, ReadyForQuery(TransactionStatus), CommandComplete { tag: String },