Skip to content

Commit

Permalink
refactor: calculate join plan as a part of the query plan
Browse files Browse the repository at this point in the history
  • Loading branch information
retro committed Aug 30, 2024
1 parent b8a1324 commit 9014b5f
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 134 deletions.
9 changes: 4 additions & 5 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2548,7 +2548,7 @@ describe("semantic layer", async () => {
},
]);

const query = queryBuilder.buildQuery({
const query2 = queryBuilder.buildQuery({
members: [
"store_sales.store_id",
"store_sales.product_id",
Expand All @@ -2557,10 +2557,9 @@ describe("semantic layer", async () => {
],
});

const result2 = await client.query<InferSqlQueryResultType<typeof query>>(
query.sql,
query.bindings,
);
const result2 = await client.query<
InferSqlQueryResultType<typeof query2>
>(query2.sql, query2.bindings);

assert.deepEqual(result2.rows, [
{
Expand Down
136 changes: 88 additions & 48 deletions src/__tests__/query-builder/query-plan.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import * as fullRepository from "../full-repository.js";

import { expect, it } from "vitest";
import { assert, it } from "vitest";

import { getQueryPlan } from "../../lib/query-builder/query-plan.js";
import { QueryMemberCache } from "../../lib/query-builder/query-plan/query-member.js";

it("can crate a query plan", () => {
const { queryBuilder } = fullRepository;

const queryPlan = getQueryPlan(queryBuilder, undefined, {
const queryMembers = new QueryMemberCache(
queryBuilder.repository,
queryBuilder.dialect,
undefined,
);
const queryPlan = getQueryPlan(queryBuilder, queryMembers, undefined, {
members: [
"artists.name",
"tracks.name",
Expand All @@ -29,9 +34,10 @@ it("can crate a query plan", () => {
order: [{ member: "artists.name", direction: "asc" }],
});

expect(queryPlan).toMatchObject({
assert.deepEqual(queryPlan, {
segments: [
{
metricsRefsSubQueryPlan: undefined,
models: ["artists", "tracks", "albums", "genres"],
modelQuery: {
dimensions: [
Expand Down Expand Up @@ -72,17 +78,30 @@ it("can crate a query plan", () => {
],
},
alias: "s0",
initialModel: "tracks",
joinPlan: {
hasRowMultiplication: false,
initialModel: "tracks",
joins: [
{
leftModel: "tracks",
rightModel: "albums",
joinType: "rightJoin",
},
{
leftModel: "albums",
rightModel: "artists",
joinType: "leftJoin",
},
{ leftModel: "tracks", rightModel: "genres", joinType: "leftJoin" },
],
},
filters: [
{
operator: "equals",
member: "genres.name",
value: ["Rock"],
},
{ operator: "equals", member: "genres.name", value: ["Rock"] },
],
},
{
models: ["artists", "tracks", "albums", "genres", "invoice_lines"],
metricsRefsSubQueryPlan: undefined,
modelQuery: {
dimensions: [
"artists.name",
Expand Down Expand Up @@ -124,17 +143,35 @@ it("can crate a query plan", () => {
],
},
alias: "s1",
initialModel: "invoice_lines",
joinPlan: {
hasRowMultiplication: false,
initialModel: "invoice_lines",
joins: [
{
leftModel: "invoice_lines",
rightModel: "tracks",
joinType: "leftJoin",
},
{
leftModel: "tracks",
rightModel: "albums",
joinType: "rightJoin",
},
{
leftModel: "albums",
rightModel: "artists",
joinType: "leftJoin",
},
{ leftModel: "tracks", rightModel: "genres", joinType: "leftJoin" },
],
},
filters: [
{
operator: "equals",
member: "genres.name",
value: ["Rock"],
},
{ operator: "equals", member: "genres.name", value: ["Rock"] },
],
},
{
models: ["artists", "tracks", "albums", "genres", "invoices"],
metricsRefsSubQueryPlan: undefined,
modelQuery: {
dimensions: [
"artists.name",
Expand Down Expand Up @@ -169,45 +206,48 @@ it("can crate a query plan", () => {
members: ["artists.name", "tracks.name", "albums.title"],
},
alias: "s2",
initialModel: "invoices",
joinPlan: {
hasRowMultiplication: true,
initialModel: "invoices",
joins: [
{
leftModel: "invoices",
rightModel: "invoice_lines",
joinType: "leftJoin",
},
{
leftModel: "invoice_lines",
rightModel: "tracks",
joinType: "leftJoin",
},
{
leftModel: "tracks",
rightModel: "albums",
joinType: "rightJoin",
},
{
leftModel: "albums",
rightModel: "artists",
joinType: "leftJoin",
},
{ leftModel: "tracks", rightModel: "genres", joinType: "leftJoin" },
],
},
filters: [
{
operator: "equals",
member: "genres.name",
value: ["Rock"],
},
{ operator: "equals", member: "genres.name", value: ["Rock"] },
],
},
],
filters: [
{
operator: "gt",
member: "invoice_lines.unit_price",
value: [0],
},
{
operator: "gt",
member: "invoice_lines.quantity",
value: [0],
},
{
operator: "gt",
member: "tracks.unit_price",
value: [0],
},
{
operator: "gt",
member: "invoices.total",
value: [100],
},
{ operator: "gt", member: "invoice_lines.unit_price", value: [0] },
{ operator: "gt", member: "invoice_lines.quantity", value: [0] },
{ operator: "gt", member: "tracks.unit_price", value: [0] },
{ operator: "gt", member: "invoices.total", value: [100] },
],
projectedDimensions: ["artists.name", "tracks.name", "albums.title"],
projectedMetrics: ["tracks.unit_price", "invoice_lines.quantity"],
order: [
{
member: "artists.name",
direction: "asc",
},
],
order: [{ member: "artists.name", direction: "asc" }],
limit: undefined,
offset: undefined,
});
});
19 changes: 1 addition & 18 deletions src/lib/model/basic-metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,24 +156,7 @@ export class BasicMetricQueryMember extends QueryMember {
return this.member.getAlias();
}
getSql() {
const result = this.sqlFnRenderResult;

if (result) {
return result;
}

const { sql: asSql, bindings } = this.member.model.getAs(
this.repository,
this.queryMembers,
this.dialect,
this.context,
);
const sql = `${asSql}.${this.dialect.asIdentifier(this.member.name)}`;

return SqlFragment.make({
sql,
bindings,
});
return this.sqlFnRenderResult;
}
getFilterSql() {
return SqlFragment.fromSql(this.dialect.asIdentifier(this.getAlias()));
Expand Down
2 changes: 1 addition & 1 deletion src/lib/model/granularity-dimension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class GranularityDimensionQueryMember extends BasicDimensionQueryMember {
super(queryMembers, repository, dialect, context, member);
}
getSql() {
const parent = this.queryMembers.getByPath(this.member.parent.getPath());
const parent = this.queryMembers.get(this.member.parent);
const result = parent.getSql();
return SqlFragment.make({
sql: this.dialect.withGranularity(this.member.granularity, result.sql),
Expand Down
9 changes: 7 additions & 2 deletions src/lib/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,13 @@ export class QueryBuilder<
parsedQuery: AnyInputQuery,
context: unknown,
): SqlQuery {
const queryPlan = getQueryPlan(this, context, parsedQuery);
const sqlQuery = buildQuery(this, context, queryPlan);
const queryMembers = new QueryMemberCache(
this.repository,
this.dialect,
context,
);
const queryPlan = getQueryPlan(this, queryMembers, context, parsedQuery);
const sqlQuery = buildQuery(this, queryMembers, context, queryPlan);

return sqlQuery.toSQL();
}
Expand Down
Loading

0 comments on commit 9014b5f

Please sign in to comment.