Skip to content

Commit

Permalink
fix: add additional validation for filters
Browse files Browse the repository at this point in the history
It is impossible to combine dimension and metric filters in the same connective (and/or). This adds
validation on schema level to ensure this behavior.
  • Loading branch information
retro committed Aug 16, 2024
1 parent 1304593 commit fcf4db8
Show file tree
Hide file tree
Showing 4 changed files with 678 additions and 346 deletions.
354 changes: 354 additions & 0 deletions src/__tests__/full-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
import * as semanticLayer from "../index.js";

const customersModel = semanticLayer
.model()
.withName("customers")
.fromTable("Customer")
.withDimension("customer_id", {
type: "number",
primaryKey: true,
sql: ({ model, sql }) => sql`${model.column("CustomerId")}`,
})
.withDimension("first_name", {
type: "string",
sql: ({ model }) => model.column("FirstName"),
})
.withDimension("last_name", {
type: "string",
sql: ({ model }) => model.column("LastName"),
})
.withDimension("full_name", {
type: "string",
sql: ({ model, sql }) =>
sql`${model.dimension("first_name")} || ' ' || ${model.dimension(
"last_name",
)}`,
})
.withDimension("company", {
type: "string",
sql: ({ model }) => model.column("Company"),
})
.withDimension("address", {
type: "string",
sql: ({ model }) => model.column("Address"),
})
.withDimension("city", {
type: "string",
sql: ({ model }) => model.column("City"),
})
.withDimension("state", {
type: "string",
sql: ({ model }) => model.column("State"),
})
.withDimension("country", {
type: "string",
sql: ({ model }) => model.column("Country"),
})
.withDimension("postal_code", {
type: "string",
sql: ({ model }) => model.column("PostalCode"),
})
.withDimension("phone", {
type: "string",
sql: ({ model }) => model.column("Phone"),
})
.withDimension("fax", {
type: "string",
sql: ({ model }) => model.column("Fax"),
})
.withDimension("email", {
type: "string",
sql: ({ model }) => model.column("Email"),
});

const invoicesModel = semanticLayer
.model()
.withName("invoices")
.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("invoice_date", {
type: "date",
sql: ({ model }) => model.column("InvoiceDate"),
})
.withDimension("billing_address", {
type: "string",
sql: ({ model }) => model.column("BillingAddress"),
})
.withDimension("billing_city", {
type: "string",
sql: ({ model }) => model.column("BillingCity"),
})
.withDimension("billing_state", {
type: "string",
sql: ({ model }) => model.column("BillingState"),
})
.withDimension("billing_country", {
type: "string",
sql: ({ model }) => model.column("BillingCountry"),
})
.withDimension("billing_postal_code", {
type: "string",
sql: ({ model }) => model.column("BillingPostalCode"),
})
.withMetric("total", {
type: "number",
description: "Invoice total.",
sql: ({ model, sql }) => sql`SUM(COALESCE(${model.column("Total")}, 0))`,
});

const invoiceLinesModel = semanticLayer
.model()
.withName("invoice_lines")
.fromTable("InvoiceLine")
.withDimension("invoice_line_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("InvoiceLineId"),
})
.withDimension("invoice_id", {
type: "number",
sql: ({ model }) => model.column("InvoiceId"),
})
.withDimension("track_id", {
type: "number",
sql: ({ model }) => model.column("TrackId"),
})
.withMetric("quantity", {
type: "number",
description: "Sum of the track quantities across models.",
sql: ({ model, sql }) => sql`SUM(COALESCE(${model.column("Quantity")}, 0))`,
})
.withMetric("unit_price", {
type: "number",
description: "Sum of the track unit prices across models.",
sql: ({ model, sql }) =>
sql`SUM(COALESCE(${model.column("UnitPrice")}, 0))`,
});

const tracksModel = semanticLayer
.model()
.withName("tracks")
.fromTable("Track")
.withDimension("track_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("TrackId"),
})
.withDimension("album_id", {
type: "number",
sql: ({ model }) => model.column("AlbumId"),
})
.withDimension("media_type_id", {
type: "number",
sql: ({ model }) => model.column("MediaTypeId"),
})
.withDimension("genre_id", {
type: "number",
sql: ({ model }) => model.column("GenreId"),
})
.withDimension("name", {
type: "string",
sql: ({ model }) => model.column("Name"),
})
.withDimension("composer", {
type: "string",
sql: ({ model }) => model.column("Composer"),
})
.withDimension("milliseconds", {
type: "number",
sql: ({ model }) => model.column("Milliseconds"),
})
.withDimension("bytes", {
type: "number",
sql: ({ model }) => model.column("Bytes"),
})
.withMetric("unit_price", {
type: "number",
description: "Sum of the track unit prices across models.",
sql: ({ model, sql }) =>
sql`SUM(COALESCE(${model.column("UnitPrice")}, 0))`,
format: (value) => `Price: $${value}`,
});

