Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cubeql

A tiny semantic layer for SQL, written in Go. Describe your tables, the columns you expose, and how tables join — all in YAML. Then ask for the columns you want, and cubeql compiles a query that joins in only the tables it needs.

The interesting part is join resolution. A wide model might have dozens of related tables, but any single query touches a handful. cubeql represents joins as a directed graph and runs a breadth-first shortest-path search from the base table to each referenced table, so the generated SQL never drags in joins a query doesn't use — and multi-hop relationships (orders → customers → regions) are stitched together automatically.

Inspired by metric/semantic layers like Cube.js and LookML, distilled to the one idea worth showing: graph-driven join planning. ~700 lines, no heavy dependencies (just a YAML parser).

How it works

 YAML model ──▶ Schema ──▶ Join graph ──▶ BFS shortest path ──▶ SQL + params
                              per view        per query
  1. Model (model/) loads cubes and views from YAML and validates them.
  2. Graph (graph/) turns a view's joins into a directed graph and resolves the minimal edge set to reach a set of target cubes.
  3. Compiler (compiler/) parses a query, asks the graph which joins are needed, and renders SELECT / FROM / LEFT JOIN / WHERE with bound ? parameters (no string interpolation of user values).

Quick start

go run ./cmd/cubeql compile -schema ./examples/schema -query ./examples/query.json

Given this query against the example e-commerce model:

{
  "cube": "order_analytics",
  "select": ["orders.id", "orders.amount", "regions.name", "products.category"],
  "filters": [
    { "column": "orders.status", "op": "in", "values": ["paid", "shipped"] },
    { "column": "regions.name",  "op": "eq", "values": ["EMEA"] }
  ]
}

cubeql emits:

SELECT orders.id AS orders_id,
       orders.amount_cents / 100.0 AS orders_amount,
       regions.name AS regions_name,
       products.category AS products_category
FROM fact_orders AS orders
LEFT JOIN dim_customers AS customers ON orders.customer_id = customers.id
LEFT JOIN dim_regions AS regions ON customers.region_id = regions.id
LEFT JOIN dim_products AS products ON orders.product_id = products.id
WHERE orders.status IN (?, ?)
  AND regions.name = ?;
-- params: ["paid","shipped","EMEA"]

Note that regions was requested but isn't directly joinable to orders — the compiler found the path orders → customers → regions and inserted the intermediate customers join on its own. Ask for only orders.id and you get zero joins.

Defining a model

A cube maps to a physical table and exposes typed columns. A column's expr is SQL evaluated in the cube's context; {token} references a sibling column and defaults to the column name.

cubes:
  - name: orders
    table: fact_orders
    columns:
      - { name: id,          type: int }
      - { name: customer_id, type: int }
      - { name: amount,      type: float, expr: "{amount_cents} / 100.0" }

A view composes cubes into a join graph rooted at base. Each join is a directed edge; from defaults to the base, so edges chain into multiple hops. The on condition references columns as {cube.column}.

cubes:
  - name: order_analytics
    base: orders
    joins:
      - { to: customers, on: "{orders.customer_id} = {customers.id}" }
      - { to: products,  on: "{orders.product_id} = {products.id}" }
      - { from: customers, to: regions, on: "{customers.region_id} = {regions.id}" }

HTTP API

go run ./cmd/cubeql serve -schema ./examples/schema -addr :8080

curl -s localhost:8080/compile -d '{
  "cube": "order_analytics",
  "select": ["orders.amount", "regions.name"]
}'

Returns { "sql": "...", "params": [...] }.

Supported filter operators

eq ne gt gte lt lte like in between null notnull. Values are always passed as bound parameters.

Testing

go test ./...

The graph tests cover shortest-path selection, de-duplication of shared join prefixes, and unreachable targets; the compiler tests cover plain cubes, views, two-hop resolution, filter-driven joins, and parameter ordering.

Layout

model/      YAML schema: cubes, columns, joins, views, validation
graph/      directed join graph + BFS shortest-path resolution
compiler/   query -> SQL + bound parameters
cmd/cubeql/ CLI (compile) and HTTP server (serve)
examples/   sample e-commerce model and query

License

MIT — see LICENSE.

About

A tiny semantic layer for SQL

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages