Skip to main content

reinhardt_db/orm/
postgres_fields.rs

1//! PostgreSQL-specific field types
2//!
3//! This module provides PostgreSQL-specific field types inspired by Django's
4//! `django/contrib/postgres/fields/`.
5//!
6//! # Available Field Types
7//!
8//! - **ArrayField**: Store arrays of values
9//! - **JSONBField**: Store JSON data efficiently with indexing support
10//! - **HStoreField**: Store key-value pairs
11//! - **RangeFields**: Integer, Date, DateTime ranges
12//! - **CITextField**: Case-insensitive text field
13//!
14//! # Example
15//!
16//! ```rust
17//! use reinhardt_db::orm::{ArrayField, JSONBField};
18//!
19//! // Array field storing tags
20//! let tags_field = ArrayField::<String>::new("VARCHAR(50)");
21//! assert_eq!(tags_field.base_type(), "VARCHAR(50)");
22//!
23//! // JSONB field for metadata
24//! let metadata_field = JSONBField::new();
25//! assert_eq!(metadata_field.sql_type(), "JSONB");
26//! ```
27
28use chrono::{NaiveDate, NaiveDateTime};
29use serde::{Deserialize, Serialize};
30use std::marker::PhantomData;
31
32/// PostgreSQL Array field
33///
34/// Stores an array of values of a specific type.
35///
36/// # Example
37///
38/// ```rust
39/// use reinhardt_db::orm::ArrayField;
40///
41/// // Array of integers
42/// let scores = ArrayField::<i32>::new("INTEGER");
43/// assert_eq!(scores.base_type(), "INTEGER");
44///
45/// // Array of strings with max length
46/// let tags = ArrayField::<String>::new("VARCHAR(50)");
47/// assert_eq!(tags.base_type(), "VARCHAR(50)");
48/// ```
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ArrayField<T> {
51	base_type: String,
52	size: Option<usize>,
53	default: Option<Vec<T>>,
54	_phantom: PhantomData<T>,
55}
56
57impl<T> ArrayField<T> {
58	/// Create a new ArrayField
59	///
60	/// # Example
61	///
62	/// ```rust
63	/// use reinhardt_db::orm::ArrayField;
64	///
65	/// let field = ArrayField::<i32>::new("INTEGER");
66	/// assert_eq!(field.base_type(), "INTEGER");
67	/// ```
68	pub fn new(base_type: impl Into<String>) -> Self {
69		Self {
70			base_type: base_type.into(),
71			size: None,
72			default: None,
73			_phantom: PhantomData,
74		}
75	}
76
77	/// Set a fixed size for the array
78	pub fn with_size(mut self, size: usize) -> Self {
79		self.size = Some(size);
80		self
81	}
82
83	/// Set a default value
84	pub fn with_default(mut self, default: Vec<T>) -> Self {
85		self.default = Some(default);
86		self
87	}
88
89	/// Get the base type
90	pub fn base_type(&self) -> &str {
91		&self.base_type
92	}
93
94	/// Get the size constraint if set
95	pub fn size(&self) -> Option<usize> {
96		self.size
97	}
98
99	/// Generate SQL type definition
100	///
101	/// # Example
102	///
103	/// ```rust
104	/// use reinhardt_db::orm::ArrayField;
105	///
106	/// let field = ArrayField::<i32>::new("INTEGER");
107	/// assert_eq!(field.sql_type(), "INTEGER[]");
108	///
109	/// let sized_field = ArrayField::<i32>::new("INTEGER").with_size(10);
110	/// assert_eq!(sized_field.sql_type(), "INTEGER[10]");
111	/// ```
112	pub fn sql_type(&self) -> String {
113		if let Some(size) = self.size {
114			format!("{}[{}]", self.base_type, size)
115		} else {
116			format!("{}[]", self.base_type)
117		}
118	}
119}
120
121/// PostgreSQL JSONB field
122///
123/// Stores JSON data in binary format with indexing support.
124/// More efficient than JSON type for querying.
125///
126/// # Example
127///
128/// ```rust
129/// use reinhardt_db::orm::JSONBField;
130///
131/// let metadata = JSONBField::new();
132/// assert_eq!(metadata.sql_type(), "JSONB");
133/// ```
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct JSONBField {
136	default: Option<serde_json::Value>,
137}
138
139impl JSONBField {
140	/// Create a new JSONBField
141	///
142	/// # Example
143	///
144	/// ```rust
145	/// use reinhardt_db::orm::JSONBField;
146	///
147	/// let field = JSONBField::new();
148	/// assert_eq!(field.sql_type(), "JSONB");
149	/// ```
150	pub fn new() -> Self {
151		Self { default: None }
152	}
153
154	/// Set a default JSON value
155	pub fn with_default(mut self, default: serde_json::Value) -> Self {
156		self.default = Some(default);
157		self
158	}
159
160	/// Generate SQL type definition
161	pub fn sql_type(&self) -> &'static str {
162		"JSONB"
163	}
164
165	/// Get default value
166	pub fn default(&self) -> Option<&serde_json::Value> {
167		self.default.as_ref()
168	}
169}
170
171impl Default for JSONBField {
172	fn default() -> Self {
173		Self::new()
174	}
175}
176
177/// PostgreSQL HStore field
178///
179/// Stores key-value pairs. Requires the `hstore` extension.
180///
181/// # Example
182///
183/// ```rust
184/// use reinhardt_db::orm::HStoreField;
185///
186/// let attributes = HStoreField::new();
187/// assert_eq!(attributes.sql_type(), "HSTORE");
188/// ```
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct HStoreField {
191	default: Option<std::collections::HashMap<String, String>>,
192}
193
194impl HStoreField {
195	/// Create a new HStoreField
196	///
197	/// # Example
198	///
199	/// ```rust
200	/// use reinhardt_db::orm::HStoreField;
201	///
202	/// let field = HStoreField::new();
203	/// assert_eq!(field.sql_type(), "HSTORE");
204	/// ```
205	pub fn new() -> Self {
206		Self { default: None }
207	}
208
209	/// Set a default value
210	pub fn with_default(mut self, default: std::collections::HashMap<String, String>) -> Self {
211		self.default = Some(default);
212		self
213	}
214
215	/// Generate SQL type definition
216	pub fn sql_type(&self) -> &'static str {
217		"HSTORE"
218	}
219}
220
221impl Default for HStoreField {
222	fn default() -> Self {
223		Self::new()
224	}
225}
226
227/// PostgreSQL Integer Range field
228///
229/// Stores a range of integers (INT4RANGE).
230///
231/// # Example
232///
233/// ```rust
234/// use reinhardt_db::orm::IntegerRangeField;
235///
236/// let age_range = IntegerRangeField::new();
237/// assert_eq!(age_range.sql_type(), "INT4RANGE");
238/// ```
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct IntegerRangeField {
241	default: Option<(Option<i32>, Option<i32>)>,
242}
243
244impl IntegerRangeField {
245	/// Create a new IntegerRangeField
246	pub fn new() -> Self {
247		Self { default: None }
248	}
249
250	/// Set default range (lower, upper)
251	pub fn with_default(mut self, lower: Option<i32>, upper: Option<i32>) -> Self {
252		self.default = Some((lower, upper));
253		self
254	}
255
256	/// Generate SQL type definition
257	pub fn sql_type(&self) -> &'static str {
258		"INT4RANGE"
259	}
260}
261
262impl Default for IntegerRangeField {
263	fn default() -> Self {
264		Self::new()
265	}
266}
267
268/// PostgreSQL BigInteger Range field
269///
270/// Stores a range of big integers (INT8RANGE).
271///
272/// # Example
273///
274/// ```rust
275/// use reinhardt_db::orm::BigIntegerRangeField;
276///
277/// let range = BigIntegerRangeField::new();
278/// assert_eq!(range.sql_type(), "INT8RANGE");
279/// ```
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct BigIntegerRangeField {
282	default: Option<(Option<i64>, Option<i64>)>,
283}
284
285impl BigIntegerRangeField {
286	/// Creates a new instance.
287	pub fn new() -> Self {
288		Self { default: None }
289	}
290
291	/// Sets the default and returns self for chaining.
292	pub fn with_default(mut self, lower: Option<i64>, upper: Option<i64>) -> Self {
293		self.default = Some((lower, upper));
294		self
295	}
296
297	/// Performs the sql type operation.
298	pub fn sql_type(&self) -> &'static str {
299		"INT8RANGE"
300	}
301}
302
303impl Default for BigIntegerRangeField {
304	fn default() -> Self {
305		Self::new()
306	}
307}
308
309/// PostgreSQL Date Range field
310///
311/// Stores a range of dates (DATERANGE).
312///
313/// # Example
314///
315/// ```rust
316/// use reinhardt_db::orm::DateRangeField;
317///
318/// let period = DateRangeField::new();
319/// assert_eq!(period.sql_type(), "DATERANGE");
320/// ```
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct DateRangeField {
323	default: Option<(Option<NaiveDate>, Option<NaiveDate>)>,
324}
325
326impl DateRangeField {
327	/// Creates a new instance.
328	pub fn new() -> Self {
329		Self { default: None }
330	}
331
332	/// Sets the default and returns self for chaining.
333	pub fn with_default(mut self, lower: Option<NaiveDate>, upper: Option<NaiveDate>) -> Self {
334		self.default = Some((lower, upper));
335		self
336	}
337
338	/// Performs the sql type operation.
339	pub fn sql_type(&self) -> &'static str {
340		"DATERANGE"
341	}
342}
343
344impl Default for DateRangeField {
345	fn default() -> Self {
346		Self::new()
347	}
348}
349
350/// PostgreSQL DateTime Range field
351///
352/// Stores a range of timestamps (TSTZRANGE).
353///
354/// # Example
355///
356/// ```rust
357/// use reinhardt_db::orm::DateTimeRangeField;
358///
359/// let period = DateTimeRangeField::new();
360/// assert_eq!(period.sql_type(), "TSTZRANGE");
361/// ```
362#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct DateTimeRangeField {
364	default: Option<(Option<NaiveDateTime>, Option<NaiveDateTime>)>,
365}
366
367impl DateTimeRangeField {
368	/// Creates a new instance.
369	pub fn new() -> Self {
370		Self { default: None }
371	}
372
373	/// Sets the default and returns self for chaining.
374	pub fn with_default(
375		mut self,
376		lower: Option<NaiveDateTime>,
377		upper: Option<NaiveDateTime>,
378	) -> Self {
379		self.default = Some((lower, upper));
380		self
381	}
382
383	/// Performs the sql type operation.
384	pub fn sql_type(&self) -> &'static str {
385		"TSTZRANGE"
386	}
387}
388
389impl Default for DateTimeRangeField {
390	fn default() -> Self {
391		Self::new()
392	}
393}
394
395/// Case-insensitive Text field
396///
397/// Uses PostgreSQL's CITEXT extension for case-insensitive text comparison.
398///
399/// # Example
400///
401/// ```rust
402/// use reinhardt_db::orm::CITextField;
403///
404/// let email = CITextField::new();
405/// assert_eq!(email.sql_type(), "CITEXT");
406/// ```
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct CITextField {
409	max_length: Option<usize>,
410	default: Option<String>,
411}
412
413impl CITextField {
414	/// Create a new CITextField
415	pub fn new() -> Self {
416		Self {
417			max_length: None,
418			default: None,
419		}
420	}
421
422	/// Set maximum length
423	pub fn with_max_length(mut self, max_length: usize) -> Self {
424		self.max_length = Some(max_length);
425		self
426	}
427
428	/// Set default value
429	pub fn with_default(mut self, default: impl Into<String>) -> Self {
430		self.default = Some(default.into());
431		self
432	}
433
434	/// Generate SQL type definition
435	pub fn sql_type(&self) -> &'static str {
436		"CITEXT"
437	}
438}
439
440impl Default for CITextField {
441	fn default() -> Self {
442		Self::new()
443	}
444}
445
446#[cfg(test)]
447mod tests {
448	use super::*;
449
450	#[test]
451	fn test_array_field_basic() {
452		let field = ArrayField::<i32>::new("INTEGER");
453		assert_eq!(field.base_type(), "INTEGER");
454		assert_eq!(field.sql_type(), "INTEGER[]");
455	}
456
457	#[test]
458	fn test_array_field_with_size() {
459		let field = ArrayField::<String>::new("VARCHAR(50)").with_size(10);
460		assert_eq!(field.size(), Some(10));
461		assert_eq!(field.sql_type(), "VARCHAR(50)[10]");
462	}
463
464	#[test]
465	fn test_jsonb_field() {
466		let field = JSONBField::new();
467		assert_eq!(field.sql_type(), "JSONB");
468	}
469
470	#[test]
471	fn test_jsonb_field_with_default() {
472		let default = serde_json::json!({"key": "value"});
473		let field = JSONBField::new().with_default(default.clone());
474		assert_eq!(field.default(), Some(&default));
475	}
476
477	#[test]
478	fn test_hstore_field() {
479		let field = HStoreField::new();
480		assert_eq!(field.sql_type(), "HSTORE");
481	}
482
483	#[test]
484	fn test_integer_range_field() {
485		let field = IntegerRangeField::new();
486		assert_eq!(field.sql_type(), "INT4RANGE");
487	}
488
489	#[test]
490	fn test_biginteger_range_field() {
491		let field = BigIntegerRangeField::new();
492		assert_eq!(field.sql_type(), "INT8RANGE");
493	}
494
495	#[test]
496	fn test_date_range_field() {
497		let field = DateRangeField::new();
498		assert_eq!(field.sql_type(), "DATERANGE");
499	}
500
501	#[test]
502	fn test_datetime_range_field() {
503		let field = DateTimeRangeField::new();
504		assert_eq!(field.sql_type(), "TSTZRANGE");
505	}
506
507	#[test]
508	fn test_citext_field() {
509		let field = CITextField::new();
510		assert_eq!(field.sql_type(), "CITEXT");
511	}
512
513	#[test]
514	fn test_citext_field_with_max_length() {
515		let field = CITextField::new().with_max_length(255);
516		assert_eq!(field.sql_type(), "CITEXT");
517	}
518}