• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / pg_graphql / 19839478330

01 Dec 2025 10:20PM UTC coverage: 83.863% (-7.5%) from 91.36%
19839478330

Pull #613

github

web-flow
Merge 73f7cb2de into 7951d27bb
Pull Request #613: Transpiler re-write to use an AST vs string concatenation

3931 of 5222 new or added lines in 20 files covered. (75.28%)

2 existing lines in 1 file now uncovered.

10160 of 12115 relevant lines covered (83.86%)

919.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.96
/src/ast/expr.rs
1
//! SQL expression types
2
//!
3
//! This module defines all SQL expression types that can appear in queries.
4
//! Expressions are the building blocks of SQL: columns, literals, operators,
5
//! function calls, etc.
6

7
use super::types::SqlType;
8

9
/// A quoted SQL identifier (table name, column name, etc.)
10
///
11
/// Identifiers are always quoted when rendered to prevent SQL injection
12
/// and handle special characters correctly.
13
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14
pub struct Ident(pub String);
15

16
impl Ident {
17
    /// Create a new identifier from any string-like type
18
    #[inline]
19
    pub fn new(s: impl Into<String>) -> Self {
12,358✔
20
        Self(s.into())
12,358✔
21
    }
12,358✔
22

23
    /// Get the identifier as a string slice
24
    #[inline]
25
    pub fn as_str(&self) -> &str {
28,110✔
26
        &self.0
28,110✔
27
    }
28,110✔
28
}
29

30
impl From<String> for Ident {
31
    fn from(s: String) -> Self {
1,381✔
32
        Self(s)
1,381✔
33
    }
1,381✔
34
}
35

36
impl From<&str> for Ident {
37
    fn from(s: &str) -> Self {
12,792✔
38
        Self(s.to_string())
12,792✔
39
    }
12,792✔
40
}
41

42
impl std::fmt::Display for Ident {
NEW
43
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
NEW
44
        write!(f, "{}", self.0)
×
NEW
45
    }
×
46
}
47

48
/// Reference to a column, optionally qualified with a table alias
49
#[derive(Debug, Clone, PartialEq)]
50
pub struct ColumnRef {
51
    /// Table alias (e.g., "t" in "t.id")
52
    pub table_alias: Option<Ident>,
53
    /// Column name
54
    pub column: Ident,
55
}
56

57
impl ColumnRef {
58
    pub fn new(column: impl Into<Ident>) -> Self {
1,189✔
59
        Self {
1,189✔
60
            table_alias: None,
1,189✔
61
            column: column.into(),
1,189✔
62
        }
1,189✔
63
    }
1,189✔
64

65
    pub fn qualified(table: impl Into<Ident>, column: impl Into<Ident>) -> Self {
5,732✔
66
        Self {
5,732✔
67
            table_alias: Some(table.into()),
5,732✔
68
            column: column.into(),
5,732✔
69
        }
5,732✔
70
    }
5,732✔
71
}
72

73
/// Reference to a query parameter ($1, $2, etc.) with type cast
74
#[derive(Debug, Clone, PartialEq)]
75
pub struct ParamRef {
76
    /// 1-indexed parameter number
77
    pub index: usize,
78
    /// Type to cast the parameter to
79
    pub type_cast: SqlType,
80
}
81

82
/// SQL literal values
83
#[derive(Debug, Clone, PartialEq)]
84
pub enum Literal {
85
    /// SQL NULL
86
    Null,
87
    /// Boolean true/false
88
    Bool(bool),
89
    /// Integer literal
90
    Integer(i64),
91
    /// Floating point literal
92
    Float(f64),
93
    /// String literal (will be properly quoted)
94
    String(String),
95
    /// SQL DEFAULT keyword (for INSERT statements)
96
    Default,
97
}
98

99
impl Literal {
NEW
100
    pub fn string(s: impl Into<String>) -> Self {
×
NEW
101
        Self::String(s.into())
×
NEW
102
    }
×
103
}
104

