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
10pub trait EncodeColumn<DB> {
12 fn column_type(&self) -> &str;
14
15 fn encode_value<'a>(&self, value: Option<&'a JsonValue>) -> Cow<'a, str>;
17
18 fn format_value<'a>(&self, value: &'a str) -> Cow<'a, str>;
20
21 fn format_filter(&self, key: &str, value: &JsonValue) -> String;
23}
24
25pub(super) trait ColumnExt {
27 fn is_compatible(&self, data_type: &str) -> bool;
29
30 fn type_annotation(&self) -> &'static str;
32
33 fn field_definition(&self, primary_key_name: &str) -> String;
35
36 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 ""
107 };
108 } else if self.auto_random() {
109 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}