diff --git a/src/node_enum.rs b/src/node_enum.rs index ddcb71e..665308c 100644 --- a/src/node_enum.rs +++ b/src/node_enum.rs @@ -19,11 +19,11 @@ impl NodeEnum { }) } - pub fn nodes(&self) -> Vec<(NodeRef, i32, Context)> { - let mut iter = vec![(self.to_ref(), 0, Context::None)]; + pub fn nodes(&self) -> Vec<(NodeRef, i32, Context, bool)> { + let mut iter = vec![(self.to_ref(), 0, Context::None, false)]; let mut nodes = Vec::new(); while !iter.is_empty() { - let (node, depth, context) = iter.remove(0); + let (node, depth, context, has_filter_columns) = iter.remove(0); let depth = depth + 1; match node { // @@ -32,33 +32,33 @@ impl NodeEnum { NodeRef::SelectStmt(s) => { s.target_list.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } }); if let Some(n) = &s.where_clause { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, true)); } } s.sort_clause.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } }); s.group_clause.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } }); if let Some(n) = &s.having_clause { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } } if let Some(clause) = &s.with_clause { clause.ctes.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } }); } @@ -66,32 +66,32 @@ impl NodeEnum { Some(protobuf::SetOperation::SetopNone) => { s.from_clause.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } }); } Some(protobuf::SetOperation::SetopUnion) => { if let Some(left) = s.larg.as_ref() { - iter.push((left.to_ref(), depth, Context::Select)); + iter.push((left.to_ref(), depth, Context::Select, false)); } if let Some(right) = s.rarg.as_ref() { - iter.push((right.to_ref(), depth, Context::Select)); + iter.push((right.to_ref(), depth, Context::Select, false)); } } Some(protobuf::SetOperation::SetopExcept) => { if let Some(left) = s.larg.as_ref() { - iter.push((left.to_ref(), depth, Context::Select)); + iter.push((left.to_ref(), depth, Context::Select, false)); } if let Some(right) = s.rarg.as_ref() { - iter.push((right.to_ref(), depth, Context::Select)); + iter.push((right.to_ref(), depth, Context::Select, false)); } } Some(protobuf::SetOperation::SetopIntersect) => { if let Some(left) = s.larg.as_ref() { - iter.push((left.to_ref(), depth, Context::Select)); + iter.push((left.to_ref(), depth, Context::Select, false)); } if let Some(right) = s.rarg.as_ref() { - iter.push((right.to_ref(), depth, Context::Select)); + iter.push((right.to_ref(), depth, Context::Select, false)); } } Some(protobuf::SetOperation::Undefined) | None => (), @@ -100,46 +100,46 @@ impl NodeEnum { NodeRef::InsertStmt(s) => { if let Some(n) = &s.select_stmt { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, false)); } } if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DML)); + iter.push((rel.to_ref(), depth, Context::DML, false)); } if let Some(clause) = &s.with_clause { clause.ctes.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, false)); } }); } if let Some(n) = &s.on_conflict_clause { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, false)); } } NodeRef::UpdateStmt(s) => { s.target_list.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, false)); } }); s.where_clause.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, true)); } }); s.from_clause.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } }); if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DML)); + iter.push((rel.to_ref(), depth, Context::DML, false)); } if let Some(clause) = &s.with_clause { clause.ctes.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, false)); } }); } @@ -147,40 +147,40 @@ impl NodeEnum { NodeRef::DeleteStmt(s) => { if let Some(n) = &s.where_clause { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, true)); } } if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DML)); + iter.push((rel.to_ref(), depth, Context::DML, false)); } if let Some(clause) = &s.with_clause { clause.ctes.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, false)); } }); } s.using_clause.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::Select)); + iter.push((n.to_ref(), depth, Context::Select, false)); } }); } NodeRef::CommonTableExpr(s) => { if let Some(n) = &s.ctequery { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, false)); } } } NodeRef::CopyStmt(s) => { if let Some(n) = &s.query { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DML)); + iter.push((n.to_ref(), depth, Context::DML, false)); } } if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DML)); + iter.push((rel.to_ref(), depth, Context::DML, false)); } } // @@ -188,89 +188,89 @@ impl NodeEnum { // NodeRef::AlterTableStmt(s) => { if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } NodeRef::CreateStmt(s) => { if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } NodeRef::CreateTableAsStmt(s) => { if let Some(n) = &s.query { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DDL)); + iter.push((n.to_ref(), depth, Context::DDL, false)); } } if let Some(n) = &s.into { if let Some(rel) = n.rel.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } } NodeRef::TruncateStmt(s) => { s.relations.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DDL)); + iter.push((n.to_ref(), depth, Context::DDL, false)); } }); } NodeRef::ViewStmt(s) => { if let Some(n) = &s.query { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DDL)); + iter.push((n.to_ref(), depth, Context::DDL, false)); } } if let Some(rel) = s.view.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } NodeRef::IndexStmt(s) => { if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } s.index_params.iter().for_each(|n| { if let Some(NodeEnum::IndexElem(n)) = n.node.as_ref() { if let Some(n) = n.expr.as_ref().and_then(|n| n.node.as_ref()) { - iter.push((n.to_ref(), depth, Context::DDL)); + iter.push((n.to_ref(), depth, Context::DDL, false)); } } }); if let Some(n) = s.where_clause.as_ref() { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DDL)); + iter.push((n.to_ref(), depth, Context::DDL, true)); } } } NodeRef::CreateTrigStmt(s) => { if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } NodeRef::RuleStmt(s) => { if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } NodeRef::VacuumStmt(s) => { for node in &s.rels { if let Some(NodeEnum::VacuumRelation(r)) = &node.node { if let Some(rel) = r.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } } } NodeRef::RefreshMatViewStmt(s) => { if let Some(rel) = s.relation.as_ref() { - iter.push((rel.to_ref(), depth, Context::DDL)); + iter.push((rel.to_ref(), depth, Context::DDL, false)); } } NodeRef::GrantStmt(s) => { if let Some(protobuf::ObjectType::ObjectTable) = protobuf::ObjectType::from_i32(s.objtype) { s.objects.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DDL)); + iter.push((n.to_ref(), depth, Context::DDL, false)); } }); } @@ -278,14 +278,14 @@ impl NodeEnum { NodeRef::LockStmt(s) => { s.relations.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, Context::DDL)); + iter.push((n.to_ref(), depth, Context::DDL, false)); } }); } NodeRef::ExplainStmt(s) => { if let Some(n) = &s.query { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, false)); } } } @@ -295,92 +295,106 @@ impl NodeEnum { NodeRef::AExpr(e) => { if let Some(n) = &e.lexpr { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } if let Some(n) = &e.rexpr { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } NodeRef::BoolExpr(e) => { e.args.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); } + NodeRef::BooleanTest(e) => { + if let Some(n) = &e.arg { + if let Some(n) = n.node.as_ref() { + iter.push((n.to_ref(), depth, context, has_filter_columns)); + } + } + } NodeRef::CoalesceExpr(e) => { e.args.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); } NodeRef::MinMaxExpr(e) => { e.args.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); } + NodeRef::NullTest(e) => { + if let Some(n) = &e.arg { + if let Some(n) = n.node.as_ref() { + iter.push((n.to_ref(), depth, context, has_filter_columns)); + } + } + } NodeRef::ResTarget(t) => { if let Some(n) = &t.val { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } NodeRef::SubLink(l) => { if let Some(n) = &l.subselect { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } NodeRef::FuncCall(c) => { c.args.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); } NodeRef::CaseExpr(c) => { c.args.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); if let Some(n) = &c.defresult { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } NodeRef::CaseWhen(w) => { if let Some(n) = &w.expr { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } if let Some(n) = &w.result { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } NodeRef::SortBy(n) => { if let Some(n) = &n.node { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } NodeRef::TypeCast(n) => { if let Some(n) = &n.arg { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } @@ -390,7 +404,7 @@ impl NodeEnum { NodeRef::List(l) => { l.items.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); } @@ -398,7 +412,7 @@ impl NodeEnum { [&e.larg, &e.rarg, &e.quals].iter().for_each(|n| { if let Some(n) = n { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } }); @@ -406,27 +420,27 @@ impl NodeEnum { NodeRef::RowExpr(e) => { e.args.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); } NodeRef::RangeSubselect(s) => { if let Some(n) = &s.subquery { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } } } NodeRef::RangeFunction(f) => { f.functions.iter().for_each(|n| { if let Some(n) = n.node.as_ref() { - iter.push((n.to_ref(), depth, context)); + iter.push((n.to_ref(), depth, context, has_filter_columns)); } }); } _ => (), } - nodes.push((node, depth, context)); + nodes.push((node, depth, context, has_filter_columns)); } nodes } @@ -761,6 +775,14 @@ impl NodeEnum { } }); } + NodeMut::NullTest(e) => { + let e = e.as_mut().unwrap(); + if let Some(n) = e.arg.as_mut() { + if let Some(n) = n.node.as_mut() { + iter.push((n.to_mut(), depth, context)); + } + } + } NodeMut::ResTarget(t) => { let t = t.as_mut().unwrap(); if let Some(n) = t.val.as_mut() { @@ -819,6 +841,14 @@ impl NodeEnum { } } } + NodeMut::TypeCast(t) => { + let t = t.as_mut().unwrap(); + if let Some(n) = t.arg.as_mut() { + if let Some(n) = n.node.as_mut() { + iter.push((n.to_mut(), depth, context)); + } + } + } // // from-clause items // diff --git a/src/parse_result.rs b/src/parse_result.rs index 19f1064..4063d80 100644 --- a/src/parse_result.rs +++ b/src/parse_result.rs @@ -24,7 +24,7 @@ impl protobuf::ParseResult { } // Note: this doesn't iterate over every possible node type, since we only care about a subset of nodes. - pub fn nodes(&self) -> Vec<(NodeRef, i32, Context)> { + pub fn nodes(&self) -> Vec<(NodeRef, i32, Context, bool)> { self.stmts .iter() .filter_map(|s| @@ -58,6 +58,7 @@ pub struct ParseResult { pub aliases: HashMap, pub cte_names: Vec, functions: Vec<(String, Context)>, + pub filter_columns: Vec<(Option, String)>, } impl ParseResult { @@ -67,8 +68,9 @@ impl ParseResult { let mut aliases: HashMap = HashMap::new(); let mut cte_names: HashSet = HashSet::new(); let mut functions: HashSet<(String, Context)> = HashSet::new(); + let mut filter_columns: HashSet<(Option, String)> = HashSet::new(); - for (node, _depth, context) in protobuf.nodes().into_iter() { + for (node, _depth, context, has_filter_columns) in protobuf.nodes().into_iter() { match node { NodeRef::CommonTableExpr(s) => { cte_names.insert(s.ctename.to_owned()); @@ -139,6 +141,23 @@ impl ParseResult { } } } + NodeRef::ColumnRef(c) => { + if !has_filter_columns { + continue; + } + let f: Vec = c + .fields + .iter() + .filter_map(|n| match n.node.as_ref() { + Some(NodeEnum::String(s)) => Some(s.sval.to_string()), + _ => None, + }) + .rev() + .collect(); + if f.len() > 0 { + filter_columns.insert((f.get(1).cloned(), f[0].to_string())); + } + } _ => (), } } @@ -150,6 +169,7 @@ impl ParseResult { aliases, cte_names: Vec::from_iter(cte_names), functions: Vec::from_iter(functions), + filter_columns: Vec::from_iter(filter_columns), } } diff --git a/tests/filter_column_tests.rs b/tests/filter_column_tests.rs new file mode 100644 index 0000000..f6c59ba --- /dev/null +++ b/tests/filter_column_tests.rs @@ -0,0 +1,105 @@ +#![allow(non_snake_case)] +#![cfg(test)] + +#[cfg(test)] +use itertools::sorted; + +use pg_query::parse; + +#[test] +fn it_finds_unqualified_names() { + let result = parse("SELECT * FROM x WHERE y = $1 AND z = 1").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "y".into()), (None, "z".into())]); +} + +#[test] +fn it_finds_qualified_names() { + let result = parse("SELECT * FROM x WHERE x.y = $1 AND x.z = 1").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(Some("x".into()), "y".into()), (Some("x".into()), "z".into())]); +} + +#[test] +fn it_traverses_into_ctes() { + let result = parse("WITH a AS (SELECT * FROM x WHERE x.y = $1 AND x.z = 1) SELECT * FROM a WHERE b = 5").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "b".into()), (Some("x".into()), "y".into()), (Some("x".into()), "z".into())]); +} + +#[test] +fn it_recognizes_boolean_tests() { + let result = parse("SELECT * FROM x WHERE x.y IS TRUE AND x.z IS NOT FALSE").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(Some("x".into()), "y".into()), (Some("x".into()), "z".into())]); +} + +#[test] +fn it_recognizes_null_tests() { + let result = parse("SELECT * FROM x WHERE x.y IS NULL AND x.z IS NOT NULL").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(Some("x".into()), "y".into()), (Some("x".into()), "z".into())]); +} + +#[test] +fn it_finds_coalesce_argument_names() { + let result = parse("SELECT * FROM x WHERE x.y = COALESCE(z.a, z.b)").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(Some("x".into()), "y".into()), (Some("z".into()), "a".into()), (Some("z".into()), "b".into())]); +} + +#[test] +fn it_finds_unqualified_names_in_union_query() { + let result = parse("SELECT * FROM x where y = $1 UNION SELECT * FROM x where z = $2").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "y".into()), (None, "z".into())]); +} + +#[test] +fn it_finds_unqualified_names_in_union_all_query() { + let result = parse("SELECT * FROM x where y = $1 UNION ALL SELECT * FROM x where z = $2").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "y".into()), (None, "z".into())]); +} + +#[test] +fn it_finds_unqualified_names_in_except_query() { + let result = parse("SELECT * FROM x where y = $1 EXCEPT SELECT * FROM x where z = $2").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "y".into()), (None, "z".into())]); +} + +#[test] +fn it_finds_unqualified_names_in_except_all_query() { + let result = parse("SELECT * FROM x where y = $1 EXCEPT ALL SELECT * FROM x where z = $2").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "y".into()), (None, "z".into())]); +} + +#[test] +fn it_finds_unqualified_names_in_intersect_query() { + let result = parse("SELECT * FROM x where y = $1 INTERSECT SELECT * FROM x where z = $2").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "y".into()), (None, "z".into())]); +} + +#[test] +fn it_finds_unqualified_names_in_intersect_all_query() { + let result = parse("SELECT * FROM x where y = $1 INTERSECT ALL SELECT * FROM x where z = $2").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(None, "y".into()), (None, "z".into())]); +} + +#[test] +fn it_ignores_target_list_columns() { + let result = parse("SELECT a, y, z FROM x WHERE x.y = $1 AND x.z = 1").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(Some("x".into()), "y".into()), (Some("x".into()), "z".into())]); +} + +#[test] +fn it_ignores_order_by_columns() { + let result = parse("SELECT * FROM x WHERE x.y = $1 AND x.z = 1 ORDER BY a, b").unwrap(); + let filter_columns: Vec<(Option, String)> = sorted(result.filter_columns).collect(); + assert_eq!(filter_columns, [(Some("x".into()), "y".into()), (Some("x".into()), "z".into())]); +}