Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/cubejs-api-gateway/openspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ components:
- "on"
- joinType
- alias
V1LoadRequestJoinHint:
type: "array"
items:
type: "string"
V1LoadRequestQuery:
type: "object"
properties:
Expand Down Expand Up @@ -412,6 +416,10 @@ components:
type: "array"
items:
$ref: "#/components/schemas/V1LoadRequestQueryJoinSubquery"
joinHints:
type: "array"
items:
$ref: "#/components/schemas/V1LoadRequestJoinHint"
V1LoadRequest:
type: "object"
properties:
Expand Down
3 changes: 3 additions & 0 deletions packages/cubejs-api-gateway/src/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ const subqueryJoin = Joi.object().keys({
alias: Joi.string(),
});

const joinHint = Joi.array().items(Joi.string());

const querySchema = Joi.object().keys({
// TODO add member expression alternatives only for SQL API queries?
measures: Joi.array().items(Joi.alternatives(id, memberExpression, parsedMemberExpression)),
Expand Down Expand Up @@ -189,6 +191,7 @@ const querySchema = Joi.object().keys({
ungrouped: Joi.boolean(),
responseFormat: Joi.valid('default', 'compact'),
subqueryJoins: Joi.array().items(subqueryJoin),
joinHints: Joi.array().items(joinHint),
});

const normalizeQueryOrder = order => {
Expand Down
4 changes: 4 additions & 0 deletions packages/cubejs-api-gateway/src/types/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ type SubqueryJoins = {
alias: string,
};

type JoinHint = Array<string>;

/**
* Incoming network query data type.
*/
Expand All @@ -143,6 +145,8 @@ interface Query {

// TODO incoming query, query with parsed exprs and query with evaluated exprs are all different types
subqueryJoins?: Array<SubqueryJoins>,

joinHints?: Array<JoinHint>
}

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ export class BaseQuery {
multiStageDimensions: this.options.multiStageDimensions,
multiStageTimeDimensions: this.options.multiStageTimeDimensions,
subqueryJoins: this.options.subqueryJoins,
joinHints: this.options.joinHints,
});
this.from = this.options.from;
this.multiStageQuery = this.options.multiStageQuery;
Expand Down Expand Up @@ -329,6 +330,7 @@ export class BaseQuery {
const hasMultiStageMeasures = this.fullKeyQueryAggregateMeasures({ hasMultipliedForPreAggregation: true }).multiStageMembers.length > 0;
this.canUseNativeSqlPlannerPreAggregation = hasMultiStageMeasures;
}
this.queryLevelJoinHints = this.options.joinHints ?? [];
this.prebuildJoin();

this.cubeAliasPrefix = this.options.cubeAliasPrefix;
Expand Down Expand Up @@ -410,7 +412,10 @@ export class BaseQuery {
*/
get allJoinHints() {
if (!this.collectedJoinHints) {
this.collectedJoinHints = this.collectJoinHints();
this.collectedJoinHints = [
...this.queryLevelJoinHints,
...this.collectJoinHints(),
];
}
return this.collectedJoinHints;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,4 +641,32 @@ describe('Multiple join paths', () => {
});
}
});

describe('Query level join hints', () => {
it('should respect query level join hints', async () => {
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
measures: [],
dimensions: [
'A.a_id',
'X.x_name_ref',
],
joinHints: [
['A', 'D'],
['D', 'E'],
['E', 'X'],
],
});

const [sql, _params] = query.buildSqlAndParams();

expect(sql).toMatch(/ON 'A' = 'D'/);
expect(sql).toMatch(/ON 'D' = 'E'/);
expect(sql).toMatch(/ON 'E' = 'X'/);
expect(sql).not.toMatch(/ON 'A' = 'B'/);
expect(sql).not.toMatch(/ON 'B' = 'C'/);
expect(sql).not.toMatch(/ON 'C' = 'X'/);
expect(sql).not.toMatch(/ON 'A' = 'F'/);
expect(sql).not.toMatch(/ON 'F' = 'X'/);
});
});
});
3 changes: 3 additions & 0 deletions rust/cubesql/cubeclient/src/models/v1_load_request_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct V1LoadRequestQuery {
pub ungrouped: Option<bool>,
#[serde(rename = "subqueryJoins", skip_serializing_if = "Option::is_none")]
pub subquery_joins: Option<Vec<crate::models::V1LoadRequestQueryJoinSubquery>>,
#[serde(rename = "joinHints", skip_serializing_if = "Option::is_none")]
pub join_hints: Option<Vec<Vec<String>>>,
}

