Skip to content

Commit 5a858b3

Browse files
committed
predecessors comparator
1 parent cde9cb7 commit 5a858b3

File tree

1 file changed

+82
-13
lines changed

1 file changed

+82
-13
lines changed

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

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ export class BaseQuery {
437437
get allJoinHints() {
438438
if (!this.collectedJoinHints) {
439439
const [rootOfJoin, ...allMembersJoinHints] = this.collectJoinHintsFromMembers(this.allMembersConcat(false));
440+
const allMembersHintsFlattened = [rootOfJoin, ...allMembersJoinHints].flat();
441+
const originalQueryMembersJoinPredecessors = this.buildPredecessors(allMembersHintsFlattened);
440442
const customSubQueryJoinHints = this.collectJoinHintsFromMembers(this.joinMembersFromCustomSubQuery());
441443
let joinMembersJoinHints = this.collectJoinHintsFromMembers(this.joinMembersFromJoin(this.join));
442444

@@ -448,7 +450,7 @@ export class BaseQuery {
448450
// It is important to use queryLevelJoinHints during the calculation if it is set.
449451

450452
const constructJH = () => {
451-
const filteredJoinMembersJoinHints = joinMembersJoinHints.filter(m => !allMembersJoinHints.includes(m));
453+
const filteredJoinMembersJoinHints = joinMembersJoinHints.filter(m => !allMembersJoinHints.includes(m) && m !== allMembersHintsFlattened[0]);
452454
return [
453455
...this.queryLevelJoinHints,
454456
...(rootOfJoin ? [rootOfJoin] : []),
@@ -458,15 +460,36 @@ export class BaseQuery {
458460
];
459461
};
460462

461-
let prevJoins = this.join;
462-
let prevJoinMembersJoinHints = joinMembersJoinHints;
463+
let prevJoin = this.join;
463464
let newJoin = this.joinGraph.buildJoin(constructJH());
464465

465-
const isOrderPreserved = (base, updated) => {
466-
const common = base.filter(value => updated.includes(value));
467-
const bFiltered = updated.filter(value => common.includes(value));
466+
const isOrderPreserved = (updatedJoinHints) => {
467+
for (let i = 0, l = updatedJoinHints.length; i < l; i++) {
468+
const predecessors = originalQueryMembersJoinPredecessors[updatedJoinHints[i]];
468469

469-
return common.every((x, i) => x === bFiltered[i]);
470+
if (predecessors?.length > 0) {
471+
const predLen = predecessors.length;
472+
473+
let predIdx = 0;
474+
let joinHintIdx = 0;
475+
476+
while (joinHintIdx < i && predIdx < predLen) {
477+
if (updatedJoinHints[joinHintIdx] === predecessors[predIdx]) {
478+
joinHintIdx++;
479+
predIdx++;
480+
} else {
481+
joinHintIdx++;
482+
}
483+
}
484+
485+
if (predIdx < predLen) {
486+
// We still have a must be present predecessor for current hint
487+
return [false, `${updatedJoinHints[i]} <-> ${predecessors[predIdx]}`];
488+
}
489+
}
490+
}
491+
492+
return [true, ''];
470493
};
471494

472495
const isJoinTreesEqual = (a, b) => {
@@ -495,16 +518,17 @@ export class BaseQuery {
495518
// Safeguard against infinite loop in case of cyclic joins somehow managed to slip through
496519
let cnt = 0;
497520

498-
while (newJoin?.joins.length > 0 && !isJoinTreesEqual(prevJoins, newJoin) && cnt < 10000) {
499-
prevJoins = newJoin;
521+
while (newJoin?.joins.length > 0 && !isJoinTreesEqual(prevJoin, newJoin) && cnt < 10000) {
522+
prevJoin = newJoin;
500523
joinMembersJoinHints = this.collectJoinHintsFromMembers(this.joinMembersFromJoin(newJoin));
524+
newJoin = this.joinGraph.buildJoin(constructJH());
501525

502-
if (!isOrderPreserved(prevJoinMembersJoinHints, joinMembersJoinHints)) {
503-
throw new UserError(`Can not construct joins for the query, potential loop detected: ${prevJoinMembersJoinHints.join('->')} vs ${joinMembersJoinHints.join('->')}`);
526+
const [isOrdered, msg] = isOrderPreserved([allMembersHintsFlattened[0], ...joinMembersJoinHints]);
527+
528+
if (!isOrdered) {
529+
throw new UserError(`Can not construct joins for the query, potential loop detected around ${msg}`);
504530
}
505531

506-
newJoin = this.joinGraph.buildJoin(constructJH());
507-
prevJoinMembersJoinHints = joinMembersJoinHints;
508532
cnt++;
509533
}
510534

@@ -540,6 +564,51 @@ export class BaseQuery {
540564
);
541565
}
542566

567+
/**
568+
* @private
569+
* @param {Array<string>} arr
570+
* @returns {{}|any}
571+
*/
572+
buildPredecessors(arr) {
573+
if (!arr || arr.length === 0) return {};
574+
575+
const root = arr[0];
576+
577+
// the first position of each unique element
578+
const firstPos = new Map();
579+
for (let i = 0; i < arr.length; i++) {
580+
if (!firstPos.has(arr[i])) firstPos.set(arr[i], i);
581+
}
582+
583+
const result = {};
584+
585+
for (const [elem, idx] of firstPos.entries()) {
586+
if (elem === root) {
587+
result[elem] = [];
588+
} else {
589+
// finding the nearest root on the left <<
590+
const seen = new Set();
591+
const path = [];
592+
593+
for (let j = idx - 1; j >= 0; j--) {
594+
const v = arr[j];
595+
if (!seen.has(v)) {
596+
seen.add(v);
597+
path.push(v);
598+
}
599+
if (v === root) {
600+
break;
601+
}
602+
}
603+
604+
path.reverse();
605+
result[elem] = path;
606+
}
607+
}
608+
609+
return result;
610+
}
611+
543612
initUngrouped() {
544613
this.ungrouped = this.options.ungrouped;
545614
if (this.ungrouped) {

0 commit comments

Comments
 (0)