use crate::error::{Error, Result};
pub(crate) const DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES: u64 = 50264;
pub(crate) const DBMS_MIN_REVISION_WITH_BLOCK_INFO: u64 = 51903;
pub(crate) const DBMS_MIN_REVISION_WITH_CLIENT_INFO: u64 = 54032;
pub(crate) const DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE: u64 = 54058;
pub(crate) const DBMS_MIN_REVISION_WITH_QUOTA_KEY_IN_CLIENT_INFO: u64 = 54060;
pub(crate) const DBMS_MIN_REVISION_WITH_SERVER_DISPLAY_NAME: u64 = 54372;
pub(crate) const DBMS_MIN_REVISION_WITH_VERSION_PATCH: u64 = 54401;
pub(crate) const DBMS_MIN_REVISION_WITH_CLIENT_WRITE_INFO: u64 = 54420;
pub(crate) const DBMS_MIN_REVISION_WITH_SETTINGS_SERIALIZED_AS_STRINGS: u64 = 54429;
pub(crate) const DBMS_MIN_REVISION_WITH_INTERSERVER_SECRET: u64 = 54441;
pub(crate) const DBMS_MIN_REVISION_WITH_OPENTELEMETRY: u64 = 54442;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_DISTRIBUTED_DEPTH: u64 = 54448;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME: u64 = 54449;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_PARALLEL_REPLICAS: u64 = 54453;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM: u64 = 54458;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS: u64 = 54459;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_SERVER_QUERY_TIME_IN_PROGRESS: u64 = 54460;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_PASSWORD_COMPLEXITY_RULES: u64 = 54461;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_TOTAL_BYTES_IN_PROGRESS: u64 = 54463;
pub(crate) const DBMS_MIN_REVISION_WITH_ROWS_BEFORE_AGGREGATION: u64 = 54469;
pub(crate) const DBMS_MIN_REVISION_WITH_V2_DYNAMIC_AND_JSON_SERIALIZATION: u64 = 54473;
pub(crate) const DBMS_MIN_REVISION_WITH_INTERSERVER_SECRET_V2: u64 = 54462;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_CHUNKED_PACKETS: u64 = 54470;
pub(crate) const DBMS_MIN_REVISION_WITH_VERSIONED_PARALLEL_REPLICAS_PROTOCOL: u64 = 54471;
pub(crate) const DBMS_MIN_PROTOCOL_VERSION_WITH_INTERSERVER_EXTERNALLY_GRANTED_ROLES: u64 = 54472;
pub(crate) const DBMS_TCP_PROTOCOL_VERSION: u64 =
DBMS_MIN_REVISION_WITH_V2_DYNAMIC_AND_JSON_SERIALIZATION;
pub(crate) const TCP_EXCEPTION_STACK_TRACE_CAP: usize = 1 << 20;
pub(crate) const TCP_EXCEPTION_MESSAGE_CAP: usize = 64 * 1024;
pub(crate) fn truncate_on_char_boundary(s: &mut String, cap: usize) {
if s.len() <= cap {
return;
}
let mut end = cap;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
s.truncate(end);
}
#[repr(u64)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ClientPacketId {
Hello = 0,
Query = 1,
Data = 2,
Cancel = 3,
Ping = 4,
}
#[repr(u64)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ServerPacketId {
Hello = 0,
Data = 1,
Exception = 2,
Progress = 3,
Pong = 4,
EndOfStream = 5,
ProfileInfo = 6,
Totals = 7,
Extremes = 8,
Log = 10,
TableColumns = 11,
ProfileEvents = 14,
TimezoneUpdate = 17,
}
impl ServerPacketId {
pub(crate) fn from_u64(i: u64) -> Result<Self> {
Ok(match i {
0 => Self::Hello,
1 => Self::Data,
2 => Self::Exception,
3 => Self::Progress,
4 => Self::Pong,
5 => Self::EndOfStream,
6 => Self::ProfileInfo,
7 => Self::Totals,
8 => Self::Extremes,
10 => Self::Log,
11 => Self::TableColumns,
14 => Self::ProfileEvents,
17 => Self::TimezoneUpdate,
x => {
return Err(Error::BadResponse(format!(
"tcp: unknown server packet id {x}"
)));
}
})
}
}
#[repr(u64)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum QueryProcessingStage {
Complete = 2,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct ServerHello {
pub server_name: String,
pub version: (u64, u64, u64),
pub revision: u64,
pub timezone: Option<String>,
pub display_name: Option<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct Exception {
pub(crate) code: i32,
pub(crate) name: String,
pub(crate) message: String,
pub(crate) stack_trace: String,
}
impl Exception {
pub(crate) fn into_error(self) -> Error {
let Exception {
code,
name,
mut message,
mut stack_trace,
} = self;
if message.len() > TCP_EXCEPTION_MESSAGE_CAP {
tracing::warn!(
original_len = message.len(),
cap = TCP_EXCEPTION_MESSAGE_CAP,
"tcp: server exception message truncated"
);
truncate_on_char_boundary(&mut message, TCP_EXCEPTION_MESSAGE_CAP);
}
if stack_trace.len() > TCP_EXCEPTION_STACK_TRACE_CAP {
tracing::warn!(
original_len = stack_trace.len(),
cap = TCP_EXCEPTION_STACK_TRACE_CAP,
"tcp: server exception stack_trace truncated"
);
truncate_on_char_boundary(&mut stack_trace, TCP_EXCEPTION_STACK_TRACE_CAP);
}
Error::ServerException {
code,
name: if name.is_empty() { None } else { Some(name) },
message,
stack_trace: if stack_trace.is_empty() {
None
} else {
Some(stack_trace)
},
}
}
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub(crate) struct Progress {
pub(crate) rows_read: u64,
pub(crate) bytes_read: u64,
pub(crate) total_rows_to_read: u64,
pub(crate) written_rows: u64,
pub(crate) written_bytes: u64,
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub(crate) struct ProfileInfo {
pub(crate) rows: u64,
pub(crate) blocks: u64,
pub(crate) bytes: u64,
pub(crate) applied_limit: bool,
pub(crate) rows_before_limit: u64,
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub(crate) struct TableColumns {
pub(crate) external_table_name: String,
pub(crate) columns_definition: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn server_packet_id_from_u64_round_trips_known() {
let cases = [
(0, ServerPacketId::Hello),
(1, ServerPacketId::Data),
(2, ServerPacketId::Exception),
(3, ServerPacketId::Progress),
(4, ServerPacketId::Pong),
(5, ServerPacketId::EndOfStream),
(6, ServerPacketId::ProfileInfo),
(7, ServerPacketId::Totals),
(8, ServerPacketId::Extremes),
(10, ServerPacketId::Log),
(11, ServerPacketId::TableColumns),
(14, ServerPacketId::ProfileEvents),
(17, ServerPacketId::TimezoneUpdate),
];
for (i, expected) in cases {
assert_eq!(ServerPacketId::from_u64(i).unwrap(), expected);
}
}
#[test]
fn server_packet_id_from_u64_rejects_unknown() {
let err = ServerPacketId::from_u64(99).unwrap_err();
match err {
Error::BadResponse(msg) => {
assert!(msg.contains("99"), "msg should mention id, got: {msg}");
}
other => panic!("expected BadResponse, got {other:?}"),
}
}
#[test]
fn exception_into_error_flat() {
let exc = Exception {
code: 60,
name: "UNKNOWN_TABLE".to_string(),
message: "table foo does not exist".to_string(),
stack_trace: "frame0".to_string(),
};
match exc.into_error() {
Error::ServerException {
code,
name,
message,
stack_trace,
} => {
assert_eq!(code, 60);
assert_eq!(name.as_deref(), Some("UNKNOWN_TABLE"));
assert_eq!(message, "table foo does not exist");
assert_eq!(stack_trace.as_deref(), Some("frame0"));
}
other => panic!("expected ServerException, got {other:?}"),
}
}
#[test]
fn exception_into_error_caps_stack_trace() {
let big = "x".repeat(TCP_EXCEPTION_STACK_TRACE_CAP + 1024);
let exc = Exception {
code: 1,
name: "N".to_string(),
message: "m".to_string(),
stack_trace: big,
};
match exc.into_error() {
Error::ServerException { stack_trace, .. } => {
let s = stack_trace.expect("stack_trace populated");
assert_eq!(s.len(), TCP_EXCEPTION_STACK_TRACE_CAP);
}
other => panic!("expected ServerException, got {other:?}"),
}
}
#[test]
fn into_error_stack_trace_truncation_is_char_boundary_safe() {
let big = "\u{20ac}".repeat(TCP_EXCEPTION_STACK_TRACE_CAP);
assert!(!big.is_char_boundary(TCP_EXCEPTION_STACK_TRACE_CAP));
let exc = Exception {
code: 1,
name: "N".to_string(),
message: "m".to_string(),
stack_trace: big,
};
match exc.into_error() {
Error::ServerException { stack_trace, .. } => {
let s = stack_trace.expect("stack_trace populated");
assert!(s.len() <= TCP_EXCEPTION_STACK_TRACE_CAP);
assert!(s.len() > TCP_EXCEPTION_STACK_TRACE_CAP - 3);
}
other => panic!("expected ServerException, got {other:?}"),
}
}
#[test]
fn into_error_message_truncation_is_char_boundary_safe() {
let big = "\u{20ac}".repeat(TCP_EXCEPTION_MESSAGE_CAP);
assert!(!big.is_char_boundary(TCP_EXCEPTION_MESSAGE_CAP));
let exc = Exception {
code: 60,
name: "N".to_string(),
message: big,
stack_trace: String::new(),
};
match exc.into_error() {
Error::ServerException { message, .. } => {
assert!(
message.len() <= TCP_EXCEPTION_MESSAGE_CAP,
"message must be capped, got {} bytes",
message.len()
);
assert!(message.len() > TCP_EXCEPTION_MESSAGE_CAP - 3);
assert!(message.starts_with('\u{20ac}'));
}
other => panic!("expected ServerException, got {other:?}"),
}
}
#[test]
fn truncate_on_char_boundary_never_splits_a_character() {
for cap in 0..12 {
let mut s = "\u{20ac}\u{20ac}\u{20ac}".to_string();
truncate_on_char_boundary(&mut s, cap);
assert!(s.len() <= cap);
assert_eq!(s.len() % 3, 0, "cut mid-character at cap {cap}");
}
}
#[test]
fn exception_into_error_blank_fields_become_none() {
let exc = Exception {
code: 1,
name: String::new(),
message: "m".to_string(),
stack_trace: String::new(),
};
match exc.into_error() {
Error::ServerException {
name, stack_trace, ..
} => {
assert!(name.is_none());
assert!(stack_trace.is_none());
}
other => panic!("expected ServerException, got {other:?}"),
}
}
}