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}