Skip to main content

reinhardt_db/migrations/
fields.rs

1//! Field type definitions for migrations
2
3use serde::{Deserialize, Serialize};
4
5/// Represents database field types
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum FieldType {
8	// Integer types
9	/// BigInteger variant.
10	BigInteger,
11	/// Integer variant.
12	Integer,
13	/// SmallInteger variant.
14	SmallInteger,
15	/// TinyInt variant.
16	TinyInt, // MySQL-specific
17	/// MediumInt variant.
18	MediumInt, // MySQL-specific
19
20	// String types (with parameters)
21	/// Char variant.
22	Char(u32),
23	/// VarChar variant.
24	VarChar(u32),
25	/// Text variant.
26	Text,
27	/// TinyText variant.
28	TinyText, // MySQL-specific
29	/// MediumText variant.
30	MediumText, // MySQL-specific
31	/// LongText variant.
32	LongText, // MySQL-specific
33
34	// Date/time types
35	/// Date variant.
36	Date,
37	/// Time variant.
38	Time,
39	/// DateTime variant.
40	DateTime,
41	/// TimestampTz variant.
42	TimestampTz, // PostgreSQL TIMESTAMPTZ
43
44	// Numeric types
45	/// Decimal variant.
46	Decimal {
47		/// The precision.
48		precision: u32,
49		/// The scale.
50		scale: u32,
51	},
52	/// Float variant.
53	Float,
54	/// Double variant.
55	Double,
56	/// Real variant.
57	Real,
58
59	// Boolean type
60	/// Boolean variant.
61	Boolean,
62
63	// Binary types
64	/// Binary variant.
65	Binary,
66	/// Blob variant.
67	Blob, // MySQL-specific
68	/// TinyBlob variant.
69	TinyBlob, // MySQL-specific
70	/// MediumBlob variant.
71	MediumBlob, // MySQL-specific
72	/// LongBlob variant.
73	LongBlob, // MySQL-specific
74	/// Bytea variant.
75	Bytea, // PostgreSQL-specific
76
77	// JSON types
78	/// Json variant.
79	Json,
80	/// JsonBinary variant.
81	JsonBinary, // PostgreSQL JSONB
82
83	// PostgreSQL-specific types
84	/// PostgreSQL Array type with inner element type
85	Array(Box<FieldType>),
86	/// PostgreSQL HStore key-value store
87	HStore,
88	/// PostgreSQL case-insensitive text
89	CIText,
90	/// PostgreSQL int4range (integer range)
91	Int4Range,
92	/// PostgreSQL int8range (bigint range)
93	Int8Range,
94	/// PostgreSQL numrange (numeric range)
95	NumRange,
96	/// PostgreSQL daterange
97	DateRange,
98	/// PostgreSQL tsrange (timestamp range without timezone)
99	TsRange,
100	/// PostgreSQL tstzrange (timestamp range with timezone)
101	TsTzRange,
102	/// PostgreSQL tsvector for full-text search
103	TsVector,
104	/// PostgreSQL tsquery for full-text search queries
105	TsQuery,
106
107	// Other types
108	/// Uuid variant.
109	Uuid,
110	/// Year variant.
111	Year, // MySQL-specific
112
113	// MySQL-specific collection types
114	/// Enum variant.
115	Enum {
116		/// The values.
117		values: Vec<String>,
118	},
119	/// Set variant.
120	Set {
121		/// The values.
122		values: Vec<String>,
123	},
124
125	// Relationship field types
126	/// ForeignKey relationship field
127	ForeignKey {
128		/// The to table.
129		to_table: String,
130		/// The to field.
131		to_field: String,
132		/// The on delete.
133		on_delete: super::ForeignKeyAction,
134	},
135
136	/// OneToOne relationship field
137	OneToOne {
138		/// The to.
139		to: String, // "app.Model" format
140		/// The on delete.
141		on_delete: super::ForeignKeyAction,
142		/// The on update.
143		on_update: super::ForeignKeyAction,
144	},
145
146	/// ManyToMany relationship field
147	ManyToMany {
148		/// The to.
149		to: String, // "app.Model" format
150		/// The through.
151		through: Option<String>, // Intermediate table name (None for auto-generation)
152	},
153
154	// Custom types
155	/// Custom variant.
156	Custom(String),
157}
158
159impl FieldType {
160	/// Convert FieldType to SQL string for a specific dialect
161	///
162	/// This method returns database-specific SQL types.
163	/// Use this method when generating SQL for a specific database.
164	pub fn to_sql_for_dialect(&self, dialect: &super::operations::SqlDialect) -> String {
165		use super::operations::SqlDialect;
166
167		match self {
168			FieldType::DateTime => match dialect {
169				SqlDialect::Postgres | SqlDialect::Cockroachdb => "TIMESTAMP".to_string(),
170				SqlDialect::Mysql | SqlDialect::Sqlite => "DATETIME".to_string(),
171			},
172			FieldType::TimestampTz => match dialect {
173				SqlDialect::Postgres | SqlDialect::Cockroachdb => "TIMESTAMPTZ".to_string(),
174				SqlDialect::Mysql => "DATETIME".to_string(), // MySQL doesn't have TIMESTAMPTZ
175				SqlDialect::Sqlite => "DATETIME".to_string(), // SQLite doesn't have TIMESTAMPTZ
176			},
177			// Use "BOOLEAN" for SQLite instead of "INTEGER" to ensure sqlx's
178			// type_info().name() returns "BOOLEAN". This allows our convert_row
179			// function to properly detect boolean columns and convert integer 0/1
180			// values to bool. SQLite will still store values as integers due to
181			// type affinity, but the declared type name will be "BOOLEAN".
182			FieldType::Boolean => match dialect {
183				SqlDialect::Postgres | SqlDialect::Cockroachdb => "BOOLEAN".to_string(),
184				SqlDialect::Mysql => "TINYINT(1)".to_string(), // MySQL uses TINYINT for boolean
185				SqlDialect::Sqlite => "BOOLEAN".to_string(),   // SQLite - use BOOLEAN for type detection
186			},
187			FieldType::Uuid => match dialect {
188				SqlDialect::Postgres | SqlDialect::Cockroachdb => "UUID".to_string(),
189				SqlDialect::Mysql => "CHAR(36)".to_string(), // MySQL doesn't have native UUID
190				SqlDialect::Sqlite => "TEXT".to_string(),    // SQLite doesn't have native UUID
191			},
192			FieldType::JsonBinary => match dialect {
193				SqlDialect::Postgres | SqlDialect::Cockroachdb => "JSONB".to_string(),
194				SqlDialect::Mysql | SqlDialect::Sqlite => "JSON".to_string(), // Fallback to JSON
195			},
196			// PostgreSQL-specific types with dialect handling
197			FieldType::Array(inner) => match dialect {
198				SqlDialect::Postgres | SqlDialect::Cockroachdb => {
199					format!("{}[]", inner.to_sql_for_dialect(dialect))
200				}
201				// MySQL and SQLite don't support native arrays, fallback to JSON
202				SqlDialect::Mysql | SqlDialect::Sqlite => "JSON".to_string(),
203			},
204			FieldType::HStore => match dialect {
205				SqlDialect::Postgres | SqlDialect::Cockroachdb => "HSTORE".to_string(),
206				// Fallback to JSON for other databases
207				SqlDialect::Mysql | SqlDialect::Sqlite => "JSON".to_string(),
208			},
209			FieldType::CIText => match dialect {
210				SqlDialect::Postgres | SqlDialect::Cockroachdb => "CITEXT".to_string(),
211				// Fallback to TEXT for other databases
212				SqlDialect::Mysql | SqlDialect::Sqlite => "TEXT".to_string(),
213			},
214			FieldType::Int4Range => match dialect {
215				SqlDialect::Postgres | SqlDialect::Cockroachdb => "INT4RANGE".to_string(),
216				// No native range support in other databases
217				SqlDialect::Mysql | SqlDialect::Sqlite => "VARCHAR(50)".to_string(),
218			},
219			FieldType::Int8Range => match dialect {
220				SqlDialect::Postgres | SqlDialect::Cockroachdb => "INT8RANGE".to_string(),
221				SqlDialect::Mysql | SqlDialect::Sqlite => "VARCHAR(50)".to_string(),
222			},
223			FieldType::NumRange => match dialect {
224				SqlDialect::Postgres | SqlDialect::Cockroachdb => "NUMRANGE".to_string(),
225				SqlDialect::Mysql | SqlDialect::Sqlite => "VARCHAR(100)".to_string(),
226			},
227			FieldType::DateRange => match dialect {
228				SqlDialect::Postgres | SqlDialect::Cockroachdb => "DATERANGE".to_string(),
229				SqlDialect::Mysql | SqlDialect::Sqlite => "VARCHAR(50)".to_string(),
230			},
231			FieldType::TsRange => match dialect {
232				SqlDialect::Postgres | SqlDialect::Cockroachdb => "TSRANGE".to_string(),
233				SqlDialect::Mysql | SqlDialect::Sqlite => "VARCHAR(100)".to_string(),
234			},
235			FieldType::TsTzRange => match dialect {
236				SqlDialect::Postgres | SqlDialect::Cockroachdb => "TSTZRANGE".to_string(),
237				SqlDialect::Mysql | SqlDialect::Sqlite => "VARCHAR(100)".to_string(),
238			},
239			FieldType::TsVector => match dialect {
240				SqlDialect::Postgres | SqlDialect::Cockroachdb => "TSVECTOR".to_string(),
241				// No native full-text search vector in other databases
242				SqlDialect::Mysql | SqlDialect::Sqlite => "TEXT".to_string(),
243			},
244			FieldType::TsQuery => match dialect {
245				SqlDialect::Postgres | SqlDialect::Cockroachdb => "TSQUERY".to_string(),
246				SqlDialect::Mysql | SqlDialect::Sqlite => "TEXT".to_string(),
247			},
248			// SQLite requires INTEGER (not BIGINT) for AUTOINCREMENT support.
249			// Only INTEGER PRIMARY KEY columns can use AUTOINCREMENT in SQLite.
250			FieldType::BigInteger => match dialect {
251				SqlDialect::Sqlite => "INTEGER".to_string(),
252				_ => self.to_sql_string(),
253			},
254			FieldType::Float => match dialect {
255				SqlDialect::Postgres | SqlDialect::Cockroachdb => "REAL".to_string(),
256				_ => self.to_sql_string(),
257			},
258			FieldType::Double => match dialect {
259				SqlDialect::Postgres | SqlDialect::Cockroachdb => "DOUBLE PRECISION".to_string(),
260				_ => self.to_sql_string(),
261			},
262			// For all other types, use the generic SQL type
263			_ => self.to_sql_string(),
264		}
265	}
266
267	/// Convert FieldType to SQL string
268	///
269	/// This method returns generic SQL types that may not be compatible with all databases.
270	/// For database-specific SQL generation, use `to_sql_for_dialect()` instead.
271	pub fn to_sql_string(&self) -> String {
272		match self {
273			FieldType::BigInteger => "BIGINT".to_string(),
274			FieldType::Integer => "INTEGER".to_string(),
275			FieldType::SmallInteger => "SMALLINT".to_string(),
276			FieldType::TinyInt => "TINYINT".to_string(),
277			FieldType::MediumInt => "MEDIUMINT".to_string(),
278			FieldType::Char(max_length) => format!("CHAR({})", max_length),
279			FieldType::VarChar(max_length) => format!("VARCHAR({})", max_length),
280			FieldType::Text => "TEXT".to_string(),
281			FieldType::TinyText => "TINYTEXT".to_string(),
282			FieldType::MediumText => "MEDIUMTEXT".to_string(),
283			FieldType::LongText => "LONGTEXT".to_string(),
284			FieldType::Date => "DATE".to_string(),
285			FieldType::Time => "TIME".to_string(),
286			FieldType::DateTime => "DATETIME".to_string(),
287			FieldType::TimestampTz => "TIMESTAMPTZ".to_string(),
288			FieldType::Decimal { precision, scale } => format!("DECIMAL({}, {})", precision, scale),
289			FieldType::Float => "FLOAT".to_string(),
290			FieldType::Double => "DOUBLE".to_string(),
291			FieldType::Real => "REAL".to_string(),
292			FieldType::Boolean => "BOOLEAN".to_string(),
293			FieldType::Binary => "BINARY".to_string(),
294			FieldType::Blob => "BLOB".to_string(),
295			FieldType::TinyBlob => "TINYBLOB".to_string(),
296			FieldType::MediumBlob => "MEDIUMBLOB".to_string(),
297			FieldType::LongBlob => "LONGBLOB".to_string(),
298			FieldType::Bytea => "BYTEA".to_string(),
299			FieldType::Json => "JSON".to_string(),
300			FieldType::JsonBinary => "JSONB".to_string(),
301			// PostgreSQL-specific types
302			FieldType::Array(inner) => format!("{}[]", inner.to_sql_string()),
303			FieldType::HStore => "HSTORE".to_string(),
304			FieldType::CIText => "CITEXT".to_string(),
305			FieldType::Int4Range => "INT4RANGE".to_string(),
306			FieldType::Int8Range => "INT8RANGE".to_string(),
307			FieldType::NumRange => "NUMRANGE".to_string(),
308			FieldType::DateRange => "DATERANGE".to_string(),
309			FieldType::TsRange => "TSRANGE".to_string(),
310			FieldType::TsTzRange => "TSTZRANGE".to_string(),
311			FieldType::TsVector => "TSVECTOR".to_string(),
312			FieldType::TsQuery => "TSQUERY".to_string(),
313			FieldType::Uuid => "UUID".to_string(),
314			FieldType::Year => "YEAR".to_string(),
315			FieldType::Enum { values } => {
316				let values_str = values
317					.iter()
318					.map(|v| format!("'{}'", v))
319					.collect::<Vec<_>>()
320					.join(",");
321				format!("ENUM({})", values_str)
322			}
323			FieldType::Set { values } => {
324				let values_str = values
325					.iter()
326					.map(|v| format!("'{}'", v))
327					.collect::<Vec<_>>()
328					.join(",");
329				format!("SET({})", values_str)
330			}
331			FieldType::ForeignKey { to_table, .. } => {
332				format!("-- ForeignKey to {}", to_table)
333			}
334			FieldType::OneToOne { to, .. } => {
335				format!("-- OneToOne relationship to {}", to)
336			}
337			FieldType::ManyToMany { to, through } => match through {
338				Some(table) => format!("-- ManyToMany through {}", table),
339				None => format!("-- ManyToMany to {} (auto-generated)", to),
340			},
341			FieldType::Custom(custom_type) => custom_type.clone(),
342		}
343	}
344
345	/// Get max_length if this type has one
346	pub fn max_length(&self) -> Option<u32> {
347		match self {
348			FieldType::Char(max_length) | FieldType::VarChar(max_length) => Some(*max_length),
349			_ => None,
350		}
351	}
352}
353
354/// Trait for field types that provide their type name as a compile-time constant
355pub trait FieldTypeName {
356	/// The name constant.
357	const NAME: &'static str;
358}
359
360// Type-safe field type markers
361/// Represents a big integer field.
362pub struct BigIntegerField;
363impl FieldTypeName for BigIntegerField {
364	const NAME: &'static str = "BigIntegerField";
365}
366
367/// Represents a integer field.
368pub struct IntegerField;
369impl FieldTypeName for IntegerField {
370	const NAME: &'static str = "IntegerField";
371}
372
373/// Represents a small integer field.
374pub struct SmallIntegerField;
375impl FieldTypeName for SmallIntegerField {
376	const NAME: &'static str = "SmallIntegerField";
377}
378
379/// Represents a char field.
380pub struct CharField;
381impl FieldTypeName for CharField {
382	const NAME: &'static str = "CharField";
383}
384
385/// Represents a text field.
386pub struct TextField;
387impl FieldTypeName for TextField {
388	const NAME: &'static str = "TextField";
389}
390
391/// Represents a date time field.
392pub struct DateTimeField;
393impl FieldTypeName for DateTimeField {
394	const NAME: &'static str = "DateTimeField";
395}
396
397/// Represents a date field.
398pub struct DateField;
399impl FieldTypeName for DateField {
400	const NAME: &'static str = "DateField";
401}
402
403/// Represents a time field.
404pub struct TimeField;
405impl FieldTypeName for TimeField {
406	const NAME: &'static str = "TimeField";
407}
408
409/// Represents a boolean field.
410pub struct BooleanField;
411impl FieldTypeName for BooleanField {
412	const NAME: &'static str = "BooleanField";
413}
414
415/// Represents a decimal field.
416pub struct DecimalField;
417impl FieldTypeName for DecimalField {
418	const NAME: &'static str = "DecimalField";
419}
420
421/// Represents a binary field.
422pub struct BinaryField;
423impl FieldTypeName for BinaryField {
424	const NAME: &'static str = "BinaryField";
425}
426
427/// Represents a jsonfield.
428pub struct JSONField;
429impl FieldTypeName for JSONField {
430	const NAME: &'static str = "JSONField";
431}
432
433/// Represents a uuidfield.
434pub struct UUIDField;
435impl FieldTypeName for UUIDField {
436	const NAME: &'static str = "UUIDField";
437}
438
439// PostgreSQL-specific field type markers
440/// Represents a array field.
441pub struct ArrayField;
442impl FieldTypeName for ArrayField {
443	const NAME: &'static str = "ArrayField";
444}
445
446/// Represents a hstore field.
447pub struct HStoreField;
448impl FieldTypeName for HStoreField {
449	const NAME: &'static str = "HStoreField";
450}
451
452/// Represents a citext field.
453pub struct CITextField;
454impl FieldTypeName for CITextField {
455	const NAME: &'static str = "CITextField";
456}
457
458/// Represents a int4range field.
459pub struct Int4RangeField;
460impl FieldTypeName for Int4RangeField {
461	const NAME: &'static str = "Int4RangeField";
462}
463
464/// Represents a int8range field.
465pub struct Int8RangeField;
466impl FieldTypeName for Int8RangeField {
467	const NAME: &'static str = "Int8RangeField";
468}
469
470/// Represents a num range field.
471pub struct NumRangeField;
472impl FieldTypeName for NumRangeField {
473	const NAME: &'static str = "NumRangeField";
474}
475
476/// Represents a date range field.
477pub struct DateRangeField;
478impl FieldTypeName for DateRangeField {
479	const NAME: &'static str = "DateRangeField";
480}
481
482/// Represents a ts range field.
483pub struct TsRangeField;
484impl FieldTypeName for TsRangeField {
485	const NAME: &'static str = "TsRangeField";
486}
487
488/// Represents a ts tz range field.
489pub struct TsTzRangeField;
490impl FieldTypeName for TsTzRangeField {
491	const NAME: &'static str = "TsTzRangeField";
492}
493
494/// Represents a ts vector field.
495pub struct TsVectorField;
496impl FieldTypeName for TsVectorField {
497	const NAME: &'static str = "TsVectorField";
498}
499
500/// Represents a ts query field.
501pub struct TsQueryField;
502impl FieldTypeName for TsQueryField {
503	const NAME: &'static str = "TsQueryField";
504}
505
506/// Prelude module.
507pub mod prelude {
508	pub use super::{
509		// PostgreSQL-specific field types
510		ArrayField,
511		// Standard field types
512		BigIntegerField,
513		BinaryField,
514		BooleanField,
515		CITextField,
516		CharField,
517		DateField,
518		DateRangeField,
519		DateTimeField,
520		DecimalField,
521		FieldTypeName,
522		HStoreField,
523		Int4RangeField,
524		Int8RangeField,
525		IntegerField,
526		JSONField,
527		NumRangeField,
528		SmallIntegerField,
529		TextField,
530		TimeField,
531		TsQueryField,
532		TsRangeField,
533		TsTzRangeField,
534		TsVectorField,
535		UUIDField,
536	};
537}
538
539/// Display implementation for FieldType
540impl std::fmt::Display for FieldType {
541	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542		write!(f, "{}", self.to_sql_string())
543	}
544}