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::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 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 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}