drizzle_core/expr/util.rs
1//! Utility SQL functions (alias, cast, distinct, typeof, concat).
2
3use crate::sql::{SQL, Token};
4use crate::traits::{SQLParam, ToSQL};
5use crate::types::{DataType, Textual};
6
7use super::{Expr, NonNull, Null, NullOr, Nullability, SQLExpr, Scalar};
8
9// =============================================================================
10// ALIAS
11// =============================================================================
12
13/// Create an aliased expression.
14///
15/// # Example
16///
17/// ```ignore
18/// use drizzle_core::expr::alias;
19///
20/// // SELECT users.first_name || users.last_name AS full_name
21/// let full_name = alias(string_concat(users.first_name, users.last_name), "full_name");
22/// ```
23pub fn alias<'a, V, E>(expr: E, name: &'a str) -> SQL<'a, V>
24where
25 V: SQLParam + 'a,
26 E: ToSQL<'a, V>,
27{
28 expr.into_sql().alias(name)
29}
30
31// =============================================================================
32// TYPEOF
33// =============================================================================
34
35/// Get the SQL type of an expression.
36///
37/// Returns the data type name as text.
38///
39/// # Example
40///
41/// ```ignore
42/// use drizzle_core::expr::typeof_;
43///
44/// // SELECT TYPEOF(users.age) -- returns "integer"
45/// let age_type = typeof_(users.age);
46/// ```
47pub fn typeof_<'a, V, E>(expr: E) -> SQLExpr<'a, V, crate::types::Text, NonNull, Scalar>
48where
49 V: SQLParam + 'a,
50 E: ToSQL<'a, V>,
51{
52 SQLExpr::new(SQL::func("TYPEOF", expr.into_sql()))
53}
54
55/// Alias for typeof_ (uses Rust raw identifier syntax).
56pub fn r#typeof<'a, V, E>(expr: E) -> SQLExpr<'a, V, crate::types::Text, NonNull, Scalar>
57where
58 V: SQLParam + 'a,
59 E: ToSQL<'a, V>,
60{
61 typeof_(expr)
62}
63
64// =============================================================================
65// CAST
66// =============================================================================
67
68/// Cast an expression to a different type.
69///
70/// The target type marker specifies the result type for the type system,
71/// while the SQL type string specifies the actual SQL type name (dialect-specific).
72/// Preserves the input expression's nullability and aggregate marker.
73///
74/// # Example
75///
76/// ```ignore
77/// use drizzle_core::expr::cast;
78/// use drizzle_core::types::Text;
79///
80/// // SELECT CAST(users.age AS TEXT)
81/// let age_text = cast::<_, _, _, Text>(users.age, "TEXT");
82///
83/// // PostgreSQL-specific
84/// let age_text = cast::<_, _, _, Text>(users.age, "VARCHAR(255)");
85/// ```
86pub fn cast<'a, V, E, Target>(
87 expr: E,
88 target_type: &'a str,
89) -> SQLExpr<'a, V, Target, E::Nullable, E::Aggregate>
90where
91 V: SQLParam + 'a,
92 E: Expr<'a, V>,
93 Target: DataType,
94{
95 SQLExpr::new(SQL::func(
96 "CAST",
97 expr.into_sql()
98 .push(Token::AS)
99 .append(SQL::raw(target_type)),
100 ))
101}
102
103// =============================================================================
104// STRING CONCATENATION
105// =============================================================================
106
107/// Concatenate two string expressions using || operator.
108///
109/// Requires both operands to be `Textual` (Text or VarChar).
110/// Nullability follows SQL concatenation rules: nullable input -> nullable output.
111///
112/// # Type Safety
113///
114/// ```ignore
115/// // ✅ OK: Both are Text
116/// string_concat(users.first_name, users.last_name);
117///
118/// // ✅ OK: Text with string literal
119/// string_concat(users.first_name, " ");
120///
121/// // ❌ Compile error: Int is not Textual
122/// string_concat(users.id, users.name);
123/// ```
124///
125/// # Example
126///
127/// ```ignore
128/// use drizzle_core::expr::string_concat;
129///
130/// // SELECT users.first_name || ' ' || users.last_name
131/// let full_name = string_concat(string_concat(users.first_name, " "), users.last_name);
132/// ```
133pub fn string_concat<'a, V, L, R>(
134 left: L,
135 right: R,
136) -> SQLExpr<'a, V, crate::types::Text, <L::Nullable as NullOr<R::Nullable>>::Output, Scalar>
137where
138 V: SQLParam + 'a,
139 L: Expr<'a, V>,
140 R: Expr<'a, V>,
141 L::SQLType: Textual,
142 R::SQLType: Textual,
143 L::Nullable: NullOr<R::Nullable>,
144 R::Nullable: Nullability,
145{
146 super::concat(left, right)
147}
148
149// =============================================================================
150// RAW SQL Expression
151// =============================================================================
152
153/// Create a raw SQL expression with a specified type.
154///
155/// Use this for dialect-specific features or when the type system
156/// can't infer the correct type.
157///
158/// # Safety
159///
160/// This bypasses type checking. Use sparingly and only when necessary.
161///
162/// # Example
163///
164/// ```ignore
165/// use drizzle_core::expr::raw;
166/// use drizzle_core::types::Int;
167///
168/// let expr = raw::<_, Int>("RANDOM()");
169/// ```
170pub fn raw<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, Null, Scalar>
171where
172 V: SQLParam + 'a,
173 T: DataType,
174{
175 SQLExpr::new(SQL::raw(sql))
176}
177
178/// Create a raw SQL expression with explicit nullability.
179pub fn raw_non_null<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, NonNull, Scalar>
180where
181 V: SQLParam + 'a,
182 T: DataType,
183{
184 SQLExpr::new(SQL::raw(sql))
185}