zino_orm/
column.rs

1use super::query::QueryExt;
2use convert_case::{Case, Casing};
3use std::borrow::Cow;
4use zino_core::{
5    JsonValue,
6    extension::JsonObjectExt,
7    model::{Column, Query},
8};
9
10/// Encoding a column to be sent to the database.
11pub trait EncodeColumn<DB> {
12    /// Returns the corresponding column type in the database.
13    fn column_type(&self) -> &str;
14
15    /// Encodes a json value as a column value represented by a str.
16    fn encode_value<'a>(&self, value: Option<&'a JsonValue>) -> Cow<'a, str>;
17
18    /// Formats a string value for the column.
19    fn format_value<'a>(&self, value: &'a str) -> Cow<'a, str>;
20
21    /// Formats a column filter.
22    fn format_filter(&self, key: &str, value: &JsonValue) -> String;
23}
24
25/// Extension trait for [`Column`].
26pub(super) trait ColumnExt {
27    /// Returns `true` if it is compatible with the given data type.
28    fn is_compatible(&self, data_type: &str) -> bool;
29
30    /// Returns the type annotation.
31    fn type_annotation(&self) -> &'static str;
32
33    /// Returns the field definition.
34    fn field_definition(&self, primary_key_name: &str) -> String;
35
36    /// Returns the constraints.
37    fn constraints(&self) -> Vec<String>;
38}
39
40impl ColumnExt for Column<'_> {
41    fn is_compatible(&self, data_type: &str) -> bool {
42        let column_type = self.column_type();
43        if column_type.eq_ignore_ascii_case(data_type) {
44            return true;
45        }
46
47        let data_type = data_type.to_ascii_uppercase();
48        match column_type {
49            "INT" => data_type == "INTEGER",
50            "SMALLINT UNSIGNED" | "SMALLSERIAL" => data_type == "SMALLINT",
51            "INT UNSIGNED" | "SERIAL" => data_type == "INT",
52            "BIGINT UNSIGNED" | "BIGSERIAL" => data_type == "BIGINT",
53            "TEXT" => data_type == "VARCHAR",
54            _ => {
55                if cfg!(feature = "orm-postgres") && column_type.ends_with("[]") {
56                    data_type == "ARRAY"
57                } else if column_type.starts_with("TIMESTAMP") {
58                    data_type.starts_with("TIMESTAMP")
59                } else if column_type.starts_with("VARCHAR") {
60                    matches!(
61                        data_type.as_str(),
62                        "TEXT" | "VARCHAR" | "CHARACTER VARYING" | "ENUM"
63                    )
64                } else {
65                    false
66                }
67            }
68        }
69    }
70
71    fn type_annotation(&self) -> &'static str {
72        if cfg!(feature = "orm-postgres") {
73            match self.column_type() {
74                "UUID" => "::UUID",
75                "BIGINT" | "BIGSERIAL" => "::BIGINT",
76                "INT" | "SERIAL" => "::INT",
77                "SMALLINT" | "SMALLSERIAL" => "::SMALLINT",
78                _ => "::TEXT",
79            }
80        } else {
81            ""
82        }
83    }
84
85    fn field_definition(&self, primary_key_name: &str) -> String {
86        let column_name = self
87            .extra()
88            .get_str("column_name")
89            .unwrap_or_else(|| self.name());
90        let column_field = Query::format_field(column_name);
91        let column_type = self.column_type();
92        let mut definition = format!("{column_field} {column_type}");
93        if column_name == primary_key_name {
94            definition += " PRIMARY KEY";
95        }
96        if let Some(value) = self.default_value() {
97            if self.auto_increment() {
98                definition += if cfg!(any(
99                    feature = "orm-mariadb",
100                    feature = "orm-mysql",
101                    feature = "orm-tidb"
102                )) {
103                    " AUTO_INCREMENT"
104                } else {
105                    // PostgreSQL does not support `AUTO INCREMENT` and SQLite does not need it.
106                    ""
107                };
108            } else if self.auto_random() {
109                // Only TiDB supports this feature.
110                definition += if cfg!(feature = "orm-tidb") {
111                    " AUTO_RANDOM"
112                } else {
113                    ""
114                };
115            } else {
116                let value = self.format_value(value);
117                if cfg!(feature = "orm-sqlite") && value.contains('(') {
118                    definition = format!("{definition} DEFAULT ({value})");
119                } else {
120                    definition = format!("{definition} DEFAULT {value}");
121                }
122            }
123        } else if self.is_not_null() {
124            definition += " NOT NULL";
125        }
126        if cfg!(any(
127            feature = "orm-mariadb",
128            feature = "orm-mysql",
129            feature = "orm-tidb"
130        )) && let Some(comment) = self.comment()
131        {
132            definition = format!("{definition} COMMENT '{comment}'");
133        }
134        definition
135    }
136
137    fn constraints(&self) -> Vec<String> {
138        let mut constraints = Vec::new();
139        let extra = self.extra();
140        let column_name = self
141            .extra()
142            .get_str("column_name")
143            .unwrap_or_else(|| self.name());
144        if let Some(reference) = self
145            .reference()
146            .filter(|_| extra.contains_key("foreign_key"))
147        {
148            let column_field = Query::format_field(column_name);
149            let parent_table = Query::format_field(reference.name());
150            let parent_column_field = Query::format_field(reference.column_name());
151            let mut constraint = format!(
152                "FOREIGN KEY ({column_field}) REFERENCES {parent_table}({parent_column_field})"
153            );
154            if let Some(action) = extra.get_str("on_delete") {
155                constraint.push_str(" ON DELETE ");
156                constraint.push_str(&action.to_case(Case::Upper));
157            }
158            if let Some(action) = extra.get_str("on_update") {
159                constraint.push_str(" ON UPDATE ");
160                constraint.push_str(&action.to_case(Case::Upper));
161            }
162            constraints.push(constraint);
163        }
164        constraints
165    }
166}