Skip to main content

drizzle_types/postgres/
sql_type.rs

1//! `PostgreSQL` column type definitions
2//!
3//! Defines the core `PostgreSQL` data types for schema definition.
4
5#[cfg(feature = "serde")]
6use crate::alloc_prelude::String;
7
8/// A `PostgreSQL` column type as written in DDL.
9///
10/// Some variants need a feature: `Uuid` (`uuid`), `Json`/`Jsonb`/`Enum`
11/// (`serde`), `Interval` (`chrono`), network types (`cidr`), geometric types
12/// (`geo-types`) and bit strings (`bit-vec`).
13/// See <https://www.postgresql.org/docs/current/datatype.html>.
14///
15/// # Examples
16///
17/// ```
18/// use drizzle_types::postgres::PostgreSQLType;
19///
20/// let int_type = PostgreSQLType::Integer;
21/// assert_eq!(int_type.to_sql_type(), "INTEGER");
22///
23/// let serial = PostgreSQLType::Serial;
24/// assert!(serial.is_serial());
25/// ```
26#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub enum PostgreSQLType {
29    /// `PostgreSQL` INTEGER type - 32-bit signed integer
30    ///
31    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-INT>
32    Integer,
33
34    /// `PostgreSQL` BIGINT type - 64-bit signed integer
35    ///
36    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-INT>
37    Bigint,
38
39    /// `PostgreSQL` SMALLINT type - 16-bit signed integer
40    ///
41    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-INT>
42    Smallint,
43
44    /// `PostgreSQL` SERIAL type - auto-incrementing 32-bit integer
45    ///
46    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL>
47    Serial,
48
49    /// `PostgreSQL` SMALLSERIAL type - auto-incrementing 16-bit integer
50    ///
51    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL>
52    Smallserial,
53
54    /// `PostgreSQL` BIGSERIAL type - auto-incrementing 64-bit integer
55    ///
56    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-SERIAL>
57    Bigserial,
58
59    /// `PostgreSQL` TEXT type - variable-length character string
60    ///
61    /// See: <https://www.postgresql.org/docs/current/datatype-character.html>
62    #[default]
63    Text,
64
65    /// `PostgreSQL` VARCHAR type - variable-length character string with limit
66    ///
67    /// See: <https://www.postgresql.org/docs/current/datatype-character.html>
68    Varchar,
69
70    /// `PostgreSQL` CHAR type - fixed-length character string
71    ///
72    /// See: <https://www.postgresql.org/docs/current/datatype-character.html>
73    Char,
74
75    /// `PostgreSQL` REAL type - single precision floating-point number
76    ///
77    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-FLOAT>
78    Real,
79
80    /// `PostgreSQL` DOUBLE PRECISION type - double precision floating-point number
81    ///
82    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-FLOAT>
83    DoublePrecision,
84
85    /// `PostgreSQL` NUMERIC type - exact numeric with selectable precision
86    ///
87    /// See: <https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL>
88    Numeric,
89
90    /// `PostgreSQL` BOOLEAN type - true/false
91    ///
92    /// See: <https://www.postgresql.org/docs/current/datatype-boolean.html>
93    Boolean,
94
95    /// `PostgreSQL` BYTEA type - binary data
96    ///
97    /// See: <https://www.postgresql.org/docs/current/datatype-binary.html>
98    Bytea,
99
100    /// `PostgreSQL` UUID type - universally unique identifier
101    ///
102    /// See: <https://www.postgresql.org/docs/current/datatype-uuid.html>
103    #[cfg(feature = "uuid")]
104    Uuid,
105
106    /// `PostgreSQL` JSON type - JSON data
107    ///
108    /// See: <https://www.postgresql.org/docs/current/datatype-json.html>
109    #[cfg(feature = "serde")]
110    Json,
111
112    /// `PostgreSQL` JSONB type - binary JSON data
113    ///
114    /// See: <https://www.postgresql.org/docs/current/datatype-json.html>
115    #[cfg(feature = "serde")]
116    Jsonb,
117
118    /// `PostgreSQL` TIMESTAMP type - date and time
119    ///
120    /// See: <https://www.postgresql.org/docs/current/datatype-datetime.html>
121    Timestamp,
122
123    /// `PostgreSQL` TIMESTAMPTZ type - date and time with time zone
124    ///
125    /// See: <https://www.postgresql.org/docs/current/datatype-datetime.html>
126    Timestamptz,
127
128    /// `PostgreSQL` DATE type - calendar date
129    ///
130    /// See: <https://www.postgresql.org/docs/current/datatype-datetime.html>
131    Date,
132
133    /// `PostgreSQL` TIME type - time of day
134    ///
135    /// See: <https://www.postgresql.org/docs/current/datatype-datetime.html>
136    Time,
137
138    /// `PostgreSQL` TIMETZ type - time of day with time zone
139    ///
140    /// See: <https://www.postgresql.org/docs/current/datatype-datetime.html>
141    Timetz,
142
143    /// `PostgreSQL` INTERVAL type - time interval
144    ///
145    /// See: <https://www.postgresql.org/docs/current/datatype-datetime.html>
146    #[cfg(feature = "chrono")]
147    Interval,
148
149    /// `PostgreSQL` INET type - IPv4 or IPv6 host address
150    ///
151    /// See: <https://www.postgresql.org/docs/current/datatype-net-types.html>
152    #[cfg(feature = "cidr")]
153    Inet,
154
155    /// `PostgreSQL` CIDR type - IPv4 or IPv6 network address
156    ///
157    /// See: <https://www.postgresql.org/docs/current/datatype-net-types.html>
158    #[cfg(feature = "cidr")]
159    Cidr,
160
161    /// `PostgreSQL` MACADDR type - MAC address
162    ///
163    /// See: <https://www.postgresql.org/docs/current/datatype-net-types.html>
164    #[cfg(feature = "cidr")]
165    MacAddr,
166
167    /// `PostgreSQL` MACADDR8 type - EUI-64 MAC address
168    ///
169    /// See: <https://www.postgresql.org/docs/current/datatype-net-types.html>
170    #[cfg(feature = "cidr")]
171    MacAddr8,
172
173    /// `PostgreSQL` POINT type - geometric point
174    ///
175    /// See: <https://www.postgresql.org/docs/current/datatype-geometric.html>
176    #[cfg(feature = "geo-types")]
177    Point,
178
179    /// `PostgreSQL` LINE type - geometric line (infinite)
180    ///
181    /// See: <https://www.postgresql.org/docs/current/datatype-geometric.html>
182    #[cfg(feature = "geo-types")]
183    Line,
184
185    /// `PostgreSQL` LSEG type - geometric line segment
186    ///
187    /// See: <https://www.postgresql.org/docs/current/datatype-geometric.html>
188    #[cfg(feature = "geo-types")]
189    Lseg,
190
191    /// `PostgreSQL` BOX type - geometric box
192    ///
193    /// See: <https://www.postgresql.org/docs/current/datatype-geometric.html>
194    #[cfg(feature = "geo-types")]
195    Box,
196
197    /// `PostgreSQL` PATH type - geometric path
198    ///
199    /// See: <https://www.postgresql.org/docs/current/datatype-geometric.html>
200    #[cfg(feature = "geo-types")]
201    Path,
202
203    /// `PostgreSQL` POLYGON type - geometric polygon
204    ///
205    /// See: <https://www.postgresql.org/docs/current/datatype-geometric.html>
206    #[cfg(feature = "geo-types")]
207    Polygon,
208
209    /// `PostgreSQL` CIRCLE type - geometric circle
210    ///
211    /// See: <https://www.postgresql.org/docs/current/datatype-geometric.html>
212    #[cfg(feature = "geo-types")]
213    Circle,
214
215    /// `PostgreSQL` BIT type - fixed-length bit string
216    ///
217    /// See: <https://www.postgresql.org/docs/current/datatype-bit.html>
218    #[cfg(feature = "bit-vec")]
219    Bit,
220
221    /// `PostgreSQL` BIT VARYING type - variable-length bit string
222    ///
223    /// See: <https://www.postgresql.org/docs/current/datatype-bit.html>
224    #[cfg(feature = "bit-vec")]
225    Varbit,
226
227    /// `PostgreSQL` custom ENUM type - user-defined enumerated type
228    ///
229    /// See: <https://www.postgresql.org/docs/current/datatype-enum.html>
230    #[cfg(feature = "serde")]
231    Enum(String),
232}
233
234impl PostgreSQLType {
235    /// Parses a lowercase type name from a column attribute, such as `"int4"`
236    /// or `"double_precision"`. Returns `None` for unknown names.
237    ///
238    /// Multi-word names use underscores (`"timestamp_with_time_zone"`).
239    /// For native enums, use [`Self::from_enum_attribute`] instead.
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// use drizzle_types::postgres::PostgreSQLType;
245    ///
246    /// assert_eq!(PostgreSQLType::from_attribute_name("int8"), Some(PostgreSQLType::Bigint));
247    /// assert_eq!(PostgreSQLType::from_attribute_name("nope"), None);
248    /// ```
249    #[must_use]
250    pub fn from_attribute_name(name: &str) -> Option<Self> {
251        match name {
252            // Integer types and aliases
253            "integer" | "int" | "int4" => Some(Self::Integer),
254            "bigint" | "int8" => Some(Self::Bigint),
255            "smallint" | "int2" => Some(Self::Smallint),
256            "smallserial" | "serial2" => Some(Self::Smallserial),
257            "serial" | "serial4" => Some(Self::Serial),
258            "bigserial" | "serial8" => Some(Self::Bigserial),
259
260            // Text types and aliases
261            "text" => Some(Self::Text),
262            "varchar" | "character_varying" => Some(Self::Varchar),
263            "char" | "character" => Some(Self::Char),
264
265            // Float types and aliases
266            "real" | "float4" => Some(Self::Real),
267            "double_precision" | "float8" | "double" => Some(Self::DoublePrecision),
268            "numeric" | "decimal" => Some(Self::Numeric),
269
270            // Other basic types
271            "boolean" | "bool" => Some(Self::Boolean),
272            "bytea" => Some(Self::Bytea),
273
274            // UUID
275            #[cfg(feature = "uuid")]
276            "uuid" => Some(Self::Uuid),
277
278            // JSON types
279            #[cfg(feature = "serde")]
280            "json" => Some(Self::Json),
281            #[cfg(feature = "serde")]
282            "jsonb" => Some(Self::Jsonb),
283
284            // Date/time types and aliases
285            "timestamp" | "timestamp_without_time_zone" => Some(Self::Timestamp),
286            "timestamptz" | "timestamp_with_time_zone" => Some(Self::Timestamptz),
287            "date" => Some(Self::Date),
288            "time" | "time_without_time_zone" => Some(Self::Time),
289            "timetz" | "time_with_time_zone" => Some(Self::Timetz),
290            #[cfg(feature = "chrono")]
291            "interval" => Some(Self::Interval),
292
293            // Network address types
294            #[cfg(feature = "cidr")]
295            "inet" => Some(Self::Inet),
296            #[cfg(feature = "cidr")]
297            "cidr" => Some(Self::Cidr),
298            #[cfg(feature = "cidr")]
299            "macaddr" => Some(Self::MacAddr),
300            #[cfg(feature = "cidr")]
301            "macaddr8" => Some(Self::MacAddr8),
302
303            // Geometric types
304            #[cfg(feature = "geo-types")]
305            "point" => Some(Self::Point),
306            #[cfg(feature = "geo-types")]
307            "line" => Some(Self::Line),
308            #[cfg(feature = "geo-types")]
309            "lseg" => Some(Self::Lseg),
310            #[cfg(feature = "geo-types")]
311            "box" => Some(Self::Box),
312            #[cfg(feature = "geo-types")]
313            "path" => Some(Self::Path),
314            #[cfg(feature = "geo-types")]
315            "polygon" => Some(Self::Polygon),
316            #[cfg(feature = "geo-types")]
317            "circle" => Some(Self::Circle),
318
319            // Bit string types
320            #[cfg(feature = "bit-vec")]
321            "bit" => Some(Self::Bit),
322            #[cfg(feature = "bit-vec")]
323            "varbit" | "bit_varying" => Some(Self::Varbit),
324
325            "enum" => None, // enum() requires a parameter, handled separately
326            _ => None,
327        }
328    }
329
330    /// Creates a native enum column type with the given type name.
331    #[cfg(feature = "serde")]
332    #[must_use]
333    pub fn from_enum_attribute(enum_name: &str) -> Self {
334        Self::Enum(String::from(enum_name))
335    }
336
337    /// Returns the type as written in DDL, such as `"DOUBLE PRECISION"`.
338    ///
339    /// For a native enum this is the enum's type name.
340    #[must_use]
341    pub const fn to_sql_type(&self) -> &str {
342        match self {
343            Self::Integer => "INTEGER",
344            Self::Bigint => "BIGINT",
345            Self::Smallint => "SMALLINT",
346            Self::Smallserial => "SMALLSERIAL",
347            Self::Serial => "SERIAL",
348            Self::Bigserial => "BIGSERIAL",
349            Self::Text => "TEXT",
350            Self::Varchar => "VARCHAR",
351            Self::Char => "CHAR",
352            Self::Real => "REAL",
353            Self::DoublePrecision => "DOUBLE PRECISION",
354            Self::Numeric => "NUMERIC",
355            Self::Boolean => "BOOLEAN",
356            Self::Bytea => "BYTEA",
357            #[cfg(feature = "uuid")]
358            Self::Uuid => "UUID",
359            #[cfg(feature = "serde")]
360            Self::Json => "JSON",
361            #[cfg(feature = "serde")]
362            Self::Jsonb => "JSONB",
363            Self::Timestamp => "TIMESTAMP",
364            Self::Timestamptz => "TIMESTAMPTZ",
365            Self::Date => "DATE",
366            Self::Time => "TIME",
367            Self::Timetz => "TIMETZ",
368            #[cfg(feature = "chrono")]
369            Self::Interval => "INTERVAL",
370            #[cfg(feature = "cidr")]
371            Self::Inet => "INET",
372            #[cfg(feature = "cidr")]
373            Self::Cidr => "CIDR",
374            #[cfg(feature = "cidr")]
375            Self::MacAddr => "MACADDR",
376            #[cfg(feature = "cidr")]
377            Self::MacAddr8 => "MACADDR8",
378            #[cfg(feature = "geo-types")]
379            Self::Point => "POINT",
380            #[cfg(feature = "geo-types")]
381            Self::Line => "LINE",
382            #[cfg(feature = "geo-types")]
383            Self::Lseg => "LSEG",
384            #[cfg(feature = "geo-types")]
385            Self::Box => "BOX",
386            #[cfg(feature = "geo-types")]
387            Self::Path => "PATH",
388            #[cfg(feature = "geo-types")]
389            Self::Polygon => "POLYGON",
390            #[cfg(feature = "geo-types")]
391            Self::Circle => "CIRCLE",
392            #[cfg(feature = "bit-vec")]
393            Self::Bit => "BIT",
394            #[cfg(feature = "bit-vec")]
395            Self::Varbit => "VARBIT",
396            #[cfg(feature = "serde")]
397            Self::Enum(name) => name.as_str(), // Custom enum type name
398        }
399    }
400
401    /// Returns `true` for the auto-incrementing types: `SMALLSERIAL`, `SERIAL`, `BIGSERIAL`.
402    #[must_use]
403    pub const fn is_serial(&self) -> bool {
404        matches!(self, Self::Smallserial | Self::Serial | Self::Bigserial)
405    }
406
407    /// Returns `true` if the type can be a primary key; `JSON` and `JSONB` cannot.
408    #[must_use]
409    pub const fn supports_primary_key(&self) -> bool {
410        core::cfg_select! {
411            feature = "serde" => !matches!(self, Self::Json | Self::Jsonb),
412            _ => true,
413        }
414    }
415
416    /// Returns `true` if the column attribute flag (such as `"identity"`,
417    /// `"json"` or `"enum"`) can be used with this type.
418    ///
419    /// `primary`, `primary_key`, `unique`, `not_null` and `check` are valid
420    /// for every type.
421    #[must_use]
422    pub fn is_valid_flag(&self, flag: &str) -> bool {
423        match (self, flag) {
424            (Self::Smallserial | Self::Serial | Self::Bigserial, "identity") => true,
425            (Self::Text | Self::Bytea, "json") => true,
426            #[cfg(feature = "serde")]
427            (Self::Json | Self::Jsonb, "json") => true,
428            (Self::Text | Self::Integer | Self::Smallint | Self::Bigint, "enum") => true,
429            #[cfg(feature = "serde")]
430            (Self::Enum(_), "enum") => true,
431            (_, "primary" | "primary_key" | "unique" | "not_null" | "check") => true,
432            _ => false,
433        }
434    }
435}
436
437impl core::fmt::Display for PostgreSQLType {
438    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
439        f.write_str(self.to_sql_type())
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    #[test]
448    fn test_from_attribute_name() {
449        assert_eq!(
450            PostgreSQLType::from_attribute_name("integer"),
451            Some(PostgreSQLType::Integer)
452        );
453        assert_eq!(
454            PostgreSQLType::from_attribute_name("int"),
455            Some(PostgreSQLType::Integer)
456        );
457        assert_eq!(
458            PostgreSQLType::from_attribute_name("text"),
459            Some(PostgreSQLType::Text)
460        );
461        assert_eq!(
462            PostgreSQLType::from_attribute_name("varchar"),
463            Some(PostgreSQLType::Varchar)
464        );
465        assert_eq!(
466            PostgreSQLType::from_attribute_name("serial"),
467            Some(PostgreSQLType::Serial)
468        );
469        assert_eq!(
470            PostgreSQLType::from_attribute_name("smallserial"),
471            Some(PostgreSQLType::Smallserial)
472        );
473        assert_eq!(PostgreSQLType::from_attribute_name("unknown"), None);
474    }
475
476    #[test]
477    fn test_to_sql_type() {
478        assert_eq!(PostgreSQLType::Integer.to_sql_type(), "INTEGER");
479        assert_eq!(PostgreSQLType::Smallserial.to_sql_type(), "SMALLSERIAL");
480        assert_eq!(PostgreSQLType::Bigint.to_sql_type(), "BIGINT");
481        assert_eq!(PostgreSQLType::Text.to_sql_type(), "TEXT");
482        assert_eq!(
483            PostgreSQLType::DoublePrecision.to_sql_type(),
484            "DOUBLE PRECISION"
485        );
486        assert_eq!(PostgreSQLType::Boolean.to_sql_type(), "BOOLEAN");
487    }
488
489    #[test]
490    fn test_is_serial() {
491        assert!(PostgreSQLType::Smallserial.is_serial());
492        assert!(PostgreSQLType::Serial.is_serial());
493        assert!(PostgreSQLType::Bigserial.is_serial());
494        assert!(!PostgreSQLType::Integer.is_serial());
495        assert!(!PostgreSQLType::Text.is_serial());
496    }
497}