105
/// Binary operators
106
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107
pub enum BinaryOperator {
108
    // Comparison
109
    Eq,
110
    NotEq,
111
    Lt,
112
    LtEq,
113
    Gt,
114
    GtEq,
115

116
    // Array operators
117
    Contains,    // @>
118
    ContainedBy, // <@
119
    Overlap,     // &&
120
    Any,         // = ANY(...)
121

122
    // String operators
123
    Like,
124
    ILike,
125
    RegEx,      // ~
126
    IRegEx,     // ~*
127
    StartsWith, // ^@
128

129
    // Logical
130
    And,
131
    Or,
132

133
    // Arithmetic
134
    Add,
135
    Sub,
136
    Mul,
137
    Div,
138
    Mod,
139

140
    // JSON operators
141
    JsonExtract,     // ->
142
    JsonExtractText, // ->>
143
    JsonPath,        // #>
144
    JsonPathText,    // #>>
145

146
    // JSONB concatenation
147
    JsonConcat, // ||
148
}
149

150
impl BinaryOperator {
151
    /// Get the SQL representation of this operator
152
    pub fn as_sql(&self) -> &'static str {
2,039✔
153
        match self {
2,039✔
154
            Self::Eq => "=",
467✔
155
            Self::NotEq => "<>",
8✔
156
            Self::Lt => "<",
49✔
157
            Self::LtEq => "<=",
4✔
158
            Self::Gt => ">",
380✔
159
            Self::GtEq => ">=",
5✔
160
            Self::Contains => "@>",
13✔
161
            Self::ContainedBy => "<@",
8✔
162
            Self::Overlap => "&&",
4✔
163
            Self::Any => "= any",
288✔
164
            Self::Like => "like",
8✔
165
            Self::ILike => "ilike",
4✔
166
            Self::RegEx => "~",
4✔
167
            Self::IRegEx => "~*",
4✔
168
            Self::StartsWith => "^@",
13✔
169
            Self::And => "and",
474✔
170
            Self::Or => "or",
261✔
NEW
171
            Self::Add => "+",
×
NEW
172
            Self::Sub => "-",
×
NEW
173
            Self::Mul => "*",
×
NEW
174
            Self::Div => "/",
×
NEW
175
            Self::Mod => "%",
×
NEW
176
            Self::JsonExtract => "->",
×
NEW
177
            Self::JsonExtractText => "->>",
×
NEW
178
            Self::JsonPath => "#>",
×
179
            Self::JsonPathText => "#>>",
18✔
180
            Self::JsonConcat => "||",
27✔
181
        }
182
    }
2,039✔
183
}
184

185
/// Unary operators
186
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187
pub enum UnaryOperator {
188
    Not,
189
    Neg,
190
    BitNot,
191
}
192

193
impl UnaryOperator {
194
    pub fn as_sql(&self) -> &'static str {
281✔
195
        match self {
281✔
196
            Self::Not => "not",
281✔
NEW
197
            Self::Neg => "-",
×
NEW
198
            Self::BitNot => "~",
×
199
        }
200
    }
281✔
201
}
202

203
/// Function argument (can be named or positional)
204
#[derive(Debug, Clone, PartialEq)]
205
pub enum FunctionArg {
206
    /// Positional argument
207
    Unnamed(Expr),
208
    /// Named argument (name => value)
209
    Named { name: Ident, value: Expr },
210
}
211

212
impl FunctionArg {
213
    pub fn unnamed(expr: Expr) -> Self {
942✔
214
        Self::Unnamed(expr)
942✔
215
    }
942✔
216

217
    pub fn named(name: impl Into<Ident>, value: Expr) -> Self {
126✔
218
        Self::Named {
126✔
219
            name: name.into(),
126✔
220
            value,
126✔
221
        }
126✔
222
    }
126✔
223
}
224

