Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dbml-homepage/configs/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const SidebarConfigs: SidebarsConfig = {
type: 'doc',
label: 'Enrichment & Visualization',
},
{
id: 'syntax/data-dependency',
type: 'doc',
label: 'Data Dependency',
},
{
id: 'syntax/language-basics',
type: 'doc',
Expand Down
167 changes: 167 additions & 0 deletions dbml-homepage/docs/syntax/data-dependency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
title: Data Dependency
---

# Data Dependency

`Dep` describes **data lineage** — a directional flow of data from an upstream element to a downstream one (for example, a raw table feeding a staging table, or a source column feeding a derived column). It is distinct from `Ref`: a `Ref` models a foreign-key relationship between columns, while a `Dep` models "where this data comes from / where it goes". Deps are used solely to annotate and support the visualization; they have no SQL equivalent.

- [Direction](#direction)
- [Short Form](#short-form)
- [Block Form](#block-form)
- [Block Header Settings](#block-header-settings)
- [Inline Form](#inline-form)
- [Endpoints](#endpoints)
- [Settings](#settings)
- [Uniqueness](#uniqueness)

## Direction

A dep always points from the **upstream** (source) element to the **downstream** (target) element. You can write the direction in either order:

- `->` points from upstream to downstream: `upstream -> downstream`
- `<-` points from downstream to upstream: `downstream <- upstream`

Both operators describe the same directed edge — pick whichever reads more naturally. These two lines are equivalent:

```text
Dep: users -> orders
Dep: orders <- users
```

## Short Form

The short form declares a single edge on one line:

```text
Dep: users -> orders
Dep: orders.user_id <- users.id
```

A `Dep` may optionally be given a name. The name is only a label — it has no effect on the lineage:

```text
Dep pipeline_step: users -> orders
```

## Block Form

The block form groups multiple edges, and lets you attach settings, inside curly braces:

```text
Dep {
raw_orders -> stg_orders
stg_orders -> fct_orders
stg_orders.amount -> fct_orders.revenue

note: 'Aggregate staging orders into facts'
color: #79AD51
}
```

Each line inside the block is one edge. Settings such as `note` and `color` are written as their own lines in the block body.

## Block Header Settings

Settings can also be placed in a `[...]` list on the block header, before the opening brace:

```text
Dep [color: #79AD51] {
raw_orders -> stg_orders
}
```

A named block takes its name before the header list:

```text
Dep etl_flow [color: #79AD51] {
raw_orders -> stg_orders
}
```

## Inline Form

You can attach a dep directly to a table or a column using the `dep` setting, without writing a separate `Dep` declaration.

On a **table header**, the endpoint is the table itself:

```text
Table mart_orders [dep: <- fct_orders] {
id int
total decimal
}
```

On a **column**, the endpoint is that column:

```text
Table fct_orders {
id int
revenue decimal [dep: -> reports.revenue]
}
```

The inline `dep` value is a direction operator (`->` or `<-`) followed by the other endpoint. The host table or column supplies the near side of the edge automatically.

## Endpoints

An endpoint can be a whole table or a single column, and may be qualified with a schema:

- Table-level: `table`, or `schema.table`
- Column-level: `table.column`, or `schema.table.column`

Both sides of an edge should refer to the same level — connect a table to a table, or a column to a column:

```text
Dep: my_schema.events -> users
Dep: my_schema.events.id -> users.id
Dep: my_schema.events.id <- another_schema.booking.id
```

When no schema is given, the endpoint resolves in the default `public` schema.

## Settings

Settings are written either in the block header `[...]` list, on a per-edge `[...]` list, or as `key: value` lines inside the block body.

- `note: 'string'`: add a note describing the dependency. See [Note Definition](./enrichment-visualization.md#note-definition). The note can use a [multi-line string](./language-basics.md#multi-line-string).
- `color: <color_code>`: change the color of the lineage line. See [Colors](./enrichment-visualization.md#colors) for accepted color formats.

```text
// header list
Dep [color: #79AD51] {
raw_orders -> stg_orders
}

// body lines
Dep {
raw_orders -> stg_orders
note: 'Nightly load'
color: #79AD51
}

// per-edge list
Dep {
raw_orders -> stg_orders [color: #79AD51]
}
```

## Uniqueness

Each directed edge must be unique. Declaring the same edge twice — whether in short form, block form, or inline form — reports the error *"Dep with same endpoints already exists"*:

```text
// error: the same edge is declared twice
Dep: a -> b
Dep: a -> b
```

Uniqueness is checked per direction, so the **reversed** pair is a different edge and is allowed:

```text
// no error: a -> b and b -> a are distinct edges
Dep: a -> b
Dep: b -> a
```

Table-level and column-level edges are also distinct, so `a -> b` and `a.id -> b.id` can both exist.
6 changes: 3 additions & 3 deletions dbml-playground/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dbml/playground",
"version": "8.3.1",
"version": "9.0.0-alpha.3",
"description": "Interactive playground for debugging and visualizing the DBML parser pipeline",
"author": "Holistics <dev@holistics.io>",
"license": "Apache-2.0",
Expand All @@ -25,8 +25,8 @@
"format": "prettier --write src/"
},
"dependencies": {
"@dbml/core": "^8.3.1",
"@dbml/parse": "^8.3.1",
"@dbml/core": "^9.0.0-alpha.3",
"@dbml/parse": "^9.0.0-alpha.3",
"@phosphor-icons/vue": "^2.2.0",
"floating-vue": "^5.2.2",
"lodash-es": "^4.17.21",
Expand Down
45 changes: 44 additions & 1 deletion dbml-playground/src/components/panes/output/tabs/DatabaseTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,40 @@
</div>
</DbSection>

<!-- Deps -->
<DbSection
v-if="database.deps?.length"
label="Deps"
:count="database.deps.length"
:icon="PhArrowRight"
icon-color="text-orange-500"
>
<template
v-for="(dep, di) in database.deps"
:key="di"
>
<div
v-for="(edge, ei) in dep.edges"
:key="`${di}-${ei}`"
class="flex items-center gap-2 py-1 border-b border-gray-50 hover:bg-blue-50"
:style="{ paddingLeft: '20px', paddingRight: '12px' }"
>
<VTooltip
placement="right"
:distance="6"
>
<PhArrowRight class="w-3.5 h-3.5 text-orange-500 flex-shrink-0" />
<template #popper>
<span class="text-xs">Dep</span>
</template>
</VTooltip>
<span class="text-blue-500">{{ depEndpointLabel(edge.upstream) }}</span>
<span class="text-gray-400">→</span>
<span class="text-blue-500">{{ depEndpointLabel(edge.downstream) }}</span>
</div>
</template>
</DbSection>

<!-- Enums -->
<DbSection
label="Enums"
Expand Down Expand Up @@ -541,6 +575,7 @@ import {
PhListBullets,
PhArrowsLeftRight,
PhArrowsDownUp,
PhArrowRight,
PhTextAa,
PhNumberSquareOne,
PhFolder,
Expand All @@ -557,6 +592,7 @@ import TabSettingsButton from './common/TabSettingsButton.vue';
import type { Database } from '@dbml/parse';

type RefEndpoint = Database['refs'][number]['endpoints'][number];
type DepEndpoint = NonNullable<Database['deps']>[number]['edges'][number]['upstream'];
type IndexEntry = Database['tables'][number]['indexes'][number];

import DbSection from './common/DbSection.vue';
Expand Down Expand Up @@ -594,7 +630,7 @@ const totalCount = computed(() => {
const db = database;
if (!db) return 0;
const indexCount = db.tables.reduce((n, t) => n + t.indexes.length, 0);
return db.tables.length + indexCount + db.refs.length + db.enums.length
return db.tables.length + indexCount + db.refs.length + (db.deps?.length ?? 0) + db.enums.length
+ db.tableGroups.length + (db.records?.length ?? 0) + (db.tablePartials?.length ?? 0)
+ (db.notes?.length ?? 0) + (db.diagramViews?.length ?? 0) + externalsCount.value;
});
Expand Down Expand Up @@ -720,4 +756,11 @@ function endpointLabel (ep: RefEndpoint): string {
const fields = ep.fieldNames.length === 1 ? ep.fieldNames[0] : `(${ep.fieldNames.join(', ')})`;
return ep.schemaName ? `${ep.schemaName}.${ep.tableName}.${fields}` : `${ep.tableName}.${fields}`;
}

function depEndpointLabel (ep: DepEndpoint): string {
const base = ep.schemaName ? `${ep.schemaName}.${ep.tableName}` : ep.tableName;
if (ep.fieldNames.length === 0) return base;
const fields = ep.fieldNames.length === 1 ? ep.fieldNames[0] : `(${ep.fieldNames.join(', ')})`;
return `${base}.${fields}`;
}
</script>
50 changes: 50 additions & 0 deletions dbml-playground/src/services/sample-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,62 @@ export interface SampleCategory {
readonly content: string;
}

export const DATA_LINEAGE_SAMPLE_CONTENT = `Table raw_orders {
id int [pk]
user_id int
amount decimal
created_at timestamp
}

Table stg_orders {
id int [pk]
user_id int
amount decimal
}

Table fct_orders {
id int [pk]
user_id int
revenue decimal
}

// Short form
Dep: raw_orders -> stg_orders

// Long form with custom attrs
Dep {
stg_orders -> fct_orders

note: 'Aggregate staging orders into facts'
materialized: table
owner: 'data-team'
}

// Reverse direction (sugar — same edge as stg_orders -> fct_orders)
Dep: fct_orders <- stg_orders

// Column-level
Dep {
stg_orders.amount -> fct_orders.revenue
}

// Inline on table header
Table mart_orders [dep: <- fct_orders] {
id int [pk]
total decimal
}`;

export const SAMPLE_CATEGORIES: readonly SampleCategory[] = [
{
name: 'Basic Example',
description: 'Simple tables with relationships',
content: DEFAULT_SAMPLE_CONTENT,
},
{
name: 'Data Lineage',
description: 'Dep blocks (data flow) — short, long, reverse, column-level, inline',
content: DATA_LINEAGE_SAMPLE_CONTENT,
},
{
name: 'E-commerce Schema',
description: 'Complete e-commerce database with users, products, and orders',
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "8.3.1",
"version": "9.0.0-alpha.3",
"npmClient": "yarn",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
8 changes: 4 additions & 4 deletions packages/dbml-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "@dbml/cli",
"version": "8.3.1",
"version": "9.0.0-alpha.3",
"description": "",
"main": "lib/index.js",
"license": "Apache-2.0",
Expand Down Expand Up @@ -32,9 +32,9 @@
],
"dependencies": {
"@babel/cli": "^7.21.0",
"@dbml/connector": "^8.3.1",
"@dbml/core": "^8.3.1",
"@dbml/parse": "^8.3.1",
"@dbml/connector": "^9.0.0-alpha.3",
"@dbml/core": "^9.0.0-alpha.3",
"@dbml/parse": "^9.0.0-alpha.3",
"bluebird": "^3.5.5",
"chalk": "^2.4.2",
"commander": "^2.20.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/dbml-connector/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "@dbml/connector",
"version": "8.3.1",
"version": "9.0.0-alpha.3",
"description": "This package was created to fetch the schema JSON from many kind of databases.",
"author": "huy.phung.sw@gmail.com",
"license": "MIT",
Expand Down
Loading
Loading