koron_query_parser/
lib.rs

1#![allow(clippy::missing_errors_doc, clippy::doc_markdown)]
2pub mod aggregation;
3pub mod comparison;
4pub mod destructured_query;
5pub mod error;
6pub mod filter;
7pub mod query_metadata;
8pub mod support;
9pub mod table;
10
11#[cfg(test)]
12mod tests {
13
14    use crate::query_metadata::QueryMetadata;
15    use crate::table::TabIdent;
16    use crate::{internal, malformed_query, unsupported};
17
18    use super::aggregation::{Aggregation, KoronFunction};
19    use super::comparison::CompareOp;
20    use super::error::ParseError;
21    use super::filter::Filter;
22
23    fn sample_sum() -> Aggregation {
24        Aggregation {
25            function: KoronFunction::Sum,
26            column: "test_column_2".to_string(),
27            alias: None,
28        }
29    }
30
31    fn sample_tab_ident() -> TabIdent {
32        TabIdent {
33            db: Some("test_db".to_string()),
34            schema: Some("test_schema".to_string()),
35            table: "test_table_1".to_string(),
36        }
37    }
38
39    #[test]
40    fn basic_aggregation() {
41        let cases = [
42            ("SUM(test_column_2)", KoronFunction::Sum),
43            ("COUNT(test_column_2)", KoronFunction::Count),
44            ("AVG(test_column_2)", KoronFunction::Average),
45            ("MEDIAN(test_column_2)", KoronFunction::Median),
46            ("VARIANCE(test_column_2)", KoronFunction::Variance),
47            ("STDDEV(test_column_2)", KoronFunction::StandardDeviation),
48            ("MIN(test_column_2)", KoronFunction::Min),
49            ("MAX(test_column_2)", KoronFunction::Max),
50        ];
51
52        for (projection, function) in cases {
53            let query = &format!("SELECT {projection} FROM test_db.test_schema.test_table_1");
54
55            let data_aggregation_query = if function == KoronFunction::Median {
56                None
57            } else {
58                Some(format!(
59                    "SELECT CAST({projection} AS TEXT) FROM test_db.test_schema.test_table_1"
60                ))
61            };
62
63            let expected = Ok(QueryMetadata {
64                table: sample_tab_ident(),
65                aggregation: Aggregation {
66                    function,
67                    column: "test_column_2".to_string(),
68                    alias: None,
69                },
70                filter: None,
71                data_extraction_query: String::from(
72                    "SELECT test_column_2 FROM test_db.test_schema.test_table_1",
73                ),
74                data_aggregation_query,
75            });
76            assert_eq!(
77                QueryMetadata::parse(query, None),
78                expected,
79                "\nfailed for aggregation {projection}",
80            );
81        }
82    }
83
84    #[test]
85    fn parenthesized_query() {
86        let query = "(((SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1)))";
87        let expected = Ok(QueryMetadata {
88            table: sample_tab_ident(),
89            aggregation: sample_sum(),
90            filter: None,
91            data_extraction_query: String::from(
92                "SELECT test_column_2 FROM test_db.test_schema.test_table_1",
93            ),
94            data_aggregation_query: Some(String::from(
95                "SELECT CAST(SUM(test_column_2) AS TEXT) FROM test_db.test_schema.test_table_1",
96            )),
97        });
98        assert_eq!(QueryMetadata::parse(query, None), expected);
99    }
100
101    #[test]
102    fn parenthesized_function() {
103        let query = "SELECT (((SUM(test_column_2)))) FROM test_db.test_schema.test_table_1";
104        let expected = Ok(QueryMetadata {
105            table: sample_tab_ident(),
106            aggregation: sample_sum(),
107            filter: None,
108            data_extraction_query:String::from("SELECT test_column_2 FROM test_db.test_schema.test_table_1"),
109            data_aggregation_query: Some(String::from("SELECT CAST((((SUM(test_column_2)))) AS TEXT) FROM test_db.test_schema.test_table_1")),
110        });
111        assert_eq!(QueryMetadata::parse(query, None), expected);
112    }
113
114    #[test]
115    fn parenthesized_column() {
116        let query = "SELECT SUM((((test_column_2)))) FROM test_db.test_schema.test_table_1";
117        let expected = Ok(QueryMetadata {
118            table: sample_tab_ident(),
119            aggregation: sample_sum(),
120            filter: None,
121            data_extraction_query:String::from("SELECT test_column_2 FROM test_db.test_schema.test_table_1"),
122            data_aggregation_query: Some(String::from("SELECT CAST(SUM((((test_column_2)))) AS TEXT) FROM test_db.test_schema.test_table_1")),
123        });
124        assert_eq!(QueryMetadata::parse(query, None), expected);
125    }
126
127    #[test]
128    fn result_alias() {
129        let query = "SELECT SUM(test_column_2) AS s FROM test_db.test_schema.test_table_1";
130        let expected = Ok(QueryMetadata {
131            table: sample_tab_ident(),
132            aggregation: Aggregation {
133                function: KoronFunction::Sum,
134                column: "test_column_2".to_string(),
135                alias: Some("s".to_string()),
136            },
137            filter: None,
138            data_extraction_query:String::from("SELECT test_column_2 FROM test_db.test_schema.test_table_1"),
139            data_aggregation_query: Some(String::from("SELECT CAST(SUM(test_column_2) AS TEXT) AS s FROM test_db.test_schema.test_table_1")),
140        });
141        assert_eq!(QueryMetadata::parse(query, None), expected);
142    }
143
144    #[test]
145    fn table_alias() {
146        let query = "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 AS t";
147        let expected = Ok(QueryMetadata {
148            table: sample_tab_ident(),
149            aggregation: sample_sum(),
150            filter: None,
151            data_extraction_query:String::from("SELECT test_column_2 FROM test_db.test_schema.test_table_1"),
152            data_aggregation_query: Some(String::from("SELECT CAST(SUM(test_column_2) AS TEXT) FROM test_db.test_schema.test_table_1 AS t")),
153        });
154        assert_eq!(QueryMetadata::parse(query, None), expected);
155    }
156
157    #[test]
158    fn unquoted_function_case_insensitive() {
159        let query = "SELECT sum(test_column_2) FROM test_db.test_schema.test_table_1";
160        let expected = Ok(QueryMetadata {
161            table: sample_tab_ident(),
162            aggregation: sample_sum(),
163            filter: None,
164            data_extraction_query: String::from(
165                "SELECT test_column_2 FROM test_db.test_schema.test_table_1",
166            ),
167            data_aggregation_query: Some(String::from(
168                "SELECT CAST(sum(test_column_2) AS TEXT) FROM test_db.test_schema.test_table_1",
169            )),
170        });
171        assert_eq!(QueryMetadata::parse(query, None), expected);
172    }
173
174    #[test]
175    fn quoted_function_case_sensitive() {
176        let query = "SELECT \"SUM\"(test_column_2) FROM test_db.test_schema.test_table_1";
177        let expected = Err(unsupported!(
178            "unrecognized or unsupported function: \"SUM\".".to_string()
179        ));
180        assert_eq!(QueryMetadata::parse(query, None), expected);
181    }
182
183    #[test]
184    fn unquoted_result_alias_case_insensitive() {
185        let query = "SELECT SUM(test_column_2) AS S FROM test_db.test_schema.test_table_1";
186        let expected = Ok(QueryMetadata {
187            table: sample_tab_ident(),
188            aggregation: Aggregation {
189                function: KoronFunction::Sum,
190                column: "test_column_2".to_string(),
191                alias: Some("s".to_string()),
192            },
193            filter: None,
194            data_extraction_query:String::from("SELECT test_column_2 FROM test_db.test_schema.test_table_1"),
195            data_aggregation_query: Some(String::from("SELECT CAST(SUM(test_column_2) AS TEXT) AS S FROM test_db.test_schema.test_table_1")),
196        });
197        assert_eq!(QueryMetadata::parse(query, None), expected);
198    }
199
200    #[test]
201    fn quoted_result_alias_case_sensitive() {
202        let query = "SELECT SUM(test_column_2) AS \"S\" FROM test_db.test_schema.test_table_1";
203        let expected = Ok(QueryMetadata {
204            table: sample_tab_ident(),
205            aggregation: Aggregation {
206                function: KoronFunction::Sum,
207                column: "test_column_2".to_string(),
208                alias: Some("S".to_string()),
209            },
210            filter: None,
211            data_extraction_query:String::from("SELECT test_column_2 FROM test_db.test_schema.test_table_1"),
212            data_aggregation_query: Some(String::from("SELECT CAST(SUM(test_column_2) AS TEXT) AS \"S\" FROM test_db.test_schema.test_table_1")),
213        });
214        assert_eq!(QueryMetadata::parse(query, None), expected);
215    }
216
217    #[test]
218    fn quoted_table_alias_case_sensitive() {
219        for (column, alias, extracted_alias) in [
220            ("t.test_column_2", "\"T\"", "T"),
221            ("\"T\".test_column_2", "\"t\"", "t"),
222        ] {
223            let query =
224                &format!("SELECT SUM({column}) FROM test_db.test_schema.test_table_1 AS {alias}");
225
226            let expected = Err(malformed_query!(format!(
227                "the {column} column is not part of \
228                     the table that's listed in the FROM clause ({extracted_alias}).",
229            )));
230            assert_eq!(
231                QueryMetadata::parse(query, None),
232                expected,
233                "\nfailed for query {query:?}",
234            );
235        }
236    }
237
238    #[test]
239    fn qualified_column_from_different_table() {
240        for column in [
241            "\"test_table_2\".test_column_2",
242            "\"test_schema\".test_table_2.test_column_2",
243        ] {
244            let query = &format!("SELECT SUM({column}) FROM test_db.test_schema.test_table_1");
245
246            let expected = Err(malformed_query!(format!(
247                    "the {column} column is not part of \
248                     the table that's listed in the FROM clause (test_db.test_schema.test_table_1).",
249                )));
250            assert_eq!(
251                QueryMetadata::parse(query, None),
252                expected,
253                "\nfailed for query {query:?}",
254            );
255        }
256    }
257
258    #[test]
259    fn qualified_column_not_from_table_alias() {
260        for column in [
261            "test_table_1.test_column_2",
262            "test_schema.test_table_1.test_column_2",
263        ] {
264            let query = &format!("SELECT SUM({column}) FROM test_db.test_schema.test_table_1 AS t");
265            let expected = Err(malformed_query!(format!(
266                "the {column} column is not part of \
267                     the table that's listed in the FROM clause (t).",
268            )));
269            assert_eq!(
270                QueryMetadata::parse(query, None),
271                expected,
272                "\nfailed for query {query:?}",
273            );
274        }
275    }
276
277    #[test]
278    fn sql_syntax_error() {
279        let query = "SELECT * FROM";
280        let expected = Err(malformed_query!(
281            "sql parser error: Expected identifier, found: EOF".to_string()
282        ));
283        assert_eq!(QueryMetadata::parse(query, None), expected);
284    }
285
286    #[test]
287    fn table_name_too_many_name_parts() {
288        let query = "SELECT SUM(test_column_2) FROM x.test_db.test_schema.test_table_1";
289        let expected = Err(internal!("found too many ident in table name (i.e., x.test_db.test_schema.test_table_1) in query AST.".to_string()));
290        assert_eq!(QueryMetadata::parse(query, None), expected);
291    }
292
293    #[test]
294    fn column_name_too_many_name_parts() {
295        let query = "SELECT SUM(x.test_db.test_schema.test_table_1.test_column_2) FROM test_db.test_schema.test_table_1";
296        let expected = Err(internal!("found too many ident in column name (i.e., x.test_db.test_schema.test_table_1.test_column_2)."
297                .to_string()));
298        assert_eq!(QueryMetadata::parse(query, None), expected);
299    }
300
301    #[test]
302    fn wrong_number_of_arguments() {
303        let cases = [
304            (
305                "SUM()",
306                "the SUM function takes exactly 1 argument, but 0 are provided.",
307            ),
308            (
309                "SUM(test_column_2, test_column_2)",
310                "the SUM function takes exactly 1 argument, but 2 are provided.",
311            ),
312        ];
313
314        for (projection, reason) in cases {
315            let query = &format!("SELECT {projection} FROM test_db.test_schema.test_table_1");
316            let expected = Err(malformed_query!(reason.to_string()));
317            assert_eq!(
318                QueryMetadata::parse(query, None),
319                expected,
320                "\nfailed for aggregation {projection}",
321            );
322        }
323    }
324
325    #[test]
326    fn unsupported_sql_features() {
327        let cases = [
328            (
329                "SELECT * FROM test_db.test_schema.test_table_1; SELECT * FROM test_db.test_schema.test_table_1",
330                "statements different from single SELECT statement.",
331            ),
332            (
333                "DELETE FROM test_db.test_schema.test_table_1",
334                "statements different from single SELECT statement.",
335            ),
336            (
337                "WITH t AS (SELECT 1) SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1",
338                "CTEs (i.e., WITH clause).",
339            ),
340            (
341                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 ORDER BY SUM",
342                "ORDER BY.",
343            ),
344            (
345                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 LIMIT 1",
346                "LIMIT.",
347            ),
348            (
349                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 OFFSET 1",
350                "OFFSET.",
351            ),
352            (
353                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 FETCH FIRST 1 ROW ONLY",
354                "FETCH.",
355            ),
356            (
357                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 drda FOR UPDATE",
358                "locking clauses (i.e., FOR UPDATE).",
359            ),
360            (
361                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 \
362                UNION \
363                SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1",
364                "set operations (i.e., UNION).",
365            ),
366            ("VALUES (1)", "VALUES."),
367            (
368                "INSERT INTO test_table_1(test_column_2) VALUES(1)",
369                "statements different from single SELECT statement."
370            ),
371            (
372                "SELECT DISTINCT SUM(test_column_2) FROM test_db.test_schema.test_table_1",
373                "DISTINCT.",
374            ),
375            // TOP is MSSQL syntax.
376            (
377                "SELECT TOP 1 SUM(test_column_2) FROM test_db.test_schema.test_table_1",
378                "TOP.",
379            ),
380            (
381                "SELECT SUM(test_column_2) INTO t FROM test_db.test_schema.test_table_1",
382                "SELECT INTO.",
383            ),
384            // LATERAL VIEW is HiveQL syntax.
385            (
386                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 LATERAL VIEW (SELECT 1) t",
387                "LATERAL VIEW.",
388            ),
389            (
390                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 GROUP BY SUM",
391                "GROUP BY.",
392            ),
393            // CLUSTER BY is HiveQL syntax.
394            (
395                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 CLUSTER BY SUM",
396                "CLUSTER BY.",
397            ),
398            // DISTRIBUTE BY is HiveQL syntax.
399            (
400                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 DISTRIBUTE BY SUM",
401                "DISTRIBUTE BY.",
402            ),
403            // SORT BY is HiveQL syntax.
404            (
405                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 SORT BY SUM",
406                "SORT BY.",
407            ),
408            (
409                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 HAVING sum > 0",
410                "HAVING.",
411            ),
412            (
413                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1, treasury.attachment",
414                "the FROM clause has multiple tables (no JOINs, subqueries or functions allowed).",
415            ),
416            (
417                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 CROSS JOIN treasury.attachment",
418                "the FROM clause has multiple tables (no JOINs, subqueries or functions allowed).",
419            ),
420            (
421                "SELECT SUM(test_column_2) FROM f('arg')",
422                "the FROM clause has multiple tables (no JOINs, subqueries or functions allowed).",
423            ),
424            // table hints are MSSQL syntax.
425            (
426                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 WITH (NOLOCK)",
427                "table hints (WITH in FROM clauses).",
428            ),
429            (
430                "SELECT SUM(test_column_2) FROM (SELECT * FROM test_db.test_schema.test_table_1)",
431                "the FROM clause has multiple tables (no JOINs, subqueries or functions allowed).",
432            ),
433            (
434                "SELECT SUM(test_column_2) FROM TABLE(f())",
435                "the FROM clause has multiple tables (no JOINs, subqueries or functions allowed).",
436            ),
437            (
438                "SELECT SUM(test_column_2) FROM (test_schema.test_table_1 CROSS JOIN treasury.attachment)",
439                "the FROM clause has multiple tables (no JOINs, subqueries or functions allowed).",
440            ),
441            (
442                "SELECT SUM(f) FROM test_db.test_schema.test_table_1 AS d (f, g)",
443                "table aliases with columns (such as d (f, g)).",
444            ),
445            (
446                "SELECT SUM(test_column_2), AVG(test_column_2) FROM test_db.test_schema.test_table_1",
447                "the SELECT clause must contain exactly one aggregation / analytic function. Nothing else is accepted.",
448            ),
449            (
450                "SELECT drda.* FROM test_db.test_schema.test_table_1",
451                "the SELECT clause must contain exactly one aggregation / analytic function. Nothing else is accepted.",
452            ),
453            (
454                "SELECT * FROM test_db.test_schema.test_table_1",
455                "the SELECT clause must contain exactly one aggregation / analytic function. Nothing else is accepted.",
456            ),
457            (
458                "SELECT id FROM test_db.test_schema.test_table_1",
459                "the SELECT clause must contain exactly one aggregation / analytic function. Nothing else is accepted.",
460            ),
461            (
462                "SELECT SUM(test_column_2) OVER (PARTITION BY id) FROM test_db.test_schema.test_table_1",
463                "window functions (OVER).",
464            ),
465            (
466                "SELECT SUM(DISTINCT test_column_2) FROM test_db.test_schema.test_table_1",
467                "DISTINCT.",
468            ),
469            (
470                "SELECT custom.aggregation(test_column_2) FROM test_db.test_schema.test_table_1",
471                "unrecognized or unsupported function: custom.aggregation.",
472            ),
473            (
474                "SELECT SUM(x => test_column_2) FROM test_db.test_schema.test_table_1",
475                "named function arguments (such as x => test_column_2).",
476            ),
477            (
478                "SELECT SUM(1) FROM test_db.test_schema.test_table_1",
479                "only a column name is supported as the argument of the SUM function.",
480            ),
481            (
482                "SELECT SUM(test_table_1.*) FROM test_db.test_schema.test_table_1",
483                "only a column name is supported as the argument of the SUM function.",
484            ),
485            (
486                "SELECT SUM(*) FROM test_db.test_schema.test_table_1",
487                "only a column name is supported as the argument of the SUM function.",
488            ),
489            (
490                "INSERT INTO test_table_1 SELECT * FROM test_db.test_schema.test_table_1",
491                "statements different from single SELECT statement.",
492            ),
493            (
494                "CREATE TABLE test_table_1 AS SELECT * FROM test_db.test_schema.test_table_1",
495                "statements different from single SELECT statement.",
496            ),
497            (
498                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 WHERE test_column_2 BETWEEN 1 AND 2",
499                "unsupported expression in the WHERE clause: test_column_2 BETWEEN 1 AND 2.",
500            ),
501            (
502                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 WHERE 2 < 1",
503                "2 < 1. Only comparisons between a column and a constant are supported.",
504            ),
505            (
506                "SELECT SUM(test_column_2) FROM test_db.test_schema.test_table_1 WHERE test_column_2 < test_column_3",
507                "test_column_2 < test_column_3. Only comparisons between a column and a constant are supported.",
508            ),
509            // Unsupported functions
510            (
511                "SELECT KTHELEMENT(test_column_2, 3) FROM test_db.test_schema.test_table_1;",
512                "unrecognized or unsupported function: KTHELEMENT."
513            )
514        ];
515
516        for (query, reason) in cases {
517            let expected = Err(unsupported!(reason.to_string()));
518            assert_eq!(
519                QueryMetadata::parse(query, None),
520                expected,
521                "\nfailed for query {query:?}",
522            );
523        }
524    }
525
526    #[test]
527    fn aggregation_with_single_where() {
528        let cases = [
529            (
530                "test_column_2 < 1",
531                Filter {
532                    column: "test_column_2".to_string(),
533                    comparison: CompareOp::Lt {
534                        value: "1".to_string(),
535                    },
536                },
537            ),
538            (
539                "1 < test_column_2",
540                Filter {
541                    column: "test_column_2".to_string(),
542                    comparison: CompareOp::Gt {
543                        value: "1".to_string(),
544                    },
545                },
546            ),
547            (
548                "test_column_2 <= 1",
549                Filter {
550                    column: "test_column_2".to_string(),
551                    comparison: CompareOp::LtEq {
552                        value: "1".to_string(),
553                    },
554                },
555            ),
556            (
557                "1 <= test_column_2",
558                Filter {
559                    column: "test_column_2".to_string(),
560                    comparison: CompareOp::GtEq {
561                        value: "1".to_string(),
562                    },
563                },
564            ),
565            (
566                "test_column_2 > 1",
567                Filter {
568                    column: "test_column_2".to_string(),
569                    comparison: CompareOp::Gt {
570                        value: "1".to_string(),
571                    },
572                },
573            ),
574            (
575                "1 > test_column_2",
576                Filter {
577                    column: "test_column_2".to_string(),
578                    comparison: CompareOp::Lt {
579                        value: "1".to_string(),
580                    },
581                },
582            ),
583            (
584                "test_column_2 >= 1",
585                Filter {
586                    column: "test_column_2".to_string(),
587                    comparison: CompareOp::GtEq {
588                        value: "1".to_string(),
589                    },
590                },
591            ),
592            (
593                "1 >= test_column_2",
594                Filter {
595                    column: "test_column_2".to_string(),
596                    comparison: CompareOp::LtEq {
597                        value: "1".to_string(),
598                    },
599                },
600            ),
601            (
602                "test_column_3 > '2021-04-02T05:02:16.04+03:00'",
603                Filter {
604                    column: "test_column_3".to_string(),
605                    comparison: CompareOp::Gt {
606                        value: "2021-04-02T05:02:16.04+03:00".to_string(),
607                    },
608                },
609            ),
610            (
611                "-1 >= test_column_4",
612                Filter {
613                    column: "test_column_4".to_string(),
614                    comparison: CompareOp::LtEq {
615                        value: "-1".to_string(),
616                    },
617                },
618            ),
619            (
620                "+1 >= test_column_2",
621                Filter {
622                    column: "test_column_2".to_string(),
623                    comparison: CompareOp::LtEq {
624                        value: "1".to_string(),
625                    },
626                },
627            ),
628            (
629                "+1 = test_column_2",
630                Filter {
631                    column: "test_column_2".to_string(),
632                    comparison: CompareOp::Eq {
633                        value: "1".to_string(),
634                    },
635                },
636            ),
637            (
638                "+1 <> test_column_2",
639                Filter {
640                    column: "test_column_2".to_string(),
641                    comparison: CompareOp::NotEq {
642                        value: "1".to_string(),
643                    },
644                },
645            ),
646            (
647                "test_column_2 IS NULL",
648                Filter {
649                    column: "test_column_2".to_string(),
650                    comparison: CompareOp::IsNull,
651                },
652            ),
653            (
654                "test_column_2 IS NOT NULL",
655                Filter {
656                    column: "test_column_2".to_string(),
657                    comparison: CompareOp::IsNotNull,
658                },
659            ),
660            (
661                "test_column_1 = NULL",
662                Filter {
663                    column: "test_column_1".to_string(),
664                    comparison: CompareOp::Eq {
665                        value: "Null".to_string(),
666                    },
667                },
668            ),
669            (
670                "test_column_2 = NULL",
671                Filter {
672                    column: "test_column_2".to_string(),
673                    comparison: CompareOp::Eq {
674                        value: "Null".to_string(),
675                    },
676                },
677            ),
678            (
679                "test_column_3 = NULL",
680                Filter {
681                    column: "test_column_3".to_string(),
682                    comparison: CompareOp::Eq {
683                        value: "Null".to_string(),
684                    },
685                },
686            ),
687            (
688                "test_column_4 = NULL",
689                Filter {
690                    column: "test_column_4".to_string(),
691                    comparison: CompareOp::Eq {
692                        value: "Null".to_string(),
693                    },
694                },
695            ),
696            (
697                "test_column_5 IS TRUE",
698                Filter {
699                    column: "test_column_5".to_string(),
700                    comparison: CompareOp::IsTrue,
701                },
702            ),
703            (
704                "test_column_5 IS NOT TRUE",
705                Filter {
706                    column: "test_column_5".to_string(),
707                    comparison: CompareOp::IsNotTrue,
708                },
709            ),
710            (
711                "test_column_5 = true",
712                Filter {
713                    column: "test_column_5".to_string(),
714                    comparison: CompareOp::Eq {
715                        value: "true".to_string(),
716                    },
717                },
718            ),
719            (
720                "test_column_5 <> true",
721                Filter {
722                    column: "test_column_5".to_string(),
723                    comparison: CompareOp::NotEq {
724                        value: "true".to_string(),
725                    },
726                },
727            ),
728            (
729                "test_column_5 IS FALSE",
730                Filter {
731                    column: "test_column_5".to_string(),
732                    comparison: CompareOp::IsFalse,
733                },
734            ),
735            (
736                "test_column_5 IS NOT FALSE",
737                Filter {
738                    column: "test_column_5".to_string(),
739                    comparison: CompareOp::IsNotFalse,
740                },
741            ),
742            (
743                "test_column_5 = false",
744                Filter {
745                    column: "test_column_5".to_string(),
746                    comparison: CompareOp::Eq {
747                        value: "false".to_string(),
748                    },
749                },
750            ),
751            (
752                "test_column_5 <> false",
753                Filter {
754                    column: "test_column_5".to_string(),
755                    comparison: CompareOp::NotEq {
756                        value: "false".to_string(),
757                    },
758                },
759            ),
760        ];
761
762        let analytical_functions = [("SUM", KoronFunction::Sum), ("COUNT", KoronFunction::Count)];
763
764        let test_cases = |enum_fn: KoronFunction, query: &String| {
765            for (selection, filter) in cases.clone() {
766                let query = &format!("{query} WHERE {selection}");
767                let mut aggregation = sample_sum();
768                aggregation.function = enum_fn;
769                let expected_query = if &filter.column == "test_column_2" {
770                    "SELECT test_column_2 FROM test_db.test_schema.test_table_1".to_string()
771                } else {
772                    format!(
773                        "SELECT test_column_2, {} FROM test_db.test_schema.test_table_1",
774                        filter.column
775                    )
776                };
777                let expected = QueryMetadata {
778                    table: sample_tab_ident(),
779                    aggregation,
780                    filter: Some(filter.clone()),
781                    data_extraction_query: expected_query,
782                    data_aggregation_query: None,
783                };
784                let result = QueryMetadata::parse(query, None).unwrap();
785                assert_eq!(
786                    result.aggregation, expected.aggregation,
787                    "\nfailed for selection {selection:?}",
788                );
789                assert_eq!(
790                    result.table, expected.table,
791                    "\nfailed for selection {selection:?}",
792                );
793                assert_eq!(
794                    result.filter, expected.filter,
795                    "\nfailed for selection {selection:?}",
796                );
797                assert_eq!(
798                    result.data_extraction_query, expected.data_extraction_query,
799                    "\nfailed for selection {selection:?}",
800                );
801            }
802        };
803
804        for (function, enum_fn) in analytical_functions {
805            let query =
806                format!("SELECT {function}(test_column_2) FROM test_db.test_schema.test_table_1");
807            test_cases(enum_fn, &query);
808        }
809    }
810}