Semantic-layer transpiler: one neutral IR, many dialects (dbt, Snowflake Cortex, semantic views, supersimple, nao). Written in Go.
A semantic-layer transpiler: one neutral IR, many dialects.
Built for benchouse.ai, the independent leaderboard for analytics agents, and open-sourced for everyone.
Where sqlglot translates across SQL dialects, semglot translates across semantic-layer dialects (dbt semantic models, Snowflake Cortex, Snowflake semantic views, Databricks metric views, and more) through one neutral intermediate representation (IR).
You point it at a source layer, pick a target dialect, and it writes the equivalent layer out. Because everything routes through the IR, adding a dialect adds every conversion into and out of it, not just one.
Semantic Layer Dialects
A source is read into the IR; a target is written from it. dbt is both, so dbt to dbt is a lossless round-trip.
| Dialect | Source | Target | |---------------------------|:------:|:------:| | dbt | โ | โ | | cortex | | โ | | snowflake-semantic-view | | โ | | supersimple | | โ | | nao-yaml | | โ | | nao-context-rules | | โ | | databricks-metric-view | | โ |
Adding a dialect is small, self-contained work: implement a Parser (dialect files to IR), an Emitter (IR to dialect files), or both โ the interfaces in dialect/dialect.go โ register it via init(), and every conversion to and from it comes for free. A target dialect is one method, Emit. See CONTRIBUTING.md. Missing one you need? Please open an issue or PR.
Install
go install github.com/benchouse/semglot/cmd/semglot@latest
or, from a clone:
go build -o semglot ./cmd/semglot
Usage
build transpiles a source semantic layer into a target dialect. Builds are configured with named profiles in semglot.yaml:
# semglot.yaml
profiles:
catalog:
source: ./models
target-dialect: snowflake-semantic-view
output: ./out
database: ANALYTICS
model-name: catalog
Given a small dbt table:
# models/schema.yml
models:
- name: dim_product
description: Product dimension.
columns:
- name: product_id
description: Product surrogate key.
data_type: number
constraints:
- type: primary_key
- name: category
description: Product category.
data_type: varchar
- name: title
description: Product title.
data_type: varchar
run the profile:
semglot build --profile catalog
semglot writes out/definition.md with the create statement:
create or replace semantic view CATALOG
tables (
DIMPRODUCT as ANALYTICS.MAIN.DIMPRODUCT primary key (PRODUCT_ID) comment='Product dimension.'
)
dimensions (
DIMPRODUCT.PRODUCTID as dimproduct.PRODUCTID comment='Product surrogate key.',
DIMPRODUCT.CATEGORY as dimproduct.CATEGORY comment='Product category.',
DIMPRODUCT.TITLE as dimproduct.TITLE comment='Product title.'
)
;
Options
--profile <name>selects a profile from the config. Required.--config <path>points at the config file. Defaults to./semglot.yaml.
NOTES.md sidecar listing metrics that don't map).
Configuration
A profile is a complete, self-contained build. Every field:
# semglot.yaml
profiles:
view_prod:
source: ./models # required. dbt source dir, or a list of dirs
source-dialect: dbt # optional. default: dbt
target-dialect: snowflake-semantic-view # required
output: ./out/view # required. directory to write into
database: ANALYTICS # required for warehouse targets (cortex, snowflake-semantic-view, databricks-metric-view)
schema: SEM # optional. default: MAIN (schema of the source tables)
view-schema: SEM_VIEWS # optional. schema for the emitted semantic-view object; defaults to schema
model-name: catalog # optional. default: source dir name
description: Curated view. # optional
- Each profile is independent: there is no shared or inherited config. Staging and
database and output.
- Omitted optional fields take defaults:
source-dialectisdbt,schemais
MAIN, and model-name is the source directory name.
buildfails clearly when the config is missing or unparseable, the--profile
source, target-dialect, output) is absent,
or a warehouse target has no database.
Dialect support
Every dialect maps to the same neutral IR, but targets differ in how much of it they can express. This is what each target emits today (dbt is currently the only source).
| Feature | dbt | cortex | snowflake-semantic-view | supersimple | nao-yaml | nao-context-rules | databricks-metric-view | |-------------------------|:-----:|:--------:|:-------------------------:|:-------------:|:----------:|:-------------------:|:------------------------:| | Tables | โ | โ | โ | โ | | ~ | โ | | Columns | โ | โ | โ | โ | โ | ~ | โ | | Time dimensions | โ | โ | ~ | โ | โ | ~ | ~ | | Descriptions | โ | โ | โ | โ | ~ | โ | โ | | Data types | โ | โ | | โ | | | | | Primary keys | โ | โ | โ | โ | | | | | Relationships | โ | โ | โ | โ | | โ | โ | | Metrics (aggregations) | โ | โ | โ | โ | ~ | โ | โ | | Ratio & derived metrics | โ | โ | โ | ~ | โ | โ | โ | | Synonyms | ~ | โ | | | โ | โ | โ | | Enums / allowed values | โ | ~ | โ | โ | โ | โ | โ |
โ structured ยท โ rolled up as text in a description or comment ยท ~ partial ยท blank not emitted.
Where a target has no native slot for something, semglot degrades rather than drops it:
โmeans the value survives only as text inside a parent's description or
Values: โฆ), not as a structured field. Enums and synonyms fold this
way when the target has no native slot for them.
cortexis the exception: it has a native synonyms field and keeps enum
supersimpleemits division ratios; other arithmetic is deferred to a
NOTES.md sidecar.
nao-yamlis a flat, model-global document, so it has no table grouping.nao-context-rulesis prose, so it lists only elements that carry a
databricks-metric-viewrequires Databricks Runtime 17.2+ for the base
display_name and synonyms are emitted whenever
the IR carries a label or synonyms and require Runtime 17.3+. On an older
warehouse, a view containing them is rejected.
License
MIT. See LICENSE.