Skip to content

Commit d7018b1

Browse files
committed
test: improve binder and operator coverage
1 parent 213c8f8 commit d7018b1

3 files changed

Lines changed: 223 additions & 1 deletion

File tree

src/binder/select.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,6 +2280,25 @@ mod tests {
22802280
Ok(())
22812281
}
22822282

2283+
#[test]
2284+
fn test_select_bind_reports_set_alias_join_and_subquery_errors() -> Result<(), DatabaseError> {
2285+
let table_states = build_t1_table()?;
2286+
let invalid_queries = [
2287+
"select c1 from t1 union all select c3, c4 from t2",
2288+
"select * from t1 as aliased(only_one)",
2289+
"select * from t1 join t2 using (missing)",
2290+
"select exists(select c3 from t2) from t1",
2291+
"select * from t1 where c1 = (select c3 from t2 where c4 = c2)",
2292+
"select * from t1 where exists(select 1 from t2) and c1 = (select max(c3) from t2)",
2293+
];
2294+
2295+
for sql in invalid_queries {
2296+
assert!(table_states.plan(sql).is_err(), "query should fail: {sql}");
2297+
}
2298+
2299+
Ok(())
2300+
}
2301+
22832302
#[test]
22842303
fn test_right_side_position_globalizer_only_shifts_right_columns() -> Result<(), DatabaseError>
22852304
{

src/optimizer/rule/normalization/parameterized_index.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,144 @@ fn schema_contains_column(
234234
.iter()
235235
.any(|candidate| arena.same_column(*candidate, *column))
236236
}
237+
238+
// GRCOV_EXCL_START
239+
#[cfg(all(test, not(target_arch = "wasm32")))]
240+
mod tests {
241+
use super::*;
242+
use crate::catalog::{ColumnCatalog, ColumnDesc};
243+
use crate::optimizer::core::rule::NormalizationRule;
244+
use crate::planner::operator::filter::FilterOperator;
245+
use crate::planner::{PlanArena, TableArenaCell};
246+
use crate::types::LogicalType;
247+
248+
fn column(arena: &mut PlanArena, name: &str) -> ColumnRef {
249+
arena.alloc_column(ColumnCatalog::new(
250+
name.to_string(),
251+
true,
252+
ColumnDesc::new(LogicalType::Integer, None, false, None).unwrap(),
253+
))
254+
}
255+
256+
fn eq(left: ScalarExpression, right: ScalarExpression) -> ScalarExpression {
257+
ScalarExpression::Binary {
258+
op: BinaryOperator::Eq,
259+
left_expr: Box::new(left),
260+
right_expr: Box::new(right),
261+
evaluator: None,
262+
ty: LogicalType::Boolean,
263+
}
264+
}
265+
266+
#[test]
267+
fn probe_detection_covers_quantifiers_and_rejected_sides() -> Result<(), DatabaseError> {
268+
let table_arena = TableArenaCell::default();
269+
let mut arena = PlanArena::new(&table_arena);
270+
let left = column(&mut arena, "left");
271+
let right = column(&mut arena, "right");
272+
let outside = column(&mut arena, "outside");
273+
let left_schema = vec![left];
274+
let right_schema = vec![right];
275+
let overlapping_schema = vec![left, right];
276+
277+
assert!(find_parameterized_probe(
278+
MarkApplyKind::Quantified(MarkApplyQuantifier::Any),
279+
&[],
280+
&left_schema,
281+
&right_schema,
282+
&arena,
283+
)?
284+
.is_none());
285+
assert!(find_parameterized_probe(
286+
MarkApplyKind::Quantified(MarkApplyQuantifier::All),
287+
&[eq(
288+
ScalarExpression::column_expr(right, 0),
289+
ScalarExpression::column_expr(left, 0),
290+
)],
291+
&left_schema,
292+
&right_schema,
293+
&arena,
294+
)?
295+
.is_none());
296+
297+
let predicates = vec![
298+
ScalarExpression::from(true),
299+
eq(
300+
ScalarExpression::column_expr(right, 0),
301+
ScalarExpression::column_expr(left, 0),
302+
),
303+
];
304+
let probe = find_parameterized_probe(
305+
MarkApplyKind::Exists,
306+
&predicates,
307+
&left_schema,
308+
&right_schema,
309+
&arena,
310+
)?
311+
.expect("right = left should be parameterizable");
312+
assert_eq!(probe.0, right);
313+
314+
assert!(extract_parameterized_probe(
315+
&eq(
316+
ScalarExpression::column_expr(right, 0),
317+
ScalarExpression::column_expr(outside, 0),
318+
),
319+
&left_schema,
320+
&right_schema,
321+
&arena,
322+
)?
323+
.is_none());
324+
assert!(extract_parameterized_probe(
325+
&eq(
326+
ScalarExpression::column_expr(right, 0),
327+
ScalarExpression::column_expr(right, 0),
328+
),
329+
&overlapping_schema,
330+
&right_schema,
331+
&arena,
332+
)?
333+
.is_none());
334+
assert!(extract_parameterized_probe(
335+
&ScalarExpression::from(false),
336+
&left_schema,
337+
&right_schema,
338+
&arena,
339+
)?
340+
.is_none());
341+
342+
Ok(())
343+
}
344+
345+
#[test]
346+
fn parameterization_rejects_unsupported_operator_and_child_shapes() -> Result<(), DatabaseError>
347+
{
348+
let table_arena = TableArenaCell::default();
349+
let mut arena = PlanArena::new(&table_arena);
350+
let column = column(&mut arena, "value");
351+
let mut plan = LogicalPlan::new(Operator::Dummy, Childrens::None);
352+
353+
assert!(!ParameterizeMarkApply.apply(&mut plan, &mut arena)?);
354+
assert!(!parameterize_right_subtree(&mut plan, &column, &arena));
355+
356+
let mut filter = LogicalPlan::new(
357+
Operator::Filter(FilterOperator {
358+
predicate: ScalarExpression::from(true),
359+
is_optimized: false,
360+
having: false,
361+
}),
362+
Childrens::None,
363+
);
364+
assert!(!parameterize_right_subtree(&mut filter, &column, &arena));
365+
366+
assert_eq!(
367+
index_priority(IndexType::PrimaryKey { is_multiple: false }),
368+
0
369+
);
370+
assert_eq!(index_priority(IndexType::Unique), 1);
371+
assert_eq!(index_priority(IndexType::Composite), 2);
372+
assert_eq!(index_priority(IndexType::Normal), 3);
373+
374+
Ok(())
375+
}
376+
}
377+
// GRCOV_EXCL_STOP

src/planner/operator/mod.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,11 @@ mod tests {
667667
PlanImpl::IndexScan(Box::new(index_info())).to_string(),
668668
"IndexScan By #4 => EMPTY"
669669
);
670+
#[cfg(feature = "copy")]
671+
{
672+
assert_eq!(PlanImpl::CopyFromFile.to_string(), "CopyFromFile");
673+
assert_eq!(PlanImpl::CopyToFile.to_string(), "CopyToFile");
674+
}
670675
}
671676

