Skip to content

Commit a2e604e

Browse files
authored
feat(cubesqlplanner): Base joins support (#8656)
1 parent 9ac3a1f commit a2e604e

File tree

103 files changed

+5741
-351
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+5741
-351
lines changed

packages/cubejs-backend-native/Cargo.lock

Lines changed: 32 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,11 +609,18 @@ export class BaseQuery {
609609
const queryParams = {
610610
measures: this.options.measures,
611611
dimensions: this.options.dimensions,
612+
timeDimensions: this.options.timeDimensions,
613+
timezone: this.options.timezone,
612614
joinRoot: this.join.root,
615+
joinGraph: this.joinGraph,
613616
cubeEvaluator: this.cubeEvaluator,
617+
filters: this.options.filters,
618+
baseTools: this,
614619

615620
};
616621
const res = nativeBuildSqlAndParams(queryParams);
622+
// FIXME
623+
res[1] = [...res[1]];
617624
return res;
618625
}
619626

@@ -3233,6 +3240,21 @@ export class BaseQuery {
32333240
ilike: '{{ expr }} {% if negated %}NOT {% endif %}ILIKE {{ pattern }}',
32343241
like_escape: '{{ like_expr }} ESCAPE {{ escape_char }}',
32353242
},
3243+
filters: {
3244+
equals: '{{ column }} = {{ value }}{{ is_null_check }}',
3245+
not_equals: '{{ column }} <> {{ value }}{{ is_null_check }}',
3246+
or_is_null_check: ' OR {{ column }} IS NULL',
3247+
set_where: '{{ column }} IS NOT NULL',
3248+
not_set_where: '{{ column }} IS NULL',
3249+
in: '{{ column }} IN ({{ values_concat }}){{ is_null_check }}',
3250+
not_in: '{{ column }} NOT IN ({{ values_concat }}){{ is_null_check }}',
3251+
time_range_filter: '{{ column }} >= {{ from_timestamp }} AND {{ column }} <= {{ to_timestamp }}',
3252+
gt: '{{ column }} > {{ param }}',
3253+
gte: '{{ column }} >= {{ param }}',
3254+
lt: '{{ column }} < {{ param }}',
3255+
lte: '{{ column }} <= {{ param }}'
3256+
3257+
},
32363258
quotes: {
32373259
identifiers: '"',
32383260
escape: '""'
@@ -3661,6 +3683,10 @@ export class BaseQuery {
36613683
};
36623684
}
36633685

3686+
securityContextForRust() {
3687+
return this.contextSymbolsProxy(this.contextSymbols.securityContext);
3688+
}
3689+
36643690
contextSymbolsProxy(symbols) {
36653691
return BaseQuery.contextSymbolsProxyFrom(symbols, this.paramAllocator.allocateParam.bind(this.paramAllocator));
36663692
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,9 +462,10 @@ export class CubeEvaluator extends CubeSymbols {
462462

463463
public isInstanceOfType(type: 'measures' | 'dimensions' | 'segments', path: string | string[]): boolean {
464464
const cubeAndName = Array.isArray(path) ? path : path.split('.');
465-
return this.evaluatedCubes[cubeAndName[0]] &&
465+
const symbol = this.evaluatedCubes[cubeAndName[0]] &&
466466
this.evaluatedCubes[cubeAndName[0]][type] &&
467467
this.evaluatedCubes[cubeAndName[0]][type][cubeAndName[1]];
468+
return symbol !== undefined;
468469
}
469470

470471
public byPathAnyType(path: string[]) {

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

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,81 @@ export class CubeSymbols {
493493
return joinHints;
494494
}
495495

496+
resolveSymbolsCallDeps(cubeName, sql) {
497+
try {
498+
return this.resolveSymbolsCallDeps2(cubeName, sql);
499+
} catch (e) {
500+
console.log(e);
501+
return [];
502+
}
503+
}
504+
505+
resolveSymbolsCallDeps2(cubeName, sql) {
506+
const deps = [];
507+
this.resolveSymbolsCall(sql, (name) => {
508+
deps.push({ name, undefined });
509+
const resolvedSymbol = this.resolveSymbol(
510+
cubeName,
511+
name
512+
);
513+
if (resolvedSymbol._objectWithResolvedProperties) {
514+
return resolvedSymbol;
515+
}
516+
return '';
517+
}, {
518+
depsResolveFn: (name, parent) => {
519+
deps.push({ name, parent });
520+
return deps.length - 1;
521+
},
522+
currResolveIndexFn: () => deps.length - 1,
523+
contextSymbols: this.depsContextSymbols(),
524+
525+
});
526+
return deps;
527+
}
528+
529+
depsContextSymbols() {
530+
return Object.assign({
531+
filterParams: this.filtersProxyDep(),
532+
filterGroup: this.filterGroupFunctionDep(),
533+
securityContext: BaseQuery.contextSymbolsProxyFrom({}, (param) => param)
534+
});
535+
}
536+
537+
filtersProxyDep() {
538+
return new Proxy({}, {
539+
get: (target, name) => {
540+
if (name === '_objectWithResolvedProperties') {
541+
return true;
542+
}
543+
// allFilters is null whenever it's used to test if the member is owned by cube so it should always render to `1 = 1`
544+
// and do not check cube validity as it's part of compilation step.
545+
const cubeName = this.cubeNameFromPath(name);
546+
return new Proxy({ cube: cubeName }, {
547+
get: (cubeNameObj, propertyName) => ({
548+
filter: (column) => ({
549+
__column() {
550+
return column;
551+
},
552+
__member() {
553+
return this.pathFromArray([cubeNameObj.cube, propertyName]);
554+
},
555+
toString() {
556+
return '';
557+
}
558+
})
559+
})
560+
});
561+
}
562+
});
563+
}
564+
565+
filterGroupFunctionDep() {
566+
return (...filterParamArgs) => '';
567+
}
568+
496569
resolveSymbol(cubeName, name) {
497-
const { sqlResolveFn, contextSymbols, collectJoinHints } = this.resolveSymbolsCallContext || {};
570+
const { sqlResolveFn, contextSymbols, collectJoinHints, depsResolveFn, currResolveIndexFn } = this.resolveSymbolsCallContext || {};
498571

499572
if (name === 'USER_CONTEXT') {
500573
throw new Error('Support for USER_CONTEXT was removed, please migrate to SECURITY_CONTEXT.');
@@ -528,8 +601,14 @@ export class CubeSymbols {
528601
name
529602
);
530603
}
604+
} else if (depsResolveFn) {
605+
if (cube) {
606+
const newCubeName = this.isCurrentCube(name) ? cubeName : name;
607+
const parentIndex = currResolveIndexFn();
608+
cube = this.cubeDependenciesProxy(parentIndex, newCubeName);
609+
return cube;
610+
}
531611
}
532-
533612
return cube || (this.symbols[cubeName] && this.symbols[cubeName][name]);
534613
}
535614

@@ -623,6 +702,44 @@ export class CubeSymbols {
623702
return cube && cube[dimName] && cube[dimName][gr] && cube[dimName][gr][granName];
624703
}
625704

705+
cubeDependenciesProxy(parentIndex, cubeName) {
706+
const self = this;
707+
const { depsResolveFn } = self.resolveSymbolsCallContext || {};
708+
return new Proxy({}, {
709+
get: (v, propertyName) => {
710+
if (propertyName === '__cubeName') {
711+
depsResolveFn('__cubeName', parentIndex);
712+
return cubeName;
713+
}
714+
const cube = self.symbols[cubeName];
715+
716+
if (propertyName === 'toString') {
717+
depsResolveFn('toString', parentIndex);
718+
return () => '';
719+
}
720+
if (propertyName === 'sql') {
721+
depsResolveFn('sql', parentIndex);
722+
return () => '';
723+
}
724+
if (propertyName === '_objectWithResolvedProperties') {
725+
return true;
726+
}
727+
if (cube[propertyName]) {
728+
depsResolveFn(propertyName, parentIndex);
729+
return '';
730+
}
731+
if (self.symbols[propertyName]) {
732+
const index = depsResolveFn(propertyName, parentIndex);
733+
return this.cubeDependenciesProxy(index, propertyName);
734+
}
735+
if (typeof propertyName === 'string') {
736+
throw new UserError(`${cubeName}.${propertyName} cannot be resolved. There's no such member or cube.`);
737+
}
738+
return undefined;
739+
}
740+
});
741+
}
742+
626743
isCurrentCube(name) {
627744
return CURRENT_CUBE_CONSTANTS.indexOf(name) >= 0;
628745
}

packages/cubejs-schema-compiler/test/integration/mssql/MSSqlDbRunner.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class MSSqlDbRunner extends BaseDbRunner {
8484
(2, 1, 2),
8585
(3, 3, 6)
8686
`);
87-
await query(`CREATE TABLE ##numbers (num INT);`);
87+
await query('CREATE TABLE ##numbers (num INT);');
8888
await query(`
8989
INSERT INTO ##numbers (num) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9),
9090
(10), (11), (12), (13), (14), (15), (16), (17), (18), (19),

packages/cubejs-schema-compiler/test/integration/mysql/MySqlDbRunner.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class MySqlDbRunner extends BaseDbRunner {
7373
(2, 1, 2),
7474
(3, 3, 6)
7575
`);
76-
await query(`CREATE TEMPORARY TABLE numbers (num INT);`);
76+
await query('CREATE TEMPORARY TABLE numbers (num INT);');
7777
await query(`
7878
INSERT INTO numbers (num) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9),
7979
(10), (11), (12), (13), (14), (15), (16), (17), (18), (19),

packages/cubejs-schema-compiler/test/integration/postgres/sql-generation-logic.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,6 @@ describe('SQL Generation', () => {
401401
timezone: 'America/Los_Angeles'
402402
});
403403

404-
console.log(query.buildSqlAndParams());
405-
406404
return dbRunner.testQuery(query.buildSqlAndParams()).then(res => {
407405
console.log(JSON.stringify(res));
408406
expect(res).toEqual(

packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -561,9 +561,6 @@ describe('SQL Generation', () => {
561561
}]
562562
});
563563

564-
const queryAndParams = query.buildSqlAndParams();
565-
console.log(queryAndParams);
566-
567564
return dbRunner.testQuery(query.buildSqlAndParams()).then(res => {
568565
expect(res).toEqual(
569566
[

packages/cubejs-schema-compiler/test/integration/utils/BaseDbRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import R from 'ramda';
22
import { PreAggregationPartitionRangeLoader } from '@cubejs-backend/query-orchestrator';
3-
import { BaseQuery } from "../../../src";
3+
import { BaseQuery } from '../../../src';
44

55
export class BaseDbRunner {
66
protected containerLazyInitPromise: any = null;

0 commit comments

Comments
 (0)