impl V1LoadRequestQuery {
Expand All @@ -45,6 +47,7 @@ impl V1LoadRequestQuery {
filters: None,
ungrouped: None,
subquery_joins: None,
join_hints: None,
}
}
}
1 change: 1 addition & 0 deletions rust/cubesql/cubesql/src/compile/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ impl QueryBuilder {
},
ungrouped: None,
subquery_joins: None,
join_hints: None,
},
meta: self.meta,
}
Expand Down
2 changes: 2 additions & 0 deletions rust/cubesql/cubesql/src/compile/engine/df/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3368,6 +3368,8 @@ impl WrappedSelectNode {
time_dimensions: load_request.time_dimensions.clone(),
subquery_joins: (!prepared_join_subqueries.is_empty())
.then_some(prepared_join_subqueries),

join_hints: load_request.join_hints.clone(),
};

// TODO time dimensions, filters, segments
Expand Down
29 changes: 29 additions & 0 deletions rust/cubesql/cubesql/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4665,6 +4665,13 @@ ORDER BY "ca_4" ASC
segments: Some(vec![]),
dimensions: Some(vec!["Logs.read".to_string()]),
order: Some(vec![]),
join_hints: Some(vec![
vec!["KibanaSampleDataEcommerce".to_string(), "Logs".to_string(),],
vec![
"KibanaSampleDataEcommerce".to_string(),
"NumberCube".to_string(),
],
]),
..Default::default()
}
);
Expand Down Expand Up @@ -4726,6 +4733,10 @@ ORDER BY
date_range: None,
}]),
order: Some(vec![]),
join_hints: Some(vec![vec![
"KibanaSampleDataEcommerce".to_string(),
"Logs".to_string(),
],]),
..Default::default()
}
);
Expand Down Expand Up @@ -8218,6 +8229,10 @@ ORDER BY "source"."str0" ASC
segments: Some(vec![]),
order: Some(vec![]),
ungrouped: Some(true),
join_hints: Some(vec![vec![
"KibanaSampleDataEcommerce".to_string(),
"Logs".to_string(),
],]),
..Default::default()
}
)
Expand Down Expand Up @@ -9794,6 +9809,10 @@ ORDER BY "source"."str0" ASC
segments: Some(vec![]),
order: Some(vec![]),
ungrouped: Some(true),
join_hints: Some(vec![vec![
"Logs".to_string(),
"KibanaSampleDataEcommerce".to_string(),
],]),
..Default::default()
},
);
Expand Down Expand Up @@ -11845,6 +11864,12 @@ ORDER BY "source"."str0" ASC
}).to_string(),
]),
order: Some(vec![]),
join_hints: Some(vec![
vec![
"KibanaSampleDataEcommerce".to_string(),
"Logs".to_string(),
],
]),
..Default::default()
}
);
Expand Down Expand Up @@ -12271,6 +12296,10 @@ ORDER BY "source"."str0" ASC
]),
segments: Some(vec![]),
order: Some(vec![]),
join_hints: Some(vec![vec![
"KibanaSampleDataEcommerce".to_string(),
"Logs".to_string(),
],]),
..Default::default()
}
)
Expand Down
16 changes: 11 additions & 5 deletions rust/cubesql/cubesql/src/compile/rewrite/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ use crate::{
AggregateFunctionExprDistinct, AggregateFunctionExprFun, AggregateSplit,
AggregateUDFExprFun, AliasExprAlias, AnyExprAll, AnyExprOp, BetweenExprNegated,
BinaryExprOp, CastExprDataType, ChangeUserMemberValue, ColumnExprColumn,
CubeScanAliasToCube, CubeScanLimit, CubeScanOffset, CubeScanUngrouped, CubeScanWrapped,
DimensionName, EmptyRelationDerivedSourceTableName, EmptyRelationIsWrappable,
EmptyRelationProduceOneRow, FilterMemberMember, FilterMemberOp, FilterMemberValues,
FilterOpOp, GroupingSetExprType, GroupingSetType, InListExprNegated,
InSubqueryExprNegated, JoinJoinConstraint, JoinJoinType, JoinLeftOn,
CubeScanAliasToCube, CubeScanJoinHints, CubeScanLimit, CubeScanOffset,
CubeScanUngrouped, CubeScanWrapped, DimensionName, EmptyRelationDerivedSourceTableName,
EmptyRelationIsWrappable, EmptyRelationProduceOneRow, FilterMemberMember,
FilterMemberOp, FilterMemberValues, FilterOpOp, GroupingSetExprType, GroupingSetType,
InListExprNegated, InSubqueryExprNegated, JoinJoinConstraint, JoinJoinType, JoinLeftOn,
JoinNullEqualsNull, JoinRightOn, LikeExprEscapeChar, LikeExprLikeType, LikeExprNegated,
LikeType, LimitFetch, LimitSkip, LiteralExprValue, LiteralMemberRelation,
LiteralMemberValue, LogicalPlanLanguage, MeasureName, MemberErrorError, OrderAsc,
Expand Down Expand Up @@ -2002,6 +2002,12 @@ impl LanguageToLogicalPlanConverter {
query.ungrouped = Some(true);
}

let join_hints =
match_data_node!(node_by_id, cube_scan_params[10], CubeScanJoinHints);
if join_hints.len() > 0 {
query.join_hints = Some(join_hints);
}

query.order = if !query_order.is_empty() {
Some(query_order)
} else {
Expand Down
1 change: 0 additions & 1 deletion rust/cubesql/cubesql/src/compile/rewrite/cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ impl BestCubePlan {
LogicalPlanLanguage::GroupAggregateSplitReplacer(_) => 1,
LogicalPlanLanguage::MemberPushdownReplacer(_) => 1,
LogicalPlanLanguage::EventNotification(_) => 1,
LogicalPlanLanguage::MergedMembersReplacer(_) => 1,
LogicalPlanLanguage::CaseExprReplacer(_) => 1,
LogicalPlanLanguage::WrapperPushdownReplacer(_) => 1,
LogicalPlanLanguage::WrapperPullupReplacer(_) => 1,
Expand Down
33 changes: 15 additions & 18 deletions rust/cubesql/cubesql/src/compile/rewrite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ crate::plan_to_language! {
can_pushdown_join: bool,
wrapped: bool,
ungrouped: bool,
join_hints: Vec<Vec<String>>,
},
CubeScanWrapper {
input: Arc<LogicalPlan>,
Expand Down Expand Up @@ -385,9 +386,6 @@ crate::plan_to_language! {
old_members: Arc<LogicalPlan>,
alias_to_cube: Vec<((String, String), String)>,
},
MergedMembersReplacer {
members: Vec<LogicalPlan>,
},
ListConcatPushdownReplacer {
members: Arc<LogicalPlan>,
},
Expand Down Expand Up @@ -1901,10 +1899,6 @@ fn member_pushdown_replacer(
)
}

fn merged_members_replacer(members: impl Display) -> String {
format!("(MergedMembersReplacer {})", members)
}

fn list_concat_pushdown_replacer(members: impl Display) -> String {
format!("(ListConcatPushdownReplacer {})", members)
}
Expand Down Expand Up @@ -2184,19 +2178,22 @@ fn cube_scan(
can_pushdown_join: impl Display,
wrapped: impl Display,
ungrouped: impl Display,
join_hints: impl Display,
) -> String {
format!(
"(CubeScan {} {} {} {} {} {} {} {} {} {})",
alias_to_cube,
members,
filters,
orders,
limit,
offset,
split,
can_pushdown_join,
wrapped,
ungrouped
r#"(CubeScan
{alias_to_cube}
{members}
{filters}
{orders}
{limit}
{offset}
{split}
{can_pushdown_join}
{wrapped}
{ungrouped}
{join_hints}
)"#
)
}

Expand Down
Loading
Loading