hyperdb_api_core/client/statement.rs
1// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Prepared statement handling.
5
6use crate::types::Oid;
7
8/// Format code for column data.
9///
10/// This indicates how data values are encoded in the wire protocol.
11/// The format affects how values are serialized/deserialized and can
12/// significantly impact performance.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum ColumnFormat {
15 /// Text format (human-readable ASCII).
16 ///
17 /// Values are sent as UTF-8 strings. Slower but human-readable.
18 /// Use for debugging or when compatibility with text-based tools is needed.
19 #[default]
20 Text,
21 /// Standard `PostgreSQL` binary format.
22 ///
23 /// Uses `PostgreSQL`'s standard binary encoding (`BigEndian` for most types).
24 /// Compatible with standard `PostgreSQL` clients.
25 Binary,
26 /// Hyper-specific binary format (little-endian, optimized).
27 ///
28 /// Uses Hyper's optimized binary format where all multi-byte values are
29 /// **little-endian** (x86/ARM-LE native byte order), avoiding byte-swapping
30 /// on modern hardware. This contrasts with standard `PostgreSQL` binary format
31 /// which uses **big-endian** (network byte order).
32 ///
33 /// Additional differences from `PostgreSQL` binary:
34 /// - No per-row field count prefix (rows are implicitly framed)
35 /// - NULL is a 1-byte indicator on nullable columns only (vs 4-byte `-1` length)
36 /// - Fixed-size types have no length prefix (vs 4-byte length in PG binary)
37 ///
38 /// This is the fastest format and is used by default for `query_fast()`,
39 /// `query_streaming()`, and the COPY bulk insertion path.
40 HyperBinary,
41}
42
43impl ColumnFormat {
44 /// Creates a `ColumnFormat` from the wire protocol format code.
45 ///
46 /// Format codes: 0 = Text, 1 = Binary, 2 = `HyperBinary`
47 #[must_use]
48 pub fn from_code(code: i16) -> Self {
49 match code {
50 0 => ColumnFormat::Text,
51 1 => ColumnFormat::Binary,
52 2 => ColumnFormat::HyperBinary,
53 _ => ColumnFormat::Text, // Default to text for unknown codes
54 }
55 }
56
57 /// Returns the wire protocol format code.
58 #[must_use]
59 pub fn to_code(self) -> i16 {
60 match self {
61 ColumnFormat::Text => 0,
62 ColumnFormat::Binary => 1,
63 ColumnFormat::HyperBinary => 2,
64 }
65 }
66
67 /// Returns true if this is a binary format (Binary or `HyperBinary`).
68 #[must_use]
69 pub fn is_binary(self) -> bool {
70 matches!(self, ColumnFormat::Binary | ColumnFormat::HyperBinary)
71 }
72}
73
74/// Metadata about a column in a result set.
75///
76/// `Column` carries the three pieces of information the wire protocol's
77/// `RowDescription` message provides for each result field: the column
78/// name, its type OID, and the type modifier that encodes width-specific
79/// parameters like `NUMERIC(precision, scale)` or `VARCHAR(n)`.
80///
81/// The type modifier is essential for decoding types whose wire format
82/// depends on declared precision/scale (e.g. `NUMERIC`, where precision
83/// ≤ 18 uses an 8-byte `i64` wire form and precision > 18 uses a
84/// 16-byte `i128` wire form — and in both cases the scale needed to
85/// interpret the unscaled integer value lives only in the type
86/// modifier). Upper layers construct a `SqlType` from OID + modifier
87/// via [`crate::types::SqlType::from_oid_and_modifier`] — using
88/// [`crate::types::SqlType::from_oid`] alone silently drops precision
89/// and scale, causing decoders to default to scale = 0 and corrupt
90/// fractional values.
91#[derive(Debug, Clone)]
92pub struct Column {
93 pub(crate) name: String,
94 pub(crate) type_oid: Oid,
95 /// PostgreSQL-style type modifier. For NUMERIC columns the encoding
96 /// is `((precision << 16) | scale) + 4`; for VARCHAR it's
97 /// `length + 4`; for most other types the server sends `-1`
98 /// (no modifier). Parse with
99 /// [`SqlType::from_oid_and_modifier`](crate::types::SqlType::from_oid_and_modifier).
100 pub(crate) type_modifier: i32,
101 pub(crate) format: ColumnFormat,
102}
103
104impl Column {
105 /// Creates a new Column.
106 #[inline]
107 pub(crate) fn new(
108 name: String,
109 type_oid: Oid,
110 type_modifier: i32,
111 format: ColumnFormat,
112 ) -> Self {
113 Column {
114 name,
115 type_oid,
116 type_modifier,
117 format,
118 }
119 }
120
121 /// Returns the column name.
122 #[inline]
123 #[must_use]
124 pub fn name(&self) -> &str {
125 &self.name
126 }
127
128 /// Returns the column type OID.
129 #[inline]
130 #[must_use]
131 pub fn type_oid(&self) -> Oid {
132 self.type_oid
133 }
134
135 /// Returns the column's type modifier (PostgreSQL-style `atttypmod`).
136 ///
137 /// For `NUMERIC` columns this encodes precision and scale and is
138 /// required for correct decode (see
139 /// [`crate::types::SqlType::from_oid_and_modifier`]). For most other
140 /// types the server sends `-1` to indicate "no modifier".
141 #[inline]
142 #[must_use]
143 pub fn type_modifier(&self) -> i32 {
144 self.type_modifier
145 }
146
147 /// Returns the data format for this column.
148 #[inline]
149 #[must_use]
150 pub fn format(&self) -> ColumnFormat {
151 self.format
152 }
153}