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