672677
#[test]
@@ -749,6 +754,48 @@ mod tests {
749754
});
750755
assert_eq!(referenced_columns(&update, &mut arena)?, vec![a]);
751756

757+
let add_column = Operator::AddColumn(AddColumnOperator {
758+
table_name: "users".into(),
759+
if_not_exists: false,
760+
column: ColumnCatalog::new(
761+
"added".to_string(),
762+
true,
763+
ColumnDesc::new(
764+
LogicalType::Integer,
765+
None,
766+
false,
767+
Some(ScalarExpression::from(1_i32)),
768+
)?,
769+
),
770+
});
771+
assert!(referenced_columns(&add_column, &mut arena)?.is_empty());
772+
773+
let change_column = Operator::ChangeColumn(ChangeColumnOperator {
774+
table_name: "users".into(),
775+
old_column_name: "old".to_string(),
776+
new_column_name: "new".to_string(),
777+
data_type: LogicalType::Integer,
778+
default_change: DefaultChange::Set(column_expr(b, 1)),
779+
not_null_change: NotNullChange::NoChange,
780+
});
781+
assert_eq!(referenced_columns(&change_column, &mut arena)?, vec![b]);
782+
783+
let create_table = Operator::CreateTable(CreateTableOperator {
784+
table_name: "created".into(),
785+
columns: vec![ColumnCatalog::new(
786+
"value".to_string(),
787+
true,
788+
ColumnDesc::new(
789+
LogicalType::Integer,
790+
None,
791+
false,
792+
Some(ScalarExpression::from(2_i32)),
793+
)?,
794+
)],
795+
if_not_exists: false,
796+
});
797+
assert!(referenced_columns(&create_table, &mut arena)?.is_empty());
798+
752799
let table_scan = Operator::TableScan(TableScanOperator {
753800
table_name: "users".into(),
754801
columns: vec![a, d],
@@ -1134,7 +1181,7 @@ mod tests {
11341181

11351182
#[cfg(feature = "copy")]
11361183
#[test]
1137-
fn copy_from_file_display_formats_source_table_and_schema() {
1184+
fn copy_display_formats_source_target_table_and_schema() {
11381185
use crate::binder::copy::{ExtSource, FileFormat};
11391186
use std::path::PathBuf;
11401187

@@ -1161,6 +1208,21 @@ mod tests {
11611208
operator.to_string(),
11621209
"Copy /tmp/users.csv -> users [#0, #1]"
11631210
);
1211+
assert_eq!(
1212+
Operator::CopyToFile(CopyToFileOperator {
1213+
target: ExtSource {
1214+
path: PathBuf::from("/tmp/output.csv"),
1215+
format: FileFormat::Csv {
1216+
delimiter: ',',
1217+
quote: '"',
1218+
escape: None,
1219+
header: false,
1220+
},
1221+
},
1222+
})
1223+
.to_string(),
1224+
"Copy To /tmp/output.csv"
1225+
);
11641226
}
11651227
}
11661228
// GRCOV_EXCL_STOP

0 commit comments

Comments
 (0)