225
/// A function call expression
226
#[derive(Debug, Clone, PartialEq)]
227
pub struct FunctionCall {
228
    /// Schema (e.g., "pg_catalog", "public")
229
    pub schema: Option<Ident>,
230
    /// Function name
231
    pub name: Ident,
232
    /// Arguments
233
    pub args: Vec<FunctionArg>,
234
    /// FILTER clause (for aggregate functions)
235
    pub filter: Option<Box<Expr>>,
236
    /// ORDER BY within the function (for ordered-set aggregates)
237
    pub order_by: Option<Vec<OrderByExpr>>,
238
}
239

240
impl FunctionCall {
241
    pub fn new(name: impl Into<Ident>, args: Vec<FunctionArg>) -> Self {
1,086✔
242
        Self {
1,086✔
243
            schema: None,
1,086✔
244
            name: name.into(),
1,086✔
245
            args,
1,086✔
246
            filter: None,
1,086✔
247
            order_by: None,
1,086✔
248
        }
1,086✔
249
    }
1,086✔
250

251
    pub fn with_schema(
154✔
252
        schema: impl Into<Ident>,
154✔
253
        name: impl Into<Ident>,
154✔
254
        args: Vec<FunctionArg>,
154✔
255
    ) -> Self {
154✔
256
        Self {
154✔
257
            schema: Some(schema.into()),
154✔
258
            name: name.into(),
154✔
259
            args,
154✔
260
            filter: None,
154✔
261
            order_by: None,
154✔
262
        }
154✔
263
    }
154✔
264

265
    /// Add a FILTER clause
NEW
266
    pub fn with_filter(mut self, filter: Expr) -> Self {
×
NEW
267
        self.filter = Some(Box::new(filter));
×
NEW
268
        self
×
NEW
269
    }
×
270

271
    /// Add ORDER BY within the function
NEW
272
    pub fn with_order_by(mut self, order_by: Vec<OrderByExpr>) -> Self {
×
NEW
273
        self.order_by = Some(order_by);
×
NEW
274
        self
×
NEW
275
    }
×
276
}
277

278
/// Aggregate functions
279
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280
pub enum AggregateFunction {
281
    Count,
282
    Sum,
283
    Avg,
284
    Min,
285
    Max,
286
    JsonAgg,
287
    JsonbAgg,
288
    ArrayAgg,
289
    BoolAnd,
290
    BoolOr,
291
    StringAgg,
292
}
293

294
impl AggregateFunction {
295
    pub fn as_sql(&self) -> &'static str {
1,419✔
296
        match self {
1,419✔
297
            Self::Count => "count",
629✔
NEW
298
            Self::Sum => "sum",
×
NEW
299
            Self::Avg => "avg",
×
NEW
300
            Self::Min => "min",
×
NEW
301
            Self::Max => "max",
×
NEW
302
            Self::JsonAgg => "json_agg",
×
303
            Self::JsonbAgg => "jsonb_agg",
310✔
304
            Self::ArrayAgg => "array_agg",
240✔
305
            Self::BoolAnd => "bool_and",
240✔
NEW
306
            Self::BoolOr => "bool_or",
×
NEW
307
            Self::StringAgg => "string_agg",
×
308
        }
309
    }
1,419✔
310
}
311

312
/// An aggregate expression with optional FILTER and ORDER BY
313
#[derive(Debug, Clone, PartialEq)]
314
pub struct AggregateExpr {
315
    pub function: AggregateFunction,
316
    pub args: Vec<Expr>,
317
    pub distinct: bool,
318
    pub filter: Option<Box<Expr>>,
319
    pub order_by: Option<Vec<OrderByExpr>>,
320
}
321

