Skip to content

Commit

Permalink
feat: add ability to clone models (use same model under different nam…
Browse files Browse the repository at this point in the history
…e in a repository)
  • Loading branch information
retro committed Aug 1, 2024
1 parent ecca169 commit a33487d
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 0 deletions.
89 changes: 89 additions & 0 deletions src/__tests__/clone.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as semanticLayer from "../index.js";

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

const userModel = semanticLayer
.model()
.withName("user")
.fromTable("User")
.withDimension("user_id", {
type: "number",
primaryKey: true,
sql: ({ model, sql }) => sql`${model.column("UserId")}`,
})
.withDimension("first_name", {
type: "string",
sql: ({ model }) => model.column("FirstName"),
})
.withDimension("last_name", {
type: "string",
sql: ({ model }) => model.column("LastName"),
})
.withMetric("count", {
type: "string",
sql: ({ model, sql }) => sql`COUNT(DISTINCT ${model.column("UserId")})`,
});

const customerModel = userModel.clone("customer");
const employeeModel = userModel.clone("employee");

const invoiceModel = semanticLayer
.model()
.withName("invoice")
.fromTable("Invoice")
.withDimension("invoice_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("InvoiceId"),
})
.withDimension("customer_id", {
type: "number",
sql: ({ model }) => model.column("CustomerId"),
})
.withDimension("employee_id", {
type: "number",
sql: ({ model }) => model.column("EmployeeId"),
});

const repository = semanticLayer
.repository()
.withModel(customerModel)
.withModel(employeeModel)
.withModel(invoiceModel)
.joinOneToMany(
"customer",
"invoice",
({ sql, models }) =>
sql`${models.customer.dimension("user_id")} = ${models.invoice.dimension(
"customer_id",
)}`,
)
.joinOneToMany(
"employee",
"invoice",
({ sql, models }) =>
sql`${models.employee.dimension("user_id")} = ${models.invoice.dimension(
"employee_id",
)}`,
);

const queryBuilder = repository.build("postgresql");

describe("clone", async () => {
it("can clone a model", async () => {
const query = queryBuilder.buildQuery({
members: [
"customer.user_id",
"customer.count",
"employee.user_id",
"employee.count",
"invoice.invoice_id",
],
});

assert.equal(
query.sql,
'select "q0"."customer___user_id" as "customer___user_id", "q0"."employee___user_id" as "employee___user_id", "q0"."invoice___invoice_id" as "invoice___invoice_id", "q0"."customer___count" as "customer___count", "q1"."employee___count" as "employee___count" from (select "customer_query"."customer___user_id" as "customer___user_id", "customer_query"."employee___user_id" as "employee___user_id", "customer_query"."invoice___invoice_id" as "invoice___invoice_id", COUNT(DISTINCT count___metric_ref_0) as "customer___count" from (select distinct "User"."UserId" AS "count___metric_ref_0", "User"."UserId" as "customer___user_id", "Invoice"."InvoiceId" as "invoice___invoice_id", "User"."UserId" as "employee___user_id" from "User" left join "Invoice" on "User"."UserId" = "Invoice"."CustomerId" right join "User" on "User"."UserId" = "Invoice"."EmployeeId") as "customer_query" group by "customer_query"."customer___user_id", "customer_query"."employee___user_id", "customer_query"."invoice___invoice_id") as "q0" inner join (select "employee_query"."customer___user_id" as "customer___user_id", "employee_query"."employee___user_id" as "employee___user_id", "employee_query"."invoice___invoice_id" as "invoice___invoice_id", COUNT(DISTINCT count___metric_ref_0) as "employee___count" from (select distinct "User"."UserId" AS "count___metric_ref_0", "User"."UserId" as "employee___user_id", "Invoice"."InvoiceId" as "invoice___invoice_id", "User"."UserId" as "customer___user_id" from "User" left join "Invoice" on "User"."UserId" = "Invoice"."EmployeeId" right join "User" on "User"."UserId" = "Invoice"."CustomerId") as "employee_query" group by "employee_query"."customer___user_id", "employee_query"."employee___user_id", "employee_query"."invoice___invoice_id") as "q1" on "q0"."customer___user_id" = "q1"."customer___user_id" and "q0"."employee___user_id" = "q1"."employee___user_id" and "q0"."invoice___invoice_id" = "q1"."invoice___invoice_id" order by "customer___count" desc limit $1 offset $2',
);
});
});
17 changes: 17 additions & 0 deletions src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export abstract class Member {
getFormat() {
return this.props.format;
}
abstract clone(model: AnyModel): Member;
}

export class Dimension extends Member {
Expand All @@ -269,6 +270,9 @@ export class Dimension extends Member {
) {
super();
}
clone(model: AnyModel) {
return new Dimension(model, this.name, { ...this.props }, this.granularity);
}
getSql(dialect: AnyBaseDialect, context: unknown) {
const result = this.getSqlWithoutGranularity(dialect, context);

Expand Down Expand Up @@ -319,6 +323,9 @@ export class Metric extends Member {
) {
super();
}
clone(model: AnyModel) {
return new Metric(model, this.name, { ...this.props });
}
getNextColumnRefOrDimensionRefAlias() {
let columnRefOrDimensionRefAliasCounter = 0;
return () =>
Expand Down Expand Up @@ -493,6 +500,16 @@ export class Model<
});
return result.render(dialect, context);
}
clone<N extends string>(name: N) {
const newModel = new Model<C, N, D, M>(name, this.config);
for (const [key, value] of Object.entries(this.dimensions)) {
newModel.dimensions[key] = value.clone(newModel);
}
for (const [key, value] of Object.entries(this.metrics)) {
newModel.metrics[key] = value.clone(newModel);
}
return newModel;
}
}

const VALID_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
Expand Down

0 comments on commit a33487d

Please sign in to comment.