1use super::fields::{BaseField, Field, FieldDeconstruction, FieldKwarg};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
14pub enum StorageType {
15 Stored,
18 #[default]
21 Virtual,
22}
23
24impl StorageType {
25 pub fn to_sql(&self) -> &'static str {
36 match self {
37 StorageType::Stored => "STORED",
38 StorageType::Virtual => "VIRTUAL",
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
67pub struct GeneratedField {
68 pub base: BaseField,
70 pub expression: String,
72 pub storage_type: StorageType,
74 pub db_persist: bool,
76}
77
78impl GeneratedField {
79 pub fn new(expression: impl Into<String>, storage_type: StorageType) -> Self {
94 let mut base = BaseField::new();
95 base.editable = false;
97
98 Self {
99 base,
100 expression: expression.into(),
101 storage_type,
102 db_persist: storage_type == StorageType::Stored,
103 }
104 }
105
106 pub fn virtual_field(expression: impl Into<String>) -> Self {
117 Self::new(expression, StorageType::Virtual)
118 }
119
120 pub fn stored_field(expression: impl Into<String>) -> Self {
131 Self::new(expression, StorageType::Stored)
132 }
133
134 pub fn to_sql(&self) -> String {
146 format!(
147 "GENERATED ALWAYS AS ({}) {}",
148 self.expression,
149 self.storage_type.to_sql()
150 )
151 }
152
153 pub fn to_postgres_sql(&self) -> String {
165 format!(
167 "GENERATED ALWAYS AS ({}) {}",
168 self.expression,
169 self.storage_type.to_sql()
170 )
171 }
172
173 pub fn to_mysql_sql(&self) -> String {
185 format!(
187 "GENERATED ALWAYS AS ({}) {}",
188 self.expression,
189 self.storage_type.to_sql()
190 )
191 }
192
193 pub fn to_sqlite_sql(&self) -> String {
205 format!(
207 "GENERATED ALWAYS AS ({}) {}",
208 self.expression,
209 self.storage_type.to_sql()
210 )
211 }
212}
213
214impl Field for GeneratedField {
215 fn deconstruct(&self) -> FieldDeconstruction {
216 let mut kwargs = self.base.get_kwargs();
217
218 kwargs.insert(
220 "expression".to_string(),
221 FieldKwarg::String(self.expression.clone()),
222 );
223 kwargs.insert(
224 "storage_type".to_string(),
225 FieldKwarg::String(self.storage_type.to_sql().to_string()),
226 );
227 if self.db_persist {
228 kwargs.insert("db_persist".to_string(), FieldKwarg::Bool(true));
229 }
230
231 kwargs.remove("editable");
233
234 FieldDeconstruction {
235 name: self.base.name.clone(),
236 path: "reinhardt.orm.models.GeneratedField".to_string(),
237 args: vec![],
238 kwargs,
239 }
240 }
241
242 fn set_attributes_from_name(&mut self, name: &str) {
243 self.base.name = Some(name.to_string());
244 }
245
246 fn name(&self) -> Option<&str> {
247 self.base.name.as_deref()
248 }
249
250 fn is_null(&self) -> bool {
251 self.base.null
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn test_storage_type_to_sql() {
261 assert_eq!(StorageType::Stored.to_sql(), "STORED");
262 assert_eq!(StorageType::Virtual.to_sql(), "VIRTUAL");
263 }
264
265 #[test]
266 fn test_storage_type_default() {
267 assert_eq!(StorageType::default(), StorageType::Virtual);
268 }
269
270 #[test]
271 fn test_generated_field_new() {
272 let field = GeneratedField::new("price * 1.1", StorageType::Stored);
273 assert_eq!(field.expression, "price * 1.1");
274 assert_eq!(field.storage_type, StorageType::Stored);
275 assert!(!field.base.editable);
276 assert!(field.db_persist);
277 }
278
279 #[test]
280 fn test_generated_field_virtual() {
281 let field = GeneratedField::virtual_field("UPPER(name)");
282 assert_eq!(field.expression, "UPPER(name)");
283 assert_eq!(field.storage_type, StorageType::Virtual);
284 assert!(!field.db_persist);
285 }
286
287 #[test]
288 fn test_generated_field_stored() {
289 let field = GeneratedField::stored_field("quantity * price");
290 assert_eq!(field.expression, "quantity * price");
291 assert_eq!(field.storage_type, StorageType::Stored);
292 assert!(field.db_persist);
293 }
294
295 #[test]
296 fn test_generated_field_to_sql() {
297 let field = GeneratedField::new("a + b", StorageType::Virtual);
298 let sql = field.to_sql();
299 assert_eq!(
300 sql, "GENERATED ALWAYS AS (a + b) VIRTUAL",
301 "Expected exact generated field SQL, got: {}",
302 sql
303 );
304 }
305
306 #[test]
307 fn test_generated_field_to_sql_stored() {
308 let field = GeneratedField::stored_field("CONCAT(first_name, ' ', last_name)");
309 let sql = field.to_sql();
310 assert_eq!(
311 sql, "GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) STORED",
312 "Expected exact stored generated field SQL, got: {}",
313 sql
314 );
315 }
316
317 #[test]
318 fn test_generated_field_postgres_sql() {
319 let field = GeneratedField::virtual_field("price * 1.2");
320 let sql = field.to_postgres_sql();
321 assert_eq!(
322 sql, "GENERATED ALWAYS AS (price * 1.2) VIRTUAL",
323 "Expected exact PostgreSQL generated field SQL, got: {}",
324 sql
325 );
326 }
327
328 #[test]
329 fn test_generated_field_mysql_sql() {
330 let field = GeneratedField::stored_field("quantity * unit_price");
331 let sql = field.to_mysql_sql();
332 assert_eq!(
333 sql, "GENERATED ALWAYS AS (quantity * unit_price) STORED",
334 "Expected exact MySQL generated field SQL, got: {}",
335 sql
336 );
337 }
338
339 #[test]
340 fn test_generated_field_sqlite_sql() {
341 let field = GeneratedField::virtual_field("json_extract(data, '$.name')");
342 let sql = field.to_sqlite_sql();
343 assert_eq!(
344 sql, "GENERATED ALWAYS AS (json_extract(data, '$.name')) VIRTUAL",
345 "Expected exact SQLite generated field SQL, got: {}",
346 sql
347 );
348 }
349
350 #[test]
351 fn test_generated_field_deconstruct() {
352 let mut field = GeneratedField::stored_field("price + tax");
353 field.set_attributes_from_name("total_price");
354
355 let dec = field.deconstruct();
356 assert_eq!(dec.name, Some("total_price".to_string()));
357 assert_eq!(dec.path, "reinhardt.orm.models.GeneratedField");
358 assert_eq!(
359 dec.kwargs.get("expression"),
360 Some(&FieldKwarg::String("price + tax".to_string()))
361 );
362 assert_eq!(
363 dec.kwargs.get("storage_type"),
364 Some(&FieldKwarg::String("STORED".to_string()))
365 );
366 assert_eq!(dec.kwargs.get("db_persist"), Some(&FieldKwarg::Bool(true)));
367 }
368
369 #[test]
370 fn test_generated_field_not_editable() {
371 let field = GeneratedField::virtual_field("col1 + col2");
372 assert!(!field.base.editable);
373
374 let dec = field.deconstruct();
375 assert!(!dec.kwargs.contains_key("editable"));
377 }
378
379 #[test]
380 fn test_field_trait_implementation() {
381 let mut field = GeneratedField::virtual_field("x * y");
382 assert!(field.name().is_none());
383
384 field.set_attributes_from_name("result");
385 assert_eq!(field.name(), Some("result"));
386 }
387
388 #[test]
389 fn test_complex_expression() {
390 let field = GeneratedField::stored_field(
391 "CASE WHEN status = 'active' THEN price * 0.9 ELSE price END",
392 );
393 let sql = field.to_sql();
394 assert_eq!(
395 sql,
396 "GENERATED ALWAYS AS (CASE WHEN status = 'active' THEN price * 0.9 ELSE price END) STORED",
397 "Expected exact complex expression SQL, got: {}",
398 sql
399 );
400 }
401
402 #[test]
403 fn test_concat_expression() {
404 let field = GeneratedField::virtual_field("CONCAT(first_name, ' ', last_name)");
405 assert_eq!(field.expression, "CONCAT(first_name, ' ', last_name)");
406 assert_eq!(field.storage_type, StorageType::Virtual);
407 }
408
409 #[test]
410 fn test_arithmetic_expression() {
411 let field = GeneratedField::stored_field("(price - discount) * quantity");
412 let sql = field.to_sql();
413 assert_eq!(
414 sql, "GENERATED ALWAYS AS ((price - discount) * quantity) STORED",
415 "Expected exact arithmetic expression SQL, got: {}",
416 sql
417 );
418 }
419
420 #[test]
421 fn test_json_extract_expression() {
422 let field = GeneratedField::virtual_field("json_extract(metadata, '$.title')");
423 assert_eq!(field.expression, "json_extract(metadata, '$.title')");
424 }
425
426 #[test]
427 fn test_null_field() {
428 let mut field = GeneratedField::virtual_field("col1 + col2");
429 field.base.null = true;
430
431 assert!(field.is_null());
432 let dec = field.deconstruct();
433 assert_eq!(dec.kwargs.get("null"), Some(&FieldKwarg::Bool(true)));
434 }
435
436 #[test]
437 fn test_multiple_backends_sql_generation() {
438 let field = GeneratedField::stored_field("price * tax_rate");
439
440 let pg_sql = field.to_postgres_sql();
441 let mysql_sql = field.to_mysql_sql();
442 let sqlite_sql = field.to_sqlite_sql();
443
444 let expected = "GENERATED ALWAYS AS (price * tax_rate) STORED";
445
446 assert_eq!(
447 pg_sql, expected,
448 "Expected exact PostgreSQL SQL, got: {}",
449 pg_sql
450 );
451 assert_eq!(
452 mysql_sql, expected,
453 "Expected exact MySQL SQL, got: {}",
454 mysql_sql
455 );
456 assert_eq!(
457 sqlite_sql, expected,
458 "Expected exact SQLite SQL, got: {}",
459 sqlite_sql
460 );
461 }
462
463 #[test]
464 fn test_storage_type_equality() {
465 let stored1 = StorageType::Stored;
466 let stored2 = StorageType::Stored;
467 let virtual1 = StorageType::Virtual;
468
469 assert_eq!(stored1, stored2);
470 assert_ne!(stored1, virtual1);
471 }
472
473 #[test]
474 fn test_field_name_setting() {
475 let mut field = GeneratedField::virtual_field("a + b");
476 assert!(field.name().is_none());
477
478 field.set_attributes_from_name("sum_field");
479 assert_eq!(field.name(), Some("sum_field"));
480
481 field.set_attributes_from_name("total");
482 assert_eq!(field.name(), Some("total"));
483 }
484
485 #[test]
486 fn test_db_persist_flag() {
487 let virtual_field = GeneratedField::virtual_field("col1");
488 assert!(!virtual_field.db_persist);
489
490 let stored_field = GeneratedField::stored_field("col2");
491 assert!(stored_field.db_persist);
492 }
493
494 #[test]
495 fn test_date_expression() {
496 let field = GeneratedField::stored_field("DATE_ADD(created_at, INTERVAL 30 DAY)");
497 let sql = field.to_sql();
498 assert_eq!(
499 sql, "GENERATED ALWAYS AS (DATE_ADD(created_at, INTERVAL 30 DAY)) STORED",
500 "Expected exact date expression SQL, got: {}",
501 sql
502 );
503 }
504
505 #[test]
506 fn test_string_function_expression() {
507 let field = GeneratedField::virtual_field("LOWER(TRIM(email))");
508 assert_eq!(field.expression, "LOWER(TRIM(email))");
509 }
510
511 #[test]
512 fn test_conditional_expression() {
513 let field = GeneratedField::stored_field("IF(quantity > 10, price * 0.9, price)");
514 let sql = field.to_sql();
515 assert_eq!(
516 sql, "GENERATED ALWAYS AS (IF(quantity > 10, price * 0.9, price)) STORED",
517 "Expected exact conditional expression SQL, got: {}",
518 sql
519 );
520 }
521
522 #[test]
523 fn test_subquery_expression() {
524 let field = GeneratedField::virtual_field(
525 "(SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id)",
526 );
527 assert_eq!(
528 field.expression, "(SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id)",
529 "Expected exact subquery expression, got: {}",
530 field.expression
531 );
532 }
533
534 #[test]
535 fn test_aggregate_expression() {
536 let field = GeneratedField::stored_field("COALESCE(discount, 0) + base_price");
537 let sql = field.to_sql();
538 assert_eq!(
539 sql, "GENERATED ALWAYS AS (COALESCE(discount, 0) + base_price) STORED",
540 "Expected exact aggregate expression SQL, got: {}",
541 sql
542 );
543 }
544
545 #[test]
546 fn test_deconstruct_virtual_field() {
547 let field = GeneratedField::virtual_field("x + y");
548 let dec = field.deconstruct();
549
550 assert_eq!(
551 dec.kwargs.get("storage_type"),
552 Some(&FieldKwarg::String("VIRTUAL".to_string()))
553 );
554 assert!(!dec.kwargs.contains_key("db_persist"));
555 }
556
557 #[test]
558 fn test_expression_with_special_characters() {
559 let field = GeneratedField::stored_field("regexp_replace(text, '[^a-zA-Z]', '')");
560 assert_eq!(field.expression, "regexp_replace(text, '[^a-zA-Z]', '')");
561 }
562}