322
impl AggregateExpr {
323
    pub fn new(function: AggregateFunction, args: Vec<Expr>) -> Self {
1,419✔
324
        Self {
1,419✔
325
            function,
1,419✔
326
            args,
1,419✔
327
            distinct: false,
1,419✔
328
            filter: None,
1,419✔
329
            order_by: None,
1,419✔
330
        }
1,419✔
331
    }
1,419✔
332

333
    pub fn count_star() -> Self {
629✔
334
        Self::new(
629✔
335
            AggregateFunction::Count,
629✔
336
            vec![Expr::Literal(Literal::String("*".to_string()))],
629✔
337
        )
338
    }
629✔
339

NEW
340
    pub fn count_all() -> Self {
×
NEW
341
        Self::new(AggregateFunction::Count, vec![])
×
NEW
342
    }
×
343

NEW
344
    pub fn with_distinct(mut self) -> Self {
×
NEW
345
        self.distinct = true;
×
NEW
346
        self
×
NEW
347
    }
×
348

NEW
349
    pub fn with_filter(mut self, filter: Expr) -> Self {
×
NEW
350
        self.filter = Some(Box::new(filter));
×
NEW
351
        self
×
NEW
352
    }
×
353

NEW
354
    pub fn with_order_by(mut self, order_by: Vec<OrderByExpr>) -> Self {
×
NEW
355
        self.order_by = Some(order_by);
×
NEW
356
        self
×
NEW
357
    }
×
358
}
359

360
/// CASE expression
361
#[derive(Debug, Clone, PartialEq)]
362
pub struct CaseExpr {
363
    /// CASE <operand> (simple case) vs CASE WHEN (searched case)
364
    pub operand: Option<Box<Expr>>,
365
    /// WHEN ... THEN ... pairs
366
    pub when_clauses: Vec<(Expr, Expr)>,
367
    /// ELSE clause
368
    pub else_clause: Option<Box<Expr>>,
369
}
370

371
impl CaseExpr {
372
    /// Create a searched CASE expression (CASE WHEN ... THEN ...)
373
    pub fn searched(when_clauses: Vec<(Expr, Expr)>, else_clause: Option<Expr>) -> Self {
83✔
374
        Self {
83✔
375
            operand: None,
83✔
376
            when_clauses,
83✔
377
            else_clause: else_clause.map(Box::new),
83✔
378
        }
83✔
379
    }
83✔
380

381
    /// Create a simple CASE expression (CASE x WHEN ... THEN ...)
NEW
382
    pub fn simple(
×
NEW
383
        operand: Expr,
×
NEW
384
        when_clauses: Vec<(Expr, Expr)>,
×
NEW
385
        else_clause: Option<Expr>,
×
NEW
386
    ) -> Self {
×
NEW
387
        Self {
×
NEW
388
            operand: Some(Box::new(operand)),
×
NEW
389
            when_clauses,
×
NEW
390
            else_clause: else_clause.map(Box::new),
×
NEW
391
        }
×
NEW
392
    }
×
393
}
394

395
/// JSON/JSONB building expressions
396
#[derive(Debug, Clone, PartialEq)]
397
pub enum JsonBuildExpr {
398
    /// jsonb_build_object(k1, v1, k2, v2, ...)
399
    Object(Vec<(Expr, Expr)>),
400
    /// jsonb_build_array(v1, v2, ...)
401
    Array(Vec<Expr>),
402
}
403

404
/// ORDER BY expression component
405
#[derive(Debug, Clone, PartialEq)]
406
pub struct OrderByExpr {
407
    pub expr: Expr,
408
    pub direction: Option<OrderDirection>,
409
    pub nulls: Option<NullsOrder>,
410
}
411

