1use serde::{Deserialize, Serialize};
6
7use crate::value::Value;
8
9#[non_exhaustive]
16#[derive(
17 Debug,
18 Clone,
19 Copy,
20 PartialEq,
21 Eq,
22 Hash,
23 Serialize,
24 Deserialize,
25 zerompk::ToMessagePack,
26 zerompk::FromMessagePack,
27)]
28#[serde(tag = "type", content = "params")]
29pub enum ColumnType {
30 Int64,
31 Float64,
32 String,
33 Bool,
34 Bytes,
35 Timestamp,
37 Timestamptz,
39 SystemTimestamp,
44 Decimal {
49 precision: u8,
50 scale: u8,
51 },
52 Geometry,
53 Vector(u32),
55 SparseVector,
60 Uuid,
61 Json,
64 Ulid,
66 Duration,
68 Array,
70 Set,
72 Regex,
74 Range,
76 Record,
78}
79
80impl ColumnType {
81 pub fn fixed_size(&self) -> Option<usize> {
83 match self {
84 Self::Int64
85 | Self::Float64
86 | Self::Timestamp
87 | Self::Timestamptz
88 | Self::SystemTimestamp
89 | Self::Duration => Some(8),
90 Self::Bool => Some(1),
91 Self::Decimal { .. } | Self::Uuid | Self::Ulid => Some(16),
92 Self::Vector(dim) => Some(*dim as usize * 4),
93 Self::String
94 | Self::Bytes
95 | Self::Geometry
96 | Self::Json
97 | Self::SparseVector
98 | Self::Array
99 | Self::Set
100 | Self::Regex
101 | Self::Range
102 | Self::Record => None,
103 }
104 }
105
106 pub fn is_variable_length(&self) -> bool {
108 self.fixed_size().is_none()
109 }
110
111 pub fn to_pg_oid(&self) -> u32 {
128 match self {
129 Self::Bool => 16,
130 Self::Bytes => 17,
131 Self::Int64 => 20,
132 Self::Float64 => 701,
133 Self::String => 25,
134 Self::Timestamp | Self::SystemTimestamp => 1114,
135 Self::Timestamptz => 1184,
136 Self::Decimal { .. } => 1700,
137 Self::Uuid | Self::Ulid => 2950,
138 Self::Json => 3802,
139 Self::Duration => 1186,
140 Self::Geometry => 25,
142 Self::Vector(_) => 1021,
144 Self::Array
148 | Self::Set
149 | Self::Range
150 | Self::Record
151 | Self::Regex
152 | Self::SparseVector => 3802,
153 }
154 }
155
156 pub fn accepts(&self, value: &Value) -> bool {
162 matches!(
163 (self, value),
164 (Self::Int64, Value::Integer(_))
165 | (Self::Float64, Value::Float(_) | Value::Integer(_))
166 | (Self::String, Value::String(_))
167 | (Self::Bool, Value::Bool(_))
168 | (Self::Bytes, Value::Bytes(_))
169 | (
170 Self::Timestamp,
171 Value::NaiveDateTime(_) | Value::Integer(_) | Value::String(_)
172 )
173 | (
174 Self::Timestamptz,
175 Value::DateTime(_) | Value::Integer(_) | Value::String(_)
176 )
177 | (
178 Self::SystemTimestamp,
179 Value::DateTime(_) | Value::Integer(_)
180 )
181 | (
182 Self::Decimal { .. },
183 Value::Decimal(_) | Value::String(_) | Value::Float(_) | Value::Integer(_)
184 )
185 | (Self::Geometry, Value::Geometry(_) | Value::String(_))
186 | (Self::Vector(_), Value::Array(_) | Value::Bytes(_))
187 | (Self::Uuid, Value::Uuid(_) | Value::String(_))
188 | (Self::Ulid, Value::Ulid(_) | Value::String(_))
189 | (
190 Self::Duration,
191 Value::Duration(_) | Value::Integer(_) | Value::String(_)
192 )
193 | (Self::Array, Value::Array(_))
194 | (Self::Set, Value::Set(_) | Value::Array(_))
195 | (Self::Regex, Value::Regex(_) | Value::String(_))
196 | (Self::Range, Value::Range { .. })
197 | (Self::Record, Value::Record { .. } | Value::String(_))
198 | (Self::SparseVector, Value::String(_) | Value::Bytes(_))
199 | (Self::Json, _)
200 | (_, Value::Null)
201 )
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 fn nodedb_types_datetime_epoch() -> crate::datetime::NdbDateTime {
210 crate::datetime::NdbDateTime::from_micros(0)
211 }
212
213 #[test]
214 fn to_pg_oid_stable() {
215 assert_eq!(ColumnType::Bool.to_pg_oid(), 16);
216 assert_eq!(ColumnType::Bytes.to_pg_oid(), 17);
217 assert_eq!(ColumnType::Int64.to_pg_oid(), 20);
218 assert_eq!(ColumnType::String.to_pg_oid(), 25);
219 assert_eq!(ColumnType::Float64.to_pg_oid(), 701);
220 assert_eq!(ColumnType::Timestamp.to_pg_oid(), 1114);
221 assert_eq!(ColumnType::Timestamptz.to_pg_oid(), 1184);
222 assert_eq!(ColumnType::SystemTimestamp.to_pg_oid(), 1114);
223 assert_eq!(ColumnType::Duration.to_pg_oid(), 1186);
224 assert_eq!(
225 ColumnType::Decimal {
226 precision: 38,
227 scale: 10
228 }
229 .to_pg_oid(),
230 1700
231 );
232 assert_eq!(ColumnType::Uuid.to_pg_oid(), 2950);
233 assert_eq!(ColumnType::Ulid.to_pg_oid(), 2950);
234 assert_eq!(ColumnType::Json.to_pg_oid(), 3802);
235 assert_eq!(ColumnType::Geometry.to_pg_oid(), 25);
236 assert_eq!(ColumnType::Vector(768).to_pg_oid(), 1021);
237 assert_eq!(ColumnType::Array.to_pg_oid(), 3802);
238 assert_eq!(ColumnType::Set.to_pg_oid(), 3802);
239 assert_eq!(ColumnType::Range.to_pg_oid(), 3802);
240 assert_eq!(ColumnType::Record.to_pg_oid(), 3802);
241 assert_eq!(ColumnType::Regex.to_pg_oid(), 3802);
242 }
243
244 #[test]
245 fn parse_system_timestamp() {
246 assert_eq!(
247 "SYSTEM_TIMESTAMP".parse::<ColumnType>().unwrap(),
248 ColumnType::SystemTimestamp
249 );
250 assert_eq!(
251 "SystemTimestamp".parse::<ColumnType>().unwrap(),
252 ColumnType::SystemTimestamp
253 );
254 assert_eq!(ColumnType::SystemTimestamp.fixed_size(), Some(8));
255 assert!(!ColumnType::SystemTimestamp.is_variable_length());
256 assert_eq!(ColumnType::SystemTimestamp.to_string(), "SYSTEM_TIMESTAMP");
257 assert!(!ColumnType::SystemTimestamp.accepts(&Value::String("2024-01-01".into())));
258 assert!(ColumnType::SystemTimestamp.accepts(&Value::Integer(1_700_000_000)));
259 }
260
261 #[test]
262 fn parse_canonical() {
263 assert_eq!("BIGINT".parse::<ColumnType>().unwrap(), ColumnType::Int64);
264 assert_eq!(
265 "FLOAT64".parse::<ColumnType>().unwrap(),
266 ColumnType::Float64
267 );
268 assert_eq!("TEXT".parse::<ColumnType>().unwrap(), ColumnType::String);
269 assert_eq!("BOOL".parse::<ColumnType>().unwrap(), ColumnType::Bool);
270 assert_eq!(
271 "TIMESTAMP".parse::<ColumnType>().unwrap(),
272 ColumnType::Timestamp
273 );
274 assert_eq!(
275 "TIMESTAMPTZ".parse::<ColumnType>().unwrap(),
276 ColumnType::Timestamptz
277 );
278 assert_eq!(
279 "TIMESTAMP WITH TIME ZONE".parse::<ColumnType>().unwrap(),
280 ColumnType::Timestamptz
281 );
282 assert_eq!(
283 "GEOMETRY".parse::<ColumnType>().unwrap(),
284 ColumnType::Geometry
285 );
286 assert_eq!("UUID".parse::<ColumnType>().unwrap(), ColumnType::Uuid);
287 }
288
289 #[test]
290 fn parse_vector() {
291 assert_eq!(
292 "VECTOR(768)".parse::<ColumnType>().unwrap(),
293 ColumnType::Vector(768)
294 );
295 assert!("VECTOR(0)".parse::<ColumnType>().is_err());
296 }
297
298 #[test]
299 fn sparsevector_is_variable_length_string_type() {
300 let parsed = "SPARSEVECTOR".parse::<ColumnType>().unwrap();
302 assert_eq!(parsed, ColumnType::SparseVector);
303 assert_eq!(ColumnType::SparseVector.to_string(), "SPARSEVECTOR");
304 assert_eq!(
305 ColumnType::SparseVector
306 .to_string()
307 .parse::<ColumnType>()
308 .unwrap(),
309 ColumnType::SparseVector
310 );
311
312 assert_eq!(ColumnType::SparseVector.fixed_size(), None);
315 assert!(ColumnType::SparseVector.is_variable_length());
316 assert_eq!(ColumnType::SparseVector.to_pg_oid(), 3802);
317
318 assert!(ColumnType::SparseVector.accepts(&Value::String("{3:0.5}".into())));
320 assert!(ColumnType::SparseVector.accepts(&Value::Bytes(vec![1, 2, 3])));
321 assert!(ColumnType::SparseVector.accepts(&Value::Null));
322 assert!(!ColumnType::SparseVector.accepts(&Value::Integer(1)));
323 }
324
325 #[test]
326 fn display_roundtrip() {
327 for ct in [
328 ColumnType::Int64,
329 ColumnType::Float64,
330 ColumnType::String,
331 ColumnType::Timestamp,
332 ColumnType::Timestamptz,
333 ColumnType::Vector(768),
334 ColumnType::Decimal {
335 precision: 10,
336 scale: 2,
337 },
338 ColumnType::Decimal {
339 precision: 38,
340 scale: 10,
341 },
342 ] {
343 let s = ct.to_string();
344 let parsed: ColumnType = s.parse().unwrap();
345 assert_eq!(parsed, ct);
346 }
347 }
348
349 #[test]
350 fn decimal_parse_with_params() {
351 assert_eq!(
352 "NUMERIC(10,2)".parse::<ColumnType>().unwrap(),
353 ColumnType::Decimal {
354 precision: 10,
355 scale: 2
356 }
357 );
358 assert_eq!(
359 "DECIMAL(38,10)".parse::<ColumnType>().unwrap(),
360 ColumnType::Decimal {
361 precision: 38,
362 scale: 10
363 }
364 );
365 assert_eq!(
366 "NUMERIC".parse::<ColumnType>().unwrap(),
367 ColumnType::Decimal {
368 precision: 38,
369 scale: 10
370 }
371 );
372 assert_eq!(
373 "DECIMAL".parse::<ColumnType>().unwrap(),
374 ColumnType::Decimal {
375 precision: 38,
376 scale: 10
377 }
378 );
379 }
380
381 #[test]
382 fn decimal_parse_invalid() {
383 assert!("DECIMAL(5,6)".parse::<ColumnType>().is_err());
384 assert!("DECIMAL(0,0)".parse::<ColumnType>().is_err());
385 assert!("DECIMAL(39,0)".parse::<ColumnType>().is_err());
386 }
387
388 #[test]
389 fn decimal_fixed_size() {
390 assert_eq!(
391 ColumnType::Decimal {
392 precision: 10,
393 scale: 2
394 }
395 .fixed_size(),
396 Some(16)
397 );
398 }
399
400 #[test]
401 fn decimal_to_pg_oid_is_1700() {
402 assert_eq!(
403 ColumnType::Decimal {
404 precision: 10,
405 scale: 2
406 }
407 .to_pg_oid(),
408 1700
409 );
410 }
411
412 #[test]
413 fn accepts_native_values() {
414 assert!(ColumnType::Int64.accepts(&Value::Integer(42)));
415 assert!(ColumnType::Float64.accepts(&Value::Float(42.0)));
416 assert!(ColumnType::Float64.accepts(&Value::Integer(42)));
417 assert!(ColumnType::String.accepts(&Value::String("x".into())));
418 assert!(ColumnType::Bool.accepts(&Value::Bool(true)));
419 assert!(ColumnType::Bytes.accepts(&Value::Bytes(vec![1])));
420 assert!(
421 ColumnType::Uuid.accepts(&Value::Uuid("550e8400-e29b-41d4-a716-446655440000".into()))
422 );
423 assert!(
424 ColumnType::Decimal {
425 precision: 38,
426 scale: 10
427 }
428 .accepts(&Value::Decimal(rust_decimal::Decimal::ZERO))
429 );
430
431 let naive = Value::NaiveDateTime(nodedb_types_datetime_epoch());
432 let tz = Value::DateTime(nodedb_types_datetime_epoch());
433 assert!(ColumnType::Timestamp.accepts(&naive));
434 assert!(!ColumnType::Timestamp.accepts(&tz));
435 assert!(ColumnType::Timestamptz.accepts(&tz));
436 assert!(!ColumnType::Timestamptz.accepts(&naive));
437
438 assert!(ColumnType::Int64.accepts(&Value::Null));
439 assert!(!ColumnType::Int64.accepts(&Value::String("x".into())));
440 assert!(!ColumnType::Bool.accepts(&Value::Integer(1)));
441 }
442
443 #[test]
444 fn accepts_coercion_sources() {
445 assert!(ColumnType::Timestamp.accepts(&Value::String("2024-01-01".into())));
446 assert!(ColumnType::Timestamp.accepts(&Value::Integer(1_700_000_000)));
447 assert!(ColumnType::Timestamptz.accepts(&Value::String("2024-01-01T00:00:00Z".into())));
448 assert!(ColumnType::Timestamptz.accepts(&Value::Integer(1_700_000_000)));
449 assert!(ColumnType::Uuid.accepts(&Value::String(
450 "550e8400-e29b-41d4-a716-446655440000".into()
451 )));
452 assert!(
453 ColumnType::Decimal {
454 precision: 10,
455 scale: 2
456 }
457 .accepts(&Value::String("99.95".into()))
458 );
459 assert!(
460 ColumnType::Decimal {
461 precision: 10,
462 scale: 2
463 }
464 .accepts(&Value::Float(99.95))
465 );
466 assert!(ColumnType::Geometry.accepts(&Value::String("POINT(0 0)".into())));
467 }
468
469 #[test]
470 fn column_def_display() {
471 use super::super::column_def::ColumnDef;
472 let col = ColumnDef::required("id", ColumnType::Int64).with_primary_key();
473 assert_eq!(col.to_string(), "id BIGINT NOT NULL PRIMARY KEY");
474 }
475}