1mod convert;
6mod database;
7mod dynamic;
8mod host;
9mod postgres;
10mod replay;
11mod run;
12mod sqlda;
13
14pub use convert::{ReadError, Written, read, write};
15pub use database::{Abandoned, Answer, Call, Column, ColumnType, Database, OpenCursor, Outcome, Prepared, Session};
16pub use postgres::{Postgres, Stream, Tls};
17pub use replay::{Recorder, Replay};
18pub use run::{Ran, SqlHost, run};
19
20use crate::vocab::SignClause;
21
22#[derive(Clone, Debug, PartialEq)]
24pub enum Value {
25 Null,
26 Int(i64),
27 Decimal { value: i128, scale: u32 },
29 Double(f64),
30 Char(String),
31 Binary(Vec<u8>),
32 Date(String),
35 Time(String),
36 Timestamp(String),
37}
38
39impl Value {
40 pub fn text(&self) -> String {
42 match self {
43 Value::Null => String::new(),
44 Value::Int(n) => n.to_string(),
45 Value::Decimal { value, scale } => Self::decimal_text(*value, *scale),
46 Value::Double(d) => d.to_string(),
47 Value::Char(s) | Value::Date(s) | Value::Time(s) | Value::Timestamp(s) => s.trim_end().to_owned(),
48 Value::Binary(b) => b.iter().map(|byte| format!("{byte:02X}")).collect(),
49 }
50 }
51
52 pub fn decimal_text(value: i128, scale: u32) -> String {
54 let s = scale as usize;
55 let digits = format!("{:0>width$}", value.unsigned_abs(), width = s + 1);
56 let (whole, places) = digits.split_at(digits.len() - s);
57 let sign = if value < 0 { "-" } else { "" };
58 if s == 0 { format!("{sign}{whole}") } else { format!("{sign}{whole}.{places}") }
59 }
60
61 pub fn parse_decimal(text: &str) -> Option<Value> {
63 let (negative, digits) = match text.strip_prefix('-') {
64 Some(d) => (true, d),
65 None => (false, text.strip_prefix('+').unwrap_or(text)),
66 };
67 let (whole, places) = digits.split_once('.').unwrap_or((digits, ""));
68 if whole.is_empty() || !whole.bytes().chain(places.bytes()).all(|b| b.is_ascii_digit()) {
69 return None;
70 }
71 let magnitude: i128 = format!("{whole}{places}").parse().ok()?;
72 Some(Value::Decimal { value: if negative { -magnitude } else { magnitude }, scale: places.len() as u32 })
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct SqlError {
79 pub code: i32,
80 pub state: &'static str,
81}
82
83pub const NOT_ASSIGNABLE: SqlError = SqlError { code: -303, state: "42806" };
84pub const OUT_OF_RANGE: SqlError = SqlError { code: -304, state: "22003" };
85pub const NULL_WITHOUT_INDICATOR: SqlError = SqlError { code: -305, state: "22002" };
86pub const BAD_LENGTH: SqlError = SqlError { code: -311, state: "22501" };
87pub const UNCONVERTIBLE: SqlError = SqlError { code: -330, state: "22021" };
88
89#[derive(Clone, Debug, PartialEq, Eq)]
91pub enum HostType {
92 SmallInt { signed: bool },
93 Integer { signed: bool },
94 BigInt { signed: bool },
95 Decimal { digits: u32, scale: u32, signed: bool },
96 Zoned { digits: u32, scale: u32, signed: bool, sign: Option<SignClause> },
97 Real,
98 Double,
99 Char(u32),
100 VarChar(u32),
101 Graphic(u32),
103 VarGraphic(u32),
106 Structure(Vec<(usize, HostType)>),
108}
109
110pub fn fingerprint(text: &str) -> u32 {
112 text.bytes().fold(0x811c_9dc5, |h, b| (h ^ u32::from(b)).wrapping_mul(0x0100_0193))
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn the_fingerprint_of_nothing_is_the_fnv_offset() {
121 assert_eq!(fingerprint(""), 0x811c_9dc5);
122 assert_ne!(fingerprint("COMMIT"), fingerprint("ROLLBACK"));
123 }
124}