412
impl OrderByExpr {
NEW
413
    pub fn new(expr: Expr) -> Self {
×
NEW
414
        Self {
×
NEW
415
            expr,
×
NEW
416
            direction: None,
×
NEW
417
            nulls: None,
×
NEW
418
        }
×
NEW
419
    }
×
420

NEW
421
    pub fn asc(expr: Expr) -> Self {
×
NEW
422
        Self {
×
NEW
423
            expr,
×
NEW
424
            direction: Some(OrderDirection::Asc),
×
NEW
425
            nulls: None,
×
NEW
426
        }
×
NEW
427
    }
×
428

NEW
429
    pub fn desc(expr: Expr) -> Self {
×
NEW
430
        Self {
×
NEW
431
            expr,
×
NEW
432
            direction: Some(OrderDirection::Desc),
×
NEW
433
            nulls: None,
×
NEW
434
        }
×
NEW
435
    }
×
436

NEW
437
    pub fn with_nulls(mut self, nulls: NullsOrder) -> Self {
×
NEW
438
        self.nulls = Some(nulls);
×
NEW
439
        self
×
NEW
440
    }
×
441
}
442

443
/// ORDER BY direction
444
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445
pub enum OrderDirection {
446
    Asc,
447
    Desc,
448
}
449

450
impl OrderDirection {
451
    pub fn as_sql(&self) -> &'static str {
1,375✔
452
        match self {
1,375✔
453
            Self::Asc => "asc",
1,189✔
454
            Self::Desc => "desc",
186✔
455
        }
456
    }
1,375✔
457
}
458

459
/// NULLS FIRST/LAST in ORDER BY
460
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461
pub enum NullsOrder {
462
    First,
463
    Last,
464
}
465

466
impl NullsOrder {
467
    pub fn as_sql(&self) -> &'static str {
1,375✔
468
        match self {
1,375✔
469
            Self::First => "nulls first",
180✔
470
            Self::Last => "nulls last",
1,195✔
471
        }
472
    }
1,375✔
473
}
474

475
/// The main expression enum encompassing all SQL expression types
476
#[derive(Debug, Clone, PartialEq)]
477
pub enum Expr {
478
    /// Column reference: table.column or just column
479
    Column(ColumnRef),
480

481
    /// Literal value
482
    Literal(Literal),
483

484
    /// Parameterized value: $1, $2, etc.
485
    Param(ParamRef),
486

487
    /// Binary operation: expr op expr
488
    BinaryOp {
489
        left: Box<Expr>,
490
        op: BinaryOperator,
491
        right: Box<Expr>,
492
    },
493

494
    /// Unary operation: op expr (e.g., NOT)
495
    UnaryOp { op: UnaryOperator, expr: Box<Expr> },
496

497
    /// Function call
498
    FunctionCall(FunctionCall),
499

500
    /// Aggregate function
501
    Aggregate(AggregateExpr),
502

503
    /// CASE expression
504
    Case(CaseExpr),
505

506
    /// Subquery: (SELECT ...)
507
    Subquery(Box<super::stmt::SelectStmt>),
508

509
    /// Array literal: ARRAY[...]
510
    Array(Vec<Expr>),
511

512
    /// Type cast: expr::type
513
    Cast {
514
        expr: Box<Expr>,
515
        target_type: SqlType,
516
    },
517

518
    /// IS NULL / IS NOT NULL
519
    IsNull { expr: Box<Expr>, negated: bool },
520

521
    /// expr IN (values) - for a list
522
    InList {
523
        expr: Box<Expr>,
524
        list: Vec<Expr>,
525
        negated: bool,
526
    },
527

528
    /// expr BETWEEN low AND high
529
    Between {
530
        expr: Box<Expr>,
531
        low: Box<Expr>,
532
        high: Box<Expr>,
533
        negated: bool,
534
    },
535

536
    /// EXISTS (subquery)
537
    Exists {
538
        subquery: Box<super::stmt::SelectStmt>,
539
        negated: bool,
540
    },
541

542
    /// JSON/JSONB building
543
    JsonBuild(JsonBuildExpr),
544

545
    /// Coalesce function: COALESCE(expr1, expr2, ...)
546
    Coalesce(Vec<Expr>),
547

548
    /// Parenthesized expression (for explicit grouping)
549
    Nested(Box<Expr>),
550

551
    /// Raw SQL string - **DEPRECATED**: Use only in tests.
552
    ///
553
    /// This is a security-sensitive escape hatch that bypasses SQL injection protection.
554
    /// All production code should use type-safe AST nodes instead. If you need SQL
555
    /// functionality not yet supported by the AST, add a proper node type.
556
    ///
557
    /// # Security Warning
558
    ///
559
    /// Never use this with user-provided input. The string is rendered directly
560
    /// to SQL without any escaping or validation.
561
    #[cfg(test)]
562
    Raw(String),
563

564
    /// Array index access: array[index]
565
    ArrayIndex { array: Box<Expr>, index: Box<Expr> },
566

567
    /// Function call with ORDER BY clause (e.g., array_agg(x ORDER BY y))
568
    FunctionCallWithOrderBy {
569
        name: String,
570
        args: Vec<Expr>,
571
        order_by: Vec<OrderByExpr>,
572
    },
573

574
    /// ROW constructor: ROW(expr1, expr2, ...) or (expr1, expr2, ...)
575
    Row(Vec<Expr>),
576
}
577

