1use std::marker::PhantomData;
22
23use crate::expr::{Expr, OrderDirection, OrderExpr};
24use crate::value::Value;
25
26#[derive(Debug, Clone)]
32pub struct Column<T> {
33 name: &'static str,
34 table: &'static str,
35 _phantom: PhantomData<T>,
36}
37
38impl<T> Column<T> {
39 pub const fn new(name: &'static str, table: &'static str) -> Self {
41 Self {
42 name,
43 table,
44 _phantom: PhantomData,
45 }
46 }
47
48 pub const fn name(&self) -> &'static str {
50 self.name
51 }
52
53 pub const fn table(&self) -> &'static str {
55 self.table
56 }
57
58 pub const fn into_any(&self) -> AnyColumn {
60 AnyColumn {
61 name: self.name,
62 table: self.table,
63 }
64 }
65
66 fn any(&self) -> AnyColumn {
67 self.into_any()
68 }
69}
70
71impl<T> Column<T>
74where
75 T: Into<Value> + Clone,
76{
77 pub fn eq(&self, val: impl Into<Value>) -> Expr {
79 Expr::Eq(self.any(), val.into())
80 }
81
82 pub fn ne(&self, val: impl Into<Value>) -> Expr {
84 Expr::Ne(self.any(), val.into())
85 }
86
87 pub fn gt(&self, val: impl Into<Value>) -> Expr {
89 Expr::Gt(self.any(), val.into())
90 }
91
92 pub fn gte(&self, val: impl Into<Value>) -> Expr {
94 Expr::Gte(self.any(), val.into())
95 }
96
97 pub fn lt(&self, val: impl Into<Value>) -> Expr {
99 Expr::Lt(self.any(), val.into())
100 }
101
102 pub fn lte(&self, val: impl Into<Value>) -> Expr {
104 Expr::Lte(self.any(), val.into())
105 }
106
107 pub fn between(&self, low: impl Into<Value>, high: impl Into<Value>) -> Expr {
109 Expr::Between(self.any(), low.into(), high.into())
110 }
111
112 pub fn in_list(&self, vals: Vec<impl Into<Value>>) -> Expr {
114 let values: Vec<Value> = vals.into_iter().map(|v| v.into()).collect();
115 Expr::InList(self.any(), values)
116 }
117
118 pub fn not_in(&self, vals: Vec<impl Into<Value>>) -> Expr {
120 let values: Vec<Value> = vals.into_iter().map(|v| v.into()).collect();
121 Expr::NotIn(self.any(), values)
122 }
123}
124
125impl<T> Column<T> {
128 pub fn like(&self, pattern: impl Into<Value>) -> Expr {
130 Expr::Like(self.any(), pattern.into())
131 }
132
133 pub fn ilike(&self, pattern: impl Into<Value>) -> Expr {
135 Expr::ILike(self.any(), pattern.into())
136 }
137
138 pub fn contains(&self, val: &str) -> Expr {
140 Expr::Like(self.any(), Value::String(format!("%{}%", val)))
141 }
142
143 pub fn starts_with(&self, val: &str) -> Expr {
145 Expr::Like(self.any(), Value::String(format!("{}%", val)))
146 }
147
148 pub fn ends_with(&self, val: &str) -> Expr {
150 Expr::Like(self.any(), Value::String(format!("%{}", val)))
151 }
152}
153
154impl<T> Column<T> {
157 pub fn is_null(&self) -> Expr {
159 Expr::IsNull(self.any())
160 }
161
162 pub fn is_not_null(&self) -> Expr {
164 Expr::IsNotNull(self.any())
165 }
166}
167
168impl<T> Column<T> {
171 pub fn asc(&self) -> OrderExpr {
173 OrderExpr::new(self.any(), OrderDirection::Asc)
174 }
175
176 pub fn desc(&self) -> OrderExpr {
178 OrderExpr::new(self.any(), OrderDirection::Desc)
179 }
180
181 pub fn asc_nulls_last(&self) -> OrderExpr {
183 OrderExpr::new(self.any(), OrderDirection::AscNullsLast)
184 }
185
186 pub fn desc_nulls_first(&self) -> OrderExpr {
188 OrderExpr::new(self.any(), OrderDirection::DescNullsFirst)
189 }
190}
191
192#[derive(Debug, Clone)]
196pub struct AnyColumn {
197 name: &'static str,
198 table: &'static str,
199}
200
201impl AnyColumn {
202 pub const fn new(name: &'static str, table: &'static str) -> Self {
203 Self { name, table }
204 }
205
206 pub const fn name(&self) -> &'static str {
207 self.name
208 }
209
210 pub const fn table(&self) -> &'static str {
211 self.table
212 }
213}
214
215#[cfg(test)]
218mod tests {
219 use super::*;
220
221 fn col_id() -> Column<i32> {
222 Column::new("id", "users")
223 }
224
225 fn col_age() -> Column<i32> {
226 Column::new("age", "users")
227 }
228
229 fn col_name() -> Column<String> {
230 Column::new("name", "users")
231 }
232
233 fn col_email() -> Column<String> {
234 Column::new("email", "users")
235 }
236
237 #[test]
240 fn column_name_and_table() {
241 let col = col_age();
242 assert_eq!(col.name(), "age");
243 assert_eq!(col.table(), "users");
244 }
245
246 #[test]
247 fn column_into_any() {
248 let any = col_age().into_any();
249 assert_eq!(any.name(), "age");
250 assert_eq!(any.table(), "users");
251 }
252
253 #[test]
256 fn column_eq() {
257 let expr = col_age().eq(25);
258 match expr {
259 Expr::Eq(col, Value::Int(25)) => {
260 assert_eq!(col.name(), "age");
261 }
262 _ => panic!("Expected Eq with Int(25)"),
263 }
264 }
265
266 #[test]
267 fn column_ne() {
268 let expr = col_age().ne(0);
269 assert!(matches!(expr, Expr::Ne(_, Value::Int(0))));
270 }
271
272 #[test]
273 fn column_gt() {
274 let expr = col_age().gt(25);
275 assert!(matches!(expr, Expr::Gt(_, Value::Int(25))));
276 }
277
278 #[test]
279 fn column_gte() {
280 let expr = col_age().gte(18);
281 assert!(matches!(expr, Expr::Gte(_, Value::Int(18))));
282 }
283
284 #[test]
285 fn column_lt() {
286 let expr = col_age().lt(65);
287 assert!(matches!(expr, Expr::Lt(_, Value::Int(65))));
288 }
289
290 #[test]
291 fn column_lte() {
292 let expr = col_age().lte(100);
293 assert!(matches!(expr, Expr::Lte(_, Value::Int(100))));
294 }
295
296 #[test]
299 fn column_between() {
300 let expr = col_age().between(18, 65);
301 match expr {
302 Expr::Between(col, Value::Int(18), Value::Int(65)) => {
303 assert_eq!(col.name(), "age");
304 }
305 _ => panic!("Expected Between(18, 65)"),
306 }
307 }
308
309 #[test]
310 fn column_in_list() {
311 let expr = col_id().in_list(vec![1, 2, 3]);
312 match expr {
313 Expr::InList(col, vals) => {
314 assert_eq!(col.name(), "id");
315 assert_eq!(vals.len(), 3);
316 assert_eq!(vals[0], Value::Int(1));
317 assert_eq!(vals[1], Value::Int(2));
318 assert_eq!(vals[2], Value::Int(3));
319 }
320 _ => panic!("Expected InList"),
321 }
322 }
323
324 #[test]
325 fn column_in_list_empty() {
326 let expr = col_id().in_list(Vec::<i32>::new());
327 match expr {
328 Expr::InList(_, vals) => assert!(vals.is_empty()),
329 _ => panic!("Expected InList"),
330 }
331 }
332
333 #[test]
334 fn column_not_in() {
335 let expr = col_id().not_in(vec![4, 5]);
336 match expr {
337 Expr::NotIn(col, vals) => {
338 assert_eq!(col.name(), "id");
339 assert_eq!(vals.len(), 2);
340 }
341 _ => panic!("Expected NotIn"),
342 }
343 }
344
345 #[test]
348 fn column_like() {
349 let expr = col_name().like("A%");
350 match expr {
351 Expr::Like(col, Value::String(ref s)) => {
352 assert_eq!(col.name(), "name");
353 assert_eq!(s, "A%");
354 }
355 _ => panic!("Expected Like"),
356 }
357 }
358
359 #[test]
360 fn column_ilike() {
361 let expr = col_name().ilike("alice%");
362 assert!(matches!(expr, Expr::ILike(_, _)));
363 }
364
365 #[test]
366 fn column_contains() {
367 let expr = col_name().contains("foo");
368 match expr {
369 Expr::Like(_, Value::String(ref s)) => {
370 assert_eq!(s, "%foo%");
371 }
372 _ => panic!("Expected Like with %foo%"),
373 }
374 }
375
376 #[test]
377 fn column_starts_with() {
378 let expr = col_name().starts_with("A");
379 match expr {
380 Expr::Like(_, Value::String(ref s)) => {
381 assert_eq!(s, "A%");
382 }
383 _ => panic!("Expected Like with A%"),
384 }
385 }
386
387 #[test]
388 fn column_ends_with() {
389 let expr = col_name().ends_with("z");
390 match expr {
391 Expr::Like(_, Value::String(ref s)) => {
392 assert_eq!(s, "%z");
393 }
394 _ => panic!("Expected Like with %z"),
395 }
396 }
397
398 #[test]
401 fn column_is_null() {
402 let expr = col_email().is_null();
403 match expr {
404 Expr::IsNull(col) => assert_eq!(col.name(), "email"),
405 _ => panic!("Expected IsNull"),
406 }
407 }
408
409 #[test]
410 fn column_is_not_null() {
411 let expr = col_email().is_not_null();
412 match expr {
413 Expr::IsNotNull(col) => assert_eq!(col.name(), "email"),
414 _ => panic!("Expected IsNotNull"),
415 }
416 }
417
418 #[test]
421 fn column_asc() {
422 let o = col_name().asc();
423 assert_eq!(o.column.name(), "name");
424 assert_eq!(o.direction, OrderDirection::Asc);
425 }
426
427 #[test]
428 fn column_desc() {
429 let o = col_age().desc();
430 assert_eq!(o.direction, OrderDirection::Desc);
431 }
432
433 #[test]
434 fn column_asc_nulls_last() {
435 let o = col_age().asc_nulls_last();
436 assert_eq!(o.direction, OrderDirection::AscNullsLast);
437 }
438
439 #[test]
440 fn column_desc_nulls_first() {
441 let o = col_age().desc_nulls_first();
442 assert_eq!(o.direction, OrderDirection::DescNullsFirst);
443 }
444
445 #[test]
448 fn column_chain_and() {
449 let expr = col_age().gt(25).and(col_name().eq("Alice"));
450 assert!(matches!(expr, Expr::And(_, _)));
451 }
452
453 #[test]
454 fn column_chain_or() {
455 let expr = col_age().lt(18).or(col_age().gt(65));
456 assert!(matches!(expr, Expr::Or(_, _)));
457 }
458
459 #[test]
460 fn column_chain_not() {
461 let expr = col_email().is_null().not();
462 assert!(matches!(expr, Expr::Not(_)));
463 }
464
465 #[test]
466 fn column_complex_filter() {
467 let expr = col_age()
469 .between(18, 65)
470 .and(col_email().is_not_null())
471 .or(col_name().eq("admin"));
472
473 match expr {
475 Expr::Or(left, right) => {
476 assert!(matches!(*left, Expr::And(_, _)));
477 assert!(matches!(*right, Expr::Eq(_, _)));
478 }
479 _ => panic!("Expected Or(And(...), Eq(...))"),
480 }
481 }
482
483 #[test]
486 fn column_string_eq() {
487 let expr = col_name().eq("Alice");
488 match expr {
489 Expr::Eq(_, Value::String(ref s)) => assert_eq!(s, "Alice"),
490 _ => panic!("Expected Eq with String"),
491 }
492 }
493
494 #[test]
495 fn column_i16() {
496 let col: Column<i16> = Column::new("age", "users");
497 let expr = col.eq(25i16);
498 assert!(matches!(expr, Expr::Eq(_, Value::Short(25))));
499 }
500
501 #[test]
502 fn column_i64() {
503 let col: Column<i64> = Column::new("big_id", "users");
504 let expr = col.eq(999i64);
505 assert!(matches!(expr, Expr::Eq(_, Value::BigInt(999))));
506 }
507
508 #[test]
509 fn column_bool() {
510 let col: Column<bool> = Column::new("is_active", "users");
511 let expr = col.eq(true);
512 assert!(matches!(expr, Expr::Eq(_, Value::Bool(true))));
513 }
514}