diff --git a/packages/cubejs-backend-shared/src/env.ts b/packages/cubejs-backend-shared/src/env.ts index 15098821b7175..dd0391a68a0d2 100644 --- a/packages/cubejs-backend-shared/src/env.ts +++ b/packages/cubejs-backend-shared/src/env.ts @@ -224,6 +224,7 @@ const variables: Record any> = { .default('1') .asInt(), nativeSqlPlanner: () => get('CUBEJS_TESSERACT_SQL_PLANNER').default('false').asBool(), + nativeSqlPlannerPreAggregations: () => get('CUBEJS_TESSERACT_PRE_AGGREGATIONS').default('false').asBool(), nativeOrchestrator: () => get('CUBEJS_TESSERACT_ORCHESTRATOR') .default('true') .asBoolStrict(), diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index 483efe6e3e44d..c5de58b620094 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -336,8 +336,8 @@ export class BaseQuery { */ this.customSubQueryJoins = this.options.subqueryJoins ?? []; this.useNativeSqlPlanner = this.options.useNativeSqlPlanner ?? getEnv('nativeSqlPlanner'); - this.canUseNativeSqlPlannerPreAggregation = false; - if (this.useNativeSqlPlanner && !this.neverUseSqlPlannerPreaggregation()) { + this.canUseNativeSqlPlannerPreAggregation = getEnv('nativeSqlPlannerPreAggregations'); + if (this.useNativeSqlPlanner && !this.canUseNativeSqlPlannerPreAggregation && !this.neverUseSqlPlannerPreaggregation()) { const fullAggregateMeasures = this.fullKeyQueryAggregateMeasures({ hasMultipliedForPreAggregation: true }); this.canUseNativeSqlPlannerPreAggregation = fullAggregateMeasures.multiStageMembers.length > 0; diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations.test.ts index 81ea1daee1e3e..d38ce42485245 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/pre-aggregations.test.ts @@ -278,6 +278,50 @@ describe('PreAggregations', () => { } }) + cube('visitor_checkins2', { + sql: \` + select * from visitor_checkins + \`, + + sqlAlias: 'vc2', + + measures: { + count: { + type: 'count' + } + }, + + dimensions: { + id: { + type: 'number', + sql: 'id', + primaryKey: true + }, + visitor_id: { + type: 'number', + sql: 'visitor_id' + }, + source: { + type: 'string', + sql: 'source' + }, + created_at: { + type: 'time', + sql: 'created_at' + } + }, + preAggregations: { + forLambdaS: { + type: 'rollup', + measureReferences: [count], + dimensionReferences: [visitor_id], + timeDimensionReference: created_at, + partitionGranularity: 'day', + granularity: 'day' + }, + } + }) + cube('visitor_checkins', { sql: \` @@ -316,6 +360,10 @@ describe('PreAggregations', () => { main: { type: 'originalSql' }, + lambda: { + type: 'rollupLambda', + rollups: [visitor_checkins.forLambda, visitor_checkins2.forLambdaS], + }, forJoin: { type: 'rollup', measureReferences: [count], @@ -327,6 +375,14 @@ describe('PreAggregations', () => { dimensionReferences: [visitors.source], rollupReferences: [visitor_checkins.forJoin, visitors.forJoin], }, + forLambda: { + type: 'rollup', + measureReferences: [count], + dimensionReferences: [visitor_id], + timeDimensionReference: created_at, + partitionGranularity: 'day', + granularity: 'day' + }, joinedPartitioned: { type: 'rollupJoin', measureReferences: [count], @@ -1922,7 +1978,7 @@ describe('PreAggregations', () => { }, { id: 'visitors.source' }], - cubestoreSupportMultistage: getEnv("nativeSqlPlanner") + cubestoreSupportMultistage: getEnv('nativeSqlPlanner') }); const queryAndParams = query.buildSqlAndParams(); @@ -2000,7 +2056,7 @@ describe('PreAggregations', () => { }, { id: 'visitors.source' }], - cubestoreSupportMultistage: getEnv("nativeSqlPlanner") + cubestoreSupportMultistage: getEnv('nativeSqlPlanner') }); const queryAndParams = query.buildSqlAndParams(); @@ -2138,6 +2194,78 @@ describe('PreAggregations', () => { }); }); + if (getEnv('nativeSqlPlanner') && getEnv('nativeSqlPlannerPreAggregations')) { + it('rollup lambda', async () => { + await compiler.compile(); + + const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, { + measures: [ + 'visitor_checkins.count', + ], + dimensions: ['visitor_checkins.visitor_id'], + timeDimensions: [{ + dimension: 'visitor_checkins.created_at', + granularity: 'day', + dateRange: ['2016-12-26', '2017-01-08'] + }], + timezone: 'America/Los_Angeles', + preAggregationsSchema: '', + order: [{ + id: 'visitor_checkins.visitor_id', + }], + }); + + const queryAndParams = query.buildSqlAndParams(); + console.log(queryAndParams); + const preAggregationsDescription: any = query.preAggregations?.preAggregationsDescription(); + console.log(preAggregationsDescription); + + console.log(query.preAggregations?.rollupMatchResultDescriptions()); + + const queries = dbRunner.tempTablePreAggregations(preAggregationsDescription); + + console.log(JSON.stringify(queries.concat(queryAndParams))); + + return dbRunner.evaluateQueryWithPreAggregations(query).then(res => { + console.log(JSON.stringify(res)); + expect(res).toEqual( + [ + { + vc__visitor_id: 1, + vc__created_at_day: '2017-01-02T00:00:00.000Z', + vc__count: '2' + }, + { + vc__visitor_id: 1, + vc__created_at_day: '2017-01-03T00:00:00.000Z', + vc__count: '2' + }, + { + vc__visitor_id: 1, + vc__created_at_day: '2017-01-04T00:00:00.000Z', + vc__count: '2' + }, + { + vc__visitor_id: 2, + vc__created_at_day: '2017-01-04T00:00:00.000Z', + vc__count: '4' + }, + { + vc__visitor_id: 3, + vc__created_at_day: '2017-01-05T00:00:00.000Z', + vc__count: '2' + } + ] + ); + }); + }); + } else { + it.skip('rollup lambda: baseQuery generate wrong sql for not external pre-aggregations', async () => { + // This should be fixed in Tesseract. + + }); + } + it('rollup join', async () => { await compiler.compile(); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/compiled_pre_aggregation.rs b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/compiled_pre_aggregation.rs index 291b7e1b69ff1..d95c75af20d8b 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/compiled_pre_aggregation.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/compiled_pre_aggregation.rs @@ -4,8 +4,8 @@ use std::rc::Rc; #[derive(Clone)] pub struct PreAggregationJoinItem { - pub from: PreAggregationTable, - pub to: PreAggregationTable, + pub from: Rc, + pub to: Rc, pub from_members: Vec>, pub to_members: Vec>, pub on_sql: Rc, @@ -13,10 +13,15 @@ pub struct PreAggregationJoinItem { #[derive(Clone)] pub struct PreAggregationJoin { - pub root: PreAggregationTable, + pub root: Rc, pub items: Vec, } +#[derive(Clone)] +pub struct PreAggregationUnion { + pub items: Vec>, +} + #[derive(Clone)] pub struct PreAggregationTable { pub cube_name: String, @@ -26,8 +31,9 @@ pub struct PreAggregationTable { #[derive(Clone)] pub enum PreAggregationSource { - Table(PreAggregationTable), + Single(PreAggregationTable), Join(PreAggregationJoin), + Union(PreAggregationUnion), } #[derive(Clone)] diff --git a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/pre_aggregations_compiler.rs b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/pre_aggregations_compiler.rs index b97b8a529907f..1caef67833177 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/pre_aggregations_compiler.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/pre_aggregations_compiler.rs @@ -6,6 +6,7 @@ use crate::cube_bridge::pre_aggregation_description::PreAggregationDescription; use crate::logical_plan::PreAggregationJoin; use crate::logical_plan::PreAggregationJoinItem; use crate::logical_plan::PreAggregationTable; +use crate::logical_plan::PreAggregationUnion; use crate::planner::planners::JoinPlanner; use crate::planner::planners::ResolvedJoinItem; use crate::planner::query_tools::QueryTools; @@ -103,6 +104,11 @@ impl PreAggregationsCompiler { }; let static_data = description.static_data(); + + if static_data.pre_aggregation_type == "rollupLambda" { + return self.build_lambda(name, &description); + } + let measures = if let Some(refs) = description.measure_references()? { Self::symbols_from_ref( self.query_tools.clone(), @@ -151,7 +157,7 @@ impl PreAggregationsCompiler { let source = if static_data.pre_aggregation_type == "rollupJoin" { PreAggregationSource::Join(self.build_join_source(&measures, &dimensions, &rollups)?) } else { - PreAggregationSource::Table(PreAggregationTable { + PreAggregationSource::Single(PreAggregationTable { cube_name: name.cube_name.clone(), name: name.name.clone(), alias: static_data.sql_alias.clone(), @@ -173,6 +179,108 @@ impl PreAggregationsCompiler { Ok(res) } + fn build_lambda( + &mut self, + name: &PreAggregationFullName, + description: &Rc, + ) -> Result, CubeError> { + let rollups = if let Some(refs) = description.rollup_references()? { + let r = self + .query_tools + .cube_evaluator() + .evaluate_rollup_references(name.cube_name.clone(), refs)?; + r + } else { + Vec::new() + }; + if rollups.is_empty() { + return Err(CubeError::user(format!( + "rollupLambda '{}.{}' should reference at least one rollup", + name.cube_name, name.name + ))); + } + + let pre_aggrs_for_lambda = rollups + .iter() + .map(|item| -> Result<_, CubeError> { + self.compile_pre_aggregation(&PreAggregationFullName::from_string(item)?) + }) + .collect::, _>>()?; + + let mut sources = vec![]; + for (i, rollup) in pre_aggrs_for_lambda.clone().iter().enumerate() { + match rollup.source.as_ref() { + PreAggregationSource::Single(table) => { + sources.push(Rc::new(table.clone())); + } + _ => { + return Err(CubeError::user(format!("Rollup lambda can't be nested"))); + } + } + if i > 1 { + Self::match_symbols(&rollup.measures, &pre_aggrs_for_lambda[0].measures)?; + Self::match_symbols(&rollup.dimensions, &pre_aggrs_for_lambda[0].dimensions)?; + Self::match_time_dimensions( + &rollup.time_dimensions, + &pre_aggrs_for_lambda[0].time_dimensions, + )?; + } + } + + let measures = pre_aggrs_for_lambda[0].measures.clone(); + let dimensions = pre_aggrs_for_lambda[0].dimensions.clone(); + let time_dimensions = pre_aggrs_for_lambda[0].time_dimensions.clone(); + let allow_non_strict_date_range_match = description + .static_data() + .allow_non_strict_date_range_match + .unwrap_or(false); + let granularity = pre_aggrs_for_lambda[0].granularity.clone(); + let source = PreAggregationSource::Union(PreAggregationUnion { items: sources }); + + let static_data = description.static_data(); + let res = Rc::new(CompiledPreAggregation { + name: static_data.name.clone(), + cube_name: name.cube_name.clone(), + source: Rc::new(source), + granularity, + external: static_data.external, + measures, + dimensions, + time_dimensions, + allow_non_strict_date_range_match, + }); + self.compiled_cache.insert(name.clone(), res.clone()); + Ok(res) + } + + fn match_symbols( + a: &Vec>, + b: &Vec>, + ) -> Result<(), CubeError> { + if !a.iter().zip(b.iter()).all(|(a, b)| a.name() == b.name()) { + return Err(CubeError::user(format!( + "Names for pre-aggregation symbols in lambda pre-aggragation don't match" + ))); + } + Ok(()) + } + + fn match_time_dimensions( + a: &Vec<(Rc, Option)>, + b: &Vec<(Rc, Option)>, + ) -> Result<(), CubeError> { + if !a + .iter() + .zip(b.iter()) + .all(|(a, b)| a.0.name() == b.0.name() && a.1 == b.1) + { + return Err(CubeError::user(format!( + "Names for pre-aggregation symbols in lambda pre-aggragation don't match" + ))); + } + Ok(()) + } + fn build_join_source( &mut self, measures: &Vec>, @@ -249,21 +357,9 @@ impl PreAggregationsCompiler { let to_pre_aggr = self.find_pre_aggregation_for_join(pre_aggrs_for_join, &join_item.to_members)?; - let from_table = match from_pre_aggr.source.as_ref() { - PreAggregationSource::Table(t) => t.clone(), - PreAggregationSource::Join(_) => { - return Err(CubeError::user(format!("Rollup join can't be nested"))); - } - }; - let to_table = match to_pre_aggr.source.as_ref() { - PreAggregationSource::Table(t) => t.clone(), - PreAggregationSource::Join(_) => { - return Err(CubeError::user(format!("Rollup join can't be nested"))); - } - }; let res = PreAggregationJoinItem { - from: from_table, - to: to_table, + from: from_pre_aggr.source.clone(), + to: to_pre_aggr.source.clone(), from_members: join_item.from_members.clone(), to_members: join_item.to_members.clone(), on_sql: join_item.on_sql.clone(), diff --git a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs index d61a8facc853d..e2ee80f9aaacf 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/pre_aggregation.rs @@ -24,7 +24,7 @@ impl PrettyPrint for PreAggregation { result.println(&format!("cube_name: {}", self.cube_name), &state); result.println(&format!("source:"), &state); match self.source.as_ref() { - PreAggregationSource::Table(table) => { + PreAggregationSource::Single(table) => { let state = state.new_level(); result.println( &format!("table: {}.{}", table.cube_name, table.name), @@ -35,6 +35,13 @@ impl PrettyPrint for PreAggregation { let state = state.new_level(); result.println(&format!("rollup join"), &state); } + PreAggregationSource::Union(union) => { + result.println("Union:", &state); + let state = state.new_level(); + for item in union.items.iter() { + result.println(&format!("-{}.{}", item.cube_name, item.name), &state); + } + } } result.println(&format!("external: {}", self.external), &state); result.println( diff --git a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs index a102820733571..b7099a954ab38 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs @@ -238,6 +238,7 @@ impl PhysicalPlanBuilder { pre_aggregation_schema.add_column(SchemaColumn::new(alias, Some(meas.full_name()))); } let from = self.make_pre_aggregation_source( + &pre_aggregation, &pre_aggregation.source, context, measure_references, @@ -248,21 +249,26 @@ impl PhysicalPlanBuilder { fn make_pre_aggregation_source( &self, + pre_aggregation: &Rc, source: &Rc, _context: &PhysicalPlanBuilderContext, _measure_references: &mut HashMap, dimensions_references: &mut HashMap, ) -> Result, CubeError> { let from = match source.as_ref() { - PreAggregationSource::Table(table) => { + PreAggregationSource::Single(table) => { let table_source = self.make_pre_aggregation_table_source(table)?; From::new(FromSource::Single(table_source)) } + PreAggregationSource::Union(union) => { + let source = self.make_pre_aggregation_union_source(pre_aggregation, union)?; + source + } PreAggregationSource::Join(join) => { - let root_table_source = self.make_pre_aggregation_table_source(&join.root)?; + let root_table_source = self.make_pre_aggregation_join_source(&join.root)?; let mut join_builder = JoinBuilder::new(root_table_source); for item in join.items.iter() { - let to_table_source = self.make_pre_aggregation_table_source(&item.to)?; + let to_table_source = self.make_pre_aggregation_join_source(&item.to)?; let condition = SqlJoinCondition::try_new(self.query_tools.clone(), item.on_sql.clone())?; let on = JoinCondition::new_base_join(condition); @@ -287,6 +293,114 @@ impl PhysicalPlanBuilder { Ok(from) } + fn make_pre_aggregation_join_source( + &self, + source: &PreAggregationSource, + ) -> Result { + match source { + PreAggregationSource::Single(table) => self.make_pre_aggregation_table_source(table), + PreAggregationSource::Union(_) => Err(CubeError::user(format!( + "Lambda rollups not allowed inside join rollups" + ))), + PreAggregationSource::Join(_) => { + Err(CubeError::user(format!("Nested rollup joins not allowed"))) + } + } + } + + fn make_pre_aggregation_union_source( + &self, + pre_aggregation: &Rc, + union: &PreAggregationUnion, + ) -> Result, CubeError> { + if union.items.len() == 1 { + let table_source = self.make_pre_aggregation_table_source(&union.items[0])?; + return Ok(From::new(FromSource::Single(table_source))); + } + + let mut union_sources = Vec::new(); + for item in union.items.iter() { + let table_source = self.make_pre_aggregation_table_source(&item)?; + let from = From::new(FromSource::Single(table_source)); + let mut select_builder = SelectBuilder::new(from); + for dim in pre_aggregation.dimensions.iter() { + let member_ref = dim.clone().as_base_member(self.query_tools.clone())?; + let name_in_table = BaseMemberHelper::default_alias( + &item.cube_name, + &dim.name(), + &None, + self.query_tools.clone(), + )?; + let alias = BaseMemberHelper::default_alias( + &dim.cube_name(), + &dim.name(), + &None, + self.query_tools.clone(), + )?; + select_builder.add_projection_reference_member( + &member_ref, + QualifiedColumnName::new(None, name_in_table), + Some(alias), + ); + } + for (dim, granularity) in pre_aggregation.time_dimensions.iter() { + let member_ref = dim.clone().as_base_member(self.query_tools.clone())?; + let name_in_table = BaseMemberHelper::default_alias( + &item.cube_name, + &dim.name(), + granularity, + self.query_tools.clone(), + )?; + let alias = BaseMemberHelper::default_alias( + &dim.cube_name(), + &dim.name(), + granularity, + self.query_tools.clone(), + )?; + select_builder.add_projection_reference_member( + &member_ref, + QualifiedColumnName::new(None, name_in_table.clone()), + Some(alias), + ); + } + for meas in pre_aggregation.measures.iter() { + let member_ref = meas.clone().as_base_member(self.query_tools.clone())?; + let name_in_table = BaseMemberHelper::default_alias( + &item.cube_name, + &meas.name(), + &meas.alias_suffix(), + self.query_tools.clone(), + )?; + let alias = BaseMemberHelper::default_alias( + &meas.cube_name(), + &meas.name(), + &meas.alias_suffix(), + self.query_tools.clone(), + )?; + select_builder.add_projection_reference_member( + &member_ref, + QualifiedColumnName::new(None, name_in_table.clone()), + Some(alias), + ); + } + let context = SqlNodesFactory::new(); + let select = select_builder.build(context); + let query_plan = QueryPlan::Select(Rc::new(select)); + union_sources.push(query_plan); + } + + let plan = QueryPlan::Union(Rc::new(Union::new(union_sources))); + let source = SingleSource::Subquery(Rc::new(plan)); + let alias = PlanSqlTemplates::memeber_alias_name( + &pre_aggregation.cube_name, + &pre_aggregation.name, + &None, + ); + let aliased_source = SingleAliasedSource { source, alias }; + let from = From::new(FromSource::Single(aliased_source)); + Ok(from) + } + fn make_pre_aggregation_table_source( &self, table: &PreAggregationTable, diff --git a/rust/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs b/rust/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs index 1b2cd5c521996..11177a70173a8 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs @@ -110,6 +110,28 @@ impl SelectBuilder { self.result_schema .add_column(SchemaColumn::new(alias.clone(), None)); } + pub fn add_projection_reference_member( + &mut self, + member: &Rc, + reference: QualifiedColumnName, + alias: Option, + ) { + let alias = if let Some(alias) = alias { + alias + } else { + reference.name().clone() + }; + + let expr = Expr::Reference(reference); + let aliased_expr = AliasedExpr { + expr, + alias: alias.clone(), + }; + + self.projection_columns.push(aliased_expr); + self.result_schema + .add_column(SchemaColumn::new(alias.clone(), Some(member.full_name()))); + } pub fn add_projection_coalesce_member( &mut self, member: &Rc, diff --git a/rust/cubesqlplanner/cubesqlplanner/src/plan/from.rs b/rust/cubesqlplanner/cubesqlplanner/src/plan/from.rs index baf1ee38b8224..bbbade5a6a25b 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/plan/from.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/plan/from.rs @@ -81,6 +81,14 @@ impl SingleAliasedSource { context: Rc, ) -> Result { let sql = self.source.to_sql(templates, context)?; + /* if let SingleSource::Subquery(plan) = &self.source { + if let QueryPlan::Union(_) = plan.as_ref() { + //FIXME CubeStore (at least old cubestore) don't support alias for union + if templates.is_external() { + return Ok(sql); + } + } + } */ templates.query_aliased(&sql, &self.alias) } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs index 826bbfee2ddaa..e3f9b48399988 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs @@ -163,7 +163,7 @@ impl QueryTools { pub fn plan_sql_templates(&self, external: bool) -> Result { let driver_tools = self.base_tools.driver_tools(external)?; - PlanSqlTemplates::try_new(driver_tools) + PlanSqlTemplates::try_new(driver_tools, external) } pub fn base_tools(&self) -> &Rc { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs index 551aacd6847d9..82c05856ece9a 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs @@ -11,6 +11,7 @@ use std::rc::Rc; pub struct PlanSqlTemplates { render: Rc, driver_tools: Rc, + external: bool, } pub const UNDERSCORE_UPPER_BOUND: Boundary = Boundary { name: "UnderscoreUpper", @@ -41,11 +42,12 @@ pub const UPPER_UPPER_BOUND: Boundary = Boundary { }; impl PlanSqlTemplates { - pub fn try_new(driver_tools: Rc) -> Result { + pub fn try_new(driver_tools: Rc, external: bool) -> Result { let render = driver_tools.sql_templates()?; Ok(Self { render, driver_tools, + external, }) } @@ -53,6 +55,10 @@ impl PlanSqlTemplates { self.driver_tools.convert_tz(field) } + pub fn is_external(&self) -> bool { + self.external + } + pub fn time_grouped_column( &self, granularity: String,