Skip to main content

questdb/egress/wire/
msg_kind.rs

1/*******************************************************************************
2 *     ___                  _   ____  ____
3 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
4 *   | | | | | | |/ _ \/ __| __| | | |  _ \
5 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
6 *    \__\_\\__,_|\___||___/\__|____/|____/
7 *
8 *  Copyright (c) 2014-2019 Appsicle
9 *  Copyright (c) 2019-2025 QuestDB
10 *
11 *  Licensed under the Apache License, Version 2.0 (the "License");
12 *  you may not use this file except in compliance with the License.
13 *  You may obtain a copy of the License at
14 *
15 *  http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 *
23 ******************************************************************************/
24
25//! Message kind discriminator (first byte of frame payload).
26//!
27//! ABI-stable: variants append-only, never reorder.
28
29use crate::error::{Result, fmt};
30
31/// Message kind code (uint8). `repr(u8)` keeps wire transcoding trivial.
32///
33/// `#[non_exhaustive]` because the QWP message-kind table is
34/// append-only across protocol revisions.
35#[repr(u8)]
36#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
37#[non_exhaustive]
38pub enum MsgKind {
39    /// Client → Server: initiate cursor with SQL + binds.
40    QueryRequest = 0x10,
41    /// Server → Client: one table block of results.
42    ResultBatch = 0x11,
43    /// Server → Client: successful stream termination.
44    ResultEnd = 0x12,
45    /// Server → Client: failure at any lifecycle point.
46    QueryError = 0x13,
47    /// Client → Server: request query termination.
48    Cancel = 0x14,
49    /// Client → Server: extend byte-credit window.
50    Credit = 0x15,
51    /// Server → Client: non-SELECT acknowledgement.
52    ExecDone = 0x16,
53    /// Server → Client: clear connection caches.
54    CacheReset = 0x17,
55    /// Server → Client: role + cluster identity.
56    ServerInfo = 0x18,
57}
58
59impl MsgKind {
60    /// Parse a wire byte into a known kind.
61    pub fn from_u8(byte: u8) -> Result<Self> {
62        Ok(match byte {
63            0x10 => MsgKind::QueryRequest,
64            0x11 => MsgKind::ResultBatch,
65            0x12 => MsgKind::ResultEnd,
66            0x13 => MsgKind::QueryError,
67            0x14 => MsgKind::Cancel,
68            0x15 => MsgKind::Credit,
69            0x16 => MsgKind::ExecDone,
70            0x17 => MsgKind::CacheReset,
71            0x18 => MsgKind::ServerInfo,
72            other => return Err(fmt!(ProtocolError, "unknown msg_kind 0x{:02X}", other)),
73        })
74    }
75
76    /// Wire byte for this kind.
77    pub fn as_u8(self) -> u8 {
78        self as u8
79    }
80}
81
82/// QWP status codes carried by `QUERY_ERROR` (and surfaced to clients).
83///
84/// `#[non_exhaustive]` because the status table is append-only across
85/// protocol revisions.
86#[repr(u8)]
87#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
88#[non_exhaustive]
89pub enum StatusCode {
90    SchemaMismatch = 0x03,
91    ParseError = 0x05,
92    InternalError = 0x06,
93    SecurityError = 0x08,
94    Cancelled = 0x0A,
95    LimitExceeded = 0x0B,
96}
97
98impl StatusCode {
99    pub fn from_u8(byte: u8) -> Result<Self> {
100        Ok(match byte {
101            0x03 => StatusCode::SchemaMismatch,
102            0x05 => StatusCode::ParseError,
103            0x06 => StatusCode::InternalError,
104            0x08 => StatusCode::SecurityError,
105            0x0A => StatusCode::Cancelled,
106            0x0B => StatusCode::LimitExceeded,
107            other => {
108                return Err(fmt!(
109                    ProtocolError,
110                    "unknown QWP status code 0x{:02X}",
111                    other
112                ));
113            }
114        })
115    }
116
117    pub fn as_u8(self) -> u8 {
118        self as u8
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn msg_kind_roundtrip() {
128        for &k in &[
129            MsgKind::QueryRequest,
130            MsgKind::ResultBatch,
131            MsgKind::ResultEnd,
132            MsgKind::QueryError,
133            MsgKind::Cancel,
134            MsgKind::Credit,
135            MsgKind::ExecDone,
136            MsgKind::CacheReset,
137            MsgKind::ServerInfo,
138        ] {
139            let b = k.as_u8();
140            assert_eq!(MsgKind::from_u8(b).unwrap(), k);
141        }
142    }
143
144    #[test]
145    fn unknown_msg_kind_rejected() {
146        assert!(MsgKind::from_u8(0x00).is_err());
147        assert!(MsgKind::from_u8(0xFF).is_err());
148        assert!(MsgKind::from_u8(0x09).is_err());
149    }
150
151    #[test]
152    fn status_code_roundtrip() {
153        for &s in &[
154            StatusCode::SchemaMismatch,
155            StatusCode::ParseError,
156            StatusCode::InternalError,
157            StatusCode::SecurityError,
158            StatusCode::Cancelled,
159            StatusCode::LimitExceeded,
160        ] {
161            assert_eq!(StatusCode::from_u8(s.as_u8()).unwrap(), s);
162        }
163    }
164}