Skip to main content

sqlmodel_core/
field.rs

1//! Field and column definitions.
2
3use crate::types::SqlType;
4
5/// Referential action for foreign key constraints (ON DELETE / ON UPDATE).
6///
7/// These define what happens to referencing rows when the referenced row is
8/// deleted or updated.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum ReferentialAction {
11    /// No action - raise error if any references exist.
12    /// This is the default and most restrictive option.
13    #[default]
14    NoAction,
15    /// Restrict - same as NO ACTION (alias for compatibility).
16    Restrict,
17    /// Cascade - automatically delete/update referencing rows.
18    Cascade,
19    /// Set null - set referencing columns to NULL.
20    SetNull,
21    /// Set default - set referencing columns to their default values.
22    SetDefault,
23}
24
25impl ReferentialAction {
26    /// Get the SQL representation of this action.
27    #[must_use]
28    pub const fn as_sql(&self) -> &'static str {
29        match self {
30            ReferentialAction::NoAction => "NO ACTION",
31            ReferentialAction::Restrict => "RESTRICT",
32            ReferentialAction::Cascade => "CASCADE",
33            ReferentialAction::SetNull => "SET NULL",
34            ReferentialAction::SetDefault => "SET DEFAULT",
35        }
36    }
37
38    /// Parse a referential action from a string (case-insensitive).
39    ///
40    /// Returns `None` if the string is not a recognized action.
41    #[must_use]
42    pub fn from_str(s: &str) -> Option<Self> {
43        match s.to_uppercase().as_str() {
44            "NO ACTION" | "NOACTION" | "NO_ACTION" => Some(ReferentialAction::NoAction),
45            "RESTRICT" => Some(ReferentialAction::Restrict),
46            "CASCADE" => Some(ReferentialAction::Cascade),
47            "SET NULL" | "SETNULL" | "SET_NULL" => Some(ReferentialAction::SetNull),
48            "SET DEFAULT" | "SETDEFAULT" | "SET_DEFAULT" => Some(ReferentialAction::SetDefault),
49            _ => None,
50        }
51    }
52}
53
54/// Metadata about a model field/column.
55#[derive(Debug, Clone)]
56pub struct FieldInfo {
57    /// Rust field name
58    pub name: &'static str,
59    /// Database column name (may differ from field name)
60    pub column_name: &'static str,
61    /// SQL type for this field
62    pub sql_type: SqlType,
63    /// Explicit SQL type override string (e.g., "VARCHAR(255)", "DECIMAL(10,2)")
64    /// When set, this takes precedence over `sql_type` in DDL generation.
65    pub sql_type_override: Option<&'static str>,
66    /// Precision for DECIMAL/NUMERIC types (total digits)
67    pub precision: Option<u8>,
68    /// Scale for DECIMAL/NUMERIC types (digits after decimal point)
69    pub scale: Option<u8>,
70    /// Whether this field is nullable
71    pub nullable: bool,
72    /// Whether this is a primary key
73    pub primary_key: bool,
74    /// Whether this field auto-increments
75    pub auto_increment: bool,
76    /// Whether this field has a unique constraint
77    pub unique: bool,
78    /// Default value expression (SQL)
79    pub default: Option<&'static str>,
80    /// Foreign key reference (table.column)
81    pub foreign_key: Option<&'static str>,
82    /// Referential action for ON DELETE (only valid with foreign_key)
83    pub on_delete: Option<ReferentialAction>,
84    /// Referential action for ON UPDATE (only valid with foreign_key)
85    pub on_update: Option<ReferentialAction>,
86    /// Index name if indexed
87    pub index: Option<&'static str>,
88    /// Alias for both input and output (like serde rename).
89    /// When set, this name is used instead of `name` for serialization/deserialization.
90    pub alias: Option<&'static str>,
91    /// Alias used only during deserialization/validation (input-only).
92    /// Accepts this name as an alternative to `name` or `alias` during parsing.
93    pub validation_alias: Option<&'static str>,
94    /// Alias used only during serialization (output-only).
95    /// Overrides `alias` when outputting the field name.
96    pub serialization_alias: Option<&'static str>,
97    /// Whether this is a computed field (not stored in database).
98    /// Computed fields are excluded from database operations but included
99    /// in serialization (model_dump) unless exclude_computed_fields is set.
100    pub computed: bool,
101    /// Whether to exclude this field from serialization (model_dump).
102    /// When true, the field will never appear in serialized output.
103    pub exclude: bool,
104    /// Schema title for JSON Schema generation.
105    /// Used as the "title" property in the generated JSON Schema.
106    pub title: Option<&'static str>,
107    /// Schema description for JSON Schema generation.
108    /// Used as the "description" property in the generated JSON Schema.
109    pub description: Option<&'static str>,
110    /// Extra JSON Schema properties (as JSON string, merged into schema).
111    /// The string should be valid JSON that will be merged into the field's schema.
112    pub schema_extra: Option<&'static str>,
113    /// JSON representation of the field's default value (for exclude_defaults).
114    /// When set, model_dump with exclude_defaults=true will compare the current
115    /// value against this and exclude the field if they match.
116    pub default_json: Option<&'static str>,
117    /// Whether this field has a default value (used for exclude_unset tracking).
118    /// Fields with defaults can be distinguished from fields that were explicitly set.
119    pub has_default: bool,
120    /// Whether this field is constant (immutable after creation).
121    /// Const fields cannot be modified after initial construction.
122    /// This is enforced at validation/session level, not compile-time.
123    pub const_field: bool,
124    /// Additional SQL constraints for DDL generation (e.g., CHECK constraints).
125    /// Each string is a SQL constraint expression that will be added to the column definition.
126    pub column_constraints: &'static [&'static str],
127    /// SQL comment for the column (used in DDL generation).
128    /// This maps to the COMMENT ON COLUMN or inline COMMENT clause depending on the database.
129    pub column_comment: Option<&'static str>,
130    /// Extra metadata as JSON string (for custom extensions/info).
131    /// This can be used to store additional information that doesn't fit in other fields.
132    pub column_info: Option<&'static str>,
133    /// SQL expression for hybrid properties.
134    ///
135    /// When set, this field is a hybrid property: it has both a Rust-side computed
136    /// value and a SQL expression that can be used in queries (WHERE, ORDER BY, etc.).
137    /// The macro generates a `{field}_expr()` associated function that returns
138    /// `Expr::raw(this_sql)`.
139    pub hybrid_sql: Option<&'static str>,
140    /// Discriminator field name for union types.
141    ///
142    /// When this field contains a union/enum type, the discriminator specifies
143    /// which field within the union variants is used to determine the type.
144    /// This maps to Pydantic's `Field(discriminator='field_name')`.
145    ///
146    /// In Rust, discriminated unions are typically handled by serde's
147    /// `#[serde(tag = "field_name")]` attribute on the enum. This field
148    /// stores the discriminator info for:
149    /// - JSON Schema generation (OpenAPI discriminator)
150    /// - Documentation purposes
151    /// - Runtime validation hints
152    ///
153    /// Example:
154    /// ```ignore
155    /// #[derive(Model)]
156    /// struct Owner {
157    ///     #[sqlmodel(discriminator = "pet_type")]
158    ///     pet: PetUnion, // PetUnion should have #[serde(tag = "pet_type")]
159    /// }
160    /// ```
161    pub discriminator: Option<&'static str>,
162}
163
164impl FieldInfo {
165    /// Create a new field info with minimal required data.
166    pub const fn new(name: &'static str, column_name: &'static str, sql_type: SqlType) -> Self {
167        Self {
168            name,
169            column_name,
170            sql_type,
171            sql_type_override: None,
172            precision: None,
173            scale: None,
174            nullable: false,
175            primary_key: false,
176            auto_increment: false,
177            unique: false,
178            default: None,
179            foreign_key: None,
180            on_delete: None,
181            on_update: None,
182            index: None,
183            alias: None,
184            validation_alias: None,
185            serialization_alias: None,
186            computed: false,
187            exclude: false,
188            title: None,
189            description: None,
190            schema_extra: None,
191            default_json: None,
192            has_default: false,
193            const_field: false,
194            column_constraints: &[],
195            column_comment: None,
196            column_info: None,
197            hybrid_sql: None,
198            discriminator: None,
199        }
200    }
201
202    /// Set the database column name.
203    pub const fn column(mut self, name: &'static str) -> Self {
204        self.column_name = name;
205        self
206    }
207
208    /// Set explicit SQL type override.
209    ///
210    /// When set, this string will be used directly in DDL generation instead
211    /// of the `sql_type.sql_name()`. Use this for database-specific types like
212    /// `VARCHAR(255)`, `DECIMAL(10,2)`, `TINYINT UNSIGNED`, etc.
213    pub const fn sql_type_override(mut self, type_str: &'static str) -> Self {
214        self.sql_type_override = Some(type_str);
215        self
216    }
217
218    /// Set SQL type override from optional.
219    pub const fn sql_type_override_opt(mut self, type_str: Option<&'static str>) -> Self {
220        self.sql_type_override = type_str;
221        self
222    }
223
224    /// Set precision for DECIMAL/NUMERIC types.
225    ///
226    /// Precision is the total number of digits (before and after decimal point).
227    /// Typical range: 1-38, depends on database.
228    ///
229    /// # Example
230    ///
231    /// ```ignore
232    /// // DECIMAL(10, 2) - 10 total digits, 2 after decimal
233    /// FieldInfo::new("price", "price", SqlType::Decimal { precision: 10, scale: 2 })
234    ///     .precision(10)
235    ///     .scale(2)
236    /// ```
237    pub const fn precision(mut self, value: u8) -> Self {
238        self.precision = Some(value);
239        self
240    }
241
242    /// Set precision from optional.
243    pub const fn precision_opt(mut self, value: Option<u8>) -> Self {
244        self.precision = value;
245        self
246    }
247
248    /// Set scale for DECIMAL/NUMERIC types.
249    ///
250    /// Scale is the number of digits after the decimal point.
251    /// Must be less than or equal to precision.
252    pub const fn scale(mut self, value: u8) -> Self {
253        self.scale = Some(value);
254        self
255    }
256
257    /// Set scale from optional.
258    pub const fn scale_opt(mut self, value: Option<u8>) -> Self {
259        self.scale = value;
260        self
261    }
262
263    /// Set both precision and scale for DECIMAL/NUMERIC types.
264    ///
265    /// # Example
266    ///
267    /// ```ignore
268    /// // DECIMAL(10, 2) for currency
269    /// FieldInfo::new("price", "price", SqlType::Decimal { precision: 10, scale: 2 })
270    ///     .decimal_precision(10, 2)
271    /// ```
272    pub const fn decimal_precision(mut self, precision: u8, scale: u8) -> Self {
273        self.precision = Some(precision);
274        self.scale = Some(scale);
275        self
276    }
277
278    /// Get the effective SQL type name for DDL generation.
279    ///
280    /// Priority:
281    /// 1. `sql_type_override` if set
282    /// 2. For DECIMAL/NUMERIC: uses `precision` and `scale` fields if set
283    /// 3. Falls back to `sql_type.sql_name()`
284    #[must_use]
285    pub fn effective_sql_type(&self) -> String {
286        // sql_type_override takes highest precedence
287        if let Some(override_str) = self.sql_type_override {
288            return override_str.to_string();
289        }
290
291        // For Decimal/Numeric types, use precision/scale fields if available
292        match self.sql_type {
293            SqlType::Decimal { .. } | SqlType::Numeric { .. } => {
294                if let (Some(p), Some(s)) = (self.precision, self.scale) {
295                    let type_name = match self.sql_type {
296                        SqlType::Decimal { .. } => "DECIMAL",
297                        SqlType::Numeric { .. } => "NUMERIC",
298                        _ => unreachable!(),
299                    };
300                    return format!("{}({}, {})", type_name, p, s);
301                }
302            }
303            _ => {}
304        }
305
306        // Fall back to sql_type's own name generation
307        self.sql_type.sql_name()
308    }
309
310    /// Set nullable flag.
311    pub const fn nullable(mut self, value: bool) -> Self {
312        self.nullable = value;
313        self
314    }
315
316    /// Set primary key flag.
317    pub const fn primary_key(mut self, value: bool) -> Self {
318        self.primary_key = value;
319        self
320    }
321
322    /// Set auto-increment flag.
323    pub const fn auto_increment(mut self, value: bool) -> Self {
324        self.auto_increment = value;
325        self
326    }
327
328    /// Set unique flag.
329    pub const fn unique(mut self, value: bool) -> Self {
330        self.unique = value;
331        self
332    }
333
334    /// Set default value.
335    pub const fn default(mut self, expr: &'static str) -> Self {
336        self.default = Some(expr);
337        self
338    }
339
340    /// Set default value from optional.
341    pub const fn default_opt(mut self, expr: Option<&'static str>) -> Self {
342        self.default = expr;
343        self
344    }
345
346    /// Set foreign key reference.
347    pub const fn foreign_key(mut self, reference: &'static str) -> Self {
348        self.foreign_key = Some(reference);
349        self
350    }
351
352    /// Set foreign key reference from optional.
353    pub const fn foreign_key_opt(mut self, reference: Option<&'static str>) -> Self {
354        self.foreign_key = reference;
355        self
356    }
357
358    /// Set ON DELETE action for foreign key.
359    ///
360    /// This is only meaningful when `foreign_key` is also set.
361    pub const fn on_delete(mut self, action: ReferentialAction) -> Self {
362        self.on_delete = Some(action);
363        self
364    }
365
366    /// Set ON DELETE action from optional.
367    pub const fn on_delete_opt(mut self, action: Option<ReferentialAction>) -> Self {
368        self.on_delete = action;
369        self
370    }
371
372    /// Set ON UPDATE action for foreign key.
373    ///
374    /// This is only meaningful when `foreign_key` is also set.
375    pub const fn on_update(mut self, action: ReferentialAction) -> Self {
376        self.on_update = Some(action);
377        self
378    }
379
380    /// Set ON UPDATE action from optional.
381    pub const fn on_update_opt(mut self, action: Option<ReferentialAction>) -> Self {
382        self.on_update = action;
383        self
384    }
385
386    /// Set index name.
387    pub const fn index(mut self, name: &'static str) -> Self {
388        self.index = Some(name);
389        self
390    }
391
392    /// Set index name from optional.
393    pub const fn index_opt(mut self, name: Option<&'static str>) -> Self {
394        self.index = name;
395        self
396    }
397
398    /// Set alias for both input and output.
399    ///
400    /// When set, this name is used instead of the field name for both
401    /// serialization and deserialization.
402    pub const fn alias(mut self, name: &'static str) -> Self {
403        self.alias = Some(name);
404        self
405    }
406
407    /// Set alias from optional.
408    pub const fn alias_opt(mut self, name: Option<&'static str>) -> Self {
409        self.alias = name;
410        self
411    }
412
413    /// Set validation alias (input-only).
414    ///
415    /// This name is accepted as an alternative during deserialization,
416    /// in addition to the field name and regular alias.
417    pub const fn validation_alias(mut self, name: &'static str) -> Self {
418        self.validation_alias = Some(name);
419        self
420    }
421
422    /// Set validation alias from optional.
423    pub const fn validation_alias_opt(mut self, name: Option<&'static str>) -> Self {
424        self.validation_alias = name;
425        self
426    }
427
428    /// Set serialization alias (output-only).
429    ///
430    /// This name is used instead of the field name or regular alias
431    /// when serializing the field.
432    pub const fn serialization_alias(mut self, name: &'static str) -> Self {
433        self.serialization_alias = Some(name);
434        self
435    }
436
437    /// Set serialization alias from optional.
438    pub const fn serialization_alias_opt(mut self, name: Option<&'static str>) -> Self {
439        self.serialization_alias = name;
440        self
441    }
442
443    /// Mark this field as computed (not stored in database).
444    ///
445    /// Computed fields are:
446    /// - Excluded from database operations (INSERT, UPDATE, SELECT)
447    /// - Initialized with Default::default() when loading from database
448    /// - Included in serialization (model_dump) unless exclude_computed_fields is set
449    ///
450    /// Use this for fields whose value is derived from other fields at access time.
451    pub const fn computed(mut self, value: bool) -> Self {
452        self.computed = value;
453        self
454    }
455
456    /// Mark this field as excluded from serialization (model_dump).
457    ///
458    /// Excluded fields will never appear in serialized output, regardless
459    /// of other serialization settings. This is useful for sensitive data
460    /// like passwords or internal fields.
461    ///
462    /// # Example
463    ///
464    /// ```ignore
465    /// #[derive(Model)]
466    /// struct User {
467    ///     id: i32,
468    ///     #[sqlmodel(exclude)]
469    ///     password_hash: String,  // Never serialized
470    /// }
471    /// ```
472    pub const fn exclude(mut self, value: bool) -> Self {
473        self.exclude = value;
474        self
475    }
476
477    /// Set the schema title for JSON Schema generation.
478    ///
479    /// The title appears as the "title" property in the field's JSON Schema.
480    pub const fn title(mut self, value: &'static str) -> Self {
481        self.title = Some(value);
482        self
483    }
484
485    /// Set the schema title from optional.
486    pub const fn title_opt(mut self, value: Option<&'static str>) -> Self {
487        self.title = value;
488        self
489    }
490
491    /// Set the schema description for JSON Schema generation.
492    ///
493    /// The description appears as the "description" property in the field's JSON Schema.
494    pub const fn description(mut self, value: &'static str) -> Self {
495        self.description = Some(value);
496        self
497    }
498
499    /// Set the schema description from optional.
500    pub const fn description_opt(mut self, value: Option<&'static str>) -> Self {
501        self.description = value;
502        self
503    }
504
505    /// Set extra JSON Schema properties (as JSON string).
506    ///
507    /// The string should be valid JSON that will be merged into the field's schema.
508    /// For example: `{"examples": ["John Doe"], "minLength": 1}`
509    pub const fn schema_extra(mut self, value: &'static str) -> Self {
510        self.schema_extra = Some(value);
511        self
512    }
513
514    /// Set schema_extra from optional.
515    pub const fn schema_extra_opt(mut self, value: Option<&'static str>) -> Self {
516        self.schema_extra = value;
517        self
518    }
519
520    /// Set the JSON representation of the field's default value.
521    ///
522    /// This is used by model_dump with exclude_defaults=true to compare
523    /// the current value against the default.
524    ///
525    /// # Example
526    ///
527    /// ```ignore
528    /// FieldInfo::new("count", "count", SqlType::Integer)
529    ///     .default_json("0")
530    ///     .has_default(true)
531    /// ```
532    pub const fn default_json(mut self, value: &'static str) -> Self {
533        self.default_json = Some(value);
534        self.has_default = true;
535        self
536    }
537
538    /// Set default_json from optional.
539    pub const fn default_json_opt(mut self, value: Option<&'static str>) -> Self {
540        self.default_json = value;
541        if value.is_some() {
542            self.has_default = true;
543        }
544        self
545    }
546
547    /// Mark whether this field has a default value.
548    ///
549    /// Used for exclude_unset tracking - fields with defaults may not need
550    /// to be explicitly set.
551    pub const fn has_default(mut self, value: bool) -> Self {
552        self.has_default = value;
553        self
554    }
555
556    /// Mark this field as constant (immutable after creation).
557    ///
558    /// Const fields cannot be modified after the model is initially created.
559    /// This is useful for version numbers, creation timestamps, or other
560    /// immutable identifiers.
561    ///
562    /// # Example
563    ///
564    /// ```ignore
565    /// FieldInfo::new("version", "version", SqlType::Text)
566    ///     .const_field(true)
567    /// ```
568    pub const fn const_field(mut self, value: bool) -> Self {
569        self.const_field = value;
570        self
571    }
572
573    /// Set additional SQL constraints for DDL generation.
574    ///
575    /// These constraints are added to the column definition.
576    /// Common uses include CHECK constraints.
577    ///
578    /// # Example
579    ///
580    /// ```ignore
581    /// FieldInfo::new("age", "age", SqlType::Integer)
582    ///     .column_constraints(&["CHECK(age >= 0)", "CHECK(age <= 150)"])
583    /// ```
584    pub const fn column_constraints(mut self, constraints: &'static [&'static str]) -> Self {
585        self.column_constraints = constraints;
586        self
587    }
588
589    /// Set a SQL comment for the column.
590    ///
591    /// The comment will be included in DDL generation.
592    pub const fn column_comment(mut self, comment: &'static str) -> Self {
593        self.column_comment = Some(comment);
594        self
595    }
596
597    /// Set column comment from optional.
598    pub const fn column_comment_opt(mut self, comment: Option<&'static str>) -> Self {
599        self.column_comment = comment;
600        self
601    }
602
603    /// Set extra metadata as JSON string.
604    ///
605    /// This can be used for custom extensions or information
606    /// that doesn't fit in other fields.
607    pub const fn column_info(mut self, info: &'static str) -> Self {
608        self.column_info = Some(info);
609        self
610    }
611
612    /// Set column info from optional.
613    pub const fn column_info_opt(mut self, info: Option<&'static str>) -> Self {
614        self.column_info = info;
615        self
616    }
617
618    /// Set hybrid SQL expression.
619    pub const fn hybrid_sql(mut self, sql: &'static str) -> Self {
620        self.hybrid_sql = Some(sql);
621        self
622    }
623
624    /// Set hybrid SQL expression from optional.
625    pub const fn hybrid_sql_opt(mut self, sql: Option<&'static str>) -> Self {
626        self.hybrid_sql = sql;
627        self
628    }
629
630    /// Set discriminator field name for union types.
631    ///
632    /// This specifies which field in the union variants is used to determine
633    /// the concrete type during deserialization. The union type should have
634    /// a matching `#[serde(tag = "discriminator_field")]` attribute.
635    pub const fn discriminator(mut self, field: &'static str) -> Self {
636        self.discriminator = Some(field);
637        self
638    }
639
640    /// Set discriminator from optional.
641    pub const fn discriminator_opt(mut self, field: Option<&'static str>) -> Self {
642        self.discriminator = field;
643        self
644    }
645
646    /// Get the name to use when serializing (output).
647    ///
648    /// Priority: serialization_alias > alias > name
649    #[must_use]
650    pub const fn output_name(&self) -> &'static str {
651        if let Some(ser_alias) = self.serialization_alias {
652            ser_alias
653        } else if let Some(alias) = self.alias {
654            alias
655        } else {
656            self.name
657        }
658    }
659
660    /// Check if a given name matches this field for input (deserialization).
661    ///
662    /// Matches: name, alias, or validation_alias
663    #[must_use]
664    pub fn matches_input_name(&self, input: &str) -> bool {
665        if input == self.name {
666            return true;
667        }
668        if let Some(alias) = self.alias {
669            if input == alias {
670                return true;
671            }
672        }
673        if let Some(val_alias) = self.validation_alias {
674            if input == val_alias {
675                return true;
676            }
677        }
678        false
679    }
680
681    /// Check if this field has any alias configuration.
682    #[must_use]
683    pub const fn has_alias(&self) -> bool {
684        self.alias.is_some()
685            || self.validation_alias.is_some()
686            || self.serialization_alias.is_some()
687    }
688}
689
690/// A column reference used in queries.
691#[derive(Debug, Clone)]
692pub struct Column {
693    /// Table name (optional, for joins)
694    pub table: Option<String>,
695    /// Column name
696    pub name: String,
697    /// Alias (AS name)
698    pub alias: Option<String>,
699}
700
701impl Column {
702    /// Create a new column reference.
703    pub fn new(name: impl Into<String>) -> Self {
704        Self {
705            table: None,
706            name: name.into(),
707            alias: None,
708        }
709    }
710
711    /// Create a column reference with table prefix.
712    pub fn qualified(table: impl Into<String>, name: impl Into<String>) -> Self {
713        Self {
714            table: Some(table.into()),
715            name: name.into(),
716            alias: None,
717        }
718    }
719
720    /// Set an alias for this column.
721    pub fn alias(mut self, alias: impl Into<String>) -> Self {
722        self.alias = Some(alias.into());
723        self
724    }
725
726    /// Generate SQL for this column reference.
727    pub fn to_sql(&self) -> String {
728        let mut sql = if let Some(table) = &self.table {
729            format!("{}.{}", table, self.name)
730        } else {
731            self.name.clone()
732        };
733
734        if let Some(alias) = &self.alias {
735            sql.push_str(" AS ");
736            sql.push_str(alias);
737        }
738
739        sql
740    }
741}
742
743/// A field reference for type-safe column access.
744///
745/// This is used by generated code to provide compile-time
746/// checked column references.
747#[derive(Debug, Clone, Copy)]
748pub struct Field<T> {
749    /// The column name
750    pub name: &'static str,
751    /// Phantom data for the field type
752    _marker: std::marker::PhantomData<T>,
753}
754
755impl<T> Field<T> {
756    /// Create a new typed field reference.
757    pub const fn new(name: &'static str) -> Self {
758        Self {
759            name,
760            _marker: std::marker::PhantomData,
761        }
762    }
763}
764
765/// Table inheritance strategy.
766///
767/// Determines how model hierarchies are mapped to database tables.
768#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
769pub enum InheritanceStrategy {
770    /// No inheritance (default). Model is standalone.
771    #[default]
772    None,
773    /// Single table inheritance: all subclasses share one table with discriminator column.
774    ///
775    /// The base model specifies this strategy and the discriminator column name.
776    /// Child models inherit from the base and specify their discriminator value.
777    ///
778    /// Example:
779    /// ```ignore
780    /// #[derive(Model)]
781    /// #[sqlmodel(table, inheritance = "single", discriminator = "type")]
782    /// struct Employee { type_: String, ... }
783    ///
784    /// #[derive(Model)]
785    /// #[sqlmodel(inherits = "Employee", discriminator_value = "manager")]
786    /// struct Manager { department: String, ... }
787    /// ```
788    Single,
789    /// Joined table inheritance: each class has its own table with FK to parent.
790    ///
791    /// Base and child models each have their own table. Child tables have a foreign
792    /// key column referencing the parent's primary key. Queries join the tables.
793    ///
794    /// Example:
795    /// ```ignore
796    /// #[derive(Model)]
797    /// #[sqlmodel(table, inheritance = "joined")]
798    /// struct Person { id: i64, name: String }
799    ///
800    /// #[derive(Model)]
801    /// #[sqlmodel(table, inherits = "Person")]
802    /// struct Employee { employee_id: i64, department: String }
803    /// ```
804    Joined,
805    /// Concrete table inheritance: each class is independent, no DB-level inheritance.
806    ///
807    /// Each model has its own complete table with all columns. There's no database
808    /// relationship between parent and child tables. Useful for shared behavior
809    /// without database relationships.
810    Concrete,
811}
812
813impl InheritanceStrategy {
814    /// Check if this strategy uses a discriminator column.
815    #[must_use]
816    pub const fn uses_discriminator(&self) -> bool {
817        matches!(self, Self::Single)
818    }
819
820    /// Check if this strategy requires table joins for child models.
821    #[must_use]
822    pub const fn requires_join(&self) -> bool {
823        matches!(self, Self::Joined)
824    }
825
826    /// Check if this is any form of inheritance (not None).
827    #[must_use]
828    pub const fn is_inheritance(&self) -> bool {
829        !matches!(self, Self::None)
830    }
831}
832
833/// Inheritance metadata for a model.
834///
835/// This struct captures the inheritance configuration for models that participate
836/// in table inheritance hierarchies.
837#[derive(Debug, Clone, Default)]
838pub struct InheritanceInfo {
839    /// The inheritance strategy for this model.
840    pub strategy: InheritanceStrategy,
841    /// The parent model name (for child models).
842    ///
843    /// When set, this model inherits from the specified parent model.
844    pub parent: Option<&'static str>,
845    /// The discriminator column name (for single table inheritance base models).
846    ///
847    /// For single table inheritance, this specifies which column contains the
848    /// type discriminator values that distinguish between different model types.
849    pub discriminator_column: Option<&'static str>,
850    /// The discriminator value for this model (single table inheritance child).
851    ///
852    /// For single table inheritance, this value is stored in the discriminator
853    /// column to identify rows belonging to this specific model type.
854    pub discriminator_value: Option<&'static str>,
855}
856
857impl InheritanceInfo {
858    /// Create a new InheritanceInfo with no inheritance.
859    pub const fn none() -> Self {
860        Self {
861            strategy: InheritanceStrategy::None,
862            parent: None,
863            discriminator_column: None,
864            discriminator_value: None,
865        }
866    }
867
868    /// Create inheritance info for a base model with single table inheritance.
869    pub const fn single_table() -> Self {
870        Self {
871            strategy: InheritanceStrategy::Single,
872            parent: None,
873            discriminator_column: None,
874            discriminator_value: None,
875        }
876    }
877
878    /// Create inheritance info for a base model with joined table inheritance.
879    pub const fn joined_table() -> Self {
880        Self {
881            strategy: InheritanceStrategy::Joined,
882            parent: None,
883            discriminator_column: None,
884            discriminator_value: None,
885        }
886    }
887
888    /// Create inheritance info for a base model with concrete table inheritance.
889    pub const fn concrete_table() -> Self {
890        Self {
891            strategy: InheritanceStrategy::Concrete,
892            parent: None,
893            discriminator_column: None,
894            discriminator_value: None,
895        }
896    }
897
898    /// Create inheritance info for a child model.
899    pub const fn child(parent: &'static str) -> Self {
900        Self {
901            strategy: InheritanceStrategy::None, // Inherits from parent's strategy
902            parent: Some(parent),
903            discriminator_column: None,
904            discriminator_value: None,
905        }
906    }
907
908    /// Set the discriminator column name (builder pattern, for base models).
909    pub const fn with_discriminator_column(mut self, column: &'static str) -> Self {
910        self.discriminator_column = Some(column);
911        self
912    }
913
914    /// Set the discriminator value (builder pattern, for child models).
915    pub const fn with_discriminator_value(mut self, value: &'static str) -> Self {
916        self.discriminator_value = Some(value);
917        self
918    }
919
920    /// Check if this model is a child in an inheritance hierarchy.
921    #[must_use]
922    pub const fn is_child(&self) -> bool {
923        self.parent.is_some()
924    }
925
926    /// Check if this model is a base model in an inheritance hierarchy.
927    #[must_use]
928    pub const fn is_base(&self) -> bool {
929        self.parent.is_none() && self.strategy.is_inheritance()
930    }
931}
932
933#[cfg(test)]
934mod tests {
935    use super::*;
936    use crate::SqlType;
937
938    #[test]
939    fn test_field_info_new() {
940        let field = FieldInfo::new(
941            "price",
942            "price",
943            SqlType::Decimal {
944                precision: 10,
945                scale: 2,
946            },
947        );
948        assert_eq!(field.name, "price");
949        assert_eq!(field.column_name, "price");
950        assert!(field.precision.is_none());
951        assert!(field.scale.is_none());
952    }
953
954    #[test]
955    fn test_field_info_precision_scale() {
956        let field = FieldInfo::new(
957            "amount",
958            "amount",
959            SqlType::Decimal {
960                precision: 10,
961                scale: 2,
962            },
963        )
964        .precision(12)
965        .scale(4);
966        assert_eq!(field.precision, Some(12));
967        assert_eq!(field.scale, Some(4));
968    }
969
970    #[test]
971    fn test_field_info_decimal_precision() {
972        let field = FieldInfo::new(
973            "total",
974            "total",
975            SqlType::Numeric {
976                precision: 10,
977                scale: 2,
978            },
979        )
980        .decimal_precision(18, 6);
981        assert_eq!(field.precision, Some(18));
982        assert_eq!(field.scale, Some(6));
983    }
984
985    #[test]
986    fn test_effective_sql_type_override_takes_precedence() {
987        let field = FieldInfo::new(
988            "amount",
989            "amount",
990            SqlType::Decimal {
991                precision: 10,
992                scale: 2,
993            },
994        )
995        .sql_type_override("MONEY")
996        .precision(18)
997        .scale(4);
998        // Override should take precedence over precision/scale
999        assert_eq!(field.effective_sql_type(), "MONEY");
1000    }
1001
1002    #[test]
1003    fn test_effective_sql_type_uses_precision_scale() {
1004        let field = FieldInfo::new(
1005            "price",
1006            "price",
1007            SqlType::Decimal {
1008                precision: 10,
1009                scale: 2,
1010            },
1011        )
1012        .precision(15)
1013        .scale(3);
1014        assert_eq!(field.effective_sql_type(), "DECIMAL(15, 3)");
1015    }
1016
1017    #[test]
1018    fn test_effective_sql_type_numeric_uses_precision_scale() {
1019        let field = FieldInfo::new(
1020            "value",
1021            "value",
1022            SqlType::Numeric {
1023                precision: 10,
1024                scale: 2,
1025            },
1026        )
1027        .precision(20)
1028        .scale(8);
1029        assert_eq!(field.effective_sql_type(), "NUMERIC(20, 8)");
1030    }
1031
1032    #[test]
1033    fn test_effective_sql_type_fallback_to_sql_type() {
1034        let field = FieldInfo::new("count", "count", SqlType::BigInt);
1035        assert_eq!(field.effective_sql_type(), "BIGINT");
1036    }
1037
1038    #[test]
1039    fn test_effective_sql_type_decimal_without_precision_scale() {
1040        // When precision/scale not set on FieldInfo, use SqlType's values
1041        let field = FieldInfo::new(
1042            "amount",
1043            "amount",
1044            SqlType::Decimal {
1045                precision: 10,
1046                scale: 2,
1047            },
1048        );
1049        // Falls back to sql_type.sql_name() which should generate "DECIMAL(10, 2)"
1050        assert_eq!(field.effective_sql_type(), "DECIMAL(10, 2)");
1051    }
1052
1053    #[test]
1054    fn test_precision_opt() {
1055        let field = FieldInfo::new(
1056            "test",
1057            "test",
1058            SqlType::Decimal {
1059                precision: 10,
1060                scale: 2,
1061            },
1062        )
1063        .precision_opt(Some(16));
1064        assert_eq!(field.precision, Some(16));
1065
1066        let field2 = FieldInfo::new(
1067            "test2",
1068            "test2",
1069            SqlType::Decimal {
1070                precision: 10,
1071                scale: 2,
1072            },
1073        )
1074        .precision_opt(None);
1075        assert_eq!(field2.precision, None);
1076    }
1077
1078    #[test]
1079    fn test_scale_opt() {
1080        let field = FieldInfo::new(
1081            "test",
1082            "test",
1083            SqlType::Decimal {
1084                precision: 10,
1085                scale: 2,
1086            },
1087        )
1088        .scale_opt(Some(5));
1089        assert_eq!(field.scale, Some(5));
1090
1091        let field2 = FieldInfo::new(
1092            "test2",
1093            "test2",
1094            SqlType::Decimal {
1095                precision: 10,
1096                scale: 2,
1097            },
1098        )
1099        .scale_opt(None);
1100        assert_eq!(field2.scale, None);
1101    }
1102
1103    // ========================================================================
1104    // Field alias tests
1105    // ========================================================================
1106
1107    #[test]
1108    fn test_field_info_alias() {
1109        let field = FieldInfo::new("name", "name", SqlType::Text).alias("userName");
1110        assert_eq!(field.alias, Some("userName"));
1111        assert!(field.validation_alias.is_none());
1112        assert!(field.serialization_alias.is_none());
1113    }
1114
1115    #[test]
1116    fn test_field_info_validation_alias() {
1117        let field = FieldInfo::new("name", "name", SqlType::Text).validation_alias("user_name");
1118        assert!(field.alias.is_none());
1119        assert_eq!(field.validation_alias, Some("user_name"));
1120        assert!(field.serialization_alias.is_none());
1121    }
1122
1123    #[test]
1124    fn test_field_info_serialization_alias() {
1125        let field = FieldInfo::new("name", "name", SqlType::Text).serialization_alias("user-name");
1126        assert!(field.alias.is_none());
1127        assert!(field.validation_alias.is_none());
1128        assert_eq!(field.serialization_alias, Some("user-name"));
1129    }
1130
1131    #[test]
1132    fn test_field_info_all_aliases() {
1133        let field = FieldInfo::new("name", "name", SqlType::Text)
1134            .alias("nm")
1135            .validation_alias("input_name")
1136            .serialization_alias("outputName");
1137
1138        assert_eq!(field.alias, Some("nm"));
1139        assert_eq!(field.validation_alias, Some("input_name"));
1140        assert_eq!(field.serialization_alias, Some("outputName"));
1141    }
1142
1143    #[test]
1144    fn test_field_info_alias_opt() {
1145        let field1 = FieldInfo::new("name", "name", SqlType::Text).alias_opt(Some("userName"));
1146        assert_eq!(field1.alias, Some("userName"));
1147
1148        let field2 = FieldInfo::new("name", "name", SqlType::Text).alias_opt(None);
1149        assert!(field2.alias.is_none());
1150    }
1151
1152    #[test]
1153    fn test_field_info_validation_alias_opt() {
1154        let field1 =
1155            FieldInfo::new("name", "name", SqlType::Text).validation_alias_opt(Some("user_name"));
1156        assert_eq!(field1.validation_alias, Some("user_name"));
1157
1158        let field2 = FieldInfo::new("name", "name", SqlType::Text).validation_alias_opt(None);
1159        assert!(field2.validation_alias.is_none());
1160    }
1161
1162    #[test]
1163    fn test_field_info_serialization_alias_opt() {
1164        let field1 = FieldInfo::new("name", "name", SqlType::Text)
1165            .serialization_alias_opt(Some("user-name"));
1166        assert_eq!(field1.serialization_alias, Some("user-name"));
1167
1168        let field2 = FieldInfo::new("name", "name", SqlType::Text).serialization_alias_opt(None);
1169        assert!(field2.serialization_alias.is_none());
1170    }
1171
1172    #[test]
1173    fn test_field_info_output_name() {
1174        // No aliases - uses field name
1175        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1176        assert_eq!(field1.output_name(), "name");
1177
1178        // Only alias - uses alias
1179        let field2 = FieldInfo::new("name", "name", SqlType::Text).alias("nm");
1180        assert_eq!(field2.output_name(), "nm");
1181
1182        // serialization_alias takes precedence
1183        let field3 = FieldInfo::new("name", "name", SqlType::Text)
1184            .alias("nm")
1185            .serialization_alias("outputName");
1186        assert_eq!(field3.output_name(), "outputName");
1187
1188        // Only serialization_alias
1189        let field4 = FieldInfo::new("name", "name", SqlType::Text).serialization_alias("userName");
1190        assert_eq!(field4.output_name(), "userName");
1191    }
1192
1193    #[test]
1194    fn test_field_info_matches_input_name() {
1195        // No aliases - only matches field name
1196        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1197        assert!(field1.matches_input_name("name"));
1198        assert!(!field1.matches_input_name("userName"));
1199
1200        // With alias - matches both
1201        let field2 = FieldInfo::new("name", "name", SqlType::Text).alias("nm");
1202        assert!(field2.matches_input_name("name"));
1203        assert!(field2.matches_input_name("nm"));
1204        assert!(!field2.matches_input_name("userName"));
1205
1206        // With validation_alias - matches both
1207        let field3 = FieldInfo::new("name", "name", SqlType::Text).validation_alias("user_name");
1208        assert!(field3.matches_input_name("name"));
1209        assert!(field3.matches_input_name("user_name"));
1210        assert!(!field3.matches_input_name("userName"));
1211
1212        // With both - matches all three
1213        let field4 = FieldInfo::new("name", "name", SqlType::Text)
1214            .alias("nm")
1215            .validation_alias("user_name");
1216        assert!(field4.matches_input_name("name"));
1217        assert!(field4.matches_input_name("nm"));
1218        assert!(field4.matches_input_name("user_name"));
1219    }
1220
1221    #[test]
1222    fn test_field_info_has_alias() {
1223        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1224        assert!(!field1.has_alias());
1225
1226        let field2 = FieldInfo::new("name", "name", SqlType::Text).alias("nm");
1227        assert!(field2.has_alias());
1228
1229        let field3 = FieldInfo::new("name", "name", SqlType::Text).validation_alias("user_name");
1230        assert!(field3.has_alias());
1231
1232        let field4 = FieldInfo::new("name", "name", SqlType::Text).serialization_alias("userName");
1233        assert!(field4.has_alias());
1234
1235        let field5 = FieldInfo::new("name", "name", SqlType::Text)
1236            .alias("nm")
1237            .validation_alias("user_name")
1238            .serialization_alias("userName");
1239        assert!(field5.has_alias());
1240    }
1241
1242    #[test]
1243    fn test_field_info_exclude() {
1244        // Default: not excluded
1245        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1246        assert!(!field1.exclude);
1247
1248        // Excluded field
1249        let field2 = FieldInfo::new("password", "password", SqlType::Text).exclude(true);
1250        assert!(field2.exclude);
1251
1252        // Exclude can be explicitly set to false
1253        let field3 = FieldInfo::new("email", "email", SqlType::Text).exclude(false);
1254        assert!(!field3.exclude);
1255    }
1256
1257    #[test]
1258    fn test_field_info_exclude_combined_with_other_attrs() {
1259        // Exclude can be combined with other attributes
1260        let field = FieldInfo::new("secret", "secret", SqlType::Text)
1261            .exclude(true)
1262            .nullable(true)
1263            .alias("hidden_value");
1264
1265        assert!(field.exclude);
1266        assert!(field.nullable);
1267        assert_eq!(field.alias, Some("hidden_value"));
1268    }
1269
1270    #[test]
1271    fn test_field_info_title() {
1272        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1273        assert_eq!(field1.title, None);
1274
1275        let field2 = FieldInfo::new("name", "name", SqlType::Text).title("User Name");
1276        assert_eq!(field2.title, Some("User Name"));
1277    }
1278
1279    #[test]
1280    fn test_field_info_description() {
1281        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1282        assert_eq!(field1.description, None);
1283
1284        let field2 =
1285            FieldInfo::new("name", "name", SqlType::Text).description("The full name of the user");
1286        assert_eq!(field2.description, Some("The full name of the user"));
1287    }
1288
1289    #[test]
1290    fn test_field_info_schema_extra() {
1291        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1292        assert_eq!(field1.schema_extra, None);
1293
1294        let field2 =
1295            FieldInfo::new("name", "name", SqlType::Text).schema_extra(r#"{"examples": ["John"]}"#);
1296        assert_eq!(field2.schema_extra, Some(r#"{"examples": ["John"]}"#));
1297    }
1298
1299    #[test]
1300    fn test_field_info_all_schema_metadata() {
1301        let field = FieldInfo::new("name", "name", SqlType::Text)
1302            .title("User Name")
1303            .description("The full name of the user")
1304            .schema_extra(r#"{"examples": ["John Doe"]}"#);
1305
1306        assert_eq!(field.title, Some("User Name"));
1307        assert_eq!(field.description, Some("The full name of the user"));
1308        assert_eq!(field.schema_extra, Some(r#"{"examples": ["John Doe"]}"#));
1309    }
1310
1311    #[test]
1312    fn test_field_info_const_field() {
1313        // Default should be false
1314        let field1 = FieldInfo::new("version", "version", SqlType::Text);
1315        assert!(!field1.const_field);
1316
1317        // Explicitly set to true
1318        let field2 = FieldInfo::new("version", "version", SqlType::Text).const_field(true);
1319        assert!(field2.const_field);
1320
1321        // Can combine with other attributes
1322        let field3 = FieldInfo::new("version", "version", SqlType::Text)
1323            .const_field(true)
1324            .default("'1.0.0'");
1325        assert!(field3.const_field);
1326        assert_eq!(field3.default, Some("'1.0.0'"));
1327    }
1328
1329    #[test]
1330    fn test_field_info_column_constraints() {
1331        static CONSTRAINTS: &[&str] = &["CHECK(price > 0)", "CHECK(price < 1000)"];
1332
1333        // Default should be empty
1334        let field1 = FieldInfo::new("price", "price", SqlType::Integer);
1335        assert!(field1.column_constraints.is_empty());
1336
1337        // With constraints
1338        let field2 =
1339            FieldInfo::new("price", "price", SqlType::Integer).column_constraints(CONSTRAINTS);
1340        assert_eq!(field2.column_constraints.len(), 2);
1341        assert_eq!(field2.column_constraints[0], "CHECK(price > 0)");
1342    }
1343
1344    #[test]
1345    fn test_field_info_column_comment() {
1346        // Default should be None
1347        let field1 = FieldInfo::new("name", "name", SqlType::Text);
1348        assert_eq!(field1.column_comment, None);
1349
1350        // With comment
1351        let field2 =
1352            FieldInfo::new("name", "name", SqlType::Text).column_comment("User's display name");
1353        assert_eq!(field2.column_comment, Some("User's display name"));
1354    }
1355
1356    #[test]
1357    fn test_field_info_column_info() {
1358        // Default should be None
1359        let field1 = FieldInfo::new("email", "email", SqlType::Text);
1360        assert_eq!(field1.column_info, None);
1361
1362        // With info
1363        let field2 =
1364            FieldInfo::new("email", "email", SqlType::Text).column_info(r#"{"deprecated": true}"#);
1365        assert_eq!(field2.column_info, Some(r#"{"deprecated": true}"#));
1366    }
1367
1368    #[test]
1369    fn test_field_info_discriminator() {
1370        // Default should be None
1371        let field1 = FieldInfo::new("pet", "pet", SqlType::Json);
1372        assert_eq!(field1.discriminator, None);
1373
1374        // With discriminator
1375        let field2 = FieldInfo::new("pet", "pet", SqlType::Json).discriminator("pet_type");
1376        assert_eq!(field2.discriminator, Some("pet_type"));
1377
1378        // With discriminator_opt(None)
1379        let field3 = FieldInfo::new("pet", "pet", SqlType::Json).discriminator_opt(None);
1380        assert_eq!(field3.discriminator, None);
1381
1382        // With discriminator_opt(Some)
1383        let field4 = FieldInfo::new("pet", "pet", SqlType::Json).discriminator_opt(Some("kind"));
1384        assert_eq!(field4.discriminator, Some("kind"));
1385    }
1386
1387    // =========================================================================
1388    // InheritanceStrategy Tests
1389    // =========================================================================
1390
1391    #[test]
1392    fn test_inheritance_strategy_default() {
1393        let strategy = InheritanceStrategy::default();
1394        assert_eq!(strategy, InheritanceStrategy::None);
1395        assert!(!strategy.is_inheritance());
1396        assert!(!strategy.uses_discriminator());
1397        assert!(!strategy.requires_join());
1398    }
1399
1400    #[test]
1401    fn test_inheritance_strategy_single() {
1402        let strategy = InheritanceStrategy::Single;
1403        assert!(strategy.is_inheritance());
1404        assert!(strategy.uses_discriminator());
1405        assert!(!strategy.requires_join());
1406    }
1407
1408    #[test]
1409    fn test_inheritance_strategy_joined() {
1410        let strategy = InheritanceStrategy::Joined;
1411        assert!(strategy.is_inheritance());
1412        assert!(!strategy.uses_discriminator());
1413        assert!(strategy.requires_join());
1414    }
1415
1416    #[test]
1417    fn test_inheritance_strategy_concrete() {
1418        let strategy = InheritanceStrategy::Concrete;
1419        assert!(strategy.is_inheritance());
1420        assert!(!strategy.uses_discriminator());
1421        assert!(!strategy.requires_join());
1422    }
1423
1424    // =========================================================================
1425    // InheritanceInfo Tests
1426    // =========================================================================
1427
1428    #[test]
1429    fn test_inheritance_info_none() {
1430        let info = InheritanceInfo::none();
1431        assert_eq!(info.strategy, InheritanceStrategy::None);
1432        assert!(info.parent.is_none());
1433        assert!(info.discriminator_value.is_none());
1434        assert!(!info.is_child());
1435        assert!(!info.is_base());
1436    }
1437
1438    #[test]
1439    fn test_inheritance_info_single_table_base() {
1440        let info = InheritanceInfo::single_table();
1441        assert_eq!(info.strategy, InheritanceStrategy::Single);
1442        assert!(info.parent.is_none());
1443        assert!(info.is_base());
1444        assert!(!info.is_child());
1445    }
1446
1447    #[test]
1448    fn test_inheritance_info_joined_table_base() {
1449        let info = InheritanceInfo::joined_table();
1450        assert_eq!(info.strategy, InheritanceStrategy::Joined);
1451        assert!(info.parent.is_none());
1452        assert!(info.is_base());
1453        assert!(!info.is_child());
1454    }
1455
1456    #[test]
1457    fn test_inheritance_info_concrete_table_base() {
1458        let info = InheritanceInfo::concrete_table();
1459        assert_eq!(info.strategy, InheritanceStrategy::Concrete);
1460        assert!(info.parent.is_none());
1461        assert!(info.is_base());
1462        assert!(!info.is_child());
1463    }
1464
1465    #[test]
1466    fn test_inheritance_info_child() {
1467        let info = InheritanceInfo::child("Employee");
1468        assert_eq!(info.parent, Some("Employee"));
1469        assert!(info.is_child());
1470        assert!(!info.is_base());
1471    }
1472
1473    #[test]
1474    fn test_inheritance_info_child_with_discriminator_value() {
1475        let info = InheritanceInfo::child("Employee").with_discriminator_value("manager");
1476        assert_eq!(info.parent, Some("Employee"));
1477        assert_eq!(info.discriminator_value, Some("manager"));
1478        assert!(info.is_child());
1479    }
1480
1481    #[test]
1482    fn test_inheritance_info_single_table_with_discriminator_column() {
1483        let info = InheritanceInfo::single_table().with_discriminator_column("type");
1484        assert_eq!(info.strategy, InheritanceStrategy::Single);
1485        assert_eq!(info.discriminator_column, Some("type"));
1486        assert!(info.is_base());
1487    }
1488}