1use std::collections::{BTreeMap, VecDeque};
4
5use bytes::Bytes;
6
7use crate::codec::{BackendMessage, DiagnosticResponse, TransactionStatus};
8
9#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
11pub struct CommandIndex(pub u64);
12
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct TaggedNotice {
16 pub command: CommandIndex,
18 pub fields: DiagnosticResponse,
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct Notification {
25 pub process_id: u32,
27 pub channel: Bytes,
29 pub payload: Bytes,
31}
32
33#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct ParameterStatus {
36 pub name: Bytes,
38 pub value: Bytes,
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
44pub enum AsyncEvent {
45 Notice(TaggedNotice),
47 ParameterStatus(ParameterStatus),
49 Notification(Notification),
51}
52
53#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct OrderedAsyncEvent {
56 pub sequence: u64,
58 pub command: CommandIndex,
60 pub event: AsyncEvent,
62}
63
64#[derive(Clone, Eq, Hash, PartialEq)]
65pub struct CancelKey {
67 pub process_id: u32,
69 pub secret_key: Bytes,
71}
72
73impl std::fmt::Debug for CancelKey {
74 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 formatter
76 .debug_struct("CancelKey")
77 .field("process_id", &self.process_id)
78 .field("secret_key", &"[REDACTED]")
79 .finish()
80 }
81}
82
83#[derive(Clone, Debug, Eq, PartialEq)]
85pub enum SessionItem {
86 Message(BackendMessage),
88 ReadyForQuery {
90 status: TransactionStatus,
92 parameters_changed: bool,
94 },
95 CommandComplete {
97 tag: Bytes,
99 command: CommandIndex,
101 notices: Vec<TaggedNotice>,
103 },
104}
105
106impl SessionItem {
107 #[must_use]
114 pub fn into_backend_message(self) -> BackendMessage {
115 match self {
116 Self::Message(message) => message,
117 Self::ReadyForQuery { status, .. } => BackendMessage::ReadyForQuery(status),
118 Self::CommandComplete { tag, .. } => BackendMessage::CommandComplete(tag),
119 }
120 }
121}
122
123#[derive(Debug, Default)]
125pub struct Demux {
126 command: CommandIndex,
127 pending_notices: Vec<TaggedNotice>,
128 notices: VecDeque<TaggedNotice>,
129 notifications: VecDeque<Notification>,
130 parameter_statuses: VecDeque<ParameterStatus>,
131 async_events: VecDeque<OrderedAsyncEvent>,
132 next_async_sequence: u64,
133 parameters: BTreeMap<Bytes, Bytes>,
134 startup_parameters: Option<BTreeMap<Bytes, Bytes>>,
135 parameters_changed: bool,
136 cancel_key: Option<CancelKey>,
137 transaction_status: Option<TransactionStatus>,
138}
139
140impl Demux {
141 #[must_use]
146 pub fn is_asynchronous(message: &BackendMessage) -> bool {
147 matches!(
148 message,
149 BackendMessage::NoticeResponse(_)
150 | BackendMessage::ParameterStatus { .. }
151 | BackendMessage::NotificationResponse { .. }
152 | BackendMessage::BackendKeyData { .. }
153 )
154 }
155
156 pub fn route(&mut self, message: BackendMessage) -> Option<SessionItem> {
161 match message {
162 BackendMessage::NoticeResponse(fields) => {
163 let notice = TaggedNotice {
164 command: self.command,
165 fields,
166 };
167 self.pending_notices.push(notice.clone());
168 self.notices.push_back(notice.clone());
169 self.push_async(AsyncEvent::Notice(notice));
170 None
171 }
172 BackendMessage::ParameterStatus { name, value } => {
173 self.parameters.insert(name.clone(), value.clone());
174 let status = ParameterStatus { name, value };
175 self.parameter_statuses.push_back(status.clone());
176 self.push_async(AsyncEvent::ParameterStatus(status));
177 if let Some(startup_parameters) = &self.startup_parameters {
178 self.parameters_changed = self.parameters != *startup_parameters;
179 }
180 None
181 }
182 BackendMessage::NotificationResponse {
183 process_id,
184 channel,
185 payload,
186 } => {
187 let notification = Notification {
188 process_id,
189 channel,
190 payload,
191 };
192 self.notifications.push_back(notification.clone());
193 self.push_async(AsyncEvent::Notification(notification));
194 None
195 }
196 BackendMessage::BackendKeyData {
197 process_id,
198 secret_key,
199 } => {
200 self.cancel_key = Some(CancelKey {
201 process_id,
202 secret_key: secret_key.clone(),
203 });
204 Some(SessionItem::Message(BackendMessage::BackendKeyData {
205 process_id,
206 secret_key,
207 }))
208 }
209 BackendMessage::ReadyForQuery(status) => {
210 self.transaction_status = Some(status);
211 if self.startup_parameters.is_none() {
212 self.startup_parameters = Some(self.parameters.clone());
213 }
214 Some(SessionItem::ReadyForQuery {
215 status,
216 parameters_changed: self.parameters_changed,
217 })
218 }
219 BackendMessage::CommandComplete(tag) => {
220 let command = self.command;
221 let notices = std::mem::take(&mut self.pending_notices);
222 self.command.0 = self.command.0.saturating_add(1);
223 Some(SessionItem::CommandComplete {
224 tag,
225 command,
226 notices,
227 })
228 }
229 message => Some(SessionItem::Message(message)),
230 }
231 }
232
233 #[must_use]
235 pub fn parameters(&self) -> &BTreeMap<Bytes, Bytes> {
236 &self.parameters
237 }
238
239 #[must_use]
241 pub const fn parameters_changed(&self) -> bool {
242 self.parameters_changed
243 }
244
245 #[must_use]
247 pub const fn cancel_key(&self) -> Option<&CancelKey> {
248 self.cancel_key.as_ref()
249 }
250
251 #[must_use]
253 pub const fn transaction_status(&self) -> Option<TransactionStatus> {
254 self.transaction_status
255 }
256
257 pub fn pop_notification(&mut self) -> Option<Notification> {
259 self.notifications.pop_front()
260 }
261
262 pub fn pop_notice(&mut self) -> Option<TaggedNotice> {
264 self.notices.pop_front()
265 }
266
267 pub fn pop_parameter_status(&mut self) -> Option<ParameterStatus> {
269 self.parameter_statuses.pop_front()
270 }
271
272 pub fn pop_async_event(&mut self) -> Option<OrderedAsyncEvent> {
274 self.async_events.pop_front()
275 }
276
277 fn push_async(&mut self, event: AsyncEvent) {
278 let sequence = self.next_async_sequence;
279 self.next_async_sequence = self.next_async_sequence.saturating_add(1);
280 self.async_events.push_back(OrderedAsyncEvent {
281 sequence,
282 command: self.command,
283 event,
284 });
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn notices_are_attached_to_their_command_boundary() {
294 let mut demux = Demux::default();
295 assert_eq!(
296 demux.route(BackendMessage::NoticeResponse(DiagnosticResponse {
297 fields: vec![crate::codec::DiagnosticField {
298 code: b'M',
299 value: Bytes::from_static(b"notice"),
300 }],
301 })),
302 None
303 );
304 let completion = demux
305 .route(BackendMessage::CommandComplete(Bytes::from_static(
306 b"SELECT 1",
307 )))
308 .expect("command completion advances the session");
309 assert_eq!(
310 completion,
311 SessionItem::CommandComplete {
312 tag: Bytes::from_static(b"SELECT 1"),
313 command: CommandIndex(0),
314 notices: vec![TaggedNotice {
315 command: CommandIndex(0),
316 fields: DiagnosticResponse {
317 fields: vec![crate::codec::DiagnosticField {
318 code: b'M',
319 value: Bytes::from_static(b"notice"),
320 }],
321 },
322 }],
323 }
324 );
325 assert_eq!(
326 demux.pop_notice(),
327 Some(TaggedNotice {
328 command: CommandIndex(0),
329 fields: DiagnosticResponse {
330 fields: vec![crate::codec::DiagnosticField {
331 code: b'M',
332 value: Bytes::from_static(b"notice"),
333 }],
334 },
335 })
336 );
337 assert_eq!(demux.pop_notice(), None);
338 }
339
340 #[test]
341 fn startup_parameters_establish_a_clean_baseline() {
342 let mut demux = Demux::default();
343 assert!(
344 demux
345 .route(BackendMessage::ParameterStatus {
346 name: Bytes::from_static(b"client_encoding"),
347 value: Bytes::from_static(b"UTF8"),
348 })
349 .is_none()
350 );
351 demux.route(BackendMessage::ReadyForQuery(TransactionStatus::Idle));
352 assert!(!demux.parameters_changed());
353
354 demux.route(BackendMessage::ParameterStatus {
355 name: Bytes::from_static(b"client_encoding"),
356 value: Bytes::from_static(b"LATIN1"),
357 });
358 assert!(demux.parameters_changed());
359 }
360
361 #[test]
362 fn parameter_statuses_remain_ordered_for_proxy_forwarding() {
363 let mut demux = Demux::default();
364 for (name, value) in [
365 (b"TimeZone".as_slice(), b"UTC".as_slice()),
366 (b"TimeZone", b"GMT"),
367 ] {
368 assert!(
369 demux
370 .route(BackendMessage::ParameterStatus {
371 name: Bytes::copy_from_slice(name),
372 value: Bytes::copy_from_slice(value),
373 })
374 .is_none()
375 );
376 }
377
378 assert_eq!(
379 demux.pop_parameter_status(),
380 Some(ParameterStatus {
381 name: Bytes::from_static(b"TimeZone"),
382 value: Bytes::from_static(b"UTC"),
383 })
384 );
385 assert_eq!(
386 demux.pop_parameter_status(),
387 Some(ParameterStatus {
388 name: Bytes::from_static(b"TimeZone"),
389 value: Bytes::from_static(b"GMT"),
390 })
391 );
392 assert_eq!(demux.pop_parameter_status(), None);
393 assert_eq!(
394 demux.parameters().get(b"TimeZone".as_slice()),
395 Some(&Bytes::from_static(b"GMT"))
396 );
397 }
398
399 #[test]
400 fn notification_is_not_a_session_transition() {
401 let mut demux = Demux::default();
402 assert!(
403 demux
404 .route(BackendMessage::NotificationResponse {
405 process_id: 42,
406 channel: Bytes::from_static(b"events"),
407 payload: Bytes::from_static(b"payload"),
408 })
409 .is_none()
410 );
411 assert_eq!(
412 demux.pop_notification(),
413 Some(Notification {
414 process_id: 42,
415 channel: Bytes::from_static(b"events"),
416 payload: Bytes::from_static(b"payload"),
417 })
418 );
419 }
420
421 #[test]
422 fn asynchronous_events_preserve_cross_kind_wire_order() {
423 let mut demux = Demux::default();
424 demux.route(BackendMessage::ParameterStatus {
425 name: Bytes::from_static(b"TimeZone"),
426 value: Bytes::from_static(b"UTC"),
427 });
428 demux.route(BackendMessage::NotificationResponse {
429 process_id: 7,
430 channel: Bytes::from_static(b"jobs"),
431 payload: Bytes::from_static(b"ready"),
432 });
433 demux.route(BackendMessage::NoticeResponse(DiagnosticResponse {
434 fields: vec![],
435 }));
436
437 let events = std::iter::from_fn(|| demux.pop_async_event()).collect::<Vec<_>>();
438 assert_eq!(events.len(), 3);
439 assert_eq!(events[0].sequence, 0);
440 assert!(matches!(events[0].event, AsyncEvent::ParameterStatus(_)));
441 assert_eq!(events[1].sequence, 1);
442 assert!(matches!(events[1].event, AsyncEvent::Notification(_)));
443 assert_eq!(events[2].sequence, 2);
444 assert!(matches!(events[2].event, AsyncEvent::Notice(_)));
445 assert!(events.iter().all(|event| event.command == CommandIndex(0)));
446 }
447}