1use nodedb_types::Value;
10
11use crate::error::SqlError;
12
13#[derive(Debug, Clone, PartialEq)]
15pub enum TypeExpr {
16 Null,
18 Simple(SimpleType),
20 TypedArray(Box<TypeExpr>),
22 TypedSet(Box<TypeExpr>),
24 Union(Vec<TypeExpr>),
26}
27
28#[derive(Debug, Clone, PartialEq)]
30pub enum SimpleType {
31 Int,
32 Float,
33 String,
34 Bool,
35 Bytes,
36 Timestamp,
37 Timestamptz,
38 Decimal,
39 Uuid,
40 Ulid,
41 Geometry,
42 Duration,
43 Array,
45 Object,
46 Json,
47 Set,
49 Regex,
50 Range,
51 Record,
52 Vector(u32),
54 SparseVector,
57}
58
59pub fn parse_type_expr(s: &str) -> Result<TypeExpr, SqlError> {
75 let s = s.trim();
76 if s.is_empty() {
77 return Err(SqlError::Parse {
78 detail: "empty type expression".to_string(),
79 });
80 }
81 let mut pos = 0usize;
82 let chars: Vec<char> = s.chars().collect();
83 parse_union(&chars, &mut pos, false)
84}
85
86fn parse_union(chars: &[char], pos: &mut usize, stop_at_gt: bool) -> Result<TypeExpr, SqlError> {
91 let mut variants: Vec<TypeExpr> = Vec::new();
92 variants.push(parse_single(chars, pos, stop_at_gt)?);
93
94 loop {
95 skip_ws(chars, pos);
96 if *pos >= chars.len() {
97 break;
98 }
99 if stop_at_gt && chars[*pos] == '>' {
100 break;
101 }
102 if chars[*pos] != '|' {
103 break;
104 }
105 *pos += 1; skip_ws(chars, pos);
107 variants.push(parse_single(chars, pos, stop_at_gt)?);
108 }
109
110 if variants.len() == 1 {
111 Ok(variants.remove(0))
112 } else {
113 Ok(TypeExpr::Union(variants))
114 }
115}
116
117fn parse_single(chars: &[char], pos: &mut usize, stop_at_gt: bool) -> Result<TypeExpr, SqlError> {
119 skip_ws(chars, pos);
120 let keyword = read_keyword(chars, pos);
121 if keyword.is_empty() {
122 return Err(SqlError::Parse {
123 detail: format!(
124 "expected type keyword at position {pos}, found: {:?}",
125 chars.get(*pos)
126 ),
127 });
128 }
129
130 match keyword.as_str() {
131 "NULL" => Ok(TypeExpr::Null),
132
133 "INT" | "INTEGER" | "BIGINT" | "INT64" => Ok(TypeExpr::Simple(SimpleType::Int)),
134 "FLOAT" | "DOUBLE" | "REAL" | "FLOAT64" => Ok(TypeExpr::Simple(SimpleType::Float)),
135 "STRING" | "TEXT" | "VARCHAR" => Ok(TypeExpr::Simple(SimpleType::String)),
136 "BOOL" | "BOOLEAN" => Ok(TypeExpr::Simple(SimpleType::Bool)),
137 "BYTES" | "BYTEA" | "BLOB" => Ok(TypeExpr::Simple(SimpleType::Bytes)),
138 "TIMESTAMP" => Ok(TypeExpr::Simple(SimpleType::Timestamp)),
139 "TIMESTAMPTZ" => Ok(TypeExpr::Simple(SimpleType::Timestamptz)),
140 "DECIMAL" | "NUMERIC" => Ok(TypeExpr::Simple(SimpleType::Decimal)),
141 "UUID" => Ok(TypeExpr::Simple(SimpleType::Uuid)),
142 "ULID" => Ok(TypeExpr::Simple(SimpleType::Ulid)),
143 "GEOMETRY" => Ok(TypeExpr::Simple(SimpleType::Geometry)),
144 "DURATION" => Ok(TypeExpr::Simple(SimpleType::Duration)),
145 "OBJECT" => Ok(TypeExpr::Simple(SimpleType::Object)),
146 "JSON" => Ok(TypeExpr::Simple(SimpleType::Json)),
147 "REGEX" => Ok(TypeExpr::Simple(SimpleType::Regex)),
148 "RANGE" => Ok(TypeExpr::Simple(SimpleType::Range)),
149 "RECORD" => Ok(TypeExpr::Simple(SimpleType::Record)),
150
151 "SPARSEVECTOR" => Ok(TypeExpr::Simple(SimpleType::SparseVector)),
155
156 "VECTOR" => {
157 skip_ws(chars, pos);
159 if *pos >= chars.len() || chars[*pos] != '(' {
160 return Err(SqlError::Parse {
161 detail: format!("expected '(' after VECTOR at position {pos}"),
162 });
163 }
164 *pos += 1; skip_ws(chars, pos);
166 let digits = read_digits(chars, pos);
167 if digits.is_empty() {
168 return Err(SqlError::Parse {
169 detail: "expected dimension digits inside VECTOR(...)".to_string(),
170 });
171 }
172 let dim: u32 = digits.parse().map_err(|_| SqlError::Parse {
173 detail: format!("invalid VECTOR dimension: '{digits}'"),
174 })?;
175 if dim == 0 {
176 return Err(SqlError::Parse {
177 detail: "VECTOR dimension must be > 0".to_string(),
178 });
179 }
180 skip_ws(chars, pos);
181 if *pos >= chars.len() || chars[*pos] != ')' {
182 return Err(SqlError::Parse {
183 detail: format!("expected ')' to close VECTOR({dim} at position {pos}"),
184 });
185 }
186 *pos += 1; Ok(TypeExpr::Simple(SimpleType::Vector(dim)))
188 }
189
190 "ARRAY" => {
191 skip_ws(chars, pos);
193 if *pos < chars.len() && chars[*pos] == '<' {
194 *pos += 1; skip_ws(chars, pos);
196 let inner = parse_union(chars, pos, true)?;
197 skip_ws(chars, pos);
198 if *pos >= chars.len() || chars[*pos] != '>' {
199 return Err(SqlError::Parse {
200 detail: format!("expected '>' to close ARRAY<...> at position {pos}"),
201 });
202 }
203 *pos += 1; Ok(TypeExpr::TypedArray(Box::new(inner)))
205 } else {
206 Ok(TypeExpr::Simple(SimpleType::Array))
207 }
208 }
209
210 "SET" => {
211 skip_ws(chars, pos);
213 if *pos < chars.len() && chars[*pos] == '<' && !stop_at_gt {
214 *pos += 1; skip_ws(chars, pos);
216 let inner = parse_union(chars, pos, true)?;
217 skip_ws(chars, pos);
218 if *pos >= chars.len() || chars[*pos] != '>' {
219 return Err(SqlError::Parse {
220 detail: format!("expected '>' to close SET<...> at position {pos}"),
221 });
222 }
223 *pos += 1; Ok(TypeExpr::TypedSet(Box::new(inner)))
225 } else if *pos < chars.len() && chars[*pos] == '<' {
226 *pos += 1;
228 skip_ws(chars, pos);
229 let inner = parse_union(chars, pos, true)?;
230 skip_ws(chars, pos);
231 if *pos >= chars.len() || chars[*pos] != '>' {
232 return Err(SqlError::Parse {
233 detail: format!("expected '>' to close SET<...> at position {pos}"),
234 });
235 }
236 *pos += 1;
237 Ok(TypeExpr::TypedSet(Box::new(inner)))
238 } else {
239 Ok(TypeExpr::Simple(SimpleType::Set))
240 }
241 }
242
243 other => Err(SqlError::Parse {
244 detail: format!("unknown type keyword: '{other}'"),
245 }),
246 }
247}
248
249fn skip_ws(chars: &[char], pos: &mut usize) {
250 while *pos < chars.len() && chars[*pos].is_ascii_whitespace() {
251 *pos += 1;
252 }
253}
254
255fn read_keyword(chars: &[char], pos: &mut usize) -> String {
257 let mut s = String::new();
258 while *pos < chars.len() {
259 let c = chars[*pos];
260 if c.is_ascii_alphanumeric() || c == '_' {
261 s.push(c.to_ascii_uppercase());
262 *pos += 1;
263 } else {
264 break;
265 }
266 }
267 s
268}
269
270fn read_digits(chars: &[char], pos: &mut usize) -> String {
272 let mut s = String::new();
273 while *pos < chars.len() && chars[*pos].is_ascii_digit() {
274 s.push(chars[*pos]);
275 *pos += 1;
276 }
277 s
278}
279
280pub fn value_matches_type(value: &Value, expr: &TypeExpr) -> bool {
296 match expr {
297 TypeExpr::Null => matches!(value, Value::Null),
298
299 TypeExpr::Simple(simple) => value_matches_simple(value, simple),
300
301 TypeExpr::TypedArray(inner) => match value {
302 Value::Array(items) => items.iter().all(|item| value_matches_type(item, inner)),
303 _ => false,
304 },
305
306 TypeExpr::TypedSet(inner) => match value {
307 Value::Set(items) => items.iter().all(|item| value_matches_type(item, inner)),
308 _ => false,
309 },
310
311 TypeExpr::Union(variants) => variants.iter().any(|v| value_matches_type(value, v)),
312 }
313}
314
315fn value_matches_simple(value: &Value, simple: &SimpleType) -> bool {
316 match simple {
317 SimpleType::Int => matches!(value, Value::Integer(_)),
318 SimpleType::Float => matches!(value, Value::Float(_) | Value::Integer(_)),
319 SimpleType::String => matches!(value, Value::String(_)),
320 SimpleType::Bool => matches!(value, Value::Bool(_)),
321 SimpleType::Bytes => matches!(value, Value::Bytes(_)),
322 SimpleType::Timestamp => matches!(
323 value,
324 Value::NaiveDateTime(_) | Value::Integer(_) | Value::String(_)
325 ),
326 SimpleType::Timestamptz => matches!(
327 value,
328 Value::DateTime(_) | Value::Integer(_) | Value::String(_)
329 ),
330 SimpleType::Decimal => matches!(
331 value,
332 Value::Decimal(_) | Value::Float(_) | Value::Integer(_) | Value::String(_)
333 ),
334 SimpleType::Uuid => matches!(value, Value::Uuid(_) | Value::String(_)),
335 SimpleType::Ulid => matches!(value, Value::Ulid(_) | Value::String(_)),
336 SimpleType::Geometry => matches!(value, Value::Geometry(_) | Value::String(_)),
337 SimpleType::Duration => matches!(value, Value::Duration(_)),
338 SimpleType::Array => matches!(value, Value::Array(_)),
339 SimpleType::Object => matches!(value, Value::Object(_)),
340 SimpleType::Json => true, SimpleType::Set => matches!(value, Value::Set(_)),
342 SimpleType::Regex => matches!(value, Value::Regex(_)),
343 SimpleType::Range => matches!(value, Value::Range { .. }),
344 SimpleType::Record => matches!(value, Value::Record { .. }),
345 SimpleType::Vector(_) => matches!(value, Value::Array(_) | Value::Bytes(_)),
346 SimpleType::SparseVector => matches!(value, Value::String(_) | Value::Bytes(_)),
349 }
350}
351
352#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
361 fn parse_simple() {
362 assert_eq!(
363 parse_type_expr("STRING").unwrap(),
364 TypeExpr::Simple(SimpleType::String)
365 );
366 }
367
368 #[test]
369 fn parse_union() {
370 assert_eq!(
371 parse_type_expr("STRING|NULL").unwrap(),
372 TypeExpr::Union(vec![TypeExpr::Simple(SimpleType::String), TypeExpr::Null])
373 );
374 }
375
376 #[test]
377 fn parse_typed_array() {
378 assert_eq!(
379 parse_type_expr("ARRAY<STRING>").unwrap(),
380 TypeExpr::TypedArray(Box::new(TypeExpr::Simple(SimpleType::String)))
381 );
382 }
383
384 #[test]
385 fn parse_typed_array_union() {
386 assert_eq!(
387 parse_type_expr("ARRAY<INT|FLOAT>").unwrap(),
388 TypeExpr::TypedArray(Box::new(TypeExpr::Union(vec![
389 TypeExpr::Simple(SimpleType::Int),
390 TypeExpr::Simple(SimpleType::Float),
391 ])))
392 );
393 }
394
395 #[test]
396 fn parse_vector() {
397 assert_eq!(
398 parse_type_expr("VECTOR(384)").unwrap(),
399 TypeExpr::Simple(SimpleType::Vector(384))
400 );
401 }
402
403 #[test]
404 fn parse_sparsevector() {
405 assert_eq!(
407 parse_type_expr("SPARSEVECTOR").unwrap(),
408 TypeExpr::Simple(SimpleType::SparseVector)
409 );
410 assert_eq!(
411 parse_type_expr("sparsevector").unwrap(),
412 TypeExpr::Simple(SimpleType::SparseVector)
413 );
414 }
415
416 #[test]
417 fn match_sparsevector_accepts_string_literal() {
418 let expr = parse_type_expr("SPARSEVECTOR").unwrap();
419 assert!(value_matches_type(&Value::String("{3:0.5}".into()), &expr));
420 assert!(value_matches_type(&Value::Bytes(vec![1, 2, 3]), &expr));
421 assert!(!value_matches_type(&Value::Integer(1), &expr));
422 }
423
424 #[test]
425 fn parse_case_insensitive() {
426 assert_eq!(
427 parse_type_expr("string|null").unwrap(),
428 TypeExpr::Union(vec![TypeExpr::Simple(SimpleType::String), TypeExpr::Null])
429 );
430 }
431
432 #[test]
433 fn parse_aliases() {
434 assert_eq!(
436 parse_type_expr("INTEGER").unwrap(),
437 TypeExpr::Simple(SimpleType::Int)
438 );
439 assert_eq!(
440 parse_type_expr("BIGINT").unwrap(),
441 TypeExpr::Simple(SimpleType::Int)
442 );
443 assert_eq!(
444 parse_type_expr("INT64").unwrap(),
445 TypeExpr::Simple(SimpleType::Int)
446 );
447 assert_eq!(
449 parse_type_expr("TEXT").unwrap(),
450 TypeExpr::Simple(SimpleType::String)
451 );
452 assert_eq!(
454 parse_type_expr("BOOLEAN").unwrap(),
455 TypeExpr::Simple(SimpleType::Bool)
456 );
457 assert_eq!(
459 parse_type_expr("BYTEA").unwrap(),
460 TypeExpr::Simple(SimpleType::Bytes)
461 );
462 }
463
464 #[test]
465 fn parse_typed_set() {
466 assert_eq!(
467 parse_type_expr("SET<INT>").unwrap(),
468 TypeExpr::TypedSet(Box::new(TypeExpr::Simple(SimpleType::Int)))
469 );
470 }
471
472 #[test]
473 fn parse_untyped_array() {
474 assert_eq!(
475 parse_type_expr("ARRAY").unwrap(),
476 TypeExpr::Simple(SimpleType::Array)
477 );
478 }
479
480 #[test]
481 fn parse_untyped_set() {
482 assert_eq!(
483 parse_type_expr("SET").unwrap(),
484 TypeExpr::Simple(SimpleType::Set)
485 );
486 }
487
488 #[test]
489 fn parse_error_unknown_keyword() {
490 assert!(parse_type_expr("FOOBAR").is_err());
491 }
492
493 #[test]
494 fn parse_error_empty() {
495 assert!(parse_type_expr("").is_err());
496 assert!(parse_type_expr(" ").is_err());
497 }
498
499 #[test]
500 fn parse_error_vector_zero_dim() {
501 assert!(parse_type_expr("VECTOR(0)").is_err());
502 }
503
504 #[test]
507 fn match_string() {
508 let expr = parse_type_expr("STRING").unwrap();
509 assert!(value_matches_type(&Value::String("hello".into()), &expr));
510 assert!(!value_matches_type(&Value::Integer(1), &expr));
511 }
512
513 #[test]
514 fn match_null_union() {
515 let expr = parse_type_expr("STRING|NULL").unwrap();
516 assert!(value_matches_type(&Value::Null, &expr));
517 assert!(value_matches_type(&Value::String("x".into()), &expr));
518 assert!(!value_matches_type(&Value::Integer(1), &expr));
519 }
520
521 #[test]
522 fn match_typed_array() {
523 let expr = parse_type_expr("ARRAY<INT>").unwrap();
524 assert!(value_matches_type(
525 &Value::Array(vec![Value::Integer(1), Value::Integer(2)]),
526 &expr
527 ));
528 }
529
530 #[test]
531 fn match_typed_array_fail() {
532 let expr = parse_type_expr("ARRAY<INT>").unwrap();
533 assert!(!value_matches_type(
535 &Value::Array(vec![Value::Integer(1), Value::String("x".into())]),
536 &expr
537 ));
538 }
539
540 #[test]
541 fn match_int_coercion() {
542 let expr = parse_type_expr("FLOAT").unwrap();
544 assert!(value_matches_type(&Value::Integer(42), &expr));
545 }
546
547 #[test]
548 fn no_match() {
549 let expr = parse_type_expr("STRING").unwrap();
550 assert!(!value_matches_type(&Value::Integer(99), &expr));
551 }
552
553 #[test]
554 fn match_timestamp_coercions() {
555 let expr = parse_type_expr("TIMESTAMP").unwrap();
556 assert!(value_matches_type(
557 &Value::String("2024-01-01".into()),
558 &expr
559 ));
560 assert!(value_matches_type(&Value::Integer(1_700_000_000), &expr));
561 }
562
563 #[test]
564 fn parse_timestamptz() {
565 assert_eq!(
566 parse_type_expr("TIMESTAMPTZ").unwrap(),
567 TypeExpr::Simple(SimpleType::Timestamptz)
568 );
569 }
570
571 #[test]
572 fn match_timestamptz_coercions() {
573 let expr = parse_type_expr("TIMESTAMPTZ").unwrap();
574 let dt = nodedb_types::NdbDateTime::from_micros(1_700_000_000_000_000);
575 assert!(value_matches_type(&Value::DateTime(dt), &expr));
576 assert!(value_matches_type(
577 &Value::String("2024-01-01T00:00:00Z".into()),
578 &expr
579 ));
580 assert!(value_matches_type(&Value::Integer(1_700_000_000), &expr));
581 }
582
583 #[test]
584 fn match_null_expr() {
585 let expr = TypeExpr::Null;
586 assert!(value_matches_type(&Value::Null, &expr));
587 assert!(!value_matches_type(&Value::Integer(0), &expr));
588 }
589
590 #[test]
591 fn match_json_accepts_any() {
592 let expr = TypeExpr::Simple(SimpleType::Json);
593 assert!(value_matches_type(&Value::Null, &expr));
594 assert!(value_matches_type(&Value::Integer(1), &expr));
595 assert!(value_matches_type(&Value::String("x".into()), &expr));
596 assert!(value_matches_type(&Value::Bool(true), &expr));
597 }
598
599 #[test]
600 fn match_vector_type() {
601 let expr = parse_type_expr("VECTOR(128)").unwrap();
602 assert!(value_matches_type(
604 &Value::Array(vec![Value::Float(0.1)]),
605 &expr
606 ));
607 assert!(value_matches_type(&Value::Bytes(vec![0u8; 512]), &expr));
608 assert!(!value_matches_type(&Value::Integer(1), &expr));
609 }
610}