Skip to content

Commit e0c5a44

Browse files
committed
feat(tesseract): Custom granularities support
1 parent 2c1b4ab commit e0c5a44

File tree

25 files changed

+1295
-102
lines changed

25 files changed

+1295
-102
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {
2020
QueryAlias,
2121
getEnv,
2222
localTimestampToUtc,
23-
timeSeries as timeSeriesBase
23+
timeSeries as timeSeriesBase,
24+
timeSeriesFromCustomInterval
2425
} from '@cubejs-backend/shared';
2526

2627
import { UserError } from '../compiler/UserError';
@@ -725,6 +726,11 @@ export class BaseQuery {
725726
return timeSeriesBase(granularity, dateRange);
726727
}
727728

729+
// FIXME helper for native generator, maybe should be moved entire to rust
730+
generateCustomTimeSeries(granularityInterval, dateRange, origin) {
731+
return timeSeriesFromCustomInterval(granularityInterval, dateRange, moment(origin), { timestampPrecision: 3 });
732+
}
733+
728734
get shouldReuseParams() {
729735
return false;
730736
}

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -640,37 +640,32 @@ export class CubeSymbols {
640640

641641
protected resolveSymbolsCallDeps(cubeName, sql) {
642642
try {
643-
return this.resolveSymbolsCallDeps2(cubeName, sql);
643+
const deps: any[] = [];
644+
this.resolveSymbolsCall(sql, (name) => {
645+
deps.push({ name, undefined });
646+
const resolvedSymbol = this.resolveSymbol(
647+
cubeName,
648+
name
649+
);
650+
if (resolvedSymbol._objectWithResolvedProperties) {
651+
return resolvedSymbol;
652+
}
653+
return '';
654+
}, {
655+
depsResolveFn: (name, parent) => {
656+
deps.push({ name, parent });
657+
return deps.length - 1;
658+
},
659+
currResolveIndexFn: () => deps.length - 1,
660+
contextSymbols: this.depsContextSymbols(),
661+
662+
});
663+
return deps;
644664
} catch (e) {
645-
console.log(e);
646665
return [];
647666
}
648667
}
649668

650-
protected resolveSymbolsCallDeps2(cubeName, sql) {
651-
const deps: any[] = [];
652-
this.resolveSymbolsCall(sql, (name) => {
653-
deps.push({ name, undefined });
654-
const resolvedSymbol = this.resolveSymbol(
655-
cubeName,
656-
name
657-
);
658-
if (resolvedSymbol._objectWithResolvedProperties) {
659-
return resolvedSymbol;
660-
}
661-
return '';
662-
}, {
663-
depsResolveFn: (name, parent) => {
664-
deps.push({ name, parent });
665-
return deps.length - 1;
666-
},
667-
currResolveIndexFn: () => deps.length - 1,
668-
contextSymbols: this.depsContextSymbols(),
669-
670-
});
671-
return deps;
672-
}
673-
674669
protected depsContextSymbols() {
675670
return Object.assign({
676671
filterParams: this.filtersProxyDep(),
@@ -719,7 +714,6 @@ export class CubeSymbols {
719714

720715
public resolveSymbol(cubeName, name) {
721716
const { sqlResolveFn, contextSymbols, collectJoinHints, depsResolveFn, currResolveIndexFn } = this.resolveSymbolsCallContext || {};
722-
723717
if (name === 'USER_CONTEXT') {
724718
throw new Error('Support for USER_CONTEXT was removed, please migrate to SECURITY_CONTEXT.');
725719
}
@@ -758,6 +752,9 @@ export class CubeSymbols {
758752
const parentIndex = currResolveIndexFn();
759753
cube = this.cubeDependenciesProxy(parentIndex, newCubeName);
760754
return cube;
755+
} else if (this.symbols[cubeName] && this.symbols[cubeName][name] && this.symbols[cubeName][name].type === 'time') {
756+
const parentIndex = currResolveIndexFn();
757+
return this.timeDimDependenciesProxy(parentIndex);
761758
}
762759
}
763760
return cube || (this.symbols[cubeName] && this.symbols[cubeName][name]);
@@ -877,6 +874,10 @@ export class CubeSymbols {
877874
}
878875
if (cube[propertyName]) {
879876
depsResolveFn(propertyName, parentIndex);
877+
if (cube[propertyName].type === 'time') {
878+
return this.timeDimDependenciesProxy(parentIndex);
879+
}
880+
880881
return '';
881882
}
882883
if (self.symbols[propertyName]) {
@@ -891,6 +892,25 @@ export class CubeSymbols {
891892
});
892893
}
893894

895+
protected timeDimDependenciesProxy(parentIndex) {
896+
const self = this;
897+
const { depsResolveFn } = self.resolveSymbolsCallContext || {};
898+
return new Proxy({}, {
899+
get: (v, propertyName) => {
900+
if (propertyName === '_objectWithResolvedProperties') {
901+
return true;
902+
}
903+
if (propertyName === 'toString') {
904+
return () => '';
905+
}
906+
if (typeof propertyName === 'string') {
907+
depsResolveFn(propertyName, parentIndex);
908+
}
909+
return undefined;
910+
}
911+
});
912+
}
913+
894914
public isCurrentCube(name) {
895915
return CURRENT_CUBE_CONSTANTS.indexOf(name) >= 0;
896916
}

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

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use super::base_query_options::FilterItem;
22
use super::filter_group::{FilterGroup, NativeFilterGroup};
33
use super::filter_params::{FilterParams, NativeFilterParams};
4-
use super::member_sql::{MemberSql, NativeMemberSql};
54
use super::security_context::{NativeSecurityContext, SecurityContext};
65
use super::sql_templates_render::{NativeSqlTemplatesRender, SqlTemplatesRender};
76
use super::sql_utils::{NativeSqlUtils, SqlUtils};
@@ -11,16 +10,9 @@ use cubenativeutils::wrappers::serializer::{
1110
use cubenativeutils::wrappers::NativeContextHolder;
1211
use cubenativeutils::wrappers::NativeObjectHandle;
1312
use cubenativeutils::CubeError;
14-
use serde::Deserialize;
1513
use std::any::Any;
1614
use std::rc::Rc;
1715

18-
#[derive(Deserialize, Debug)]
19-
pub struct CallDep {
20-
pub name: String,
21-
pub parent: Option<usize>,
22-
}
23-
2416
#[nativebridge::native_bridge]
2517
pub trait BaseTools {
2618
fn convert_tz(&self, field: String) -> Result<String, CubeError>;
@@ -30,11 +22,6 @@ pub trait BaseTools {
3022
dimension: String,
3123
) -> Result<String, CubeError>;
3224
fn sql_templates(&self) -> Result<Rc<dyn SqlTemplatesRender>, CubeError>;
33-
fn resolve_symbols_call_deps(
34-
&self,
35-
cube_name: String,
36-
sql: Rc<dyn MemberSql>,
37-
) -> Result<Vec<CallDep>, CubeError>;
3825
fn security_context_for_rust(&self) -> Result<Rc<dyn SecurityContext>, CubeError>;
3926
fn sql_utils_for_rust(&self) -> Result<Rc<dyn SqlUtils>, CubeError>;
4027
fn filters_proxy_for_rust(
@@ -52,11 +39,23 @@ pub trait BaseTools {
5239
granularity: String,
5340
date_range: Vec<String>,
5441
) -> Result<Vec<Vec<String>>, CubeError>;
42+
fn generate_custom_time_series(
43+
&self,
44+
granularity: String,
45+
date_range: Vec<String>,
46+
origin: String,
47+
) -> Result<Vec<Vec<String>>, CubeError>;
5548
fn get_allocated_params(&self) -> Result<Vec<String>, CubeError>;
5649
fn all_cube_members(&self, path: String) -> Result<Vec<String>, CubeError>;
5750
//===== TODO Move to templates
5851
fn hll_init(&self, sql: String) -> Result<String, CubeError>;
5952
fn hll_merge(&self, sql: String) -> Result<String, CubeError>;
6053
fn hll_cardinality_merge(&self, sql: String) -> Result<String, CubeError>;
6154
fn count_distinct_approx(&self, sql: String) -> Result<String, CubeError>;
55+
fn date_bin(
56+
&self,
57+
interval: String,
58+
source: String,
59+
origin: String,
60+
) -> Result<String, CubeError>;
6261
}

rust/cubesqlplanner/cubesqlplanner/src/cube_bridge/dimension_definition.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ use serde::{Deserialize, Serialize};
1111
use std::any::Any;
1212
use std::rc::Rc;
1313

14+
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash)]
15+
pub struct GranularityDefinition {
16+
pub interval: String,
17+
pub origin: Option<String>,
18+
pub offset: Option<String>,
19+
}
1420
#[derive(Serialize, Deserialize, Debug)]
1521
pub struct DimenstionDefinitionStatic {
1622
#[serde(rename = "type")]

rust/cubesqlplanner/cubesqlplanner/src/cube_bridge/evaluator.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use super::cube_definition::{CubeDefinition, NativeCubeDefinition};
2-
use super::dimension_definition::{DimensionDefinition, NativeDimensionDefinition};
2+
use super::dimension_definition::{
3+
DimensionDefinition, GranularityDefinition, NativeDimensionDefinition,
4+
};
35
use super::measure_definition::{MeasureDefinition, NativeMeasureDefinition};
46
use super::member_sql::{MemberSql, NativeMemberSql};
57
use super::segment_definition::{NativeSegmentDefinition, SegmentDefinition};
@@ -48,4 +50,5 @@ pub trait CubeEvaluator {
4850
cube_name: String,
4951
sql: Rc<dyn MemberSql>,
5052
) -> Result<Vec<CallDep>, CubeError>;
53+
fn resolve_granularity(&self, path: Vec<String>) -> Result<GranularityDefinition, CubeError>;
5154
}

rust/cubesqlplanner/cubesqlplanner/src/plan/time_series.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::{Schema, SchemaColumn};
22
use crate::planner::{
33
query_tools::QueryTools,
44
sql_templates::{PlanSqlTemplates, TemplateProjectionColumn},
5+
Granularity,
56
};
67
use cubenativeutils::CubeError;
78
use std::rc::Rc;
@@ -11,7 +12,7 @@ pub struct TimeSeries {
1112
#[allow(dead_code)]
1213
time_dimension_name: String,
1314
date_range: TimeSeriesDateRange,
14-
granularity: String,
15+
granularity: Granularity,
1516
schema: Rc<Schema>,
1617
}
1718

@@ -25,7 +26,7 @@ impl TimeSeries {
2526
query_tools: Rc<QueryTools>,
2627
time_dimension_name: String,
2728
date_range: TimeSeriesDateRange,
28-
granularity: String,
29+
granularity: Granularity,
2930
) -> Self {
3031
let column = SchemaColumn::new(format!("date_from"), Some(time_dimension_name.clone()));
3132
let schema = Rc::new(Schema::new(vec![column]));
@@ -88,7 +89,11 @@ impl TimeSeries {
8889
(format!("({})", from), format!("({})", to))
8990
}
9091
};
91-
templates.generated_time_series_select(&from_date, &to_date, &self.granularity)
92+
templates.generated_time_series_select(
93+
&from_date,
94+
&to_date,
95+
&self.granularity.granularity_interval(),
96+
)
9297
} else {
9398
let (from_date, to_date) = match &self.date_range {
9499
TimeSeriesDateRange::Filter(from_date, to_date) => {
@@ -100,10 +105,18 @@ impl TimeSeries {
100105
));
101106
}
102107
};
103-
let series = self.query_tools.base_tools().generate_time_series(
104-
self.granularity.clone(),
105-
vec![from_date.clone(), to_date.clone()],
106-
)?;
108+
let series = if self.granularity.is_predefined_granularity() {
109+
self.query_tools.base_tools().generate_time_series(
110+
self.granularity.granularity().clone(),
111+
vec![from_date.clone(), to_date.clone()],
112+
)?
113+
} else {
114+
self.query_tools.base_tools().generate_custom_time_series(
115+
self.granularity.granularity_interval().clone(),
116+
vec![from_date.clone(), to_date.clone()],
117+
self.granularity.origin_local_formatted(),
118+
)?
119+
};
107120
templates.time_series_select(from_date.clone(), to_date.clone(), series)
108121
}
109122
}

rust/cubesqlplanner/cubesqlplanner/src/planner/base_dimension.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ impl BaseDimension {
132132
self.member_evaluator.clone()
133133
}
134134

135+
pub fn definition(&self) -> Option<Rc<dyn DimensionDefinition>> {
136+
self.definition.clone()
137+
}
138+
135139
pub fn sql_call(&self) -> Result<Rc<SqlCall>, CubeError> {
136140
match self.member_evaluator.as_ref() {
137141
MemberSymbol::Dimension(d) => {

0 commit comments

Comments
 (0)