nodedb_types/columnar/
column_parse.rs1use std::fmt;
7use std::str::FromStr;
8
9use super::column_type::ColumnType;
10
11#[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::SparseVector => f.write_str("SPARSEVECTOR"),
42 Self::Uuid => f.write_str("UUID"),
43 Self::Json => f.write_str("JSON"),
44 Self::Ulid => f.write_str("ULID"),
45 Self::Duration => f.write_str("DURATION"),
46 Self::Array => f.write_str("ARRAY"),
47 Self::Set => f.write_str("SET"),
48 Self::Regex => f.write_str("REGEX"),
49 Self::Range => f.write_str("RANGE"),
50 Self::Record => f.write_str("RECORD"),
51 }
52 }
53}
54
55impl FromStr for ColumnType {
56 type Err = ColumnTypeParseError;
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 let upper = s.trim().to_uppercase();
60
61 if upper.starts_with("NUMERIC") || upper.starts_with("DECIMAL") {
63 let base = if upper.starts_with("NUMERIC") {
64 "NUMERIC"
65 } else {
66 "DECIMAL"
67 };
68 let rest = upper[base.len()..].trim();
69 if rest.is_empty() {
70 return Ok(Self::Decimal {
71 precision: 38,
72 scale: 10,
73 });
74 }
75 if rest.starts_with('(') && rest.ends_with(')') {
76 let inner = &rest[1..rest.len() - 1];
77 let parts: Vec<&str> = inner.splitn(2, ',').collect();
78 let precision: u8 = parts[0]
79 .trim()
80 .parse()
81 .map_err(|_| ColumnTypeParseError::InvalidDecimalParams(rest.to_string()))?;
82 let scale: u8 = parts
83 .get(1)
84 .map(|p| p.trim())
85 .unwrap_or("0")
86 .parse()
87 .map_err(|_| ColumnTypeParseError::InvalidDecimalParams(rest.to_string()))?;
88 if precision == 0 || precision > 38 {
89 return Err(ColumnTypeParseError::InvalidDecimalParams(format!(
90 "precision {precision} out of range 1-38"
91 )));
92 }
93 if scale > precision {
94 return Err(ColumnTypeParseError::InvalidDecimalParams(format!(
95 "scale {scale} must be <= precision {precision}"
96 )));
97 }
98 return Ok(Self::Decimal { precision, scale });
99 }
100 return Err(ColumnTypeParseError::InvalidDecimalParams(rest.to_string()));
101 }
102
103 if upper.starts_with("VECTOR") {
105 let inner = upper
106 .trim_start_matches("VECTOR")
107 .trim()
108 .trim_start_matches('(')
109 .trim_end_matches(')')
110 .trim();
111 if inner.is_empty() {
112 return Err(ColumnTypeParseError::InvalidVectorDim("empty".into()));
113 }
114 let dim: u32 = inner
115 .parse()
116 .map_err(|_| ColumnTypeParseError::InvalidVectorDim(inner.into()))?;
117 if dim == 0 {
118 return Err(ColumnTypeParseError::InvalidVectorDim("0".into()));
119 }
120 return Ok(Self::Vector(dim));
121 }
122
123 match upper.as_str() {
124 "BIGINT" | "INT64" | "INTEGER" | "INT" => Ok(Self::Int64),
125 "FLOAT64" | "DOUBLE" | "REAL" | "FLOAT" => Ok(Self::Float64),
126 "TEXT" | "STRING" | "VARCHAR" => Ok(Self::String),
127 "BOOL" | "BOOLEAN" => Ok(Self::Bool),
128 "BYTES" | "BYTEA" | "BLOB" => Ok(Self::Bytes),
129 "TIMESTAMP" => Ok(Self::Timestamp),
130 "TIMESTAMPTZ" | "TIMESTAMP WITH TIME ZONE" => Ok(Self::Timestamptz),
131 "SYSTEM_TIMESTAMP" | "SYSTEMTIMESTAMP" => Ok(Self::SystemTimestamp),
132 "GEOMETRY" => Ok(Self::Geometry),
133 "SPARSEVECTOR" => Ok(Self::SparseVector),
138 "UUID" => Ok(Self::Uuid),
139 "JSON" | "JSONB" => Ok(Self::Json),
140 "ULID" => Ok(Self::Ulid),
141 "DURATION" => Ok(Self::Duration),
142 "ARRAY" => Ok(Self::Array),
143 "SET" => Ok(Self::Set),
144 "REGEX" => Ok(Self::Regex),
145 "RANGE" => Ok(Self::Range),
146 "RECORD" => Ok(Self::Record),
147 "DATETIME" => Err(ColumnTypeParseError::UseTimestamp),
148 other => Err(ColumnTypeParseError::Unknown(other.to_string())),
149 }
150 }
151}