const albumsModel = semanticLayer
.model()
.withName("albums")
.fromTable("Album")
.withDimension("album_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("AlbumId"),
})
.withDimension("artist_id", {
type: "number",
sql: ({ model }) => model.column("ArtistId"),
})
.withDimension("title", {
type: "string",
sql: ({ model }) => model.column("Title"),
});

const artistModel = semanticLayer
.model()
.withName("artists")
.fromTable("Artist")
.withDimension("artist_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("ArtistId"),
})
.withDimension("name", {
type: "string",
sql: ({ model }) => model.column("Name"),
format: (value) => `Artist: ${value}`,
});

const mediaTypeModel = semanticLayer
.model()
.withName("media_types")
.fromTable("MediaType")
.withDimension("media_type_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("MediaTypeId"),
})
.withDimension("name", {
type: "string",
sql: ({ model }) => model.column("Name"),
});

const genreModel = semanticLayer
.model()
.withName("genres")
.fromTable("Genre")
.withDimension("name", {
type: "string",
sql: ({ model }) => model.column("Name"),
})
.withDimension("genre_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("GenreId"),
});

const playlistModel = semanticLayer
.model()
.withName("playlists")
.fromTable("Playlist")
.withDimension("playlist_id", {
type: "number",
primaryKey: true,
sql: ({ model }) => model.column("PlaylistId"),
})
.withDimension("name", {
type: "string",
sql: ({ model }) => model.column("Name"),
});

const playlistTrackModel = semanticLayer
.model()
.withName("playlist_tracks")
.fromTable("PlaylistTrack")
.withDimension("playlist_id", {
type: "number",
sql: ({ model }) => model.column("PlaylistId"),
})
.withDimension("track_id", {
type: "number",
sql: ({ model }) => model.column("TrackId"),
});

export const repository = semanticLayer
.repository()
.withModel(customersModel)
.withModel(invoicesModel)
.withModel(invoiceLinesModel)
.withModel(tracksModel)
.withModel(albumsModel)
.withModel(artistModel)
.withModel(mediaTypeModel)
.withModel(genreModel)
.withModel(playlistModel)
.withModel(playlistTrackModel)
.joinOneToMany(
"customers",
"invoices",
({ sql, models }) =>
sql`${models.customers.dimension(
"customer_id",
)} = ${models.invoices.dimension("customer_id")}`,
)
.joinOneToMany(
"invoices",
"invoice_lines",
({ sql, models }) =>
sql`${models.invoices.dimension(
"invoice_id",
)} = ${models.invoice_lines.dimension("invoice_id")}`,
)
.joinManyToOne(
"invoice_lines",
"tracks",
({ sql, models }) =>
sql`${models.invoice_lines.dimension(
"track_id",
)} = ${models.tracks.dimension("track_id")}`,
)
.joinOneToMany(
"albums",
"tracks",
({ sql, models }) =>
sql`${models.tracks.dimension("album_id")} = ${models.albums.dimension(
"album_id",
)}`,
)
.joinManyToOne(
"albums",
"artists",
({ sql, models }) =>
sql`${models.albums.dimension("artist_id")} = ${models.artists.dimension(
"artist_id",
)}`,
)
.joinOneToOne(
"tracks",
"media_types",
({ sql, models }) =>
sql`${models.tracks.dimension(
"media_type_id",
)} = ${models.media_types.dimension("media_type_id")}`,
)
.joinOneToOne(
"tracks",
"genres",
({ sql, models }) =>
sql`${models.tracks.dimension("genre_id")} = ${models.genres.dimension(
"genre_id",
)}`,
)
.joinManyToMany(
"playlists",
"playlist_tracks",
({ sql, models }) =>
sql`${models.playlists.dimension(
"playlist_id",
)} = ${models.playlist_tracks.dimension("playlist_id")}`,
)
.joinManyToMany(
"playlist_tracks",
"tracks",
({ sql, models }) =>
sql`${models.playlist_tracks.dimension(
"track_id",
)} = ${models.tracks.dimension("track_id")}`,
);

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

0 comments on commit fcf4db8

Please sign in to comment.