Skip to content

Commit

Permalink
feat: add ability to introspect query
Browse files Browse the repository at this point in the history
  • Loading branch information
retro committed Mar 20, 2024
1 parent f436c73 commit 65b023f
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 2 deletions.
50 changes: 48 additions & 2 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
PostgreSqlContainer,
StartedPostgreSqlContainer,
} from "@testcontainers/postgresql";
import { InferSqlQueryResultType, QueryBuilderQuery } from "../index.js";

import fs from "node:fs/promises";
import path from "node:path";
import pg from "pg";
import { zodToJsonSchema } from "zod-to-json-schema";
import { InferSqlQueryResultType } from "../index.js";

//import { format as sqlFormat } from "sql-formatter";

Expand Down Expand Up @@ -1083,7 +1083,7 @@ await describe("semantic layer", async () => {
});
});

await describe("model descriptions", async () => {
await describe("model descriptions and query introspection", async () => {
const customersModel = C.model("customers")
.fromSqlQuery('select * from "Customer"')
.withDimension("customer_id", {
Expand Down Expand Up @@ -1123,6 +1123,8 @@ await describe("semantic layer", async () => {
sql`${dimensions.customers.customer_id} = ${dimensions.invoices.customer_id}`,
);

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

await it("allows access to the model descriptions", () => {
const docs: string[] = [];
const dimensions = repository.getDimensions();
Expand All @@ -1149,5 +1151,49 @@ await describe("semantic layer", async () => {
"METRIC: invoices.total, TYPE: string, DESCRIPTION: -, FORMAT: percentage",
]);
});

await it("allows introspection of a query", () => {
const query: QueryBuilderQuery<typeof queryBuilder> = {
dimensions: [
"customers.customer_id",
"invoices.invoice_id",
"invoices.customer_id",
],
metrics: ["invoices.total"],
};

const introspection = queryBuilder.introspect(query);

assert.deepEqual(introspection, {
customers___customer_id: {
memberType: "dimension",
path: "customers.customer_id",
type: "number",
description: "The unique identifier of the customer",
format: undefined,
},
invoices___invoice_id: {
memberType: "dimension",
path: "invoices.invoice_id",
type: "number",
description: "The unique identifier of the invoice",
format: undefined,
},
invoices___customer_id: {
memberType: "dimension",
path: "invoices.customer_id",
type: "number",
description: "The unique identifier of the invoice customer",
format: undefined,
},
invoices___total: {
memberType: "metric",
path: "invoices.total",
format: "percentage",
type: "string",
description: undefined,
},
});
});
});
});
39 changes: 39 additions & 0 deletions src/lib/query-builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
AnyQuery,
AnyQueryFilter,
MemberFormat,
MemberNameToType,
MemberType,
Query,
QueryMemberName,
QueryReturnType,
Expand Down Expand Up @@ -148,4 +150,41 @@ export class QueryBuilder<

return result;
}

introspect(query: AnyQuery) {
const queryDimensions = query.dimensions ?? [];
const queryMetrics = query.metrics ?? [];

return [...queryDimensions, ...queryMetrics].reduce<
Record<
string,
{
memberType: "dimension" | "metric";
path: string;
format?: MemberFormat;
type: MemberType;
description?: string;
}
>
>((acc, memberName) => {
const member = this.repository.getMember(memberName);
acc[memberName.replaceAll(".", "___")] = {
memberType: member.isDimension() ? "dimension" : "metric",
path: member.getPath(),
format: member.getFormat(),
type: member.getType(),
description: member.getDescription(),
};

return acc;
}, {});
}
}

export type QueryBuilderQuery<Q> = Q extends QueryBuilder<
infer D,
infer M,
infer F
>
? Query<string & keyof D, string & keyof M, F>
: never;

0 comments on commit 65b023f

Please sign in to comment.