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.to_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.to_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.to_sql().push(Token::AS).append(SQL::raw(target_type)),
98 ))
99}
100
101// =============================================================================
102// STRING CONCATENATION
103// =============================================================================
104
105/// Concatenate two string expressions using || operator.
106///
107/// Requires both operands to be `Textual` (Text or VarChar).
108/// Nullability follows SQL concatenation rules: nullable input -> nullable output.
109///
110/// # Type Safety
111///
112/// ```ignore
113/// // ✅ OK: Both are Text
114/// string_concat(users.first_name, users.last_name);
115///
116/// // ✅ OK: Text with string literal
117/// string_concat(users.first_name, " ");
118///
119/// // ❌ Compile error: Int is not Textual
120/// string_concat(users.id, users.name);
121/// ```
122///
123/// # Example
124///
125/// ```ignore
126/// use drizzle_core::expr::string_concat;
127///
128/// // SELECT users.first_name || ' ' || users.last_name
129/// let full_name = string_concat(string_concat(users.first_name, " "), users.last_name);
130/// ```
131pub fn string_concat<'a, V, L, R>(
132 left: L,
133 right: R,
134) -> SQLExpr<'a, V, crate::types::Text, <L::Nullable as NullOr<R::Nullable>>::Output, Scalar>
135where
136 V: SQLParam + 'a,
137 L: Expr<'a, V>,
138 R: Expr<'a, V>,
139 L::SQLType: Textual,
140 R::SQLType: Textual,
141 L::Nullable: NullOr<R::Nullable>,
142 R::Nullable: Nullability,
143{
144 super::concat(left, right)
145}
146
147// =============================================================================
148// RAW SQL Expression
149// =============================================================================
150
151/// Create a raw SQL expression with a specified type.
152///
153/// Use this for dialect-specific features or when the type system
154/// can't infer the correct type.
155///
156/// # Safety
157///
158/// This bypasses type checking. Use sparingly and only when necessary.
159///
160/// # Example
161///
162/// ```ignore
163/// use drizzle_core::expr::raw;
164/// use drizzle_core::types::Int;
165///
166/// let expr = raw::<_, Int>("RANDOM()");
167/// ```
168pub fn raw<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, Null, Scalar>
169where
170 V: SQLParam + 'a,
171 T: DataType,
172{
173 SQLExpr::new(SQL::raw(sql))
174}
175
176/// Create a raw SQL expression with explicit nullability.
177pub fn raw_non_null<'a, V, T>(sql: &'a str) -> SQLExpr<'a, V, T, NonNull, Scalar>
178where
179 V: SQLParam + 'a,
180 T: DataType,
181{
182 SQLExpr::new(SQL::raw(sql))
183}