razeri_plugin_contract/data_type.rs
1// Copyright (C) 2026 Gabriel Shea
2// Licensed under either of the Apache License, Version 2.0 or the MIT
3// license, at your option.
4
5//! Generic data types for cross-database compatibility.
6//!
7//! This module defines the unified [`DataType`] enum and all component types
8//! (integer, decimal, float, boolean, string, binary, date/time, special, and
9//! collection types) that serve as the foundation for SQL rendering across
10//! all supported database platforms.
11//!
12//! # Design Philosophy
13//!
14//! - **Abstract over database-specific implementations** — `DataType` captures
15//! enough metadata to generate correct DDL for each target, but does not
16//! embed platform-specific syntax.
17//! - **Preserve precision metadata** — Decimal types carry precision and scale;
18//! string types carry length constraints; timestamp types carry fractional
19//! seconds precision.
20//! - **Every type renders somewhere** — a dialect answers for every variant,
21//! emulating a type it does not have (JSON stored as `NVARCHAR(MAX)` on SQL
22//! Server, a struct stored as `jsonb` on `PostgreSQL`) rather than refusing.
23//!
24//! # Type Categories
25//!
26//! | Category | Types | Example |
27//! |----------|-------|---------|
28//! | Integer | [`IntegerDataType`] | `INT`, `BIGINT`, `TINYINT` |
29//! | Decimal | [`DecimalDataType`] | `DECIMAL(19,4)`, `DECIMAL(5,2)` |
30//! | Float | [`FloatDataType`] | `REAL`, `DOUBLE` |
31//! | Boolean | [`BooleanDataType`] | `BOOLEAN`, `BIT` |
32//! | String | [`CharDataType`], [`VarCharDataType`], [`TextDataType`] | `VARCHAR(100)`, `TEXT` |
33//! | Binary | [`BinaryDataType`], [`VarBinaryDataType`] | `BYTEA`, `VARBINARY` |
34//! | Date/Time | [`DateDataType`], [`TimestampDataType`], [`TimestampTzDataType`] | `DATE`, `TIMESTAMP` |
35//! | Special | [`UuidDataType`], [`JsonDataType`] | `UUID`, `JSONB` |
36//! | Collection | [`ArrayDataType`], [`MapDataType`], [`StructDataType`] | `INTEGER[]`, `MAP<TEXT,INT>`, `STRUCT<a INT>` |
37//!
38//! # Usage
39//!
40//! Convenience constructors for common types:
41//!
42//! ```rust
43//! use razeri_plugin_contract::DataType;
44//!
45//! let id = DataType::bigint();
46//! let name = DataType::varchar(255);
47//! let price = DataType::decimal(19, 4);
48//! let created = DataType::timestamp();
49//!
50//! // JSON with binary storage (JSONB in PostgreSQL, VARIANT in Snowflake)
51//! let data = DataType::json();
52//! ```
53//!
54//! Each variant maps to platform-specific types as documented in the variant
55//! definitions. Dialect plugins resolve the final SQL type string during
56//! rendering; the rendering traits live in this crate's `renderer` module.
57
58// ============================================================================
59// Integer Types
60// ============================================================================
61
62/// Integer size variants matching common database integer types.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub enum IntegerSize {
65 /// 1 byte: -128 to 127 (signed) or 0 to 255 (unsigned)
66 /// Maps to: TINYINT (SQL Server, Snowflake, Databricks), SMALLINT (`PostgreSQL`), Int8/UInt8 (`ClickHouse`)
67 Tiny,
68 /// 2 bytes: -32,768 to 32,767 (signed)
69 /// Maps to: SMALLINT (all databases), Int16/UInt16 (`ClickHouse`)
70 Small,
71 /// 4 bytes: -2.1B to 2.1B (signed)
72 /// Maps to: INT/INTEGER (all databases), Int32/UInt32 (`ClickHouse`)
73 Standard,
74 /// 8 bytes: -9.2E18 to 9.2E18 (signed)
75 /// Maps to: BIGINT (all databases), Int64/UInt64 (`ClickHouse`)
76 Big,
77}
78
79/// Integer data type supporting signed/unsigned variants.
80///
81/// Most databases only support signed integers. `ClickHouse` is the exception,
82/// supporting unsigned variants via `UInt8`, `UInt16`, `UInt32`, `UInt64`.
83/// Other dialect plugins will map unsigned types to the next larger signed type
84/// when unsigned is not supported.
85///
86/// # Example
87///
88/// ```rust
89/// use razeri_plugin_contract::{DataType, data_type::{IntegerDataType, IntegerSize}};
90///
91/// // Signed 4-byte integer (INT/INTEGER on most platforms)
92/// let _id = DataType::int();
93///
94/// // Unsigned 8-byte integer (UInt64 in ClickHouse, BIGINT elsewhere)
95/// let big = IntegerDataType::unsigned(IntegerSize::Big);
96/// assert_eq!(big.byte_size(), 8);
97/// ```
98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub struct IntegerDataType {
100 /// Size of the integer (Tiny, Small, Standard, Big).
101 pub size: IntegerSize,
102
103 /// Whether the integer is unsigned.
104 ///
105 /// Only `ClickHouse` fully supports unsigned integers. Other dialect plugins
106 /// will map unsigned types to the next larger signed type when unsigned is
107 /// not supported by the target platform.
108 ///
109 /// # Example
110 ///
111 /// ```rust
112 /// use razeri_plugin_contract::data_type::{IntegerDataType, IntegerSize};
113 ///
114 /// let signed = IntegerDataType::new(IntegerSize::Standard);
115 /// assert!(!signed.unsigned);
116 ///
117 /// let unsigned = IntegerDataType::unsigned(IntegerSize::Big);
118 /// assert!(unsigned.unsigned);
119 /// ```
120 pub unsigned: bool,
121}
122
123impl IntegerDataType {
124 /// Creates a new signed integer with the given [`IntegerSize`].
125 ///
126 /// # Example
127 ///
128 /// ```rust
129 /// use razeri_plugin_contract::data_type::{IntegerDataType, IntegerSize};
130 ///
131 /// let small = IntegerDataType::new(IntegerSize::Small);
132 /// assert_eq!(small.byte_size(), 2);
133 /// ```
134 #[must_use]
135 pub fn new(size: IntegerSize) -> Self {
136 Self { size, unsigned: false }
137 }
138
139 /// Creates a new unsigned integer with the given [`IntegerSize`].
140 ///
141 /// # Example
142 ///
143 /// ```rust
144 /// use razeri_plugin_contract::data_type::{IntegerDataType, IntegerSize};
145 ///
146 /// let unsigned_big = IntegerDataType::unsigned(IntegerSize::Big);
147 /// assert!(unsigned_big.unsigned);
148 /// assert_eq!(unsigned_big.byte_size(), 8);
149 /// ```
150 #[must_use]
151 pub fn unsigned(size: IntegerSize) -> Self {
152 Self { size, unsigned: true }
153 }
154
155 /// Returns the byte size of this integer type.
156 ///
157 /// # Example
158 ///
159 /// ```rust
160 /// use razeri_plugin_contract::data_type::{IntegerDataType, IntegerSize};
161 ///
162 /// assert_eq!(IntegerDataType::new(IntegerSize::Tiny).byte_size(), 1);
163 /// assert_eq!(IntegerDataType::new(IntegerSize::Small).byte_size(), 2);
164 /// assert_eq!(IntegerDataType::new(IntegerSize::Standard).byte_size(), 4);
165 /// assert_eq!(IntegerDataType::new(IntegerSize::Big).byte_size(), 8);
166 /// ```
167 #[must_use]
168 pub fn byte_size(&self) -> usize {
169 match self.size {
170 IntegerSize::Tiny => 1,
171 IntegerSize::Small => 2,
172 IntegerSize::Standard => 4,
173 IntegerSize::Big => 8,
174 }
175 }
176}
177
178// ============================================================================
179// Decimal/Numeric Types (Exact Precision)
180// ============================================================================
181
182/// Exact numeric type with fixed precision and scale.
183///
184/// Maps to: `NUMERIC`/`DECIMAL` (`PostgreSQL`, SQL Server), `NUMBER` (Snowflake),
185/// `DECIMAL` (Databricks), `Decimal` (`ClickHouse`).
186///
187/// # Constraints
188///
189/// - `precision`: 1–38 (most databases), up to 76 (some platforms)
190/// - `scale`: 0 to `precision`
191/// - Panics if `scale > precision` or `precision > 38`
192///
193/// # Example
194///
195/// ```rust
196/// use razeri_plugin_contract::data_type::DecimalDataType;
197///
198/// // Money: DECIMAL(19, 4)
199/// let money = DecimalDataType::money();
200/// assert_eq!(money.precision, 19);
201/// assert_eq!(money.scale, 4);
202///
203/// // Percentage: DECIMAL(5, 2)
204/// let pct = DecimalDataType::percentage();
205/// assert_eq!(pct.precision, 5);
206/// assert_eq!(pct.scale, 2);
207///
208/// // Custom decimal
209/// let custom = DecimalDataType::new(10, 2);
210/// assert_eq!(custom.precision, 10);
211/// assert_eq!(custom.scale, 2);
212/// ```
213#[derive(Debug, Clone, PartialEq, Eq, Hash)]
214pub struct DecimalDataType {
215 /// Total number of digits (1–38 for most databases, up to 76 for some).
216 pub precision: u8,
217
218 /// Number of digits after the decimal point (0 to `precision`).
219 pub scale: u8,
220}
221
222impl DecimalDataType {
223 /// Creates a new decimal type with the given precision and scale.
224 ///
225 /// # Panics
226 ///
227 /// Panics if `precision` is 0 or exceeds 38, or if `scale > precision`.
228 ///
229 /// # Example
230 ///
231 /// ```rust
232 /// use razeri_plugin_contract::data_type::DecimalDataType;
233 ///
234 /// let dec = DecimalDataType::new(10, 2);
235 /// assert_eq!(dec.precision, 10);
236 /// assert_eq!(dec.scale, 2);
237 /// ```
238 ///
239 /// # Panics
240 ///
241 /// ```should_panic
242 /// use razeri_plugin_contract::data_type::DecimalDataType;
243 ///
244 /// // Precision must be 1-38
245 /// let _ = DecimalDataType::new(0, 0); // panics
246 ///
247 /// // Scale cannot exceed precision
248 /// let _ = DecimalDataType::new(5, 10); // panics
249 /// ```
250 #[must_use]
251 pub fn new(precision: u8, scale: u8) -> Self {
252 assert!(precision > 0 && precision <= 38, "Precision must be 1-38");
253 assert!(scale <= precision, "Scale cannot exceed precision");
254 Self { precision, scale }
255 }
256
257 /// Creates the common money type: `DECIMAL(19, 4)`.
258 ///
259 /// # Example
260 ///
261 /// ```rust
262 /// use razeri_plugin_contract::data_type::DecimalDataType;
263 ///
264 /// let money = DecimalDataType::money();
265 /// assert_eq!(money.precision, 19);
266 /// assert_eq!(money.scale, 4);
267 /// ```
268 #[must_use]
269 pub fn money() -> Self {
270 Self { precision: 19, scale: 4 }
271 }
272
273 /// Creates a percentage type with 2 decimal places: `DECIMAL(5, 2)`.
274 ///
275 /// # Example
276 ///
277 /// ```rust
278 /// use razeri_plugin_contract::data_type::DecimalDataType;
279 ///
280 /// let pct = DecimalDataType::percentage();
281 /// assert_eq!(pct.precision, 5);
282 /// assert_eq!(pct.scale, 2);
283 /// ```
284 #[must_use]
285 pub fn percentage() -> Self {
286 Self { precision: 5, scale: 2 }
287 }
288}
289
290// ============================================================================
291// Floating Point Types (Approximate)
292// ============================================================================
293
294/// Floating point precision variants.
295///
296/// # Example
297///
298/// ```rust
299/// use razeri_plugin_contract::data_type::FloatPrecision;
300///
301/// let single = FloatPrecision::Single;
302/// let double = FloatPrecision::Double;
303///
304/// assert_eq!(format!("{:?}", single), "Single");
305/// assert_eq!(format!("{:?}", double), "Double");
306/// ```
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
308pub enum FloatPrecision {
309 /// Single precision (4 bytes, ~7 decimal digits).
310 ///
311 /// Maps to: `REAL`/`FLOAT4` (`PostgreSQL`), `REAL`/`FLOAT(24)` (SQL Server),
312 /// `FLOAT` (Snowflake, Databricks), `Float32` (`ClickHouse`).
313 Single,
314 /// Double precision (8 bytes, ~15 decimal digits).
315 ///
316 /// Maps to: `DOUBLE PRECISION`/`FLOAT8` (`PostgreSQL`), `FLOAT` (SQL Server),
317 /// `DOUBLE` (Snowflake, Databricks), `Float64` (`ClickHouse`).
318 Double,
319}
320
321/// Approximate floating-point numeric type.
322///
323/// Maps to: `REAL`/`DOUBLE PRECISION` (`PostgreSQL`), `REAL`/`FLOAT` (SQL Server),
324/// `FLOAT`/`DOUBLE` (Snowflake, Databricks), `Float32`/`Float64` (`ClickHouse`).
325///
326/// # Example
327///
328/// ```
329/// use razeri_plugin_contract::DataType;
330///
331/// // Single precision (REAL)
332/// let real = DataType::float();
333///
334/// // Double precision (DOUBLE)
335/// let double = DataType::double();
336/// ```
337#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338pub struct FloatDataType {
339 /// Precision level (Single or Double).
340 pub precision: FloatPrecision,
341}
342
343impl FloatDataType {
344 /// Creates a single-precision (4-byte) float type.
345 ///
346 /// # Example
347 ///
348 /// ```rust
349 /// use razeri_plugin_contract::data_type::{FloatDataType, FloatPrecision};
350 ///
351 /// let single = FloatDataType::single();
352 /// assert_eq!(single.precision, FloatPrecision::Single);
353 /// ```
354 #[must_use]
355 pub fn single() -> Self {
356 Self { precision: FloatPrecision::Single }
357 }
358
359 /// Creates a double-precision (8-byte) float type.
360 ///
361 /// # Example
362 ///
363 /// ```rust
364 /// use razeri_plugin_contract::data_type::{FloatDataType, FloatPrecision};
365 ///
366 /// let double = FloatDataType::double();
367 /// assert_eq!(double.precision, FloatPrecision::Double);
368 /// ```
369 #[must_use]
370 pub fn double() -> Self {
371 Self { precision: FloatPrecision::Double }
372 }
373}
374
375// ============================================================================
376// Boolean Type
377// ============================================================================
378
379/// Boolean data type (true/false).
380///
381/// Maps to: `BOOLEAN` (`PostgreSQL`, Snowflake, Databricks, `ClickHouse`),
382/// `BIT` (SQL Server), `Bool`/`UInt8` (`ClickHouse`).
383///
384/// # Example
385///
386/// ```
387/// use razeri_plugin_contract::DataType;
388///
389/// let bool_type = DataType::boolean();
390/// ```
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
392pub struct BooleanDataType;
393
394// ============================================================================
395// String/Text Types
396// ============================================================================
397
398/// Character encoding for string types.
399///
400/// Determines whether strings use ASCII/Latin1 (1 byte per character) or
401/// UTF-8/Unicode (variable bytes per character). The default is `Unicode`.
402///
403/// # Example
404///
405/// ```rust
406/// use razeri_plugin_contract::data_type::StringEncoding;
407///
408/// let ascii = StringEncoding::Ascii;
409/// let unicode = StringEncoding::Unicode;
410///
411/// assert_eq!(ascii, StringEncoding::Ascii);
412/// assert_eq!(unicode, StringEncoding::Unicode);
413/// ```
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
415pub enum StringEncoding {
416 /// ASCII/Latin1 encoding (1 byte per character).
417 ///
418 /// Maps to: `CHAR`/`VARCHAR` (SQL Server), `CHAR`/`VARCHAR` (`PostgreSQL`).
419 Ascii,
420 /// UTF-8/Unicode encoding (variable bytes per character).
421 ///
422 /// Maps to: `NCHAR`/`NVARCHAR` (SQL Server), all strings in
423 /// `PostgreSQL`/Snowflake/`ClickHouse`.
424 #[default]
425 Unicode,
426}
427
428/// Fixed-length character string.
429///
430/// Maps to: `CHAR(n)` (`PostgreSQL`, SQL Server, Snowflake), `FixedString(n)` (`ClickHouse`).
431/// Note: Databricks does not have a fixed-length char type.
432///
433/// # Example
434///
435/// ```rust
436/// use razeri_plugin_contract::data_type::{CharDataType, StringEncoding};
437///
438/// // Default: Unicode encoding
439/// let char_type = CharDataType::new(50);
440/// assert_eq!(char_type.length, 50);
441///
442/// // Explicit ASCII encoding
443/// let ascii_char = CharDataType::with_encoding(10, StringEncoding::Ascii);
444/// ```
445#[derive(Debug, Clone, PartialEq, Eq, Hash)]
446pub struct CharDataType {
447 /// Fixed length in characters (1-8000 for SQL Server, 1-16777216 for Snowflake).
448 pub length: u32,
449
450 /// Character encoding (default: Unicode).
451 pub encoding: StringEncoding,
452}
453
454impl CharDataType {
455 /// Creates a fixed-length character string with Unicode encoding.
456 ///
457 /// # Example
458 ///
459 /// ```rust
460 /// use razeri_plugin_contract::data_type::CharDataType;
461 ///
462 /// let char = CharDataType::new(100);
463 /// assert_eq!(char.length, 100);
464 /// ```
465 #[must_use]
466 pub fn new(length: u32) -> Self {
467 Self { length, encoding: StringEncoding::Unicode }
468 }
469
470 /// Creates a fixed-length character string with the specified encoding.
471 ///
472 /// # Example
473 ///
474 /// ```rust
475 /// use razeri_plugin_contract::data_type::{CharDataType, StringEncoding};
476 ///
477 /// let ascii = CharDataType::with_encoding(20, StringEncoding::Ascii);
478 /// assert_eq!(ascii.encoding, StringEncoding::Ascii);
479 /// ```
480 #[must_use]
481 pub fn with_encoding(length: u32, encoding: StringEncoding) -> Self {
482 Self { length, encoding }
483 }
484}
485
486/// Variable-length character string with optional max length.
487///
488/// Maps to: `VARCHAR(n)` (`PostgreSQL`, SQL Server, Snowflake), `STRING` (Databricks, `ClickHouse`).
489///
490/// # Example
491///
492/// ```rust
493/// use razeri_plugin_contract::data_type::{VarCharDataType, StringEncoding};
494///
495/// // Fixed max length
496/// let varchar = VarCharDataType::new(255);
497/// assert_eq!(varchar.max_length, Some(255));
498///
499/// // Unlimited length (TEXT/VARCHAR(MAX)/STRING)
500/// let text = VarCharDataType::unlimited();
501/// assert_eq!(text.max_length, None);
502///
503/// // Explicit ASCII encoding
504/// let ascii = VarCharDataType::with_encoding(Some(100), StringEncoding::Ascii);
505/// ```
506#[derive(Debug, Clone, PartialEq, Eq, Hash)]
507pub struct VarCharDataType {
508 /// Maximum length in characters (None = unlimited/MAX).
509 ///
510 /// The longest an engine takes is its dialect's answer, in
511 /// [`DialectCapabilities::text_limits`](crate::DialectCapabilities::text_limits).
512 pub max_length: Option<u32>,
513
514 /// Character encoding (default: Unicode).
515 pub encoding: StringEncoding,
516}
517
518impl VarCharDataType {
519 /// Creates a VARCHAR with specified max length.
520 ///
521 /// # Example
522 ///
523 /// ```rust
524 /// use razeri_plugin_contract::data_type::VarCharDataType;
525 ///
526 /// let varchar = VarCharDataType::new(100);
527 /// assert_eq!(varchar.max_length, Some(100));
528 /// ```
529 #[must_use]
530 pub fn new(max_length: u32) -> Self {
531 Self { max_length: Some(max_length), encoding: StringEncoding::Unicode }
532 }
533
534 /// Creates an unlimited length text type (`VARCHAR(MAX)`, `TEXT`, `STRING`).
535 ///
536 /// # Example
537 ///
538 /// ```rust
539 /// use razeri_plugin_contract::data_type::VarCharDataType;
540 ///
541 /// let text = VarCharDataType::unlimited();
542 /// assert_eq!(text.max_length, None);
543 /// ```
544 #[must_use]
545 pub fn unlimited() -> Self {
546 Self { max_length: None, encoding: StringEncoding::Unicode }
547 }
548
549 /// Creates a VARCHAR with the specified length and encoding.
550 ///
551 /// # Example
552 ///
553 /// ```rust
554 /// use razeri_plugin_contract::data_type::{VarCharDataType, StringEncoding};
555 ///
556 /// let ascii = VarCharDataType::with_encoding(Some(50), StringEncoding::Ascii);
557 /// assert_eq!(ascii.max_length, Some(50));
558 /// assert_eq!(ascii.encoding, StringEncoding::Ascii);
559 /// ```
560 #[must_use]
561 pub fn with_encoding(max_length: Option<u32>, encoding: StringEncoding) -> Self {
562 Self { max_length, encoding }
563 }
564}
565
566/// Unbounded text type for large text storage.
567///
568/// Maps to: `TEXT` (`PostgreSQL`), `VARCHAR(MAX)` (SQL Server),
569/// `STRING` (Snowflake, Databricks, `ClickHouse`).
570///
571/// # Example
572///
573/// ```
574/// use razeri_plugin_contract::DataType;
575///
576/// let text = DataType::text();
577/// ```
578#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
579pub struct TextDataType;
580
581// ============================================================================
582// Binary Types
583// ============================================================================
584
585/// Fixed-length binary data.
586///
587/// Maps to: `BYTEA` (`PostgreSQL`), `BINARY(n)` (SQL Server, Snowflake),
588/// `FixedString(n)` (`ClickHouse`).
589///
590/// # Example
591///
592/// ```rust
593/// use razeri_plugin_contract::data_type::BinaryDataType;
594///
595/// let binary = BinaryDataType::new(256);
596/// assert_eq!(binary.length, 256);
597/// ```
598#[derive(Debug, Clone, PartialEq, Eq, Hash)]
599pub struct BinaryDataType {
600 /// Fixed length in bytes.
601 pub length: u32,
602}
603
604impl BinaryDataType {
605 /// Creates a fixed-length binary type with the given length in bytes.
606 ///
607 /// # Example
608 ///
609 /// ```rust
610 /// use razeri_plugin_contract::data_type::BinaryDataType;
611 ///
612 /// let binary = BinaryDataType::new(128);
613 /// assert_eq!(binary.length, 128);
614 /// ```
615 #[must_use]
616 pub fn new(length: u32) -> Self {
617 Self { length }
618 }
619}
620
621/// Variable-length binary data.
622///
623/// Maps to: `BYTEA` (`PostgreSQL`), `VARBINARY(n)` (SQL Server, Snowflake),
624/// `String` (`ClickHouse`), `BINARY` (Databricks).
625///
626/// # Example
627///
628/// ```rust
629/// use razeri_plugin_contract::data_type::VarBinaryDataType;
630///
631/// // Fixed max length
632/// let vb = VarBinaryDataType::new(1024);
633/// assert_eq!(vb.max_length, Some(1024));
634///
635/// // Unlimited length
636/// let blob = VarBinaryDataType::unlimited();
637/// assert_eq!(blob.max_length, None);
638/// ```
639#[derive(Debug, Clone, PartialEq, Eq, Hash)]
640pub struct VarBinaryDataType {
641 /// Maximum length in bytes (None = unlimited/MAX).
642 pub max_length: Option<u32>,
643}
644
645impl VarBinaryDataType {
646 /// Creates a variable-length binary type with the specified max length in bytes.
647 ///
648 /// # Example
649 ///
650 /// ```rust
651 /// use razeri_plugin_contract::data_type::VarBinaryDataType;
652 ///
653 /// let vb = VarBinaryDataType::new(2048);
654 /// assert_eq!(vb.max_length, Some(2048));
655 /// ```
656 #[must_use]
657 pub fn new(max_length: u32) -> Self {
658 Self { max_length: Some(max_length) }
659 }
660
661 /// Creates an unlimited-length binary type (BYTEA, VARBINARY(MAX), etc.).
662 ///
663 /// # Example
664 ///
665 /// ```rust
666 /// use razeri_plugin_contract::data_type::VarBinaryDataType;
667 ///
668 /// let blob = VarBinaryDataType::unlimited();
669 /// assert_eq!(blob.max_length, None);
670 /// ```
671 #[must_use]
672 pub fn unlimited() -> Self {
673 Self { max_length: None }
674 }
675}
676
677// ============================================================================
678// Date and Time Types
679// ============================================================================
680
681/// Date only (year, month, day) without time component.
682///
683/// Maps to: `DATE` (all databases), `Date`/`Date32` (`ClickHouse`).
684///
685/// # Example
686///
687/// ```
688/// use razeri_plugin_contract::DataType;
689///
690/// let date = DataType::date();
691/// ```
692#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
693pub struct DateDataType;
694
695/// Time only without date component.
696///
697/// Maps to: `TIME` (`PostgreSQL`, SQL Server, Snowflake), not directly supported in
698/// `ClickHouse`/Databricks.
699///
700/// # Precision
701///
702/// - `PostgreSQL`: 0-6
703/// - SQL Server: 0-7
704/// - Snowflake: 0-9
705/// - `ClickHouse`: 0-9
706///
707/// # Example
708///
709/// ```rust
710/// use razeri_plugin_contract::data_type::TimeDataType;
711///
712/// // Default precision: 6, no timezone
713/// let time = TimeDataType::default();
714/// assert_eq!(time.precision, 6);
715/// assert!(!time.with_timezone);
716///
717/// // Precision 3, with timezone
718/// let tz_time = TimeDataType::with_timezone(3);
719/// assert_eq!(tz_time.precision, 3);
720/// assert!(tz_time.with_timezone);
721/// ```
722#[derive(Debug, Clone, PartialEq, Eq, Hash)]
723pub struct TimeDataType {
724 /// Fractional seconds precision (0-7 for SQL Server, 0-6 for `PostgreSQL`, 0-9 for Snowflake).
725 pub precision: u8,
726
727 /// Whether to include timezone offset.
728 ///
729 /// Maps to: `TIME WITH TIME ZONE` (`PostgreSQL`), not supported in SQL Server.
730 pub with_timezone: bool,
731}
732
733impl TimeDataType {
734 /// Creates a new `TimeDataType` with the given fractional seconds precision.
735 ///
736 /// # Panics
737 ///
738 /// Panics if `precision` exceeds 9.
739 ///
740 /// # Example
741 ///
742 /// ```rust
743 /// use razeri_plugin_contract::data_type::TimeDataType;
744 ///
745 /// let time = TimeDataType::new(3);
746 /// assert_eq!(time.precision, 3);
747 /// assert!(!time.with_timezone);
748 /// ```
749 ///
750 /// ```rust,should_panic
751 /// use razeri_plugin_contract::data_type::TimeDataType;
752 ///
753 /// let _ = TimeDataType::new(10); // panics: precision must be 0-9
754 /// ```
755 #[must_use]
756 pub fn new(precision: u8) -> Self {
757 assert!(precision <= 9, "Time precision must be 0-9");
758 Self { precision, with_timezone: false }
759 }
760
761 /// Creates a `TimeDataType` with timezone and the given precision.
762 ///
763 /// # Panics
764 ///
765 /// Panics if `precision` exceeds 9.
766 ///
767 /// # Example
768 ///
769 /// ```rust
770 /// use razeri_plugin_contract::data_type::TimeDataType;
771 ///
772 /// let tz_time = TimeDataType::with_timezone(3);
773 /// assert_eq!(tz_time.precision, 3);
774 /// assert!(tz_time.with_timezone);
775 /// ```
776 #[must_use]
777 pub fn with_timezone(precision: u8) -> Self {
778 assert!(precision <= 9, "Time precision must be 0-9");
779 Self { precision, with_timezone: true }
780 }
781}
782
783impl Default for TimeDataType {
784 fn default() -> Self {
785 Self { precision: 6, with_timezone: false }
786 }
787}
788
789/// Timestamp (date and time) without timezone.
790///
791/// Maps to: `TIMESTAMP` (`PostgreSQL`), `DATETIME2` (SQL Server),
792/// `TIMESTAMP_NTZ` (Snowflake), `TIMESTAMP` (Databricks), `DateTime` (`ClickHouse`).
793///
794/// # Precision
795///
796/// - `PostgreSQL`: 0-6
797/// - SQL Server: 0-7
798/// - Snowflake: 0-9
799/// - `ClickHouse`: 0-9
800///
801/// # Example
802///
803/// ```rust
804/// use razeri_plugin_contract::data_type::{DataType, TimestampDataType};
805///
806/// // Default: precision 6
807/// let ts = TimestampDataType::default();
808/// assert_eq!(ts.precision, 6);
809///
810/// // Custom precision
811/// let ts3 = TimestampDataType::new(3);
812/// assert_eq!(ts3.precision, 3);
813///
814/// // Via DataType convenience
815/// let created = DataType::timestamp();
816/// ```
817#[derive(Debug, Clone, PartialEq, Eq, Hash)]
818pub struct TimestampDataType {
819 /// Fractional seconds precision (0-9).
820 ///
821 /// - `PostgreSQL`: 0-6
822 /// - SQL Server: 0-7
823 /// - Snowflake: 0-9
824 /// - `ClickHouse`: 0-9
825 pub precision: u8,
826}
827
828impl TimestampDataType {
829 /// Creates a new `TimestampDataType` with the given fractional seconds precision.
830 ///
831 /// # Panics
832 ///
833 /// Panics if `precision` exceeds 9.
834 ///
835 /// # Example
836 ///
837 /// ```rust
838 /// use razeri_plugin_contract::data_type::TimestampDataType;
839 ///
840 /// let ts = TimestampDataType::new(3);
841 /// assert_eq!(ts.precision, 3);
842 /// ```
843 ///
844 /// ```should_panic
845 /// use razeri_plugin_contract::data_type::TimestampDataType;
846 ///
847 /// let _ = TimestampDataType::new(10); // panics: precision must be 0-9
848 /// ```
849 #[must_use]
850 pub fn new(precision: u8) -> Self {
851 assert!(precision <= 9, "Timestamp precision must be 0-9");
852 Self { precision }
853 }
854}
855
856impl Default for TimestampDataType {
857 fn default() -> Self {
858 Self { precision: 6 }
859 }
860}
861
862/// Timestamp with timezone awareness.
863///
864/// Maps to: `TIMESTAMPTZ` (`PostgreSQL`), `DATETIMEOFFSET` (SQL Server),
865/// `TIMESTAMP_TZ`/`TIMESTAMP_LTZ` (Snowflake), `TIMESTAMP` (Databricks),
866/// `DateTime64` (`ClickHouse`).
867///
868/// # Example
869///
870/// ```rust
871/// use razeri_plugin_contract::DataType;
872/// use razeri_plugin_contract::data_type::TimestampTzDataType;
873///
874/// // Default: precision 6
875/// let ts = TimestampTzDataType::default();
876/// assert_eq!(ts.precision, 6);
877///
878/// // Via DataType convenience
879/// let created_tz = DataType::timestamp_tz();
880/// ```
881#[derive(Debug, Clone, PartialEq, Eq, Hash)]
882pub struct TimestampTzDataType {
883 /// Fractional seconds precision (0-9).
884 pub precision: u8,
885}
886
887impl TimestampTzDataType {
888 /// Creates a new `TimestampTzDataType` with the given fractional seconds precision.
889 ///
890 /// # Panics
891 ///
892 /// Panics if `precision` exceeds 9.
893 ///
894 /// # Example
895 ///
896 /// ```rust
897 /// use razeri_plugin_contract::data_type::TimestampTzDataType;
898 ///
899 /// let ts = TimestampTzDataType::new(3);
900 /// assert_eq!(ts.precision, 3);
901 /// ```
902 ///
903 /// ```should_panic
904 /// use razeri_plugin_contract::data_type::TimestampTzDataType;
905 ///
906 /// let _ = TimestampTzDataType::new(10); // panics: precision must be 0-9
907 /// ```
908 #[must_use]
909 pub fn new(precision: u8) -> Self {
910 assert!(precision <= 9, "Timestamp precision must be 0-9");
911 Self { precision }
912 }
913}
914
915impl Default for TimestampTzDataType {
916 fn default() -> Self {
917 Self { precision: 6 }
918 }
919}
920
921/// Combined date and time for legacy compatibility.
922///
923/// Maps to: `DATETIME` (SQL Server legacy), `TIMESTAMP` (`PostgreSQL`).
924/// Prefer [`TimestampDataType`] for new designs.
925///
926/// # Example
927///
928/// ```rust
929/// use razeri_plugin_contract::data_type::DateTimeDataType;
930///
931/// let dt = DateTimeDataType::new(3, true);
932/// assert_eq!(dt.precision, 3);
933/// assert!(dt.with_timezone);
934/// ```
935#[derive(Debug, Clone, PartialEq, Eq, Hash)]
936pub struct DateTimeDataType {
937 /// Fractional seconds precision (0-9).
938 pub precision: u8,
939
940 /// Whether to include timezone offset.
941 pub with_timezone: bool,
942}
943
944impl DateTimeDataType {
945 /// Creates a new `DateTimeDataType` with the given precision and timezone flag.
946 ///
947 /// # Example
948 ///
949 /// ```rust
950 /// use razeri_plugin_contract::data_type::DateTimeDataType;
951 ///
952 /// let dt = DateTimeDataType::new(6, false);
953 /// assert_eq!(dt.precision, 6);
954 /// assert!(!dt.with_timezone);
955 /// ```
956 #[must_use]
957 pub fn new(precision: u8, with_timezone: bool) -> Self {
958 Self { precision, with_timezone }
959 }
960}
961
962impl Default for DateTimeDataType {
963 fn default() -> Self {
964 Self { precision: 6, with_timezone: false }
965 }
966}
967
968/// Interval/duration type for time spans.
969///
970/// Maps to: `INTERVAL` (`PostgreSQL`, Snowflake, Databricks), not directly supported in
971/// SQL Server/`ClickHouse`.
972///
973/// # Example
974///
975/// ```rust
976/// use razeri_plugin_contract::data_type::IntervalDataType;
977///
978/// let ym = IntervalDataType::YearMonth;
979/// let dt = IntervalDataType::DayTime;
980///
981/// assert_eq!(format!("{:?}", ym), "YearMonth");
982/// assert_eq!(format!("{:?}", dt), "DayTime");
983/// ```
984#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
985pub enum IntervalDataType {
986 /// Year to month intervals.
987 YearMonth,
988 /// Day to second intervals (with optional fractional seconds precision).
989 DayTime,
990}
991
992// ============================================================================
993// Special Types
994// ============================================================================
995
996/// Universally Unique Identifier (UUID/GUID).
997///
998/// Maps to: `UUID` (`PostgreSQL`), `UNIQUEIDENTIFIER` (SQL Server),
999/// `STRING` (Snowflake, Databricks), `UUID` (`ClickHouse`).
1000///
1001/// # Example
1002///
1003/// ```
1004/// use razeri_plugin_contract::DataType;
1005///
1006/// let uuid = DataType::uuid();
1007/// ```
1008#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1009pub struct UuidDataType;
1010
1011/// JSON storage format preference.
1012///
1013/// Determines whether JSON data is stored as text (preserving whitespace and key order)
1014/// or in a binary-optimized format (normalized, faster queries, indexable).
1015///
1016/// # Example
1017///
1018/// ```rust
1019/// use razeri_plugin_contract::data_type::JsonStorageFormat;
1020///
1021/// let text = JsonStorageFormat::Text;
1022/// let binary = JsonStorageFormat::Binary;
1023///
1024/// assert_eq!(format!("{:?}", text), "Text");
1025/// assert_eq!(format!("{:?}", binary), "Binary");
1026/// assert!(!text.is_binary());
1027/// assert!(binary.is_binary());
1028/// ```
1029#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1030pub enum JsonStorageFormat {
1031 /// Text-based JSON storage (preserves whitespace, key order).
1032 ///
1033 /// Maps to: `JSON` (`PostgreSQL`), `NVARCHAR(MAX)` (SQL Server),
1034 /// `STRING` (Databricks).
1035 Text,
1036 /// Binary-optimized JSON storage (normalized, faster queries, indexable).
1037 ///
1038 /// Maps to: `JSONB` (`PostgreSQL`), `VARIANT` (Snowflake), `JSON` (`ClickHouse`).
1039 ///
1040 /// This is the recommended default for most use cases.
1041 #[default]
1042 Binary,
1043}
1044
1045impl JsonStorageFormat {
1046 /// Returns `true` if this is the binary storage format.
1047 ///
1048 /// # Example
1049 ///
1050 /// ```rust
1051 /// use razeri_plugin_contract::data_type::JsonStorageFormat;
1052 ///
1053 /// assert!(!JsonStorageFormat::Text.is_binary());
1054 /// assert!(JsonStorageFormat::Binary.is_binary());
1055 /// ```
1056 #[must_use]
1057 pub const fn is_binary(&self) -> bool {
1058 matches!(self, Self::Binary)
1059 }
1060}
1061
1062/// JSON/Semi-structured data type.
1063///
1064/// Database mapping details:
1065///
1066/// | Platform | Type | Notes |
1067/// |----------|------|-------|
1068/// | **`PostgreSQL`** | `JSON` or `JSONB` | JSONB supports GIN indexing and is faster for queries |
1069/// | **SQL Server** | `NVARCHAR(MAX)` with JSON functions (2016+), or native JSON (2025+) | |
1070/// | **Snowflake** | `VARIANT` | Native semi-structured type, supports nested data and arrays |
1071/// | **Databricks** | `STRING` with JSON functions, or `VARIANT` | |
1072/// | **`ClickHouse`** | `JSON` (experimental/production), or `String` with JSON functions | |
1073/// | **`CitusDB`** | Same as `PostgreSQL` (JSONB recommended) | |
1074///
1075/// # Storage Formats
1076///
1077/// - **Binary** (default): Normalized storage, faster queries, indexable.
1078/// Maps to `JSONB` in `PostgreSQL`, `VARIANT` in Snowflake.
1079/// - **Text**: Preserves whitespace, key order, and duplicate keys.
1080/// Maps to `JSON` in `PostgreSQL`, `NVARCHAR(MAX)` in SQL Server.
1081///
1082/// # Example
1083///
1084/// ```rust
1085/// use razeri_plugin_contract::DataType;
1086/// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1087///
1088/// // Binary format (recommended) - uses JSONB in PostgreSQL, VARIANT in Snowflake
1089/// let json_binary = JsonDataType::default();
1090/// assert_eq!(json_binary.storage_format, JsonStorageFormat::Binary);
1091///
1092/// // Text format - preserves original formatting
1093/// let json_text = JsonDataType::text();
1094/// assert_eq!(json_text.storage_format, JsonStorageFormat::Text);
1095///
1096/// // Via DataType convenience
1097/// let data = DataType::json();
1098/// ```
1099#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
1100pub struct JsonDataType {
1101 /// Storage format preference (binary is recommended for most use cases).
1102 pub storage_format: JsonStorageFormat,
1103
1104 /// Whether to validate JSON on insert (if supported by the database).
1105 ///
1106 /// `PostgreSQL` always validates, some other databases may not.
1107 pub validate_on_insert: bool,
1108}
1109
1110impl JsonDataType {
1111 /// Creates a new JSON type with binary storage format (JSONB in `PostgreSQL`).
1112 ///
1113 /// This is the recommended format for most use cases as it enables:
1114 /// - Faster query performance
1115 /// - GIN indexing support (`PostgreSQL`)
1116 /// - Normalized storage (no duplicate keys, sorted keys)
1117 ///
1118 /// # Example
1119 ///
1120 /// ```rust
1121 /// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1122 ///
1123 /// let json = JsonDataType::new();
1124 /// assert_eq!(json.storage_format, JsonStorageFormat::Binary);
1125 /// assert!(!json.validate_on_insert);
1126 /// ```
1127 #[must_use]
1128 pub fn new() -> Self {
1129 Self::default()
1130 }
1131
1132 /// Creates a JSON type with text storage format.
1133 ///
1134 /// Use this when you need to preserve:
1135 /// - Original whitespace formatting
1136 /// - Original key order
1137 /// - Duplicate keys (not recommended but sometimes required)
1138 ///
1139 /// # Example
1140 ///
1141 /// ```rust
1142 /// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1143 ///
1144 /// let json = JsonDataType::text();
1145 /// assert_eq!(json.storage_format, JsonStorageFormat::Text);
1146 /// assert!(json.validate_on_insert);
1147 /// ```
1148 #[must_use]
1149 pub fn text() -> Self {
1150 Self { storage_format: JsonStorageFormat::Text, validate_on_insert: true }
1151 }
1152
1153 /// Creates a JSON type with binary storage format.
1154 ///
1155 /// # Example
1156 ///
1157 /// ```rust
1158 /// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1159 ///
1160 /// let json = JsonDataType::binary();
1161 /// assert_eq!(json.storage_format, JsonStorageFormat::Binary);
1162 /// assert!(json.validate_on_insert);
1163 /// ```
1164 #[must_use]
1165 pub fn binary() -> Self {
1166 Self { storage_format: JsonStorageFormat::Binary, validate_on_insert: true }
1167 }
1168
1169 /// Sets whether to validate JSON on insert.
1170 ///
1171 /// # Example
1172 ///
1173 /// ```rust
1174 /// use razeri_plugin_contract::data_type::JsonDataType;
1175 ///
1176 /// let json = JsonDataType::new().with_validation(false);
1177 /// assert!(!json.validate_on_insert);
1178 /// ```
1179 #[must_use]
1180 pub fn with_validation(mut self, validate: bool) -> Self {
1181 self.validate_on_insert = validate;
1182 self
1183 }
1184}
1185
1186// ============================================================================
1187// Complex/Collection Types
1188// ============================================================================
1189
1190/// Array type containing elements of another type.
1191///
1192/// Supported by: `PostgreSQL`, `ClickHouse`, Snowflake (ARRAY), Databricks (ARRAY).
1193/// Not directly supported by SQL Server.
1194///
1195/// # Example
1196///
1197/// ```rust
1198/// use razeri_plugin_contract::{DataType, data_type::ArrayDataType};
1199///
1200/// let int_array = ArrayDataType::new(DataType::int());
1201/// assert_eq!(*int_array.element_type, DataType::int());
1202///
1203/// let _str_array = ArrayDataType::new(DataType::varchar(100));
1204/// ```
1205#[derive(Debug, Clone, PartialEq, Eq)]
1206pub struct ArrayDataType {
1207 /// The data type of array elements.
1208 pub element_type: Box<DataType>,
1209}
1210
1211impl ArrayDataType {
1212 /// Creates a new array type with the given element type.
1213 ///
1214 /// # Example
1215 ///
1216 /// ```rust
1217 /// use razeri_plugin_contract::{DataType, data_type::ArrayDataType};
1218 ///
1219 /// let arr = ArrayDataType::new(DataType::bigint());
1220 /// assert_eq!(*arr.element_type, DataType::bigint());
1221 /// ```
1222 #[must_use]
1223 pub fn new(element_type: DataType) -> Self {
1224 Self { element_type: Box::new(element_type) }
1225 }
1226}
1227
1228/// Map/Dictionary type with key-value pairs.
1229///
1230/// Supported by: `ClickHouse`, Snowflake (OBJECT), Databricks (MAP).
1231/// `PostgreSQL` uses JSONB, SQL Server uses JSON string.
1232///
1233/// # Example
1234///
1235/// ```rust
1236/// use razeri_plugin_contract::DataType;
1237/// use razeri_plugin_contract::data_type::MapDataType;
1238///
1239/// let map = MapDataType::new(DataType::varchar(50), DataType::int());
1240/// assert_eq!(*map.key_type, DataType::varchar(50));
1241/// assert_eq!(*map.value_type, DataType::int());
1242/// ```
1243#[derive(Debug, Clone, PartialEq, Eq)]
1244pub struct MapDataType {
1245 /// The data type of map keys.
1246 pub key_type: Box<DataType>,
1247
1248 /// The data type of map values.
1249 pub value_type: Box<DataType>,
1250}
1251
1252impl MapDataType {
1253 /// Creates a new map type with the given key and value types.
1254 ///
1255 /// # Example
1256 ///
1257 /// ```rust
1258 /// use razeri_plugin_contract::DataType;
1259 /// use razeri_plugin_contract::data_type::MapDataType;
1260 ///
1261 /// let map = MapDataType::new(DataType::varchar(50), DataType::int());
1262 /// assert_eq!(*map.key_type, DataType::varchar(50));
1263 /// assert_eq!(*map.value_type, DataType::int());
1264 /// ```
1265 #[must_use]
1266 pub fn new(key_type: DataType, value_type: DataType) -> Self {
1267 Self { key_type: Box::new(key_type), value_type: Box::new(value_type) }
1268 }
1269}
1270
1271/// One named field inside a [`StructDataType`], in the order it was declared.
1272///
1273/// A struct's fields are named on every engine that has a record type, and
1274/// positional on some, so both the name and the position are part of the type.
1275///
1276/// # Example
1277///
1278/// ```rust
1279/// use razeri_plugin_contract::{DataType, data_type::StructField};
1280///
1281/// let zip = StructField::new("zip", DataType::varchar(10));
1282/// assert_eq!(zip.name, "zip");
1283/// assert_eq!(zip.data_type, DataType::varchar(10));
1284/// ```
1285#[derive(Debug, Clone, PartialEq, Eq)]
1286pub struct StructField {
1287 /// The field's name, as the design declared it.
1288 pub name: String,
1289
1290 /// The field's type. May itself be a collection or another struct.
1291 pub data_type: DataType,
1292}
1293
1294impl StructField {
1295 /// Creates a named field of the given type.
1296 ///
1297 /// # Example
1298 ///
1299 /// ```rust
1300 /// use razeri_plugin_contract::{DataType, data_type::StructField};
1301 ///
1302 /// let field = StructField::new("category", DataType::varchar(100));
1303 /// assert_eq!(field.data_type, DataType::varchar(100));
1304 /// ```
1305 pub fn new(name: impl Into<String>, data_type: DataType) -> Self {
1306 Self { name: name.into(), data_type }
1307 }
1308}
1309
1310/// Struct/record type: an ordered list of named fields.
1311///
1312/// Supported natively by `BigQuery` (`STRUCT`), Databricks (`STRUCT`),
1313/// `ClickHouse` (named `Tuple`) and `DuckDB` (`STRUCT`). Engines without a
1314/// record type render their JSON type instead; the field names and types are
1315/// still part of the declared type, they are simply not restated in the DDL.
1316///
1317/// Field order is significant: it is the order the design declared, it is the
1318/// order every engine with a record type writes, and two structs holding the
1319/// same fields in a different order are different types.
1320///
1321/// # Example
1322///
1323/// ```rust
1324/// use razeri_plugin_contract::{DataType, data_type::{StructDataType, StructField}};
1325///
1326/// let address = StructDataType::new(vec![
1327/// StructField::new("street", DataType::varchar(200)),
1328/// StructField::new("zip", DataType::varchar(10)),
1329/// ]);
1330/// assert_eq!(address.fields.len(), 2);
1331/// assert_eq!(address.fields[0].name, "street");
1332/// ```
1333#[derive(Debug, Clone, PartialEq, Eq)]
1334pub struct StructDataType {
1335 /// The struct's fields, in declaration order.
1336 pub fields: Vec<StructField>,
1337}
1338
1339impl StructDataType {
1340 /// Creates a struct type from an ordered list of named fields.
1341 ///
1342 /// # Example
1343 ///
1344 /// ```rust
1345 /// use razeri_plugin_contract::{DataType, data_type::{StructDataType, StructField}};
1346 ///
1347 /// let point = StructDataType::new(vec![
1348 /// StructField::new("x", DataType::int()),
1349 /// StructField::new("y", DataType::int()),
1350 /// ]);
1351 /// assert_eq!(point.fields[1].name, "y");
1352 /// ```
1353 #[must_use]
1354 pub fn new(fields: Vec<StructField>) -> Self {
1355 Self { fields }
1356 }
1357}
1358
1359// ============================================================================
1360// Unified Data Type Enum
1361// ============================================================================
1362
1363/// Unified data type enumeration covering all supported types across databases.
1364///
1365/// This is the **primary type system** for Razeri. Every field, column, and
1366/// parameter in a design document is typed using `DataType`. The enum captures
1367/// enough metadata to generate correct DDL for every target while remaining
1368/// abstract over platform-specific implementation details.
1369///
1370/// # Type Categories
1371///
1372/// | Category | Variants | Example |
1373/// |----------|----------|---------|
1374/// | Numeric | `Integer`, `Decimal`, `Float` | `INT`, `DECIMAL(19,4)`, `DOUBLE` |
1375/// | Boolean | `Boolean` | `BOOLEAN`, `BIT` |
1376/// | String | `Char`, `VarChar`, `Text` | `VARCHAR(100)`, `TEXT` |
1377/// | Binary | `Binary`, `VarBinary` | `BYTEA`, `VARBINARY` |
1378/// | Date/Time | `Date`, `Time`, `Timestamp`, `TimestampTz`, `DateTime`, `Interval` | `DATE`, `TIMESTAMP` |
1379/// | Special | `Uuid`, `Json` | `UUID`, `JSONB` |
1380/// | Collection | `Array`, `Map`, `Struct` | `INTEGER[]`, `MAP<TEXT,INT>`, `STRUCT<a INT>` |
1381///
1382/// # Design Principles
1383///
1384/// 1. **Abstract over database-specific implementations** — `DataType` captures
1385/// enough metadata to generate correct DDL for each target, but does not
1386/// embed platform-specific syntax.
1387/// 2. **Preserve precision metadata** — Decimal types carry precision and scale;
1388/// string types carry length constraints; timestamp types carry fractional
1389/// seconds precision.
1390/// 3. **Every type renders somewhere** — a dialect answers for every variant,
1391/// emulating a type it does not have (JSON stored as `NVARCHAR(MAX)` on SQL
1392/// Server, a struct stored as `jsonb` on `PostgreSQL`) rather than refusing.
1393///
1394/// # Usage
1395///
1396/// ```
1397/// use razeri_plugin_contract::DataType;
1398///
1399/// // Convenience constructors for common types
1400/// let id = DataType::bigint();
1401/// let name = DataType::varchar(255);
1402/// let price = DataType::decimal(19, 4);
1403/// let created = DataType::timestamp();
1404///
1405/// // JSON with binary storage (JSONB in PostgreSQL, VARIANT in Snowflake)
1406/// let data = DataType::json();
1407/// ```
1408///
1409/// # Cross-Database Mapping
1410///
1411/// Each variant maps to platform-specific types as documented in the variant
1412/// definitions. Dialect plugins resolve the final SQL type string during
1413/// rendering; the rendering traits live in this crate's `renderer` module.
1414///
1415/// # The enum is `#[non_exhaustive]`
1416///
1417/// New types are added over time, so a `match` outside this crate needs a
1418/// fallback arm. A dialect's fallback should render the closest type the engine
1419/// does have — its JSON or text type — rather than panicking: a type the
1420/// dialect has not been taught about is still a column someone declared.
1421#[derive(Debug, Clone, PartialEq, Eq)]
1422#[non_exhaustive]
1423pub enum DataType {
1424 // Numeric types
1425 /// Signed/unsigned integer (1, 2, 4, or 8 bytes).
1426 Integer(IntegerDataType),
1427
1428 /// Exact numeric with fixed precision and scale.
1429 Decimal(DecimalDataType),
1430
1431 /// Approximate floating-point (4 or 8 bytes).
1432 Float(FloatDataType),
1433
1434 /// Boolean (true/false).
1435 Boolean(BooleanDataType),
1436
1437 // String types
1438 /// Fixed-length character string.
1439 Char(CharDataType),
1440
1441 /// Variable-length character string.
1442 VarChar(VarCharDataType),
1443
1444 /// Unbounded text type.
1445 Text(TextDataType),
1446
1447 // Binary types
1448 /// Fixed-length binary data.
1449 Binary(BinaryDataType),
1450
1451 /// Variable-length binary data.
1452 VarBinary(VarBinaryDataType),
1453
1454 // Date/Time types
1455 /// Date only (year, month, day).
1456 Date(DateDataType),
1457
1458 /// Time only (hour, minute, second, optional timezone).
1459 Time(TimeDataType),
1460
1461 /// Timestamp without timezone.
1462 Timestamp(TimestampDataType),
1463
1464 /// Timestamp with timezone.
1465 TimestampTz(TimestampTzDataType),
1466
1467 /// Combined date and time (legacy).
1468 DateTime(DateTimeDataType),
1469
1470 /// Interval/duration.
1471 Interval(IntervalDataType),
1472
1473 // Special types
1474 /// UUID/GUID.
1475 Uuid(UuidDataType),
1476
1477 /// JSON/semi-structured data.
1478 Json(JsonDataType),
1479
1480 // Complex types
1481 /// Array of elements.
1482 Array(ArrayDataType),
1483
1484 /// Map/dictionary with key-value pairs.
1485 Map(MapDataType),
1486
1487 /// Record with an ordered list of named fields.
1488 Struct(StructDataType),
1489}
1490
1491impl DataType {
1492 /// Whether this type holds more than one value inside one column — an
1493 /// array, a map, or a record.
1494 ///
1495 /// Nested columns are answered differently from scalars in several places:
1496 /// they cannot be part of a key, nothing aggregates them, and an engine
1497 /// with no record type of its own stores them as JSON or text. This is the
1498 /// one question all of those ask, so they ask it the same way.
1499 ///
1500 /// # Example
1501 ///
1502 /// ```
1503 /// use razeri_plugin_contract::{DataType, data_type::ArrayDataType};
1504 ///
1505 /// assert!(DataType::Array(ArrayDataType::new(DataType::int())).is_nested());
1506 /// assert!(!DataType::bigint().is_nested());
1507 /// ```
1508 #[must_use]
1509 pub const fn is_nested(&self) -> bool {
1510 matches!(self, DataType::Array(_) | DataType::Map(_) | DataType::Struct(_))
1511 }
1512
1513 // ==========================================================================
1514 // Numeric Type Constructors
1515 // ==========================================================================
1516
1517 /// Creates a 1-byte signed integer (`TINYINT` / `SMALLINT` / `Int8`).
1518 ///
1519 /// # Example
1520 ///
1521 /// ```
1522 /// use razeri_plugin_contract::DataType;
1523 ///
1524 /// let ty = DataType::tinyint();
1525 /// assert!(matches!(ty, DataType::Integer(_)));
1526 /// ```
1527 #[must_use]
1528 pub fn tinyint() -> Self {
1529 DataType::Integer(IntegerDataType::new(IntegerSize::Tiny))
1530 }
1531
1532 /// Creates a 2-byte signed integer (`SMALLINT` / `Int16`).
1533 ///
1534 /// # Example
1535 ///
1536 /// ```
1537 /// use razeri_plugin_contract::DataType;
1538 ///
1539 /// let ty = DataType::smallint();
1540 /// assert!(matches!(ty, DataType::Integer(_)));
1541 /// ```
1542 #[must_use]
1543 pub fn smallint() -> Self {
1544 DataType::Integer(IntegerDataType::new(IntegerSize::Small))
1545 }
1546
1547 /// Creates a 4-byte signed integer (`INT` / `INTEGER` / `Int32`).
1548 ///
1549 /// # Example
1550 ///
1551 /// ```
1552 /// use razeri_plugin_contract::DataType;
1553 ///
1554 /// let id = DataType::int();
1555 /// assert!(matches!(id, DataType::Integer(_)));
1556 /// ```
1557 #[must_use]
1558 pub fn int() -> Self {
1559 DataType::Integer(IntegerDataType::new(IntegerSize::Standard))
1560 }
1561
1562 /// Creates an 8-byte signed integer (`BIGINT` / `Int64`).
1563 ///
1564 /// # Example
1565 ///
1566 /// ```
1567 /// use razeri_plugin_contract::DataType;
1568 ///
1569 /// let id = DataType::bigint();
1570 /// assert!(matches!(id, DataType::Integer(_)));
1571 /// ```
1572 #[must_use]
1573 pub fn bigint() -> Self {
1574 DataType::Integer(IntegerDataType::new(IntegerSize::Big))
1575 }
1576
1577 /// Creates a `DECIMAL(precision, scale)` type.
1578 ///
1579 /// # Panics
1580 ///
1581 /// Panics if `precision` is 0 or exceeds 38, or if `scale > precision`.
1582 ///
1583 /// # Example
1584 ///
1585 /// ```
1586 /// use razeri_plugin_contract::DataType;
1587 ///
1588 /// let price = DataType::decimal(19, 4);
1589 /// assert!(matches!(price, DataType::Decimal(_)));
1590 /// ```
1591 ///
1592 /// ```should_panic
1593 /// use razeri_plugin_contract::DataType;
1594 ///
1595 /// let _ = DataType::decimal(0, 0); // panics: precision must be 1-38
1596 /// ```
1597 #[must_use]
1598 pub fn decimal(precision: u8, scale: u8) -> Self {
1599 DataType::Decimal(DecimalDataType::new(precision, scale))
1600 }
1601
1602 /// Creates a single-precision float (`REAL` / `FLOAT4`).
1603 ///
1604 /// # Example
1605 ///
1606 /// ```
1607 /// use razeri_plugin_contract::DataType;
1608 ///
1609 /// let ty = DataType::float();
1610 /// assert!(matches!(ty, DataType::Float(_)));
1611 /// ```
1612 #[must_use]
1613 pub fn float() -> Self {
1614 DataType::Float(FloatDataType::single())
1615 }
1616
1617 /// Creates a double-precision float (`DOUBLE PRECISION` / `DOUBLE`).
1618 ///
1619 /// # Example
1620 ///
1621 /// ```
1622 /// use razeri_plugin_contract::DataType;
1623 ///
1624 /// let ty = DataType::double();
1625 /// assert!(matches!(ty, DataType::Float(_)));
1626 /// ```
1627 #[must_use]
1628 pub fn double() -> Self {
1629 DataType::Float(FloatDataType::double())
1630 }
1631
1632 // ==========================================================================
1633 // Boolean Type Constructor
1634 // ==========================================================================
1635
1636 /// Creates a boolean type (`BOOLEAN` / `BIT`).
1637 ///
1638 /// # Example
1639 ///
1640 /// ```
1641 /// use razeri_plugin_contract::DataType;
1642 ///
1643 /// let ty = DataType::boolean();
1644 /// assert!(matches!(ty, DataType::Boolean(_)));
1645 /// ```
1646 #[must_use]
1647 pub fn boolean() -> Self {
1648 DataType::Boolean(BooleanDataType)
1649 }
1650
1651 // ==========================================================================
1652 // String Type Constructors
1653 // ==========================================================================
1654
1655 /// Creates a `VARCHAR(max_length)` type.
1656 ///
1657 /// # Example
1658 ///
1659 /// ```
1660 /// use razeri_plugin_contract::DataType;
1661 ///
1662 /// let name = DataType::varchar(255);
1663 /// assert!(matches!(name, DataType::VarChar(_)));
1664 /// ```
1665 #[must_use]
1666 pub fn varchar(max_length: u32) -> Self {
1667 DataType::VarChar(VarCharDataType::new(max_length))
1668 }
1669
1670 /// Creates an unbounded text type (`TEXT` / `VARCHAR(MAX)` / `STRING`).
1671 ///
1672 /// # Example
1673 ///
1674 /// ```
1675 /// use razeri_plugin_contract::DataType;
1676 ///
1677 /// let ty = DataType::text();
1678 /// assert!(matches!(ty, DataType::Text(_)));
1679 /// ```
1680 #[must_use]
1681 pub fn text() -> Self {
1682 DataType::Text(TextDataType)
1683 }
1684
1685 // ==========================================================================
1686 // Date/Time Type Constructors
1687 // ==========================================================================
1688
1689 /// Creates a date-only type (`DATE`).
1690 ///
1691 /// # Example
1692 ///
1693 /// ```
1694 /// use razeri_plugin_contract::DataType;
1695 ///
1696 /// let ty = DataType::date();
1697 /// assert!(matches!(ty, DataType::Date(_)));
1698 /// ```
1699 #[must_use]
1700 pub fn date() -> Self {
1701 DataType::Date(DateDataType)
1702 }
1703
1704 /// Creates a timestamp without timezone (`TIMESTAMP`).
1705 ///
1706 /// # Example
1707 ///
1708 /// ```
1709 /// use razeri_plugin_contract::DataType;
1710 ///
1711 /// let created = DataType::timestamp();
1712 /// assert!(matches!(created, DataType::Timestamp(_)));
1713 /// ```
1714 #[must_use]
1715 pub fn timestamp() -> Self {
1716 DataType::Timestamp(TimestampDataType::default())
1717 }
1718
1719 /// Creates a timestamp with timezone (`TIMESTAMPTZ` / `DATETIMEOFFSET`).
1720 ///
1721 /// # Example
1722 ///
1723 /// ```
1724 /// use razeri_plugin_contract::DataType;
1725 ///
1726 /// let created_tz = DataType::timestamp_tz();
1727 /// assert!(matches!(created_tz, DataType::TimestampTz(_)));
1728 /// ```
1729 #[must_use]
1730 pub fn timestamp_tz() -> Self {
1731 DataType::TimestampTz(TimestampTzDataType::default())
1732 }
1733
1734 // ==========================================================================
1735 // Special Type Constructors
1736 // ==========================================================================
1737
1738 /// Creates a UUID type (`UUID` / `UNIQUEIDENTIFIER`).
1739 ///
1740 /// # Example
1741 ///
1742 /// ```
1743 /// use razeri_plugin_contract::DataType;
1744 ///
1745 /// let ty = DataType::uuid();
1746 /// assert!(matches!(ty, DataType::Uuid(_)));
1747 /// ```
1748 #[must_use]
1749 pub fn uuid() -> Self {
1750 DataType::Uuid(UuidDataType)
1751 }
1752
1753 /// Creates a JSON type with binary storage (JSONB in `PostgreSQL`, VARIANT in Snowflake).
1754 ///
1755 /// # Example
1756 ///
1757 /// ```
1758 /// use razeri_plugin_contract::DataType;
1759 ///
1760 /// let data = DataType::json();
1761 /// assert!(matches!(data, DataType::Json(_)));
1762 /// ```
1763 #[must_use]
1764 pub fn json() -> Self {
1765 DataType::Json(JsonDataType::binary())
1766 }
1767
1768 /// Creates a JSON type with text storage (preserves formatting).
1769 ///
1770 /// # Example
1771 ///
1772 /// ```
1773 /// use razeri_plugin_contract::DataType;
1774 ///
1775 /// let data = DataType::json_text();
1776 /// assert!(matches!(data, DataType::Json(_)));
1777 /// ```
1778 #[must_use]
1779 pub fn json_text() -> Self {
1780 DataType::Json(JsonDataType::text())
1781 }
1782}
1783
1784// ============================================================================
1785// Tests
1786// ============================================================================
1787
1788#[cfg(test)]
1789mod tests {
1790 use super::*;
1791
1792 #[test]
1793 fn test_integer_types() {
1794 let tiny = IntegerDataType::new(IntegerSize::Tiny);
1795 assert_eq!(tiny.byte_size(), 1);
1796 assert!(!tiny.unsigned);
1797
1798 let big_unsigned = IntegerDataType::unsigned(IntegerSize::Big);
1799 assert_eq!(big_unsigned.byte_size(), 8);
1800 assert!(big_unsigned.unsigned);
1801 }
1802
1803 #[test]
1804 fn test_decimal_type() {
1805 let money = DecimalDataType::money();
1806 assert_eq!(money.precision, 19);
1807 assert_eq!(money.scale, 4);
1808 }
1809
1810 #[test]
1811 #[should_panic(expected = "Scale cannot exceed precision")]
1812 fn test_decimal_invalid_scale() {
1813 let _ = DecimalDataType::new(5, 10);
1814 }
1815
1816 #[test]
1817 fn test_convenience_constructors() {
1818 assert!(matches!(DataType::int(), DataType::Integer(_)));
1819 assert!(matches!(DataType::varchar(100), DataType::VarChar(_)));
1820 assert!(matches!(DataType::timestamp(), DataType::Timestamp(_)));
1821 }
1822
1823 /// A struct is built from named fields and keeps them in the order given.
1824 #[test]
1825 fn a_struct_keeps_its_fields_in_declaration_order() {
1826 let address = StructDataType::new(vec![
1827 StructField::new("street", DataType::varchar(200)),
1828 StructField::new("city", DataType::varchar(100)),
1829 StructField::new("zip", DataType::varchar(10)),
1830 ]);
1831
1832 let names: Vec<&str> = address.fields.iter().map(|f| f.name.as_str()).collect();
1833 assert_eq!(names, ["street", "city", "zip"]);
1834 assert_eq!(address.fields[2].data_type, DataType::varchar(10));
1835 assert!(matches!(DataType::Struct(address), DataType::Struct(_)));
1836 }
1837
1838 /// Field order is part of the type: the same fields in another order are a
1839 /// different struct, and the same fields in the same order are the same one.
1840 #[test]
1841 fn two_structs_are_equal_only_when_their_fields_agree_in_order() {
1842 let field = |name: &str| StructField::new(name, DataType::int());
1843 let xy = StructDataType::new(vec![field("x"), field("y")]);
1844 let xy_again = StructDataType::new(vec![field("x"), field("y")]);
1845 let yx = StructDataType::new(vec![field("y"), field("x")]);
1846 let renamed = StructDataType::new(vec![field("x"), field("z")]);
1847 let retyped = StructDataType::new(vec![
1848 StructField::new("x", DataType::int()),
1849 StructField::new("y", DataType::bigint()),
1850 ]);
1851
1852 assert_eq!(DataType::Struct(xy.clone()), DataType::Struct(xy_again));
1853 assert_ne!(DataType::Struct(xy.clone()), DataType::Struct(yx));
1854 assert_ne!(DataType::Struct(xy.clone()), DataType::Struct(renamed));
1855 assert_ne!(DataType::Struct(xy), DataType::Struct(retyped));
1856 }
1857
1858 /// A struct nests: its fields may themselves be collections or structs, so
1859 /// the array of structs a nested source table carries is one value.
1860 #[test]
1861 fn a_struct_field_may_itself_be_a_collection_or_a_struct() {
1862 let inner = StructDataType::new(vec![
1863 StructField::new("key", DataType::varchar(100)),
1864 StructField::new("value", DataType::varchar(1000)),
1865 ]);
1866 let outer = StructDataType::new(vec![
1867 StructField::new("id", DataType::bigint()),
1868 StructField::new(
1869 "extras",
1870 DataType::Array(ArrayDataType::new(DataType::Struct(inner))),
1871 ),
1872 StructField::new(
1873 "labels",
1874 DataType::Map(MapDataType::new(DataType::varchar(50), DataType::text())),
1875 ),
1876 ]);
1877
1878 let DataType::Array(extras) = &outer.fields[1].data_type else {
1879 panic!("the second field is an array");
1880 };
1881 let DataType::Struct(element) = extras.element_type.as_ref() else {
1882 panic!("the array's element is a struct");
1883 };
1884 assert_eq!(element.fields[0].name, "key");
1885 assert!(matches!(outer.fields[2].data_type, DataType::Map(_)));
1886 }
1887}
1888
1889// ============================================================================
1890// Simplified Data Type Reference (for CAST expressions)
1891// ============================================================================
1892
1893/// Simplified data type reference used in CAST / `TRY_CAST` / CONVERT expressions.
1894///
1895/// Unlike [`DataType`], which captures full type metadata for DDL generation,
1896/// `DataTypeRef` represents the type names commonly used in SQL expressions.
1897///
1898/// # Purpose
1899///
1900/// `DataTypeRef` is a lightweight, platform-agnostic type reference suitable for
1901/// embedding in SQL expressions such as:
1902///
1903/// ```sql
1904/// SELECT CAST(column_name AS VARCHAR(100)) FROM table
1905/// SELECT TRY_CAST(value AS DECIMAL(19, 4)) FROM source
1906/// SELECT CONVERT(DATETIME2, timestamp_col) FROM events
1907/// ```
1908///
1909/// # Differences from `DataType`
1910///
1911/// | Aspect | `DataType` | `DataTypeRef` |
1912/// |--------|-----------|---------------|
1913/// | **Use case** | DDL generation | CAST / CONVERT expressions |
1914/// | **Metadata** | Full (encoding, precision, storage format) | Minimal (precision, scale, length) |
1915/// | **Platform variants** | Unified (one variant per type) | Separate (`Decimal` vs `Numeric`, `DateTime` vs `DateTime2`) |
1916/// | **Collections** | Array, Map, Struct | Not supported |
1917///
1918/// # Platform-Specific Variants
1919///
1920/// Some types have separate variants for platform-specific naming:
1921///
1922/// - `Decimal` vs `Numeric` — equivalent in SQL, but some dialects prefer one over the other
1923/// - `DateTime` vs `DateTime2` — SQL Server uses `DATETIME2` for precision
1924/// - `Json` vs `Jsonb` — `PostgreSQL` distinguishes between text and binary JSON
1925/// - `Boolean` vs `Bit` — SQL Server uses `BIT` for boolean
1926///
1927/// # Example
1928///
1929/// ```
1930/// use razeri_plugin_contract::DataTypeRef;
1931///
1932/// let varchar = DataTypeRef::Varchar { length: Some(100) };
1933/// let decimal = DataTypeRef::Decimal { precision: 19, scale: 4 };
1934/// let timestamp = DataTypeRef::Timestamp { precision: Some(6) };
1935/// ```
1936#[derive(Debug, Clone, PartialEq)]
1937pub enum DataTypeRef {
1938 // ==========================================================================
1939 // Numeric Types
1940 // ==========================================================================
1941 /// 4-byte signed integer (`INT` / `INTEGER`).
1942 Int,
1943
1944 /// 8-byte signed integer (`BIGINT`).
1945 BigInt,
1946
1947 /// 2-byte signed integer (`SMALLINT`).
1948 SmallInt,
1949
1950 /// 1-byte signed integer (`TINYINT` / `SMALLINT` in `PostgreSQL`).
1951 TinyInt,
1952
1953 /// Single-precision float (`REAL` / `FLOAT4`).
1954 Float,
1955
1956 /// Double-precision float (`DOUBLE PRECISION` / `DOUBLE`).
1957 Double,
1958
1959 /// Single-precision float (`REAL` / `FLOAT4`).
1960 Real,
1961
1962 /// Exact numeric with precision and scale (`DECIMAL(p, s)`).
1963 Decimal {
1964 /// Total number of digits.
1965 precision: u8,
1966 /// Number of digits after the decimal point.
1967 scale: u8,
1968 },
1969
1970 /// Exact numeric with precision and scale (`NUMERIC(p, s)`).
1971 ///
1972 /// Equivalent to `Decimal` in SQL, but some dialects prefer `NUMERIC`
1973 /// over `DECIMAL` for exact numeric types.
1974 Numeric {
1975 /// Total number of digits.
1976 precision: u8,
1977 /// Number of digits after the decimal point.
1978 scale: u8,
1979 },
1980
1981 // ==========================================================================
1982 // String Types
1983 // ==========================================================================
1984 /// Variable-length character string (`VARCHAR(n)`).
1985 Varchar {
1986 /// Maximum length in characters (None = unlimited/MAX).
1987 length: Option<u16>,
1988 },
1989
1990 /// Fixed-length character string (`CHAR(n)`).
1991 Char {
1992 /// Length in characters.
1993 length: u16,
1994 },
1995
1996 /// Unbounded text type (`TEXT`).
1997 Text,
1998
1999 /// Variable-length Unicode string (`NVARCHAR(n)` — SQL Server).
2000 NVarchar {
2001 /// Maximum length in characters (None = unlimited/MAX).
2002 length: Option<u16>,
2003 },
2004
2005 /// Fixed-length Unicode string (`NCHAR(n)` — SQL Server).
2006 NChar {
2007 /// Length in characters.
2008 length: u16,
2009 },
2010
2011 // ==========================================================================
2012 // Binary Types
2013 // ==========================================================================
2014 /// Fixed-length binary data (`BINARY(n)`).
2015 Binary {
2016 /// Length in bytes.
2017 length: Option<u16>,
2018 },
2019
2020 /// Variable-length binary data (`VARBINARY(n)`).
2021 VarBinary {
2022 /// Maximum length in bytes (None = unlimited/MAX).
2023 length: Option<u16>,
2024 },
2025
2026 /// Unbounded binary data (`BLOB` / `BYTEA`).
2027 Blob,
2028
2029 // ==========================================================================
2030 // Date/Time Types
2031 // ==========================================================================
2032 /// Date only (`DATE`).
2033 Date,
2034
2035 /// Time with optional precision (`TIME(p)`).
2036 Time {
2037 /// Fractional seconds precision (None = default).
2038 precision: Option<u8>,
2039 },
2040
2041 /// Combined date and time (`DATETIME` — SQL Server legacy).
2042 DateTime,
2043
2044 /// Combined date and time with precision (`DATETIME2(p)` — SQL Server).
2045 DateTime2 {
2046 /// Fractional seconds precision (None = default).
2047 precision: Option<u8>,
2048 },
2049
2050 /// Timestamp with optional precision (`TIMESTAMP(p)`).
2051 Timestamp {
2052 /// Fractional seconds precision (None = default).
2053 precision: Option<u8>,
2054 },
2055
2056 /// Timestamp with timezone and optional precision (`TIMESTAMPTZ(p)`).
2057 TimestampTz {
2058 /// Fractional seconds precision (None = default).
2059 precision: Option<u8>,
2060 },
2061
2062 /// Interval/duration (`INTERVAL`).
2063 Interval,
2064
2065 // ==========================================================================
2066 // Boolean Types
2067 // ==========================================================================
2068 /// Boolean (`BOOLEAN` — `PostgreSQL`, Snowflake, `ClickHouse`).
2069 Boolean,
2070
2071 /// Bit (`BIT` — SQL Server).
2072 Bit,
2073
2074 // ==========================================================================
2075 // JSON/Semi-structured Types
2076 // ==========================================================================
2077 /// Text-based JSON (`JSON` — `PostgreSQL`).
2078 Json,
2079
2080 /// Binary-optimized JSON (`JSONB` — `PostgreSQL`).
2081 Jsonb,
2082
2083 /// Semi-structured variant (`VARIANT` — Snowflake).
2084 Variant,
2085
2086 // ==========================================================================
2087 // Other Types
2088 // ==========================================================================
2089 /// UUID/GUID (`UUID` / `UNIQUEIDENTIFIER`).
2090 Uuid,
2091
2092 /// XML data (`XML`).
2093 Xml,
2094
2095 /// Custom type name (for types not covered by the enum variants).
2096 Custom(String),
2097}