Skip to main content

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, Some(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, Some(3));
720/// assert!(tz_time.with_timezone);
721/// ```
722#[derive(Debug, Clone, PartialEq, Eq, Hash)]
723pub struct TimeDataType {
724    /// **The digits of a second a time keeps**, where the design states them:
725    /// `Some(0)` is whole seconds, `Some(3)` milliseconds. `None` is a bare
726    /// `time`, which keeps whatever the engine's own time keeps — a dialect
727    /// spells it without a precision, or with the one it has always written.
728    pub precision: Option<u8>,
729
730    /// Whether to include timezone offset.
731    ///
732    /// Maps to: `TIME WITH TIME ZONE` (`PostgreSQL`), not supported in SQL Server.
733    pub with_timezone: bool,
734}
735
736impl TimeDataType {
737    /// Creates a new `TimeDataType` with the given fractional seconds precision.
738    ///
739    /// # Panics
740    ///
741    /// Panics if `precision` exceeds 9.
742    ///
743    /// # Example
744    ///
745    /// ```rust
746    /// use razeri_plugin_contract::data_type::TimeDataType;
747    ///
748    /// let time = TimeDataType::new(3);
749    /// assert_eq!(time.precision, Some(3));
750    /// assert!(!time.with_timezone);
751    /// ```
752    ///
753    /// ```rust,should_panic
754    /// use razeri_plugin_contract::data_type::TimeDataType;
755    ///
756    /// let _ = TimeDataType::new(10); // panics: precision must be 0-9
757    /// ```
758    #[must_use]
759    pub fn new(precision: u8) -> Self {
760        assert!(precision <= 9, "Time precision must be 0-9");
761        Self { precision: Some(precision), with_timezone: false }
762    }
763
764    /// **A bare `time`**: no digits of a second stated, so the engine's own
765    /// time keeps whatever it keeps.
766    #[must_use]
767    pub const fn engines_own() -> Self {
768        Self { precision: None, with_timezone: false }
769    }
770
771    /// Creates a `TimeDataType` with timezone and the given precision.
772    ///
773    /// # Panics
774    ///
775    /// Panics if `precision` exceeds 9.
776    ///
777    /// # Example
778    ///
779    /// ```rust
780    /// use razeri_plugin_contract::data_type::TimeDataType;
781    ///
782    /// let tz_time = TimeDataType::with_timezone(3);
783    /// assert_eq!(tz_time.precision, Some(3));
784    /// assert!(tz_time.with_timezone);
785    /// ```
786    #[must_use]
787    pub fn with_timezone(precision: u8) -> Self {
788        assert!(precision <= 9, "Time precision must be 0-9");
789        Self { precision: Some(precision), with_timezone: true }
790    }
791}
792
793impl Default for TimeDataType {
794    fn default() -> Self {
795        Self { precision: Some(6), with_timezone: false }
796    }
797}
798
799/// Timestamp (date and time) without timezone.
800///
801/// Maps to: `TIMESTAMP` (`PostgreSQL`), `DATETIME2` (SQL Server),
802/// `TIMESTAMP_NTZ` (Snowflake), `TIMESTAMP` (Databricks), `DateTime` (`ClickHouse`).
803///
804/// # Precision
805///
806/// - `PostgreSQL`: 0-6
807/// - SQL Server: 0-7
808/// - Snowflake: 0-9
809/// - `ClickHouse`: 0-9
810///
811/// # Example
812///
813/// ```rust
814/// use razeri_plugin_contract::data_type::{DataType, TimestampDataType};
815///
816/// // Default: precision 6
817/// let ts = TimestampDataType::default();
818/// assert_eq!(ts.precision, 6);
819///
820/// // Custom precision
821/// let ts3 = TimestampDataType::new(3);
822/// assert_eq!(ts3.precision, 3);
823///
824/// // Via DataType convenience
825/// let created = DataType::timestamp();
826/// ```
827#[derive(Debug, Clone, PartialEq, Eq, Hash)]
828pub struct TimestampDataType {
829    /// Fractional seconds precision (0-9). **0 is whole seconds**, spelled so
830    /// by every engine that has a spelling for it; a design's bare
831    /// `timestamp` is 6.
832    ///
833    /// - `PostgreSQL`: 0-6
834    /// - SQL Server: 0-7
835    /// - Snowflake: 0-9
836    /// - `ClickHouse`: 0-9
837    pub precision: u8,
838}
839
840impl TimestampDataType {
841    /// Creates a new `TimestampDataType` with the given fractional seconds precision.
842    ///
843    /// # Panics
844    ///
845    /// Panics if `precision` exceeds 9.
846    ///
847    /// # Example
848    ///
849    /// ```rust
850    /// use razeri_plugin_contract::data_type::TimestampDataType;
851    ///
852    /// let ts = TimestampDataType::new(3);
853    /// assert_eq!(ts.precision, 3);
854    /// ```
855    ///
856    /// ```should_panic
857    /// use razeri_plugin_contract::data_type::TimestampDataType;
858    ///
859    /// let _ = TimestampDataType::new(10); // panics: precision must be 0-9
860    /// ```
861    #[must_use]
862    pub fn new(precision: u8) -> Self {
863        assert!(precision <= 9, "Timestamp precision must be 0-9");
864        Self { precision }
865    }
866}
867
868impl Default for TimestampDataType {
869    fn default() -> Self {
870        Self { precision: 6 }
871    }
872}
873
874/// Timestamp with timezone awareness.
875///
876/// Maps to: `TIMESTAMPTZ` (`PostgreSQL`), `DATETIMEOFFSET` (SQL Server),
877/// `TIMESTAMP_TZ`/`TIMESTAMP_LTZ` (Snowflake), `TIMESTAMP` (Databricks),
878/// `DateTime64` (`ClickHouse`).
879///
880/// # Example
881///
882/// ```rust
883/// use razeri_plugin_contract::DataType;
884/// use razeri_plugin_contract::data_type::TimestampTzDataType;
885///
886/// // Default: precision 6
887/// let ts = TimestampTzDataType::default();
888/// assert_eq!(ts.precision, 6);
889///
890/// // Via DataType convenience
891/// let created_tz = DataType::timestamp_tz();
892/// ```
893#[derive(Debug, Clone, PartialEq, Eq, Hash)]
894pub struct TimestampTzDataType {
895    /// Fractional seconds precision (0-9).
896    pub precision: u8,
897}
898
899impl TimestampTzDataType {
900    /// Creates a new `TimestampTzDataType` with the given fractional seconds precision.
901    ///
902    /// # Panics
903    ///
904    /// Panics if `precision` exceeds 9.
905    ///
906    /// # Example
907    ///
908    /// ```rust
909    /// use razeri_plugin_contract::data_type::TimestampTzDataType;
910    ///
911    /// let ts = TimestampTzDataType::new(3);
912    /// assert_eq!(ts.precision, 3);
913    /// ```
914    ///
915    /// ```should_panic
916    /// use razeri_plugin_contract::data_type::TimestampTzDataType;
917    ///
918    /// let _ = TimestampTzDataType::new(10); // panics: precision must be 0-9
919    /// ```
920    #[must_use]
921    pub fn new(precision: u8) -> Self {
922        assert!(precision <= 9, "Timestamp precision must be 0-9");
923        Self { precision }
924    }
925}
926
927impl Default for TimestampTzDataType {
928    fn default() -> Self {
929        Self { precision: 6 }
930    }
931}
932
933/// Combined date and time for legacy compatibility.
934///
935/// Maps to: `DATETIME` (SQL Server legacy), `TIMESTAMP` (`PostgreSQL`).
936/// Prefer [`TimestampDataType`] for new designs.
937///
938/// # Example
939///
940/// ```rust
941/// use razeri_plugin_contract::data_type::DateTimeDataType;
942///
943/// let dt = DateTimeDataType::new(3, true);
944/// assert_eq!(dt.precision, 3);
945/// assert!(dt.with_timezone);
946/// ```
947#[derive(Debug, Clone, PartialEq, Eq, Hash)]
948pub struct DateTimeDataType {
949    /// Fractional seconds precision (0-9).
950    pub precision: u8,
951
952    /// Whether to include timezone offset.
953    pub with_timezone: bool,
954}
955
956impl DateTimeDataType {
957    /// Creates a new `DateTimeDataType` with the given precision and timezone flag.
958    ///
959    /// # Example
960    ///
961    /// ```rust
962    /// use razeri_plugin_contract::data_type::DateTimeDataType;
963    ///
964    /// let dt = DateTimeDataType::new(6, false);
965    /// assert_eq!(dt.precision, 6);
966    /// assert!(!dt.with_timezone);
967    /// ```
968    #[must_use]
969    pub fn new(precision: u8, with_timezone: bool) -> Self {
970        Self { precision, with_timezone }
971    }
972}
973
974impl Default for DateTimeDataType {
975    fn default() -> Self {
976        Self { precision: 6, with_timezone: false }
977    }
978}
979
980/// Interval/duration type for time spans.
981///
982/// Maps to: `INTERVAL` (`PostgreSQL`, Snowflake, Databricks), not directly supported in
983/// SQL Server/`ClickHouse`.
984///
985/// # Example
986///
987/// ```rust
988/// use razeri_plugin_contract::data_type::IntervalDataType;
989///
990/// let ym = IntervalDataType::YearMonth;
991/// let dt = IntervalDataType::DayTime;
992///
993/// assert_eq!(format!("{:?}", ym), "YearMonth");
994/// assert_eq!(format!("{:?}", dt), "DayTime");
995/// ```
996#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
997pub enum IntervalDataType {
998    /// Year to month intervals.
999    YearMonth,
1000    /// Day to second intervals (with optional fractional seconds precision).
1001    DayTime,
1002}
1003
1004// ============================================================================
1005// Special Types
1006// ============================================================================
1007
1008/// Universally Unique Identifier (UUID/GUID).
1009///
1010/// Maps to: `UUID` (`PostgreSQL`), `UNIQUEIDENTIFIER` (SQL Server),
1011/// `STRING` (Snowflake, Databricks), `UUID` (`ClickHouse`).
1012///
1013/// # Example
1014///
1015/// ```
1016/// use razeri_plugin_contract::DataType;
1017///
1018/// let uuid = DataType::uuid();
1019/// ```
1020#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1021pub struct UuidDataType;
1022
1023/// JSON storage format preference.
1024///
1025/// Determines whether JSON data is stored as text (preserving whitespace and key order)
1026/// or in a binary-optimized format (normalized, faster queries, indexable).
1027///
1028/// # Example
1029///
1030/// ```rust
1031/// use razeri_plugin_contract::data_type::JsonStorageFormat;
1032///
1033/// let text = JsonStorageFormat::Text;
1034/// let binary = JsonStorageFormat::Binary;
1035///
1036/// assert_eq!(format!("{:?}", text), "Text");
1037/// assert_eq!(format!("{:?}", binary), "Binary");
1038/// assert!(!text.is_binary());
1039/// assert!(binary.is_binary());
1040/// ```
1041#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1042pub enum JsonStorageFormat {
1043    /// Text-based JSON storage (preserves whitespace, key order).
1044    ///
1045    /// Maps to: `JSON` (`PostgreSQL`), `NVARCHAR(MAX)` (SQL Server),
1046    /// `STRING` (Databricks).
1047    Text,
1048    /// Binary-optimized JSON storage (normalized, faster queries, indexable).
1049    ///
1050    /// Maps to: `JSONB` (`PostgreSQL`), `VARIANT` (Snowflake), `JSON` (`ClickHouse`).
1051    ///
1052    /// This is the recommended default for most use cases.
1053    #[default]
1054    Binary,
1055}
1056
1057impl JsonStorageFormat {
1058    /// Returns `true` if this is the binary storage format.
1059    ///
1060    /// # Example
1061    ///
1062    /// ```rust
1063    /// use razeri_plugin_contract::data_type::JsonStorageFormat;
1064    ///
1065    /// assert!(!JsonStorageFormat::Text.is_binary());
1066    /// assert!(JsonStorageFormat::Binary.is_binary());
1067    /// ```
1068    #[must_use]
1069    pub const fn is_binary(&self) -> bool {
1070        matches!(self, Self::Binary)
1071    }
1072}
1073
1074/// JSON/Semi-structured data type.
1075///
1076/// Database mapping details:
1077///
1078/// | Platform | Type | Notes |
1079/// |----------|------|-------|
1080/// | **`PostgreSQL`** | `JSON` or `JSONB` | JSONB supports GIN indexing and is faster for queries |
1081/// | **SQL Server** | `NVARCHAR(MAX)` with JSON functions (2016+), or native JSON (2025+) | |
1082/// | **Snowflake** | `VARIANT` | Native semi-structured type, supports nested data and arrays |
1083/// | **Databricks** | `STRING` with JSON functions, or `VARIANT` | |
1084/// | **`ClickHouse`** | `JSON` (experimental/production), or `String` with JSON functions | |
1085/// | **`CitusDB`** | Same as `PostgreSQL` (JSONB recommended) | |
1086///
1087/// # Storage Formats
1088///
1089/// - **Binary** (default): Normalized storage, faster queries, indexable.
1090///   Maps to `JSONB` in `PostgreSQL`, `VARIANT` in Snowflake.
1091/// - **Text**: Preserves whitespace, key order, and duplicate keys.
1092///   Maps to `JSON` in `PostgreSQL`, `NVARCHAR(MAX)` in SQL Server.
1093///
1094/// # Example
1095///
1096/// ```rust
1097/// use razeri_plugin_contract::DataType;
1098/// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1099///
1100/// // Binary format (recommended) - uses JSONB in PostgreSQL, VARIANT in Snowflake
1101/// let json_binary = JsonDataType::default();
1102/// assert_eq!(json_binary.storage_format, JsonStorageFormat::Binary);
1103///
1104/// // Text format - preserves original formatting
1105/// let json_text = JsonDataType::text();
1106/// assert_eq!(json_text.storage_format, JsonStorageFormat::Text);
1107///
1108/// // Via DataType convenience
1109/// let data = DataType::json();
1110/// ```
1111#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
1112pub struct JsonDataType {
1113    /// Storage format preference (binary is recommended for most use cases).
1114    pub storage_format: JsonStorageFormat,
1115
1116    /// Whether to validate JSON on insert (if supported by the database).
1117    ///
1118    /// `PostgreSQL` always validates, some other databases may not.
1119    pub validate_on_insert: bool,
1120}
1121
1122impl JsonDataType {
1123    /// Creates a new JSON type with binary storage format (JSONB in `PostgreSQL`).
1124    ///
1125    /// This is the recommended format for most use cases as it enables:
1126    /// - Faster query performance
1127    /// - GIN indexing support (`PostgreSQL`)
1128    /// - Normalized storage (no duplicate keys, sorted keys)
1129    ///
1130    /// # Example
1131    ///
1132    /// ```rust
1133    /// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1134    ///
1135    /// let json = JsonDataType::new();
1136    /// assert_eq!(json.storage_format, JsonStorageFormat::Binary);
1137    /// assert!(!json.validate_on_insert);
1138    /// ```
1139    #[must_use]
1140    pub fn new() -> Self {
1141        Self::default()
1142    }
1143
1144    /// Creates a JSON type with text storage format.
1145    ///
1146    /// Use this when you need to preserve:
1147    /// - Original whitespace formatting
1148    /// - Original key order
1149    /// - Duplicate keys (not recommended but sometimes required)
1150    ///
1151    /// # Example
1152    ///
1153    /// ```rust
1154    /// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1155    ///
1156    /// let json = JsonDataType::text();
1157    /// assert_eq!(json.storage_format, JsonStorageFormat::Text);
1158    /// assert!(json.validate_on_insert);
1159    /// ```
1160    #[must_use]
1161    pub fn text() -> Self {
1162        Self { storage_format: JsonStorageFormat::Text, validate_on_insert: true }
1163    }
1164
1165    /// Creates a JSON type with binary storage format.
1166    ///
1167    /// # Example
1168    ///
1169    /// ```rust
1170    /// use razeri_plugin_contract::data_type::{JsonDataType, JsonStorageFormat};
1171    ///
1172    /// let json = JsonDataType::binary();
1173    /// assert_eq!(json.storage_format, JsonStorageFormat::Binary);
1174    /// assert!(json.validate_on_insert);
1175    /// ```
1176    #[must_use]
1177    pub fn binary() -> Self {
1178        Self { storage_format: JsonStorageFormat::Binary, validate_on_insert: true }
1179    }
1180
1181    /// Sets whether to validate JSON on insert.
1182    ///
1183    /// # Example
1184    ///
1185    /// ```rust
1186    /// use razeri_plugin_contract::data_type::JsonDataType;
1187    ///
1188    /// let json = JsonDataType::new().with_validation(false);
1189    /// assert!(!json.validate_on_insert);
1190    /// ```
1191    #[must_use]
1192    pub fn with_validation(mut self, validate: bool) -> Self {
1193        self.validate_on_insert = validate;
1194        self
1195    }
1196}
1197
1198// ============================================================================
1199// Complex/Collection Types
1200// ============================================================================
1201
1202/// Array type containing elements of another type.
1203///
1204/// Supported by: `PostgreSQL`, `ClickHouse`, Snowflake (ARRAY), Databricks (ARRAY).
1205/// Not directly supported by SQL Server.
1206///
1207/// # Example
1208///
1209/// ```rust
1210/// use razeri_plugin_contract::{DataType, data_type::ArrayDataType};
1211///
1212/// let int_array = ArrayDataType::new(DataType::int());
1213/// assert_eq!(*int_array.element_type, DataType::int());
1214///
1215/// let _str_array = ArrayDataType::new(DataType::varchar(100));
1216/// ```
1217#[derive(Debug, Clone, PartialEq, Eq)]
1218pub struct ArrayDataType {
1219    /// The data type of array elements.
1220    pub element_type: Box<DataType>,
1221}
1222
1223impl ArrayDataType {
1224    /// Creates a new array type with the given element type.
1225    ///
1226    /// # Example
1227    ///
1228    /// ```rust
1229    /// use razeri_plugin_contract::{DataType, data_type::ArrayDataType};
1230    ///
1231    /// let arr = ArrayDataType::new(DataType::bigint());
1232    /// assert_eq!(*arr.element_type, DataType::bigint());
1233    /// ```
1234    #[must_use]
1235    pub fn new(element_type: DataType) -> Self {
1236        Self { element_type: Box::new(element_type) }
1237    }
1238}
1239
1240/// Map/Dictionary type with key-value pairs.
1241///
1242/// Supported by: `ClickHouse`, Snowflake (OBJECT), Databricks (MAP).
1243/// `PostgreSQL` uses JSONB, SQL Server uses JSON string.
1244///
1245/// # Example
1246///
1247/// ```rust
1248/// use razeri_plugin_contract::DataType;
1249/// use razeri_plugin_contract::data_type::MapDataType;
1250///
1251/// let map = MapDataType::new(DataType::varchar(50), DataType::int());
1252/// assert_eq!(*map.key_type, DataType::varchar(50));
1253/// assert_eq!(*map.value_type, DataType::int());
1254/// ```
1255#[derive(Debug, Clone, PartialEq, Eq)]
1256pub struct MapDataType {
1257    /// The data type of map keys.
1258    pub key_type: Box<DataType>,
1259
1260    /// The data type of map values.
1261    pub value_type: Box<DataType>,
1262}
1263
1264impl MapDataType {
1265    /// Creates a new map type with the given key and value types.
1266    ///
1267    /// # Example
1268    ///
1269    /// ```rust
1270    /// use razeri_plugin_contract::DataType;
1271    /// use razeri_plugin_contract::data_type::MapDataType;
1272    ///
1273    /// let map = MapDataType::new(DataType::varchar(50), DataType::int());
1274    /// assert_eq!(*map.key_type, DataType::varchar(50));
1275    /// assert_eq!(*map.value_type, DataType::int());
1276    /// ```
1277    #[must_use]
1278    pub fn new(key_type: DataType, value_type: DataType) -> Self {
1279        Self { key_type: Box::new(key_type), value_type: Box::new(value_type) }
1280    }
1281}
1282
1283/// One named field inside a [`StructDataType`], in the order it was declared.
1284///
1285/// A struct's fields are named on every engine that has a record type, and
1286/// positional on some, so both the name and the position are part of the type.
1287///
1288/// # Example
1289///
1290/// ```rust
1291/// use razeri_plugin_contract::{DataType, data_type::StructField};
1292///
1293/// let zip = StructField::new("zip", DataType::varchar(10));
1294/// assert_eq!(zip.name, "zip");
1295/// assert_eq!(zip.data_type, DataType::varchar(10));
1296/// ```
1297#[derive(Debug, Clone, PartialEq, Eq)]
1298pub struct StructField {
1299    /// The field's name, as the design declared it.
1300    pub name: String,
1301
1302    /// The field's type. May itself be a collection or another struct.
1303    pub data_type: DataType,
1304}
1305
1306impl StructField {
1307    /// Creates a named field of the given type.
1308    ///
1309    /// # Example
1310    ///
1311    /// ```rust
1312    /// use razeri_plugin_contract::{DataType, data_type::StructField};
1313    ///
1314    /// let field = StructField::new("category", DataType::varchar(100));
1315    /// assert_eq!(field.data_type, DataType::varchar(100));
1316    /// ```
1317    pub fn new(name: impl Into<String>, data_type: DataType) -> Self {
1318        Self { name: name.into(), data_type }
1319    }
1320}
1321
1322/// Struct/record type: an ordered list of named fields.
1323///
1324/// Supported natively by `BigQuery` (`STRUCT`), Databricks (`STRUCT`),
1325/// `ClickHouse` (named `Tuple`) and `DuckDB` (`STRUCT`). Engines without a
1326/// record type render their JSON type instead; the field names and types are
1327/// still part of the declared type, they are simply not restated in the DDL.
1328///
1329/// Field order is significant: it is the order the design declared, it is the
1330/// order every engine with a record type writes, and two structs holding the
1331/// same fields in a different order are different types.
1332///
1333/// # Example
1334///
1335/// ```rust
1336/// use razeri_plugin_contract::{DataType, data_type::{StructDataType, StructField}};
1337///
1338/// let address = StructDataType::new(vec![
1339///     StructField::new("street", DataType::varchar(200)),
1340///     StructField::new("zip", DataType::varchar(10)),
1341/// ]);
1342/// assert_eq!(address.fields.len(), 2);
1343/// assert_eq!(address.fields[0].name, "street");
1344/// ```
1345#[derive(Debug, Clone, PartialEq, Eq)]
1346pub struct StructDataType {
1347    /// The struct's fields, in declaration order.
1348    pub fields: Vec<StructField>,
1349}
1350
1351impl StructDataType {
1352    /// Creates a struct type from an ordered list of named fields.
1353    ///
1354    /// # Example
1355    ///
1356    /// ```rust
1357    /// use razeri_plugin_contract::{DataType, data_type::{StructDataType, StructField}};
1358    ///
1359    /// let point = StructDataType::new(vec![
1360    ///     StructField::new("x", DataType::int()),
1361    ///     StructField::new("y", DataType::int()),
1362    /// ]);
1363    /// assert_eq!(point.fields[1].name, "y");
1364    /// ```
1365    #[must_use]
1366    pub fn new(fields: Vec<StructField>) -> Self {
1367        Self { fields }
1368    }
1369}
1370
1371// ============================================================================
1372// Unified Data Type Enum
1373// ============================================================================
1374
1375/// Unified data type enumeration covering all supported types across databases.
1376///
1377/// This is the **primary type system** for Razeri. Every field, column, and
1378/// parameter in a design document is typed using `DataType`. The enum captures
1379/// enough metadata to generate correct DDL for every target while remaining
1380/// abstract over platform-specific implementation details.
1381///
1382/// # Type Categories
1383///
1384/// | Category | Variants | Example |
1385/// |----------|----------|---------|
1386/// | Numeric | `Integer`, `Decimal`, `Float` | `INT`, `DECIMAL(19,4)`, `DOUBLE` |
1387/// | Boolean | `Boolean` | `BOOLEAN`, `BIT` |
1388/// | String | `Char`, `VarChar`, `Text` | `VARCHAR(100)`, `TEXT` |
1389/// | Binary | `Binary`, `VarBinary` | `BYTEA`, `VARBINARY` |
1390/// | Date/Time | `Date`, `Time`, `Timestamp`, `TimestampTz`, `DateTime`, `Interval` | `DATE`, `TIMESTAMP` |
1391/// | Special | `Uuid`, `Json` | `UUID`, `JSONB` |
1392/// | Collection | `Array`, `Map`, `Struct` | `INTEGER[]`, `MAP<TEXT,INT>`, `STRUCT<a INT>` |
1393///
1394/// # Design Principles
1395///
1396/// 1. **Abstract over database-specific implementations** — `DataType` captures
1397///    enough metadata to generate correct DDL for each target, but does not
1398///    embed platform-specific syntax.
1399/// 2. **Preserve precision metadata** — Decimal types carry precision and scale;
1400///    string types carry length constraints; timestamp types carry fractional
1401///    seconds precision.
1402/// 3. **Every type renders somewhere** — a dialect answers for every variant,
1403///    emulating a type it does not have (JSON stored as `NVARCHAR(MAX)` on SQL
1404///    Server, a struct stored as `jsonb` on `PostgreSQL`) rather than refusing.
1405///
1406/// # Usage
1407///
1408/// ```
1409/// use razeri_plugin_contract::DataType;
1410///
1411/// // Convenience constructors for common types
1412/// let id = DataType::bigint();
1413/// let name = DataType::varchar(255);
1414/// let price = DataType::decimal(19, 4);
1415/// let created = DataType::timestamp();
1416///
1417/// // JSON with binary storage (JSONB in PostgreSQL, VARIANT in Snowflake)
1418/// let data = DataType::json();
1419/// ```
1420///
1421/// # Cross-Database Mapping
1422///
1423/// Each variant maps to platform-specific types as documented in the variant
1424/// definitions. Dialect plugins resolve the final SQL type string during
1425/// rendering; the rendering traits live in this crate's `renderer` module.
1426///
1427/// # The enum is `#[non_exhaustive]`
1428///
1429/// New types are added over time, so a `match` outside this crate needs a
1430/// fallback arm. A dialect's fallback should render the closest type the engine
1431/// does have — its JSON or text type — rather than panicking: a type the
1432/// dialect has not been taught about is still a column someone declared.
1433#[derive(Debug, Clone, PartialEq, Eq)]
1434#[non_exhaustive]
1435pub enum DataType {
1436    // Numeric types
1437    /// Signed/unsigned integer (1, 2, 4, or 8 bytes).
1438    Integer(IntegerDataType),
1439
1440    /// Exact numeric with fixed precision and scale.
1441    Decimal(DecimalDataType),
1442
1443    /// Approximate floating-point (4 or 8 bytes).
1444    Float(FloatDataType),
1445
1446    /// Boolean (true/false).
1447    Boolean(BooleanDataType),
1448
1449    // String types
1450    /// Fixed-length character string.
1451    Char(CharDataType),
1452
1453    /// Variable-length character string.
1454    VarChar(VarCharDataType),
1455
1456    /// Unbounded text type.
1457    Text(TextDataType),
1458
1459    // Binary types
1460    /// Fixed-length binary data.
1461    Binary(BinaryDataType),
1462
1463    /// Variable-length binary data.
1464    VarBinary(VarBinaryDataType),
1465
1466    // Date/Time types
1467    /// Date only (year, month, day).
1468    Date(DateDataType),
1469
1470    /// Time only (hour, minute, second, optional timezone).
1471    Time(TimeDataType),
1472
1473    /// Timestamp without timezone.
1474    Timestamp(TimestampDataType),
1475
1476    /// Timestamp with timezone.
1477    TimestampTz(TimestampTzDataType),
1478
1479    /// Combined date and time (legacy).
1480    DateTime(DateTimeDataType),
1481
1482    /// Interval/duration.
1483    Interval(IntervalDataType),
1484
1485    // Special types
1486    /// UUID/GUID.
1487    Uuid(UuidDataType),
1488
1489    /// JSON/semi-structured data.
1490    Json(JsonDataType),
1491
1492    // Complex types
1493    /// Array of elements.
1494    Array(ArrayDataType),
1495
1496    /// Map/dictionary with key-value pairs.
1497    Map(MapDataType),
1498
1499    /// Record with an ordered list of named fields.
1500    Struct(StructDataType),
1501}
1502
1503impl DataType {
1504    /// Whether this type holds more than one value inside one column — an
1505    /// array, a map, or a record.
1506    ///
1507    /// Nested columns are answered differently from scalars in several places:
1508    /// they cannot be part of a key, nothing aggregates them, and an engine
1509    /// with no record type of its own stores them as JSON or text. This is the
1510    /// one question all of those ask, so they ask it the same way.
1511    ///
1512    /// # Example
1513    ///
1514    /// ```
1515    /// use razeri_plugin_contract::{DataType, data_type::ArrayDataType};
1516    ///
1517    /// assert!(DataType::Array(ArrayDataType::new(DataType::int())).is_nested());
1518    /// assert!(!DataType::bigint().is_nested());
1519    /// ```
1520    #[must_use]
1521    pub const fn is_nested(&self) -> bool {
1522        matches!(self, DataType::Array(_) | DataType::Map(_) | DataType::Struct(_))
1523    }
1524
1525    // ==========================================================================
1526    // Numeric Type Constructors
1527    // ==========================================================================
1528
1529    /// Creates a 1-byte signed integer (`TINYINT` / `SMALLINT` / `Int8`).
1530    ///
1531    /// # Example
1532    ///
1533    /// ```
1534    /// use razeri_plugin_contract::DataType;
1535    ///
1536    /// let ty = DataType::tinyint();
1537    /// assert!(matches!(ty, DataType::Integer(_)));
1538    /// ```
1539    #[must_use]
1540    pub fn tinyint() -> Self {
1541        DataType::Integer(IntegerDataType::new(IntegerSize::Tiny))
1542    }
1543
1544    /// Creates a 2-byte signed integer (`SMALLINT` / `Int16`).
1545    ///
1546    /// # Example
1547    ///
1548    /// ```
1549    /// use razeri_plugin_contract::DataType;
1550    ///
1551    /// let ty = DataType::smallint();
1552    /// assert!(matches!(ty, DataType::Integer(_)));
1553    /// ```
1554    #[must_use]
1555    pub fn smallint() -> Self {
1556        DataType::Integer(IntegerDataType::new(IntegerSize::Small))
1557    }
1558
1559    /// Creates a 4-byte signed integer (`INT` / `INTEGER` / `Int32`).
1560    ///
1561    /// # Example
1562    ///
1563    /// ```
1564    /// use razeri_plugin_contract::DataType;
1565    ///
1566    /// let id = DataType::int();
1567    /// assert!(matches!(id, DataType::Integer(_)));
1568    /// ```
1569    #[must_use]
1570    pub fn int() -> Self {
1571        DataType::Integer(IntegerDataType::new(IntegerSize::Standard))
1572    }
1573
1574    /// Creates an 8-byte signed integer (`BIGINT` / `Int64`).
1575    ///
1576    /// # Example
1577    ///
1578    /// ```
1579    /// use razeri_plugin_contract::DataType;
1580    ///
1581    /// let id = DataType::bigint();
1582    /// assert!(matches!(id, DataType::Integer(_)));
1583    /// ```
1584    #[must_use]
1585    pub fn bigint() -> Self {
1586        DataType::Integer(IntegerDataType::new(IntegerSize::Big))
1587    }
1588
1589    /// Creates a `DECIMAL(precision, scale)` type.
1590    ///
1591    /// # Panics
1592    ///
1593    /// Panics if `precision` is 0 or exceeds 38, or if `scale > precision`.
1594    ///
1595    /// # Example
1596    ///
1597    /// ```
1598    /// use razeri_plugin_contract::DataType;
1599    ///
1600    /// let price = DataType::decimal(19, 4);
1601    /// assert!(matches!(price, DataType::Decimal(_)));
1602    /// ```
1603    ///
1604    /// ```should_panic
1605    /// use razeri_plugin_contract::DataType;
1606    ///
1607    /// let _ = DataType::decimal(0, 0); // panics: precision must be 1-38
1608    /// ```
1609    #[must_use]
1610    pub fn decimal(precision: u8, scale: u8) -> Self {
1611        DataType::Decimal(DecimalDataType::new(precision, scale))
1612    }
1613
1614    /// Creates a single-precision float (`REAL` / `FLOAT4`).
1615    ///
1616    /// # Example
1617    ///
1618    /// ```
1619    /// use razeri_plugin_contract::DataType;
1620    ///
1621    /// let ty = DataType::float();
1622    /// assert!(matches!(ty, DataType::Float(_)));
1623    /// ```
1624    #[must_use]
1625    pub fn float() -> Self {
1626        DataType::Float(FloatDataType::single())
1627    }
1628
1629    /// Creates a double-precision float (`DOUBLE PRECISION` / `DOUBLE`).
1630    ///
1631    /// # Example
1632    ///
1633    /// ```
1634    /// use razeri_plugin_contract::DataType;
1635    ///
1636    /// let ty = DataType::double();
1637    /// assert!(matches!(ty, DataType::Float(_)));
1638    /// ```
1639    #[must_use]
1640    pub fn double() -> Self {
1641        DataType::Float(FloatDataType::double())
1642    }
1643
1644    // ==========================================================================
1645    // Boolean Type Constructor
1646    // ==========================================================================
1647
1648    /// Creates a boolean type (`BOOLEAN` / `BIT`).
1649    ///
1650    /// # Example
1651    ///
1652    /// ```
1653    /// use razeri_plugin_contract::DataType;
1654    ///
1655    /// let ty = DataType::boolean();
1656    /// assert!(matches!(ty, DataType::Boolean(_)));
1657    /// ```
1658    #[must_use]
1659    pub fn boolean() -> Self {
1660        DataType::Boolean(BooleanDataType)
1661    }
1662
1663    // ==========================================================================
1664    // String Type Constructors
1665    // ==========================================================================
1666
1667    /// Creates a `VARCHAR(max_length)` type.
1668    ///
1669    /// # Example
1670    ///
1671    /// ```
1672    /// use razeri_plugin_contract::DataType;
1673    ///
1674    /// let name = DataType::varchar(255);
1675    /// assert!(matches!(name, DataType::VarChar(_)));
1676    /// ```
1677    #[must_use]
1678    pub fn varchar(max_length: u32) -> Self {
1679        DataType::VarChar(VarCharDataType::new(max_length))
1680    }
1681
1682    /// Creates an unbounded text type (`TEXT` / `VARCHAR(MAX)` / `STRING`).
1683    ///
1684    /// # Example
1685    ///
1686    /// ```
1687    /// use razeri_plugin_contract::DataType;
1688    ///
1689    /// let ty = DataType::text();
1690    /// assert!(matches!(ty, DataType::Text(_)));
1691    /// ```
1692    #[must_use]
1693    pub fn text() -> Self {
1694        DataType::Text(TextDataType)
1695    }
1696
1697    // ==========================================================================
1698    // Date/Time Type Constructors
1699    // ==========================================================================
1700
1701    /// Creates a date-only type (`DATE`).
1702    ///
1703    /// # Example
1704    ///
1705    /// ```
1706    /// use razeri_plugin_contract::DataType;
1707    ///
1708    /// let ty = DataType::date();
1709    /// assert!(matches!(ty, DataType::Date(_)));
1710    /// ```
1711    #[must_use]
1712    pub fn date() -> Self {
1713        DataType::Date(DateDataType)
1714    }
1715
1716    /// Creates a timestamp without timezone (`TIMESTAMP`).
1717    ///
1718    /// # Example
1719    ///
1720    /// ```
1721    /// use razeri_plugin_contract::DataType;
1722    ///
1723    /// let created = DataType::timestamp();
1724    /// assert!(matches!(created, DataType::Timestamp(_)));
1725    /// ```
1726    #[must_use]
1727    pub fn timestamp() -> Self {
1728        DataType::Timestamp(TimestampDataType::default())
1729    }
1730
1731    /// Creates a timestamp with timezone (`TIMESTAMPTZ` / `DATETIMEOFFSET`).
1732    ///
1733    /// # Example
1734    ///
1735    /// ```
1736    /// use razeri_plugin_contract::DataType;
1737    ///
1738    /// let created_tz = DataType::timestamp_tz();
1739    /// assert!(matches!(created_tz, DataType::TimestampTz(_)));
1740    /// ```
1741    #[must_use]
1742    pub fn timestamp_tz() -> Self {
1743        DataType::TimestampTz(TimestampTzDataType::default())
1744    }
1745
1746    // ==========================================================================
1747    // Special Type Constructors
1748    // ==========================================================================
1749
1750    /// Creates a UUID type (`UUID` / `UNIQUEIDENTIFIER`).
1751    ///
1752    /// # Example
1753    ///
1754    /// ```
1755    /// use razeri_plugin_contract::DataType;
1756    ///
1757    /// let ty = DataType::uuid();
1758    /// assert!(matches!(ty, DataType::Uuid(_)));
1759    /// ```
1760    #[must_use]
1761    pub fn uuid() -> Self {
1762        DataType::Uuid(UuidDataType)
1763    }
1764
1765    /// Creates a JSON type with binary storage (JSONB in `PostgreSQL`, VARIANT in Snowflake).
1766    ///
1767    /// # Example
1768    ///
1769    /// ```
1770    /// use razeri_plugin_contract::DataType;
1771    ///
1772    /// let data = DataType::json();
1773    /// assert!(matches!(data, DataType::Json(_)));
1774    /// ```
1775    #[must_use]
1776    pub fn json() -> Self {
1777        DataType::Json(JsonDataType::binary())
1778    }
1779
1780    /// Creates a JSON type with text storage (preserves formatting).
1781    ///
1782    /// # Example
1783    ///
1784    /// ```
1785    /// use razeri_plugin_contract::DataType;
1786    ///
1787    /// let data = DataType::json_text();
1788    /// assert!(matches!(data, DataType::Json(_)));
1789    /// ```
1790    #[must_use]
1791    pub fn json_text() -> Self {
1792        DataType::Json(JsonDataType::text())
1793    }
1794}
1795
1796// ============================================================================
1797// Tests
1798// ============================================================================
1799
1800#[cfg(test)]
1801mod tests {
1802    use super::*;
1803
1804    #[test]
1805    fn test_integer_types() {
1806        let tiny = IntegerDataType::new(IntegerSize::Tiny);
1807        assert_eq!(tiny.byte_size(), 1);
1808        assert!(!tiny.unsigned);
1809
1810        let big_unsigned = IntegerDataType::unsigned(IntegerSize::Big);
1811        assert_eq!(big_unsigned.byte_size(), 8);
1812        assert!(big_unsigned.unsigned);
1813    }
1814
1815    #[test]
1816    fn test_decimal_type() {
1817        let money = DecimalDataType::money();
1818        assert_eq!(money.precision, 19);
1819        assert_eq!(money.scale, 4);
1820    }
1821
1822    #[test]
1823    #[should_panic(expected = "Scale cannot exceed precision")]
1824    fn test_decimal_invalid_scale() {
1825        let _ = DecimalDataType::new(5, 10);
1826    }
1827
1828    #[test]
1829    fn test_convenience_constructors() {
1830        assert!(matches!(DataType::int(), DataType::Integer(_)));
1831        assert!(matches!(DataType::varchar(100), DataType::VarChar(_)));
1832        assert!(matches!(DataType::timestamp(), DataType::Timestamp(_)));
1833    }
1834
1835    /// A struct is built from named fields and keeps them in the order given.
1836    #[test]
1837    fn a_struct_keeps_its_fields_in_declaration_order() {
1838        let address = StructDataType::new(vec![
1839            StructField::new("street", DataType::varchar(200)),
1840            StructField::new("city", DataType::varchar(100)),
1841            StructField::new("zip", DataType::varchar(10)),
1842        ]);
1843
1844        let names: Vec<&str> = address.fields.iter().map(|f| f.name.as_str()).collect();
1845        assert_eq!(names, ["street", "city", "zip"]);
1846        assert_eq!(address.fields[2].data_type, DataType::varchar(10));
1847        assert!(matches!(DataType::Struct(address), DataType::Struct(_)));
1848    }
1849
1850    /// Field order is part of the type: the same fields in another order are a
1851    /// different struct, and the same fields in the same order are the same one.
1852    #[test]
1853    fn two_structs_are_equal_only_when_their_fields_agree_in_order() {
1854        let field = |name: &str| StructField::new(name, DataType::int());
1855        let xy = StructDataType::new(vec![field("x"), field("y")]);
1856        let xy_again = StructDataType::new(vec![field("x"), field("y")]);
1857        let yx = StructDataType::new(vec![field("y"), field("x")]);
1858        let renamed = StructDataType::new(vec![field("x"), field("z")]);
1859        let retyped = StructDataType::new(vec![
1860            StructField::new("x", DataType::int()),
1861            StructField::new("y", DataType::bigint()),
1862        ]);
1863
1864        assert_eq!(DataType::Struct(xy.clone()), DataType::Struct(xy_again));
1865        assert_ne!(DataType::Struct(xy.clone()), DataType::Struct(yx));
1866        assert_ne!(DataType::Struct(xy.clone()), DataType::Struct(renamed));
1867        assert_ne!(DataType::Struct(xy), DataType::Struct(retyped));
1868    }
1869
1870    /// A struct nests: its fields may themselves be collections or structs, so
1871    /// the array of structs a nested source table carries is one value.
1872    #[test]
1873    fn a_struct_field_may_itself_be_a_collection_or_a_struct() {
1874        let inner = StructDataType::new(vec![
1875            StructField::new("key", DataType::varchar(100)),
1876            StructField::new("value", DataType::varchar(1000)),
1877        ]);
1878        let outer = StructDataType::new(vec![
1879            StructField::new("id", DataType::bigint()),
1880            StructField::new(
1881                "extras",
1882                DataType::Array(ArrayDataType::new(DataType::Struct(inner))),
1883            ),
1884            StructField::new(
1885                "labels",
1886                DataType::Map(MapDataType::new(DataType::varchar(50), DataType::text())),
1887            ),
1888        ]);
1889
1890        let DataType::Array(extras) = &outer.fields[1].data_type else {
1891            panic!("the second field is an array");
1892        };
1893        let DataType::Struct(element) = extras.element_type.as_ref() else {
1894            panic!("the array's element is a struct");
1895        };
1896        assert_eq!(element.fields[0].name, "key");
1897        assert!(matches!(outer.fields[2].data_type, DataType::Map(_)));
1898    }
1899}
1900
1901// ============================================================================
1902// Simplified Data Type Reference (for CAST expressions)
1903// ============================================================================
1904
1905/// Simplified data type reference used in CAST / `TRY_CAST` / CONVERT expressions.
1906///
1907/// Unlike [`DataType`], which captures full type metadata for DDL generation,
1908/// `DataTypeRef` represents the type names commonly used in SQL expressions.
1909///
1910/// # Purpose
1911///
1912/// `DataTypeRef` is a lightweight, platform-agnostic type reference suitable for
1913/// embedding in SQL expressions such as:
1914///
1915/// ```sql
1916/// SELECT CAST(column_name AS VARCHAR(100)) FROM table
1917/// SELECT TRY_CAST(value AS DECIMAL(19, 4)) FROM source
1918/// SELECT CONVERT(DATETIME2, timestamp_col) FROM events
1919/// ```
1920///
1921/// # Differences from `DataType`
1922///
1923/// | Aspect | `DataType` | `DataTypeRef` |
1924/// |--------|-----------|---------------|
1925/// | **Use case** | DDL generation | CAST / CONVERT expressions |
1926/// | **Metadata** | Full (encoding, precision, storage format) | Minimal (precision, scale, length) |
1927/// | **Platform variants** | Unified (one variant per type) | Separate (`Decimal` vs `Numeric`, `DateTime` vs `DateTime2`) |
1928/// | **Collections** | Array, Map, Struct | Not supported |
1929///
1930/// # Platform-Specific Variants
1931///
1932/// Some types have separate variants for platform-specific naming:
1933///
1934/// - `Decimal` vs `Numeric` — equivalent in SQL, but some dialects prefer one over the other
1935/// - `DateTime` vs `DateTime2` — SQL Server uses `DATETIME2` for precision
1936/// - `Json` vs `Jsonb` — `PostgreSQL` distinguishes between text and binary JSON
1937/// - `Boolean` vs `Bit` — SQL Server uses `BIT` for boolean
1938///
1939/// # Example
1940///
1941/// ```
1942/// use razeri_plugin_contract::DataTypeRef;
1943///
1944/// let varchar = DataTypeRef::Varchar { length: Some(100) };
1945/// let decimal = DataTypeRef::Decimal { precision: 19, scale: 4 };
1946/// let timestamp = DataTypeRef::Timestamp { precision: Some(6) };
1947/// ```
1948#[derive(Debug, Clone, PartialEq)]
1949pub enum DataTypeRef {
1950    // ==========================================================================
1951    // Numeric Types
1952    // ==========================================================================
1953    /// 4-byte signed integer (`INT` / `INTEGER`).
1954    Int,
1955
1956    /// 8-byte signed integer (`BIGINT`).
1957    BigInt,
1958
1959    /// 2-byte signed integer (`SMALLINT`).
1960    SmallInt,
1961
1962    /// 1-byte signed integer (`TINYINT` / `SMALLINT` in `PostgreSQL`).
1963    TinyInt,
1964
1965    /// Single-precision float (`REAL` / `FLOAT4`).
1966    Float,
1967
1968    /// Double-precision float (`DOUBLE PRECISION` / `DOUBLE`).
1969    Double,
1970
1971    /// Single-precision float (`REAL` / `FLOAT4`).
1972    Real,
1973
1974    /// Exact numeric with precision and scale (`DECIMAL(p, s)`).
1975    Decimal {
1976        /// Total number of digits.
1977        precision: u8,
1978        /// Number of digits after the decimal point.
1979        scale: u8,
1980    },
1981
1982    /// Exact numeric with precision and scale (`NUMERIC(p, s)`).
1983    ///
1984    /// Equivalent to `Decimal` in SQL, but some dialects prefer `NUMERIC`
1985    /// over `DECIMAL` for exact numeric types.
1986    Numeric {
1987        /// Total number of digits.
1988        precision: u8,
1989        /// Number of digits after the decimal point.
1990        scale: u8,
1991    },
1992
1993    // ==========================================================================
1994    // String Types
1995    // ==========================================================================
1996    /// Variable-length character string (`VARCHAR(n)`).
1997    Varchar {
1998        /// Maximum length in characters (None = unlimited/MAX).
1999        length: Option<u16>,
2000    },
2001
2002    /// Fixed-length character string (`CHAR(n)`).
2003    Char {
2004        /// Length in characters.
2005        length: u16,
2006    },
2007
2008    /// Unbounded text type (`TEXT`).
2009    Text,
2010
2011    /// Variable-length Unicode string (`NVARCHAR(n)` — SQL Server).
2012    NVarchar {
2013        /// Maximum length in characters (None = unlimited/MAX).
2014        length: Option<u16>,
2015    },
2016
2017    /// Fixed-length Unicode string (`NCHAR(n)` — SQL Server).
2018    NChar {
2019        /// Length in characters.
2020        length: u16,
2021    },
2022
2023    // ==========================================================================
2024    // Binary Types
2025    // ==========================================================================
2026    /// Fixed-length binary data (`BINARY(n)`).
2027    Binary {
2028        /// Length in bytes.
2029        length: Option<u16>,
2030    },
2031
2032    /// Variable-length binary data (`VARBINARY(n)`).
2033    VarBinary {
2034        /// Maximum length in bytes (None = unlimited/MAX).
2035        length: Option<u16>,
2036    },
2037
2038    /// Unbounded binary data (`BLOB` / `BYTEA`).
2039    Blob,
2040
2041    // ==========================================================================
2042    // Date/Time Types
2043    // ==========================================================================
2044    /// Date only (`DATE`).
2045    Date,
2046
2047    /// Time with optional precision (`TIME(p)`).
2048    Time {
2049        /// Fractional seconds precision (None = default).
2050        precision: Option<u8>,
2051    },
2052
2053    /// Combined date and time (`DATETIME` — SQL Server legacy).
2054    DateTime,
2055
2056    /// Combined date and time with precision (`DATETIME2(p)` — SQL Server).
2057    DateTime2 {
2058        /// Fractional seconds precision (None = default).
2059        precision: Option<u8>,
2060    },
2061
2062    /// Timestamp with optional precision (`TIMESTAMP(p)`).
2063    Timestamp {
2064        /// Fractional seconds precision (None = default).
2065        precision: Option<u8>,
2066    },
2067
2068    /// Timestamp with timezone and optional precision (`TIMESTAMPTZ(p)`).
2069    TimestampTz {
2070        /// Fractional seconds precision (None = default).
2071        precision: Option<u8>,
2072    },
2073
2074    /// Interval/duration (`INTERVAL`).
2075    Interval,
2076
2077    // ==========================================================================
2078    // Boolean Types
2079    // ==========================================================================
2080    /// Boolean (`BOOLEAN` — `PostgreSQL`, Snowflake, `ClickHouse`).
2081    Boolean,
2082
2083    /// Bit (`BIT` — SQL Server).
2084    Bit,
2085
2086    // ==========================================================================
2087    // JSON/Semi-structured Types
2088    // ==========================================================================
2089    /// Text-based JSON (`JSON` — `PostgreSQL`).
2090    Json,
2091
2092    /// Binary-optimized JSON (`JSONB` — `PostgreSQL`).
2093    Jsonb,
2094
2095    /// Semi-structured variant (`VARIANT` — Snowflake).
2096    Variant,
2097
2098    // ==========================================================================
2099    // Other Types
2100    // ==========================================================================
2101    /// UUID/GUID (`UUID` / `UNIQUEIDENTIFIER`).
2102    Uuid,
2103
2104    /// XML data (`XML`).
2105    Xml,
2106
2107    /// Custom type name (for types not covered by the enum variants).
2108    Custom(String),
2109}