578
impl Expr {
579
    // Convenience constructors
580

581
    /// Create a column reference
NEW
582
    pub fn column(name: impl Into<Ident>) -> Self {
×
NEW
583
        Self::Column(ColumnRef::new(name))
×
NEW
584
    }
×
585

586
    /// Create a qualified column reference (table.column)
NEW
587
    pub fn qualified_column(table: impl Into<Ident>, column: impl Into<Ident>) -> Self {
×
NEW
588
        Self::Column(ColumnRef::qualified(table, column))
×
NEW
589
    }
×
590

591
    /// Create a NULL literal
NEW
592
    pub fn null() -> Self {
×
NEW
593
        Self::Literal(Literal::Null)
×
NEW
594
    }
×
595

596
    /// Create a boolean literal
NEW
597
    pub fn bool(b: bool) -> Self {
×
NEW
598
        Self::Literal(Literal::Bool(b))
×
NEW
599
    }
×
600

601
    /// Create an integer literal
NEW
602
    pub fn int(n: i64) -> Self {
×
NEW
603
        Self::Literal(Literal::Integer(n))
×
NEW
604
    }
×
605

606
    /// Create a string literal
NEW
607
    pub fn string(s: impl Into<String>) -> Self {
×
NEW
608
        Self::Literal(Literal::String(s.into()))
×
NEW
609
    }
×
610

611
    /// Create a binary operation
NEW
612
    pub fn binary(left: Expr, op: BinaryOperator, right: Expr) -> Self {
×
NEW
613
        Self::BinaryOp {
×
NEW
614
            left: Box::new(left),
×
NEW
615
            op,
×
NEW
616
            right: Box::new(right),
×
NEW
617
        }
×
NEW
618
    }
×
619

620
    /// Create a NOT expression
NEW
621
    pub fn not(expr: Expr) -> Self {
×
NEW
622
        Self::UnaryOp {
×
NEW
623
            op: UnaryOperator::Not,
×
NEW
624
            expr: Box::new(expr),
×
NEW
625
        }
×
NEW
626
    }
×
627

628
    /// Create an IS NULL expression
NEW
629
    pub fn is_null(expr: Expr) -> Self {
×
NEW
630
        Self::IsNull {
×
NEW
631
            expr: Box::new(expr),
×
NEW
632
            negated: false,
×
NEW
633
        }
×
NEW
634
    }
×
635

636
    /// Create an IS NOT NULL expression
NEW
637
    pub fn is_not_null(expr: Expr) -> Self {
×
NEW
638
        Self::IsNull {
×
NEW
639
            expr: Box::new(expr),
×
NEW
640
            negated: true,
×
NEW
641
        }
×
NEW
642
    }
×
643

644
    /// Create a type cast
NEW
645
    pub fn cast(expr: Expr, target_type: SqlType) -> Self {
×
NEW
646
        Self::Cast {
×
NEW
647
            expr: Box::new(expr),
×
NEW
648
            target_type,
×
NEW
649
        }
×
NEW
650
    }
×
651

652
    /// Create a function call
NEW
653
    pub fn function(name: impl Into<Ident>, args: Vec<Expr>) -> Self {
×
NEW
654
        Self::FunctionCall(FunctionCall::new(
×
NEW
655
            name,
×
NEW
656
            args.into_iter().map(FunctionArg::Unnamed).collect(),
×
NEW
657
        ))
×
NEW
658
    }
×
659

660
    /// Create a COALESCE expression
NEW
661
    pub fn coalesce(exprs: Vec<Expr>) -> Self {
×
NEW
662
        Self::Coalesce(exprs)
×
NEW
663
    }
×
664

665
    /// Create jsonb_build_object
NEW
666
    pub fn jsonb_build_object(pairs: Vec<(Expr, Expr)>) -> Self {
×
NEW
667
        Self::JsonBuild(JsonBuildExpr::Object(pairs))
×
NEW
668
    }
×
669

670
    /// Create jsonb_build_array
NEW
671
    pub fn jsonb_build_array(exprs: Vec<Expr>) -> Self {
×
NEW
672
        Self::JsonBuild(JsonBuildExpr::Array(exprs))
×
NEW
673
    }
×
674

675
    /// Wrap in parentheses
NEW
676
    pub fn nested(self) -> Self {
×
NEW
677
        Self::Nested(Box::new(self))
×
NEW
678
    }
×
679

680
    /// Combine with AND
NEW
681
    pub fn and(self, other: Expr) -> Self {
×
NEW
682
        Self::binary(self, BinaryOperator::And, other)
×
NEW
683
    }
×
684

685
    /// Combine with OR
NEW
686
    pub fn or(self, other: Expr) -> Self {
×
NEW
687
        Self::binary(self, BinaryOperator::Or, other)
×
NEW
688
    }
×
689

690
    /// Check equality
NEW
691
    pub fn eq(self, other: Expr) -> Self {
×
NEW
692
        Self::binary(self, BinaryOperator::Eq, other)
×
NEW
693
    }
×
694

695
    /// Create raw SQL - **DEPRECATED**: Use only in tests.
696
    ///
697
    /// # Security Warning
698
    ///
699
    /// This bypasses SQL injection protection. Never use with user input.
700
    #[cfg(test)]
701
    pub fn raw(sql: impl Into<String>) -> Self {
702
        Self::Raw(sql.into())
703
    }
704
}
705

