Skip to content

Commit

Permalink
feat: API Improvements
Browse files Browse the repository at this point in the history
- type inference for query results
- rename database to repository.
  • Loading branch information
retro committed Mar 20, 2024
1 parent d9821e3 commit 6e6ada5
Show file tree
Hide file tree
Showing 10 changed files with 386 additions and 256 deletions.
37 changes: 25 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ This library allows you to define models and their respective fields, including
**Defining a Model:**

```typescript
const customersModel = C.model("customers")
import * as semanticLayer from "@verybigthings/semantic-layer";

const customersModel = semanticLayer
.model("customers")
.fromTable("Customer")
.withDimension("customer_id", {
type: "number",
Expand All @@ -49,19 +52,23 @@ const customersModel = C.model("customers")
sql: ({ model }) => model.column("LastName"),
});

const invoicesModel = C.model("invoices")
const invoicesModel = semanticLayer
.model("invoices")
.fromTable("Invoice")
.withDimension("invoice_id", {
type: "number",
primaryKey: true,
sql: ({ model, sql }) => sql`${model.column("InvoiceId")}`,
})
.withMetric("total", {
type: "sum",
// node-postgres returns string types for big integers
type: "string",
aggregateWith: "sum",
sql: ({ model }) => model.column("Total"),
});

const invoiceLinesModel = C.model("invoice_lines")
const invoiceLinesModel = semanticLayer
.model("invoice_lines")
.fromTable("InvoiceLine")
.withDimension("invoice_line_id", {
type: "number",
Expand All @@ -77,19 +84,25 @@ const invoiceLinesModel = C.model("invoice_lines")
sql: ({ model }) => model.column("TrackId"),
})
.withMetric("quantity", {
type: "sum",
// node-postgres returns string types for big integers
type: "string",
aggregateWith: "sum",
sql: ({ model }) => model.column("Quantity"),
})
.withMetric("total_unit_price", {
type: "sum",
// node-postgres returns string types for big integers

type: "string",
aggregateWith: "sum"
sql: ({ model }) => model.column("UnitPrice"),
});
```

**Defining a Database and joining models:**
**Defining a Repository and joining models:**

