Skip to content

Commit ad251c2

Browse files
committed
feat(tesseract): Initial BigQuery support
1 parent 02c4cc4 commit ad251c2

File tree

26 files changed

+194
-137
lines changed

26 files changed

+194
-137
lines changed

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ export class BaseQuery {
326326
this.allFilters = this.timeDimensions.concat(this.segments).concat(this.filters);
327327
this.useNativeSqlPlanner = this.options.useNativeSqlPlanner ?? getEnv('nativeSqlPlanner');
328328
this.canUseNativeSqlPlannerPreAggregation = false;
329-
if (this.useNativeSqlPlanner) {
329+
if (this.useNativeSqlPlanner && !this.neverUseSqlPlannerPreaggregation()) {
330330
const hasMultiStageMeasures = this.fullKeyQueryAggregateMeasures({ hasMultipliedForPreAggregation: true }).multiStageMembers.length > 0;
331331
this.canUseNativeSqlPlannerPreAggregation = hasMultiStageMeasures;
332332
}
@@ -349,6 +349,11 @@ export class BaseQuery {
349349
this.initUngrouped();
350350
}
351351

352+
// Temporary workaround to avoid checking for multistage in CubeStoreQuery, since that could lead to errors when HLL functions are present in the query.
353+
neverUseSqlPlannerPreaggregation() {
354+
return false;
355+
}
356+
352357
prebuildJoin() {
353358
try {
354359
// TODO allJoinHints should contain join hints form pre-agg
@@ -747,7 +752,8 @@ export class BaseQuery {
747752
}
748753
}
749754

750-
return this.buildSqlAndParamsRust(exportAnnotatedSql);
755+
const res = this.buildSqlAndParamsRust(exportAnnotatedSql);
756+
return res;
751757
}
752758

753759
if (!this.options.preAggregationQuery && !this.options.disableExternalPreAggregations && this.externalQueryClass) {
@@ -756,7 +762,7 @@ export class BaseQuery {
756762
}
757763
}
758764

759-
return this.compilers.compiler.withQuery(
765+
const res = this.compilers.compiler.withQuery(
760766
this,
761767
() => this.cacheValue(
762768
['buildSqlAndParams', exportAnnotatedSql],
@@ -768,14 +774,14 @@ export class BaseQuery {
768774
{ cache: this.queryCache }
769775
)
770776
);
777+
return res;
771778
}
772779

773780
buildSqlAndParamsRust(exportAnnotatedSql) {
774781
const order = this.options.order && R.pipe(
775782
R.map((hash) => ((!hash || !hash.id) ? null : hash)),
776783
R.reject(R.isNil),
777784
)(this.options.order);
778-
779785
const queryParams = {
780786
measures: this.options.measures,
781787
dimensions: this.options.dimensions,
@@ -792,7 +798,8 @@ export class BaseQuery {
792798
baseTools: this,
793799
ungrouped: this.options.ungrouped,
794800
exportAnnotatedSql: exportAnnotatedSql === true,
795-
preAggregationQuery: this.options.preAggregationQuery
801+
preAggregationQuery: this.options.preAggregationQuery,
802+
totalQuery: this.options.totalQuery,
796803
};
797804

798805
const buildResult = nativeBuildSqlAndParams(queryParams);
@@ -871,12 +878,12 @@ export class BaseQuery {
871878

872879
// FIXME helper for native generator, maybe should be moved entirely to rust
873880
generateTimeSeries(granularity, dateRange) {
874-
return timeSeriesBase(granularity, dateRange);
881+
return timeSeriesBase(granularity, dateRange, { timestampPrecision: this.timestampPrecision() });
875882
}
876883

877884
// FIXME helper for native generator, maybe should be moved entirely to rust
878885
generateCustomTimeSeries(granularityInterval, dateRange, origin) {
879-
return timeSeriesFromCustomInterval(granularityInterval, dateRange, moment(origin), { timestampPrecision: 3 });
886+
return timeSeriesFromCustomInterval(granularityInterval, dateRange, moment(origin), { timestampPrecision: this.timestampPrecision() });
880887
}
881888

882889
getPreAggregationByName(cube, preAggregationName) {
@@ -3827,6 +3834,9 @@ export class BaseQuery {
38273834
like_escape: '{{ like_expr }} ESCAPE {{ escape_char }}',
38283835
concat_strings: '{{ strings | join(\' || \' ) }}',
38293836
},
3837+
tesseract: {
3838+
ilike: '{{ expr }} {% if negated %}NOT {% endif %}ILIKE {{ pattern }}', // May require different overloads in Tesseract than the ilike from expressions used in SQLAPI.
3839+
},
38303840
filters: {
38313841
equals: '{{ column }} = {{ value }}{{ is_null_check }}',
38323842
not_equals: '{{ column }} <> {{ value }}{{ is_null_check }}',

packages/cubejs-schema-compiler/src/adapter/BigqueryQuery.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,24 @@ export class BigqueryQuery extends BaseQuery {
261261
templates.expressions.timestamp_literal = 'TIMESTAMP(\'{{ value }}\')';
262262
delete templates.expressions.ilike;
263263
delete templates.expressions.like_escape;
264+
templates.filters.like_pattern = 'CONCAT({% if start_wild %}\'%\'{% else %}\'\'{% endif %}, LOWER({{ value }}), {% if end_wild %}\'%\'{% else %}\'\'{% endif %})';
265+
templates.tesseract.ilike = 'LOWER({{ expr }}) {% if negated %}NOT {% endif %} LIKE {{ pattern }}';
264266
templates.types.boolean = 'BOOL';
265267
templates.types.float = 'FLOAT64';
266268
templates.types.double = 'FLOAT64';
267269
templates.types.decimal = 'BIGDECIMAL({{ precision }},{{ scale }})';
268270
templates.types.binary = 'BYTES';
271+
templates.expressions.cast_to_string = 'CAST({{ expr }} AS STRING)';
269272
templates.operators.is_not_distinct_from = 'IS NOT DISTINCT FROM';
270273
templates.join_types.full = 'FULL';
274+
templates.statements.time_series_select = 'SELECT DATETIME(TIMESTAMP(f)) date_from, DATETIME(TIMESTAMP(t)) date_to \n' +
275+
'FROM (\n' +
276+
'{% for time_item in seria %}' +
277+
' select \'{{ time_item[0] }}\' f, \'{{ time_item[1] }}\' t \n' +
278+
'{% if not loop.last %} UNION ALL\n{% endif %}' +
279+
'{% endfor %}' +
280+
') AS dates';
281+
271282
return templates;
272283
}
273284
}

packages/cubejs-schema-compiler/src/adapter/CubeStoreQuery.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ export class CubeStoreQuery extends BaseQuery {
6868
return `date_trunc('${GRANULARITY_TO_INTERVAL[granularity]}', ${dimension})`;
6969
}
7070

71+
// Temporary workaround to avoid checking for multistage in CubeStoreQuery, since that could lead to errors when HLL functions are present in the query.
72+
public neverUseSqlPlannerPreaggregation() {
73+
return true;
74+
}
75+
7176
/**
7277
* Returns sql for source expression floored to timestamps aligned with
7378
* intervals relative to origin timestamp point.

packages/cubejs-testing-drivers/fixtures/bigquery.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"CUBESQL_SQL_PUSH_DOWN": "true",
1818

1919
"CUBEJS_DB_EXPORT_BUCKET": "cube-open-source-export-bucket",
20-
"CUBEJS_DB_EXPORT_BUCKET_TYPE": "gcp"
20+
"CUBEJS_DB_EXPORT_BUCKET_TYPE": "gcp",
21+
"_CUBEJS_TESSERACT_SQL_PLANNER": "true"
2122
},
2223
"ports" : ["4000", "5656"]
2324
},

packages/cubejs-testing-drivers/fixtures/postgres.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"CUBEJS_PG_SQL_PORT": "5656",
1313
"CUBEJS_SQL_USER": "admin",
1414
"CUBEJS_SQL_PASSWORD": "admin_password",
15-
"CUBESQL_SQL_PUSH_DOWN": "true"
15+
"CUBESQL_SQL_PUSH_DOWN": "true",
16+
"_CUBEJS_TESSERACT_SQL_PLANNER": "true"
1617
},
1718
"depends_on": ["data"],
1819
"links": ["data"],

rust/cubesqlplanner/cubesqlplanner/src/cube_bridge/base_query_options.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pub struct BaseQueryOptionsStatic {
6363
pub export_annotated_sql: bool,
6464
#[serde(rename = "preAggregationQuery")]
6565
pub pre_aggregation_query: Option<bool>,
66+
#[serde(rename = "totalQuery")]
67+
pub total_query: Option<bool>,
6668
}
6769

6870
#[nativebridge::native_bridge(BaseQueryOptionsStatic)]

rust/cubesqlplanner/cubesqlplanner/src/cube_bridge/base_tools.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ pub trait BaseTools {
3434
used_filters: Option<Vec<FilterItem>>,
3535
) -> Result<Rc<dyn FilterGroup>, CubeError>;
3636
fn timestamp_precision(&self) -> Result<u32, CubeError>;
37+
fn time_stamp_cast(&self, field: String) -> Result<String, CubeError>; //TODO move to templates
38+
fn date_time_cast(&self, field: String) -> Result<String, CubeError>; //TODO move to templates
3739
fn in_db_time_zone(&self, date: String) -> Result<String, CubeError>;
3840
fn generate_time_series(
3941
&self,
@@ -47,6 +49,8 @@ pub trait BaseTools {
4749
origin: String,
4850
) -> Result<Vec<Vec<String>>, CubeError>;
4951
fn get_allocated_params(&self) -> Result<Vec<String>, CubeError>;
52+
fn subtract_interval(&self, date: String, interval: String) -> Result<String, CubeError>;
53+
fn add_interval(&self, date: String, interval: String) -> Result<String, CubeError>;
5054
fn all_cube_members(&self, path: String) -> Result<Vec<String>, CubeError>;
5155
//===== TODO Move to templates
5256
fn hll_init(&self, sql: String) -> Result<String, CubeError>;

rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use itertools::Itertools;
1616
use std::collections::HashMap;
1717
use std::collections::HashSet;
1818
use std::rc::Rc;
19+
const TOTAL_COUNT: &'static str = "total_count";
20+
const ORIGINAL_QUERY: &'static str = "original_query";
1921

2022
#[derive(Clone, Debug)]
2123
struct PhysicalPlanBuilderContext {
@@ -56,7 +58,7 @@ pub struct PhysicalPlanBuilder {
5658

5759
impl PhysicalPlanBuilder {
5860
pub fn new(query_tools: Rc<QueryTools>) -> Self {
59-
let plan_sql_templates = PlanSqlTemplates::new(query_tools.templates_render());
61+
let plan_sql_templates = query_tools.plan_sql_templates();
6062
Self {
6163
query_tools,
6264
plan_sql_templates,
@@ -67,10 +69,25 @@ impl PhysicalPlanBuilder {
6769
&self,
6870
logical_plan: Rc<Query>,
6971
original_sql_pre_aggregations: HashMap<String, String>,
72+
total_query: bool,
7073
) -> Result<Rc<Select>, CubeError> {
7174
let mut context = PhysicalPlanBuilderContext::default();
7275
context.original_sql_pre_aggregations = original_sql_pre_aggregations;
73-
self.build_impl(logical_plan, &context)
76+
let query = self.build_impl(logical_plan, &context)?;
77+
let query = if total_query {
78+
self.build_total_count(query, &context)?
79+
} else {
80+
query
81+
};
82+
Ok(query)
83+
}
84+
85+
fn build_total_count(&self, source: Rc<Select>, context: &PhysicalPlanBuilderContext) -> Result<Rc<Select>, CubeError> {
86+
let from = From::new_from_subselect(source.clone(), ORIGINAL_QUERY.to_string());
87+
let mut select_builder = SelectBuilder::new(from);
88+
select_builder.add_count_all(TOTAL_COUNT.to_string());
89+
let context_factory = context.make_sql_nodes_factory();
90+
Ok(Rc::new(select_builder.build(context_factory)))
7491
}
7592

7693
fn build_impl(
@@ -957,7 +974,7 @@ impl PhysicalPlanBuilder {
957974
));
958975
};
959976

960-
let templates = PlanSqlTemplates::new(self.query_tools.templates_render());
977+
let templates = self.query_tools.plan_sql_templates();
961978

962979
let ts_date_range = if templates.supports_generated_time_series() {
963980
if let Some(date_range) = time_dimension_symbol

rust/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ impl SelectBuilder {
7575
.add_column(SchemaColumn::new(alias.clone(), Some(member.full_name())));
7676
}
7777

78+
pub fn add_count_all(&mut self, alias: String) {
79+
let func = Expr::Function(FunctionExpression {
80+
function: "COUNT".to_string(),
81+
arguments: vec![Expr::Asterisk],
82+
});
83+
let aliased_expr = AliasedExpr {
84+
expr: func,
85+
alias: alias.clone(),
86+
};
87+
self.projection_columns.push(aliased_expr);
88+
self.result_schema.add_column(SchemaColumn::new(alias.clone(), None));
89+
}
7890
pub fn add_projection_function_expression(
7991
&mut self,
8092
function: &str,

rust/cubesqlplanner/cubesqlplanner/src/plan/expression.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub enum Expr {
3434
Member(MemberExpression),
3535
Reference(QualifiedColumnName),
3636
Function(FunctionExpression),
37+
Asterisk,
3738
}
3839

3940
impl Expr {
@@ -65,6 +66,7 @@ impl Expr {
6566
None,
6667
None,
6768
),
69+
Self::Asterisk => Ok("*".to_string()),
6870
}
6971
}
7072
}

0 commit comments

Comments
 (0)