diff --git a/src/query/src/dist_plan/predicate_extractor.rs b/src/query/src/dist_plan/predicate_extractor.rs index 778f5b9fc94..020e6d169ef 100644 --- a/src/query/src/dist_plan/predicate_extractor.rs +++ b/src/query/src/dist_plan/predicate_extractor.rs @@ -18,10 +18,9 @@ use std::collections::HashSet; -use arrow::datatypes::DataType; use common_telemetry::debug; use datafusion_common::Result as DfResult; -use datafusion_expr::{Expr, LogicalPlan, Operator}; +use datafusion_expr::{Expr, LogicalPlan, Operator, utils::split_conjunction}; use datatypes::value::Value; use partition::expr::{Operand, PartitionExpr, RestrictedOp}; @@ -48,33 +47,46 @@ impl PredicateExtractor { let partition_set: HashSet = partition_columns.iter().cloned().collect(); for filter_expr in filter_exprs { - match DataFusionExprConverter::convert(&filter_expr) { - Ok(partition_expr) => { - // Check expression for safe partition pruning - match ExpressionChecker::check_expression_for_pruning( - &partition_expr, - &partition_set, - ) { - ExpressionCheckResult::UseAsIs(expr) => { - partition_exprs.push(expr); - } - ExpressionCheckResult::UsePartial(exprs) => { - partition_exprs.extend(exprs); - } - ExpressionCheckResult::Drop => { - debug!( - "Dropping mixed expression for correctness: {}", - partition_expr - ); - } - } - } + let partition_expr = match DataFusionExprConverter::convert(&filter_expr) { + Ok(expr) => Some(expr), Err(err) => { debug!( - "Failed to convert filter expression to PartitionExpr: {}, skipping", + "Failed to convert filter expression to PartitionExpr: {}, trying conjuncts", err ); - continue; + split_conjunction(&filter_expr) + .into_iter() + .filter_map(|conjunct| match DataFusionExprConverter::convert(conjunct) { + Ok(expr) => Some(expr), + Err(err) => { + debug!( + "Failed to convert filter conjunct to PartitionExpr: {}, skipping", + err + ); + None + } + }) + // Keep salvaged conjuncts in one AND expression, not as alternatives. + .reduce(PartitionExpr::and) + } + }; + let Some(partition_expr) = partition_expr else { + continue; + }; + + // Check expression for safe partition pruning + match ExpressionChecker::check_expression_for_pruning(&partition_expr, &partition_set) { + ExpressionCheckResult::UseAsIs(expr) => { + partition_exprs.push(expr); + } + ExpressionCheckResult::UsePartial(exprs) => { + partition_exprs.extend(exprs); + } + ExpressionCheckResult::Drop => { + debug!( + "Dropping mixed expression for correctness: {}", + partition_expr + ); } } } @@ -423,7 +435,11 @@ impl DataFusionExprConverter { Ok(Operand::Column(column_name)) } Expr::Literal(scalar_value, _) => { - let value = Value::try_from(scalar_value.clone()).unwrap(); + let value = Value::try_from(scalar_value.clone()).map_err(|err| { + datafusion_common::DataFusionError::Plan(format!( + "Failed to convert literal for partition pruning: {err}" + )) + })?; Ok(Operand::Value(value)) } Expr::Alias(alias_expr) => { @@ -431,19 +447,20 @@ impl DataFusionExprConverter { Self::convert_to_operand(&alias_expr.expr) } Expr::Cast(cast_expr) => { - // For safe casts, unwrap to the inner expression - // For unsafe casts, skip with debug logging - if Self::is_safe_cast_for_partition_pruning(cast_expr.field.data_type()) { - Self::convert_to_operand(&cast_expr.expr) + // Column casts need input type/value equivalence proof; literals must use the actual cast value. + if let Expr::Literal(value, _) = cast_expr.expr.as_ref() { + let value = value.cast_to(cast_expr.field.data_type())?; + let value = Value::try_from(value).map_err(|err| { + datafusion_common::DataFusionError::Plan(format!( + "Failed to convert cast literal for partition pruning: {err}" + )) + })?; + Ok(Operand::Value(value)) } else { - debug!( - "Skipping unsafe cast for partition pruning: {:?}", - cast_expr.field.data_type() - ); - Err(datafusion_common::DataFusionError::Plan(format!( - "Cast to {:?} not supported for partition pruning", - cast_expr.field.data_type() - ))) + debug!("Skipping cast expression for partition pruning: {:?}", expr); + Err(datafusion_common::DataFusionError::Plan( + "Cast expression not supported for partition pruning".to_string(), + )) } } other => { @@ -481,46 +498,6 @@ impl DataFusionExprConverter { }; Self::convert_operator(&negated) } - - /// Determine if a cast is safe for partition pruning - /// Safe casts don't change the logical meaning of constraints - fn is_safe_cast_for_partition_pruning(data_type: &DataType) -> bool { - match data_type { - // Integer widening casts are generally safe - DataType::Int8 => true, - DataType::Int16 => true, - DataType::Int32 => true, - DataType::Int64 => true, - DataType::UInt8 => true, - DataType::UInt16 => true, - DataType::UInt32 => true, - DataType::UInt64 => true, - - // Float casts are generally safe for equality/inequality comparisons - DataType::Float32 => true, - DataType::Float64 => true, - - // String casts might be safe in some cases - DataType::Utf8 => true, - DataType::LargeUtf8 => true, - // Dictionary encoding does not change the logical value. DataFusion inserts this cast - // when a predicate literal is compared with a dictionary-encoded scan column. - DataType::Dictionary(_, value_type) => { - Self::is_safe_cast_for_partition_pruning(value_type) - } - - // Date/time casts might be safe if they don't change precision significantly - DataType::Date32 => true, - DataType::Date64 => true, - DataType::Timestamp(_, _) => true, - - // Boolean casts are straightforward - DataType::Boolean => true, - - // For other types, be conservative and skip - _ => false, - } - } } #[cfg(test)] @@ -654,6 +631,190 @@ mod tests { ); } + #[test] + fn test_cast_literals_are_evaluated_and_column_casts_are_skipped() { + let casts = [ + (DataType::Int64, lit(1i64)), + (DataType::Float32, lit(1.0f32)), + (DataType::Utf8, lit("1")), + (DataType::Date32, lit(1i32)), + ( + DataType::Timestamp(datafusion::arrow::datatypes::TimeUnit::Nanosecond, None), + lit(1i64), + ), + ]; + for (data_type, value) in casts { + let cast = Expr::Cast(datafusion_expr::Cast::new( + Box::new(col("user_id")), + data_type, + )); + assert!(DataFusionExprConverter::convert(&cast.eq(value)).is_err()); + } + + let chain = Expr::Cast(datafusion_expr::Cast::new( + Box::new(Expr::Cast(datafusion_expr::Cast::new( + Box::new(col("user_id")), + DataType::Float64, + ))), + DataType::Int64, + )); + assert!(DataFusionExprConverter::convert(&chain.eq(lit(1i64))).is_err()); + + let cast_int = Expr::Cast(datafusion_expr::Cast::new( + Box::new(lit(1.5f64)), + DataType::Int64, + )); + assert_eq!( + DataFusionExprConverter::convert(&col("user_id").eq(cast_int)) + .unwrap() + .rhs(), + &Operand::Value(Value::Int64(1)) + ); + + let cast_string = Expr::Cast(datafusion_expr::Cast::new( + Box::new(lit("12")), + DataType::Int64, + )); + assert_eq!( + DataFusionExprConverter::convert(&col("user_id").eq(cast_string)) + .unwrap() + .rhs(), + &Operand::Value(Value::Int64(12)) + ); + + let unsupported = Expr::Cast(datafusion_expr::Cast::new( + Box::new(lit(1i64)), + DataType::Decimal256(10, 2), + )); + assert!(DataFusionExprConverter::convert_to_operand(&unsupported).is_err()); + } + + #[test] + fn test_cast_predicates_are_skipped_in_and_and_or() { + let cast = Expr::Cast(datafusion_expr::Cast::new( + Box::new(col("user_id")), + DataType::Float32, + )); + let lower = PartitionExpr::new( + Operand::Column("user_id".to_string()), + RestrictedOp::GtEq, + Operand::Value(Value::Int64(1)), + ); + let upper = PartitionExpr::new( + Operand::Column("user_id".to_string()), + RestrictedOp::Lt, + Operand::Value(Value::Int64(10)), + ); + let bounds = PartitionExpr::new( + Operand::Expr(lower.clone()), + RestrictedOp::And, + Operand::Expr(upper.clone()), + ); + check_partition_expressions(vec![ + FilterTestCase::new( + "cast_in_and_preserves_partition_bound", + col("user_id") + .eq(lit(1i64)) + .and(cast.clone().eq(lit(1.0f32))), + vec![PartitionExpr::new( + Operand::Column("user_id".to_string()), + RestrictedOp::Eq, + Operand::Value(Value::Int64(1)), + )], + vec!["user_id"], + ), + FilterTestCase::new( + "cast_between_two_bounds_preserves_both", + col("user_id") + .gt_eq(lit(1i64)) + .and(col("user_id").lt(lit(10i64))) + .and(cast.clone().eq(lit(1.0f32))), + vec![bounds.clone()], + vec!["user_id"], + ), + FilterTestCase::new( + "cast_left_of_bounds_preserves_both", + cast.clone() + .eq(lit(1.0f32)) + .and(col("user_id").gt_eq(lit(1i64))) + .and(col("user_id").lt(lit(10i64))), + vec![bounds.clone()], + vec!["user_id"], + ), + FilterTestCase::new( + "cast_between_bounds_preserves_both", + col("user_id") + .gt_eq(lit(1i64)) + .and(cast.clone().eq(lit(1.0f32))) + .and(col("user_id").lt(lit(10i64))), + vec![bounds], + vec!["user_id"], + ), + FilterTestCase::new( + "is_not_null_cast_preserves_partition_bound", + col("user_id").eq(lit(1i64)).and( + Expr::Cast(datafusion_expr::Cast::new( + Box::new(col("value")), + DataType::Int32, + )) + .is_not_null(), + ), + vec![PartitionExpr::new( + Operand::Column("user_id".to_string()), + RestrictedOp::Eq, + Operand::Value(Value::Int64(1)), + )], + vec!["user_id"], + ), + FilterTestCase::new( + "all_unsupported_conjunction_is_dropped", + Expr::Cast(datafusion_expr::Cast::new( + Box::new(col("value")), + DataType::Int32, + )) + .eq(lit(1i32)) + .and( + Expr::Cast(datafusion_expr::Cast::new( + Box::new(col("timestamp")), + DataType::Float32, + )) + .eq(lit(1.0f32)), + ), + vec![], + vec!["user_id"], + ), + FilterTestCase::new( + "nested_and_with_or_side_preserves_bound", + col("user_id").eq(lit(1i64)).and( + col("user_id") + .eq(lit(2i64)) + .or(cast.clone().eq(lit(1.0f32))), + ), + vec![PartitionExpr::new( + Operand::Column("user_id".to_string()), + RestrictedOp::Eq, + Operand::Value(Value::Int64(1)), + )], + vec!["user_id"], + ), + FilterTestCase::new( + "or_with_nested_and_cast_drops_whole_expression", + col("user_id") + .eq(lit(1i64)) + .and(cast.clone().eq(lit(1.0f32))) + .or(col("user_id").eq(lit(2i64))), + vec![], + vec!["user_id"], + ), + FilterTestCase::new( + "cast_in_or_drops_entire_predicate", + col("user_id").eq(lit(1i64)).or(cast.eq(lit(1.0f32))), + vec![], + vec!["user_id"], + ), + ]); + } + #[test] fn test_combines_table_scan_filters_as_conjunction() { let table_scan = create_test_table_scan(); @@ -663,32 +824,42 @@ mod tests { panic!("expected test table scan"); }; let plan = LogicalPlan::TableScan(datafusion_expr::logical_plan::TableScan { - filters: vec![filter_a, filter_b], + filters: vec![filter_a.clone(), filter_b.clone()], + ..scan.clone() + }); + let partition_columns = ["user_id".to_string(), "value".to_string()]; + let expected = vec![PartitionExpr::new( + Operand::Expr(PartitionExpr::new( + Operand::Column("user_id".to_string()), + RestrictedOp::Eq, + Operand::Value(Value::Int64(10)), + )), + RestrictedOp::And, + Operand::Expr(PartitionExpr::new( + Operand::Column("value".to_string()), + RestrictedOp::Eq, + Operand::Value(Value::Int64(20)), + )), + )]; + + let partition_exprs = + PredicateExtractor::extract_partition_expressions(&plan, &partition_columns).unwrap(); + assert_eq!(partition_exprs, expected); + + let unsupported = Expr::Cast(datafusion_expr::Cast::new( + Box::new(col("timestamp")), + DataType::Float32, + )) + .eq(lit(1.0f32)); + let plan = LogicalPlan::TableScan(datafusion_expr::logical_plan::TableScan { + filters: vec![filter_a, unsupported, filter_b], ..scan }); - let partition_exprs = PredicateExtractor::extract_partition_expressions( - &plan, - &["user_id".to_string(), "value".to_string()], - ) - .unwrap(); + let partition_exprs = + PredicateExtractor::extract_partition_expressions(&plan, &partition_columns).unwrap(); - assert_eq!( - partition_exprs, - vec![PartitionExpr::new( - Operand::Expr(PartitionExpr::new( - Operand::Column("user_id".to_string()), - RestrictedOp::Eq, - Operand::Value(Value::Int64(10)), - )), - RestrictedOp::And, - Operand::Expr(PartitionExpr::new( - Operand::Column("value".to_string()), - RestrictedOp::Eq, - Operand::Value(Value::Int64(20)), - )), - )] - ); + assert_eq!(partition_exprs, expected); } #[test] @@ -1010,31 +1181,23 @@ mod tests { fn test_cast_expressions() { let cases = vec![ FilterTestCase::new( - "safe_cast", + "column_cast_requires_type_proof", Expr::Cast(datafusion_expr::Cast::new( Box::new(col("user_id")), DataType::Int64, )) .eq(lit(100i64)), - vec![PartitionExpr::new( - Operand::Column("user_id".to_string()), - RestrictedOp::Eq, - Operand::Value(Value::Int64(100)), - )], + vec![], vec!["user_id"], ), FilterTestCase::new( - "cast_with_alias", + "cast_with_alias_requires_type_proof", Expr::Cast(datafusion_expr::Cast::new( Box::new(col("user_id").alias("uid")), DataType::Int64, )) .eq(lit(100i64)), - vec![PartitionExpr::new( - Operand::Column("user_id".to_string()), - RestrictedOp::Eq, - Operand::Value(Value::Int64(100)), - )], + vec![], vec!["user_id"], ), FilterTestCase::new( @@ -1122,11 +1285,7 @@ mod tests { let in_expr = col("user_id") .alias("uid") .in_list(vec![lit(100i64), lit(200i64)], false); - let cast_expr = Expr::Cast(datafusion_expr::Cast::new( - Box::new(col("user_id")), - DataType::Int64, - )); - let between_expr = cast_expr.between(lit(300i64), lit(400i64)); + let between_expr = col("user_id").between(lit(300i64), lit(400i64)); in_expr.or(between_expr) }, vec![PartitionExpr::new( diff --git a/tests/cases/distributed/optimizer/partition_pruning_cast.result b/tests/cases/distributed/optimizer/partition_pruning_cast.result new file mode 100644 index 00000000000..2b18475fda8 --- /dev/null +++ b/tests/cases/distributed/optimizer/partition_pruning_cast.result @@ -0,0 +1,175 @@ +CREATE TABLE partition_pruning_cast_partitioned ( + ts TIMESTAMP TIME INDEX, + k FLOAT PRIMARY KEY, + v BIGINT, +) +PARTITION ON COLUMNS (k) ( + k < 1.25, + k >= 1.25 +); + +Affected Rows: 0 + +CREATE TABLE partition_pruning_cast_control ( + ts TIMESTAMP TIME INDEX, + k FLOAT PRIMARY KEY, + v BIGINT, +); + +Affected Rows: 0 + +INSERT INTO partition_pruning_cast_partitioned VALUES + (1000, 1.0, 10), + (1000, 1.5, 15), + (1000, 2.5, 25); + +Affected Rows: 3 + +INSERT INTO partition_pruning_cast_control VALUES + (1000, 1.0, 10), + (1000, 1.5, 15), + (1000, 2.5, 25); + +Affected Rows: 3 + +SELECT k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_partitioned ORDER BY k; + ++-----+----+--------+ +| k | v | cast_k | ++-----+----+--------+ +| 1.0 | 10 | 1 | +| 1.5 | 15 | 1 | +| 2.5 | 25 | 2 | ++-----+----+--------+ + +SELECT k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_control ORDER BY k; + ++-----+----+--------+ +| k | v | cast_k | ++-----+----+--------+ +| 1.0 | 10 | 1 | +| 1.5 | 15 | 1 | +| 2.5 | 25 | 2 | ++-----+----+--------+ + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +EXPLAIN SELECT k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_partitioned +WHERE CAST(k AS BIGINT) = 1; + ++---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| plan_type | plan | ++---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| logical_plan | MergeScan [is_placeholder=false, remote_input=[ | +| | Projection: partition_pruning_cast_partitioned.k, partition_pruning_cast_partitioned.v, CAST(partition_pruning_cast_partitioned.k AS Int64) AS cast_k | +| | Filter: CAST(partition_pruning_cast_partitioned.k AS Int64) = Int64(1) | +| | TableScan: partition_pruning_cast_partitioned, partial_filters=[CAST(partition_pruning_cast_partitioned.k AS Int64) = Int64(1)] | +| | ]] | +| physical_plan | CooperativeExec | +| | MergeScanExec: REDACTED +| | | ++---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+ + +SELECT ts, k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_partitioned +WHERE CAST(k AS BIGINT) = 1 +ORDER BY k; + ++---------------------+-----+----+--------+ +| ts | k | v | cast_k | ++---------------------+-----+----+--------+ +| 1970-01-01T00:00:01 | 1.0 | 10 | 1 | +| 1970-01-01T00:00:01 | 1.5 | 15 | 1 | ++---------------------+-----+----+--------+ + +SELECT ts, k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_control +WHERE CAST(k AS BIGINT) = 1 +ORDER BY k; + ++---------------------+-----+----+--------+ +| ts | k | v | cast_k | ++---------------------+-----+----+--------+ +| 1970-01-01T00:00:01 | 1.0 | 10 | 1 | +| 1970-01-01T00:00:01 | 1.5 | 15 | 1 | ++---------------------+-----+----+--------+ + +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +-- SQLNESS REPLACE (elapsed_compute.*) REDACTED +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (metrics.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED +-- SQLNESS REPLACE \\?"(dyn_filters|time_range|file_summary|max_part_range)\\?":.* "$1":REDACTED +EXPLAIN ANALYZE SELECT k, v +FROM partition_pruning_cast_partitioned +WHERE k >= 1.25::FLOAT AND k < 2.5::FLOAT; + ++-+-+-+ +| stage | node | plan_| ++-+-+-+ +| 0_| 0_|_CooperativeExec REDACTED +|_|_|_MergeScanExec: REDACTED +|_|_|_| +| 1_| 0_|_CooperativeExec REDACTED +|_|_|_SeqScan: region=REDACTED, "partition_count":{"count":1, "mem_ranges":1, "files":0, "file_ranges":0}, "projection": ["k", "v"], "filters": ["k >= Float32(1.25)", "k < Float32(2.5)", "k >= Float32(1.25)"], "time_range":REDACTED +|_|_|_| +|_|_| Total rows: 1_| ++-+-+-+ + +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +-- SQLNESS REPLACE (elapsed_compute.*) REDACTED +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (metrics.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED +-- SQLNESS REPLACE \\?"(dyn_filters|time_range|file_summary|max_part_range)\\?":.* "$1":REDACTED +EXPLAIN ANALYZE SELECT k, v +FROM partition_pruning_cast_partitioned +WHERE k >= 1.25::FLOAT AND k < 2.5::FLOAT AND CAST(v AS INT) IS NOT NULL; + ++-+-+-+ +| stage | node | plan_| ++-+-+-+ +| 0_| 0_|_CooperativeExec REDACTED +|_|_|_MergeScanExec: REDACTED +|_|_|_| +| 1_| 0_|_FilterExec: CAST(v@1 AS Int32) IS NOT NULL REDACTED +|_|_|_CooperativeExec REDACTED +|_|_|_SeqScan: region=REDACTED, "partition_count":{"count":1, "mem_ranges":1, "files":0, "file_ranges":0}, "projection": ["k", "v"], "filters": ["k >= Float32(1.25)", "k < Float32(2.5)", "CAST(v AS Int32) IS NOT NULL", "k >= Float32(1.25)"], "time_range":REDACTED +|_|_|_| +|_|_| Total rows: 1_| ++-+-+-+ + +SELECT k, v FROM partition_pruning_cast_partitioned +WHERE k >= 1.25::FLOAT AND k < 2.5::FLOAT AND CAST(v AS INT) IS NOT NULL; + ++-----+----+ +| k | v | ++-----+----+ +| 1.5 | 15 | ++-----+----+ + +SELECT k FROM partition_pruning_cast_partitioned +WHERE k = 1.0 OR CAST(k AS BIGINT) = 1 ORDER BY k; + ++-----+ +| k | ++-----+ +| 1.0 | +| 1.5 | ++-----+ + +DROP TABLE partition_pruning_cast_partitioned; + +Affected Rows: 0 + +DROP TABLE partition_pruning_cast_control; + +Affected Rows: 0 + diff --git a/tests/cases/distributed/optimizer/partition_pruning_cast.sql b/tests/cases/distributed/optimizer/partition_pruning_cast.sql new file mode 100644 index 00000000000..058045f733a --- /dev/null +++ b/tests/cases/distributed/optimizer/partition_pruning_cast.sql @@ -0,0 +1,75 @@ +CREATE TABLE partition_pruning_cast_partitioned ( + ts TIMESTAMP TIME INDEX, + k FLOAT PRIMARY KEY, + v BIGINT, +) +PARTITION ON COLUMNS (k) ( + k < 1.25, + k >= 1.25 +); + +CREATE TABLE partition_pruning_cast_control ( + ts TIMESTAMP TIME INDEX, + k FLOAT PRIMARY KEY, + v BIGINT, +); + +INSERT INTO partition_pruning_cast_partitioned VALUES + (1000, 1.0, 10), + (1000, 1.5, 15), + (1000, 2.5, 25); +INSERT INTO partition_pruning_cast_control VALUES + (1000, 1.0, 10), + (1000, 1.5, 15), + (1000, 2.5, 25); + +SELECT k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_partitioned ORDER BY k; +SELECT k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_control ORDER BY k; + +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +EXPLAIN SELECT k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_partitioned +WHERE CAST(k AS BIGINT) = 1; + +SELECT ts, k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_partitioned +WHERE CAST(k AS BIGINT) = 1 +ORDER BY k; +SELECT ts, k, v, CAST(k AS BIGINT) AS cast_k +FROM partition_pruning_cast_control +WHERE CAST(k AS BIGINT) = 1 +ORDER BY k; + +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +-- SQLNESS REPLACE (elapsed_compute.*) REDACTED +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (metrics.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED +-- SQLNESS REPLACE \\?"(dyn_filters|time_range|file_summary|max_part_range)\\?":.* "$1":REDACTED +EXPLAIN ANALYZE SELECT k, v +FROM partition_pruning_cast_partitioned +WHERE k >= 1.25::FLOAT AND k < 2.5::FLOAT; +-- SQLNESS REPLACE (-+) - +-- SQLNESS REPLACE (\s\s+) _ +-- SQLNESS REPLACE (elapsed_compute.*) REDACTED +-- SQLNESS REPLACE (peers.*) REDACTED +-- SQLNESS REPLACE (metrics.*) REDACTED +-- SQLNESS REPLACE (partitioning.*) REDACTED +-- SQLNESS REPLACE region=\d+\(\d+,\s+\d+\) region=REDACTED +-- SQLNESS REPLACE \\?"(dyn_filters|time_range|file_summary|max_part_range)\\?":.* "$1":REDACTED +EXPLAIN ANALYZE SELECT k, v +FROM partition_pruning_cast_partitioned +WHERE k >= 1.25::FLOAT AND k < 2.5::FLOAT AND CAST(v AS INT) IS NOT NULL; +SELECT k, v FROM partition_pruning_cast_partitioned +WHERE k >= 1.25::FLOAT AND k < 2.5::FLOAT AND CAST(v AS INT) IS NOT NULL; + +SELECT k FROM partition_pruning_cast_partitioned +WHERE k = 1.0 OR CAST(k AS BIGINT) = 1 ORDER BY k; + +DROP TABLE partition_pruning_cast_partitioned; +DROP TABLE partition_pruning_cast_control;