1use super::{IntoSqlValue, query::QueryExt};
2use std::{
3 fmt::{self, Display},
4 marker::PhantomData,
5};
6use zino_core::{
7 JsonValue,
8 model::{Model, Query},
9};
10
11pub trait Entity: Model {
13 type Column: ModelColumn<Self>;
15
16 const PRIMARY_KEY: Self::Column;
18
19 #[inline]
21 fn format_column(col: &Self::Column) -> String {
22 [Self::MODEL_NAME, ".", col.as_ref()].concat()
23 }
24}
25
26pub trait ModelColumn<E: Entity>: AsRef<str> + Display {
28 fn into_column_expr(self) -> String;
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub struct DerivedColumn<E: Entity> {
35 expr: String,
37 phantom: PhantomData<E>,
39}
40
41impl<E: Entity> DerivedColumn<E> {
42 #[inline]
44 pub fn new(expr: String) -> Self {
45 Self {
46 expr,
47 phantom: PhantomData,
48 }
49 }
50
51 #[inline]
53 pub fn alias(alias: &str) -> Self {
54 Self::new(alias.to_owned())
55 }
56
57 pub fn coalesce<V: IntoSqlValue>(col: E::Column, value: V) -> Self {
59 let col_name = E::format_column(&col);
60 let field = Query::format_field(&col_name);
61 let expr = match value.into_sql_value() {
62 JsonValue::Null => format!("coalesce({field}, NULL)"),
63 JsonValue::Bool(b) => {
64 if b {
65 format!("coalesce({field}, TRUE)")
66 } else {
67 format!("coalesce({field}, FALSE)")
68 }
69 }
70 JsonValue::Number(n) => {
71 format!("coalesce({field}, {n})")
72 }
73 JsonValue::String(s) => {
74 let value = Query::escape_string(s);
75 format!("coalesce({field}, {value})")
76 }
77 value => {
78 let value = Query::escape_string(value);
79 format!("coalesce({field}, {value})")
80 }
81 };
82 Self::new(expr)
83 }
84
85 #[inline]
87 pub fn year(col: E::Column) -> Self {
88 let col_name = E::format_column(&col);
89 let field = Query::format_field(&col_name);
90 let expr = if cfg!(feature = "orm-sqlite") {
91 format!("strftime('%Y', {field})")
92 } else {
93 format!("year({field})")
94 };
95 Self::new(expr)
96 }
97
98 #[inline]
100 pub fn year_month(col: E::Column) -> Self {
101 let col_name = E::format_column(&col);
102 let field = Query::format_field(&col_name);
103 let expr = if cfg!(any(
104 feature = "orm-mariadb",
105 feature = "orm-mysql",
106 feature = "orm-tidb"
107 )) {
108 format!("date_format({field}, '%Y-%m')")
109 } else if cfg!(feature = "orm-postgres") {
110 format!("to_char({field}, 'YYYY-MM')")
111 } else {
112 format!("strftime('%Y-%m', {field})")
113 };
114 Self::new(expr)
115 }
116
117 #[inline]
119 pub fn date(col: E::Column) -> Self {
120 let col_name = E::format_column(&col);
121 let field = Query::format_field(&col_name);
122 Self::new(format!("date({field})"))
123 }
124
125 #[inline]
127 pub fn format_date_time(col: E::Column) -> Self {
128 let col_name = E::format_column(&col);
129 let field = Query::format_field(&col_name);
130 let expr = if cfg!(any(
131 feature = "orm-mariadb",
132 feature = "orm-mysql",
133 feature = "orm-tidb"
134 )) {
135 format!("date_format({field}, '%Y-%m-%d %H:%i:%s')")
136 } else if cfg!(feature = "orm-postgres") {
137 format!("to_char({field}, 'YYYY-MM-DD HH24:MI:SS')")
138 } else {
139 format!("strftime('%Y-%m-%d %H:%M:%S', {field})")
140 };
141 Self::new(expr)
142 }
143
144 #[inline]
146 pub fn json_extract(col: E::Column, path: &str) -> Self {
147 let col_name = E::format_column(&col);
148 let field = Query::format_field(&col_name);
149 let expr = if cfg!(feature = "orm-postgres") {
150 let path = path.strip_prefix("$.").unwrap_or(path).replace('.', ", ");
151 format!(r#"({field} #>> '{{{path}}}')"#)
152 } else {
153 format!(r#"json_unquote(json_extract({field}, '{path}'))"#)
154 };
155 Self::new(expr)
156 }
157}
158
159impl<E: Entity> AsRef<str> for DerivedColumn<E> {
160 #[inline]
161 fn as_ref(&self) -> &str {
162 self.expr.as_str()
163 }
164}
165
166impl<E: Entity> Display for DerivedColumn<E> {
167 #[inline]
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 self.expr.fmt(f)
170 }
171}
172
173impl<E: Entity> ModelColumn<E> for DerivedColumn<E> {
174 #[inline]
175 fn into_column_expr(self) -> String {
176 self.expr
177 }
178}