Skip to main content

reinhardt_query/types/
ddl.rs

1//! DDL (Data Definition Language) type definitions
2//!
3//! This module provides types for DDL operations:
4//!
5//! - [`ColumnType`]: SQL column types (INTEGER, VARCHAR, etc.)
6//! - [`ColumnDef`]: Column definition for CREATE TABLE
7//! - [`TableConstraint`]: Table constraints (PRIMARY KEY, FOREIGN KEY, etc.)
8//! - [`IndexDef`]: Index definition
9//! - [`ForeignKeyAction`]: Actions for foreign key constraints
10
11use crate::{
12	expr::SimpleExpr,
13	types::{DynIden, IntoIden, TableRef},
14};
15
16/// SQL column types
17///
18/// This enum represents the various column types supported across
19/// PostgreSQL, MySQL, and SQLite.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum ColumnType {
23	/// CHAR(n) - Fixed-length character string
24	Char(Option<u32>),
25	/// VARCHAR(n) - Variable-length character string
26	String(Option<u32>),
27	/// TEXT - Variable-length text
28	Text,
29	/// TINYINT - Very small integer (1 byte)
30	TinyInteger,
31	/// SMALLINT - Small integer (2 bytes)
32	SmallInteger,
33	/// INTEGER - Standard integer (4 bytes)
34	Integer,
35	/// BIGINT - Large integer (8 bytes)
36	BigInteger,
37	/// FLOAT - Single precision floating point
38	Float,
39	/// DOUBLE - Double precision floating point
40	Double,
41	/// DECIMAL(p, s) - Exact numeric with precision and scale
42	Decimal(Option<(u32, u32)>),
43	/// BOOLEAN - Boolean value
44	Boolean,
45	/// DATE - Date (year, month, day)
46	Date,
47	/// TIME - Time of day
48	Time,
49	/// DATETIME - Date and time (MySQL)
50	DateTime,
51	/// TIMESTAMP - Timestamp with timezone
52	Timestamp,
53	/// TIMESTAMPTZ - Timestamp with timezone (PostgreSQL)
54	TimestampWithTimeZone,
55	/// BINARY(n) - Fixed-length binary data
56	Binary(Option<u32>),
57	/// VARBINARY(n) - Variable-length binary data
58	VarBinary(u32),
59	/// BLOB - Binary large object
60	Blob,
61	/// UUID - Universally unique identifier
62	Uuid,
63	/// JSON - JSON data
64	Json,
65	/// JSONB - Binary JSON (PostgreSQL)
66	JsonBinary,
67	/// ARRAY - Array type (PostgreSQL)
68	Array(Box<ColumnType>),
69	/// Custom type - for database-specific types
70	///
71	/// # Security Note
72	///
73	/// Only use with trusted type names. Do not use with user input.
74	Custom(String),
75}
76
77/// Column definition for CREATE TABLE
78///
79/// This struct represents a column definition, including its type,
80/// constraints, and default value.
81///
82/// # Examples
83///
84/// ```rust,ignore
85/// use reinhardt_query::types::ddl::{ColumnDef, ColumnType};
86///
87/// // id INTEGER PRIMARY KEY AUTO_INCREMENT
88/// let id_col = ColumnDef::new("id")
89///     .column_type(ColumnType::Integer)
90///     .primary_key(true)
91///     .auto_increment(true);
92///
93/// // name VARCHAR(100) NOT NULL
94/// let name_col = ColumnDef::new("name")
95///     .column_type(ColumnType::String(Some(100)))
96///     .not_null(true);
97/// ```
98#[derive(Debug, Clone)]
99pub struct ColumnDef {
100	pub(crate) name: DynIden,
101	pub(crate) column_type: Option<ColumnType>,
102	pub(crate) not_null: bool,
103	pub(crate) unique: bool,
104	pub(crate) primary_key: bool,
105	pub(crate) auto_increment: bool,
106	pub(crate) default: Option<SimpleExpr>,
107	pub(crate) check: Option<SimpleExpr>,
108	pub(crate) comment: Option<String>,
109}
110
111impl ColumnDef {
112	/// Create a new column definition
113	pub fn new<T>(name: T) -> Self
114	where
115		T: IntoIden,
116	{
117		Self {
118			name: name.into_iden(),
119			column_type: None,
120			not_null: false,
121			unique: false,
122			primary_key: false,
123			auto_increment: false,
124			default: None,
125			check: None,
126			comment: None,
127		}
128	}
129
130	/// Set the column type
131	pub fn column_type(mut self, column_type: ColumnType) -> Self {
132		self.column_type = Some(column_type);
133		self
134	}
135
136	/// Set NOT NULL constraint
137	pub fn not_null(mut self, not_null: bool) -> Self {
138		self.not_null = not_null;
139		self
140	}
141
142	/// Set UNIQUE constraint
143	pub fn unique(mut self, unique: bool) -> Self {
144		self.unique = unique;
145		self
146	}
147
148	/// Set PRIMARY KEY constraint
149	pub fn primary_key(mut self, primary_key: bool) -> Self {
150		self.primary_key = primary_key;
151		self
152	}
153
154	/// Set AUTO_INCREMENT attribute
155	pub fn auto_increment(mut self, auto_increment: bool) -> Self {
156		self.auto_increment = auto_increment;
157		self
158	}
159
160	/// Set DEFAULT value
161	pub fn default(mut self, value: SimpleExpr) -> Self {
162		self.default = Some(value);
163		self
164	}
165
166	/// Set CHECK constraint
167	pub fn check(mut self, expr: SimpleExpr) -> Self {
168		self.check = Some(expr);
169		self
170	}
171
172	/// Set column comment
173	pub fn comment<S: Into<String>>(mut self, comment: S) -> Self {
174		self.comment = Some(comment.into());
175		self
176	}
177
178	// Convenience type methods
179
180	/// Set column type to INTEGER
181	pub fn integer(self) -> Self {
182		self.column_type(ColumnType::Integer)
183	}
184
185	/// Set column type to BIGINT
186	pub fn big_integer(self) -> Self {
187		self.column_type(ColumnType::BigInteger)
188	}
189
190	/// Set column type to SMALLINT
191	pub fn small_integer(self) -> Self {
192		self.column_type(ColumnType::SmallInteger)
193	}
194
195	/// Set column type to TINYINT
196	pub fn tiny_integer(self) -> Self {
197		self.column_type(ColumnType::TinyInteger)
198	}
199
200	/// Set column type to VARCHAR (no length limit)
201	pub fn string(self) -> Self {
202		self.column_type(ColumnType::String(None))
203	}
204
205	/// Set column type to VARCHAR(len)
206	pub fn string_len(self, len: u32) -> Self {
207		self.column_type(ColumnType::String(Some(len)))
208	}
209
210	/// Set column type to CHAR (no length limit)
211	pub fn char(self) -> Self {
212		self.column_type(ColumnType::Char(None))
213	}
214
215	/// Set column type to CHAR(len)
216	pub fn char_len(self, len: u32) -> Self {
217		self.column_type(ColumnType::Char(Some(len)))
218	}
219
220	/// Set column type to TEXT
221	pub fn text(self) -> Self {
222		self.column_type(ColumnType::Text)
223	}
224
225	/// Set column type to BOOLEAN
226	pub fn boolean(self) -> Self {
227		self.column_type(ColumnType::Boolean)
228	}
229
230	/// Set column type to FLOAT
231	pub fn float(self) -> Self {
232		self.column_type(ColumnType::Float)
233	}
234
235	/// Set column type to DOUBLE
236	pub fn double(self) -> Self {
237		self.column_type(ColumnType::Double)
238	}
239
240	/// Set column type to DECIMAL(precision, scale)
241	pub fn decimal(self, precision: u32, scale: u32) -> Self {
242		self.column_type(ColumnType::Decimal(Some((precision, scale))))
243	}
244
245	/// Set column type to DATE
246	pub fn date(self) -> Self {
247		self.column_type(ColumnType::Date)
248	}
249
250	/// Set column type to TIME
251	pub fn time(self) -> Self {
252		self.column_type(ColumnType::Time)
253	}
254
255	/// Set column type to DATETIME
256	pub fn date_time(self) -> Self {
257		self.column_type(ColumnType::DateTime)
258	}
259
260	/// Set column type to TIMESTAMP
261	pub fn timestamp(self) -> Self {
262		self.column_type(ColumnType::Timestamp)
263	}
264
265	/// Set column type to TIMESTAMPTZ
266	pub fn timestamp_with_time_zone(self) -> Self {
267		self.column_type(ColumnType::TimestampWithTimeZone)
268	}
269
270	/// Set column type to UUID
271	pub fn uuid(self) -> Self {
272		self.column_type(ColumnType::Uuid)
273	}
274
275	/// Set column type to JSON
276	pub fn json(self) -> Self {
277		self.column_type(ColumnType::Json)
278	}
279
280	/// Set column type to JSONB
281	pub fn json_binary(self) -> Self {
282		self.column_type(ColumnType::JsonBinary)
283	}
284
285	/// Set column type to BLOB
286	pub fn blob(self) -> Self {
287		self.column_type(ColumnType::Blob)
288	}
289
290	/// Set column type to BINARY(len)
291	pub fn binary(self, len: u32) -> Self {
292		self.column_type(ColumnType::Binary(Some(len)))
293	}
294
295	/// Set column type to BINARY(len)
296	///
297	/// Alias for [`binary`](Self::binary) for reinhardt-query compatibility.
298	pub fn binary_len(self, len: u32) -> Self {
299		self.column_type(ColumnType::Binary(Some(len)))
300	}
301
302	/// Set column type to VARBINARY(len)
303	pub fn var_binary(self, len: u32) -> Self {
304		self.column_type(ColumnType::VarBinary(len))
305	}
306
307	/// Set column type to a custom type
308	pub fn custom<S: Into<String>>(self, name: S) -> Self {
309		self.column_type(ColumnType::Custom(name.into()))
310	}
311
312	/// Set column type to ARRAY of given element type
313	pub fn array(self, element_type: ColumnType) -> Self {
314		self.column_type(ColumnType::Array(Box::new(element_type)))
315	}
316}
317
318/// Table constraint
319///
320/// This enum represents various table-level constraints.
321#[derive(Debug, Clone)]
322#[non_exhaustive]
323pub enum TableConstraint {
324	/// PRIMARY KEY constraint
325	PrimaryKey {
326		/// Constraint name
327		name: Option<DynIden>,
328		/// Columns in the primary key
329		columns: Vec<DynIden>,
330	},
331	/// UNIQUE constraint
332	Unique {
333		/// Constraint name
334		name: Option<DynIden>,
335		/// Columns that must be unique
336		columns: Vec<DynIden>,
337	},
338	/// FOREIGN KEY constraint
339	ForeignKey {
340		/// Constraint name
341		name: Option<DynIden>,
342		/// Columns in this table
343		columns: Vec<DynIden>,
344		/// Referenced table
345		ref_table: Box<TableRef>,
346		/// Referenced columns
347		ref_columns: Vec<DynIden>,
348		/// ON DELETE action
349		on_delete: Option<ForeignKeyAction>,
350		/// ON UPDATE action
351		on_update: Option<ForeignKeyAction>,
352	},
353	/// CHECK constraint
354	Check {
355		/// Constraint name
356		name: Option<DynIden>,
357		/// Check expression
358		expr: SimpleExpr,
359	},
360}
361
362/// Foreign key action
363///
364/// This enum represents actions for foreign key constraints.
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366#[non_exhaustive]
367pub enum ForeignKeyAction {
368	/// RESTRICT - Reject the delete/update
369	Restrict,
370	/// CASCADE - Delete/update the referencing rows
371	Cascade,
372	/// SET NULL - Set the foreign key column(s) to NULL
373	SetNull,
374	/// SET DEFAULT - Set the foreign key column(s) to their default values
375	SetDefault,
376	/// NO ACTION - Similar to RESTRICT (default)
377	NoAction,
378}
379
380impl ForeignKeyAction {
381	/// Get the SQL keyword for this action
382	pub fn as_str(&self) -> &'static str {
383		match self {
384			Self::Restrict => "RESTRICT",
385			Self::Cascade => "CASCADE",
386			Self::SetNull => "SET NULL",
387			Self::SetDefault => "SET DEFAULT",
388			Self::NoAction => "NO ACTION",
389		}
390	}
391}
392
393/// Index definition
394///
395/// This struct represents an index definition for CREATE INDEX.
396///
397/// Note: The `name` and `table` fields are defined for future use in CREATE TABLE statements
398/// with inline index definitions, but are not yet used in the current backend implementations.
399/// The dead_code warning is allowed because this is part of the planned API.
400#[derive(Debug, Clone)]
401#[allow(dead_code)]
402pub struct IndexDef {
403	pub(crate) name: DynIden,
404	pub(crate) table: TableRef,
405	pub(crate) columns: Vec<DynIden>,
406	pub(crate) unique: bool,
407	pub(crate) r#where: Option<SimpleExpr>,
408}
409
410impl IndexDef {
411	/// Create a new index definition
412	pub fn new<T, R>(name: T, table: R) -> Self
413	where
414		T: IntoIden,
415		R: Into<TableRef>,
416	{
417		Self {
418			name: name.into_iden(),
419			table: table.into(),
420			columns: Vec::new(),
421			unique: false,
422			r#where: None,
423		}
424	}
425
426	/// Add a column to the index
427	pub fn column<C>(mut self, col: C) -> Self
428	where
429		C: IntoIden,
430	{
431		self.columns.push(col.into_iden());
432		self
433	}
434
435	/// Add multiple columns to the index
436	pub fn columns<I, C>(mut self, cols: I) -> Self
437	where
438		I: IntoIterator<Item = C>,
439		C: IntoIden,
440	{
441		for col in cols {
442			self.columns.push(col.into_iden());
443		}
444		self
445	}
446
447	/// Set UNIQUE attribute
448	pub fn unique(mut self, unique: bool) -> Self {
449		self.unique = unique;
450		self
451	}
452
453	/// Set WHERE clause for partial index
454	pub fn r#where(mut self, expr: SimpleExpr) -> Self {
455		self.r#where = Some(expr);
456		self
457	}
458}
459
460#[cfg(test)]
461mod tests {
462	use super::*;
463	use rstest::rstest;
464
465	#[rstest]
466	fn test_column_def_integer() {
467		// Arrange & Act
468		let col = ColumnDef::new("age").integer().not_null(true);
469
470		// Assert
471		assert_eq!(col.column_type, Some(ColumnType::Integer));
472		assert!(col.not_null);
473	}
474
475	#[rstest]
476	fn test_column_def_string_len() {
477		// Arrange & Act
478		let col = ColumnDef::new("name").string_len(100);
479
480		// Assert
481		assert_eq!(col.column_type, Some(ColumnType::String(Some(100))));
482	}
483
484	#[rstest]
485	fn test_column_def_text() {
486		// Arrange & Act
487		let col = ColumnDef::new("bio").text();
488
489		// Assert
490		assert_eq!(col.column_type, Some(ColumnType::Text));
491	}
492
493	#[rstest]
494	fn test_column_def_boolean() {
495		// Arrange & Act
496		let col = ColumnDef::new("active").boolean();
497
498		// Assert
499		assert_eq!(col.column_type, Some(ColumnType::Boolean));
500	}
501
502	#[rstest]
503	fn test_column_def_timestamp() {
504		// Arrange & Act
505		let col = ColumnDef::new("created_at").timestamp();
506
507		// Assert
508		assert_eq!(col.column_type, Some(ColumnType::Timestamp));
509	}
510
511	#[rstest]
512	fn test_column_def_uuid() {
513		// Arrange & Act
514		let col = ColumnDef::new("id").uuid().primary_key(true);
515
516		// Assert
517		assert_eq!(col.column_type, Some(ColumnType::Uuid));
518		assert!(col.primary_key);
519	}
520
521	#[rstest]
522	fn test_column_def_chaining() {
523		// Arrange & Act
524		let col = ColumnDef::new("email")
525			.string_len(255)
526			.not_null(true)
527			.unique(true);
528
529		// Assert
530		assert_eq!(col.column_type, Some(ColumnType::String(Some(255))));
531		assert!(col.not_null);
532		assert!(col.unique);
533	}
534
535	#[rstest]
536	fn test_column_def_json_binary() {
537		// Arrange & Act
538		let col = ColumnDef::new("data").json_binary();
539
540		// Assert
541		assert_eq!(col.column_type, Some(ColumnType::JsonBinary));
542	}
543
544	#[rstest]
545	fn test_column_def_decimal() {
546		// Arrange & Act
547		let col = ColumnDef::new("price").decimal(10, 2);
548
549		// Assert
550		assert_eq!(col.column_type, Some(ColumnType::Decimal(Some((10, 2)))));
551	}
552
553	#[rstest]
554	fn test_column_def_custom() {
555		// Arrange & Act
556		let col = ColumnDef::new("data").custom("CITEXT");
557
558		// Assert
559		assert_eq!(
560			col.column_type,
561			Some(ColumnType::Custom("CITEXT".to_string()))
562		);
563	}
564}