Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,061 changes: 74 additions & 987 deletions rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions rust/cubesqlplanner/cubesqlplanner/src/planner/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ pub mod base_filter;
pub mod base_segment;
pub mod compiler;
pub mod filter_operator;
mod operators;
pub mod typed_filter;

pub use base_filter::BaseFilter;
pub use base_segment::BaseSegment;
pub use filter_operator::FilterOperator;
pub use typed_filter::resolve_base_symbol;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use super::{FilterOperationSql, FilterSqlContext};
use cubenativeutils::CubeError;

#[derive(Clone, Debug)]
pub enum ComparisonKind {
Gt,
Gte,
Lt,
Lte,
}

#[derive(Clone, Debug)]
pub struct ComparisonOp {
kind: ComparisonKind,
value: String,
member_type: Option<String>,
}

impl ComparisonOp {
pub fn new(kind: ComparisonKind, value: String, member_type: Option<String>) -> Self {
Self {
kind,
value,
member_type,
}
}
}

impl FilterOperationSql for ComparisonOp {
fn to_sql(&self, ctx: &FilterSqlContext) -> Result<String, CubeError> {
let param = ctx.allocate_and_cast(&self.value, &self.member_type)?;
match self.kind {
ComparisonKind::Gt => ctx.plan_templates.gt(ctx.member_sql.to_string(), param),
ComparisonKind::Gte => ctx.plan_templates.gte(ctx.member_sql.to_string(), param),
ComparisonKind::Lt => ctx.plan_templates.lt(ctx.member_sql.to_string(), param),
ComparisonKind::Lte => ctx.plan_templates.lte(ctx.member_sql.to_string(), param),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::{FilterOperationSql, FilterSqlContext};
use cubenativeutils::CubeError;

#[derive(Clone, Debug)]
pub enum DateRangeKind {
InRange,
NotInRange,
}

#[derive(Clone, Debug)]
pub struct DateRangeOp {
kind: DateRangeKind,
from: String,
to: String,
}

impl DateRangeOp {
pub fn new(kind: DateRangeKind, from: String, to: String) -> Self {
Self { kind, from, to }
}
}

impl FilterOperationSql for DateRangeOp {
fn to_sql(&self, ctx: &FilterSqlContext) -> Result<String, CubeError> {
let from_param = ctx.format_and_allocate_from_date(&self.from)?;
let to_param = ctx.format_and_allocate_to_date(&self.to)?;
match self.kind {
DateRangeKind::InRange => ctx.plan_templates.time_range_filter(
ctx.member_sql.to_string(),
from_param,
to_param,
),
DateRangeKind::NotInRange => ctx.plan_templates.time_not_in_range_filter(
ctx.member_sql.to_string(),
from_param,
to_param,
),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use super::{FilterOperationSql, FilterSqlContext};
use cubenativeutils::CubeError;

#[derive(Clone, Debug)]
pub enum DateSingleKind {
Before,
BeforeOrOn,
After,
AfterOrOn,
}

#[derive(Clone, Debug)]
pub struct DateSingleOp {
kind: DateSingleKind,
value: String,
}

impl DateSingleOp {
pub fn new(kind: DateSingleKind, value: String) -> Self {
Self { kind, value }
}
}

impl FilterOperationSql for DateSingleOp {
fn to_sql(&self, ctx: &FilterSqlContext) -> Result<String, CubeError> {
match self.kind {
DateSingleKind::Before | DateSingleKind::AfterOrOn => {
let param = ctx.format_and_allocate_from_date(&self.value)?;
match self.kind {
DateSingleKind::Before => {
ctx.plan_templates.lt(ctx.member_sql.to_string(), param)
}
DateSingleKind::AfterOrOn => {
ctx.plan_templates.gte(ctx.member_sql.to_string(), param)
}
_ => unreachable!(),
}
}
DateSingleKind::BeforeOrOn | DateSingleKind::After => {
let param = ctx.format_and_allocate_to_date(&self.value)?;
match self.kind {
DateSingleKind::BeforeOrOn => {
ctx.plan_templates.lte(ctx.member_sql.to_string(), param)
}
DateSingleKind::After => {
ctx.plan_templates.gt(ctx.member_sql.to_string(), param)
}
_ => unreachable!(),
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use super::{FilterOperationSql, FilterSqlContext};
use cubenativeutils::CubeError;

#[derive(Clone, Debug)]
pub struct EqualityOp {
negated: bool,
value: String,
member_type: Option<String>,
}

impl EqualityOp {
pub fn new(negated: bool, value: String, member_type: Option<String>) -> Self {
Self {
negated,
value,
member_type,
}
}
}

impl FilterOperationSql for EqualityOp {
fn to_sql(&self, ctx: &FilterSqlContext) -> Result<String, CubeError> {
let param = ctx.allocate_and_cast(&self.value, &self.member_type)?;
// For negated (notEquals), add OR IS NULL check when value is not null
let need_null_check = self.negated;
if self.negated {
ctx.plan_templates
.not_equals(ctx.member_sql.to_string(), param, need_null_check)
} else {
ctx.plan_templates
.equals(ctx.member_sql.to_string(), param, false)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
use crate::planner::query_tools::QueryTools;
use crate::planner::sql_templates::{PlanSqlTemplates, TemplateProjectionColumn};
use crate::planner::QueryDateTimeHelper;
use cubenativeutils::CubeError;
use std::rc::Rc;

const FROM_PARTITION_RANGE: &str = "__FROM_PARTITION_RANGE";
const TO_PARTITION_RANGE: &str = "__TO_PARTITION_RANGE";

pub struct FilterSqlContext<'a> {
pub member_sql: &'a str,
pub query_tools: &'a Rc<QueryTools>,
pub plan_templates: &'a PlanSqlTemplates,
pub use_db_time_zone: bool,
pub use_raw_values: bool,
}

impl<'a> FilterSqlContext<'a> {
pub fn allocate_param(&self, value: &str) -> String {
self.query_tools.allocate_param(value)
}

pub fn cast_param(
&self,
value: &str,
member_type: &Option<String>,
) -> Result<String, CubeError> {
match member_type.as_deref() {
Some("boolean") => self.plan_templates.bool_param_cast(value),
Some("number") => self.plan_templates.number_param_cast(value),
_ => Ok(value.to_string()),
}
}

pub fn allocate_and_cast(
&self,
value: &str,
member_type: &Option<String>,
) -> Result<String, CubeError> {
let allocated = self.allocate_param(value);
self.cast_param(&allocated, member_type)
}

pub fn allocate_and_cast_values(
&self,
values: &[Option<String>],
member_type: &Option<String>,
) -> Result<Vec<String>, CubeError> {
values
.iter()
.filter_map(|v| v.as_ref())
.map(|v| self.allocate_and_cast(v, member_type))
.collect()
}

pub fn allocate_timestamp_param(&self, value: &str) -> Result<String, CubeError> {
if self.use_raw_values {
return Ok(value.to_string());
}
let placeholder = self.query_tools.allocate_param(value);
self.plan_templates.time_stamp_cast(placeholder)
}

pub fn format_and_allocate_from_date(&self, value: &str) -> Result<String, CubeError> {
if self.use_raw_values {
return Ok(value.to_string());
}
if self.is_partition_range(value) {
return self.allocate_timestamp_param(value);
}
let precision = self.plan_templates.timestamp_precision()?;
let formatted = QueryDateTimeHelper::format_from_date(value, precision)?;
let with_tz = self.apply_db_time_zone(formatted)?;
self.allocate_timestamp_param(&with_tz)
}

pub fn format_and_allocate_to_date(&self, value: &str) -> Result<String, CubeError> {
if self.use_raw_values {
return Ok(value.to_string());
}
if self.is_partition_range(value) {
return self.allocate_timestamp_param(value);
}
let precision = self.plan_templates.timestamp_precision()?;
let formatted = QueryDateTimeHelper::format_to_date(value, precision)?;
let with_tz = self.apply_db_time_zone(formatted)?;
self.allocate_timestamp_param(&with_tz)
}

fn is_partition_range(&self, value: &str) -> bool {
value == FROM_PARTITION_RANGE || value == TO_PARTITION_RANGE
}

fn apply_db_time_zone(&self, value: String) -> Result<String, CubeError> {
if self.use_db_time_zone {
self.plan_templates.in_db_time_zone(value)
} else {
Ok(value)
}
}

pub fn convert_tz(&self, field: &str) -> Result<String, CubeError> {
self.plan_templates.convert_tz(field.to_string())
}

pub fn date_range_from_time_series(&self) -> Result<(String, String), CubeError> {
let from_expr = format!(
"min({})",
self.plan_templates.quote_identifier("date_from")?
);
let to_expr = format!("max({})", self.plan_templates.quote_identifier("date_to")?);
let from_expr = self.plan_templates.series_bounds_cast(&from_expr)?;
let to_expr = self.plan_templates.series_bounds_cast(&to_expr)?;
let alias = "value".to_string();
let time_series_cte_name = "time_series".to_string();

let from_column = TemplateProjectionColumn {
expr: from_expr.clone(),
alias: alias.clone(),
aliased: self.plan_templates.column_aliased(&from_expr, &alias)?,
};
let to_column = TemplateProjectionColumn {
expr: to_expr.clone(),
alias: alias.clone(),
aliased: self.plan_templates.column_aliased(&to_expr, &alias)?,
};

let from = self.plan_templates.select(
vec![],
&time_series_cte_name,
vec![from_column],
None,
vec![],
None,
vec![],
None,
None,
false,
)?;
let to = self.plan_templates.select(
vec![],
&time_series_cte_name,
vec![to_column],
None,
vec![],
None,
vec![],
None,
None,
false,
)?;
Ok((format!("({})", from), format!("({})", to)))
}

pub fn extend_date_range_bound(
&self,
date: String,
interval: &Option<String>,
is_sub: bool,
) -> Result<Option<String>, CubeError> {
match interval {
Some(interval) if interval != "unbounded" => {
if is_sub {
Ok(Some(
self.plan_templates
.subtract_interval(date, interval.clone())?,
))
} else {
Ok(Some(
self.plan_templates.add_interval(date, interval.clone())?,
))
}
}
Some(_) => Ok(None), // unbounded
None => Ok(Some(date)),
}
}
}

pub trait FilterOperationSql {
fn to_sql(&self, ctx: &FilterSqlContext) -> Result<String, CubeError>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use super::{FilterOperationSql, FilterSqlContext};
use cubenativeutils::CubeError;

#[derive(Clone, Debug)]
pub struct InListOp {
negated: bool,
values: Vec<Option<String>>,
member_type: Option<String>,
}

impl InListOp {
pub fn new(negated: bool, values: Vec<Option<String>>, member_type: Option<String>) -> Self {
Self {
negated,
values,
member_type,
}
}
}

impl FilterOperationSql for InListOp {
fn to_sql(&self, ctx: &FilterSqlContext) -> Result<String, CubeError> {
let has_null = self.values.iter().any(|v| v.is_none());
let need_null_check = if self.negated { !has_null } else { has_null };
let allocated = ctx.allocate_and_cast_values(&self.values, &self.member_type)?;

if self.negated {
ctx.plan_templates
.not_in_where(ctx.member_sql.to_string(), allocated, need_null_check)
} else {
ctx.plan_templates
.in_where(ctx.member_sql.to_string(), allocated, need_null_check)
}
}
}
Loading
Loading