```typescript
const db = C.database()
const repository = semanticLayer
.repository()
.withModel(customersModel)
.withModel(invoicesModel)
.withModel(invoiceLinesModel)
Expand All @@ -113,31 +126,31 @@ Leverage the library's querying capabilities to fetch dimensions and metrics, ap

```typescript
// Dimension and metric query
const query = db.query({
const query = repository.query({
dimensions: ["customers.customer_id"],
metrics: ["invoices.total"],
order: { "customers.customer_id": "asc" },
limit: 10,
});

// Metric query with filters
const query = db.query({
const query = repository.query({
metrics: ["invoices.total", "invoice_lines.quantity"],
filters: [
{ operator: "equals", member: "customers.customer_id", value: [1] },
],
});

// Dimension query with filters
const query = db.query({
const query = repository.query({
dimensions: ["customers.first_name", "customers.last_name"],
filters: [
{ operator: "equals", member: "customers.customer_id", value: [1] },
],
});

// Filtering and sorting
const query = db.query({
const query = repository.query({
dimensions: ["customers.first_name"],
metrics: ["invoices.total"],
filters: [{ operator: "gt", member: "invoices.total", value: [100] }],
Expand Down
72 changes: 51 additions & 21 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {
import fs from "node:fs/promises";
import path from "node:path";
import pg from "pg";
import { InferSqlQueryResultType } from "../index.js";

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

const __dirname = path.dirname(new URL(import.meta.url).pathname);

/*const query = db.query({
/*const query = built.query({
dimensions: [
"customers.customer_id",
//'invoice_lines.invoice_line_id',
Expand Down Expand Up @@ -128,7 +129,8 @@ await describe("semantic layer", async () => {
sql: ({ model }) => model.column("InvoiceDate"),
})
.withMetric("total", {
type: "sum",
type: "string",
aggregateWith: "sum",
sql: ({ model }) => model.column("Total"),
});

Expand All @@ -148,11 +150,13 @@ await describe("semantic layer", async () => {
sql: ({ model }) => model.column("TrackId"),
})
.withMetric("quantity", {
type: "sum",
type: "string",
aggregateWith: "sum",
sql: ({ model }) => model.column("Quantity"),
})
.withMetric("total_unit_price", {
type: "sum",
type: "string",
aggregateWith: "sum",
sql: ({ model }) => model.column("UnitPrice"),
});

Expand Down Expand Up @@ -184,7 +188,7 @@ await describe("semantic layer", async () => {
sql: ({ model }) => model.column("Title"),
});

const db = C.database()
const repository = C.repository()
.withModel(customersModel)
.withModel(invoicesModel)
.withModel(invoiceLinesModel)
Expand Down Expand Up @@ -215,15 +219,20 @@ await describe("semantic layer", async () => {
sql`${dimensions.tracks.album_id} = ${dimensions.albums.album_id}`,
);

const queryBuilder = repository.build();

await it("can query one dimension and one metric", async () => {
const query = db.query({
const query = queryBuilder.build({
dimensions: ["customers.customer_id"],
metrics: ["invoices.total"],
order: { "customers.customer_id": "asc" },
limit: 10,
});

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

assert.deepEqual(result.rows, [
{ customers___customer_id: 1, invoices___total: "39.62" },
Expand All @@ -240,14 +249,17 @@ await describe("semantic layer", async () => {
});

await it("can query one dimension and multiple metrics", async () => {
const query = db.query({
const query = queryBuilder.build({
dimensions: ["customers.customer_id"],
metrics: ["invoices.total", "invoice_lines.total_unit_price"],
order: { "customers.customer_id": "asc" },
limit: 10,
});

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

assert.deepEqual(result.rows, [
{
Expand Down Expand Up @@ -304,7 +316,7 @@ await describe("semantic layer", async () => {
});

await it("can query one dimension and metric and filter by a different metric", async () => {
const query = db.query({
const query = queryBuilder.build({
dimensions: ["customers.customer_id"],
metrics: ["invoices.total"],
order: { "customers.customer_id": "asc" },
Expand All @@ -318,7 +330,10 @@ await describe("semantic layer", async () => {
],
});

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

assert.deepEqual(result.rows, [
{ customers___customer_id: 2, invoices___total: "37.62" },
Expand All @@ -335,42 +350,51 @@ await describe("semantic layer", async () => {
});

await it("can query a metric and filter by a dimension", async () => {
const query = db.query({
const query = queryBuilder.build({
metrics: ["invoices.total"],
filters: [
{ operator: "equals", member: "customers.customer_id", value: [1] },
],
});

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

assert.deepEqual(result.rows, [{ invoices___total: "39.62" }]);
});

await it("can query multiple metrics and filter by a dimension", async () => {
const query = db.query({
const query = queryBuilder.build({
metrics: ["invoices.total", "invoice_lines.quantity"],
filters: [
{ operator: "equals", member: "customers.customer_id", value: [1] },
],
});

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

assert.deepEqual(result.rows, [
{ invoices___total: "39.62", invoice_lines___quantity: "38" },
]);
});

await it("can query dimensions only", async () => {
const query = db.query({
const query = queryBuilder.build({
dimensions: ["customers.customer_id", "albums.title"],
filters: [
{ operator: "equals", member: "customers.customer_id", value: [1] },
],
});

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

assert.deepEqual(result.rows, [
{ customers___customer_id: 1, albums___title: "Ac�stico MTV" },
Expand Down Expand Up @@ -441,11 +465,12 @@ await describe("semantic layer", async () => {
sql: ({ model }) => model.column("CustomerId"),
})
.withMetric("total", {
type: "sum",
type: "string",
aggregateWith: "sum",
sql: ({ model }) => model.column("Total"),
});

const db = C.database()
const repository = C.repository()
.withModel(customersModel)
.withModel(invoicesModel)
.joinOneToMany(
Expand All @@ -455,15 +480,20 @@ await describe("semantic layer", async () => {
sql`${dimensions.customers.customer_id} = ${dimensions.invoices.customer_id}`,
);

const queryBuilder = repository.build();

await it("can query one dimension and multiple metrics", async () => {
const query = db.query({
const query = queryBuilder.build({
dimensions: ["customers.customer_id"],
metrics: ["invoices.total"],
order: { "customers.customer_id": "asc" },
limit: 10,
});

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

assert.deepEqual(result.rows, [
{
Expand Down
Loading

0 comments on commit 6e6ada5

Please sign in to comment.