706
#[cfg(test)]
707
mod tests {
708
    use super::*;
709

710
    #[test]
711
    fn test_column_ref() {
712
        let col = ColumnRef::new("id");
713
        assert_eq!(col.column.as_str(), "id");
714
        assert!(col.table_alias.is_none());
715

716
        let col = ColumnRef::qualified("users", "id");
717
        assert_eq!(col.table_alias.unwrap().as_str(), "users");
718
        assert_eq!(col.column.as_str(), "id");
719
    }
720

721
    #[test]
722
    fn test_expr_constructors() {
723
        let expr = Expr::qualified_column("t", "id");
724
        match expr {
725
            Expr::Column(c) => {
726
                assert_eq!(c.table_alias.unwrap().as_str(), "t");
727
                assert_eq!(c.column.as_str(), "id");
728
            }
729
            _ => panic!("Expected Column"),
730
        }
731

732
        let expr = Expr::int(42);
733
        match expr {
734
            Expr::Literal(Literal::Integer(n)) => assert_eq!(n, 42),
735
            _ => panic!("Expected Integer"),
736
        }
737
    }
738

739
    #[test]
740
    fn test_binary_op() {
741
        let expr = Expr::qualified_column("t", "id").eq(Expr::int(1));
742
        match expr {
743
            Expr::BinaryOp { op, .. } => assert_eq!(op, BinaryOperator::Eq),
744
            _ => panic!("Expected BinaryOp"),
745
        }
746
    }
747
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc