Skip to main content

nodedb_types/columnar/
column_parse.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! [`std::fmt::Display`] and [`std::str::FromStr`] for [`ColumnType`], plus
4//! [`ColumnTypeParseError`].
5
6use std::fmt;
7use std::str::FromStr;
8
9use super::column_type::ColumnType;
10
11/// Error from parsing a column type string.
12#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13#[non_exhaustive]
14pub enum ColumnTypeParseError {
15    #[error("unknown column type: '{0}'")]
16    Unknown(String),
17    #[error("'DATETIME' is not a valid type — use 'TIMESTAMP' instead")]
18    UseTimestamp,
19    #[error("invalid VECTOR dimension: '{0}' (must be a positive integer)")]
20    InvalidVectorDim(String),
21    #[error(
22        "invalid DECIMAL/NUMERIC params: '{0}' (expected DECIMAL(precision, scale) with precision 1-38 and scale <= precision)"
23    )]
24    InvalidDecimalParams(String),
25}
26
27impl fmt::Display for ColumnType {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::Int64 => f.write_str("BIGINT"),
31            Self::Float64 => f.write_str("FLOAT64"),
32            Self::String => f.write_str("TEXT"),
33            Self::Bool => f.write_str("BOOL"),
34            Self::Bytes => f.write_str("BYTES"),
35            Self::Timestamp => f.write_str("TIMESTAMP"),
36            Self::Timestamptz => f.write_str("TIMESTAMPTZ"),
37            Self::SystemTimestamp => f.write_str("SYSTEM_TIMESTAMP"),
38            Self::Decimal { precision, scale } => write!(f, "DECIMAL({precision},{scale})"),
39            Self::Geometry => f.write_str("GEOMETRY"),
40            Self::Vector(dim) => write!(f, "VECTOR({dim})"),
41            Self::Uuid => f.write_str("UUID"),
42            Self::Json => f.write_str("JSON"),
43            Self::Ulid => f.write_str("ULID"),
44            Self::Duration => f.write_str("DURATION"),
45            Self::Array => f.write_str("ARRAY"),
46            Self::Set => f.write_str("SET"),
47            Self::Regex => f.write_str("REGEX"),
48            Self::Range => f.write_str("RANGE"),
49            Self::Record => f.write_str("RECORD"),
50        }
51    }
52}
53
54impl FromStr for ColumnType {
55    type Err = ColumnTypeParseError;
56
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        let upper = s.trim().to_uppercase();
59
60        // NUMERIC(p,s) / DECIMAL(p,s) special case.
61        if upper.starts_with("NUMERIC") || upper.starts_with("DECIMAL") {
62            let base = if upper.starts_with("NUMERIC") {
63                "NUMERIC"
64            } else {
65                "DECIMAL"
66            };
67            let rest = upper[base.len()..].trim();
68            if rest.is_empty() {
69                return Ok(Self::Decimal {
70                    precision: 38,
71                    scale: 10,
72                });
73            }
74            if rest.starts_with('(') && rest.ends_with(')') {
75                let inner = &rest[1..rest.len() - 1];
76                let parts: Vec<&str> = inner.splitn(2, ',').collect();
77                let precision: u8 = parts[0]
78                    .trim()
79                    .parse()
80                    .map_err(|_| ColumnTypeParseError::InvalidDecimalParams(rest.to_string()))?;
81                let scale: u8 = parts
82                    .get(1)
83                    .map(|p| p.trim())
84                    .unwrap_or("0")
85                    .parse()
86                    .map_err(|_| ColumnTypeParseError::InvalidDecimalParams(rest.to_string()))?;
87                if precision == 0 || precision > 38 {
88                    return Err(ColumnTypeParseError::InvalidDecimalParams(format!(
89                        "precision {precision} out of range 1-38"
90                    )));
91                }
92                if scale > precision {
93                    return Err(ColumnTypeParseError::InvalidDecimalParams(format!(
94                        "scale {scale} must be <= precision {precision}"
95                    )));
96                }
97                return Ok(Self::Decimal { precision, scale });
98            }
99            return Err(ColumnTypeParseError::InvalidDecimalParams(rest.to_string()));
100        }
101
102        // VECTOR(N) special case.
103        if upper.starts_with("VECTOR") {
104            let inner = upper
105                .trim_start_matches("VECTOR")
106                .trim()
107                .trim_start_matches('(')
108                .trim_end_matches(')')
109                .trim();
110            if inner.is_empty() {
111                return Err(ColumnTypeParseError::InvalidVectorDim("empty".into()));
112            }
113            let dim: u32 = inner
114                .parse()
115                .map_err(|_| ColumnTypeParseError::InvalidVectorDim(inner.into()))?;
116            if dim == 0 {
117                return Err(ColumnTypeParseError::InvalidVectorDim("0".into()));
118            }
119            return Ok(Self::Vector(dim));
120        }
121
122        match upper.as_str() {
123            "BIGINT" | "INT64" | "INTEGER" | "INT" => Ok(Self::Int64),
124            "FLOAT64" | "DOUBLE" | "REAL" | "FLOAT" => Ok(Self::Float64),
125            "TEXT" | "STRING" | "VARCHAR" => Ok(Self::String),
126            "BOOL" | "BOOLEAN" => Ok(Self::Bool),
127            "BYTES" | "BYTEA" | "BLOB" => Ok(Self::Bytes),
128            "TIMESTAMP" => Ok(Self::Timestamp),
129            "TIMESTAMPTZ" | "TIMESTAMP WITH TIME ZONE" => Ok(Self::Timestamptz),
130            "SYSTEM_TIMESTAMP" | "SYSTEMTIMESTAMP" => Ok(Self::SystemTimestamp),
131            "GEOMETRY" => Ok(Self::Geometry),
132            "UUID" => Ok(Self::Uuid),
133            "JSON" | "JSONB" => Ok(Self::Json),
134            "ULID" => Ok(Self::Ulid),
135            "DURATION" => Ok(Self::Duration),
136            "ARRAY" => Ok(Self::Array),
137            "SET" => Ok(Self::Set),
138            "REGEX" => Ok(Self::Regex),
139            "RANGE" => Ok(Self::Range),
140            "RECORD" => Ok(Self::Record),
141            "DATETIME" => Err(ColumnTypeParseError::UseTimestamp),
142            other => Err(ColumnTypeParseError::Unknown(other.to_string())),
143        }
144    }
145}