diff --git a/dbml-homepage/docs/docs.md b/dbml-homepage/docs/docs.md index b5717a861..6d834a45c 100644 --- a/dbml-homepage/docs/docs.md +++ b/dbml-homepage/docs/docs.md @@ -25,6 +25,7 @@ This part covers all constructs that define database structure and map directly - [TablePartial](#tablepartial) - [Data Sample](#data-sample) - [Data Types](#data-types) +- [Data Lineage](#data-lineage) ## Project Definition @@ -534,3 +535,68 @@ records users(id, name, age, status, created_at) { 3, 'Charlie', , Status.pending, '2024-01-15' } ``` + +## Data Lineage + +`Dep` lets you describe **data dependency** among tables and their columns. It allows you to model: Which tables (or columns) this table (or column) is computed from? Think of it like a SQL view where the view is computed from one or more source tables (or other views). + +A **dependency** syntax consists of 3 parts: +- The **upstream** endpoint: The source table (or column) that data come from +- The **downstream** endpoint: The destination table (or column) where the source data are processed and computed to derive a new value +- An arrow (`<-` or `->`) pointing from the **upstream** endpoint to the **downstream** endpoint + +A **dependency** can be categorized into one of the two types: +- Table-level dependency: Both the upstream endpoint and the downstream endpoint are tables. +- Column-level dependency: Both the upstream endpoint and the downstream endpoint are columns. + +Note that both sides of an edge must be at the same level (table-to-table or column-to-column). + +```text +/* Short form — separate from the table or column*/ + +// `raw_orders` is the upstream table endpoint +// `stg_orders` is the downstream table endpoint +// `->` and `<-` always point from the upstream to the downstream +Dep: raw_orders -> stg_orders +Dep: stg_orders <- raw_orders + +// Column-level dependency +Dep: raw_orders.amount -> stg_orders.revenue // column-level + +/* Short form — on a table header or column */ +Table fct_orders [dep: <- stg_orders] { + id int + revenue decimal [dep: <- stg_orders.amount] +} +``` + +### Dependency Block + +When a table is produced by a specific transformation step (e.g. a dbt model, a SQL view, or an ETL job), you can express this transformation using a dependency block: + +``` +/* Block form — group edges for a transformation step */ +Dep order_staging [color: #79AD51] { + raw_orders -> stg_orders + raw_payments -> stg_orders + raw_orders.amount -> stg_orders.revenue + + note: 'Join orders with payments, compute revenue' + materialized: table + query: ''' + Transformation query + ''' + owner: 'data-team' +} +``` + +Block settings: + +- `note`: description of the transformation. Supports [multi-line strings](./syntax/language-basics.md#multi-line-string). +- `color`: lineage line color. See [Colors](./syntax/enrichment-visualization.md#colors). +- Custom keys (e.g. `materialized`, `owner`) are preserved in the output. + +:::note +- All edges in a block must target the **same downstream table**. +- Each directed edge must be unique. Reversed pairs and different levels are considered distinct. +::: diff --git a/dbml-homepage/static/llms.txt b/dbml-homepage/static/llms.txt index 0a36bb353..e6f7b41ee 100644 --- a/dbml-homepage/static/llms.txt +++ b/dbml-homepage/static/llms.txt @@ -7,7 +7,7 @@ For the dbdiagram.io web app (diagrams, collaboration, API), see [dbdiagram docs ## Language and reference - [Home](https://dbml.dbdiagram.io/home): Overview, ecosystem, and links to tools. -- [Core database markup](https://dbml.dbdiagram.io/docs): Tables, columns, relationships, indexes, enums, and other core syntax. +- [Core database markup](https://dbml.dbdiagram.io/docs): Tables, columns, relationships (including optional and inactive refs), indexes, enums, records (sample data), data dependencies (Dep), and other core syntax. - [Enrichment and visualization](https://dbml.dbdiagram.io/syntax/enrichment-visualization): Notes, labels, colors, and display-oriented syntax like table groups, diagram views. - [Language basics](https://dbml.dbdiagram.io/syntax/language-basics): Core language constructs (strings, comments, and syntax conventions). diff --git a/dbml-playground/package.json b/dbml-playground/package.json index a12869051..43ec249b6 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,6 +1,6 @@ { "name": "@dbml/playground", - "version": "8.4.0-alpha.1", + "version": "9.0.0-alpha.21", "description": "Interactive playground for debugging and visualizing the DBML parser pipeline", "author": "Holistics ", "license": "Apache-2.0", @@ -25,8 +25,8 @@ "format": "prettier --write src/" }, "dependencies": { - "@dbml/core": "^8.4.0-alpha.1", - "@dbml/parse": "^8.4.0-alpha.1", + "@dbml/core": "^9.0.0-alpha.21", + "@dbml/parse": "^9.0.0-alpha.21", "@phosphor-icons/vue": "^2.2.0", "floating-vue": "^5.2.2", "lodash-es": "^4.17.21", diff --git a/dbml-playground/src/components/panes/output/tabs/DatabaseTab.vue b/dbml-playground/src/components/panes/output/tabs/DatabaseTab.vue index 17a53f16b..c9fc60306 100644 --- a/dbml-playground/src/components/panes/output/tabs/DatabaseTab.vue +++ b/dbml-playground/src/components/panes/output/tabs/DatabaseTab.vue @@ -234,6 +234,39 @@ + + + + + [number]['edges'][number]['upstream']; type IndexEntry = Database['tables'][number]['indexes'][number]; import DbSection from './common/DbSection.vue'; @@ -594,7 +629,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; }); @@ -720,4 +755,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}`; +} diff --git a/dbml-playground/src/services/sample-content.ts b/dbml-playground/src/services/sample-content.ts index 82a80ceaf..3a890f609 100644 --- a/dbml-playground/src/services/sample-content.ts +++ b/dbml-playground/src/services/sample-content.ts @@ -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', diff --git a/lerna.json b/lerna.json index e0f90de42..217cafafc 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "8.4.0-alpha.1", + "version": "9.0.0-alpha.21", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/packages/dbml-cli/package.json b/packages/dbml-cli/package.json index f3969faed..c2e2ce8cc 100644 --- a/packages/dbml-cli/package.json +++ b/packages/dbml-cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/cli", - "version": "8.4.0-alpha.1", + "version": "9.0.0-alpha.21", "description": "", "main": "lib/index.js", "license": "Apache-2.0", @@ -32,9 +32,9 @@ ], "dependencies": { "@babel/cli": "^7.21.0", - "@dbml/connector": "^8.4.0-alpha.1", - "@dbml/core": "^8.4.0-alpha.1", - "@dbml/parse": "^8.4.0-alpha.1", + "@dbml/connector": "^9.0.0-alpha.21", + "@dbml/core": "^9.0.0-alpha.21", + "@dbml/parse": "^9.0.0-alpha.21", "bluebird": "^3.5.5", "chalk": "^2.4.2", "commander": "^2.20.0", diff --git a/packages/dbml-connector/package.json b/packages/dbml-connector/package.json index 6fa7c88b8..ec7059d63 100644 --- a/packages/dbml-connector/package.json +++ b/packages/dbml-connector/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/connector", - "version": "8.4.0-alpha.1", + "version": "9.0.0-alpha.21", "description": "This package was created to fetch the schema JSON from many kind of databases.", "author": "huy.phung.sw@gmail.com", "license": "MIT", diff --git a/packages/dbml-core/__tests__/examples/model_exporter/dbml_exporter/input/dep.in.json b/packages/dbml-core/__tests__/examples/model_exporter/dbml_exporter/input/dep.in.json new file mode 100644 index 000000000..a2e005f78 --- /dev/null +++ b/packages/dbml-core/__tests__/examples/model_exporter/dbml_exporter/input/dep.in.json @@ -0,0 +1,46 @@ +{ + "tables": [ + { + "name": "raw_orders", + "fields": [ + { "name": "id", "type": { "type_name": "int" }, "pk": true } + ] + }, + { + "name": "stg_orders", + "fields": [ + { "name": "id", "type": { "type_name": "int" }, "pk": true } + ] + }, + { + "name": "fct_orders", + "fields": [ + { "name": "id", "type": { "type_name": "int" }, "pk": true }, + { "name": "amount", "type": { "type_name": "decimal" } } + ] + } + ], + "refs": [], + "enums": [], + "tableGroups": [], + "notes": [], + "records": [], + "tablePartials": [], + "deps": [ + { + "schemaName": "public", + "edges": [ + { "upstream": { "tableName": "raw_orders", "fieldNames": [] }, "downstream": { "tableName": "stg_orders", "fieldNames": [] } } + ] + }, + { + "schemaName": "public", + "note": { "value": "Aggregate staging into facts" }, + "metadata": { "materialized": "table", "owner": "data-team", "priority": 1 }, + "edges": [ + { "upstream": { "tableName": "stg_orders", "fieldNames": [] }, "downstream": { "tableName": "fct_orders", "fieldNames": [] } }, + { "upstream": { "tableName": "stg_orders", "fieldNames": ["id"] }, "downstream": { "tableName": "fct_orders", "fieldNames": ["amount"] } } + ] + } + ] +} diff --git a/packages/dbml-core/__tests__/examples/model_exporter/dbml_exporter/output/dep.out.dbml b/packages/dbml-core/__tests__/examples/model_exporter/dbml_exporter/output/dep.out.dbml new file mode 100644 index 000000000..3988cf51c --- /dev/null +++ b/packages/dbml-core/__tests__/examples/model_exporter/dbml_exporter/output/dep.out.dbml @@ -0,0 +1,24 @@ +Table "raw_orders" { + "id" int [pk] +} + +Table "stg_orders" { + "id" int [pk] +} + +Table "fct_orders" { + "id" int [pk] + "amount" decimal +} + +Dep: "raw_orders" -> "stg_orders" + +Dep { + "stg_orders" -> "fct_orders" + "stg_orders"."id" -> "fct_orders"."amount" + + note: 'Aggregate staging into facts' + materialized: 'table' + owner: 'data-team' + priority: 1 +} diff --git a/packages/dbml-core/__tests__/examples/model_structure/dep.json b/packages/dbml-core/__tests__/examples/model_structure/dep.json new file mode 100644 index 000000000..cbb76bd3d --- /dev/null +++ b/packages/dbml-core/__tests__/examples/model_structure/dep.json @@ -0,0 +1,43 @@ +{ + "schemas": [], + "aliases": [], + "project": { "database_type": "Lineage", "name": "lineage_test" }, + "tables": [ + { "name": "raw_orders", "schemaName": "public", "fields": [{ "name": "id", "type": { "type_name": "int" }, "pk": true }] }, + { "name": "stg_orders", "schemaName": "public", "fields": [{ "name": "id", "type": { "type_name": "int" }, "pk": true }] }, + { "name": "fct_orders", "schemaName": "public", "fields": [{ "name": "id", "type": { "type_name": "int" }, "pk": true }, { "name": "amount", "type": { "type_name": "decimal" } }] }, + { "name": "events", "schemaName": "analytics", "fields": [{ "name": "id", "type": { "type_name": "int" }, "pk": true }, { "name": "ts", "type": { "type_name": "timestamp" } }] } + ], + "refs": [], + "enums": [], + "tableGroups": [], + "notes": [], + "records": [], + "tablePartials": [], + "deps": [ + { + "name": null, + "schemaName": "public", + "edges": [ + { "upstream": { "schemaName": null, "tableName": "raw_orders", "fieldNames": [] }, "downstream": { "schemaName": null, "tableName": "stg_orders", "fieldNames": [] } } + ] + }, + { + "name": null, + "schemaName": "public", + "note": { "value": "Aggregate staging into facts" }, + "metadata": { "materialized": "table", "owner": "data-team" }, + "edges": [ + { "upstream": { "schemaName": null, "tableName": "stg_orders", "fieldNames": [] }, "downstream": { "schemaName": null, "tableName": "fct_orders", "fieldNames": [] } }, + { "upstream": { "schemaName": null, "tableName": "stg_orders", "fieldNames": ["id"] }, "downstream": { "schemaName": null, "tableName": "fct_orders", "fieldNames": ["amount"] } } + ] + }, + { + "name": null, + "schemaName": "public", + "edges": [ + { "upstream": { "schemaName": "analytics", "tableName": "events", "fieldNames": ["ts"] }, "downstream": { "schemaName": "public", "tableName": "fct_orders", "fieldNames": ["id"] } } + ] + } + ] +} diff --git a/packages/dbml-core/__tests__/examples/model_structure/dep.spec.ts b/packages/dbml-core/__tests__/examples/model_structure/dep.spec.ts new file mode 100644 index 000000000..a363f830a --- /dev/null +++ b/packages/dbml-core/__tests__/examples/model_structure/dep.spec.ts @@ -0,0 +1,143 @@ +import Database from '../../../src/model_structure/database'; +import jsonDb from './dep.json'; +import { test, expect, describe, beforeAll } from 'vitest'; + +describe('@dbml/core - Dep model', () => { + let database: Database; + beforeAll(() => { + database = new Database(jsonDb as any); + }); + + describe('schema.deps', () => { + test('public schema has 3 deps', () => { + const pub = database.schemas.find((s) => s.name === 'public')!; + expect(pub.deps).toHaveLength(3); + }); + }); + + describe('Dep #1 — bare-table endpoints', () => { + test('1 edge with empty fieldNames on both endpoints', () => { + const dep = database.schemas[0].deps[0]; + expect(dep.edges).toHaveLength(1); + expect(dep.edges[0].upstream.tableName).toBe('raw_orders'); + expect(dep.edges[0].upstream.fieldNames).toEqual([]); + expect(dep.edges[0].downstream.tableName).toBe('stg_orders'); + expect(dep.edges[0].downstream.fieldNames).toEqual([]); + }); + + test('no note, no custom', () => { + const dep = database.schemas[0].deps[0]; + expect(dep.note).toBeNull(); + expect(dep.metadata).toBeNull(); + }); + }); + + describe('Dep #2 — multi-edge with note + custom', () => { + test('2 edges; first is bare-bare, second is column-level', () => { + const dep = database.schemas[0].deps[1]; + expect(dep.edges).toHaveLength(2); + expect(dep.edges[0].upstream.fieldNames).toEqual([]); + expect(dep.edges[0].downstream.fieldNames).toEqual([]); + expect(dep.edges[1].upstream.fieldNames).toEqual(['id']); + expect(dep.edges[1].downstream.fieldNames).toEqual(['amount']); + }); + + test('note + custom carried correctly', () => { + const dep = database.schemas[0].deps[1]; + expect(dep.note).toBe('Aggregate staging into facts'); + expect(dep.metadata).toEqual({ materialized: 'table', owner: 'data-team' }); + }); + }); + + describe('Dep #3 — schema-qualified, cross-schema', () => { + test('upstream is analytics.events.ts, downstream is public.fct_orders.id', () => { + const dep = database.schemas[0].deps[2]; + expect(dep.edges).toHaveLength(1); + const edge = dep.edges[0]; + expect(edge.upstream.schemaName).toBe('analytics'); + expect(edge.upstream.tableName).toBe('events'); + expect(edge.upstream.fieldNames).toEqual(['ts']); + expect(edge.downstream.schemaName).toBe('public'); + expect(edge.downstream.tableName).toBe('fct_orders'); + expect(edge.downstream.fieldNames).toEqual(['id']); + }); + }); + + describe('DepEdge — table + field resolution', () => { + test('bare-table edge resolves upstreamTable / downstreamTable, empty fields arrays', () => { + const edge = database.schemas[0].deps[0].edges[0]; + expect(edge.upstreamTable?.name).toBe('raw_orders'); + expect(edge.downstreamTable?.name).toBe('stg_orders'); + expect(edge.upstreamFields).toEqual([]); + expect(edge.downstreamFields).toEqual([]); + }); + + test('column-level edge resolves to actual Field instances', () => { + const edge = database.schemas[0].deps[1].edges[1]; + expect(edge.upstreamFields).toHaveLength(1); + expect(edge.upstreamFields[0].name).toBe('id'); + expect(edge.downstreamFields).toHaveLength(1); + expect(edge.downstreamFields[0].name).toBe('amount'); + }); + + test('schema-qualified edge resolves table from non-public schema', () => { + const edge = database.schemas[0].deps[2].edges[0]; + const analyticsSchema = database.schemas.find((s) => s.name === 'analytics'); + const eventsTable = analyticsSchema?.tables.find((t) => t.name === 'events'); + expect(edge.upstreamTable).toBe(eventsTable); + expect(edge.upstreamFields[0].name).toBe('ts'); + }); + }); + + describe('normalize', () => { + test('model.deps populated with all 3 Dep records', () => { + const model = database.normalize(); + const deps = Object.values(model.deps); + expect(deps).toHaveLength(3); + }); + + test('each Dep record has a numeric id and edgeIds pointing to existing depEdges', () => { + const model = database.normalize(); + const deps = Object.values(model.deps); + deps.forEach((dep: any) => { + expect(typeof dep.id).toBe('number'); + expect(dep.edgeIds).toBeDefined(); + expect(dep.edgeIds.length).toBeGreaterThan(0); + dep.edgeIds.forEach((eid: number) => { + expect(typeof eid).toBe('number'); + expect(model.depEdges[eid]).toBeDefined(); + }); + }); + }); + + test('depEdge carries full upstream + downstream data (tableName, schemaName, fieldNames)', () => { + const model = database.normalize(); + const dep3 = Object.values(model.deps).find((d: any) => d.edgeIds.length === 1 && model.depEdges[d.edgeIds[0]].upstream.schemaName === 'analytics') as any; + expect(dep3).toBeDefined(); + const edge = model.depEdges[dep3.edgeIds[0]]; + expect(edge.upstream.schemaName).toBe('analytics'); + expect(edge.upstream.tableName).toBe('events'); + expect(edge.upstream.fieldNames).toEqual(['ts']); + expect(edge.downstream.schemaName).toBe('public'); + expect(edge.downstream.tableName).toBe('fct_orders'); + expect(edge.downstream.fieldNames).toEqual(['id']); + }); + + test('normalize preserves note + metadata on the dep record', () => { + const model = database.normalize(); + const depWithMetadata = Object.values(model.deps).find((d: any) => d.metadata) as any; + expect(depWithMetadata).toBeDefined(); + expect(depWithMetadata.note).toBe('Aggregate staging into facts'); + expect(depWithMetadata.metadata).toEqual({ materialized: 'table', owner: 'data-team' }); + }); + + test('depEdge.depId points back to its parent dep', () => { + const model = database.normalize(); + Object.values(model.deps).forEach((dep: any) => { + dep.edgeIds.forEach((eid: number) => { + expect(model.depEdges[eid].depId).toBe(dep.id); + }); + }); + }); + }); +}); diff --git a/packages/dbml-core/__tests__/examples/model_structure/dep_color.spec.ts b/packages/dbml-core/__tests__/examples/model_structure/dep_color.spec.ts new file mode 100644 index 000000000..64e8fc9ee --- /dev/null +++ b/packages/dbml-core/__tests__/examples/model_structure/dep_color.spec.ts @@ -0,0 +1,60 @@ +import Parser from '../../../src/parse/Parser'; +import { test, describe, expect } from 'vitest'; + +const dbml = ` +Table users { id int } +Table orders { id int } +Table events { id int } +Table reports { id int } + +// header attribute form +Dep dep_header [color: #aabbcc] { + users -> orders +} + +// body sub-declaration form +Dep dep_body { + orders -> events + color: #123456 +} + +// short / inline-setting form +Dep: users -> reports [color: #abcdef] + +// uncolored +Dep: events -> users +`; + +describe('@dbml/core - Dep color (first-class setting)', () => { + const database = (new Parser()).parse(dbml, 'dbmlv2'); + const deps = database.schemas[0].deps; + + const byName = (name: string) => deps.find((d) => d.name === name)!; + const byEdge = (up: string, down: string) => + deps.find((d) => d.edges[0].upstream.tableName === up && d.edges[0].downstream.tableName === down)!; + + test('[color] in the header attribute list parses into dep.color', () => { + expect(byName('dep_header').color).toBe('#aabbcc'); + }); + + test('color in a body sub-declaration parses into dep.color', () => { + expect(byName('dep_body').color).toBe('#123456'); + }); + + test('[color] on a short-form Dep parses into dep.color', () => { + expect(byEdge('users', 'reports').color).toBe('#abcdef'); + }); + + test('an uncolored Dep has no color', () => { + expect(byEdge('events', 'users').color).toBeUndefined(); + }); + + test('color appears on the normalized dep', () => { + const model = database.normalize(); + const colors = Object.values(model.deps) + .map((d: any) => d.color) + .filter(Boolean) + .sort(); + expect(colors).toEqual(['#123456', '#aabbcc', '#abcdef']); + }); +}); diff --git a/packages/dbml-core/__tests__/examples/model_structure/dep_self_loop.spec.ts b/packages/dbml-core/__tests__/examples/model_structure/dep_self_loop.spec.ts new file mode 100644 index 000000000..c2f86355f --- /dev/null +++ b/packages/dbml-core/__tests__/examples/model_structure/dep_self_loop.spec.ts @@ -0,0 +1,57 @@ +import Database from '../../../src/model_structure/database'; +import { test, expect, describe } from 'vitest'; + +// Self-reference rule: a table may depend on itself (A -> A), a column may feed +// another column of its table (a.x -> a.y) or its own table (a.x -> a); +// only a column depending on itself (a.x -> a.x) is an error. +const rawDb = (edges: { up: string[]; down: string[] }[]) => ({ + schemas: [], + aliases: [], + tables: [ + { + name: 'a', + schemaName: 'public', + fields: [ + { name: 'x', type: { type_name: 'int' } }, + { name: 'y', type: { type_name: 'int' } }, + ], + }, + ], + refs: [], + enums: [], + tableGroups: [], + notes: [], + records: [], + tablePartials: [], + deps: [ + { + name: null, + schemaName: 'public', + edges: edges.map(({ up, down }) => ({ + upstream: { schemaName: null, tableName: 'a', fieldNames: up }, + downstream: { schemaName: null, tableName: 'a', fieldNames: down }, + })), + }, + ], +}); + +describe('@dbml/core - Dep self-reference rule', () => { + test('a table may depend on itself (A -> A)', () => { + const database = new Database(rawDb([{ up: [], down: [] }]) as any); + expect(database.schemas[0].deps[0].edges).toHaveLength(1); + }); + + test('a column may feed another column of the same table (a.x -> a.y)', () => { + const database = new Database(rawDb([{ up: ['x'], down: ['y'] }]) as any); + expect(database.schemas[0].deps[0].edges).toHaveLength(1); + }); + + test('a column may feed its own table (a.x -> a)', () => { + const database = new Database(rawDb([{ up: ['x'], down: [] }]) as any); + expect(database.schemas[0].deps[0].edges).toHaveLength(1); + }); + + test('a column cannot depend on itself (a.x -> a.x)', () => { + expect(() => new Database(rawDb([{ up: ['x'], down: ['x'] }]) as any)).toThrow(/Self-loop/); + }); +}); diff --git a/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.in.dbml b/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.in.dbml new file mode 100644 index 000000000..3486ba0ab --- /dev/null +++ b/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.in.dbml @@ -0,0 +1,149 @@ +// ========================================== +// E-Commerce Data Pipeline Example +// Demonstrates Dep (dependency) blocks for data lineage +// ========================================== + +// ------------------------------------------ +// Source Layer (Raw Data) +// ------------------------------------------ +TableGroup raw_layer { + raw_users + raw_orders + raw_order_items +} + +Table raw_users { + id int [pk] + email varchar + name varchar + created_at timestamp + raw_data json +} + +Table raw_orders { + id int [pk] + user_id int + ordered_at timestamp + status varchar + raw_data json +} + +Table raw_order_items { + id int [pk] + order_id int + product_id int + quantity int + price decimal +} + +// ------------------------------------------ +// Staging Layer (Cleaned Data) +// ------------------------------------------ +TableGroup staging_layer { + stg_users + stg_orders + stg_order_items +} + +Table stg_users { + user_id int [pk] + email varchar + name varchar + created_at timestamp +} + +Table stg_orders { + order_id int [pk] + user_id int + ordered_at timestamp + status varchar +} + +Table stg_order_items { + order_item_id int [pk] + order_id int + product_id int + quantity int + unit_price decimal + line_total decimal +} + +// ------------------------------------------ +// Domain/Mart Layer (Business Models) +// ------------------------------------------ +TableGroup mart_layer { + dim_users + fct_orders +} + +Table dim_users { + user_id int [pk] + email varchar + name varchar + first_order_at timestamp + total_orders int + total_spent decimal + created_at timestamp +} + +Table fct_orders { + order_id int [pk] + user_id int + ordered_at timestamp + status varchar + total_items int + order_total decimal +} + +// ------------------------------------------ +// Foreign Key References (Logical) +// ------------------------------------------ +Ref: raw_orders.user_id > raw_users.id +Ref: raw_order_items.order_id > raw_orders.id +Ref: stg_orders.user_id > stg_users.user_id +Ref: stg_order_items.order_id > stg_orders.order_id +Ref: fct_orders.user_id > dim_users.user_id + +// ------------------------------------------ +// Data Dependencies (Lineage) +// ------------------------------------------ + +// Column-level dependencies (detailed lineage) +// Raw -> Staging dependencies +Dep: raw_users.id -> stg_users.user_id [note: 'Cast + rename: raw id → canonical user_id'] +Dep: raw_users.email -> stg_users.email [note: 'Lowercase + validate email'] +Dep: raw_users.name -> stg_users.name [note: 'Trim + normalize name'] +Dep: raw_users.created_at -> stg_users.created_at [note: 'Parse timestamp (UTC)'] + +Dep: raw_orders.id -> stg_orders.order_id [note: 'Rename: id → order_id'] +Dep: raw_orders.user_id -> stg_orders.user_id [note: 'Join key to users dimension'] +Dep: raw_orders.ordered_at -> stg_orders.ordered_at [note: 'Parse + standardize order timestamp'] +Dep: raw_orders.status -> stg_orders.status [note: 'Map raw status codes → business statuses'] + +Dep: raw_order_items.id -> stg_order_items.order_item_id +Dep: raw_order_items.order_id -> stg_order_items.order_id +Dep: raw_order_items.product_id -> stg_order_items.product_id +Dep: raw_order_items.quantity -> stg_order_items.quantity +Dep: raw_order_items.price -> stg_order_items.unit_price [note: 'Rename: price → unit_price'] + +// Staging -> Mart dependencies +Dep: stg_users.user_id -> dim_users.user_id +Dep: stg_users.email -> dim_users.email +Dep: stg_users.name -> dim_users.name +Dep: stg_users.created_at -> dim_users.created_at +Dep: stg_orders.ordered_at -> dim_users.first_order_at [note: 'Aggregation: MIN(ordered_at) by user'] +Dep: stg_orders.order_id -> dim_users.total_orders [note: 'Aggregation: COUNT(order_id) by user'] + +Dep: stg_orders.order_id -> fct_orders.order_id +Dep: stg_orders.user_id -> fct_orders.user_id +Dep: stg_orders.ordered_at -> fct_orders.ordered_at +Dep: stg_orders.status -> fct_orders.status +Dep: stg_order_items.quantity -> fct_orders.total_items [note: 'Aggregation: SUM(quantity) by order_id'] +Dep: stg_order_items.line_total -> fct_orders.order_total [note: 'Aggregation: SUM(line_total) by order_id'] + +Dep: raw_users -> stg_users [note: 'Raw data → Staging data'] +Dep: raw_orders -> stg_orders [note: 'Raw data → Staging data'] +Dep: raw_order_items -> stg_order_items [note: 'Raw data → Staging data'] +Dep: stg_users -> dim_users [note: 'Staging data → Domain data'] +Dep: stg_orders -> fct_orders [note: 'Staging data → Domain data'] +Dep: stg_order_items -> fct_orders [note: 'Staging data → Domain data'] \ No newline at end of file diff --git a/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.out.json b/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.out.json new file mode 100644 index 000000000..bacf90e79 --- /dev/null +++ b/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.out.json @@ -0,0 +1,2393 @@ +{ + "database": { + "1": { + "id": 1, + "hasDefaultSchema": false, + "note": null, + "schemaIds": [ + 1 + ], + "noteIds": [] + } + }, + "schemas": { + "1": { + "id": 1, + "name": "public", + "note": "Default Public Schema", + "tableIds": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "enumIds": [], + "tableGroupIds": [ + 1, + 2, + 3 + ], + "refIds": [ + 1, + 2, + 3, + 4, + 5 + ], + "depIds": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31 + ], + "databaseId": 1 + } + }, + "notes": {}, + "refs": { + "1": { + "id": 1, + "name": null, + "endpointIds": [ + 1, + 2 + ], + "schemaId": 1 + }, + "2": { + "id": 2, + "name": null, + "endpointIds": [ + 3, + 4 + ], + "schemaId": 1 + }, + "3": { + "id": 3, + "name": null, + "endpointIds": [ + 5, + 6 + ], + "schemaId": 1 + }, + "4": { + "id": 4, + "name": null, + "endpointIds": [ + 7, + 8 + ], + "schemaId": 1 + }, + "5": { + "id": 5, + "name": null, + "endpointIds": [ + 9, + 10 + ], + "schemaId": 1 + } + }, + "deps": { + "1": { + "id": 1, + "name": null, + "note": "Cast + rename: raw id → canonical user_id", + "metadata": null, + "edgeIds": [ + 1 + ], + "schemaId": 1 + }, + "2": { + "id": 2, + "name": null, + "note": "Lowercase + validate email", + "metadata": null, + "edgeIds": [ + 2 + ], + "schemaId": 1 + }, + "3": { + "id": 3, + "name": null, + "note": "Trim + normalize name", + "metadata": null, + "edgeIds": [ + 3 + ], + "schemaId": 1 + }, + "4": { + "id": 4, + "name": null, + "note": "Parse timestamp (UTC)", + "metadata": null, + "edgeIds": [ + 4 + ], + "schemaId": 1 + }, + "5": { + "id": 5, + "name": null, + "note": "Rename: id → order_id", + "metadata": null, + "edgeIds": [ + 5 + ], + "schemaId": 1 + }, + "6": { + "id": 6, + "name": null, + "note": "Join key to users dimension", + "metadata": null, + "edgeIds": [ + 6 + ], + "schemaId": 1 + }, + "7": { + "id": 7, + "name": null, + "note": "Parse + standardize order timestamp", + "metadata": null, + "edgeIds": [ + 7 + ], + "schemaId": 1 + }, + "8": { + "id": 8, + "name": null, + "note": "Map raw status codes → business statuses", + "metadata": null, + "edgeIds": [ + 8 + ], + "schemaId": 1 + }, + "9": { + "id": 9, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 9 + ], + "schemaId": 1 + }, + "10": { + "id": 10, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 10 + ], + "schemaId": 1 + }, + "11": { + "id": 11, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 11 + ], + "schemaId": 1 + }, + "12": { + "id": 12, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 12 + ], + "schemaId": 1 + }, + "13": { + "id": 13, + "name": null, + "note": "Rename: price → unit_price", + "metadata": null, + "edgeIds": [ + 13 + ], + "schemaId": 1 + }, + "14": { + "id": 14, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 14 + ], + "schemaId": 1 + }, + "15": { + "id": 15, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 15 + ], + "schemaId": 1 + }, + "16": { + "id": 16, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 16 + ], + "schemaId": 1 + }, + "17": { + "id": 17, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 17 + ], + "schemaId": 1 + }, + "18": { + "id": 18, + "name": null, + "note": "Aggregation: MIN(ordered_at) by user", + "metadata": null, + "edgeIds": [ + 18 + ], + "schemaId": 1 + }, + "19": { + "id": 19, + "name": null, + "note": "Aggregation: COUNT(order_id) by user", + "metadata": null, + "edgeIds": [ + 19 + ], + "schemaId": 1 + }, + "20": { + "id": 20, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 20 + ], + "schemaId": 1 + }, + "21": { + "id": 21, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 21 + ], + "schemaId": 1 + }, + "22": { + "id": 22, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 22 + ], + "schemaId": 1 + }, + "23": { + "id": 23, + "name": null, + "note": null, + "metadata": null, + "edgeIds": [ + 23 + ], + "schemaId": 1 + }, + "24": { + "id": 24, + "name": null, + "note": "Aggregation: SUM(quantity) by order_id", + "metadata": null, + "edgeIds": [ + 24 + ], + "schemaId": 1 + }, + "25": { + "id": 25, + "name": null, + "note": "Aggregation: SUM(line_total) by order_id", + "metadata": null, + "edgeIds": [ + 25 + ], + "schemaId": 1 + }, + "26": { + "id": 26, + "name": null, + "note": "Raw data → Staging data", + "metadata": null, + "edgeIds": [ + 26 + ], + "schemaId": 1 + }, + "27": { + "id": 27, + "name": null, + "note": "Raw data → Staging data", + "metadata": null, + "edgeIds": [ + 27 + ], + "schemaId": 1 + }, + "28": { + "id": 28, + "name": null, + "note": "Raw data → Staging data", + "metadata": null, + "edgeIds": [ + 28 + ], + "schemaId": 1 + }, + "29": { + "id": 29, + "name": null, + "note": "Staging data → Domain data", + "metadata": null, + "edgeIds": [ + 29 + ], + "schemaId": 1 + }, + "30": { + "id": 30, + "name": null, + "note": "Staging data → Domain data", + "metadata": null, + "edgeIds": [ + 30 + ], + "schemaId": 1 + }, + "31": { + "id": 31, + "name": null, + "note": "Staging data → Domain data", + "metadata": null, + "edgeIds": [ + 31 + ], + "schemaId": 1 + } + }, + "depEdges": { + "1": { + "id": 1, + "upstream": { + "schemaName": null, + "tableName": "raw_users", + "fieldNames": [ + "id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "user_id" + ] + }, + "depId": 1, + "upstreamTableId": 1, + "upstreamFieldIds": [ + 1 + ], + "downstreamTableId": 4, + "downstreamFieldIds": [ + 16 + ] + }, + "2": { + "id": 2, + "upstream": { + "schemaName": null, + "tableName": "raw_users", + "fieldNames": [ + "email" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "email" + ] + }, + "depId": 2, + "upstreamTableId": 1, + "upstreamFieldIds": [ + 2 + ], + "downstreamTableId": 4, + "downstreamFieldIds": [ + 17 + ] + }, + "3": { + "id": 3, + "upstream": { + "schemaName": null, + "tableName": "raw_users", + "fieldNames": [ + "name" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "name" + ] + }, + "depId": 3, + "upstreamTableId": 1, + "upstreamFieldIds": [ + 3 + ], + "downstreamTableId": 4, + "downstreamFieldIds": [ + 18 + ] + }, + "4": { + "id": 4, + "upstream": { + "schemaName": null, + "tableName": "raw_users", + "fieldNames": [ + "created_at" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "created_at" + ] + }, + "depId": 4, + "upstreamTableId": 1, + "upstreamFieldIds": [ + 4 + ], + "downstreamTableId": 4, + "downstreamFieldIds": [ + 19 + ] + }, + "5": { + "id": 5, + "upstream": { + "schemaName": null, + "tableName": "raw_orders", + "fieldNames": [ + "id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "order_id" + ] + }, + "depId": 5, + "upstreamTableId": 2, + "upstreamFieldIds": [ + 6 + ], + "downstreamTableId": 5, + "downstreamFieldIds": [ + 20 + ] + }, + "6": { + "id": 6, + "upstream": { + "schemaName": null, + "tableName": "raw_orders", + "fieldNames": [ + "user_id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "user_id" + ] + }, + "depId": 6, + "upstreamTableId": 2, + "upstreamFieldIds": [ + 7 + ], + "downstreamTableId": 5, + "downstreamFieldIds": [ + 21 + ] + }, + "7": { + "id": 7, + "upstream": { + "schemaName": null, + "tableName": "raw_orders", + "fieldNames": [ + "ordered_at" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "ordered_at" + ] + }, + "depId": 7, + "upstreamTableId": 2, + "upstreamFieldIds": [ + 8 + ], + "downstreamTableId": 5, + "downstreamFieldIds": [ + 22 + ] + }, + "8": { + "id": 8, + "upstream": { + "schemaName": null, + "tableName": "raw_orders", + "fieldNames": [ + "status" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "status" + ] + }, + "depId": 8, + "upstreamTableId": 2, + "upstreamFieldIds": [ + 9 + ], + "downstreamTableId": 5, + "downstreamFieldIds": [ + 23 + ] + }, + "9": { + "id": 9, + "upstream": { + "schemaName": null, + "tableName": "raw_order_items", + "fieldNames": [ + "id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "order_item_id" + ] + }, + "depId": 9, + "upstreamTableId": 3, + "upstreamFieldIds": [ + 11 + ], + "downstreamTableId": 6, + "downstreamFieldIds": [ + 24 + ] + }, + "10": { + "id": 10, + "upstream": { + "schemaName": null, + "tableName": "raw_order_items", + "fieldNames": [ + "order_id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "order_id" + ] + }, + "depId": 10, + "upstreamTableId": 3, + "upstreamFieldIds": [ + 12 + ], + "downstreamTableId": 6, + "downstreamFieldIds": [ + 25 + ] + }, + "11": { + "id": 11, + "upstream": { + "schemaName": null, + "tableName": "raw_order_items", + "fieldNames": [ + "product_id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "product_id" + ] + }, + "depId": 11, + "upstreamTableId": 3, + "upstreamFieldIds": [ + 13 + ], + "downstreamTableId": 6, + "downstreamFieldIds": [ + 26 + ] + }, + "12": { + "id": 12, + "upstream": { + "schemaName": null, + "tableName": "raw_order_items", + "fieldNames": [ + "quantity" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "quantity" + ] + }, + "depId": 12, + "upstreamTableId": 3, + "upstreamFieldIds": [ + 14 + ], + "downstreamTableId": 6, + "downstreamFieldIds": [ + 27 + ] + }, + "13": { + "id": 13, + "upstream": { + "schemaName": null, + "tableName": "raw_order_items", + "fieldNames": [ + "price" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "unit_price" + ] + }, + "depId": 13, + "upstreamTableId": 3, + "upstreamFieldIds": [ + 15 + ], + "downstreamTableId": 6, + "downstreamFieldIds": [ + 28 + ] + }, + "14": { + "id": 14, + "upstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "user_id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [ + "user_id" + ] + }, + "depId": 14, + "upstreamTableId": 4, + "upstreamFieldIds": [ + 16 + ], + "downstreamTableId": 7, + "downstreamFieldIds": [ + 30 + ] + }, + "15": { + "id": 15, + "upstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "email" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [ + "email" + ] + }, + "depId": 15, + "upstreamTableId": 4, + "upstreamFieldIds": [ + 17 + ], + "downstreamTableId": 7, + "downstreamFieldIds": [ + 31 + ] + }, + "16": { + "id": 16, + "upstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "name" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [ + "name" + ] + }, + "depId": 16, + "upstreamTableId": 4, + "upstreamFieldIds": [ + 18 + ], + "downstreamTableId": 7, + "downstreamFieldIds": [ + 32 + ] + }, + "17": { + "id": 17, + "upstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "created_at" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [ + "created_at" + ] + }, + "depId": 17, + "upstreamTableId": 4, + "upstreamFieldIds": [ + 19 + ], + "downstreamTableId": 7, + "downstreamFieldIds": [ + 36 + ] + }, + "18": { + "id": 18, + "upstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "ordered_at" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [ + "first_order_at" + ] + }, + "depId": 18, + "upstreamTableId": 5, + "upstreamFieldIds": [ + 22 + ], + "downstreamTableId": 7, + "downstreamFieldIds": [ + 33 + ] + }, + "19": { + "id": 19, + "upstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "order_id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [ + "total_orders" + ] + }, + "depId": 19, + "upstreamTableId": 5, + "upstreamFieldIds": [ + 20 + ], + "downstreamTableId": 7, + "downstreamFieldIds": [ + 34 + ] + }, + "20": { + "id": 20, + "upstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "order_id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [ + "order_id" + ] + }, + "depId": 20, + "upstreamTableId": 5, + "upstreamFieldIds": [ + 20 + ], + "downstreamTableId": 8, + "downstreamFieldIds": [ + 37 + ] + }, + "21": { + "id": 21, + "upstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "user_id" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [ + "user_id" + ] + }, + "depId": 21, + "upstreamTableId": 5, + "upstreamFieldIds": [ + 21 + ], + "downstreamTableId": 8, + "downstreamFieldIds": [ + 38 + ] + }, + "22": { + "id": 22, + "upstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "ordered_at" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [ + "ordered_at" + ] + }, + "depId": 22, + "upstreamTableId": 5, + "upstreamFieldIds": [ + 22 + ], + "downstreamTableId": 8, + "downstreamFieldIds": [ + 39 + ] + }, + "23": { + "id": 23, + "upstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "status" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [ + "status" + ] + }, + "depId": 23, + "upstreamTableId": 5, + "upstreamFieldIds": [ + 23 + ], + "downstreamTableId": 8, + "downstreamFieldIds": [ + 40 + ] + }, + "24": { + "id": 24, + "upstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "quantity" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [ + "total_items" + ] + }, + "depId": 24, + "upstreamTableId": 6, + "upstreamFieldIds": [ + 27 + ], + "downstreamTableId": 8, + "downstreamFieldIds": [ + 41 + ] + }, + "25": { + "id": 25, + "upstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "line_total" + ] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [ + "order_total" + ] + }, + "depId": 25, + "upstreamTableId": 6, + "upstreamFieldIds": [ + 29 + ], + "downstreamTableId": 8, + "downstreamFieldIds": [ + 42 + ] + }, + "26": { + "id": 26, + "upstream": { + "schemaName": null, + "tableName": "raw_users", + "fieldNames": [] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [] + }, + "depId": 26, + "upstreamTableId": 1, + "upstreamFieldIds": [], + "downstreamTableId": 4, + "downstreamFieldIds": [] + }, + "27": { + "id": 27, + "upstream": { + "schemaName": null, + "tableName": "raw_orders", + "fieldNames": [] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [] + }, + "depId": 27, + "upstreamTableId": 2, + "upstreamFieldIds": [], + "downstreamTableId": 5, + "downstreamFieldIds": [] + }, + "28": { + "id": 28, + "upstream": { + "schemaName": null, + "tableName": "raw_order_items", + "fieldNames": [] + }, + "downstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [] + }, + "depId": 28, + "upstreamTableId": 3, + "upstreamFieldIds": [], + "downstreamTableId": 6, + "downstreamFieldIds": [] + }, + "29": { + "id": 29, + "upstream": { + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [] + }, + "downstream": { + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [] + }, + "depId": 29, + "upstreamTableId": 4, + "upstreamFieldIds": [], + "downstreamTableId": 7, + "downstreamFieldIds": [] + }, + "30": { + "id": 30, + "upstream": { + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [] + }, + "depId": 30, + "upstreamTableId": 5, + "upstreamFieldIds": [], + "downstreamTableId": 8, + "downstreamFieldIds": [] + }, + "31": { + "id": 31, + "upstream": { + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [] + }, + "downstream": { + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [] + }, + "depId": 31, + "upstreamTableId": 6, + "upstreamFieldIds": [], + "downstreamTableId": 8, + "downstreamFieldIds": [] + } + }, + "enums": {}, + "tableGroups": { + "1": { + "id": 1, + "name": "raw_layer", + "note": null, + "tableIds": [ + 1, + 2, + 3 + ], + "schemaId": 1 + }, + "2": { + "id": 2, + "name": "staging_layer", + "note": null, + "tableIds": [ + 4, + 5, + 6 + ], + "schemaId": 1 + }, + "3": { + "id": 3, + "name": "mart_layer", + "note": null, + "tableIds": [ + 7, + 8 + ], + "schemaId": 1 + } + }, + "tables": { + "1": { + "id": 1, + "name": "raw_users", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 1, + 2, + 3, + 4, + 5 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 1 + }, + "2": { + "id": 2, + "name": "raw_orders", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 6, + 7, + 8, + 9, + 10 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 1 + }, + "3": { + "id": 3, + "name": "raw_order_items", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 11, + 12, + 13, + 14, + 15 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 1 + }, + "4": { + "id": 4, + "name": "stg_users", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 16, + 17, + 18, + 19 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 2 + }, + "5": { + "id": 5, + "name": "stg_orders", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 20, + 21, + 22, + 23 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 2 + }, + "6": { + "id": 6, + "name": "stg_order_items", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 24, + 25, + 26, + 27, + 28, + 29 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 2 + }, + "7": { + "id": 7, + "name": "dim_users", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 30, + 31, + 32, + 33, + 34, + 35, + 36 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 3 + }, + "8": { + "id": 8, + "name": "fct_orders", + "alias": null, + "note": null, + "partials": [], + "recordIds": [], + "fieldIds": [ + 37, + 38, + 39, + 40, + 41, + 42 + ], + "indexIds": [], + "checkIds": [], + "schemaId": 1, + "groupId": 3 + } + }, + "endpoints": { + "1": { + "id": 1, + "schemaName": null, + "tableName": "raw_orders", + "fieldNames": [ + "user_id" + ], + "relation": "*", + "refId": 1, + "fieldIds": [ + 7 + ] + }, + "2": { + "id": 2, + "schemaName": null, + "tableName": "raw_users", + "fieldNames": [ + "id" + ], + "relation": "1", + "refId": 1, + "fieldIds": [ + 1 + ] + }, + "3": { + "id": 3, + "schemaName": null, + "tableName": "raw_order_items", + "fieldNames": [ + "order_id" + ], + "relation": "*", + "refId": 2, + "fieldIds": [ + 12 + ] + }, + "4": { + "id": 4, + "schemaName": null, + "tableName": "raw_orders", + "fieldNames": [ + "id" + ], + "relation": "1", + "refId": 2, + "fieldIds": [ + 6 + ] + }, + "5": { + "id": 5, + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "user_id" + ], + "relation": "*", + "refId": 3, + "fieldIds": [ + 21 + ] + }, + "6": { + "id": 6, + "schemaName": null, + "tableName": "stg_users", + "fieldNames": [ + "user_id" + ], + "relation": "1", + "refId": 3, + "fieldIds": [ + 16 + ] + }, + "7": { + "id": 7, + "schemaName": null, + "tableName": "stg_order_items", + "fieldNames": [ + "order_id" + ], + "relation": "*", + "refId": 4, + "fieldIds": [ + 25 + ] + }, + "8": { + "id": 8, + "schemaName": null, + "tableName": "stg_orders", + "fieldNames": [ + "order_id" + ], + "relation": "1", + "refId": 4, + "fieldIds": [ + 20 + ] + }, + "9": { + "id": 9, + "schemaName": null, + "tableName": "fct_orders", + "fieldNames": [ + "user_id" + ], + "relation": "*", + "refId": 5, + "fieldIds": [ + 38 + ] + }, + "10": { + "id": 10, + "schemaName": null, + "tableName": "dim_users", + "fieldNames": [ + "user_id" + ], + "relation": "1", + "refId": 5, + "fieldIds": [ + 30 + ] + } + }, + "enumValues": {}, + "indexes": {}, + "indexColumns": {}, + "checks": {}, + "fields": { + "1": { + "id": 1, + "name": "id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 2 + ], + "depEdgeIds": [ + 1 + ], + "tableId": 1, + "enumId": null + }, + "2": { + "id": 2, + "name": "email", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 2 + ], + "tableId": 1, + "enumId": null + }, + "3": { + "id": 3, + "name": "name", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 3 + ], + "tableId": 1, + "enumId": null + }, + "4": { + "id": 4, + "name": "created_at", + "type": { + "schemaName": null, + "type_name": "timestamp", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 4 + ], + "tableId": 1, + "enumId": null + }, + "5": { + "id": 5, + "name": "raw_data", + "type": { + "schemaName": null, + "type_name": "json", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [], + "tableId": 1, + "enumId": null + }, + "6": { + "id": 6, + "name": "id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 4 + ], + "depEdgeIds": [ + 5 + ], + "tableId": 2, + "enumId": null + }, + "7": { + "id": 7, + "name": "user_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 1 + ], + "depEdgeIds": [ + 6 + ], + "tableId": 2, + "enumId": null + }, + "8": { + "id": 8, + "name": "ordered_at", + "type": { + "schemaName": null, + "type_name": "timestamp", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 7 + ], + "tableId": 2, + "enumId": null + }, + "9": { + "id": 9, + "name": "status", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 8 + ], + "tableId": 2, + "enumId": null + }, + "10": { + "id": 10, + "name": "raw_data", + "type": { + "schemaName": null, + "type_name": "json", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [], + "tableId": 2, + "enumId": null + }, + "11": { + "id": 11, + "name": "id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 9 + ], + "tableId": 3, + "enumId": null + }, + "12": { + "id": 12, + "name": "order_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 3 + ], + "depEdgeIds": [ + 10 + ], + "tableId": 3, + "enumId": null + }, + "13": { + "id": 13, + "name": "product_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 11 + ], + "tableId": 3, + "enumId": null + }, + "14": { + "id": 14, + "name": "quantity", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 12 + ], + "tableId": 3, + "enumId": null + }, + "15": { + "id": 15, + "name": "price", + "type": { + "schemaName": null, + "type_name": "decimal", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 13 + ], + "tableId": 3, + "enumId": null + }, + "16": { + "id": 16, + "name": "user_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 6 + ], + "depEdgeIds": [ + 1, + 14 + ], + "tableId": 4, + "enumId": null + }, + "17": { + "id": 17, + "name": "email", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 2, + 15 + ], + "tableId": 4, + "enumId": null + }, + "18": { + "id": 18, + "name": "name", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 3, + 16 + ], + "tableId": 4, + "enumId": null + }, + "19": { + "id": 19, + "name": "created_at", + "type": { + "schemaName": null, + "type_name": "timestamp", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 4, + 17 + ], + "tableId": 4, + "enumId": null + }, + "20": { + "id": 20, + "name": "order_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 8 + ], + "depEdgeIds": [ + 5, + 19, + 20 + ], + "tableId": 5, + "enumId": null + }, + "21": { + "id": 21, + "name": "user_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 5 + ], + "depEdgeIds": [ + 6, + 21 + ], + "tableId": 5, + "enumId": null + }, + "22": { + "id": 22, + "name": "ordered_at", + "type": { + "schemaName": null, + "type_name": "timestamp", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 7, + 18, + 22 + ], + "tableId": 5, + "enumId": null + }, + "23": { + "id": 23, + "name": "status", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 8, + 23 + ], + "tableId": 5, + "enumId": null + }, + "24": { + "id": 24, + "name": "order_item_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 9 + ], + "tableId": 6, + "enumId": null + }, + "25": { + "id": 25, + "name": "order_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 7 + ], + "depEdgeIds": [ + 10 + ], + "tableId": 6, + "enumId": null + }, + "26": { + "id": 26, + "name": "product_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 11 + ], + "tableId": 6, + "enumId": null + }, + "27": { + "id": 27, + "name": "quantity", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 12, + 24 + ], + "tableId": 6, + "enumId": null + }, + "28": { + "id": 28, + "name": "unit_price", + "type": { + "schemaName": null, + "type_name": "decimal", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 13 + ], + "tableId": 6, + "enumId": null + }, + "29": { + "id": 29, + "name": "line_total", + "type": { + "schemaName": null, + "type_name": "decimal", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 25 + ], + "tableId": 6, + "enumId": null + }, + "30": { + "id": 30, + "name": "user_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 10 + ], + "depEdgeIds": [ + 14 + ], + "tableId": 7, + "enumId": null + }, + "31": { + "id": 31, + "name": "email", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 15 + ], + "tableId": 7, + "enumId": null + }, + "32": { + "id": 32, + "name": "name", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 16 + ], + "tableId": 7, + "enumId": null + }, + "33": { + "id": 33, + "name": "first_order_at", + "type": { + "schemaName": null, + "type_name": "timestamp", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 18 + ], + "tableId": 7, + "enumId": null + }, + "34": { + "id": 34, + "name": "total_orders", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 19 + ], + "tableId": 7, + "enumId": null + }, + "35": { + "id": 35, + "name": "total_spent", + "type": { + "schemaName": null, + "type_name": "decimal", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [], + "tableId": 7, + "enumId": null + }, + "36": { + "id": 36, + "name": "created_at", + "type": { + "schemaName": null, + "type_name": "timestamp", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 17 + ], + "tableId": 7, + "enumId": null + }, + "37": { + "id": 37, + "name": "order_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": true, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 20 + ], + "tableId": 8, + "enumId": null + }, + "38": { + "id": 38, + "name": "user_id", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [ + 9 + ], + "depEdgeIds": [ + 21 + ], + "tableId": 8, + "enumId": null + }, + "39": { + "id": 39, + "name": "ordered_at", + "type": { + "schemaName": null, + "type_name": "timestamp", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 22 + ], + "tableId": 8, + "enumId": null + }, + "40": { + "id": 40, + "name": "status", + "type": { + "schemaName": null, + "type_name": "varchar", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 23 + ], + "tableId": 8, + "enumId": null + }, + "41": { + "id": 41, + "name": "total_items", + "type": { + "schemaName": null, + "type_name": "int", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 24 + ], + "tableId": 8, + "enumId": null + }, + "42": { + "id": 42, + "name": "order_total", + "type": { + "schemaName": null, + "type_name": "decimal", + "args": null + }, + "unique": false, + "pk": false, + "note": null, + "injectedPartialId": null, + "checkIds": [], + "endpointIds": [], + "depEdgeIds": [ + 25 + ], + "tableId": 8, + "enumId": null + } + }, + "records": {}, + "tablePartials": {} +} \ No newline at end of file diff --git a/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.spec.ts b/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.spec.ts new file mode 100644 index 000000000..76f66b005 --- /dev/null +++ b/packages/dbml-core/__tests__/examples/normalized_model/dep_pipeline.spec.ts @@ -0,0 +1,22 @@ +import { readFileSync } from 'fs'; +import Parser from '../../../src/parse/Parser'; +import { NormalizedModel } from '../../../types/model_structure/database'; +import path from 'path'; +import { test, beforeAll, describe, expect } from 'vitest'; + +describe('@dbml/core - normalized_structure', () => { + let normalizedModel: NormalizedModel; + + beforeAll(() => { + const dbml = readFileSync(path.resolve(__dirname, 'dep_pipeline.in.dbml'), { encoding: 'utf8' }); + const database = (new Parser()).parse(dbml, 'dbmlv2'); + normalizedModel = database.normalize(); + }); + + describe('dep_pipeline', () => { + test('normalized database matches snapshot', () => { + const serialized = JSON.stringify(normalizedModel, null, 2); + expect(serialized).toMatchFileSnapshot(path.resolve(__dirname, 'dep_pipeline.out.json')); + }); + }); +}); diff --git a/packages/dbml-core/package.json b/packages/dbml-core/package.json index 400077ae0..b5bc1bac9 100644 --- a/packages/dbml-core/package.json +++ b/packages/dbml-core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/core", - "version": "8.4.0-alpha.1", + "version": "9.0.0-alpha.21", "description": "> TODO: description", "author": "Holistics ", "license": "Apache-2.0", @@ -46,7 +46,7 @@ "lint:fix": "eslint --fix ." }, "dependencies": { - "@dbml/parse": "^8.4.0-alpha.1", + "@dbml/parse": "^9.0.0-alpha.21", "antlr4": "^4.13.1", "lodash": "^4.18.1", "lodash-es": "^4.18.1", diff --git a/packages/dbml-core/src/export/DbmlExporter.ts b/packages/dbml-core/src/export/DbmlExporter.ts index c13b6a75a..6b10d742e 100644 --- a/packages/dbml-core/src/export/DbmlExporter.ts +++ b/packages/dbml-core/src/export/DbmlExporter.ts @@ -1,5 +1,5 @@ import { groupBy, isEmpty, reduce } from 'lodash-es'; -import { addDoubleQuoteIfNeeded, formatRecordValue } from '@dbml/parse'; +import { addDoubleQuoteIfNeeded, escapeString, formatRecordValue, normalizeQualifiedName } from '@dbml/parse'; import { shouldPrintSchema } from './utils'; import { DEFAULT_SCHEMA_NAME } from '../model_structure/config'; import type { NormalizedModel, RecordValue } from '../../types/model_structure/database'; @@ -339,6 +339,55 @@ class DbmlExporter { return settings.length ? ` [${settings.join(', ')}]` : ''; } + static formatDepEndpoint (ep: { schemaName: string | null; tableName: string; fieldNames: string[] }): string { + const schemaPart = ep.schemaName ? `"${ep.schemaName}".` : ''; + const tablePart = `"${ep.tableName}"`; + if (!ep.fieldNames || ep.fieldNames.length === 0) return `${schemaPart}${tablePart}`; + const fieldsPart = ep.fieldNames.length === 1 + ? `"${ep.fieldNames[0]}"` + : `(${ep.fieldNames.map((f) => `"${f}"`).join(', ')})`; + return `${schemaPart}${tablePart}.${fieldsPart}`; + } + + static formatDepCustomValue (value: unknown): string { + if (typeof value === 'string') return `'${escapeString(value)}'`; + if (value === null || value === undefined) return 'null'; + return String(value); + } + + static exportDeps (depIds: number[], model: NormalizedModel): string { + const strArr = depIds.map((depId) => { + const dep = model.deps[depId]; + const edges = dep.edgeIds.map((eid) => model.depEdges[eid]); + + const hasNote = !!dep.note; + const customEntries = dep.metadata ? Object.entries(dep.metadata) : []; + const hasAttrs = hasNote || customEntries.length > 0; + + if (edges.length === 1 && !hasAttrs) { + return `Dep: ${DbmlExporter.formatDepEndpoint(edges[0].upstream)} -> ${DbmlExporter.formatDepEndpoint(edges[0].downstream)}\n`; + } + + let str = 'Dep {\n'; + edges.forEach((edge) => { + str += ` ${DbmlExporter.formatDepEndpoint(edge.upstream)} -> ${DbmlExporter.formatDepEndpoint(edge.downstream)}\n`; + }); + + if (hasAttrs) { + str += '\n'; + if (hasNote) str += ` note: ${DbmlExporter.escapeNote(dep.note)}\n`; + customEntries.forEach(([key, value]) => { + str += ` ${key}: ${DbmlExporter.formatDepCustomValue(value)}\n`; + }); + } + + str += '}\n'; + return str; + }); + + return strArr.length ? strArr.join('\n') : ''; + } + static exportTableGroups (tableGroupIds: number[], model: NormalizedModel): string { const tableGroupStrs = tableGroupIds.map((groupId) => { const group = model.tableGroups[groupId]; @@ -395,7 +444,7 @@ class DbmlExporter { // Build table reference const tableRef = schemaName - ? `${addDoubleQuoteIfNeeded(schemaName)}.${addDoubleQuoteIfNeeded(tableName)}` + ? normalizeQualifiedName(schemaName, tableName) : addDoubleQuoteIfNeeded(tableName); // Collect all unique columns in order @@ -426,13 +475,14 @@ class DbmlExporter { database.schemaIds.forEach((schemaId) => { const { - enumIds, tableIds, tableGroupIds, refIds, + enumIds, tableIds, tableGroupIds, refIds, depIds, } = model.schemas[schemaId]; if (!isEmpty(enumIds)) elementStrs.push(DbmlExporter.exportEnums(enumIds, model)); if (!isEmpty(tableIds)) elementStrs.push(DbmlExporter.exportTables(tableIds, model)); if (!isEmpty(tableGroupIds)) elementStrs.push(DbmlExporter.exportTableGroups(tableGroupIds, model)); if (!isEmpty(refIds)) elementStrs.push(DbmlExporter.exportRefs(refIds, model)); + if (!isEmpty(depIds)) elementStrs.push(DbmlExporter.exportDeps(depIds, model)); }); if (!isEmpty(model.notes)) elementStrs.push(DbmlExporter.exportStickyNotes(model)); diff --git a/packages/dbml-core/src/index.ts b/packages/dbml-core/src/index.ts index 4ed75e9a2..190b3a0d8 100644 --- a/packages/dbml-core/src/index.ts +++ b/packages/dbml-core/src/index.ts @@ -5,8 +5,12 @@ import importer from './import'; import exporter from './export'; import { renameTable, + updateElementSetting, + updateElementSettingEdit, syncDiagramView, findDiagramViewBlocks, + syncDep, + findDepBlocks, } from './transform'; import { VERSION } from './utils/version'; @@ -14,8 +18,12 @@ export { importer, exporter, renameTable, + updateElementSetting, + updateElementSettingEdit, syncDiagramView, findDiagramViewBlocks, + syncDep, + findDepBlocks, ModelExporter, CompilerError, Parser, @@ -44,6 +52,8 @@ export { dbmlMonarchTokensProvider, DEFAULT_ENTRY, Filepath, + SymbolKind, + MetadataKind, } from '@dbml/parse'; // Re-export types @@ -53,5 +63,19 @@ export type { FilterConfig, DiagramViewSyncOperation, DiagramViewBlock, + DepSyncOperation, + DepSyncEdge, + DepEndpointRef, + DepBlock, TextEdit, + ElementIdentifier, + SchemaIdentifier, + TableIdentifier, + ColumnIdentifier, + EnumIdentifier, + EndpointRef, + RefIdentifier, + DepIdentifier, + NoteIdentifier, + TableGroupIdentifier, } from '@dbml/parse'; diff --git a/packages/dbml-core/src/model_structure/config.js b/packages/dbml-core/src/model_structure/config.ts similarity index 88% rename from packages/dbml-core/src/model_structure/config.js rename to packages/dbml-core/src/model_structure/config.ts index 869a90127..b4d10e489 100644 --- a/packages/dbml-core/src/model_structure/config.js +++ b/packages/dbml-core/src/model_structure/config.ts @@ -3,4 +3,5 @@ export const TABLE = 'table'; export const NOTE = 'note'; export const ENUM = 'enum'; export const REF = 'ref'; +export const DEP = 'dep'; export const TABLE_GROUP = 'table_group'; diff --git a/packages/dbml-core/src/model_structure/database.js b/packages/dbml-core/src/model_structure/database.js index fc0a40bce..447dd9159 100644 --- a/packages/dbml-core/src/model_structure/database.js +++ b/packages/dbml-core/src/model_structure/database.js @@ -1,8 +1,9 @@ import { capitalize, get } from 'lodash-es'; import { - DEFAULT_SCHEMA_NAME, ENUM, NOTE, REF, TABLE, TABLE_GROUP, + DEFAULT_SCHEMA_NAME, DEP, ENUM, NOTE, REF, TABLE, TABLE_GROUP, } from './config'; import DbState from './dbState'; +import Dep from './dep'; import Element from './element'; import Enum from './enum'; import Ref from './ref'; @@ -22,6 +23,7 @@ class Database extends Element { notes = [], enums = [], refs = [], + deps = [], tableGroups = [], project = {}, aliases = [], @@ -60,6 +62,7 @@ class Database extends Element { this.linkRecordsToTables(); this.processSchemaElements(notes, NOTE); this.processSchemaElements(refs, REF); + this.processSchemaElements(deps, DEP); this.processSchemaElements(tableGroups, TABLE_GROUP); this.injectedRawRefs.forEach((rawRef) => { @@ -159,6 +162,10 @@ class Database extends Element { schema.pushRef(new Ref({ ...element, schema })); break; + case DEP: + schema.pushDep(new Dep({ ...element, schema })); + break; + default: break; } @@ -285,6 +292,8 @@ class Database extends Element { schemas: {}, notes: {}, refs: {}, + deps: {}, + depEdges: {}, enums: {}, tableGroups: {}, tables: {}, diff --git a/packages/dbml-core/src/model_structure/dbState.js b/packages/dbml-core/src/model_structure/dbState.js deleted file mode 100644 index 479a1ae67..000000000 --- a/packages/dbml-core/src/model_structure/dbState.js +++ /dev/null @@ -1,44 +0,0 @@ -export default class DbState { - constructor () { - /** @type {number} */ - this.dbId = 1; - /** @type {number} */ - this.schemaId = 1; - /** @type {number} */ - this.enumId = 1; - /** @type {number} */ - this.tableGroupId = 1; - /** @type {number} */ - this.refId = 1; - /** @type {number} */ - this.tableId = 1; - /** @type {number} */ - this.noteId = 1; - /** @type {number} */ - this.enumValueId = 1; - /** @type {number} */ - this.endpointId = 1; - /** @type {number} */ - this.indexId = 1; - /** @type {number} */ - this.checkId = 1; - /** @type {number} */ - this.fieldId = 1; - /** @type {number} */ - this.indexColumnId = 1; - /** @type {number} */ - this.recordId = 1; - /** @type {number} */ - this.tablePartialId = 1; - } - - /** - * @param {string} el - * @returns {number} - */ - generateId (el) { - const id = this[el]; - this[el] += 1; - return id; - } -} diff --git a/packages/dbml-core/src/model_structure/dbState.ts b/packages/dbml-core/src/model_structure/dbState.ts new file mode 100644 index 000000000..87a8fe472 --- /dev/null +++ b/packages/dbml-core/src/model_structure/dbState.ts @@ -0,0 +1,25 @@ +export default class DbState { + dbId = 1; + schemaId = 1; + enumId = 1; + tableGroupId = 1; + refId = 1; + depId = 1; + depEdgeId = 1; + tableId = 1; + noteId = 1; + enumValueId = 1; + endpointId = 1; + indexId = 1; + checkId = 1; + fieldId = 1; + indexColumnId = 1; + recordId = 1; + tablePartialId = 1; + + generateId (el: keyof DbState): number { + const id = this[el] as number; + (this[el] as number) += 1; + return id; + } +} diff --git a/packages/dbml-core/src/model_structure/dep.ts b/packages/dbml-core/src/model_structure/dep.ts new file mode 100644 index 000000000..5c2e917fe --- /dev/null +++ b/packages/dbml-core/src/model_structure/dep.ts @@ -0,0 +1,105 @@ +import { get } from 'lodash-es'; +import { DEFAULT_SCHEMA_NAME } from './config'; +import DepEdge from './depEdge'; +import Element from './element'; +import type { RawDep, RawDepEdgeInput } from '../../types/model_structure/dep'; +import type { RawDepEdge } from '../../types/model_structure/dep_edge'; +import type { Token, Color } from '../../types/model_structure/element'; +import type { NormalizedModel } from '../../types/model_structure/database'; +import type Schema from '../../types/model_structure/schema'; +import type DbState from '../../types/model_structure/dbState'; + +class Dep extends Element { + name: string | null; + color: Color | undefined; + note: string | null; + noteToken: Token | null; + metadata: Record | null; + edges: DepEdge[]; + schema: Schema; + dbState: DbState; + id!: number; + + constructor ({ + name, color, note, metadata, edges, token, schema = {} as Schema, + }: RawDep) { + super(token); + this.name = name ?? null; + this.color = color; + this.note = note ? get(note, 'value', note) as string : null; + this.noteToken = note ? get(note, 'token', null) : null; + this.metadata = metadata ?? null; + this.edges = []; + this.schema = schema; + this.dbState = this.schema.dbState; + this.generateId(); + + this.processEdges(edges ?? []); + } + + generateId () { + this.id = this.dbState.generateId('depId'); + } + + processEdges (rawEdges: RawDepEdgeInput[]) { + rawEdges.forEach((rawEdge) => { + const upSchema = rawEdge.upstream?.schemaName || DEFAULT_SCHEMA_NAME; + const downSchema = rawEdge.downstream?.schemaName || DEFAULT_SCHEMA_NAME; + const upTable = rawEdge.upstream?.tableName; + const downTable = rawEdge.downstream?.tableName; + const upColumn = rawEdge.upstream?.fieldNames?.join(","); + const downColumn = rawEdge.downstream?.fieldNames?.join(","); + if (upSchema === downSchema && upTable === downTable && upColumn === downColumn && upColumn) { + this.error(`Self-loop Dep edge not allowed: "${upSchema}"."${upTable}" columns cannot depend on itself`); + } + this.edges.push(new DepEdge({ ...rawEdge, dep: this } as RawDepEdge)); + }); + } + + export () { + return { + ...this.shallowExport(), + ...this.exportChild(), + }; + } + + shallowExport () { + return { + name: this.name, + color: this.color, + note: this.note, + metadata: this.metadata, + }; + } + + exportChild () { + return { + edges: this.edges.map((e) => e.export()), + }; + } + + exportChildIds () { + return { + edgeIds: this.edges.map((e) => e.id), + }; + } + + exportParentIds () { + return { + schemaId: this.schema.id, + }; + } + + normalize (model: NormalizedModel) { + model.deps[this.id] = { + id: this.id, + ...this.shallowExport(), + ...this.exportChildIds(), + ...this.exportParentIds(), + }; + + this.edges.forEach((edge) => edge.normalize(model)); + } +} + +export default Dep; diff --git a/packages/dbml-core/src/model_structure/depEdge.ts b/packages/dbml-core/src/model_structure/depEdge.ts new file mode 100644 index 000000000..845fdf29d --- /dev/null +++ b/packages/dbml-core/src/model_structure/depEdge.ts @@ -0,0 +1,115 @@ +import { DEFAULT_SCHEMA_NAME } from './config'; +import Element from './element'; +import { shouldPrintSchema, shouldPrintSchemaName } from './utils'; +import type { DepEndpointData, RawDepEdge } from '../../types/model_structure/dep_edge'; +import type { NormalizedModel } from '../../types/model_structure/database'; +import type Dep from '../../types/model_structure/dep'; +import type DbState from '../../types/model_structure/dbState'; +import type Table from '../../types/model_structure/table'; +import type Field from '../../types/model_structure/field'; +import type Database from '../../types/model_structure/database'; + +class DepEdge extends Element { + upstream: DepEndpointData; + downstream: DepEndpointData; + dep: Dep; + dbState: DbState; + id!: number; + upstreamTable: Table | null; + upstreamFields: Field[]; + downstreamTable: Table | null; + downstreamFields: Field[]; + + constructor ({ upstream, downstream, token, dep }: RawDepEdge) { + super(token); + this.upstream = { + schemaName: upstream.schemaName ?? null, + tableName: upstream.tableName, + fieldNames: upstream.fieldNames ?? [], + }; + this.downstream = { + schemaName: downstream.schemaName ?? null, + tableName: downstream.tableName, + fieldNames: downstream.fieldNames ?? [], + }; + this.dep = dep; + this.dbState = this.dep.dbState; + this.generateId(); + + const database = this.dep.schema.database; + const up = this.resolveEndpoint(this.upstream, database); + const down = this.resolveEndpoint(this.downstream, database); + this.upstreamTable = up.table; + this.upstreamFields = up.fields; + this.downstreamTable = down.table; + this.downstreamFields = down.fields; + + this.upstreamFields.forEach((field) => field.pushDepEdge(this)); + this.downstreamFields.forEach((field) => field.pushDepEdge(this)); + } + + /** + * Resolve an upstream or downstream endpoint against the database. + * Throws via this.error() if the referenced table or field can't be found. + */ + resolveEndpoint ( + endpointData: { schemaName?: string | null; tableName: string; fieldNames?: string[] }, + database: Database, + ): { table: Table; fields: Field[] } { + const schemaName = endpointData.schemaName || DEFAULT_SCHEMA_NAME; + const table = database.findTable(schemaName, endpointData.tableName); + if (!table) { + this.error(`Can't find table ${shouldPrintSchemaName(schemaName) + ? `"${schemaName}".` + : ''}"${endpointData.tableName}" referenced in Dep edge`); + } + + const fieldNames = endpointData.fieldNames ?? []; + const fields = fieldNames.map((name) => { + const field = table.findField(name); + if (!field) { + this.error(`Can't find field ${shouldPrintSchema(table.schema) + ? `"${table.schema.name}".` + : ''}"${name}" in table "${table.name}" referenced in Dep edge`); + } + return field; + }); + return { table, fields }; + } + + generateId () { + this.id = this.dbState.generateId('depEdgeId'); + } + + export () { + return this.shallowExport(); + } + + shallowExport () { + return { + upstream: this.upstream, + downstream: this.downstream, + }; + } + + exportParentIds () { + return { + depId: this.dep.id, + upstreamTableId: this.upstreamTable ? this.upstreamTable.id : null, + upstreamFieldIds: this.upstreamFields.map((f) => f.id), + downstreamTableId: this.downstreamTable ? this.downstreamTable.id : null, + downstreamFieldIds: this.downstreamFields.map((f) => f.id), + }; + } + + normalize (model: NormalizedModel) { + if (!model.depEdges) model.depEdges = {}; + model.depEdges[this.id] = { + id: this.id, + ...this.shallowExport(), + ...this.exportParentIds(), + }; + } +} + +export default DepEdge; diff --git a/packages/dbml-core/src/model_structure/field.js b/packages/dbml-core/src/model_structure/field.js index f6ae16555..bbec541d4 100644 --- a/packages/dbml-core/src/model_structure/field.js +++ b/packages/dbml-core/src/model_structure/field.js @@ -41,6 +41,8 @@ class Field extends Element { this.checks = []; /** @type {import('../../types/model_structure/endpoint').default[]} */ this.endpoints = []; + /** @type {import('./depEdge').default[]} */ + this.depEdges = []; /** @type {import('../../types/model_structure/table').default} */ this.table = table; /** @type {import('../../types/model_structure/tablePartial').default} */ @@ -93,6 +95,13 @@ class Field extends Element { this.endpoints.push(endpoint); } + /** + * @param {import('./depEdge').default} depEdge + */ + pushDepEdge (depEdge) { + this.depEdges.push(depEdge); + } + export () { return { ...this.shallowExport(), @@ -109,6 +118,7 @@ class Field extends Element { exportChildIds () { return { endpointIds: this.endpoints.map((e) => e.id), + depEdgeIds: this.depEdges.map((e) => e.id), }; } diff --git a/packages/dbml-core/src/model_structure/schema.js b/packages/dbml-core/src/model_structure/schema.js index c5eda0b23..a722e6002 100644 --- a/packages/dbml-core/src/model_structure/schema.js +++ b/packages/dbml-core/src/model_structure/schema.js @@ -1,4 +1,5 @@ import { get } from 'lodash-es'; +import Dep from './dep'; import Element from './element'; import Enum from './enum'; import Ref from './ref'; @@ -11,7 +12,7 @@ class Schema extends Element { * @param {import('../../types/model_structure/schema').RawSchema} param0 */ constructor ({ - name, alias, note, tables = [], refs = [], enums = [], tableGroups = [], token, database = {}, noteToken = null, + name, alias, note, tables = [], refs = [], deps = [], enums = [], tableGroups = [], token, database = {}, noteToken = null, } = {}) { super(token); /** @type {import('../../types/model_structure/table').default[]} */ @@ -22,6 +23,8 @@ class Schema extends Element { this.tableGroups = []; /** @type {import('../../types/model_structure/ref').default[]} */ this.refs = []; + /** @type {import('./dep').default[]} */ + this.deps = []; /** @type {string} */ this.name = name; /** @type {string} */ @@ -39,6 +42,7 @@ class Schema extends Element { this.processEnums(enums); this.processTables(tables); this.processRefs(refs); + this.processDeps(deps); this.processTableGroups(tableGroups); } @@ -141,6 +145,22 @@ class Schema extends Element { } } + /** + * @param {any[]} rawDeps + */ + processDeps (rawDeps) { + rawDeps.forEach((dep) => { + this.pushDep(new Dep({ ...dep, schema: this })); + }); + } + + /** + * @param {import('./dep').default} dep + */ + pushDep (dep) { + this.deps.push(dep); + } + /** * @param {any[]} rawTableGroups */ @@ -191,6 +211,7 @@ class Schema extends Element { enums: this.enums.map((e) => e.export()), tableGroups: this.tableGroups.map((tg) => tg.export()), refs: this.refs.map((r) => r.export()), + deps: this.deps.map((d) => d.export()), }; } @@ -200,6 +221,7 @@ class Schema extends Element { enumIds: this.enums.map((e) => e.id), tableGroupIds: this.tableGroups.map((tg) => tg.id), refIds: this.refs.map((r) => r.id), + depIds: this.deps.map((d) => d.id), }; } @@ -232,6 +254,7 @@ class Schema extends Element { this.enums.forEach((_enum) => _enum.normalize(model)); this.tableGroups.forEach((tableGroup) => tableGroup.normalize(model)); this.refs.forEach((ref) => ref.normalize(model)); + this.deps.forEach((dep) => dep.normalize(model)); } } diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js index 0e53ac97c..cfe93fa93 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mssql/MssqlASTGen.js @@ -1,12 +1,15 @@ import { first, flatten, flattenDepth, last, nth, } from 'lodash-es'; +import { ParseTreeListener, ParseTreeWalker } from 'antlr4'; import TSqlParserVisitor from '../../parsers/mssql/TSqlParserVisitor'; +import TSqlParser from '../../parsers/mssql/TSqlParser'; import { Field, Index, Table, TableRecord, } from '../AST'; import { COLUMN_CONSTRAINT_KIND, DATA_TYPE, TABLE_CONSTRAINT_KIND } from '../constants'; import { getOriginalText } from '../helpers'; +import { normalizeQualifiedName } from '@dbml/parse'; const ADD_DESCRIPTION_FUNCTION_NAME = 'sp_addextendedproperty'; @@ -104,6 +107,7 @@ export default class MssqlASTGen extends TSqlParserVisitor { schemas: [], tables: [], refs: [], + deps: [], enums: [], tableGroups: [], aliases: [], @@ -112,10 +116,63 @@ export default class MssqlASTGen extends TSqlParserVisitor { }; } + visitCreate_view (ctx) { + const simpleName = ctx.simple_name(); + if (!simpleName) return; + const text = simpleName.getText(); + const ids = text.split('.').map((p) => p.replace(/\[|\]|"|`/g, '')); + const tableName = ids[ids.length - 1]; + const schemaName = ids.length > 1 ? ids[ids.length - 2] : undefined; + const ddl = getOriginalText(ctx); + this.data.tables.push({ + name: tableName, + schemaName, + fields: [], + indexes: [], + checks: [], + note: null, + headerColor: null, + custom: { query: ddl, kind: 'view' }, + }); + // Walk the SELECT body to find source tables and emit dep edges + const selectCtx = ctx.select_statement_standalone(); + if (!selectCtx) return; + + const seenTables = new Set(); + const viewKey = normalizeQualifiedName(schemaName ?? DEFAULT_SCHEMA, tableName); + + // Collect all deps of this view + const collector = new ParseTreeListener(); + collector.enterEveryRule = (node) => { + // Only handle full table name + if (!(node instanceof TSqlParser.Full_table_nameContext)) return; + + const names = node.accept(this); + const { tableName: srcTable, schemaName: srcSchema } = getSchemaAndTableName(names); + + // Skip self-references and duplicates + const key = normalizeQualifiedName(srcSchema ?? DEFAULT_SCHEMA, srcTable); + if (key === viewKey) return; + if (seenTables.has(key)) return; + if (!this.findTable(srcSchema, srcTable)) return; + + seenTables.add(key); + this.data.deps.push({ + edges: [{ + upstream: { schemaName: srcSchema, tableName: srcTable, fieldNames: [] }, + downstream: { schemaName, tableName, fieldNames: [] }, + }], + note: null, + custom: {}, + }); + }; + ParseTreeWalker.DEFAULT.walk(collector, selectCtx); + } + findTable (schemaName, tableName) { - const realSchemaName = schemaName || DEFAULT_SCHEMA; + const realSchemaName = schemaName ?? DEFAULT_SCHEMA; const table = this.data.tables.find((t) => { - const targetSchemaName = t.schemaName || DEFAULT_SCHEMA; + const targetSchemaName = t.schemaName ?? DEFAULT_SCHEMA; return targetSchemaName === realSchemaName && t.name === tableName; }); return table; diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.js index d42a9a667..c4d4e459c 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/mysql/MySQLASTGen.js @@ -1,5 +1,7 @@ import { flatten, flattenDepth, last } from 'lodash-es'; +import { ParseTreeListener, ParseTreeWalker } from 'antlr4'; import MySQLParserVisitor from '../../parsers/mysql/MySqlParserVisitor'; +import MySqlParser from '../../parsers/mysql/MySqlParser'; import { Endpoint, Enum, Field, Index, Ref, Table, TableRecord, @@ -8,6 +10,8 @@ import { COLUMN_CONSTRAINT_KIND, CONSTRAINT_TYPE, DATA_TYPE, TABLE_CONSTRAINT_KIND, } from '../constants'; import { getOriginalText } from '../helpers'; +import { DEFAULT_SCHEMA_NAME } from '../../../../model_structure/config'; +import { normalizeQualifiedName } from '@dbml/parse'; const TABLE_OPTIONS_KIND = { NOTE: 'note', @@ -51,6 +55,7 @@ export default class MySQLASTGen extends MySQLParserVisitor { schemas: [], tables: [], refs: [], + deps: [], enums: [], tableGroups: [], aliases: [], @@ -59,11 +64,61 @@ export default class MySQLASTGen extends MySQLParserVisitor { }; } - // TODO: support configurable default schema name other than 'public' + visitCreateView (ctx) { + const fullId = ctx.fullId(); + const ids = fullId ? fullId.getText().replace(/`/g, '').split('.') : []; + if (ids.length === 0) return; + const tableName = ids[ids.length - 1]; + const schemaName = ids.length > 1 ? ids[ids.length - 2] : undefined; + const ddl = ctx.getText(); + this.data.tables.push({ + name: tableName, + schemaName, + fields: [], + indexes: [], + checks: [], + note: null, + headerColor: null, + custom: { query: ddl, kind: 'view' }, + }); + // Walk the SELECT body to find source tables and emit dep edges + const selectCtx = ctx.selectStatement(); + if (!selectCtx) return; + + const seenTables = new Set(); + + // Collect all deps of this view + const collector = new ParseTreeListener(); + collector.enterEveryRule = (node) => { + // Only handle table name + if (!(node instanceof MySqlParser.TableNameContext)) return; + + const names = node.accept(this); + const { tableName: srcTable, schemaName: srcSchema } = getTableNames(names); + + // Skip self-references and duplicates + const key = normalizeQualifiedName(srcSchema ?? DEFAULT_SCHEMA_NAME, srcTable); + if (key === normalizeQualifiedName(schemaName ?? DEFAULT_SCHEMA_NAME, tableName)) return; + if (seenTables.has(key)) return; + if (!this.findTable(srcSchema, srcTable)) return; + + seenTables.add(key); + this.data.deps.push({ + edges: [{ + upstream: { schemaName: srcSchema, tableName: srcTable, fieldNames: [] }, + downstream: { schemaName, tableName, fieldNames: [] }, + }], + note: null, + custom: {}, + }); + }; + ParseTreeWalker.DEFAULT.walk(collector, selectCtx); + } + findTable (schemaName, tableName) { - const realSchemaName = schemaName || 'public'; + const realSchemaName = schemaName ?? DEFAULT_SCHEMA_NAME; const table = this.data.tables.find((t) => { - const targetSchemaName = t.schemaName || 'public'; + const targetSchemaName = t.schemaName ?? DEFAULT_SCHEMA_NAME; return targetSchemaName === realSchemaName && t.name === tableName; }); return table; diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.js index 41eac7a83..eaf4d8d5f 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/oraclesql/OracleSQLASTGen.js @@ -1,6 +1,8 @@ import { flatten, last } from 'lodash-es'; +import { ParseTreeListener, ParseTreeWalker } from 'antlr4'; import { CompilerError } from '../../../error'; import OracleSqlParserVisitor from '../../parsers/oraclesql/OracleSqlParserVisitor'; +import OracleSqlParser from '../../parsers/oraclesql/OracleSqlParser'; import { Endpoint, Field, @@ -14,6 +16,7 @@ import { DATA_TYPE, } from '../constants'; import { getOriginalText } from '../helpers'; +import { normalizeQualifiedName } from '@dbml/parse'; // We cannot use TABLE_CONSTRAINT_KIND and COLUMN_CONSTRAINT_KIND from '../constants' as their values are indistinguishable from each other // For example: TABLE_CONSTRAINT_KIND.UNIQUE === COLUMN_CONSTRAINT_KIND.UNIQUE @@ -41,9 +44,9 @@ const COLUMN_CONSTRAINT_KIND = { }; const findTable = (tables, schemaName, tableName) => { - const realSchemaName = schemaName || null; + const realSchemaName = schemaName ?? null; const table = tables.find((table) => { - const targetSchemaName = table.schemaName || null; + const targetSchemaName = table.schemaName ?? null; return targetSchemaName === realSchemaName && table.name === tableName; }); return table; @@ -117,6 +120,7 @@ export default class OracleSqlASTGen extends OracleSqlParserVisitor { schemas: [], tables: [], refs: [], + deps: [], enums: [], tableGroups: [], aliases: [], @@ -125,6 +129,46 @@ export default class OracleSqlASTGen extends OracleSqlParserVisitor { }; } + visitCreate_view (ctx) { + const idExpr = ctx.id_expression ? ctx.id_expression() : null; + if (!idExpr) return; + const tableName = idExpr.getText().replace(/"/g, ''); + const schemaNameNode = ctx.schema_name ? ctx.schema_name() : null; + const schemaName = schemaNameNode ? schemaNameNode.getText().replace(/"/g, '') : undefined; + const ddl = getOriginalText(ctx); + this.data.tables.push({ + name: tableName, + schemaName, + fields: [], + indexes: [], + checks: [], + note: null, + headerColor: null, + custom: { query: ddl, kind: 'view' }, + }); + collectViewDeps(this, ctx.select_only_statement(), tableName, schemaName); + } + + visitCreate_materialized_view (ctx) { + const text = ctx.getText ? ctx.getText() : ''; + const m = text.match(/MATERIALIZEDVIEW(?:IFNOTEXISTS)?(?:"([^"]+)"\.)?"?([^("\s]+)"?/i); + if (!m) return; + const schemaName = m[1] || undefined; + const tableName = m[2]; + const ddl = getOriginalText(ctx); + this.data.tables.push({ + name: tableName, + schemaName, + fields: [], + indexes: [], + checks: [], + note: null, + headerColor: null, + custom: { query: ddl, kind: 'materialized_view', materialized: true }, + }); + collectViewDeps(this, ctx.select_only_statement(), tableName, schemaName); + } + // sql_script // : sql_plus_command_no_semicolon? ( // (((sql_plus_command SEMICOLON? '/'?) | (unit_statement SEMICOLON '/'?))* (sql_plus_command | unit_statement)) SEMICOLON? '/'? @@ -1060,3 +1104,39 @@ export default class OracleSqlASTGen extends OracleSqlParserVisitor { return unquoteString(getOriginalText(ctx), '"'); } } + +// Walk a SELECT subtree to find source tables and emit dep edges +function collectViewDeps (visitor, selectCtx, viewName, viewSchemaName) { + if (!selectCtx) return; + + const seenTables = new Set(); + const viewKey = normalizeQualifiedName(viewSchemaName ?? '', viewName); + + // Collect all deps of this view + const collector = new ParseTreeListener(); + collector.enterEveryRule = (node) => { + // Only handle table/view name references + if (!(node instanceof OracleSqlParser.Tableview_nameContext)) return; + + const names = node.accept(visitor); + const srcTable = Array.isArray(names) ? last(names) : names; + const srcSchema = Array.isArray(names) && names.length > 1 ? names[names.length - 2] : undefined; + + // Skip self-references and duplicates + const key = normalizeQualifiedName(srcSchema ?? '', srcTable); + if (key === viewKey) return; + if (seenTables.has(key)) return; + if (!findTable(visitor.data.tables, srcSchema, srcTable)) return; + + seenTables.add(key); + visitor.data.deps.push({ + edges: [{ + upstream: { schemaName: srcSchema, tableName: srcTable, fieldNames: [] }, + downstream: { schemaName: viewSchemaName, tableName: viewName, fieldNames: [] }, + }], + note: null, + custom: {}, + }); + }; + ParseTreeWalker.DEFAULT.walk(collector, selectCtx); +} diff --git a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.js b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.js index 21d464437..9fc6ddbbd 100644 --- a/packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.js +++ b/packages/dbml-core/src/parse/ANTLR/ASTGeneration/postgres/PostgresASTGen.js @@ -1,5 +1,7 @@ import { flatten, flattenDepth, last } from 'lodash-es'; +import { ParseTreeListener, ParseTreeWalker } from 'antlr4'; import PostgreSQLParserVisitor from '../../parsers/postgresql/PostgreSQLParserVisitor'; +import PostgreSQLParser from '../../parsers/postgresql/PostgreSQLParser'; import { Enum, Field, Index, Table, TableRecord, } from '../AST'; @@ -7,6 +9,8 @@ import { COLUMN_CONSTRAINT_KIND, CONSTRAINT_TYPE, DATA_TYPE, TABLE_CONSTRAINT_KIND, } from '../constants'; import { getOriginalText } from '../helpers'; +import { DEFAULT_SCHEMA_NAME } from '../../../../model_structure/config'; +import { normalizeQualifiedName } from '@dbml/parse'; const COMMAND_KIND = { REF: 'ref', @@ -17,9 +21,9 @@ const COMMENT_OBJECT_TYPE = { }; const findTable = (tables, schemaName, tableName) => { - const realSchemaName = schemaName || 'public'; + const realSchemaName = schemaName ?? DEFAULT_SCHEMA_NAME; const table = tables.find((table) => { - const targetSchemaName = table.schemaName || 'public'; + const targetSchemaName = table.schemaName ?? DEFAULT_SCHEMA_NAME; return targetSchemaName === realSchemaName && table.name === tableName; }); return table; @@ -39,6 +43,7 @@ export default class PostgresASTGen extends PostgreSQLParserVisitor { schemas: [], tables: [], refs: [], + deps: [], enums: [], tableGroups: [], aliases: [], @@ -47,6 +52,44 @@ export default class PostgresASTGen extends PostgreSQLParserVisitor { }; } + visitViewstmt (ctx) { + const names = ctx.qualified_name().accept(this); + const tableName = last(names); + const schemaName = names.length > 1 ? names[names.length - 2] : undefined; + const ddl = getOriginalText(ctx); + this.data.tables.push({ + name: tableName, + schemaName, + fields: [], + indexes: [], + checks: [], + note: null, + headerColor: null, + custom: { query: ddl, kind: 'view' }, + }); + collectViewDeps(this, ctx.selectstmt(), tableName, schemaName); + } + + visitCreatematviewstmt (ctx) { + const target = ctx.create_mv_target(); + if (!target) return; + const names = target.qualified_name().accept(this); + const tableName = last(names); + const schemaName = names.length > 1 ? names[names.length - 2] : undefined; + const ddl = getOriginalText(ctx); + this.data.tables.push({ + name: tableName, + schemaName, + fields: [], + indexes: [], + checks: [], + note: null, + headerColor: null, + custom: { query: ddl, kind: 'materialized_view', materialized: true }, + }); + collectViewDeps(this, ctx.selectstmt(), tableName, schemaName); + } + // stmtblock EOF visitRoot (ctx) { ctx.stmtblock().accept(this); @@ -1241,3 +1284,39 @@ export default class PostgresASTGen extends PostgreSQLParserVisitor { }; } } + +// Walk a SELECT subtree to find source tables and emit dep edges +function collectViewDeps (visitor, selectCtx, viewName, viewSchemaName) { + if (!selectCtx) return; + + const seenTables = new Set(); + const viewKey = normalizeQualifiedName(viewSchemaName ?? DEFAULT_SCHEMA_NAME, viewName); + + // Collect all deps of this view + const collector = new ParseTreeListener(); + collector.enterEveryRule = (node) => { + // Only handle relation expressions (table references) + if (!(node instanceof PostgreSQLParser.Relation_exprContext)) return; + + const names = node.accept(visitor); + const srcTable = last(names); + const srcSchema = names.length > 1 ? names[names.length - 2] : undefined; + + // Skip self-references and duplicates + const key = normalizeQualifiedName(srcSchema ?? DEFAULT_SCHEMA_NAME, srcTable); + if (key === viewKey) return; + if (seenTables.has(key)) return; + if (!findTable(visitor.data.tables, srcSchema, srcTable)) return; + + seenTables.add(key); + visitor.data.deps.push({ + edges: [{ + upstream: { schemaName: srcSchema, tableName: srcTable, fieldNames: [] }, + downstream: { schemaName: viewSchemaName, tableName: viewName, fieldNames: [] }, + }], + note: null, + custom: {}, + }); + }; + ParseTreeWalker.DEFAULT.walk(collector, selectCtx); +} diff --git a/packages/dbml-core/src/transform/index.ts b/packages/dbml-core/src/transform/index.ts index a2d51d8ab..2d95dea74 100644 --- a/packages/dbml-core/src/transform/index.ts +++ b/packages/dbml-core/src/transform/index.ts @@ -1,9 +1,12 @@ import { Compiler, DEFAULT_ENTRY, MemoryProjectLayout } from '@dbml/parse'; -import type { DiagramViewSyncOperation, DiagramViewBlock, TextEdit } from '@dbml/parse'; +import type { + DiagramViewSyncOperation, DiagramViewBlock, TextEdit, + DepSyncOperation, DepBlock, ElementIdentifier, TableIdentifier, +} from '@dbml/parse'; -type TableName = string | { schema?: string; table: string }; +export { findDiagramViewBlocks, findDepBlocks } from '@dbml/parse'; -export function renameTable (oldName: TableName, newName: TableName, dbmlCode: string): string { +export function renameTable (oldName: string | TableIdentifier, newName: string | TableIdentifier, dbmlCode: string): string { const layout = new MemoryProjectLayout(); layout.setSource(DEFAULT_ENTRY, dbmlCode); const compiler = new Compiler(layout); @@ -22,9 +25,37 @@ export function syncDiagramView ( return compiler.syncDiagramView(DEFAULT_ENTRY, operations, blocks); } -export function findDiagramViewBlocks (dbmlCode: string): DiagramViewBlock[] { +export function syncDep ( + dbmlCode: string, + operations: DepSyncOperation[], + blocks?: DepBlock[], +): { newDbml: string; edits: TextEdit[] } { + const layout = new MemoryProjectLayout(); + layout.setSource(DEFAULT_ENTRY, dbmlCode); + const compiler = new Compiler(layout); + return compiler.syncDep(DEFAULT_ENTRY, operations, blocks); +} + +export function updateElementSettingEdit ( + dbmlCode: string, + target: ElementIdentifier, + settingName: string, + value: string | null | undefined, +): TextEdit[] { + const layout = new MemoryProjectLayout(); + layout.setSource(DEFAULT_ENTRY, dbmlCode); + const compiler = new Compiler(layout); + return compiler.updateElementSettingEdit(DEFAULT_ENTRY, target, settingName, value); +} + +export function updateElementSetting ( + dbmlCode: string, + target: ElementIdentifier, + settingName: string, + value: string | null | undefined, +): string { const layout = new MemoryProjectLayout(); layout.setSource(DEFAULT_ENTRY, dbmlCode); const compiler = new Compiler(layout); - return compiler.findDiagramViewBlocks(DEFAULT_ENTRY); + return compiler.updateElementSetting(DEFAULT_ENTRY, target, settingName, value); } diff --git a/packages/dbml-core/types/index.d.ts b/packages/dbml-core/types/index.d.ts index c380ff1ea..90edb1acd 100644 --- a/packages/dbml-core/types/index.d.ts +++ b/packages/dbml-core/types/index.d.ts @@ -4,13 +4,21 @@ import importer from './import'; import exporter from './export'; import { renameTable, + updateElementSetting, + updateElementSettingEdit, syncDiagramView, findDiagramViewBlocks, + syncDep, + findDepBlocks, } from './transform'; export { renameTable, + updateElementSetting, + updateElementSettingEdit, syncDiagramView, findDiagramViewBlocks, + syncDep, + findDepBlocks, importer, exporter, ModelExporter, @@ -42,6 +50,8 @@ export { formatRecordValue, DEFAULT_ENTRY, Filepath, + SymbolKind, + MetadataKind, } from '@dbml/parse'; // Re-export types @@ -51,5 +61,19 @@ export type { FilterConfig, DiagramViewSyncOperation, DiagramViewBlock, + DepSyncOperation, + DepSyncEdge, + DepEndpointRef, + DepBlock, TextEdit, + ElementIdentifier, + SchemaIdentifier, + TableIdentifier, + ColumnIdentifier, + EnumIdentifier, + EndpointRef, + RefIdentifier, + DepIdentifier, + NoteIdentifier, + TableGroupIdentifier, } from '@dbml/parse'; diff --git a/packages/dbml-core/types/model_structure/database.d.ts b/packages/dbml-core/types/model_structure/database.d.ts index 07b7b6d0a..0e436d37a 100644 --- a/packages/dbml-core/types/model_structure/database.d.ts +++ b/packages/dbml-core/types/model_structure/database.d.ts @@ -1,5 +1,7 @@ import Schema, { NormalizedSchemaIdMap, RawSchema } from './schema'; import Ref, { NormalizedRefIdMap } from './ref'; +import Dep, { NormalizedDepIdMap } from './dep'; +import { NormalizedDepEdgeIdMap } from './dep_edge'; import Enum, { NormalizedEnumIdMap } from './enum'; import TableGroup, { NormalizedTableGroupIdMap } from './tableGroup'; import Table, { NormalizedTableIdMap } from './table'; @@ -56,6 +58,7 @@ export interface RawDatabase { notes: StickyNote[]; enums: Enum[]; refs: Ref[]; + deps?: Dep[]; tableGroups: TableGroup[]; project: Project; records: RawTableRecord[]; @@ -83,7 +86,7 @@ declare class Database extends Element { checkSchema(schema: Schema): void; processSchemaElements(elements: Schema[] | Table[] | Enum[] | TableGroup[] | Ref[], elementType: any): void; findOrCreateSchema(schemaName: string): Schema; - findTable(rawTable: any): Table; + findTable(schemaName: string | null, tableName: string): Table; processTablePartials(rawTablePartials: any[]): TablePartial[]; findTablePartial(partialName: string): TablePartial; export(): { @@ -313,6 +316,8 @@ export interface NormalizedModel { schemas: NormalizedSchemaIdMap; endpoints: NormalizedEndpointIdMap; refs: NormalizedRefIdMap; + deps: NormalizedDepIdMap; + depEdges: NormalizedDepEdgeIdMap; fields: NormalizedFieldIdMap; tables: NormalizedTableIdMap; tableGroups: NormalizedTableGroupIdMap; diff --git a/packages/dbml-core/types/model_structure/dep.d.ts b/packages/dbml-core/types/model_structure/dep.d.ts new file mode 100644 index 000000000..caaf2b919 --- /dev/null +++ b/packages/dbml-core/types/model_structure/dep.d.ts @@ -0,0 +1,87 @@ +import Element, { Token, Color } from './element'; +import DepEdge from './dep_edge'; +import Schema from './schema'; +import DbState from './dbState'; +import Database, { NormalizedModel } from './database'; + +export interface RawDepEndpoint { + schemaName?: string | null; + tableName: string; + fieldNames?: string[]; +} + +export interface RawDepEdgeInput { + upstream: RawDepEndpoint; + downstream: RawDepEndpoint; + token?: Token; +} + +export interface RawDep { + name?: string | null; + color?: Color; + note?: { value?: string; token?: Token } | string | null; + metadata?: Record | null; + edges: RawDepEdgeInput[]; + token?: Token; + schema: Schema; +} + +declare class Dep extends Element { + name: string | null; + color?: Color; + note: string | null; + noteToken: Token; + metadata: Record | null; + edges: DepEdge[]; + schema: Schema; + dbState: DbState; + id: number; + constructor({ name, note, metadata, edges, token, schema }: RawDep); + generateId(): void; + processEdges(rawEdges: RawDepEdgeInput[]): void; + export(): { + name: string | null; + color?: Color; + note: string | null; + metadata: Record | null; + edges: { + upstream: { schemaName: string | null; tableName: string; fieldNames: string[] }; + downstream: { schemaName: string | null; tableName: string; fieldNames: string[] }; + }[]; + }; + shallowExport(): { + name: string | null; + color?: Color; + note: string | null; + metadata: Record | null; + }; + exportChild(): { + edges: { + upstream: { schemaName: string | null; tableName: string; fieldNames: string[] }; + downstream: { schemaName: string | null; tableName: string; fieldNames: string[] }; + }[]; + }; + exportChildIds(): { + edgeIds: number[]; + }; + exportParentIds(): { + schemaId: number; + }; + normalize(model: NormalizedModel): void; +} + +export interface NormalizedDep { + id: number; + name: string | null; + color?: Color; + note: string | null; + metadata: Record | null; + schemaId: number; + edgeIds: number[]; +} + +export interface NormalizedDepIdMap { + [_id: number]: NormalizedDep; +} + +export default Dep; diff --git a/packages/dbml-core/types/model_structure/dep_edge.d.ts b/packages/dbml-core/types/model_structure/dep_edge.d.ts new file mode 100644 index 000000000..3161cdf93 --- /dev/null +++ b/packages/dbml-core/types/model_structure/dep_edge.d.ts @@ -0,0 +1,67 @@ +import Element, { Token } from './element'; +import Field from './field'; +import Table from './table'; +import Dep from './dep'; +import DbState from './dbState'; +import { NormalizedModel } from './database'; + +export interface DepEndpointData { + schemaName: string | null; + tableName: string; + fieldNames: string[]; +} + +export interface RawDepEdge { + upstream: DepEndpointData; + downstream: DepEndpointData; + token?: Token; + dep: Dep; +} + +declare class DepEdge extends Element { + upstream: DepEndpointData; + downstream: DepEndpointData; + dep: Dep; + dbState: DbState; + id: number; + upstreamTable: Table | null; + upstreamFields: Field[]; + downstreamTable: Table | null; + downstreamFields: Field[]; + constructor(args: RawDepEdge); + generateId(): void; + resolveEndpoint(endpointData: { schemaName?: string | null; tableName: string; fieldNames?: string[] }, database: import('./database').default): { table: Table; fields: Field[] }; + export(): { + upstream: DepEndpointData; + downstream: DepEndpointData; + }; + shallowExport(): { + upstream: DepEndpointData; + downstream: DepEndpointData; + }; + exportParentIds(): { + depId: number; + upstreamTableId: number | null; + upstreamFieldIds: number[]; + downstreamTableId: number | null; + downstreamFieldIds: number[]; + }; + normalize(model: NormalizedModel): void; +} + +export interface NormalizedDepEdge { + id: number; + upstream: DepEndpointData; + downstream: DepEndpointData; + depId: number; + upstreamTableId: number | null; + upstreamFieldIds: number[]; + downstreamTableId: number | null; + downstreamFieldIds: number[]; +} + +export interface NormalizedDepEdgeIdMap { + [_id: number]: NormalizedDepEdge; +} + +export default DepEdge; diff --git a/packages/dbml-core/types/model_structure/field.d.ts b/packages/dbml-core/types/model_structure/field.d.ts index 8e322abaa..e776ad49b 100644 --- a/packages/dbml-core/types/model_structure/field.d.ts +++ b/packages/dbml-core/types/model_structure/field.d.ts @@ -2,6 +2,7 @@ import { NormalizedModel } from './database'; import DbState from './dbState'; import Element, { Token, RawNote } from './element'; import Endpoint from './endpoint'; +import DepEdge from './dep_edge'; import Enum from './enum'; import Table from './table'; import TablePartial from './tablePartial'; @@ -49,12 +50,14 @@ declare class Field extends Element { checks: Check[]; table: Table; endpoints: Endpoint[]; + depEdges: DepEdge[]; _enum: Enum; injectedPartial?: TablePartial; injectedToken: Token; constructor({ name, type, unique, pk, token, not_null, note, dbdefault, increment, checks, table }: RawField); generateId(): void; pushEndpoint(endpoint: any): void; + pushDepEdge(depEdge: DepEdge): void; processChecks(checks: any[]): void; export(): { name: string; @@ -73,6 +76,7 @@ declare class Field extends Element { }; exportChildIds(): { endpointIds: number[]; + depEdgeIds: number[]; }; shallowExport(): { name: string; @@ -106,6 +110,7 @@ export interface NormalizedField { }; increment: boolean; endpointIds: number[]; + depEdgeIds: number[]; tableId: number; enumId: number | null; injectedPartialId: number | null; diff --git a/packages/dbml-core/types/model_structure/schema.d.ts b/packages/dbml-core/types/model_structure/schema.d.ts index 4afebf4ae..c36be578e 100644 --- a/packages/dbml-core/types/model_structure/schema.d.ts +++ b/packages/dbml-core/types/model_structure/schema.d.ts @@ -3,6 +3,7 @@ import Element, { RawNote, Token, Color } from './element'; import Enum from './enum'; import TableGroup from './tableGroup'; import Ref from './ref'; +import Dep from './dep'; import Database, { NormalizedModel } from './database'; import DbState from './dbState'; export interface RawSchema { @@ -11,6 +12,7 @@ export interface RawSchema { note?: RawNote; tables?: Table[]; refs?: Ref[]; + deps?: Dep[]; enums?: Enum[]; tableGroups?: TableGroup[]; token?: Token; @@ -23,6 +25,7 @@ declare class Schema extends Element { noteToken: Token; tables: Table[]; refs: Ref[]; + deps: Dep[]; enums: Enum[]; tableGroups: TableGroup[]; database: Database; @@ -182,6 +185,7 @@ export interface NormalizedSchema { tableIds: number[]; noteIds: number[]; refIds: number[]; + depIds: number[]; tableGroupIds: number[]; enumIds: number[]; databaseId: number; diff --git a/packages/dbml-core/types/transform/index.d.ts b/packages/dbml-core/types/transform/index.d.ts index 56f813a1a..c271ac6c9 100644 --- a/packages/dbml-core/types/transform/index.d.ts +++ b/packages/dbml-core/types/transform/index.d.ts @@ -1,13 +1,28 @@ -import type { DiagramViewSyncOperation, DiagramViewBlock, TextEdit } from '@dbml/parse'; - -export type TableNameInput = string | { schema?: string; table: string }; +import type { + DiagramViewSyncOperation, DiagramViewBlock, TextEdit, + DepSyncOperation, DepBlock, ElementIdentifier, TableIdentifier, +} from '@dbml/parse'; export function renameTable( - oldName: TableNameInput, - newName: TableNameInput, + oldName: string | TableIdentifier, + newName: string | TableIdentifier, dbmlCode: string ): string; +export function updateElementSettingEdit( + dbmlCode: string, + target: ElementIdentifier, + settingName: string, + value: string | null | undefined, +): TextEdit[]; + +export function updateElementSetting( + dbmlCode: string, + target: ElementIdentifier, + settingName: string, + value: string | null | undefined, +): string; + export function syncDiagramView( dbmlCode: string, operations: DiagramViewSyncOperation[], @@ -17,3 +32,13 @@ export function syncDiagramView( export function findDiagramViewBlocks( dbmlCode: string, ): DiagramViewBlock[]; + +export function syncDep( + dbmlCode: string, + operations: DepSyncOperation[], + blocks?: DepBlock[], +): { newDbml: string; edits: TextEdit[] }; + +export function findDepBlocks( + dbmlCode: string, +): DepBlock[]; diff --git a/packages/dbml-parse/__tests__/examples/binder/binder.test.ts b/packages/dbml-parse/__tests__/examples/binder/binder.test.ts index fe0506d01..a99caa69c 100644 --- a/packages/dbml-parse/__tests__/examples/binder/binder.test.ts +++ b/packages/dbml-parse/__tests__/examples/binder/binder.test.ts @@ -689,6 +689,20 @@ describe('[example] binder', () => { expect(findMember(compiler, schemaSymbol, SymbolKind.Table, 'posts')).toSatisfy((s: any) => s?.isKind(SymbolKind.Table)); }); + test('should error when inline ref target matches table name but resolves differently', () => { + // Table a.b.R means schema=a, schema=b, table=R + // But ref a.b.R resolves as schema=a, table=b, column=R + // So it should error because b is a schema, not a table + const source = ` + Table a.b.R { + id int [ref: < a.b.R] + } + `; + const errors = analyze(source).getErrors(); + expect(errors.length).toBeGreaterThan(0); + expect(errors.some((e) => e.diagnostic.includes('R'))).toBe(true); + }); + test('should allow forward reference to table', () => { const source = ` Ref: posts.user_id > users.id diff --git a/packages/dbml-parse/__tests__/examples/compiler/note.test.ts b/packages/dbml-parse/__tests__/examples/compiler/note.test.ts new file mode 100644 index 000000000..9e409a97d --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/compiler/note.test.ts @@ -0,0 +1,186 @@ +import { + describe, expect, it, +} from 'vitest'; +import Compiler from '@/compiler/index'; +import { MemoryProjectLayout } from '@/compiler/projectLayout/layout'; +import { DEFAULT_ENTRY } from '@/constants'; +import { ElementDeclarationNode } from '@/core/types/nodes'; +import { ElementKind } from '@/core/types/keywords'; +import { updateNoteEdit, removeNoteEdit, addNoteEdit } from '@/core/utils/note'; +import { applyTextEdits } from '@/compiler/queries/transform/applyTextEdits'; + +function parse (dbml: string) { + const layout = new MemoryProjectLayout(); + layout.setSource(DEFAULT_ENTRY, dbml); + const compiler = new Compiler(layout); + const ast = compiler.parseFile(DEFAULT_ENTRY).getValue()!.ast; + return { compiler, ast, dbml }; +} + +function findElement (dbml: string, kind: ElementKind, index = 0): { declaration: ElementDeclarationNode; source: string } { + const { ast } = parse(dbml); + const declarations = ast.declarations + .filter((d): d is ElementDeclarationNode => d instanceof ElementDeclarationNode && d.isKind(kind)); + return { declaration: declarations[index], source: dbml }; +} + +function applyEdit (source: string, edit: { start: number; end: number; newText: string }) { + return applyTextEdits(source, [edit]); +} + + +describe('updateNoteEdit - table', () => { + it('updates a body Note sub-element', () => { + const dbml = `Table t {\n id int\n Note: 'old'\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Table); + const edit = updateNoteEdit(declaration, 'new'); + expect(edit).toBeDefined(); + expect(applyEdit(source, edit!)).toContain("'new'"); + expect(applyEdit(source, edit!)).not.toContain("'old'"); + }); + + it('updates a block Note sub-element', () => { + const dbml = `Table t {\n id int\n Note {\n 'old'\n }\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Table); + const edit = updateNoteEdit(declaration, 'new'); + expect(edit).toBeDefined(); + expect(applyEdit(source, edit!)).toContain("'new'"); + }); + + it('updates a setting attribute note', () => { + const dbml = `Table t [note: 'old'] {\n id int\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Table); + const edit = updateNoteEdit(declaration, 'new'); + expect(edit).toBeDefined(); + expect(applyEdit(source, edit!)).toContain("'new'"); + }); + + it('returns undefined if no note exists', () => { + const dbml = `Table t {\n id int\n}`; + const { declaration } = findElement(dbml, ElementKind.Table); + expect(updateNoteEdit(declaration, 'new')).toBeUndefined(); + }); +}); + +describe('removeNoteEdit - table', () => { + it('removes a body Note sub-element', () => { + const dbml = `Table t {\n id int\n Note: 'old'\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Table); + const edit = removeNoteEdit(declaration); + expect(edit).toBeDefined(); + expect(applyEdit(source, edit!)).not.toContain('Note'); + expect(applyEdit(source, edit!)).not.toContain('old'); + }); + + it('removes a setting attribute note (sole setting)', () => { + const dbml = `Table t [note: 'old'] {\n id int\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Table); + const edit = removeNoteEdit(declaration); + expect(edit).toBeDefined(); + const result = applyEdit(source, edit!); + expect(result).not.toContain('note'); + expect(result).not.toContain('['); + }); + + it('returns undefined if no note exists', () => { + const dbml = `Table t {\n id int\n}`; + const { declaration } = findElement(dbml, ElementKind.Table); + expect(removeNoteEdit(declaration)).toBeUndefined(); + }); +}); + +describe('addNoteEdit - table', () => { + it('adds a body note to a block-form table', () => { + const dbml = `Table t {\n id int\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Table); + const edit = addNoteEdit(declaration, 'hello'); + expect(edit).toBeDefined(); + const result = applyEdit(source, edit!); + expect(result).toContain("note: 'hello'"); + }); + + it('returns undefined if note already exists', () => { + const dbml = `Table t {\n id int\n Note: 'existing'\n}`; + const { declaration } = findElement(dbml, ElementKind.Table); + expect(addNoteEdit(declaration, 'new')).toBeUndefined(); + }); +}); + + +describe('updateNoteEdit - dep', () => { + it('updates a body sub-declaration note', () => { + const dbml = `Table a { id int }\nTable b { id int }\nDep {\n a -> b\n note: 'old'\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Dep); + const edit = updateNoteEdit(declaration, 'new'); + expect(edit).toBeDefined(); + expect(applyEdit(source, edit!)).toContain("'new'"); + expect(applyEdit(source, edit!)).not.toContain("'old'"); + }); + + it('updates a header setting note', () => { + const dbml = `Table a { id int }\nTable b { id int }\nDep [note: 'old'] {\n a -> b\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Dep); + const edit = updateNoteEdit(declaration, 'new'); + expect(edit).toBeDefined(); + expect(applyEdit(source, edit!)).toContain("'new'"); + }); +}); + +describe('removeNoteEdit - dep', () => { + it('removes a body sub-declaration note', () => { + const dbml = `Table a { id int }\nTable b { id int }\nDep {\n a -> b\n note: 'old'\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Dep); + const edit = removeNoteEdit(declaration); + expect(edit).toBeDefined(); + const result = applyEdit(source, edit!); + expect(result).not.toContain("note: 'old'"); + expect(result).toContain('a -> b'); + }); +}); + +describe('addNoteEdit - dep', () => { + it('adds a body note to a block-form dep', () => { + const dbml = `Table a { id int }\nTable b { id int }\nDep {\n a -> b\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Dep); + const edit = addNoteEdit(declaration, 'hello'); + expect(edit).toBeDefined(); + const result = applyEdit(source, edit!); + expect(result).toContain("note: 'hello'"); + }); + + it('adds a setting note to a short-form dep', () => { + const dbml = `Table a { id int }\nTable b { id int }\nDep: a -> b`; + const { declaration, source } = findElement(dbml, ElementKind.Dep); + const edit = addNoteEdit(declaration, 'hello'); + expect(edit).toBeDefined(); + const result = applyEdit(source, edit!); + expect(result).toContain("[note: 'hello']"); + }); + + it('appends to existing settings on a short-form dep', () => { + const dbml = `Table a { id int }\nTable b { id int }\nDep: a -> b [color: #fff]`; + const { declaration, source } = findElement(dbml, ElementKind.Dep); + const edit = addNoteEdit(declaration, 'hello'); + expect(edit).toBeDefined(); + const result = applyEdit(source, edit!); + expect(result).toContain("color: #fff, note: 'hello'"); + }); + + it('returns undefined if note already exists', () => { + const dbml = `Table a { id int }\nTable b { id int }\nDep {\n a -> b\n note: 'existing'\n}`; + const { declaration } = findElement(dbml, ElementKind.Dep); + expect(addNoteEdit(declaration, 'new')).toBeUndefined(); + }); +}); + + +describe('multiline notes', () => { + it('uses triple quotes for multiline values', () => { + const dbml = `Table t {\n id int\n}`; + const { declaration, source } = findElement(dbml, ElementKind.Table); + const edit = addNoteEdit(declaration, 'line1\nline2'); + expect(edit).toBeDefined(); + const result = applyEdit(source, edit!); + expect(result).toContain("'''\nline1\nline2\n'''"); + }); +}); diff --git a/packages/dbml-parse/__tests__/examples/compiler/renameTable.test.ts b/packages/dbml-parse/__tests__/examples/compiler/renameTable.test.ts index bec29c8fd..eccdd5742 100644 --- a/packages/dbml-parse/__tests__/examples/compiler/renameTable.test.ts +++ b/packages/dbml-parse/__tests__/examples/compiler/renameTable.test.ts @@ -4,13 +4,13 @@ import { import { DEFAULT_ENTRY } from '@/constants'; import Compiler from '@/compiler/index'; import { MemoryProjectLayout } from '@/compiler/projectLayout/layout'; -import { TableNameInput } from '@/compiler/queries/transform'; +import type { TableIdentifier } from '@/compiler/queries/transform/types'; import { Filepath } from '@/core/types/filepath'; import { setupCompiler, fp } from '../interpreter/multifile/utils'; function renameTable ( - oldName: TableNameInput, - newName: TableNameInput, + oldName: string | TableIdentifier, + newName: string | TableIdentifier, input: string, ): string { const layout = new MemoryProjectLayout(); diff --git a/packages/dbml-parse/__tests__/examples/compiler/syncDep.test.ts b/packages/dbml-parse/__tests__/examples/compiler/syncDep.test.ts new file mode 100644 index 000000000..19442731b --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/compiler/syncDep.test.ts @@ -0,0 +1,239 @@ +import { + describe, expect, it, +} from 'vitest'; +import Compiler from '@/compiler/index'; +import { MemoryProjectLayout } from '@/compiler/projectLayout/layout'; +import { DEFAULT_ENTRY } from '@/constants'; +import type { DepSyncOperation } from '@/compiler/queries/transform/syncDep'; +import { interpret } from '@tests/utils'; + +function syncDep (dbml: string, operations: DepSyncOperation[]) { + const layout = new MemoryProjectLayout(); + layout.setSource(DEFAULT_ENTRY, dbml); + const compiler = new Compiler(layout); + return compiler.syncDep(DEFAULT_ENTRY, operations); +} + +const PRELUDE = ` +Table a { id int } +Table b { id int } +`; + +const tableEdge = (up: string, down: string): DepSyncOperation['edge'] => ({ + upstream: { tableName: up, fieldNames: [] }, + downstream: { tableName: down, fieldNames: [] }, +}); + +describe('syncDep - create', () => { + it('creates a direct Dep block with a color when no block exists (aggregate table-level line)', () => { + const { newDbml } = syncDep(PRELUDE, [ + { operation: 'create', edge: tableEdge('a', 'b'), color: '#aabbcc' }, + ]); + expect(newDbml).toContain('Dep [color: #aabbcc] {'); + expect(newDbml).toContain('a -> b'); + // round-trips: color reaches the model + const db = interpret(newDbml).getValue()!; + const dep = db.deps.find((d) => d.edges[0].upstream.tableName === 'a' && d.edges[0].downstream.tableName === 'b'); + expect(dep?.color).toBe('#aabbcc'); + }); + + it('treats an existing matching block as an update - no duplicate Dep', () => { + const dbml = `${PRELUDE} +Dep { + a -> b +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'create', edge: tableEdge('a', 'b'), color: '#123456' }, + ]); + // exactly one Dep block + expect(newDbml.match(/Dep\s*\{/g)?.length ?? newDbml.match(/Dep\b/g)?.length).toBe(1); + expect(newDbml).toContain('[color: #123456]'); + const db = interpret(newDbml).getValue()!; + expect(db.deps).toHaveLength(1); + expect(db.deps[0].color).toBe('#123456'); + }); +}); + +describe('syncDep - update existing block color', () => { + it('overwrites a header [color] in place', () => { + const dbml = `${PRELUDE} +Dep [color: #000000] { + a -> b +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: '#ff0000' }, + ]); + expect(newDbml).toContain('#ff0000'); + expect(newDbml).not.toContain('#000000'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBe('#ff0000'); + }); + + it('overwrites a body sub-declaration color in place', () => { + const dbml = `${PRELUDE} +Dep { + a -> b + color: #000000 +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: '#00ff00' }, + ]); + expect(newDbml).toContain('#00ff00'); + expect(newDbml).not.toContain('#000000'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBe('#00ff00'); + }); + + it('overwrites an inline short-form [color] in place', () => { + const dbml = `${PRELUDE} +Dep: a -> b [color: #000000]`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: '#0000ff' }, + ]); + expect(newDbml).toContain('#0000ff'); + expect(newDbml).not.toContain('#000000'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBe('#0000ff'); + }); + + it('adds a [color] to a block that has none', () => { + const dbml = `${PRELUDE} +Dep { + a -> b +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: '#abcdef' }, + ]); + expect(newDbml).toContain('[color: #abcdef]'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBe('#abcdef'); + }); + + it('matches a column-level edge to update its block', () => { + const dbml = `${PRELUDE} +Dep { + a.id -> b.id +}`; + const { newDbml } = syncDep(dbml, [ + { + operation: 'update', + edge: { + upstream: { tableName: 'a', fieldNames: ['id'] }, + downstream: { tableName: 'b', fieldNames: ['id'] }, + }, + color: '#abcabc', + }, + ]); + expect(newDbml).toContain('[color: #abcabc]'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBe('#abcabc'); + }); + + it('matches a table-level edge to a column-level block (implicit table dep)', () => { + const dbml = `${PRELUDE} +Dep { + a.id -> b.id +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: '#1abc9c' }, + ]); + expect(newDbml).toContain('[color: #1abc9c]'); + // Should update in place, not create a duplicate + expect(newDbml.match(/Dep/g)?.length).toBe(1); + const db = interpret(newDbml).getValue()!; + expect(db.deps).toHaveLength(1); + expect(db.deps[0].color).toBe('#1abc9c'); + }); +}); + +describe('syncDep - create with table-level edge matching column-level block', () => { + it('treats an existing column-level block as an update when creating with a table-level edge', () => { + const dbml = `${PRELUDE} +Dep { + a.id -> b.id +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'create', edge: tableEdge('a', 'b'), color: '#ff5733' }, + ]); + // Should update existing block, not create a new one + expect(newDbml.match(/Dep/g)?.length).toBe(1); + expect(newDbml).toContain('[color: #ff5733]'); + const db = interpret(newDbml).getValue()!; + expect(db.deps).toHaveLength(1); + expect(db.deps[0].color).toBe('#ff5733'); + }); +}); + +describe('syncDep - remove color via update', () => { + it('removes a sole header [color] together with its [...] list', () => { + const dbml = `${PRELUDE} +Dep [color: #000000] { + a -> b +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: null }, + ]); + expect(newDbml).not.toContain('color'); + expect(newDbml).not.toContain('['); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBeUndefined(); + }); + + it('removes a header [color] but keeps other settings and one comma', () => { + const dbml = `${PRELUDE} +Dep [note: "x", color: #000000] { + a -> b +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: null }, + ]); + expect(newDbml).not.toContain('color'); + expect(newDbml).toContain('note: "x"'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBeUndefined(); + }); + + it('removes a body sub-declaration color', () => { + const dbml = `${PRELUDE} +Dep { + a -> b + color: #000000 +}`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: null }, + ]); + expect(newDbml).not.toContain('#000000'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBeUndefined(); + }); + + it('removes an inline short-form [color]', () => { + const dbml = `${PRELUDE} +Dep: a -> b [color: #000000]`; + const { newDbml } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: null }, + ]); + expect(newDbml).not.toContain('color'); + const db = interpret(newDbml).getValue()!; + expect(db.deps[0].color).toBeUndefined(); + }); + + it('is a no-op for an edge with no block', () => { + const { newDbml, edits } = syncDep(PRELUDE, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: null }, + ]); + expect(edits).toHaveLength(0); + expect(newDbml).toBe(PRELUDE); + }); + + it('is a no-op for a block that has no color', () => { + const dbml = `${PRELUDE} +Dep { + a -> b +}`; + const { edits } = syncDep(dbml, [ + { operation: 'update', edge: tableEdge('a', 'b'), color: null }, + ]); + expect(edits).toHaveLength(0); + }); +}); diff --git a/packages/dbml-parse/__tests__/examples/compiler/updateElementSetting.test.ts b/packages/dbml-parse/__tests__/examples/compiler/updateElementSetting.test.ts new file mode 100644 index 000000000..ed1f9155f --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/compiler/updateElementSetting.test.ts @@ -0,0 +1,323 @@ +import { + describe, expect, it, +} from 'vitest'; +import Compiler from '@/compiler/index'; +import { MemoryProjectLayout } from '@/compiler/projectLayout/layout'; +import { DEFAULT_ENTRY } from '@/constants'; +import { SymbolKind } from '@/core/types/symbol'; +import { MetadataKind } from '@/core/types/symbol/metadata'; +import type { ElementIdentifier } from '@/compiler/queries/transform/types'; + +function update (dbml: string, target: ElementIdentifier, settingName: string, value: string | null | undefined) { + const layout = new MemoryProjectLayout(); + layout.setSource(DEFAULT_ENTRY, dbml); + const compiler = new Compiler(layout); + return compiler.updateElementSetting(DEFAULT_ENTRY, target, settingName, value); +} + +describe('updateElementSetting - table', () => { + it('adds a setting to a table with no settings', () => { + const dbml = `Table users { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'users' }, 'headercolor', '#FF0000'); + expect(result).toContain('[headercolor: #FF0000]'); + }); + + it('updates an existing setting on a table', () => { + const dbml = `Table users [headercolor: #000000] { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'users' }, 'headercolor', '#FF0000'); + expect(result).toContain('headercolor: #FF0000'); + expect(result).not.toContain('#000000'); + }); + + it('removes a setting when value is null', () => { + const dbml = `Table users [headercolor: #FF0000] { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'users' }, 'headercolor', null); + expect(result).not.toContain('headercolor'); + expect(result).not.toContain('#FF0000'); + }); + + it('returns original source when setting to remove does not exist', () => { + const dbml = `Table users { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'users' }, 'headercolor', null); + expect(result).toBe(dbml); + }); + + it('returns original source when target element is not found', () => { + const dbml = `Table users { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'nonexistent' }, 'headercolor', '#FF0000'); + expect(result).toBe(dbml); + }); + + it('works with a schema-qualified identifier', () => { + const dbml = `Table myschema.users { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, schema: 'myschema', table: 'users' }, 'headercolor', '#FF0000'); + expect(result).toContain('[headercolor: #FF0000]'); + }); +}); + +describe('updateElementSetting - name-only setting', () => { + it('adds a name-only setting when value is undefined', () => { + const dbml = `Table users { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'users' }, 'pk', undefined); + expect(result).toMatch(/\[pk\]/); + }); +}); + +describe('updateElementSetting - table with multiple settings', () => { + it('removes one setting and keeps the other', () => { + const dbml = `Table users [headercolor: #FF0000, note: 'test'] { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'users' }, 'headercolor', null); + expect(result).not.toContain('headercolor'); + expect(result).toContain('note'); + }); + + it('updates one setting among multiple', () => { + const dbml = `Table users [headercolor: #FF0000, note: 'test'] { + id int +}`; + const result = update(dbml, { kind: SymbolKind.Table, table: 'users' }, 'headercolor', '#00FF00'); + expect(result).toContain('headercolor: #00FF00'); + expect(result).toContain('note'); + }); +}); + +describe('updateElementSetting - tablegroup', () => { + it('adds a setting to a tablegroup', () => { + const dbml = `Table users { + id int +} +TableGroup mygroup { + users +}`; + const result = update(dbml, { kind: SymbolKind.TableGroup, name: 'mygroup' }, 'color', '#FF0000'); + expect(result).toContain('color: #FF0000'); + }); +}); + +describe('updateElementSetting - enum', () => { + it('adds a setting to an enum', () => { + const dbml = `Enum status { + active + inactive +}`; + const result = update(dbml, { kind: SymbolKind.Enum, name: 'status' }, 'note', "'Status enum'"); + expect(result).toContain('note'); + }); +}); + +const PRELUDE = ` +Table a { + id int +} +Table b { + id int +} +`; + +describe('updateElementSetting - dep', () => { + const dep = (up: string, down: string): ElementIdentifier => ({ + kind: MetadataKind.Dep, + upstream: { tableName: up }, + downstream: { tableName: down }, + }); + + it('adds color to a dep block header', () => { + const dbml = `${PRELUDE}Dep { + a -> b +}`; + const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); + expect(result).toContain('color'); + expect(result).toContain('#FF0000'); + }); + + it('updates existing color on a dep block header', () => { + const dbml = `${PRELUDE}Dep [color: #000000] { + a -> b +}`; + const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); + expect(result).toContain('#FF0000'); + expect(result).not.toContain('#000000'); + }); + + it('removes color from a dep block', () => { + const dbml = `${PRELUDE}Dep [color: #FF0000] { + a -> b +}`; + const result = update(dbml, dep('a', 'b'), 'color', null); + expect(result).not.toContain('#FF0000'); + expect(result).not.toContain('color'); + }); + + it('updates color in dep body form', () => { + const dbml = `${PRELUDE}Dep { + a -> b + color: #000000 +}`; + const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); + expect(result).toContain('#FF0000'); + expect(result).not.toContain('#000000'); + }); + + it('adds color to a short-form dep', () => { + const dbml = `${PRELUDE}Dep: a -> b`; + const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); + expect(result).toContain('color'); + expect(result).toContain('#FF0000'); + }); + + it('updates color on short-form dep header', () => { + const dbml = `${PRELUDE}Dep [color: #000000]: a -> b`; + const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); + expect(result).toContain('#FF0000'); + expect(result).not.toContain('#000000'); + }); + + it('updates color on short-form dep edge node', () => { + const dbml = `${PRELUDE}Dep: a -> b [color: #000000]`; + const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); + expect(result).toContain('#FF0000'); + expect(result).not.toContain('#000000'); + }); + + it('removes color from short-form dep edge node', () => { + const dbml = `${PRELUDE}Dep: a -> b [color: #FF0000]`; + const result = update(dbml, dep('a', 'b'), 'color', null); + expect(result).not.toContain('#FF0000'); + }); + + it('returns original when dep not found', () => { + const dbml = `${PRELUDE}Dep { + a -> b +}`; + const result = update(dbml, dep('a', 'nonexistent'), 'color', '#FF0000'); + expect(result).toBe(dbml); + }); +}); + +describe('updateElementSetting - ref', () => { + const ref = (left: string[], right: string[]): ElementIdentifier => ({ + kind: MetadataKind.Ref, + endpoints: [ + { tableName: left[0], fieldNames: left.slice(1) }, + { tableName: right[0], fieldNames: right.slice(1) }, + ], + }); + + it('adds color to a short-form ref', () => { + const dbml = `${PRELUDE}Ref: a.id > b.id`; + const result = update(dbml, ref(['a', 'id'], ['b', 'id']), 'color', '#FF0000'); + expect(result).toContain('color'); + expect(result).toContain('#FF0000'); + }); + + it('updates existing color on a ref', () => { + const dbml = `${PRELUDE}Ref: a.id > b.id [color: #000000]`; + const result = update(dbml, ref(['a', 'id'], ['b', 'id']), 'color', '#FF0000'); + expect(result).toContain('#FF0000'); + expect(result).not.toContain('#000000'); + }); + + it('removes color from a ref', () => { + const dbml = `${PRELUDE}Ref: a.id > b.id [color: #FF0000]`; + const result = update(dbml, ref(['a', 'id'], ['b', 'id']), 'color', null); + expect(result).not.toContain('#FF0000'); + }); + + it('updates color on a ref edge in block form', () => { + const dbml = `${PRELUDE}Ref { + a.id > b.id [color: #000000] +}`; + const result = update(dbml, ref(['a', 'id'], ['b', 'id']), 'color', '#FF0000'); + expect(result).toContain('#FF0000'); + expect(result).not.toContain('#000000'); + }); + + it('returns original when ref not found', () => { + const dbml = `${PRELUDE}Ref: a.id > b.id`; + const result = update(dbml, ref(['a', 'id'], ['b', 'nonexistent']), 'color', '#FF0000'); + expect(result).toBe(dbml); + }); + + it('extracts inline ref to standalone when adding a setting', () => { + const dbml = `Table a { + id int [ref: > b.id] +} +Table b { + id int +}`; + const result = update(dbml, ref(['a', 'id'], ['b', 'id']), 'color', '#FF0000'); + expect(result).not.toContain('[ref: > b.id]'); + expect(result).toContain('Ref: a.id > b.id [color: #FF0000]'); + }); + + it('extracts inline ref with name-only setting', () => { + const dbml = `Table a { + id int [ref: > b.id] +} +Table b { + id int +}`; + const result = update(dbml, ref(['a', 'id'], ['b', 'id']), 'pk', undefined); + expect(result).not.toContain('[ref: > b.id]'); + expect(result).toContain('Ref: a.id > b.id [pk]'); + }); + + it('does nothing when removing a setting from an inline ref', () => { + const dbml = `Table a { + id int [ref: > b.id] +} +Table b { + id int +}`; + const result = update(dbml, ref(['a', 'id'], ['b', 'id']), 'color', null); + expect(result).toBe(dbml); + }); +}); + +describe('updateElementSetting - inline dep', () => { + const dep = (up: string, down: string): ElementIdentifier => ({ + kind: MetadataKind.Dep, + upstream: { tableName: up }, + downstream: { tableName: down }, + }); + + it('extracts inline dep to standalone when adding a setting', () => { + const dbml = `Table a { + id int [dep: -> b] +} +Table b { + id int +}`; + const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); + expect(result).not.toContain('[dep: -> b]'); + expect(result).toContain('color: #FF0000'); + expect(result).toContain('a -> b'); + }); + + it('does nothing when removing a setting from an inline dep', () => { + const dbml = `Table a { + id int [dep: -> b] +} +Table b { + id int +}`; + const result = update(dbml, dep('a', 'b'), 'color', null); + expect(result).toBe(dbml); + }); +}); diff --git a/packages/dbml-parse/__tests__/examples/interpreter/dep_downstream.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/dep_downstream.test.ts new file mode 100644 index 000000000..cf22daadb --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/interpreter/dep_downstream.test.ts @@ -0,0 +1,150 @@ +import { describe, expect, it } from 'vitest'; +import { interpret } from '@tests/utils'; +import { CompileErrorCode } from '@/core/types/errors'; + +const PRELUDE = ` +Table a { id int } +Table b { id int } +Table c { id int } +`; + +describe('dep downstream table constraints', () => { + describe('complex dep block: single downstream table', () => { + it('allows all edges targeting the same downstream table', () => { + const result = interpret(`${PRELUDE} +Dep { + a -> b + c -> b +}`); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(1); + }); + + it('errors when block mixes table-level and column-level edges', () => { + const errors = interpret(`${PRELUDE} +Dep { + a -> b + a.id -> b.id +}`).getErrors(); + expect(errors.some((e) => e.code === CompileErrorCode.DEP_MIXED_LEVEL)).toBe(true); + }); + + it('errors when edges target different downstream tables', () => { + const errors = interpret(`${PRELUDE} +Dep { + a -> b + a -> c +}`).getErrors(); + expect(errors.some((e) => e.code === CompileErrorCode.DEP_MIXED_DOWNSTREAM_TABLES)).toBe(true); + }); + }); + + describe('mixed-level edges', () => { + it('allows a table-level upstream with a column-level downstream', () => { + const result = interpret(`${PRELUDE} +Dep: a -> b.id +`); + expect(result.getErrors()).toHaveLength(0); + const edge = result.getValue()?.deps?.[0]?.edges?.[0]; + expect(edge?.upstream.fieldNames).toEqual([]); + expect(edge?.downstream.fieldNames).toEqual(['id']); + }); + + it('allows a column-level upstream with a table-level downstream', () => { + const result = interpret(`${PRELUDE} +Dep: a.id -> b +`); + expect(result.getErrors()).toHaveLength(0); + const edge = result.getValue()?.deps?.[0]?.edges?.[0]; + expect(edge?.upstream.fieldNames).toEqual(['id']); + expect(edge?.downstream.fieldNames).toEqual([]); + }); + + it('allows a column feeding its own table', () => { + const result = interpret(`${PRELUDE} +Dep: a.id -> a +`); + expect(result.getErrors()).toHaveLength(0); + }); + + it('allows a table depending on itself', () => { + const result = interpret(`${PRELUDE} +Dep: a -> a +`); + expect(result.getErrors()).toHaveLength(0); + }); + + it('still errors when a column depends on itself', () => { + const errors = interpret(`${PRELUDE} +Dep: a.id -> a.id +`).getErrors(); + expect(errors.some((e) => e.code === CompileErrorCode.DEP_SELF_LOOP)).toBe(true); + }); + + it('allows mixed and column edges in one block (both column-level)', () => { + const result = interpret(`${PRELUDE} +Dep { + a -> b.id + a.id -> b.id +}`); + expect(result.getErrors()).toHaveLength(0); + }); + + it('errors when a block mixes a table-level edge with a mixed edge', () => { + const errors = interpret(`${PRELUDE} +Dep { + a -> b + a -> b.id +}`).getErrors(); + expect(errors.some((e) => e.code === CompileErrorCode.DEP_MIXED_LEVEL)).toBe(true); + }); + }); + + describe('no cross-block restriction on downstream tables', () => { + it('allows multiple simple deps targeting the same downstream', () => { + const result = interpret(`${PRELUDE} +Dep: a -> b +Dep: c -> b +`); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(2); + }); + + it('allows two complex blocks targeting the same downstream', () => { + const result = interpret(`${PRELUDE} +Dep { a -> b } +Dep { c -> b } +`); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(2); + }); + + it('allows simple dep alongside complex block with same downstream', () => { + const result = interpret(`${PRELUDE} +Dep { a -> b } +Dep: c -> b +`); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(2); + }); + + it('allows inline dep alongside complex block with same downstream', () => { + const result = interpret(` +Table a { id int } +Table b { id int [dep: -> a.id] } +Dep { b -> a } +`); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(2); + }); + + it('allows complex block alongside simple deps with different downstream', () => { + const result = interpret(`${PRELUDE} +Dep { a -> b } +Dep: a -> c +`); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(2); + }); + }); +}); diff --git a/packages/dbml-parse/__tests__/examples/interpreter/dep_note.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/dep_note.test.ts new file mode 100644 index 000000000..6f96a2699 --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/interpreter/dep_note.test.ts @@ -0,0 +1,259 @@ +import { describe, expect, it } from 'vitest'; +import { interpret, analyze } from '@tests/utils'; +import { CompileErrorCode } from '@/core/types/errors'; + +const PRELUDE = ` +Table a { id int } +Table b { id int } +`; + +function getDepNote (dbml: string): string | undefined { + const db = interpret(dbml).getValue(); + return db?.deps?.[0]?.note?.value; +} + +describe('dep note interpretation', () => { + it('reads Note sub-element inside body (capitalized)', () => { + const note = getDepNote(`${PRELUDE} +Dep { + a -> b + Note: 'hello' +}`); + expect(note).toBe('hello'); + }); + + it('reads note sub-declaration inside body (lowercase)', () => { + const note = getDepNote(`${PRELUDE} +Dep { + a -> b + note: 'hello' +}`); + expect(note).toBe('hello'); + }); + + it('reads note from header setting list', () => { + const note = getDepNote(`${PRELUDE} +Dep [note: 'from header'] { + a -> b +}`); + expect(note).toBe('from header'); + }); + + it('reads note from short-form setting list', () => { + const note = getDepNote(`${PRELUDE} +Dep: a -> b [note: 'short']`); + expect(note).toBe('short'); + }); + + it('body note overrides header note (last-write-wins)', () => { + const note = getDepNote(`${PRELUDE} +Dep [note: 'header'] { + a -> b + note: 'body' +}`); + expect(note).toBe('body'); + }); + + it('reads color sub-declaration inside body (lowercase)', () => { + const db = interpret(`${PRELUDE} +Dep { + a -> b + color: #aabbcc +}`).getValue(); + expect(db?.deps?.[0]?.color).toBe('#aabbcc'); + }); + + it('reads Color sub-declaration inside body (capitalized)', () => { + const db = interpret(`${PRELUDE} +Dep { + a -> b + Color: #aabbcc +}`).getValue(); + expect(db?.deps?.[0]?.color).toBe('#aabbcc'); + }); + + it('case insensitive: NOTE in body', () => { + const note = getDepNote(`${PRELUDE} +Dep { + a -> b + NOTE: 'loud' +}`); + expect(note).toBe('loud'); + }); + + it('note with column-level edges', () => { + const note = getDepNote(`${PRELUDE} +Dep { + a.id -> b.id + note: 'with columns' +}`); + expect(note).toBe('with columns'); + }); + + it('Note block form inside body', () => { + const note = getDepNote(`${PRELUDE} +Dep { + a -> b + Note { + 'block form' + } +}`); + expect(note).toBe('block form'); + }); +}); + +describe('dep self-ref', () => { + it('allows table-level self-ref', () => { + const result = interpret(` + Table a { id int } + Dep: a -> a + `); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(1); + }); + + it('allows table-level self-ref (block form)', () => { + const result = interpret(` + Table a { id int } + Dep { a -> a } + `); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(1); + }); + + it('errors on column-level self-ref', () => { + const result = interpret(` + Table a { id int } + Dep: a.id -> a.id + `); + expect(result.getErrors().some((e) => e.code === CompileErrorCode.DEP_SELF_LOOP)).toBe(true); + }); + + it('allows column-level dep between different columns of same table', () => { + const result = interpret(` + Table a { + id int + name varchar + } + Dep: a.id -> a.name + `); + expect(result.getErrors()).toHaveLength(0); + expect(result.getValue()?.deps).toHaveLength(1); + }); +}); + +describe('dep settings validation', () => { + it('valid: color in setting list', () => { + const errors = analyze(`${PRELUDE}Dep: a -> b [color: #aabbcc]`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: note in setting list', () => { + const errors = analyze(`${PRELUDE}Dep: a -> b [note: 'hello']`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: custom string setting', () => { + const errors = analyze(`${PRELUDE}Dep: a -> b [owner: 'data-team']`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: custom identifier setting', () => { + const errors = analyze(`${PRELUDE}Dep [materialized: view] { a -> b }`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: custom numeric setting', () => { + const errors = analyze(`${PRELUDE}Dep [priority: 1] { a -> b }`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: custom color setting', () => { + const errors = analyze(`${PRELUDE}Dep: a -> b [highlight: #ff0000]`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: multiple settings', () => { + const errors = analyze(`${PRELUDE}Dep: a -> b [color: #fff, note: 'x', owner: 'team']`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('invalid: color with non-color value', () => { + const errors = analyze(`${PRELUDE}Dep: a -> b [color: notacolor]`).getErrors(); + expect(errors.length).toBeGreaterThan(0); + expect(errors.some((e) => e.message.includes('color'))).toBe(true); + }); + + it('invalid: note with non-string value', () => { + const errors = analyze(`${PRELUDE}Dep: a -> b [note: 123]`).getErrors(); + expect(errors.length).toBeGreaterThan(0); + expect(errors.some((e) => e.message.includes('note'))).toBe(true); + }); +}); + +describe('dep body sub-element validation', () => { + it('valid: color body sub-declaration', () => { + const errors = analyze(`${PRELUDE} +Dep { + a -> b + color: #aabbcc +}`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: note body sub-declaration', () => { + const errors = analyze(`${PRELUDE} +Dep { + a -> b + note: 'hello' +}`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: custom string body sub-declaration', () => { + const errors = analyze(`${PRELUDE} +Dep { + a -> b + owner: 'data-team' +}`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: custom identifier body sub-declaration', () => { + const errors = analyze(`${PRELUDE} +Dep { + a -> b + materialized: view +}`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('valid: custom numeric body sub-declaration', () => { + const errors = analyze(`${PRELUDE} +Dep { + a -> b + priority: 1 +}`).getErrors(); + expect(errors).toHaveLength(0); + }); + + it('invalid: color body sub-declaration with non-color', () => { + const errors = analyze(`${PRELUDE} +Dep { + a -> b + color: notacolor +}`).getErrors(); + expect(errors.length).toBeGreaterThan(0); + expect(errors.some((e) => e.message.includes('color'))).toBe(true); + }); + + it('invalid: note body sub-declaration with non-string', () => { + const errors = analyze(`${PRELUDE} +Dep { + a -> b + note: 123 +}`).getErrors(); + expect(errors.length).toBeGreaterThan(0); + expect(errors.some((e) => e.message.includes('note'))).toBe(true); + }); +}); diff --git a/packages/dbml-parse/__tests__/examples/interpreter/multifile/dep.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/multifile/dep.test.ts new file mode 100644 index 000000000..fa481b20f --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/interpreter/multifile/dep.test.ts @@ -0,0 +1,368 @@ +import { describe, expect, test } from 'vitest'; +import { fp, getDatabase, setupCompiler } from './utils'; + +describe('[example] multifile interpreter - cross-file standalone Dep with column-level endpoints', () => { + // Dep is declared in the consumer, both endpoint tables come from the import. + const { compiler } = setupCompiler({ + '/base.dbml': ` +Table raw_orders { + id int [pk] + user_id int +} + +Table stg_orders { + id int [pk] + user_id int +} +`, + '/consumer.dbml': ` +use { table raw_orders } from './base.dbml' +use { table stg_orders } from './base.dbml' + +Dep: raw_orders.user_id -> stg_orders.user_id +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('standalone Dep appears in exported schema', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.deps).toHaveLength(1); + }); + + test('edge endpoints point to the correct tables and columns', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + const edge = db.deps[0].edges[0]; + expect(edge.upstream.tableName).toBe('raw_orders'); + expect(edge.upstream.fieldNames).toEqual(['user_id']); + expect(edge.downstream.tableName).toBe('stg_orders'); + expect(edge.downstream.fieldNames).toEqual(['user_id']); + }); +}); + + +describe('[example] multifile interpreter - cross-file standalone Dep with bare-table endpoints', () => { + // Bare-table endpoints (no .col). Verifies my 11.12 nodeRefereeOfDepEndpoint + // and 11.18 extractTableFromDepEndpoint work across files. + const { compiler } = setupCompiler({ + '/base.dbml': ` +Table raw_orders { + id int [pk] +} + +Table stg_orders { + id int [pk] +} +`, + '/consumer.dbml': ` +use { table raw_orders } from './base.dbml' +use { table stg_orders } from './base.dbml' + +Dep: raw_orders -> stg_orders +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('bare-table Dep appears in exported schema', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.deps).toHaveLength(1); + const edge = db.deps[0].edges[0]; + expect(edge.upstream.tableName).toBe('raw_orders'); + expect(edge.upstream.fieldNames ?? []).toHaveLength(0); + expect(edge.downstream.tableName).toBe('stg_orders'); + expect(edge.downstream.fieldNames ?? []).toHaveLength(0); + }); +}); + + +describe('[example] multifile interpreter - <- reverse-direction Dep normalizes across files', () => { + // Consumer writes `Dep: stg_orders <- raw_orders`. Interpreter should swap so + // canonical shape is upstream=raw_orders, downstream=stg_orders. + const { compiler } = setupCompiler({ + '/base.dbml': ` +Table raw_orders { + id int [pk] +} + +Table stg_orders { + id int [pk] +} +`, + '/consumer.dbml': ` +use { table raw_orders } from './base.dbml' +use { table stg_orders } from './base.dbml' + +Dep: stg_orders <- raw_orders +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('upstream/downstream are swapped to canonical direction', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + const edge = db.deps[0].edges[0]; + expect(edge.upstream.tableName).toBe('raw_orders'); + expect(edge.downstream.tableName).toBe('stg_orders'); + }); +}); + + +describe('[example] multifile interpreter - inline dep on imported column carried to consumer', () => { + // Inline `[dep: -> ...]` on a column of an imported table must survive in the + // consumer's exported schema. + const { compiler } = setupCompiler({ + '/source.dbml': ` +Table accounts { + id int [pk] +} + +Table users { + id int [pk] + account_id int [dep: -> accounts.id] +} +`, + '/consumer.dbml': ` +use { table accounts } from './source.dbml' +use { table users } from './source.dbml' +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('inline dep on users.account_id appears in exported schema', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + const users = db.tables.find((t) => t.name === 'users')!; + const accountIdField = users.fields.find((f) => f.name === 'account_id')!; + expect(accountIdField.inline_deps).toHaveLength(1); + expect(accountIdField.inline_deps[0].direction).toBe('->'); + expect(accountIdField.inline_deps[0].tableName).toBe('accounts'); + expect(accountIdField.inline_deps[0].fieldNames).toContain('id'); + }); +}); + + +describe('[example] multifile interpreter - inline dep on imported table header pulled to db.deps', () => { + // Inline `[dep: <- source]` on a Table header creates a table-level Dep that + // lands in db.deps[]. + const { compiler } = setupCompiler({ + '/source.dbml': ` +Table raw_orders { + id int [pk] +} + +Table fct_orders [dep: <- raw_orders] { + id int [pk] +} +`, + '/consumer.dbml': ` +use { table raw_orders } from './source.dbml' +use { table fct_orders } from './source.dbml' +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('inline-header Dep appears in db.deps with correct upstream/downstream', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.deps).toHaveLength(1); + const edge = db.deps[0].edges[0]; + expect(edge.upstream.tableName).toBe('raw_orders'); + expect(edge.downstream.tableName).toBe('fct_orders'); + }); +}); + + +describe('[example] multifile interpreter - standalone Dep between two imported tables auto-pulled', () => { + // Source declares Dep; consumer imports both tables. Dep should auto-pull + // (parallel to Ref auto-pull behavior). + const { compiler } = setupCompiler({ + '/source.dbml': ` +Table raw_orders { + id int [pk] +} + +Table stg_orders { + id int [pk] +} + +Dep: raw_orders -> stg_orders +`, + '/consumer.dbml': ` +use { table raw_orders } from './source.dbml' +use { table stg_orders } from './source.dbml' +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('Dep auto-pulled into consumer schema', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.deps).toHaveLength(1); + const edge = db.deps[0].edges[0]; + expect(edge.upstream.tableName).toBe('raw_orders'); + expect(edge.downstream.tableName).toBe('stg_orders'); + }); +}); + + +describe('[example] multifile interpreter - Dep NOT pulled when one endpoint missing', () => { + // Only one table imported; the Dep should be filtered out. + const { compiler } = setupCompiler({ + '/source.dbml': ` +Table raw_orders { + id int [pk] +} + +Table stg_orders { + id int [pk] +} + +Dep: raw_orders -> stg_orders +`, + '/consumer.dbml': ` +use { table stg_orders } from './source.dbml' +`, + }); + + test('Dep is not pulled because raw_orders is out of scope', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.deps).toHaveLength(0); + }); +}); + + +describe('[example] multifile interpreter - Dep with custom + note auto-pulled across files', () => { + // Custom attrs and note attached to a Dep block must survive auto-pull. + const { compiler } = setupCompiler({ + '/source.dbml': ` +Table raw_orders { + id int [pk] +} + +Table fct_orders { + id int [pk] +} + +Dep { + raw_orders -> fct_orders + + note: 'Pipeline from raw to fact' + materialized: table + owner: 'data-team' +} +`, + '/consumer.dbml': ` +use { table raw_orders } from './source.dbml' +use { table fct_orders } from './source.dbml' +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('Dep carries note + custom across files', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.deps).toHaveLength(1); + const dep = db.deps[0]; + expect(dep.note?.value).toBe('Pipeline from raw to fact'); + expect(dep.metadata).toEqual({ + materialized: 'table', + owner: 'data-team', + }); + }); +}); + + +describe('[example] multifile interpreter - aliased table referenced in cross-file Dep endpoints', () => { + // Source declares Dep; consumer imports one table with alias. + // The pulled Dep's endpoints must be rewritten to use the alias. + const { compiler } = setupCompiler({ + '/source.dbml': ` +Table raw_orders { + id int [pk] +} + +Table stg_orders { + id int [pk] +} + +Dep: raw_orders -> stg_orders +`, + '/consumer.dbml': ` +use { table raw_orders as raw } from './source.dbml' +use { table stg_orders } from './source.dbml' +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('upstream endpoint uses alias name', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.deps).toHaveLength(1); + const edge = db.deps[0].edges[0]; + expect(edge.upstream.tableName).toBe('raw'); + expect(edge.downstream.tableName).toBe('stg_orders'); + }); +}); + + +describe('[example] multifile interpreter - Dep between tables from different imported files', () => { + // file-a defines raw_orders; file-b defines stg_orders + Dep targeting raw_orders. + // Consumer imports both; the Dep in file-b must auto-pull. + const { compiler } = setupCompiler({ + '/file-a.dbml': ` +Table raw_orders { + id int [pk] +} +`, + '/file-b.dbml': ` +use { table raw_orders } from './file-a.dbml' + +Table stg_orders { + id int [pk] +} + +Dep: raw_orders -> stg_orders +`, + '/consumer.dbml': ` +use { table raw_orders } from './file-a.dbml' +use { table stg_orders } from './file-b.dbml' +`, + }); + + test('no binding errors', () => { + const ast = compiler.parseFile(fp('/consumer.dbml')).getValue().ast; + expect(compiler.bindNode(ast).getErrors()).toHaveLength(0); + }); + + test('both tables present and Dep auto-pulled', () => { + const db = getDatabase(compiler, '/consumer.dbml'); + expect(db.tables.find((t) => t.name === 'raw_orders')).toBeDefined(); + expect(db.tables.find((t) => t.name === 'stg_orders')).toBeDefined(); + expect(db.deps).toHaveLength(1); + }); +}); diff --git a/packages/dbml-parse/__tests__/examples/interpreter/multifile/schema.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/multifile/schema.test.ts index 4a6c6456a..5cb48f035 100644 --- a/packages/dbml-parse/__tests__/examples/interpreter/multifile/schema.test.ts +++ b/packages/dbml-parse/__tests__/examples/interpreter/multifile/schema.test.ts @@ -721,7 +721,7 @@ describe('[example] mixed selective and wildcard reuses through schema merge', ( // c.dbml defines Table T_wild. // d.dbml defines Table T_sel. // b.dbml has `reuse * from './c'` (wildcard) and `reuse { table T_sel } from './d'` (selective). - // a.dbml imports schema public from b — should pull both. + // a.dbml imports schema public from b - should pull both. const { compiler } = setupCompiler({ '/c.dbml': ` Table T_wild { @@ -795,7 +795,7 @@ use { schema public } from './b' describe('[example] nested schema import places members under correct schema', () => { // base.dbml defines Table x.y.t1. - // main.dbml imports schema x.y from base — t1 should be under schema x.y, not x. + // main.dbml imports schema x.y from base - t1 should be under schema x.y, not x. const { compiler } = setupCompiler({ '/base.dbml': ` Table x.y.t1 { @@ -854,7 +854,7 @@ Ref: x.y.orders.user_id > x.y.users.id describe('[example] nested schema with wildcard reuse through schema merge', () => { // c.dbml defines Table x.y.deep. // b.dbml wildcard-reuses c, defines Table x.y.local. - // a.dbml imports schema x.y from b — should pull both. + // a.dbml imports schema x.y from b - should pull both. const { compiler } = setupCompiler({ '/c.dbml': ` Table x.y.deep { @@ -894,7 +894,7 @@ Ref: x.y.local.id > x.y.deep.id describe('[example] reuse * from file with reuse { schema x as y } merges to aliased schema', () => { // base.dbml has Table x.t1 and Table x.t2. // mid.dbml reuses schema x aliased as y. - // consumer.dbml does reuse * from mid — should see t1 and t2 under schema y. + // consumer.dbml does reuse * from mid - should see t1 and t2 under schema y. const { compiler } = setupCompiler({ '/base.dbml': ` Table x.t1 { @@ -937,7 +937,7 @@ Ref: y.t2.t1_id > y.t1.id }); describe('[example] reuse * from file with use { schema x as y } does NOT merge (use is local-only)', () => { - // mid.dbml uses `use` (not reuse), so the alias is local — not transitively visible. + // mid.dbml uses `use` (not reuse), so the alias is local - not transitively visible. const { compiler } = setupCompiler({ '/base.dbml': ` Table x.t1 { diff --git a/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts b/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts index 99c0c8c34..f9209f5f7 100644 --- a/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts +++ b/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts @@ -694,6 +694,7 @@ describe('[example] CompletionItemProvider', () => { 'increment', 'unique', 'ref', + 'dep', 'default', 'note', 'check', @@ -710,6 +711,7 @@ describe('[example] CompletionItemProvider', () => { 'increment', 'unique', 'ref: ', + 'dep: ', 'default: ', 'note: ', 'check: ', @@ -737,6 +739,7 @@ describe('[example] CompletionItemProvider', () => { 'increment', 'unique', 'ref', + 'dep', 'default', 'note', 'check', @@ -753,6 +756,7 @@ describe('[example] CompletionItemProvider', () => { 'increment', 'unique', 'ref: ', + 'dep: ', 'default: ', 'note: ', 'check: ', @@ -780,6 +784,7 @@ describe('[example] CompletionItemProvider', () => { 'increment', 'unique', 'ref', + 'dep', 'default', 'note', 'check', @@ -796,6 +801,7 @@ describe('[example] CompletionItemProvider', () => { 'increment', 'unique', 'ref: ', + 'dep: ', 'default: ', 'note: ', 'check: ', @@ -1556,6 +1562,7 @@ describe('[example] CompletionItemProvider', () => { 'Enum', 'Note', 'Ref', + 'Dep', 'TablePartial', ]); @@ -1567,6 +1574,7 @@ describe('[example] CompletionItemProvider', () => { 'Enum', 'Note', 'Ref', + 'Dep', 'TablePartial', ]); }); diff --git a/packages/dbml-parse/__tests__/snapshots/binder/input/dep.in.dbml b/packages/dbml-parse/__tests__/snapshots/binder/input/dep.in.dbml new file mode 100644 index 000000000..e607a32f7 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/binder/input/dep.in.dbml @@ -0,0 +1,70 @@ +Table users { + id int +} +Table orders { + user_id int +} +Table my_schema.events { + id int + ts int +} + +Table another_schema.booking { + id int + ts int +} + + +// short-form +// no error +Dep: users -> orders +Dep: orders.user_id <- users.id +Dep: my_schema.events -> users +Dep: my_schema.events.id -> users.id +Dep: my_schema.events.id <- another_schema.booking.id + +// full-form +// no error +Dep { + users -> orders + my_schema.events -> users + users.id -> orders.user_id + my_schema.events.id -> users.id +} + +// inline on table header +// no error +Table good_header_bare [dep: <- users] { id int } +Table good_header_schema [dep: <- my_schema.events] { id int } + +// inline on column +// no error +Table good_col_bare { id int [dep: -> users.id] } +Table good_col_schema { id int [dep: -> my_schema.events.id] } + +// short-form +// should produce errors +Dep: users -> unknown_table +Dep: users.unknown_col -> users.id +Dep: unknown_schema.events -> users +Dep: my_schema.unknown_table -> users +Dep: my_schema.events.unknown_col -> users.id + +// full-form +// should produce errors +Dep { + users -> unknown_a + my_schema.events -> unknown_b + users.id -> unknown_c.col + my_schema.events.id -> users.unknown_d +} + +// inline table +// should produce errors +Table bad_header_bare [dep: <- unknown_source] { id int } +Table bad_header_schema [dep: <- unknown_schema.events] { id int } + +// inline column +// should produce errors +Table bad_col_bare { id int [dep: -> unknown_table.col] } +Table bad_col_schema { id int [dep: -> my_schema.unknown_table.col] } diff --git a/packages/dbml-parse/__tests__/snapshots/binder/output/dep.out.json b/packages/dbml-parse/__tests__/snapshots/binder/output/dep.out.json new file mode 100644 index 000000000..83f0665c9 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/binder/output/dep.out.json @@ -0,0 +1,2718 @@ +{ + "errors": [ + { + "code": "BINDING_ERROR", + "diagnostic": "Table 'unknown_table' does not exist in Schema 'public'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_table@[L46:C14, L46:C27]", + "snippet": "unknown_table" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Column 'unknown_col' does not exist in Table 'public.users'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_col@[L47:C11, L47:C22]", + "snippet": "unknown_col" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Schema or Table 'unknown_schema' does not exist", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_schema@[L48:C5, L48:C19]", + "snippet": "unknown_schema" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Table 'unknown_table' does not exist in Schema 'my_schema'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_table@[L49:C15, L49:C28]", + "snippet": "unknown_table" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Column 'unknown_col' does not exist in Table 'my_schema.events'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_col@[L50:C22, L50:C33]", + "snippet": "unknown_col" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Table 'unknown_a' does not exist in Schema 'public'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_a@[L55:C11, L55:C20]", + "snippet": "unknown_a" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Table 'unknown_b' does not exist in Schema 'public'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_b@[L56:C22, L56:C31]", + "snippet": "unknown_b" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Schema or Table 'unknown_c' does not exist", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_c@[L57:C14, L57:C23]", + "snippet": "unknown_c" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Column 'unknown_d' does not exist in Table 'public.users'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_d@[L58:C31, L58:C40]", + "snippet": "unknown_d" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Table 'unknown_source' does not exist in Schema 'public'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_source@[L63:C31, L63:C45]", + "snippet": "unknown_source" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Schema 'unknown_schema' does not exist in Schema 'public'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_schema@[L64:C33, L64:C47]", + "snippet": "unknown_schema" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Table 'unknown_table' does not exist in Schema 'public'", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_table@[L68:C37, L68:C50]", + "snippet": "unknown_table" + } + } + }, + { + "code": "BINDING_ERROR", + "diagnostic": "Table or schema 'unknown_table' does not exist", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@unknown_table@[L69:C49, L69:C62]", + "snippet": "unknown_table" + } + } + } + ], + "nodeReferees": [ + { + "context": { + "id": "node@@users@[L19:C5, L19:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L19:C5, L19:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L19:C5, L19:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 192, + "fullStart": 186 + } + }, + "fullEnd": 192, + "fullStart": 186, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@orders@[L19:C14, L19:C20]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L19:C14, L19:C20]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L19:C14, L19:C20]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 202, + "fullStart": 195 + } + }, + "fullEnd": 202, + "fullStart": 195, + "referee": { + "context": { + "id": "symbol@Table@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + } + } + }, + { + "context": { + "id": "node@@orders@[L20:C5, L20:C11]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L20:C5, L20:C11]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L20:C5, L20:C11]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "orders" + } + }, + "fullEnd": 213, + "fullStart": 207 + } + }, + "fullEnd": 213, + "fullStart": 207, + "referee": { + "context": { + "id": "symbol@Table@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + } + } + }, + { + "context": { + "id": "node@@user_id@[L20:C12, L20:C19]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L20:C12, L20:C19]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L20:C12, L20:C19]", + "snippet": "user_id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "user_id" + } + }, + "fullEnd": 222, + "fullStart": 214 + } + }, + "fullEnd": 222, + "fullStart": 214, + "referee": { + "context": { + "id": "symbol@Column@user_id@[L4:C2, L4:C13]", + "snippet": "user_id int" + } + } + }, + { + "context": { + "id": "node@@users@[L20:C23, L20:C28]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L20:C23, L20:C28]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L20:C23, L20:C28]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 230, + "fullStart": 225 + } + }, + "fullEnd": 230, + "fullStart": 225, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L20:C29, L20:C31]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L20:C29, L20:C31]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L20:C29, L20:C31]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " \n", + "value": "id" + } + }, + "fullEnd": 235, + "fullStart": 231 + } + }, + "fullEnd": 235, + "fullStart": 231, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L21:C5, L21:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L21:C5, L21:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L21:C5, L21:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 249, + "fullStart": 240 + } + }, + "fullEnd": 249, + "fullStart": 240, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L21:C15, L21:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L21:C15, L21:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L21:C15, L21:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 257, + "fullStart": 250 + } + }, + "fullEnd": 257, + "fullStart": 250, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L21:C25, L21:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L21:C25, L21:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L21:C25, L21:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 266, + "fullStart": 260 + } + }, + "fullEnd": 266, + "fullStart": 260, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L22:C5, L22:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L22:C5, L22:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L22:C5, L22:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 280, + "fullStart": 271 + } + }, + "fullEnd": 280, + "fullStart": 271, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L22:C15, L22:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L22:C15, L22:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L22:C15, L22:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 287, + "fullStart": 281 + } + }, + "fullEnd": 287, + "fullStart": 281, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L22:C22, L22:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L22:C22, L22:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L22:C22, L22:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 291, + "fullStart": 288 + } + }, + "fullEnd": 291, + "fullStart": 288, + "referee": { + "context": { + "id": "symbol@Column@id@[L7:C2, L7:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L22:C28, L22:C33]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L22:C28, L22:C33]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L22:C28, L22:C33]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 299, + "fullStart": 294 + } + }, + "fullEnd": 299, + "fullStart": 294, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L22:C34, L22:C36]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L22:C34, L22:C36]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L22:C34, L22:C36]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 303, + "fullStart": 300 + } + }, + "fullEnd": 303, + "fullStart": 300, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L23:C5, L23:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L23:C5, L23:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L23:C5, L23:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 317, + "fullStart": 308 + } + }, + "fullEnd": 317, + "fullStart": 308, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L23:C15, L23:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L23:C15, L23:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L23:C15, L23:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 324, + "fullStart": 318 + } + }, + "fullEnd": 324, + "fullStart": 318, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L23:C22, L23:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L23:C22, L23:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L23:C22, L23:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 328, + "fullStart": 325 + } + }, + "fullEnd": 328, + "fullStart": 325, + "referee": { + "context": { + "id": "symbol@Column@id@[L7:C2, L7:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@another_schema@[L23:C28, L23:C42]", + "snippet": "another_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@another_schema@[L23:C28, L23:C42]", + "snippet": "another_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@another_schema@[L23:C28, L23:C42]", + "snippet": "another_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "another_schema" + } + }, + "fullEnd": 345, + "fullStart": 331 + } + }, + "fullEnd": 345, + "fullStart": 331, + "referee": { + "context": { + "id": "symbol@Schema@another_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@booking@[L23:C43, L23:C50]", + "snippet": "booking" + }, + "children": { + "expression": { + "context": { + "id": "node@@booking@[L23:C43, L23:C50]", + "snippet": "booking" + }, + "children": { + "variable": { + "context": { + "id": "token@@booking@[L23:C43, L23:C50]", + "snippet": "booking" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "booking" + } + }, + "fullEnd": 353, + "fullStart": 346 + } + }, + "fullEnd": 353, + "fullStart": 346, + "referee": { + "context": { + "id": "symbol@Table@another_schema.booking@[L11:C0, L14:C1]", + "snippet": "Table anot... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L23:C51, L23:C53]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L23:C51, L23:C53]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L23:C51, L23:C53]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 357, + "fullStart": 354 + } + }, + "fullEnd": 357, + "fullStart": 354, + "referee": { + "context": { + "id": "symbol@Column@id@[L12:C2, L12:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L28:C2, L28:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L28:C2, L28:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L28:C2, L28:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 397, + "fullStart": 389 + } + }, + "fullEnd": 397, + "fullStart": 389, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@orders@[L28:C11, L28:C17]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L28:C11, L28:C17]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L28:C11, L28:C17]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 407, + "fullStart": 400 + } + }, + "fullEnd": 407, + "fullStart": 400, + "referee": { + "context": { + "id": "symbol@Table@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L29:C2, L29:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L29:C2, L29:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L29:C2, L29:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 418, + "fullStart": 407 + } + }, + "fullEnd": 418, + "fullStart": 407, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L29:C12, L29:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L29:C12, L29:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L29:C12, L29:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 426, + "fullStart": 419 + } + }, + "fullEnd": 426, + "fullStart": 419, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L29:C22, L29:C27]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L29:C22, L29:C27]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L29:C22, L29:C27]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 435, + "fullStart": 429 + } + }, + "fullEnd": 435, + "fullStart": 429, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L30:C2, L30:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L30:C2, L30:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L30:C2, L30:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 442, + "fullStart": 435 + } + }, + "fullEnd": 442, + "fullStart": 435, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L30:C8, L30:C10]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L30:C8, L30:C10]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L30:C8, L30:C10]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 446, + "fullStart": 443 + } + }, + "fullEnd": 446, + "fullStart": 443, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@orders@[L30:C14, L30:C20]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L30:C14, L30:C20]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L30:C14, L30:C20]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "orders" + } + }, + "fullEnd": 455, + "fullStart": 449 + } + }, + "fullEnd": 455, + "fullStart": 449, + "referee": { + "context": { + "id": "symbol@Table@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + } + } + }, + { + "context": { + "id": "node@@user_id@[L30:C21, L30:C28]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L30:C21, L30:C28]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L30:C21, L30:C28]", + "snippet": "user_id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "user_id" + } + }, + "fullEnd": 464, + "fullStart": 456 + } + }, + "fullEnd": 464, + "fullStart": 456, + "referee": { + "context": { + "id": "symbol@Column@user_id@[L4:C2, L4:C13]", + "snippet": "user_id int" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L31:C2, L31:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L31:C2, L31:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L31:C2, L31:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 475, + "fullStart": 464 + } + }, + "fullEnd": 475, + "fullStart": 464, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L31:C12, L31:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L31:C12, L31:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L31:C12, L31:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 482, + "fullStart": 476 + } + }, + "fullEnd": 482, + "fullStart": 476, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L31:C19, L31:C21]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L31:C19, L31:C21]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L31:C19, L31:C21]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 486, + "fullStart": 483 + } + }, + "fullEnd": 486, + "fullStart": 483, + "referee": { + "context": { + "id": "symbol@Column@id@[L7:C2, L7:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L31:C25, L31:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L31:C25, L31:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L31:C25, L31:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 494, + "fullStart": 489 + } + }, + "fullEnd": 494, + "fullStart": 489, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L31:C31, L31:C33]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L31:C31, L31:C33]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L31:C31, L31:C33]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 498, + "fullStart": 495 + } + }, + "fullEnd": 498, + "fullStart": 495, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L36:C32, L36:C37]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L36:C32, L36:C37]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L36:C32, L36:C37]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 576, + "fullStart": 571 + } + }, + "fullEnd": 576, + "fullStart": 571, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L37:C34, L37:C43]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L37:C34, L37:C43]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L37:C34, L37:C43]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 632, + "fullStart": 623 + } + }, + "fullEnd": 632, + "fullStart": 623, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L37:C44, L37:C50]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L37:C44, L37:C50]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L37:C44, L37:C50]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 639, + "fullStart": 633 + } + }, + "fullEnd": 639, + "fullStart": 633, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L41:C38, L41:C43]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L41:C38, L41:C43]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L41:C38, L41:C43]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 728, + "fullStart": 723 + } + }, + "fullEnd": 728, + "fullStart": 723, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L41:C44, L41:C46]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L41:C44, L41:C46]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L41:C44, L41:C46]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "id" + } + }, + "fullEnd": 731, + "fullStart": 729 + } + }, + "fullEnd": 731, + "fullStart": 729, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L42:C40, L42:C49]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L42:C40, L42:C49]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L42:C40, L42:C49]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 784, + "fullStart": 775 + } + }, + "fullEnd": 784, + "fullStart": 775, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L42:C50, L42:C56]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L42:C50, L42:C56]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L42:C50, L42:C56]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 791, + "fullStart": 785 + } + }, + "fullEnd": 791, + "fullStart": 785, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L42:C57, L42:C59]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L42:C57, L42:C59]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L42:C57, L42:C59]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "id" + } + }, + "fullEnd": 794, + "fullStart": 792 + } + }, + "fullEnd": 794, + "fullStart": 792, + "referee": { + "context": { + "id": "symbol@Column@id@[L7:C2, L7:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L46:C5, L46:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L46:C5, L46:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L46:C5, L46:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 849, + "fullStart": 843 + } + }, + "fullEnd": 849, + "fullStart": 843, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L47:C5, L47:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L47:C5, L47:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L47:C5, L47:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 876, + "fullStart": 871 + } + }, + "fullEnd": 876, + "fullStart": 871, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L47:C26, L47:C31]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L47:C26, L47:C31]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L47:C26, L47:C31]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 897, + "fullStart": 892 + } + }, + "fullEnd": 897, + "fullStart": 892, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L47:C32, L47:C34]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L47:C32, L47:C34]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L47:C32, L47:C34]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 901, + "fullStart": 898 + } + }, + "fullEnd": 901, + "fullStart": 898, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L48:C30, L48:C35]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L48:C30, L48:C35]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L48:C30, L48:C35]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 937, + "fullStart": 931 + } + }, + "fullEnd": 937, + "fullStart": 931, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L49:C5, L49:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L49:C5, L49:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L49:C5, L49:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 951, + "fullStart": 942 + } + }, + "fullEnd": 951, + "fullStart": 942, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@users@[L49:C32, L49:C37]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L49:C32, L49:C37]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L49:C32, L49:C37]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 975, + "fullStart": 969 + } + }, + "fullEnd": 975, + "fullStart": 969, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L50:C5, L50:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L50:C5, L50:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L50:C5, L50:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 989, + "fullStart": 980 + } + }, + "fullEnd": 989, + "fullStart": 980, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L50:C15, L50:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L50:C15, L50:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L50:C15, L50:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 996, + "fullStart": 990 + } + }, + "fullEnd": 996, + "fullStart": 990, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L50:C37, L50:C42]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L50:C37, L50:C42]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L50:C37, L50:C42]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1017, + "fullStart": 1012 + } + }, + "fullEnd": 1017, + "fullStart": 1012, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L50:C43, L50:C45]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L50:C43, L50:C45]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L50:C43, L50:C45]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 1021, + "fullStart": 1018 + } + }, + "fullEnd": 1021, + "fullStart": 1018, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L55:C2, L55:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L55:C2, L55:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L55:C2, L55:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 1074, + "fullStart": 1066 + } + }, + "fullEnd": 1074, + "fullStart": 1066, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L56:C2, L56:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L56:C2, L56:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L56:C2, L56:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1098, + "fullStart": 1087 + } + }, + "fullEnd": 1098, + "fullStart": 1087, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L56:C12, L56:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L56:C12, L56:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L56:C12, L56:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 1106, + "fullStart": 1099 + } + }, + "fullEnd": 1106, + "fullStart": 1099, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@users@[L57:C2, L57:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L57:C2, L57:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L57:C2, L57:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1126, + "fullStart": 1119 + } + }, + "fullEnd": 1126, + "fullStart": 1119, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L57:C8, L57:C10]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L57:C8, L57:C10]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L57:C8, L57:C10]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1130, + "fullStart": 1127 + } + }, + "fullEnd": 1130, + "fullStart": 1127, + "referee": { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L58:C2, L58:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L58:C2, L58:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L58:C2, L58:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1158, + "fullStart": 1147 + } + }, + "fullEnd": 1158, + "fullStart": 1147, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + }, + { + "context": { + "id": "node@@events@[L58:C12, L58:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L58:C12, L58:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L58:C12, L58:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 1165, + "fullStart": 1159 + } + }, + "fullEnd": 1165, + "fullStart": 1159, + "referee": { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + }, + { + "context": { + "id": "node@@id@[L58:C19, L58:C21]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L58:C19, L58:C21]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L58:C19, L58:C21]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1169, + "fullStart": 1166 + } + }, + "fullEnd": 1169, + "fullStart": 1166, + "referee": { + "context": { + "id": "symbol@Column@id@[L7:C2, L7:C8]", + "snippet": "id int" + } + } + }, + { + "context": { + "id": "node@@users@[L58:C25, L58:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L58:C25, L58:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L58:C25, L58:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1177, + "fullStart": 1172 + } + }, + "fullEnd": 1177, + "fullStart": 1172, + "referee": { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + } + } + }, + { + "context": { + "id": "node@@my_schema@[L69:C39, L69:C48]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L69:C39, L69:C48]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L69:C39, L69:C48]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1506, + "fullStart": 1497 + } + }, + "fullEnd": 1506, + "fullStart": 1497, + "referee": { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + } + } + } + ], + "program": { + "publicSchema": [ + { + "context": { + "id": "symbol@Table@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + }, + "declaration": { + "id": "node@@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L1:C2, L1:C8]", + "snippet": "id int" + } + } + ], + "references": [ + { + "id": "node@@users@[L19:C5, L19:C10]", + "snippet": "users" + }, + { + "id": "node@@users@[L20:C23, L20:C28]", + "snippet": "users" + }, + { + "id": "node@@users@[L21:C25, L21:C30]", + "snippet": "users" + }, + { + "id": "node@@users@[L22:C28, L22:C33]", + "snippet": "users" + }, + { + "id": "node@@users@[L28:C2, L28:C7]", + "snippet": "users" + }, + { + "id": "node@@users@[L29:C22, L29:C27]", + "snippet": "users" + }, + { + "id": "node@@users@[L30:C2, L30:C7]", + "snippet": "users" + }, + { + "id": "node@@users@[L31:C25, L31:C30]", + "snippet": "users" + }, + { + "id": "node@@users@[L36:C32, L36:C37]", + "snippet": "users" + }, + { + "id": "node@@users@[L41:C38, L41:C43]", + "snippet": "users" + }, + { + "id": "node@@users@[L46:C5, L46:C10]", + "snippet": "users" + }, + { + "id": "node@@users@[L47:C26, L47:C31]", + "snippet": "users" + }, + { + "id": "node@@users@[L47:C5, L47:C10]", + "snippet": "users" + }, + { + "id": "node@@users@[L48:C30, L48:C35]", + "snippet": "users" + }, + { + "id": "node@@users@[L49:C32, L49:C37]", + "snippet": "users" + }, + { + "id": "node@@users@[L50:C37, L50:C42]", + "snippet": "users" + }, + { + "id": "node@@users@[L55:C2, L55:C7]", + "snippet": "users" + }, + { + "id": "node@@users@[L57:C2, L57:C7]", + "snippet": "users" + }, + { + "id": "node@@users@[L58:C25, L58:C30]", + "snippet": "users" + } + ] + }, + { + "context": { + "id": "symbol@Table@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + }, + "declaration": { + "id": "node@@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + }, + "members": [ + { + "context": { + "id": "symbol@Column@user_id@[L4:C2, L4:C13]", + "snippet": "user_id int" + } + } + ], + "references": [ + { + "id": "node@@orders@[L19:C14, L19:C20]", + "snippet": "orders" + }, + { + "id": "node@@orders@[L20:C5, L20:C11]", + "snippet": "orders" + }, + { + "id": "node@@orders@[L28:C11, L28:C17]", + "snippet": "orders" + }, + { + "id": "node@@orders@[L30:C14, L30:C20]", + "snippet": "orders" + } + ] + }, + { + "context": { + "id": "symbol@Table@good_header_bare@[L36:C0, L36:C49]", + "snippet": "Table good...{ id int }" + }, + "declaration": { + "id": "node@@good_header_bare@[L36:C0, L36:C49]", + "snippet": "Table good...{ id int }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L36:C41, L36:C47]", + "snippet": "id int" + } + } + ] + }, + { + "context": { + "id": "symbol@Table@good_header_schema@[L37:C0, L37:C62]", + "snippet": "Table good...{ id int }" + }, + "declaration": { + "id": "node@@good_header_schema@[L37:C0, L37:C62]", + "snippet": "Table good...{ id int }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L37:C54, L37:C60]", + "snippet": "id int" + } + } + ] + }, + { + "context": { + "id": "symbol@Table@good_col_bare@[L41:C0, L41:C49]", + "snippet": "Table good...sers.id] }" + }, + "declaration": { + "id": "node@@good_col_bare@[L41:C0, L41:C49]", + "snippet": "Table good...sers.id] }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L41:C22, L41:C47]", + "snippet": "id int [de... users.id]" + } + } + ] + }, + { + "context": { + "id": "symbol@Table@good_col_schema@[L42:C0, L42:C62]", + "snippet": "Table good...ents.id] }" + }, + "declaration": { + "id": "node@@good_col_schema@[L42:C0, L42:C62]", + "snippet": "Table good...ents.id] }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L42:C24, L42:C60]", + "snippet": "id int [de...events.id]" + } + } + ] + }, + { + "context": { + "id": "symbol@Table@bad_header_bare@[L63:C0, L63:C57]", + "snippet": "Table bad_...{ id int }" + }, + "declaration": { + "id": "node@@bad_header_bare@[L63:C0, L63:C57]", + "snippet": "Table bad_...{ id int }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L63:C49, L63:C55]", + "snippet": "id int" + } + } + ] + }, + { + "context": { + "id": "symbol@Table@bad_header_schema@[L64:C0, L64:C66]", + "snippet": "Table bad_...{ id int }" + }, + "declaration": { + "id": "node@@bad_header_schema@[L64:C0, L64:C66]", + "snippet": "Table bad_...{ id int }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L64:C58, L64:C64]", + "snippet": "id int" + } + } + ] + }, + { + "context": { + "id": "symbol@Table@bad_col_bare@[L68:C0, L68:C57]", + "snippet": "Table bad_...ble.col] }" + }, + "declaration": { + "id": "node@@bad_col_bare@[L68:C0, L68:C57]", + "snippet": "Table bad_...ble.col] }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L68:C21, L68:C55]", + "snippet": "id int [de...table.col]" + } + } + ] + }, + { + "context": { + "id": "symbol@Table@bad_col_schema@[L69:C0, L69:C69]", + "snippet": "Table bad_...ble.col] }" + }, + "declaration": { + "id": "node@@bad_col_schema@[L69:C0, L69:C69]", + "snippet": "Table bad_...ble.col] }" + }, + "members": [ + { + "context": { + "id": "symbol@Column@id@[L69:C23, L69:C67]", + "snippet": "id int [de...table.col]" + } + } + ] + } + ], + "schemas": [ + { + "context": { + "id": "symbol@Schema@my_schema@[L?:C?, L?:C?]" + }, + "members": [ + { + "context": { + "id": "symbol@Table@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + } + } + ], + "references": [ + { + "id": "node@@my_schema@[L21:C5, L21:C14]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L22:C5, L22:C14]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L23:C5, L23:C14]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L29:C2, L29:C11]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L31:C2, L31:C11]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L37:C34, L37:C43]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L42:C40, L42:C49]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L49:C5, L49:C14]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L50:C5, L50:C14]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L56:C2, L56:C11]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L58:C2, L58:C11]", + "snippet": "my_schema" + }, + { + "id": "node@@my_schema@[L69:C39, L69:C48]", + "snippet": "my_schema" + } + ] + }, + { + "context": { + "id": "symbol@Schema@another_schema@[L?:C?, L?:C?]" + }, + "members": [ + { + "context": { + "id": "symbol@Table@another_schema.booking@[L11:C0, L14:C1]", + "snippet": "Table anot... ts int\n}" + } + } + ], + "references": [ + { + "id": "node@@another_schema@[L23:C28, L23:C42]", + "snippet": "another_schema" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep.in.dbml b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep.in.dbml new file mode 100644 index 000000000..73c790851 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep.in.dbml @@ -0,0 +1,47 @@ +Table users { + id int +} +Table orders { + user_id int +} +Table my_schema.events { + id int + ts int +} + +Table another_schema.booking { + id int + ts int +} + + +// short-form - simple deps can share downstream tables +// no error +Dep: users -> orders +Dep: orders.user_id <- users.id +Dep: my_schema.events -> users +Dep: my_schema.events.id -> users.id +Dep: my_schema.events.id <- another_schema.booking.id + +// full-form - all edges must target the same downstream table +// no error +Dep { + orders -> users + my_schema.events -> users + orders.user_id -> users.id + + note: 'ok' + materialized: view + owner: 'team-data' + priority: 1 +} + +// inline on table header +// no error +Table good_header_bare [dep: <- users] { id int } +Table good_header_schema [dep: <- my_schema.events] { id int } + +// inline on column +// no error +Table good_col_bare { id int [dep: -> users.id] } +Table good_col_schema { id int [dep: -> my_schema.events.id] } diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_color.in.dbml b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_color.in.dbml new file mode 100644 index 000000000..05efabf40 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_color.in.dbml @@ -0,0 +1,21 @@ +Table users { id int } +Table orders { id int } +Table events { id int } +Table reports { id int } + +// header attribute form +Dep dep_header [color: #aabbcc] { + users -> orders +} + +// body sub-declaration form +Dep dep_body { + orders -> events + color: #123456 +} + +// short / inline-setting form +Dep: users -> reports [color: #abcdef] + +// uncolored, no color slot +Dep: events -> users diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_duplicate.in.dbml b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_duplicate.in.dbml new file mode 100644 index 000000000..d7b976894 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_duplicate.in.dbml @@ -0,0 +1,21 @@ +// Duplicate Dep edges (same direction) produce a "same endpoints" diagnostic. +// Reversed direction is a distinct edge and is allowed. + +Table a { + id int [pk] +} + +Table b { + id int [pk] +} + +// table-level duplicate -> error +Dep: a -> b +Dep: a -> b + +// column-level duplicate -> error +Dep: a.id -> b.id +Dep: a.id -> b.id + +// reversed direction is not a duplicate -> no error +Dep: b -> a diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_pipeline.in.dbml b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_pipeline.in.dbml new file mode 100644 index 000000000..b3b245305 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/input/dep_pipeline.in.dbml @@ -0,0 +1,42 @@ +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 - table-level edges +Dep { + stg_orders -> fct_orders + + note: 'Aggregate staging orders into facts' + materialized: table + owner: 'data-team' +} + +// Column-level edge +Dep: stg_orders.amount -> fct_orders.revenue + +// Reverse-direction sugar (<-): equivalent to raw_orders.amount -> stg_orders.amount +Dep: stg_orders.amount <- raw_orders.amount + +// Inline on table header +Table mart_orders [dep: <- fct_orders] { + id int [pk] + total decimal +} diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep.out.json new file mode 100644 index 000000000..6fd0fa6d6 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep.out.json @@ -0,0 +1,16 @@ +{ + "errors": [ + { + "code": "SAME_ENDPOINT", + "diagnostic": "Dep with same endpoints already exists", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@@[L29:C2, L29:C27]", + "snippet": "my_schema....s -> users" + } + } + } + ] +} \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_color.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_color.out.json new file mode 100644 index 000000000..aa17b13a9 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_color.out.json @@ -0,0 +1,438 @@ +{ + "database": { + "deps": [ + { + "color": "#aabbcc", + "edges": [ + { + "downstream": { + "tableName": "orders", + "token": { + "end": { + "column": 2, + "line": 9, + "offset": 175 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 7, + "offset": 122 + } + } + }, + "token": { + "end": { + "column": 2, + "line": 9, + "offset": 175 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 7, + "offset": 122 + } + }, + "upstream": { + "tableName": "users", + "token": { + "end": { + "column": 2, + "line": 9, + "offset": 175 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 7, + "offset": 122 + } + } + } + } + ], + "name": "dep_header", + "token": { + "end": { + "column": 2, + "line": 9, + "offset": 175 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 7, + "offset": 122 + } + } + }, + { + "color": "#123456", + "edges": [ + { + "downstream": { + "tableName": "events", + "token": { + "end": { + "column": 2, + "line": 15, + "offset": 258 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 12, + "offset": 206 + } + } + }, + "token": { + "end": { + "column": 2, + "line": 15, + "offset": 258 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 12, + "offset": 206 + } + }, + "upstream": { + "tableName": "orders", + "token": { + "end": { + "column": 2, + "line": 15, + "offset": 258 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 12, + "offset": 206 + } + } + } + } + ], + "name": "dep_body", + "token": { + "end": { + "column": 2, + "line": 15, + "offset": 258 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 12, + "offset": 206 + } + } + }, + { + "color": "#abcdef", + "edges": [ + { + "downstream": { + "tableName": "reports", + "token": { + "end": { + "column": 39, + "line": 18, + "offset": 329 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 18, + "offset": 291 + } + } + }, + "token": { + "end": { + "column": 39, + "line": 18, + "offset": 329 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 18, + "offset": 291 + } + }, + "upstream": { + "tableName": "users", + "token": { + "end": { + "column": 39, + "line": 18, + "offset": 329 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 18, + "offset": 291 + } + } + } + } + ], + "token": { + "end": { + "column": 39, + "line": 18, + "offset": 329 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 18, + "offset": 291 + } + } + }, + { + "edges": [ + { + "downstream": { + "tableName": "users", + "token": { + "end": { + "column": 21, + "line": 21, + "offset": 379 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 359 + } + } + }, + "token": { + "end": { + "column": 21, + "line": 21, + "offset": 379 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 359 + } + }, + "upstream": { + "tableName": "events", + "token": { + "end": { + "column": 21, + "line": 21, + "offset": 379 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 359 + } + } + } + } + ], + "token": { + "end": { + "column": 21, + "line": 21, + "offset": 379 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 359 + } + } + } + ], + "tables": [ + { + "fields": [ + { + "name": "id", + "pk": false, + "token": { + "end": { + "column": 21, + "line": 1, + "offset": 20 + }, + "filepath": "/main.dbml", + "start": { + "column": 15, + "line": 1, + "offset": 14 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + } + ], + "name": "users", + "token": { + "end": { + "column": 23, + "line": 1, + "offset": 22 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + } + }, + { + "fields": [ + { + "name": "id", + "pk": false, + "token": { + "end": { + "column": 22, + "line": 2, + "offset": 44 + }, + "filepath": "/main.dbml", + "start": { + "column": 16, + "line": 2, + "offset": 38 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + } + ], + "name": "orders", + "token": { + "end": { + "column": 24, + "line": 2, + "offset": 46 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 2, + "offset": 23 + } + } + }, + { + "fields": [ + { + "name": "id", + "pk": false, + "token": { + "end": { + "column": 22, + "line": 3, + "offset": 68 + }, + "filepath": "/main.dbml", + "start": { + "column": 16, + "line": 3, + "offset": 62 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + } + ], + "name": "events", + "token": { + "end": { + "column": 24, + "line": 3, + "offset": 70 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 3, + "offset": 47 + } + } + }, + { + "fields": [ + { + "name": "id", + "pk": false, + "token": { + "end": { + "column": 23, + "line": 4, + "offset": 93 + }, + "filepath": "/main.dbml", + "start": { + "column": 17, + "line": 4, + "offset": 87 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + } + ], + "name": "reports", + "token": { + "end": { + "column": 25, + "line": 4, + "offset": 95 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 4, + "offset": 71 + } + } + } + ], + "token": { + "end": { + "column": 1, + "line": 22, + "offset": 380 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + } + } +} \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_duplicate.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_duplicate.out.json new file mode 100644 index 000000000..04b4150a9 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_duplicate.out.json @@ -0,0 +1,28 @@ +{ + "errors": [ + { + "code": "SAME_ENDPOINT", + "diagnostic": "Dep with same endpoints already exists", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@@[L13:C5, L13:C11]", + "snippet": "a -> b" + } + } + }, + { + "code": "SAME_ENDPOINT", + "diagnostic": "Dep with same endpoints already exists", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@@[L17:C5, L17:C17]", + "snippet": "a.id -> b.id" + } + } + } + ] +} \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_pipeline.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_pipeline.out.json new file mode 100644 index 000000000..617e5d078 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/dep_pipeline.out.json @@ -0,0 +1,697 @@ +{ + "database": { + "deps": [ + { + "edges": [ + { + "downstream": { + "tableName": "stg_orders", + "token": { + "end": { + "column": 30, + "line": 21, + "offset": 268 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 239 + } + } + }, + "token": { + "end": { + "column": 30, + "line": 21, + "offset": 268 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 239 + } + }, + "upstream": { + "tableName": "raw_orders", + "token": { + "end": { + "column": 30, + "line": 21, + "offset": 268 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 239 + } + } + } + } + ], + "token": { + "end": { + "column": 30, + "line": 21, + "offset": 268 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 21, + "offset": 239 + } + } + }, + { + "edges": [ + { + "downstream": { + "tableName": "fct_orders", + "token": { + "end": { + "column": 2, + "line": 30, + "offset": 445 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 24, + "offset": 321 + } + } + }, + "token": { + "end": { + "column": 2, + "line": 30, + "offset": 445 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 24, + "offset": 321 + } + }, + "upstream": { + "tableName": "stg_orders", + "token": { + "end": { + "column": 2, + "line": 30, + "offset": 445 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 24, + "offset": 321 + } + } + } + } + ], + "metadata": { + "materialized": "table", + "owner": "data-team" + }, + "note": { + "token": { + "end": { + "column": 46, + "line": 27, + "offset": 400 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 27, + "offset": 357 + } + }, + "value": "Aggregate staging orders into facts" + }, + "token": { + "end": { + "column": 2, + "line": 30, + "offset": 445 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 24, + "offset": 321 + } + } + }, + { + "edges": [ + { + "downstream": { + "fieldNames": [ + "revenue" + ], + "tableName": "fct_orders", + "token": { + "end": { + "column": 45, + "line": 33, + "offset": 512 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 33, + "offset": 468 + } + } + }, + "token": { + "end": { + "column": 45, + "line": 33, + "offset": 512 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 33, + "offset": 468 + } + }, + "upstream": { + "fieldNames": [ + "amount" + ], + "tableName": "stg_orders", + "token": { + "end": { + "column": 45, + "line": 33, + "offset": 512 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 33, + "offset": 468 + } + } + } + } + ], + "token": { + "end": { + "column": 45, + "line": 33, + "offset": 512 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 33, + "offset": 468 + } + } + }, + { + "edges": [ + { + "downstream": { + "fieldNames": [ + "amount" + ], + "tableName": "stg_orders", + "token": { + "end": { + "column": 44, + "line": 36, + "offset": 643 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 36, + "offset": 600 + } + } + }, + "token": { + "end": { + "column": 44, + "line": 36, + "offset": 643 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 36, + "offset": 600 + } + }, + "upstream": { + "fieldNames": [ + "amount" + ], + "tableName": "raw_orders", + "token": { + "end": { + "column": 44, + "line": 36, + "offset": 643 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 36, + "offset": 600 + } + } + } + } + ], + "token": { + "end": { + "column": 44, + "line": 36, + "offset": 643 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 36, + "offset": 600 + } + } + }, + { + "edges": [ + { + "downstream": { + "tableName": "mart_orders", + "token": { + "end": { + "column": 38, + "line": 39, + "offset": 708 + }, + "filepath": "/main.dbml", + "start": { + "column": 20, + "line": 39, + "offset": 690 + } + } + }, + "token": { + "end": { + "column": 38, + "line": 39, + "offset": 708 + }, + "filepath": "/main.dbml", + "start": { + "column": 20, + "line": 39, + "offset": 690 + } + }, + "upstream": { + "tableName": "fct_orders", + "token": { + "end": { + "column": 38, + "line": 39, + "offset": 708 + }, + "filepath": "/main.dbml", + "start": { + "column": 20, + "line": 39, + "offset": 690 + } + } + } + } + ], + "token": { + "end": { + "column": 38, + "line": 39, + "offset": 708 + }, + "filepath": "/main.dbml", + "start": { + "column": 20, + "line": 39, + "offset": 690 + } + } + } + ], + "tables": [ + { + "fields": [ + { + "name": "id", + "pk": true, + "token": { + "end": { + "column": 14, + "line": 2, + "offset": 32 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 2, + "offset": 21 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + }, + { + "name": "user_id", + "pk": false, + "token": { + "end": { + "column": 14, + "line": 3, + "offset": 46 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 3, + "offset": 35 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + }, + { + "name": "amount", + "pk": false, + "token": { + "end": { + "column": 17, + "line": 4, + "offset": 63 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 4, + "offset": 49 + } + }, + "type": { + "type_name": "decimal" + }, + "unique": false + }, + { + "name": "created_at", + "pk": false, + "token": { + "end": { + "column": 23, + "line": 5, + "offset": 86 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 5, + "offset": 66 + } + }, + "type": { + "type_name": "timestamp" + }, + "unique": false + } + ], + "name": "raw_orders", + "token": { + "end": { + "column": 2, + "line": 6, + "offset": 88 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + } + }, + { + "fields": [ + { + "name": "id", + "pk": true, + "token": { + "end": { + "column": 14, + "line": 9, + "offset": 122 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 9, + "offset": 111 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + }, + { + "name": "user_id", + "pk": false, + "token": { + "end": { + "column": 14, + "line": 10, + "offset": 136 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 10, + "offset": 125 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + }, + { + "name": "amount", + "pk": false, + "token": { + "end": { + "column": 17, + "line": 11, + "offset": 153 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 11, + "offset": 139 + } + }, + "type": { + "type_name": "decimal" + }, + "unique": false + } + ], + "name": "stg_orders", + "token": { + "end": { + "column": 2, + "line": 12, + "offset": 155 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 8, + "offset": 90 + } + } + }, + { + "fields": [ + { + "name": "id", + "pk": true, + "token": { + "end": { + "column": 14, + "line": 15, + "offset": 189 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 15, + "offset": 178 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + }, + { + "name": "user_id", + "pk": false, + "token": { + "end": { + "column": 14, + "line": 16, + "offset": 203 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 16, + "offset": 192 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + }, + { + "name": "revenue", + "pk": false, + "token": { + "end": { + "column": 18, + "line": 17, + "offset": 221 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 17, + "offset": 206 + } + }, + "type": { + "type_name": "decimal" + }, + "unique": false + } + ], + "name": "fct_orders", + "token": { + "end": { + "column": 2, + "line": 18, + "offset": 223 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 14, + "offset": 157 + } + } + }, + { + "fields": [ + { + "name": "id", + "pk": true, + "token": { + "end": { + "column": 14, + "line": 40, + "offset": 725 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 40, + "offset": 714 + } + }, + "type": { + "type_name": "int" + }, + "unique": false + }, + { + "name": "total", + "pk": false, + "token": { + "end": { + "column": 16, + "line": 41, + "offset": 741 + }, + "filepath": "/main.dbml", + "start": { + "column": 3, + "line": 41, + "offset": 728 + } + }, + "type": { + "type_name": "decimal" + }, + "unique": false + } + ], + "name": "mart_orders", + "token": { + "end": { + "column": 2, + "line": 42, + "offset": 743 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 39, + "offset": 671 + } + } + } + ], + "token": { + "end": { + "column": 1, + "line": 43, + "offset": 744 + }, + "filepath": "/main.dbml", + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + } + } +} \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/lexer/input/dep_operators.in.dbml b/packages/dbml-parse/__tests__/snapshots/lexer/input/dep_operators.in.dbml new file mode 100644 index 000000000..f21fba16c --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/lexer/input/dep_operators.in.dbml @@ -0,0 +1,3 @@ +-> <- +a -> b +a<-b diff --git a/packages/dbml-parse/__tests__/snapshots/lexer/output/dep_operators.out.json b/packages/dbml-parse/__tests__/snapshots/lexer/output/dep_operators.out.json new file mode 100644 index 000000000..b94e2c79d --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/lexer/output/dep_operators.out.json @@ -0,0 +1,85 @@ +{ + "tokens": [ + { + "context": { + "id": "token@@->@[L0:C0, L0:C2]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + { + "context": { + "id": "token@@<-@[L0:C3, L0:C5]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "<-" + }, + { + "context": { + "id": "token@@a@[L1:C0, L1:C1]", + "snippet": "a" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "a" + }, + { + "context": { + "id": "token@@->@[L1:C2, L1:C4]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + { + "context": { + "id": "token@@b@[L1:C5, L1:C6]", + "snippet": "b" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "b" + }, + { + "context": { + "id": "token@@a@[L2:C0, L2:C1]", + "snippet": "a" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "a" + }, + { + "context": { + "id": "token@@<-@[L2:C1, L2:C3]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "<-" + }, + { + "context": { + "id": "token@@b@[L2:C3, L2:C4]", + "snippet": "b" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "b" + }, + { + "context": { + "id": "token@@@[L3:C0, L3:C0]", + "snippet": "" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "" + } + ] +} \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/parser/input/dep.in.dbml b/packages/dbml-parse/__tests__/snapshots/parser/input/dep.in.dbml new file mode 100644 index 000000000..73b816b75 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/parser/input/dep.in.dbml @@ -0,0 +1,70 @@ +Table users { + id int +} +Table orders { + user_id int +} +Table my_schema.events { + id int + ts int +} + +Table another_schema.booking { + id int + ts int +} + + +// short-form +// no error +Dep: users -> orders +Dep: orders.user_id <- users.id +Dep: my_schema.events -> users +Dep: my_schema.events.id -> users.id +Dep: my_schema.events.id <- another_schema.booking.id + +// full-form +// no error +Dep { + users -> orders + my_schema.events -> users + users.id -> orders.user_id + my_schema.events.id -> users.id +} + +// inline on table header +// no error +Table good_header_bare [dep: <- users] { id int } +Table good_header_schema [dep: <- my_schema.events] { id int } + +// inline on column +// no error +Table good_col_bare { id int [dep: -> users.id] } +Table good_col_schema { id int [dep: -> my_schema.events.id] } + +// short-form +// no error +Dep: users -> unknown_table +Dep: users.unknown_col -> users.id +Dep: unknown_schema.events -> users +Dep: my_schema.unknown_table -> users +Dep: my_schema.events.unknown_col -> users.id + +// full-form +// no error +Dep { + users -> unknown_a + my_schema.events -> unknown_b + users.id -> unknown_c.col + my_schema.events.id -> users.unknown_d +} + +// inline on table header +// no error +Table bad_header_bare [dep: <- unknown_source] { id int } +Table bad_header_schema [dep: <- unknown_schema.events] { id int } + +// inline on column +// no error +Table bad_col_bare { id int [dep: -> unknown_table.col] } +Table bad_col_schema { id int [dep: -> my_schema.unknown_table.col] } diff --git a/packages/dbml-parse/__tests__/snapshots/parser/output/dep.out.json b/packages/dbml-parse/__tests__/snapshots/parser/output/dep.out.json new file mode 100644 index 000000000..a2513aa24 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/parser/output/dep.out.json @@ -0,0 +1,6699 @@ +{ + "program": { + "context": { + "id": "node@@@[L0:C0, L70:C0]", + "snippet": "Table user...le.col] }\n" + }, + "children": { + "body": [ + { + "context": { + "id": "node@@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L0:C12, L2:C1]", + "snippet": "{\n id int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L2:C0, L2:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L0:C12, L0:C13]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L1:C2, L1:C8]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L1:C5, L1:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L1:C5, L1:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L1:C5, L1:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 23, + "fullStart": 19 + } + }, + "fullEnd": 23, + "fullStart": 19 + } + ], + "callee": { + "context": { + "id": "node@@id@[L1:C2, L1:C4]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L1:C2, L1:C4]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L1:C2, L1:C4]", + "snippet": "id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 19, + "fullStart": 14 + } + }, + "fullEnd": 19, + "fullStart": 14 + } + }, + "fullEnd": 23, + "fullStart": 14 + } + ] + }, + "fullEnd": 25, + "fullStart": 12 + }, + "name": { + "context": { + "id": "node@@users@[L0:C6, L0:C11]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L0:C6, L0:C11]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L0:C6, L0:C11]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 12, + "fullStart": 6 + } + }, + "fullEnd": 12, + "fullStart": 6 + }, + "type": { + "context": { + "id": "token@@Table@[L0:C0, L0:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 25, + "fullStart": 0 + }, + { + "context": { + "id": "node@@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L3:C13, L5:C1]", + "snippet": "{\n user_id int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L5:C0, L5:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L3:C13, L3:C14]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@user_id@[L4:C2, L4:C13]", + "snippet": "user_id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L4:C10, L4:C13]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L4:C10, L4:C13]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L4:C10, L4:C13]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 54, + "fullStart": 50 + } + }, + "fullEnd": 54, + "fullStart": 50 + } + ], + "callee": { + "context": { + "id": "node@@user_id@[L4:C2, L4:C9]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L4:C2, L4:C9]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L4:C2, L4:C9]", + "snippet": "user_id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "user_id" + } + }, + "fullEnd": 50, + "fullStart": 40 + } + }, + "fullEnd": 50, + "fullStart": 40 + } + }, + "fullEnd": 54, + "fullStart": 40 + } + ] + }, + "fullEnd": 56, + "fullStart": 38 + }, + "name": { + "context": { + "id": "node@@orders@[L3:C6, L3:C12]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L3:C6, L3:C12]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L3:C6, L3:C12]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "orders" + } + }, + "fullEnd": 38, + "fullStart": 31 + } + }, + "fullEnd": 38, + "fullStart": 31 + }, + "type": { + "context": { + "id": "token@@Table@[L3:C0, L3:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 56, + "fullStart": 25 + }, + { + "context": { + "id": "node@@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L6:C23, L9:C1]", + "snippet": "{\n id int... ts int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L9:C0, L9:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L6:C23, L6:C24]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L7:C2, L7:C8]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L7:C5, L7:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L7:C5, L7:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L7:C5, L7:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 90, + "fullStart": 86 + } + }, + "fullEnd": 90, + "fullStart": 86 + } + ], + "callee": { + "context": { + "id": "node@@id@[L7:C2, L7:C4]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L7:C2, L7:C4]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L7:C2, L7:C4]", + "snippet": "id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 86, + "fullStart": 81 + } + }, + "fullEnd": 86, + "fullStart": 81 + } + }, + "fullEnd": 90, + "fullStart": 81 + }, + { + "context": { + "id": "node@@ts@[L8:C2, L8:C8]", + "snippet": "ts int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L8:C5, L8:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L8:C5, L8:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L8:C5, L8:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 99, + "fullStart": 95 + } + }, + "fullEnd": 99, + "fullStart": 95 + } + ], + "callee": { + "context": { + "id": "node@@ts@[L8:C2, L8:C4]", + "snippet": "ts" + }, + "children": { + "expression": { + "context": { + "id": "node@@ts@[L8:C2, L8:C4]", + "snippet": "ts" + }, + "children": { + "variable": { + "context": { + "id": "token@@ts@[L8:C2, L8:C4]", + "snippet": "ts" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "ts" + } + }, + "fullEnd": 95, + "fullStart": 90 + } + }, + "fullEnd": 95, + "fullStart": 90 + } + }, + "fullEnd": 99, + "fullStart": 90 + } + ] + }, + "fullEnd": 101, + "fullStart": 79 + }, + "name": { + "context": { + "id": "node@@@[L6:C6, L6:C22]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L6:C6, L6:C15]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L6:C6, L6:C15]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L6:C6, L6:C15]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 71, + "fullStart": 62 + } + }, + "fullEnd": 71, + "fullStart": 62 + }, + "op": { + "context": { + "id": "token@@.@[L6:C15, L6:C16]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L6:C16, L6:C22]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L6:C16, L6:C22]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L6:C16, L6:C22]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 79, + "fullStart": 72 + } + }, + "fullEnd": 79, + "fullStart": 72 + } + }, + "fullEnd": 79, + "fullStart": 62 + }, + "type": { + "context": { + "id": "token@@Table@[L6:C0, L6:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 101, + "fullStart": 56 + }, + { + "context": { + "id": "node@@another_schema.booking@[L11:C0, L14:C1]", + "snippet": "Table anot... ts int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L11:C29, L14:C1]", + "snippet": "{\n id int... ts int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L14:C0, L14:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L11:C29, L11:C30]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L12:C2, L12:C8]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L12:C5, L12:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L12:C5, L12:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L12:C5, L12:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 142, + "fullStart": 138 + } + }, + "fullEnd": 142, + "fullStart": 138 + } + ], + "callee": { + "context": { + "id": "node@@id@[L12:C2, L12:C4]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L12:C2, L12:C4]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L12:C2, L12:C4]", + "snippet": "id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 138, + "fullStart": 133 + } + }, + "fullEnd": 138, + "fullStart": 133 + } + }, + "fullEnd": 142, + "fullStart": 133 + }, + { + "context": { + "id": "node@@ts@[L13:C2, L13:C8]", + "snippet": "ts int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L13:C5, L13:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L13:C5, L13:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L13:C5, L13:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 151, + "fullStart": 147 + } + }, + "fullEnd": 151, + "fullStart": 147 + } + ], + "callee": { + "context": { + "id": "node@@ts@[L13:C2, L13:C4]", + "snippet": "ts" + }, + "children": { + "expression": { + "context": { + "id": "node@@ts@[L13:C2, L13:C4]", + "snippet": "ts" + }, + "children": { + "variable": { + "context": { + "id": "token@@ts@[L13:C2, L13:C4]", + "snippet": "ts" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "ts" + } + }, + "fullEnd": 147, + "fullStart": 142 + } + }, + "fullEnd": 147, + "fullStart": 142 + } + }, + "fullEnd": 151, + "fullStart": 142 + } + ] + }, + "fullEnd": 153, + "fullStart": 131 + }, + "name": { + "context": { + "id": "node@@@[L11:C6, L11:C28]", + "snippet": "another_sc...ma.booking" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@another_schema@[L11:C6, L11:C20]", + "snippet": "another_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@another_schema@[L11:C6, L11:C20]", + "snippet": "another_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@another_schema@[L11:C6, L11:C20]", + "snippet": "another_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "another_schema" + } + }, + "fullEnd": 122, + "fullStart": 108 + } + }, + "fullEnd": 122, + "fullStart": 108 + }, + "op": { + "context": { + "id": "token@@.@[L11:C20, L11:C21]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@booking@[L11:C21, L11:C28]", + "snippet": "booking" + }, + "children": { + "expression": { + "context": { + "id": "node@@booking@[L11:C21, L11:C28]", + "snippet": "booking" + }, + "children": { + "variable": { + "context": { + "id": "token@@booking@[L11:C21, L11:C28]", + "snippet": "booking" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "booking" + } + }, + "fullEnd": 131, + "fullStart": 123 + } + }, + "fullEnd": 131, + "fullStart": 123 + } + }, + "fullEnd": 131, + "fullStart": 108 + }, + "type": { + "context": { + "id": "token@@Table@[L11:C0, L11:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 153, + "fullStart": 101 + }, + { + "context": { + "id": "node@@@[L19:C0, L19:C20]", + "snippet": "Dep: users -> orders" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L19:C5, L19:C20]", + "snippet": "users -> orders" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L19:C5, L19:C20]", + "snippet": "users -> orders" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L19:C5, L19:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L19:C5, L19:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L19:C5, L19:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 192, + "fullStart": 186 + } + }, + "fullEnd": 192, + "fullStart": 186 + }, + "op": { + "context": { + "id": "token@@->@[L19:C11, L19:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@orders@[L19:C14, L19:C20]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L19:C14, L19:C20]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L19:C14, L19:C20]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 202, + "fullStart": 195 + } + }, + "fullEnd": 202, + "fullStart": 195 + } + }, + "fullEnd": 202, + "fullStart": 186 + } + }, + "fullEnd": 202, + "fullStart": 186 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L19:C3, L19:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L19:C0, L19:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n\n short-form\n no error\n", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 202, + "fullStart": 153 + }, + { + "context": { + "id": "node@@@[L20:C0, L20:C31]", + "snippet": "Dep: order...- users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L20:C5, L20:C31]", + "snippet": "orders.use...- users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L20:C5, L20:C31]", + "snippet": "orders.use...- users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L20:C5, L20:C19]", + "snippet": "orders.user_id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@orders@[L20:C5, L20:C11]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L20:C5, L20:C11]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L20:C5, L20:C11]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "orders" + } + }, + "fullEnd": 213, + "fullStart": 207 + } + }, + "fullEnd": 213, + "fullStart": 207 + }, + "op": { + "context": { + "id": "token@@.@[L20:C11, L20:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@user_id@[L20:C12, L20:C19]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L20:C12, L20:C19]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L20:C12, L20:C19]", + "snippet": "user_id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "user_id" + } + }, + "fullEnd": 222, + "fullStart": 214 + } + }, + "fullEnd": 222, + "fullStart": 214 + } + }, + "fullEnd": 222, + "fullStart": 207 + }, + "op": { + "context": { + "id": "token@@<-@[L20:C20, L20:C22]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + }, + "rightExpression": { + "context": { + "id": "node@@@[L20:C23, L20:C31]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L20:C23, L20:C28]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L20:C23, L20:C28]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L20:C23, L20:C28]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 230, + "fullStart": 225 + } + }, + "fullEnd": 230, + "fullStart": 225 + }, + "op": { + "context": { + "id": "token@@.@[L20:C28, L20:C29]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L20:C29, L20:C31]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L20:C29, L20:C31]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L20:C29, L20:C31]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 234, + "fullStart": 231 + } + }, + "fullEnd": 234, + "fullStart": 231 + } + }, + "fullEnd": 234, + "fullStart": 225 + } + }, + "fullEnd": 234, + "fullStart": 207 + } + }, + "fullEnd": 234, + "fullStart": 207 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L20:C3, L20:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L20:C0, L20:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 234, + "fullStart": 202 + }, + { + "context": { + "id": "node@@@[L21:C0, L21:C30]", + "snippet": "Dep: my_sc...s -> users" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L21:C5, L21:C30]", + "snippet": "my_schema....s -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L21:C5, L21:C30]", + "snippet": "my_schema....s -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L21:C5, L21:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L21:C5, L21:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L21:C5, L21:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L21:C5, L21:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 248, + "fullStart": 239 + } + }, + "fullEnd": 248, + "fullStart": 239 + }, + "op": { + "context": { + "id": "token@@.@[L21:C14, L21:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L21:C15, L21:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L21:C15, L21:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L21:C15, L21:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 256, + "fullStart": 249 + } + }, + "fullEnd": 256, + "fullStart": 249 + } + }, + "fullEnd": 256, + "fullStart": 239 + }, + "op": { + "context": { + "id": "token@@->@[L21:C22, L21:C24]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L21:C25, L21:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L21:C25, L21:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L21:C25, L21:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 265, + "fullStart": 259 + } + }, + "fullEnd": 265, + "fullStart": 259 + } + }, + "fullEnd": 265, + "fullStart": 239 + } + }, + "fullEnd": 265, + "fullStart": 239 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L21:C3, L21:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L21:C0, L21:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 265, + "fullStart": 234 + }, + { + "context": { + "id": "node@@@[L22:C0, L22:C36]", + "snippet": "Dep: my_sc...> users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L22:C5, L22:C36]", + "snippet": "my_schema....> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L22:C5, L22:C36]", + "snippet": "my_schema....> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L22:C5, L22:C24]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L22:C5, L22:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L22:C5, L22:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L22:C5, L22:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L22:C5, L22:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 279, + "fullStart": 270 + } + }, + "fullEnd": 279, + "fullStart": 270 + }, + "op": { + "context": { + "id": "token@@.@[L22:C14, L22:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L22:C15, L22:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L22:C15, L22:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L22:C15, L22:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 286, + "fullStart": 280 + } + }, + "fullEnd": 286, + "fullStart": 280 + } + }, + "fullEnd": 286, + "fullStart": 270 + }, + "op": { + "context": { + "id": "token@@.@[L22:C21, L22:C22]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L22:C22, L22:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L22:C22, L22:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L22:C22, L22:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 290, + "fullStart": 287 + } + }, + "fullEnd": 290, + "fullStart": 287 + } + }, + "fullEnd": 290, + "fullStart": 270 + }, + "op": { + "context": { + "id": "token@@->@[L22:C25, L22:C27]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L22:C28, L22:C36]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L22:C28, L22:C33]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L22:C28, L22:C33]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L22:C28, L22:C33]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 298, + "fullStart": 293 + } + }, + "fullEnd": 298, + "fullStart": 293 + }, + "op": { + "context": { + "id": "token@@.@[L22:C33, L22:C34]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L22:C34, L22:C36]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L22:C34, L22:C36]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L22:C34, L22:C36]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 302, + "fullStart": 299 + } + }, + "fullEnd": 302, + "fullStart": 299 + } + }, + "fullEnd": 302, + "fullStart": 293 + } + }, + "fullEnd": 302, + "fullStart": 270 + } + }, + "fullEnd": 302, + "fullStart": 270 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L22:C3, L22:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L22:C0, L22:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 302, + "fullStart": 265 + }, + { + "context": { + "id": "node@@@[L23:C0, L23:C53]", + "snippet": "Dep: my_sc...booking.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L23:C5, L23:C53]", + "snippet": "my_schema....booking.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L23:C5, L23:C53]", + "snippet": "my_schema....booking.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L23:C5, L23:C24]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L23:C5, L23:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L23:C5, L23:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L23:C5, L23:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L23:C5, L23:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 316, + "fullStart": 307 + } + }, + "fullEnd": 316, + "fullStart": 307 + }, + "op": { + "context": { + "id": "token@@.@[L23:C14, L23:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L23:C15, L23:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L23:C15, L23:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L23:C15, L23:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 323, + "fullStart": 317 + } + }, + "fullEnd": 323, + "fullStart": 317 + } + }, + "fullEnd": 323, + "fullStart": 307 + }, + "op": { + "context": { + "id": "token@@.@[L23:C21, L23:C22]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L23:C22, L23:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L23:C22, L23:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L23:C22, L23:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 327, + "fullStart": 324 + } + }, + "fullEnd": 327, + "fullStart": 324 + } + }, + "fullEnd": 327, + "fullStart": 307 + }, + "op": { + "context": { + "id": "token@@<-@[L23:C25, L23:C27]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + }, + "rightExpression": { + "context": { + "id": "node@@@[L23:C28, L23:C53]", + "snippet": "another_sc...booking.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L23:C28, L23:C50]", + "snippet": "another_sc...ma.booking" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@another_schema@[L23:C28, L23:C42]", + "snippet": "another_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@another_schema@[L23:C28, L23:C42]", + "snippet": "another_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@another_schema@[L23:C28, L23:C42]", + "snippet": "another_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "another_schema" + } + }, + "fullEnd": 344, + "fullStart": 330 + } + }, + "fullEnd": 344, + "fullStart": 330 + }, + "op": { + "context": { + "id": "token@@.@[L23:C42, L23:C43]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@booking@[L23:C43, L23:C50]", + "snippet": "booking" + }, + "children": { + "expression": { + "context": { + "id": "node@@booking@[L23:C43, L23:C50]", + "snippet": "booking" + }, + "children": { + "variable": { + "context": { + "id": "token@@booking@[L23:C43, L23:C50]", + "snippet": "booking" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "booking" + } + }, + "fullEnd": 352, + "fullStart": 345 + } + }, + "fullEnd": 352, + "fullStart": 345 + } + }, + "fullEnd": 352, + "fullStart": 330 + }, + "op": { + "context": { + "id": "token@@.@[L23:C50, L23:C51]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L23:C51, L23:C53]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L23:C51, L23:C53]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L23:C51, L23:C53]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 356, + "fullStart": 353 + } + }, + "fullEnd": 356, + "fullStart": 353 + } + }, + "fullEnd": 356, + "fullStart": 330 + } + }, + "fullEnd": 356, + "fullStart": 307 + } + }, + "fullEnd": 356, + "fullStart": 307 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L23:C3, L23:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L23:C0, L23:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 356, + "fullStart": 302 + }, + { + "context": { + "id": "node@@@[L27:C0, L32:C1]", + "snippet": "Dep {\n us...users.id\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L27:C4, L32:C1]", + "snippet": "{\n users ...users.id\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L32:C0, L32:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L27:C4, L27:C5]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@@[L28:C2, L28:C17]", + "snippet": "users -> orders" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L28:C2, L28:C17]", + "snippet": "users -> orders" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L28:C2, L28:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L28:C2, L28:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L28:C2, L28:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 396, + "fullStart": 388 + } + }, + "fullEnd": 396, + "fullStart": 388 + }, + "op": { + "context": { + "id": "token@@->@[L28:C8, L28:C10]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@orders@[L28:C11, L28:C17]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L28:C11, L28:C17]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L28:C11, L28:C17]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 406, + "fullStart": 399 + } + }, + "fullEnd": 406, + "fullStart": 399 + } + }, + "fullEnd": 406, + "fullStart": 388 + } + }, + "fullEnd": 406, + "fullStart": 388 + }, + { + "context": { + "id": "node@@@[L29:C2, L29:C27]", + "snippet": "my_schema....s -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L29:C2, L29:C27]", + "snippet": "my_schema....s -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L29:C2, L29:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L29:C2, L29:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L29:C2, L29:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L29:C2, L29:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 417, + "fullStart": 406 + } + }, + "fullEnd": 417, + "fullStart": 406 + }, + "op": { + "context": { + "id": "token@@.@[L29:C11, L29:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L29:C12, L29:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L29:C12, L29:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L29:C12, L29:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 425, + "fullStart": 418 + } + }, + "fullEnd": 425, + "fullStart": 418 + } + }, + "fullEnd": 425, + "fullStart": 406 + }, + "op": { + "context": { + "id": "token@@->@[L29:C19, L29:C21]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L29:C22, L29:C27]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L29:C22, L29:C27]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L29:C22, L29:C27]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 434, + "fullStart": 428 + } + }, + "fullEnd": 434, + "fullStart": 428 + } + }, + "fullEnd": 434, + "fullStart": 406 + } + }, + "fullEnd": 434, + "fullStart": 406 + }, + { + "context": { + "id": "node@@@[L30:C2, L30:C28]", + "snippet": "users.id -...rs.user_id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L30:C2, L30:C28]", + "snippet": "users.id -...rs.user_id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L30:C2, L30:C10]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L30:C2, L30:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L30:C2, L30:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L30:C2, L30:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 441, + "fullStart": 434 + } + }, + "fullEnd": 441, + "fullStart": 434 + }, + "op": { + "context": { + "id": "token@@.@[L30:C7, L30:C8]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L30:C8, L30:C10]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L30:C8, L30:C10]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L30:C8, L30:C10]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 445, + "fullStart": 442 + } + }, + "fullEnd": 445, + "fullStart": 442 + } + }, + "fullEnd": 445, + "fullStart": 434 + }, + "op": { + "context": { + "id": "token@@->@[L30:C11, L30:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L30:C14, L30:C28]", + "snippet": "orders.user_id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@orders@[L30:C14, L30:C20]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L30:C14, L30:C20]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L30:C14, L30:C20]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "orders" + } + }, + "fullEnd": 454, + "fullStart": 448 + } + }, + "fullEnd": 454, + "fullStart": 448 + }, + "op": { + "context": { + "id": "token@@.@[L30:C20, L30:C21]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@user_id@[L30:C21, L30:C28]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L30:C21, L30:C28]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L30:C21, L30:C28]", + "snippet": "user_id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "user_id" + } + }, + "fullEnd": 463, + "fullStart": 455 + } + }, + "fullEnd": 463, + "fullStart": 455 + } + }, + "fullEnd": 463, + "fullStart": 448 + } + }, + "fullEnd": 463, + "fullStart": 434 + } + }, + "fullEnd": 463, + "fullStart": 434 + }, + { + "context": { + "id": "node@@@[L31:C2, L31:C33]", + "snippet": "my_schema....> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L31:C2, L31:C33]", + "snippet": "my_schema....> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L31:C2, L31:C21]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L31:C2, L31:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L31:C2, L31:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L31:C2, L31:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L31:C2, L31:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 474, + "fullStart": 463 + } + }, + "fullEnd": 474, + "fullStart": 463 + }, + "op": { + "context": { + "id": "token@@.@[L31:C11, L31:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L31:C12, L31:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L31:C12, L31:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L31:C12, L31:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 481, + "fullStart": 475 + } + }, + "fullEnd": 481, + "fullStart": 475 + } + }, + "fullEnd": 481, + "fullStart": 463 + }, + "op": { + "context": { + "id": "token@@.@[L31:C18, L31:C19]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L31:C19, L31:C21]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L31:C19, L31:C21]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L31:C19, L31:C21]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 485, + "fullStart": 482 + } + }, + "fullEnd": 485, + "fullStart": 482 + } + }, + "fullEnd": 485, + "fullStart": 463 + }, + "op": { + "context": { + "id": "token@@->@[L31:C22, L31:C24]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L31:C25, L31:C33]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L31:C25, L31:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L31:C25, L31:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L31:C25, L31:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 493, + "fullStart": 488 + } + }, + "fullEnd": 493, + "fullStart": 488 + }, + "op": { + "context": { + "id": "token@@.@[L31:C30, L31:C31]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L31:C31, L31:C33]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L31:C31, L31:C33]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L31:C31, L31:C33]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 497, + "fullStart": 494 + } + }, + "fullEnd": 497, + "fullStart": 494 + } + }, + "fullEnd": 497, + "fullStart": 488 + } + }, + "fullEnd": 497, + "fullStart": 463 + } + }, + "fullEnd": 497, + "fullStart": 463 + } + ] + }, + "fullEnd": 499, + "fullStart": 386 + }, + "type": { + "context": { + "id": "token@@Dep@[L27:C0, L27:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n full-form\n no error\n", + "trailingTrivia": " ", + "value": "Dep" + } + }, + "fullEnd": 499, + "fullStart": 356 + }, + { + "context": { + "id": "node@@good_header_bare@[L36:C0, L36:C49]", + "snippet": "Table good...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L36:C23, L36:C38]", + "snippet": "[dep: <- users]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L36:C24, L36:C37]", + "snippet": "dep: <- users" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L36:C27, L36:C28]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L36:C24, L36:C27]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L36:C24, L36:C27]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 565, + "fullStart": 562 + }, + "value": { + "context": { + "id": "node@@@[L36:C29, L36:C37]", + "snippet": "<- users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L36:C32, L36:C37]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L36:C32, L36:C37]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L36:C32, L36:C37]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 575, + "fullStart": 570 + } + }, + "fullEnd": 575, + "fullStart": 570 + }, + "op": { + "context": { + "id": "token@@<-@[L36:C29, L36:C31]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 575, + "fullStart": 567 + } + }, + "fullEnd": 575, + "fullStart": 562 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L36:C37, L36:C38]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L36:C23, L36:C24]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 577, + "fullStart": 561 + }, + "body": { + "context": { + "id": "node@@@[L36:C39, L36:C49]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L36:C48, L36:C49]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L36:C39, L36:C40]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L36:C41, L36:C47]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L36:C44, L36:C47]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L36:C44, L36:C47]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L36:C44, L36:C47]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 586, + "fullStart": 582 + } + }, + "fullEnd": 586, + "fullStart": 582 + } + ], + "callee": { + "context": { + "id": "node@@id@[L36:C41, L36:C43]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L36:C41, L36:C43]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L36:C41, L36:C43]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 582, + "fullStart": 579 + } + }, + "fullEnd": 582, + "fullStart": 579 + } + }, + "fullEnd": 586, + "fullStart": 579 + } + ] + }, + "fullEnd": 588, + "fullStart": 577 + }, + "name": { + "context": { + "id": "node@@good_header_bare@[L36:C6, L36:C22]", + "snippet": "good_header_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_header_bare@[L36:C6, L36:C22]", + "snippet": "good_header_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_header_bare@[L36:C6, L36:C22]", + "snippet": "good_header_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_header_bare" + } + }, + "fullEnd": 561, + "fullStart": 544 + } + }, + "fullEnd": 561, + "fullStart": 544 + }, + "type": { + "context": { + "id": "token@@Table@[L36:C0, L36:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on table header\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 588, + "fullStart": 499 + }, + { + "context": { + "id": "node@@good_header_schema@[L37:C0, L37:C62]", + "snippet": "Table good...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L37:C25, L37:C51]", + "snippet": "[dep: <- m...ma.events]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L37:C26, L37:C50]", + "snippet": "dep: <- my...ema.events" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L37:C29, L37:C30]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L37:C26, L37:C29]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L37:C26, L37:C29]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 617, + "fullStart": 614 + }, + "value": { + "context": { + "id": "node@@@[L37:C31, L37:C50]", + "snippet": "<- my_schema.events" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L37:C34, L37:C50]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L37:C34, L37:C43]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L37:C34, L37:C43]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L37:C34, L37:C43]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 631, + "fullStart": 622 + } + }, + "fullEnd": 631, + "fullStart": 622 + }, + "op": { + "context": { + "id": "token@@.@[L37:C43, L37:C44]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L37:C44, L37:C50]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L37:C44, L37:C50]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L37:C44, L37:C50]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 638, + "fullStart": 632 + } + }, + "fullEnd": 638, + "fullStart": 632 + } + }, + "fullEnd": 638, + "fullStart": 622 + }, + "op": { + "context": { + "id": "token@@<-@[L37:C31, L37:C33]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 638, + "fullStart": 619 + } + }, + "fullEnd": 638, + "fullStart": 614 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L37:C50, L37:C51]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L37:C25, L37:C26]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 640, + "fullStart": 613 + }, + "body": { + "context": { + "id": "node@@@[L37:C52, L37:C62]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L37:C61, L37:C62]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L37:C52, L37:C53]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L37:C54, L37:C60]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L37:C57, L37:C60]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L37:C57, L37:C60]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L37:C57, L37:C60]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 649, + "fullStart": 645 + } + }, + "fullEnd": 649, + "fullStart": 645 + } + ], + "callee": { + "context": { + "id": "node@@id@[L37:C54, L37:C56]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L37:C54, L37:C56]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L37:C54, L37:C56]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 645, + "fullStart": 642 + } + }, + "fullEnd": 645, + "fullStart": 642 + } + }, + "fullEnd": 649, + "fullStart": 642 + } + ] + }, + "fullEnd": 651, + "fullStart": 640 + }, + "name": { + "context": { + "id": "node@@good_header_schema@[L37:C6, L37:C24]", + "snippet": "good_header_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_header_schema@[L37:C6, L37:C24]", + "snippet": "good_header_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_header_schema@[L37:C6, L37:C24]", + "snippet": "good_header_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_header_schema" + } + }, + "fullEnd": 613, + "fullStart": 594 + } + }, + "fullEnd": 613, + "fullStart": 594 + }, + "type": { + "context": { + "id": "token@@Table@[L37:C0, L37:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 651, + "fullStart": 588 + }, + { + "context": { + "id": "node@@good_col_bare@[L41:C0, L41:C49]", + "snippet": "Table good...sers.id] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L41:C20, L41:C49]", + "snippet": "{ id int [...sers.id] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L41:C48, L41:C49]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L41:C20, L41:C21]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L41:C22, L41:C47]", + "snippet": "id int [de... users.id]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L41:C25, L41:C28]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L41:C25, L41:C28]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L41:C25, L41:C28]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 713, + "fullStart": 709 + } + }, + "fullEnd": 713, + "fullStart": 709 + }, + { + "context": { + "id": "node@@@[L41:C29, L41:C47]", + "snippet": "[dep: -> users.id]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L41:C30, L41:C46]", + "snippet": "dep: -> users.id" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L41:C33, L41:C34]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L41:C30, L41:C33]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L41:C30, L41:C33]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 717, + "fullStart": 714 + }, + "value": { + "context": { + "id": "node@@@[L41:C35, L41:C46]", + "snippet": "-> users.id" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L41:C38, L41:C46]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L41:C38, L41:C43]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L41:C38, L41:C43]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L41:C38, L41:C43]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 727, + "fullStart": 722 + } + }, + "fullEnd": 727, + "fullStart": 722 + }, + "op": { + "context": { + "id": "token@@.@[L41:C43, L41:C44]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L41:C44, L41:C46]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L41:C44, L41:C46]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L41:C44, L41:C46]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "id" + } + }, + "fullEnd": 730, + "fullStart": 728 + } + }, + "fullEnd": 730, + "fullStart": 728 + } + }, + "fullEnd": 730, + "fullStart": 722 + }, + "op": { + "context": { + "id": "token@@->@[L41:C35, L41:C37]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 730, + "fullStart": 719 + } + }, + "fullEnd": 730, + "fullStart": 714 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L41:C46, L41:C47]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L41:C29, L41:C30]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 732, + "fullStart": 713 + } + ], + "callee": { + "context": { + "id": "node@@id@[L41:C22, L41:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L41:C22, L41:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L41:C22, L41:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 709, + "fullStart": 706 + } + }, + "fullEnd": 709, + "fullStart": 706 + } + }, + "fullEnd": 732, + "fullStart": 706 + } + ] + }, + "fullEnd": 734, + "fullStart": 704 + }, + "name": { + "context": { + "id": "node@@good_col_bare@[L41:C6, L41:C19]", + "snippet": "good_col_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_col_bare@[L41:C6, L41:C19]", + "snippet": "good_col_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_col_bare@[L41:C6, L41:C19]", + "snippet": "good_col_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_col_bare" + } + }, + "fullEnd": 704, + "fullStart": 690 + } + }, + "fullEnd": 704, + "fullStart": 690 + }, + "type": { + "context": { + "id": "token@@Table@[L41:C0, L41:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on column\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 734, + "fullStart": 651 + }, + { + "context": { + "id": "node@@good_col_schema@[L42:C0, L42:C62]", + "snippet": "Table good...ents.id] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L42:C22, L42:C62]", + "snippet": "{ id int [...ents.id] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L42:C61, L42:C62]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L42:C22, L42:C23]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L42:C24, L42:C60]", + "snippet": "id int [de...events.id]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L42:C27, L42:C30]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L42:C27, L42:C30]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L42:C27, L42:C30]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 765, + "fullStart": 761 + } + }, + "fullEnd": 765, + "fullStart": 761 + }, + { + "context": { + "id": "node@@@[L42:C31, L42:C60]", + "snippet": "[dep: -> m...events.id]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L42:C32, L42:C59]", + "snippet": "dep: -> my....events.id" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L42:C35, L42:C36]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L42:C32, L42:C35]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L42:C32, L42:C35]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 769, + "fullStart": 766 + }, + "value": { + "context": { + "id": "node@@@[L42:C37, L42:C59]", + "snippet": "-> my_sche....events.id" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L42:C40, L42:C59]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L42:C40, L42:C56]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L42:C40, L42:C49]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L42:C40, L42:C49]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L42:C40, L42:C49]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 783, + "fullStart": 774 + } + }, + "fullEnd": 783, + "fullStart": 774 + }, + "op": { + "context": { + "id": "token@@.@[L42:C49, L42:C50]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L42:C50, L42:C56]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L42:C50, L42:C56]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L42:C50, L42:C56]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 790, + "fullStart": 784 + } + }, + "fullEnd": 790, + "fullStart": 784 + } + }, + "fullEnd": 790, + "fullStart": 774 + }, + "op": { + "context": { + "id": "token@@.@[L42:C56, L42:C57]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L42:C57, L42:C59]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L42:C57, L42:C59]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L42:C57, L42:C59]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "id" + } + }, + "fullEnd": 793, + "fullStart": 791 + } + }, + "fullEnd": 793, + "fullStart": 791 + } + }, + "fullEnd": 793, + "fullStart": 774 + }, + "op": { + "context": { + "id": "token@@->@[L42:C37, L42:C39]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 793, + "fullStart": 771 + } + }, + "fullEnd": 793, + "fullStart": 766 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L42:C59, L42:C60]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L42:C31, L42:C32]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 795, + "fullStart": 765 + } + ], + "callee": { + "context": { + "id": "node@@id@[L42:C24, L42:C26]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L42:C24, L42:C26]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L42:C24, L42:C26]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 761, + "fullStart": 758 + } + }, + "fullEnd": 761, + "fullStart": 758 + } + }, + "fullEnd": 795, + "fullStart": 758 + } + ] + }, + "fullEnd": 797, + "fullStart": 756 + }, + "name": { + "context": { + "id": "node@@good_col_schema@[L42:C6, L42:C21]", + "snippet": "good_col_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_col_schema@[L42:C6, L42:C21]", + "snippet": "good_col_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_col_schema@[L42:C6, L42:C21]", + "snippet": "good_col_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_col_schema" + } + }, + "fullEnd": 756, + "fullStart": 740 + } + }, + "fullEnd": 756, + "fullStart": 740 + }, + "type": { + "context": { + "id": "token@@Table@[L42:C0, L42:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 797, + "fullStart": 734 + }, + { + "context": { + "id": "node@@@[L46:C0, L46:C27]", + "snippet": "Dep: users...nown_table" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L46:C5, L46:C27]", + "snippet": "users -> u...nown_table" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L46:C5, L46:C27]", + "snippet": "users -> u...nown_table" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L46:C5, L46:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L46:C5, L46:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L46:C5, L46:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 835, + "fullStart": 829 + } + }, + "fullEnd": 835, + "fullStart": 829 + }, + "op": { + "context": { + "id": "token@@->@[L46:C11, L46:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@unknown_table@[L46:C14, L46:C27]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L46:C14, L46:C27]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L46:C14, L46:C27]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_table" + } + }, + "fullEnd": 852, + "fullStart": 838 + } + }, + "fullEnd": 852, + "fullStart": 838 + } + }, + "fullEnd": 852, + "fullStart": 829 + } + }, + "fullEnd": 852, + "fullStart": 829 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L46:C3, L46:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L46:C0, L46:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n short-form\n no error\n", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 852, + "fullStart": 797 + }, + { + "context": { + "id": "node@@@[L47:C0, L47:C34]", + "snippet": "Dep: users...> users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L47:C5, L47:C34]", + "snippet": "users.unkn...> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L47:C5, L47:C34]", + "snippet": "users.unkn...> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L47:C5, L47:C22]", + "snippet": "users.unknown_col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L47:C5, L47:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L47:C5, L47:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L47:C5, L47:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 862, + "fullStart": 857 + } + }, + "fullEnd": 862, + "fullStart": 857 + }, + "op": { + "context": { + "id": "token@@.@[L47:C10, L47:C11]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_col@[L47:C11, L47:C22]", + "snippet": "unknown_col" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_col@[L47:C11, L47:C22]", + "snippet": "unknown_col" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_col@[L47:C11, L47:C22]", + "snippet": "unknown_col" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "unknown_col" + } + }, + "fullEnd": 875, + "fullStart": 863 + } + }, + "fullEnd": 875, + "fullStart": 863 + } + }, + "fullEnd": 875, + "fullStart": 857 + }, + "op": { + "context": { + "id": "token@@->@[L47:C23, L47:C25]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L47:C26, L47:C34]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L47:C26, L47:C31]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L47:C26, L47:C31]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L47:C26, L47:C31]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 883, + "fullStart": 878 + } + }, + "fullEnd": 883, + "fullStart": 878 + }, + "op": { + "context": { + "id": "token@@.@[L47:C31, L47:C32]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L47:C32, L47:C34]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L47:C32, L47:C34]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L47:C32, L47:C34]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 887, + "fullStart": 884 + } + }, + "fullEnd": 887, + "fullStart": 884 + } + }, + "fullEnd": 887, + "fullStart": 878 + } + }, + "fullEnd": 887, + "fullStart": 857 + } + }, + "fullEnd": 887, + "fullStart": 857 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L47:C3, L47:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L47:C0, L47:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 887, + "fullStart": 852 + }, + { + "context": { + "id": "node@@@[L48:C0, L48:C35]", + "snippet": "Dep: unkno...s -> users" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L48:C5, L48:C35]", + "snippet": "unknown_sc...s -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L48:C5, L48:C35]", + "snippet": "unknown_sc...s -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L48:C5, L48:C26]", + "snippet": "unknown_sc...ema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_schema@[L48:C5, L48:C19]", + "snippet": "unknown_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_schema@[L48:C5, L48:C19]", + "snippet": "unknown_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_schema@[L48:C5, L48:C19]", + "snippet": "unknown_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_schema" + } + }, + "fullEnd": 906, + "fullStart": 892 + } + }, + "fullEnd": 906, + "fullStart": 892 + }, + "op": { + "context": { + "id": "token@@.@[L48:C19, L48:C20]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L48:C20, L48:C26]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L48:C20, L48:C26]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L48:C20, L48:C26]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 914, + "fullStart": 907 + } + }, + "fullEnd": 914, + "fullStart": 907 + } + }, + "fullEnd": 914, + "fullStart": 892 + }, + "op": { + "context": { + "id": "token@@->@[L48:C27, L48:C29]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L48:C30, L48:C35]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L48:C30, L48:C35]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L48:C30, L48:C35]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 923, + "fullStart": 917 + } + }, + "fullEnd": 923, + "fullStart": 917 + } + }, + "fullEnd": 923, + "fullStart": 892 + } + }, + "fullEnd": 923, + "fullStart": 892 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L48:C3, L48:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L48:C0, L48:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 923, + "fullStart": 887 + }, + { + "context": { + "id": "node@@@[L49:C0, L49:C37]", + "snippet": "Dep: my_sc...e -> users" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L49:C5, L49:C37]", + "snippet": "my_schema....e -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L49:C5, L49:C37]", + "snippet": "my_schema....e -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L49:C5, L49:C28]", + "snippet": "my_schema....nown_table" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L49:C5, L49:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L49:C5, L49:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L49:C5, L49:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 937, + "fullStart": 928 + } + }, + "fullEnd": 937, + "fullStart": 928 + }, + "op": { + "context": { + "id": "token@@.@[L49:C14, L49:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_table@[L49:C15, L49:C28]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L49:C15, L49:C28]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L49:C15, L49:C28]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "unknown_table" + } + }, + "fullEnd": 952, + "fullStart": 938 + } + }, + "fullEnd": 952, + "fullStart": 938 + } + }, + "fullEnd": 952, + "fullStart": 928 + }, + "op": { + "context": { + "id": "token@@->@[L49:C29, L49:C31]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L49:C32, L49:C37]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L49:C32, L49:C37]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L49:C32, L49:C37]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 961, + "fullStart": 955 + } + }, + "fullEnd": 961, + "fullStart": 955 + } + }, + "fullEnd": 961, + "fullStart": 928 + } + }, + "fullEnd": 961, + "fullStart": 928 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L49:C3, L49:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L49:C0, L49:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 961, + "fullStart": 923 + }, + { + "context": { + "id": "node@@@[L50:C0, L50:C45]", + "snippet": "Dep: my_sc...> users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L50:C5, L50:C45]", + "snippet": "my_schema....> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L50:C5, L50:C45]", + "snippet": "my_schema....> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L50:C5, L50:C33]", + "snippet": "my_schema....nknown_col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L50:C5, L50:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L50:C5, L50:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L50:C5, L50:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L50:C5, L50:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 975, + "fullStart": 966 + } + }, + "fullEnd": 975, + "fullStart": 966 + }, + "op": { + "context": { + "id": "token@@.@[L50:C14, L50:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L50:C15, L50:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L50:C15, L50:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L50:C15, L50:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 982, + "fullStart": 976 + } + }, + "fullEnd": 982, + "fullStart": 976 + } + }, + "fullEnd": 982, + "fullStart": 966 + }, + "op": { + "context": { + "id": "token@@.@[L50:C21, L50:C22]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_col@[L50:C22, L50:C33]", + "snippet": "unknown_col" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_col@[L50:C22, L50:C33]", + "snippet": "unknown_col" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_col@[L50:C22, L50:C33]", + "snippet": "unknown_col" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "unknown_col" + } + }, + "fullEnd": 995, + "fullStart": 983 + } + }, + "fullEnd": 995, + "fullStart": 983 + } + }, + "fullEnd": 995, + "fullStart": 966 + }, + "op": { + "context": { + "id": "token@@->@[L50:C34, L50:C36]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L50:C37, L50:C45]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L50:C37, L50:C42]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L50:C37, L50:C42]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L50:C37, L50:C42]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1003, + "fullStart": 998 + } + }, + "fullEnd": 1003, + "fullStart": 998 + }, + "op": { + "context": { + "id": "token@@.@[L50:C42, L50:C43]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L50:C43, L50:C45]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L50:C43, L50:C45]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L50:C43, L50:C45]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 1007, + "fullStart": 1004 + } + }, + "fullEnd": 1007, + "fullStart": 1004 + } + }, + "fullEnd": 1007, + "fullStart": 998 + } + }, + "fullEnd": 1007, + "fullStart": 966 + } + }, + "fullEnd": 1007, + "fullStart": 966 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L50:C3, L50:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L50:C0, L50:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 1007, + "fullStart": 961 + }, + { + "context": { + "id": "node@@@[L54:C0, L59:C1]", + "snippet": "Dep {\n us...nknown_d\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L54:C4, L59:C1]", + "snippet": "{\n users ...nknown_d\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L59:C0, L59:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L54:C4, L54:C5]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@@[L55:C2, L55:C20]", + "snippet": "users -> unknown_a" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L55:C2, L55:C20]", + "snippet": "users -> unknown_a" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L55:C2, L55:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L55:C2, L55:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L55:C2, L55:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 1047, + "fullStart": 1039 + } + }, + "fullEnd": 1047, + "fullStart": 1039 + }, + "op": { + "context": { + "id": "token@@->@[L55:C8, L55:C10]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@unknown_a@[L55:C11, L55:C20]", + "snippet": "unknown_a" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_a@[L55:C11, L55:C20]", + "snippet": "unknown_a" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_a@[L55:C11, L55:C20]", + "snippet": "unknown_a" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_a" + } + }, + "fullEnd": 1060, + "fullStart": 1050 + } + }, + "fullEnd": 1060, + "fullStart": 1050 + } + }, + "fullEnd": 1060, + "fullStart": 1039 + } + }, + "fullEnd": 1060, + "fullStart": 1039 + }, + { + "context": { + "id": "node@@@[L56:C2, L56:C31]", + "snippet": "my_schema.... unknown_b" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L56:C2, L56:C31]", + "snippet": "my_schema.... unknown_b" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L56:C2, L56:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L56:C2, L56:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L56:C2, L56:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L56:C2, L56:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1071, + "fullStart": 1060 + } + }, + "fullEnd": 1071, + "fullStart": 1060 + }, + "op": { + "context": { + "id": "token@@.@[L56:C11, L56:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L56:C12, L56:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L56:C12, L56:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L56:C12, L56:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 1079, + "fullStart": 1072 + } + }, + "fullEnd": 1079, + "fullStart": 1072 + } + }, + "fullEnd": 1079, + "fullStart": 1060 + }, + "op": { + "context": { + "id": "token@@->@[L56:C19, L56:C21]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@unknown_b@[L56:C22, L56:C31]", + "snippet": "unknown_b" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_b@[L56:C22, L56:C31]", + "snippet": "unknown_b" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_b@[L56:C22, L56:C31]", + "snippet": "unknown_b" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_b" + } + }, + "fullEnd": 1092, + "fullStart": 1082 + } + }, + "fullEnd": 1092, + "fullStart": 1082 + } + }, + "fullEnd": 1092, + "fullStart": 1060 + } + }, + "fullEnd": 1092, + "fullStart": 1060 + }, + { + "context": { + "id": "node@@@[L57:C2, L57:C27]", + "snippet": "users.id -...nown_c.col" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L57:C2, L57:C27]", + "snippet": "users.id -...nown_c.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L57:C2, L57:C10]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L57:C2, L57:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L57:C2, L57:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L57:C2, L57:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1099, + "fullStart": 1092 + } + }, + "fullEnd": 1099, + "fullStart": 1092 + }, + "op": { + "context": { + "id": "token@@.@[L57:C7, L57:C8]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L57:C8, L57:C10]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L57:C8, L57:C10]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L57:C8, L57:C10]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1103, + "fullStart": 1100 + } + }, + "fullEnd": 1103, + "fullStart": 1100 + } + }, + "fullEnd": 1103, + "fullStart": 1092 + }, + "op": { + "context": { + "id": "token@@->@[L57:C11, L57:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L57:C14, L57:C27]", + "snippet": "unknown_c.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_c@[L57:C14, L57:C23]", + "snippet": "unknown_c" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_c@[L57:C14, L57:C23]", + "snippet": "unknown_c" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_c@[L57:C14, L57:C23]", + "snippet": "unknown_c" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_c" + } + }, + "fullEnd": 1115, + "fullStart": 1106 + } + }, + "fullEnd": 1115, + "fullStart": 1106 + }, + "op": { + "context": { + "id": "token@@.@[L57:C23, L57:C24]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@col@[L57:C24, L57:C27]", + "snippet": "col" + }, + "children": { + "expression": { + "context": { + "id": "node@@col@[L57:C24, L57:C27]", + "snippet": "col" + }, + "children": { + "variable": { + "context": { + "id": "token@@col@[L57:C24, L57:C27]", + "snippet": "col" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "col" + } + }, + "fullEnd": 1120, + "fullStart": 1116 + } + }, + "fullEnd": 1120, + "fullStart": 1116 + } + }, + "fullEnd": 1120, + "fullStart": 1106 + } + }, + "fullEnd": 1120, + "fullStart": 1092 + } + }, + "fullEnd": 1120, + "fullStart": 1092 + }, + { + "context": { + "id": "node@@@[L58:C2, L58:C40]", + "snippet": "my_schema.....unknown_d" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L58:C2, L58:C40]", + "snippet": "my_schema.....unknown_d" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L58:C2, L58:C21]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L58:C2, L58:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L58:C2, L58:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L58:C2, L58:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L58:C2, L58:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1131, + "fullStart": 1120 + } + }, + "fullEnd": 1131, + "fullStart": 1120 + }, + "op": { + "context": { + "id": "token@@.@[L58:C11, L58:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L58:C12, L58:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L58:C12, L58:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L58:C12, L58:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 1138, + "fullStart": 1132 + } + }, + "fullEnd": 1138, + "fullStart": 1132 + } + }, + "fullEnd": 1138, + "fullStart": 1120 + }, + "op": { + "context": { + "id": "token@@.@[L58:C18, L58:C19]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L58:C19, L58:C21]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L58:C19, L58:C21]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L58:C19, L58:C21]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1142, + "fullStart": 1139 + } + }, + "fullEnd": 1142, + "fullStart": 1139 + } + }, + "fullEnd": 1142, + "fullStart": 1120 + }, + "op": { + "context": { + "id": "token@@->@[L58:C22, L58:C24]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L58:C25, L58:C40]", + "snippet": "users.unknown_d" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L58:C25, L58:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L58:C25, L58:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L58:C25, L58:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1150, + "fullStart": 1145 + } + }, + "fullEnd": 1150, + "fullStart": 1145 + }, + "op": { + "context": { + "id": "token@@.@[L58:C30, L58:C31]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_d@[L58:C31, L58:C40]", + "snippet": "unknown_d" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_d@[L58:C31, L58:C40]", + "snippet": "unknown_d" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_d@[L58:C31, L58:C40]", + "snippet": "unknown_d" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_d" + } + }, + "fullEnd": 1161, + "fullStart": 1151 + } + }, + "fullEnd": 1161, + "fullStart": 1151 + } + }, + "fullEnd": 1161, + "fullStart": 1145 + } + }, + "fullEnd": 1161, + "fullStart": 1120 + } + }, + "fullEnd": 1161, + "fullStart": 1120 + } + ] + }, + "fullEnd": 1163, + "fullStart": 1037 + }, + "type": { + "context": { + "id": "token@@Dep@[L54:C0, L54:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n full-form\n no error\n", + "trailingTrivia": " ", + "value": "Dep" + } + }, + "fullEnd": 1163, + "fullStart": 1007 + }, + { + "context": { + "id": "node@@bad_header_bare@[L63:C0, L63:C57]", + "snippet": "Table bad_...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L63:C22, L63:C46]", + "snippet": "[dep: <- u...wn_source]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L63:C23, L63:C45]", + "snippet": "dep: <- un...own_source" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L63:C26, L63:C27]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L63:C23, L63:C26]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L63:C23, L63:C26]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1228, + "fullStart": 1225 + }, + "value": { + "context": { + "id": "node@@@[L63:C28, L63:C45]", + "snippet": "<- unknown_source" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_source@[L63:C31, L63:C45]", + "snippet": "unknown_source" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_source@[L63:C31, L63:C45]", + "snippet": "unknown_source" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_source@[L63:C31, L63:C45]", + "snippet": "unknown_source" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_source" + } + }, + "fullEnd": 1247, + "fullStart": 1233 + } + }, + "fullEnd": 1247, + "fullStart": 1233 + }, + "op": { + "context": { + "id": "token@@<-@[L63:C28, L63:C30]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 1247, + "fullStart": 1230 + } + }, + "fullEnd": 1247, + "fullStart": 1225 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L63:C45, L63:C46]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L63:C22, L63:C23]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1249, + "fullStart": 1224 + }, + "body": { + "context": { + "id": "node@@@[L63:C47, L63:C57]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L63:C56, L63:C57]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L63:C47, L63:C48]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L63:C49, L63:C55]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L63:C52, L63:C55]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L63:C52, L63:C55]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L63:C52, L63:C55]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1258, + "fullStart": 1254 + } + }, + "fullEnd": 1258, + "fullStart": 1254 + } + ], + "callee": { + "context": { + "id": "node@@id@[L63:C49, L63:C51]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L63:C49, L63:C51]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L63:C49, L63:C51]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1254, + "fullStart": 1251 + } + }, + "fullEnd": 1254, + "fullStart": 1251 + } + }, + "fullEnd": 1258, + "fullStart": 1251 + } + ] + }, + "fullEnd": 1260, + "fullStart": 1249 + }, + "name": { + "context": { + "id": "node@@bad_header_bare@[L63:C6, L63:C21]", + "snippet": "bad_header_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_header_bare@[L63:C6, L63:C21]", + "snippet": "bad_header_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_header_bare@[L63:C6, L63:C21]", + "snippet": "bad_header_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_header_bare" + } + }, + "fullEnd": 1224, + "fullStart": 1208 + } + }, + "fullEnd": 1224, + "fullStart": 1208 + }, + "type": { + "context": { + "id": "token@@Table@[L63:C0, L63:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on table header\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1260, + "fullStart": 1163 + }, + { + "context": { + "id": "node@@bad_header_schema@[L64:C0, L64:C66]", + "snippet": "Table bad_...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L64:C24, L64:C55]", + "snippet": "[dep: <- u...ma.events]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L64:C25, L64:C54]", + "snippet": "dep: <- un...ema.events" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L64:C28, L64:C29]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L64:C25, L64:C28]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L64:C25, L64:C28]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1288, + "fullStart": 1285 + }, + "value": { + "context": { + "id": "node@@@[L64:C30, L64:C54]", + "snippet": "<- unknown...ema.events" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L64:C33, L64:C54]", + "snippet": "unknown_sc...ema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_schema@[L64:C33, L64:C47]", + "snippet": "unknown_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_schema@[L64:C33, L64:C47]", + "snippet": "unknown_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_schema@[L64:C33, L64:C47]", + "snippet": "unknown_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_schema" + } + }, + "fullEnd": 1307, + "fullStart": 1293 + } + }, + "fullEnd": 1307, + "fullStart": 1293 + }, + "op": { + "context": { + "id": "token@@.@[L64:C47, L64:C48]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L64:C48, L64:C54]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L64:C48, L64:C54]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L64:C48, L64:C54]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 1314, + "fullStart": 1308 + } + }, + "fullEnd": 1314, + "fullStart": 1308 + } + }, + "fullEnd": 1314, + "fullStart": 1293 + }, + "op": { + "context": { + "id": "token@@<-@[L64:C30, L64:C32]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 1314, + "fullStart": 1290 + } + }, + "fullEnd": 1314, + "fullStart": 1285 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L64:C54, L64:C55]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L64:C24, L64:C25]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1316, + "fullStart": 1284 + }, + "body": { + "context": { + "id": "node@@@[L64:C56, L64:C66]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L64:C65, L64:C66]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L64:C56, L64:C57]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L64:C58, L64:C64]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L64:C61, L64:C64]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L64:C61, L64:C64]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L64:C61, L64:C64]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1325, + "fullStart": 1321 + } + }, + "fullEnd": 1325, + "fullStart": 1321 + } + ], + "callee": { + "context": { + "id": "node@@id@[L64:C58, L64:C60]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L64:C58, L64:C60]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L64:C58, L64:C60]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1321, + "fullStart": 1318 + } + }, + "fullEnd": 1321, + "fullStart": 1318 + } + }, + "fullEnd": 1325, + "fullStart": 1318 + } + ] + }, + "fullEnd": 1327, + "fullStart": 1316 + }, + "name": { + "context": { + "id": "node@@bad_header_schema@[L64:C6, L64:C23]", + "snippet": "bad_header_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_header_schema@[L64:C6, L64:C23]", + "snippet": "bad_header_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_header_schema@[L64:C6, L64:C23]", + "snippet": "bad_header_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_header_schema" + } + }, + "fullEnd": 1284, + "fullStart": 1266 + } + }, + "fullEnd": 1284, + "fullStart": 1266 + }, + "type": { + "context": { + "id": "token@@Table@[L64:C0, L64:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1327, + "fullStart": 1260 + }, + { + "context": { + "id": "node@@bad_col_bare@[L68:C0, L68:C57]", + "snippet": "Table bad_...ble.col] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L68:C19, L68:C57]", + "snippet": "{ id int [...ble.col] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L68:C56, L68:C57]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L68:C19, L68:C20]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L68:C21, L68:C55]", + "snippet": "id int [de...table.col]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L68:C24, L68:C27]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L68:C24, L68:C27]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L68:C24, L68:C27]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1388, + "fullStart": 1384 + } + }, + "fullEnd": 1388, + "fullStart": 1384 + }, + { + "context": { + "id": "node@@@[L68:C28, L68:C55]", + "snippet": "[dep: -> u...table.col]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L68:C29, L68:C54]", + "snippet": "dep: -> un..._table.col" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L68:C32, L68:C33]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L68:C29, L68:C32]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L68:C29, L68:C32]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1392, + "fullStart": 1389 + }, + "value": { + "context": { + "id": "node@@@[L68:C34, L68:C54]", + "snippet": "-> unknown_table.col" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L68:C37, L68:C54]", + "snippet": "unknown_table.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_table@[L68:C37, L68:C50]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L68:C37, L68:C50]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L68:C37, L68:C50]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_table" + } + }, + "fullEnd": 1410, + "fullStart": 1397 + } + }, + "fullEnd": 1410, + "fullStart": 1397 + }, + "op": { + "context": { + "id": "token@@.@[L68:C50, L68:C51]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@col@[L68:C51, L68:C54]", + "snippet": "col" + }, + "children": { + "expression": { + "context": { + "id": "node@@col@[L68:C51, L68:C54]", + "snippet": "col" + }, + "children": { + "variable": { + "context": { + "id": "token@@col@[L68:C51, L68:C54]", + "snippet": "col" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "col" + } + }, + "fullEnd": 1414, + "fullStart": 1411 + } + }, + "fullEnd": 1414, + "fullStart": 1411 + } + }, + "fullEnd": 1414, + "fullStart": 1397 + }, + "op": { + "context": { + "id": "token@@->@[L68:C34, L68:C36]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 1414, + "fullStart": 1394 + } + }, + "fullEnd": 1414, + "fullStart": 1389 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L68:C54, L68:C55]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L68:C28, L68:C29]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1416, + "fullStart": 1388 + } + ], + "callee": { + "context": { + "id": "node@@id@[L68:C21, L68:C23]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L68:C21, L68:C23]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L68:C21, L68:C23]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1384, + "fullStart": 1381 + } + }, + "fullEnd": 1384, + "fullStart": 1381 + } + }, + "fullEnd": 1416, + "fullStart": 1381 + } + ] + }, + "fullEnd": 1418, + "fullStart": 1379 + }, + "name": { + "context": { + "id": "node@@bad_col_bare@[L68:C6, L68:C18]", + "snippet": "bad_col_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_col_bare@[L68:C6, L68:C18]", + "snippet": "bad_col_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_col_bare@[L68:C6, L68:C18]", + "snippet": "bad_col_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_col_bare" + } + }, + "fullEnd": 1379, + "fullStart": 1366 + } + }, + "fullEnd": 1379, + "fullStart": 1366 + }, + "type": { + "context": { + "id": "token@@Table@[L68:C0, L68:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on column\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1418, + "fullStart": 1327 + }, + { + "context": { + "id": "node@@bad_col_schema@[L69:C0, L69:C69]", + "snippet": "Table bad_...ble.col] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L69:C21, L69:C69]", + "snippet": "{ id int [...ble.col] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L69:C68, L69:C69]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L69:C21, L69:C22]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L69:C23, L69:C67]", + "snippet": "id int [de...table.col]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L69:C26, L69:C29]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L69:C26, L69:C29]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L69:C26, L69:C29]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1448, + "fullStart": 1444 + } + }, + "fullEnd": 1448, + "fullStart": 1444 + }, + { + "context": { + "id": "node@@@[L69:C30, L69:C67]", + "snippet": "[dep: -> m...table.col]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L69:C31, L69:C66]", + "snippet": "dep: -> my..._table.col" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L69:C34, L69:C35]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L69:C31, L69:C34]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L69:C31, L69:C34]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1452, + "fullStart": 1449 + }, + "value": { + "context": { + "id": "node@@@[L69:C36, L69:C66]", + "snippet": "-> my_sche..._table.col" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L69:C39, L69:C66]", + "snippet": "my_schema...._table.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L69:C39, L69:C62]", + "snippet": "my_schema....nown_table" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L69:C39, L69:C48]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L69:C39, L69:C48]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L69:C39, L69:C48]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1466, + "fullStart": 1457 + } + }, + "fullEnd": 1466, + "fullStart": 1457 + }, + "op": { + "context": { + "id": "token@@.@[L69:C48, L69:C49]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_table@[L69:C49, L69:C62]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L69:C49, L69:C62]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L69:C49, L69:C62]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_table" + } + }, + "fullEnd": 1480, + "fullStart": 1467 + } + }, + "fullEnd": 1480, + "fullStart": 1467 + } + }, + "fullEnd": 1480, + "fullStart": 1457 + }, + "op": { + "context": { + "id": "token@@.@[L69:C62, L69:C63]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@col@[L69:C63, L69:C66]", + "snippet": "col" + }, + "children": { + "expression": { + "context": { + "id": "node@@col@[L69:C63, L69:C66]", + "snippet": "col" + }, + "children": { + "variable": { + "context": { + "id": "token@@col@[L69:C63, L69:C66]", + "snippet": "col" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "col" + } + }, + "fullEnd": 1484, + "fullStart": 1481 + } + }, + "fullEnd": 1484, + "fullStart": 1481 + } + }, + "fullEnd": 1484, + "fullStart": 1457 + }, + "op": { + "context": { + "id": "token@@->@[L69:C36, L69:C38]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 1484, + "fullStart": 1454 + } + }, + "fullEnd": 1484, + "fullStart": 1449 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L69:C66, L69:C67]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L69:C30, L69:C31]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1486, + "fullStart": 1448 + } + ], + "callee": { + "context": { + "id": "node@@id@[L69:C23, L69:C25]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L69:C23, L69:C25]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L69:C23, L69:C25]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1444, + "fullStart": 1441 + } + }, + "fullEnd": 1444, + "fullStart": 1441 + } + }, + "fullEnd": 1486, + "fullStart": 1441 + } + ] + }, + "fullEnd": 1488, + "fullStart": 1439 + }, + "name": { + "context": { + "id": "node@@bad_col_schema@[L69:C6, L69:C20]", + "snippet": "bad_col_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_col_schema@[L69:C6, L69:C20]", + "snippet": "bad_col_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_col_schema@[L69:C6, L69:C20]", + "snippet": "bad_col_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_col_schema" + } + }, + "fullEnd": 1439, + "fullStart": 1424 + } + }, + "fullEnd": 1439, + "fullStart": 1424 + }, + "type": { + "context": { + "id": "token@@Table@[L69:C0, L69:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1488, + "fullStart": 1418 + } + ], + "eof": { + "context": { + "id": "token@@@[L70:C0, L70:C0]", + "snippet": "" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "" + } + }, + "fullEnd": 1488, + "fullStart": 0 + } +} \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/validator/input/dep.in.dbml b/packages/dbml-parse/__tests__/snapshots/validator/input/dep.in.dbml new file mode 100644 index 000000000..72c43b701 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/validator/input/dep.in.dbml @@ -0,0 +1,93 @@ +Table users { + id int +} +Table orders { + user_id int +} +Table my_schema.events { + id int + ts int +} + +Table another_schema.booking { + id int + ts int +} + + +// Wrong operator: `>` instead of `->` (INVALID_DEP_FIELD) +Dep: users > orders + +// Wrong operator inside long block (INVALID_DEP_FIELD on the field) +Dep { + users < orders +} + +// Dep nested inside a Table - INVALID_DEP_CONTEXT +Table nested { + id int + Dep: users -> orders +} + +// short-form +// no error +Dep: users -> orders +Dep: orders.user_id <- users.id +Dep: my_schema.events -> users +Dep: my_schema.events.id -> users.id +Dep: my_schema.events.id <- another_schema.booking.id + +// full-form +// no error +Dep { + users -> orders + my_schema.events -> users + users.id -> orders.user_id + my_schema.events.id -> users.id + + note: 'ok' + materialized: view + owner: 'team-data' + priority: 1 +} + +// inline on table header +// no error +Table good_header_bare [dep: <- users] { id int } +Table good_header_schema [dep: <- my_schema.events] { id int } + +// inline on table header with invalid dep value (INVALID_TABLE_SETTING_VALUE) +Table bad_header_op [dep: > users] { id int } +Table bad_header_literal [dep: 'not_a_dep'] { id int } + +// inline on column +// no error +Table good_col_bare { id int [dep: -> users.id] } +Table good_col_schema { id int [dep: -> my_schema.events.id] } + +// short-form +// no error +Dep: users -> unknown_table +Dep: users.unknown_col -> users.id +Dep: unknown_schema.events -> users +Dep: my_schema.unknown_table -> users +Dep: my_schema.events.unknown_col -> users.id + +// full-form +// no error +Dep { + users -> unknown_a + my_schema.events -> unknown_b + users.id -> unknown_c.col + my_schema.events.id -> users.unknown_d +} + +// inline on table header +// no error +Table bad_header_bare [dep: <- unknown_source] { id int } +Table bad_header_schema [dep: <- unknown_schema.events] { id int } + +// inline on column +// no error +Table bad_col_bare { id int [dep: -> unknown_table.col] } +Table bad_col_schema { id int [dep: -> my_schema.unknown_table.col] } diff --git a/packages/dbml-parse/__tests__/snapshots/validator/output/dep.out.json b/packages/dbml-parse/__tests__/snapshots/validator/output/dep.out.json new file mode 100644 index 000000000..6f7320b78 --- /dev/null +++ b/packages/dbml-parse/__tests__/snapshots/validator/output/dep.out.json @@ -0,0 +1,8056 @@ +{ + "errors": [ + { + "code": "INVALID_DEP_FIELD", + "diagnostic": "Dep edges must use the '->' or '<-' operator", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@@[L18:C5, L18:C19]", + "snippet": "users > orders" + } + } + }, + { + "code": "INVALID_DEP_FIELD", + "diagnostic": "Dep edges must use the '->' or '<-' operator", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@@[L22:C2, L22:C16]", + "snippet": "users < orders" + } + } + }, + { + "code": "INVALID_DEP_CONTEXT", + "diagnostic": "A Dep must appear top-level", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@@[L28:C2, L28:C22]", + "snippet": "Dep: users -> orders" + } + } + }, + { + "code": "INVALID_TABLE_SETTING_VALUE", + "diagnostic": "'dep' must be `-> target` or `<- source`", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@@[L59:C26, L59:C33]", + "snippet": "> users" + } + } + }, + { + "code": "INVALID_TABLE_SETTING_VALUE", + "diagnostic": "'dep' must be `-> target` or `<- source`", + "filepath": "/main.dbml", + "level": "error", + "node": { + "context": { + "id": "node@@not_a_dep@[L60:C31, L60:C42]", + "snippet": "'not_a_dep'" + } + } + } + ], + "program": { + "context": { + "id": "node@@@[L0:C0, L93:C0]", + "snippet": "Table user...le.col] }\n" + }, + "children": { + "body": [ + { + "context": { + "id": "node@@users@[L0:C0, L2:C1]", + "snippet": "Table user... id int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L0:C12, L2:C1]", + "snippet": "{\n id int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L2:C0, L2:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L0:C12, L0:C13]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L1:C2, L1:C8]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L1:C5, L1:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L1:C5, L1:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L1:C5, L1:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 23, + "fullStart": 19 + } + }, + "fullEnd": 23, + "fullStart": 19 + } + ], + "callee": { + "context": { + "id": "node@@id@[L1:C2, L1:C4]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L1:C2, L1:C4]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L1:C2, L1:C4]", + "snippet": "id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 19, + "fullStart": 14 + } + }, + "fullEnd": 19, + "fullStart": 14 + } + }, + "fullEnd": 23, + "fullStart": 14 + } + ] + }, + "fullEnd": 25, + "fullStart": 12 + }, + "name": { + "context": { + "id": "node@@users@[L0:C6, L0:C11]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L0:C6, L0:C11]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L0:C6, L0:C11]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 12, + "fullStart": 6 + } + }, + "fullEnd": 12, + "fullStart": 6 + }, + "type": { + "context": { + "id": "token@@Table@[L0:C0, L0:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 25, + "fullStart": 0 + }, + { + "context": { + "id": "node@@orders@[L3:C0, L5:C1]", + "snippet": "Table orde...r_id int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L3:C13, L5:C1]", + "snippet": "{\n user_id int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L5:C0, L5:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L3:C13, L3:C14]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@user_id@[L4:C2, L4:C13]", + "snippet": "user_id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L4:C10, L4:C13]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L4:C10, L4:C13]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L4:C10, L4:C13]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 54, + "fullStart": 50 + } + }, + "fullEnd": 54, + "fullStart": 50 + } + ], + "callee": { + "context": { + "id": "node@@user_id@[L4:C2, L4:C9]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L4:C2, L4:C9]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L4:C2, L4:C9]", + "snippet": "user_id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "user_id" + } + }, + "fullEnd": 50, + "fullStart": 40 + } + }, + "fullEnd": 50, + "fullStart": 40 + } + }, + "fullEnd": 54, + "fullStart": 40 + } + ] + }, + "fullEnd": 56, + "fullStart": 38 + }, + "name": { + "context": { + "id": "node@@orders@[L3:C6, L3:C12]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L3:C6, L3:C12]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L3:C6, L3:C12]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "orders" + } + }, + "fullEnd": 38, + "fullStart": 31 + } + }, + "fullEnd": 38, + "fullStart": 31 + }, + "type": { + "context": { + "id": "token@@Table@[L3:C0, L3:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 56, + "fullStart": 25 + }, + { + "context": { + "id": "node@@my_schema.events@[L6:C0, L9:C1]", + "snippet": "Table my_s... ts int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L6:C23, L9:C1]", + "snippet": "{\n id int... ts int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L9:C0, L9:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L6:C23, L6:C24]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L7:C2, L7:C8]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L7:C5, L7:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L7:C5, L7:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L7:C5, L7:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 90, + "fullStart": 86 + } + }, + "fullEnd": 90, + "fullStart": 86 + } + ], + "callee": { + "context": { + "id": "node@@id@[L7:C2, L7:C4]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L7:C2, L7:C4]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L7:C2, L7:C4]", + "snippet": "id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 86, + "fullStart": 81 + } + }, + "fullEnd": 86, + "fullStart": 81 + } + }, + "fullEnd": 90, + "fullStart": 81 + }, + { + "context": { + "id": "node@@ts@[L8:C2, L8:C8]", + "snippet": "ts int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L8:C5, L8:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L8:C5, L8:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L8:C5, L8:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 99, + "fullStart": 95 + } + }, + "fullEnd": 99, + "fullStart": 95 + } + ], + "callee": { + "context": { + "id": "node@@ts@[L8:C2, L8:C4]", + "snippet": "ts" + }, + "children": { + "expression": { + "context": { + "id": "node@@ts@[L8:C2, L8:C4]", + "snippet": "ts" + }, + "children": { + "variable": { + "context": { + "id": "token@@ts@[L8:C2, L8:C4]", + "snippet": "ts" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "ts" + } + }, + "fullEnd": 95, + "fullStart": 90 + } + }, + "fullEnd": 95, + "fullStart": 90 + } + }, + "fullEnd": 99, + "fullStart": 90 + } + ] + }, + "fullEnd": 101, + "fullStart": 79 + }, + "name": { + "context": { + "id": "node@@@[L6:C6, L6:C22]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L6:C6, L6:C15]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L6:C6, L6:C15]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L6:C6, L6:C15]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 71, + "fullStart": 62 + } + }, + "fullEnd": 71, + "fullStart": 62 + }, + "op": { + "context": { + "id": "token@@.@[L6:C15, L6:C16]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L6:C16, L6:C22]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L6:C16, L6:C22]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L6:C16, L6:C22]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 79, + "fullStart": 72 + } + }, + "fullEnd": 79, + "fullStart": 72 + } + }, + "fullEnd": 79, + "fullStart": 62 + }, + "type": { + "context": { + "id": "token@@Table@[L6:C0, L6:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 101, + "fullStart": 56 + }, + { + "context": { + "id": "node@@another_schema.booking@[L11:C0, L14:C1]", + "snippet": "Table anot... ts int\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L11:C29, L14:C1]", + "snippet": "{\n id int... ts int\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L14:C0, L14:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L11:C29, L11:C30]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L12:C2, L12:C8]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L12:C5, L12:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L12:C5, L12:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L12:C5, L12:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 142, + "fullStart": 138 + } + }, + "fullEnd": 142, + "fullStart": 138 + } + ], + "callee": { + "context": { + "id": "node@@id@[L12:C2, L12:C4]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L12:C2, L12:C4]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L12:C2, L12:C4]", + "snippet": "id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 138, + "fullStart": 133 + } + }, + "fullEnd": 138, + "fullStart": 133 + } + }, + "fullEnd": 142, + "fullStart": 133 + }, + { + "context": { + "id": "node@@ts@[L13:C2, L13:C8]", + "snippet": "ts int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L13:C5, L13:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L13:C5, L13:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L13:C5, L13:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 151, + "fullStart": 147 + } + }, + "fullEnd": 151, + "fullStart": 147 + } + ], + "callee": { + "context": { + "id": "node@@ts@[L13:C2, L13:C4]", + "snippet": "ts" + }, + "children": { + "expression": { + "context": { + "id": "node@@ts@[L13:C2, L13:C4]", + "snippet": "ts" + }, + "children": { + "variable": { + "context": { + "id": "token@@ts@[L13:C2, L13:C4]", + "snippet": "ts" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "ts" + } + }, + "fullEnd": 147, + "fullStart": 142 + } + }, + "fullEnd": 147, + "fullStart": 142 + } + }, + "fullEnd": 151, + "fullStart": 142 + } + ] + }, + "fullEnd": 153, + "fullStart": 131 + }, + "name": { + "context": { + "id": "node@@@[L11:C6, L11:C28]", + "snippet": "another_sc...ma.booking" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@another_schema@[L11:C6, L11:C20]", + "snippet": "another_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@another_schema@[L11:C6, L11:C20]", + "snippet": "another_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@another_schema@[L11:C6, L11:C20]", + "snippet": "another_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "another_schema" + } + }, + "fullEnd": 122, + "fullStart": 108 + } + }, + "fullEnd": 122, + "fullStart": 108 + }, + "op": { + "context": { + "id": "token@@.@[L11:C20, L11:C21]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@booking@[L11:C21, L11:C28]", + "snippet": "booking" + }, + "children": { + "expression": { + "context": { + "id": "node@@booking@[L11:C21, L11:C28]", + "snippet": "booking" + }, + "children": { + "variable": { + "context": { + "id": "token@@booking@[L11:C21, L11:C28]", + "snippet": "booking" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "booking" + } + }, + "fullEnd": 131, + "fullStart": 123 + } + }, + "fullEnd": 131, + "fullStart": 123 + } + }, + "fullEnd": 131, + "fullStart": 108 + }, + "type": { + "context": { + "id": "token@@Table@[L11:C0, L11:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 153, + "fullStart": 101 + }, + { + "context": { + "id": "node@@@[L18:C0, L18:C19]", + "snippet": "Dep: users > orders" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L18:C5, L18:C19]", + "snippet": "users > orders" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L18:C5, L18:C19]", + "snippet": "users > orders" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L18:C5, L18:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L18:C5, L18:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L18:C5, L18:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 225, + "fullStart": 219 + } + }, + "fullEnd": 225, + "fullStart": 219 + }, + "op": { + "context": { + "id": "token@@>@[L18:C11, L18:C12]", + "snippet": ">" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ">" + }, + "rightExpression": { + "context": { + "id": "node@@orders@[L18:C13, L18:C19]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L18:C13, L18:C19]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L18:C13, L18:C19]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 234, + "fullStart": 227 + } + }, + "fullEnd": 234, + "fullStart": 227 + } + }, + "fullEnd": 234, + "fullStart": 219 + } + }, + "fullEnd": 234, + "fullStart": 219 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L18:C3, L18:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L18:C0, L18:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n\n Wrong operator: `>` instead of `->` (INVALID_DEP_FIELD)\n", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 234, + "fullStart": 153 + }, + { + "context": { + "id": "node@@@[L21:C0, L23:C1]", + "snippet": "Dep {\n us...< orders\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L21:C4, L23:C1]", + "snippet": "{\n users < orders\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L23:C0, L23:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L21:C4, L21:C5]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@@[L22:C2, L22:C16]", + "snippet": "users < orders" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L22:C2, L22:C16]", + "snippet": "users < orders" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L22:C2, L22:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L22:C2, L22:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L22:C2, L22:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 318, + "fullStart": 310 + } + }, + "fullEnd": 318, + "fullStart": 310 + }, + "op": { + "context": { + "id": "token@@<@[L22:C8, L22:C9]", + "snippet": "<" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<" + }, + "rightExpression": { + "context": { + "id": "node@@orders@[L22:C10, L22:C16]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L22:C10, L22:C16]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L22:C10, L22:C16]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 327, + "fullStart": 320 + } + }, + "fullEnd": 327, + "fullStart": 320 + } + }, + "fullEnd": 327, + "fullStart": 310 + } + }, + "fullEnd": 327, + "fullStart": 310 + } + ] + }, + "fullEnd": 329, + "fullStart": 308 + }, + "type": { + "context": { + "id": "token@@Dep@[L21:C0, L21:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n Wrong operator inside long block (INVALID_DEP_FIELD on the field)\n", + "trailingTrivia": " ", + "value": "Dep" + } + }, + "fullEnd": 329, + "fullStart": 234 + }, + { + "context": { + "id": "node@@nested@[L26:C0, L29:C1]", + "snippet": "Table nest...> orders\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L26:C13, L29:C1]", + "snippet": "{\n id int...> orders\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L29:C0, L29:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L26:C13, L26:C14]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L27:C2, L27:C8]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L27:C5, L27:C8]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L27:C5, L27:C8]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L27:C5, L27:C8]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "int" + } + }, + "fullEnd": 405, + "fullStart": 401 + } + }, + "fullEnd": 405, + "fullStart": 401 + } + ], + "callee": { + "context": { + "id": "node@@id@[L27:C2, L27:C4]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L27:C2, L27:C4]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L27:C2, L27:C4]", + "snippet": "id" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 401, + "fullStart": 396 + } + }, + "fullEnd": 401, + "fullStart": 396 + } + }, + "fullEnd": 405, + "fullStart": 396 + }, + { + "context": { + "id": "node@@@[L28:C2, L28:C22]", + "snippet": "Dep: users -> orders" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L28:C7, L28:C22]", + "snippet": "users -> orders" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L28:C7, L28:C22]", + "snippet": "users -> orders" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L28:C7, L28:C12]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L28:C7, L28:C12]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L28:C7, L28:C12]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 418, + "fullStart": 412 + } + }, + "fullEnd": 418, + "fullStart": 412 + }, + "op": { + "context": { + "id": "token@@->@[L28:C13, L28:C15]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@orders@[L28:C16, L28:C22]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L28:C16, L28:C22]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L28:C16, L28:C22]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 428, + "fullStart": 421 + } + }, + "fullEnd": 428, + "fullStart": 421 + } + }, + "fullEnd": 428, + "fullStart": 412 + } + }, + "fullEnd": 428, + "fullStart": 412 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L28:C5, L28:C6]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L28:C2, L28:C5]", + "snippet": "Dep" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 428, + "fullStart": 405 + } + ] + }, + "fullEnd": 430, + "fullStart": 394 + }, + "name": { + "context": { + "id": "node@@nested@[L26:C6, L26:C12]", + "snippet": "nested" + }, + "children": { + "expression": { + "context": { + "id": "node@@nested@[L26:C6, L26:C12]", + "snippet": "nested" + }, + "children": { + "variable": { + "context": { + "id": "token@@nested@[L26:C6, L26:C12]", + "snippet": "nested" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "nested" + } + }, + "fullEnd": 394, + "fullStart": 387 + } + }, + "fullEnd": 394, + "fullStart": 387 + }, + "type": { + "context": { + "id": "token@@Table@[L26:C0, L26:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n Dep nested inside a Table - INVALID_DEP_CONTEXT\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 430, + "fullStart": 329 + }, + { + "context": { + "id": "node@@@[L33:C0, L33:C20]", + "snippet": "Dep: users -> orders" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L33:C5, L33:C20]", + "snippet": "users -> orders" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L33:C5, L33:C20]", + "snippet": "users -> orders" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L33:C5, L33:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L33:C5, L33:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L33:C5, L33:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 468, + "fullStart": 462 + } + }, + "fullEnd": 468, + "fullStart": 462 + }, + "op": { + "context": { + "id": "token@@->@[L33:C11, L33:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@orders@[L33:C14, L33:C20]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L33:C14, L33:C20]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L33:C14, L33:C20]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 478, + "fullStart": 471 + } + }, + "fullEnd": 478, + "fullStart": 471 + } + }, + "fullEnd": 478, + "fullStart": 462 + } + }, + "fullEnd": 478, + "fullStart": 462 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L33:C3, L33:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L33:C0, L33:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n short-form\n no error\n", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 478, + "fullStart": 430 + }, + { + "context": { + "id": "node@@@[L34:C0, L34:C31]", + "snippet": "Dep: order...- users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L34:C5, L34:C31]", + "snippet": "orders.use...- users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L34:C5, L34:C31]", + "snippet": "orders.use...- users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L34:C5, L34:C19]", + "snippet": "orders.user_id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@orders@[L34:C5, L34:C11]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L34:C5, L34:C11]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L34:C5, L34:C11]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "orders" + } + }, + "fullEnd": 489, + "fullStart": 483 + } + }, + "fullEnd": 489, + "fullStart": 483 + }, + "op": { + "context": { + "id": "token@@.@[L34:C11, L34:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@user_id@[L34:C12, L34:C19]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L34:C12, L34:C19]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L34:C12, L34:C19]", + "snippet": "user_id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "user_id" + } + }, + "fullEnd": 498, + "fullStart": 490 + } + }, + "fullEnd": 498, + "fullStart": 490 + } + }, + "fullEnd": 498, + "fullStart": 483 + }, + "op": { + "context": { + "id": "token@@<-@[L34:C20, L34:C22]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + }, + "rightExpression": { + "context": { + "id": "node@@@[L34:C23, L34:C31]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L34:C23, L34:C28]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L34:C23, L34:C28]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L34:C23, L34:C28]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 506, + "fullStart": 501 + } + }, + "fullEnd": 506, + "fullStart": 501 + }, + "op": { + "context": { + "id": "token@@.@[L34:C28, L34:C29]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L34:C29, L34:C31]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L34:C29, L34:C31]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L34:C29, L34:C31]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 510, + "fullStart": 507 + } + }, + "fullEnd": 510, + "fullStart": 507 + } + }, + "fullEnd": 510, + "fullStart": 501 + } + }, + "fullEnd": 510, + "fullStart": 483 + } + }, + "fullEnd": 510, + "fullStart": 483 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L34:C3, L34:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L34:C0, L34:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 510, + "fullStart": 478 + }, + { + "context": { + "id": "node@@@[L35:C0, L35:C30]", + "snippet": "Dep: my_sc...s -> users" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L35:C5, L35:C30]", + "snippet": "my_schema....s -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L35:C5, L35:C30]", + "snippet": "my_schema....s -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L35:C5, L35:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L35:C5, L35:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L35:C5, L35:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L35:C5, L35:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 524, + "fullStart": 515 + } + }, + "fullEnd": 524, + "fullStart": 515 + }, + "op": { + "context": { + "id": "token@@.@[L35:C14, L35:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L35:C15, L35:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L35:C15, L35:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L35:C15, L35:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 532, + "fullStart": 525 + } + }, + "fullEnd": 532, + "fullStart": 525 + } + }, + "fullEnd": 532, + "fullStart": 515 + }, + "op": { + "context": { + "id": "token@@->@[L35:C22, L35:C24]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L35:C25, L35:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L35:C25, L35:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L35:C25, L35:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 541, + "fullStart": 535 + } + }, + "fullEnd": 541, + "fullStart": 535 + } + }, + "fullEnd": 541, + "fullStart": 515 + } + }, + "fullEnd": 541, + "fullStart": 515 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L35:C3, L35:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L35:C0, L35:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 541, + "fullStart": 510 + }, + { + "context": { + "id": "node@@@[L36:C0, L36:C36]", + "snippet": "Dep: my_sc...> users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L36:C5, L36:C36]", + "snippet": "my_schema....> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L36:C5, L36:C36]", + "snippet": "my_schema....> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L36:C5, L36:C24]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L36:C5, L36:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L36:C5, L36:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L36:C5, L36:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L36:C5, L36:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 555, + "fullStart": 546 + } + }, + "fullEnd": 555, + "fullStart": 546 + }, + "op": { + "context": { + "id": "token@@.@[L36:C14, L36:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L36:C15, L36:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L36:C15, L36:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L36:C15, L36:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 562, + "fullStart": 556 + } + }, + "fullEnd": 562, + "fullStart": 556 + } + }, + "fullEnd": 562, + "fullStart": 546 + }, + "op": { + "context": { + "id": "token@@.@[L36:C21, L36:C22]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L36:C22, L36:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L36:C22, L36:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L36:C22, L36:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 566, + "fullStart": 563 + } + }, + "fullEnd": 566, + "fullStart": 563 + } + }, + "fullEnd": 566, + "fullStart": 546 + }, + "op": { + "context": { + "id": "token@@->@[L36:C25, L36:C27]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L36:C28, L36:C36]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L36:C28, L36:C33]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L36:C28, L36:C33]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L36:C28, L36:C33]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 574, + "fullStart": 569 + } + }, + "fullEnd": 574, + "fullStart": 569 + }, + "op": { + "context": { + "id": "token@@.@[L36:C33, L36:C34]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L36:C34, L36:C36]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L36:C34, L36:C36]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L36:C34, L36:C36]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 578, + "fullStart": 575 + } + }, + "fullEnd": 578, + "fullStart": 575 + } + }, + "fullEnd": 578, + "fullStart": 569 + } + }, + "fullEnd": 578, + "fullStart": 546 + } + }, + "fullEnd": 578, + "fullStart": 546 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L36:C3, L36:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L36:C0, L36:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 578, + "fullStart": 541 + }, + { + "context": { + "id": "node@@@[L37:C0, L37:C53]", + "snippet": "Dep: my_sc...booking.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L37:C5, L37:C53]", + "snippet": "my_schema....booking.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L37:C5, L37:C53]", + "snippet": "my_schema....booking.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L37:C5, L37:C24]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L37:C5, L37:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L37:C5, L37:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L37:C5, L37:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L37:C5, L37:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 592, + "fullStart": 583 + } + }, + "fullEnd": 592, + "fullStart": 583 + }, + "op": { + "context": { + "id": "token@@.@[L37:C14, L37:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L37:C15, L37:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L37:C15, L37:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L37:C15, L37:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 599, + "fullStart": 593 + } + }, + "fullEnd": 599, + "fullStart": 593 + } + }, + "fullEnd": 599, + "fullStart": 583 + }, + "op": { + "context": { + "id": "token@@.@[L37:C21, L37:C22]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L37:C22, L37:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L37:C22, L37:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L37:C22, L37:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 603, + "fullStart": 600 + } + }, + "fullEnd": 603, + "fullStart": 600 + } + }, + "fullEnd": 603, + "fullStart": 583 + }, + "op": { + "context": { + "id": "token@@<-@[L37:C25, L37:C27]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + }, + "rightExpression": { + "context": { + "id": "node@@@[L37:C28, L37:C53]", + "snippet": "another_sc...booking.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L37:C28, L37:C50]", + "snippet": "another_sc...ma.booking" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@another_schema@[L37:C28, L37:C42]", + "snippet": "another_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@another_schema@[L37:C28, L37:C42]", + "snippet": "another_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@another_schema@[L37:C28, L37:C42]", + "snippet": "another_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "another_schema" + } + }, + "fullEnd": 620, + "fullStart": 606 + } + }, + "fullEnd": 620, + "fullStart": 606 + }, + "op": { + "context": { + "id": "token@@.@[L37:C42, L37:C43]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@booking@[L37:C43, L37:C50]", + "snippet": "booking" + }, + "children": { + "expression": { + "context": { + "id": "node@@booking@[L37:C43, L37:C50]", + "snippet": "booking" + }, + "children": { + "variable": { + "context": { + "id": "token@@booking@[L37:C43, L37:C50]", + "snippet": "booking" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "booking" + } + }, + "fullEnd": 628, + "fullStart": 621 + } + }, + "fullEnd": 628, + "fullStart": 621 + } + }, + "fullEnd": 628, + "fullStart": 606 + }, + "op": { + "context": { + "id": "token@@.@[L37:C50, L37:C51]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L37:C51, L37:C53]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L37:C51, L37:C53]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L37:C51, L37:C53]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 632, + "fullStart": 629 + } + }, + "fullEnd": 632, + "fullStart": 629 + } + }, + "fullEnd": 632, + "fullStart": 606 + } + }, + "fullEnd": 632, + "fullStart": 583 + } + }, + "fullEnd": 632, + "fullStart": 583 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L37:C3, L37:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L37:C0, L37:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 632, + "fullStart": 578 + }, + { + "context": { + "id": "node@@@[L41:C0, L51:C1]", + "snippet": "Dep {\n us...ority: 1\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L41:C4, L51:C1]", + "snippet": "{\n users ...ority: 1\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L51:C0, L51:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L41:C4, L41:C5]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@@[L42:C2, L42:C17]", + "snippet": "users -> orders" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L42:C2, L42:C17]", + "snippet": "users -> orders" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L42:C2, L42:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L42:C2, L42:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L42:C2, L42:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 672, + "fullStart": 664 + } + }, + "fullEnd": 672, + "fullStart": 664 + }, + "op": { + "context": { + "id": "token@@->@[L42:C8, L42:C10]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@orders@[L42:C11, L42:C17]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L42:C11, L42:C17]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L42:C11, L42:C17]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "orders" + } + }, + "fullEnd": 682, + "fullStart": 675 + } + }, + "fullEnd": 682, + "fullStart": 675 + } + }, + "fullEnd": 682, + "fullStart": 664 + } + }, + "fullEnd": 682, + "fullStart": 664 + }, + { + "context": { + "id": "node@@@[L43:C2, L43:C27]", + "snippet": "my_schema....s -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L43:C2, L43:C27]", + "snippet": "my_schema....s -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L43:C2, L43:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L43:C2, L43:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L43:C2, L43:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L43:C2, L43:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 693, + "fullStart": 682 + } + }, + "fullEnd": 693, + "fullStart": 682 + }, + "op": { + "context": { + "id": "token@@.@[L43:C11, L43:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L43:C12, L43:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L43:C12, L43:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L43:C12, L43:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 701, + "fullStart": 694 + } + }, + "fullEnd": 701, + "fullStart": 694 + } + }, + "fullEnd": 701, + "fullStart": 682 + }, + "op": { + "context": { + "id": "token@@->@[L43:C19, L43:C21]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L43:C22, L43:C27]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L43:C22, L43:C27]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L43:C22, L43:C27]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 710, + "fullStart": 704 + } + }, + "fullEnd": 710, + "fullStart": 704 + } + }, + "fullEnd": 710, + "fullStart": 682 + } + }, + "fullEnd": 710, + "fullStart": 682 + }, + { + "context": { + "id": "node@@@[L44:C2, L44:C28]", + "snippet": "users.id -...rs.user_id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L44:C2, L44:C28]", + "snippet": "users.id -...rs.user_id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L44:C2, L44:C10]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L44:C2, L44:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L44:C2, L44:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L44:C2, L44:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 717, + "fullStart": 710 + } + }, + "fullEnd": 717, + "fullStart": 710 + }, + "op": { + "context": { + "id": "token@@.@[L44:C7, L44:C8]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L44:C8, L44:C10]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L44:C8, L44:C10]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L44:C8, L44:C10]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 721, + "fullStart": 718 + } + }, + "fullEnd": 721, + "fullStart": 718 + } + }, + "fullEnd": 721, + "fullStart": 710 + }, + "op": { + "context": { + "id": "token@@->@[L44:C11, L44:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L44:C14, L44:C28]", + "snippet": "orders.user_id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@orders@[L44:C14, L44:C20]", + "snippet": "orders" + }, + "children": { + "expression": { + "context": { + "id": "node@@orders@[L44:C14, L44:C20]", + "snippet": "orders" + }, + "children": { + "variable": { + "context": { + "id": "token@@orders@[L44:C14, L44:C20]", + "snippet": "orders" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "orders" + } + }, + "fullEnd": 730, + "fullStart": 724 + } + }, + "fullEnd": 730, + "fullStart": 724 + }, + "op": { + "context": { + "id": "token@@.@[L44:C20, L44:C21]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@user_id@[L44:C21, L44:C28]", + "snippet": "user_id" + }, + "children": { + "expression": { + "context": { + "id": "node@@user_id@[L44:C21, L44:C28]", + "snippet": "user_id" + }, + "children": { + "variable": { + "context": { + "id": "token@@user_id@[L44:C21, L44:C28]", + "snippet": "user_id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "user_id" + } + }, + "fullEnd": 739, + "fullStart": 731 + } + }, + "fullEnd": 739, + "fullStart": 731 + } + }, + "fullEnd": 739, + "fullStart": 724 + } + }, + "fullEnd": 739, + "fullStart": 710 + } + }, + "fullEnd": 739, + "fullStart": 710 + }, + { + "context": { + "id": "node@@@[L45:C2, L45:C33]", + "snippet": "my_schema....> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L45:C2, L45:C33]", + "snippet": "my_schema....> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L45:C2, L45:C21]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L45:C2, L45:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L45:C2, L45:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L45:C2, L45:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L45:C2, L45:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 750, + "fullStart": 739 + } + }, + "fullEnd": 750, + "fullStart": 739 + }, + "op": { + "context": { + "id": "token@@.@[L45:C11, L45:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L45:C12, L45:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L45:C12, L45:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L45:C12, L45:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 757, + "fullStart": 751 + } + }, + "fullEnd": 757, + "fullStart": 751 + } + }, + "fullEnd": 757, + "fullStart": 739 + }, + "op": { + "context": { + "id": "token@@.@[L45:C18, L45:C19]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L45:C19, L45:C21]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L45:C19, L45:C21]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L45:C19, L45:C21]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 761, + "fullStart": 758 + } + }, + "fullEnd": 761, + "fullStart": 758 + } + }, + "fullEnd": 761, + "fullStart": 739 + }, + "op": { + "context": { + "id": "token@@->@[L45:C22, L45:C24]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L45:C25, L45:C33]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L45:C25, L45:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L45:C25, L45:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L45:C25, L45:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 769, + "fullStart": 764 + } + }, + "fullEnd": 769, + "fullStart": 764 + }, + "op": { + "context": { + "id": "token@@.@[L45:C30, L45:C31]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L45:C31, L45:C33]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L45:C31, L45:C33]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L45:C31, L45:C33]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 773, + "fullStart": 770 + } + }, + "fullEnd": 773, + "fullStart": 770 + } + }, + "fullEnd": 773, + "fullStart": 764 + } + }, + "fullEnd": 773, + "fullStart": 739 + } + }, + "fullEnd": 773, + "fullStart": 739 + }, + { + "context": { + "id": "node@@@[L47:C2, L47:C12]", + "snippet": "note: 'ok'" + }, + "children": { + "body": { + "context": { + "id": "node@@ok@[L47:C8, L47:C12]", + "snippet": "'ok'" + }, + "children": { + "callee": { + "context": { + "id": "node@@ok@[L47:C8, L47:C12]", + "snippet": "'ok'" + }, + "children": { + "expression": { + "context": { + "id": "node@@ok@[L47:C8, L47:C12]", + "snippet": "'ok'" + }, + "children": { + "literal": { + "context": { + "id": "token@@ok@[L47:C8, L47:C12]", + "snippet": "'ok'" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "ok" + } + }, + "fullEnd": 787, + "fullStart": 782 + } + }, + "fullEnd": 787, + "fullStart": 782 + } + }, + "fullEnd": 787, + "fullStart": 782 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L47:C6, L47:C7]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@note@[L47:C2, L47:C6]", + "snippet": "note" + }, + "leadingTrivia": "\n ", + "trailingTrivia": "", + "value": "note" + } + }, + "fullEnd": 787, + "fullStart": 773 + }, + { + "context": { + "id": "node@@@[L48:C2, L48:C20]", + "snippet": "materialized: view" + }, + "children": { + "body": { + "context": { + "id": "node@@view@[L48:C16, L48:C20]", + "snippet": "view" + }, + "children": { + "callee": { + "context": { + "id": "node@@view@[L48:C16, L48:C20]", + "snippet": "view" + }, + "children": { + "expression": { + "context": { + "id": "node@@view@[L48:C16, L48:C20]", + "snippet": "view" + }, + "children": { + "variable": { + "context": { + "id": "token@@view@[L48:C16, L48:C20]", + "snippet": "view" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "view" + } + }, + "fullEnd": 808, + "fullStart": 803 + } + }, + "fullEnd": 808, + "fullStart": 803 + } + }, + "fullEnd": 808, + "fullStart": 803 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L48:C14, L48:C15]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@materialized@[L48:C2, L48:C14]", + "snippet": "materialized" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "materialized" + } + }, + "fullEnd": 808, + "fullStart": 787 + }, + { + "context": { + "id": "node@@@[L49:C2, L49:C20]", + "snippet": "owner: 'team-data'" + }, + "children": { + "body": { + "context": { + "id": "node@@team-data@[L49:C9, L49:C20]", + "snippet": "'team-data'" + }, + "children": { + "callee": { + "context": { + "id": "node@@team-data@[L49:C9, L49:C20]", + "snippet": "'team-data'" + }, + "children": { + "expression": { + "context": { + "id": "node@@team-data@[L49:C9, L49:C20]", + "snippet": "'team-data'" + }, + "children": { + "literal": { + "context": { + "id": "token@@team-data@[L49:C9, L49:C20]", + "snippet": "'team-data'" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "team-data" + } + }, + "fullEnd": 829, + "fullStart": 817 + } + }, + "fullEnd": 829, + "fullStart": 817 + } + }, + "fullEnd": 829, + "fullStart": 817 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L49:C7, L49:C8]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@owner@[L49:C2, L49:C7]", + "snippet": "owner" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "owner" + } + }, + "fullEnd": 829, + "fullStart": 808 + }, + { + "context": { + "id": "node@@@[L50:C2, L50:C13]", + "snippet": "priority: 1" + }, + "children": { + "body": { + "context": { + "id": "node@@1@[L50:C12, L50:C13]", + "snippet": "1" + }, + "children": { + "callee": { + "context": { + "id": "node@@1@[L50:C12, L50:C13]", + "snippet": "1" + }, + "children": { + "expression": { + "context": { + "id": "node@@1@[L50:C12, L50:C13]", + "snippet": "1" + }, + "children": { + "literal": { + "context": { + "id": "token@@1@[L50:C12, L50:C13]", + "snippet": "1" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "1" + } + }, + "fullEnd": 843, + "fullStart": 841 + } + }, + "fullEnd": 843, + "fullStart": 841 + } + }, + "fullEnd": 843, + "fullStart": 841 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L50:C10, L50:C11]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@priority@[L50:C2, L50:C10]", + "snippet": "priority" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "priority" + } + }, + "fullEnd": 843, + "fullStart": 829 + } + ] + }, + "fullEnd": 845, + "fullStart": 662 + }, + "type": { + "context": { + "id": "token@@Dep@[L41:C0, L41:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n full-form\n no error\n", + "trailingTrivia": " ", + "value": "Dep" + } + }, + "fullEnd": 845, + "fullStart": 632 + }, + { + "context": { + "id": "node@@good_header_bare@[L55:C0, L55:C49]", + "snippet": "Table good...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L55:C23, L55:C38]", + "snippet": "[dep: <- users]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L55:C24, L55:C37]", + "snippet": "dep: <- users" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L55:C27, L55:C28]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L55:C24, L55:C27]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L55:C24, L55:C27]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 911, + "fullStart": 908 + }, + "value": { + "context": { + "id": "node@@@[L55:C29, L55:C37]", + "snippet": "<- users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L55:C32, L55:C37]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L55:C32, L55:C37]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L55:C32, L55:C37]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 921, + "fullStart": 916 + } + }, + "fullEnd": 921, + "fullStart": 916 + }, + "op": { + "context": { + "id": "token@@<-@[L55:C29, L55:C31]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 921, + "fullStart": 913 + } + }, + "fullEnd": 921, + "fullStart": 908 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L55:C37, L55:C38]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L55:C23, L55:C24]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 923, + "fullStart": 907 + }, + "body": { + "context": { + "id": "node@@@[L55:C39, L55:C49]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L55:C48, L55:C49]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L55:C39, L55:C40]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L55:C41, L55:C47]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L55:C44, L55:C47]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L55:C44, L55:C47]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L55:C44, L55:C47]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 932, + "fullStart": 928 + } + }, + "fullEnd": 932, + "fullStart": 928 + } + ], + "callee": { + "context": { + "id": "node@@id@[L55:C41, L55:C43]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L55:C41, L55:C43]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L55:C41, L55:C43]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 928, + "fullStart": 925 + } + }, + "fullEnd": 928, + "fullStart": 925 + } + }, + "fullEnd": 932, + "fullStart": 925 + } + ] + }, + "fullEnd": 934, + "fullStart": 923 + }, + "name": { + "context": { + "id": "node@@good_header_bare@[L55:C6, L55:C22]", + "snippet": "good_header_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_header_bare@[L55:C6, L55:C22]", + "snippet": "good_header_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_header_bare@[L55:C6, L55:C22]", + "snippet": "good_header_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_header_bare" + } + }, + "fullEnd": 907, + "fullStart": 890 + } + }, + "fullEnd": 907, + "fullStart": 890 + }, + "type": { + "context": { + "id": "token@@Table@[L55:C0, L55:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on table header\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 934, + "fullStart": 845 + }, + { + "context": { + "id": "node@@good_header_schema@[L56:C0, L56:C62]", + "snippet": "Table good...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L56:C25, L56:C51]", + "snippet": "[dep: <- m...ma.events]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L56:C26, L56:C50]", + "snippet": "dep: <- my...ema.events" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L56:C29, L56:C30]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L56:C26, L56:C29]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L56:C26, L56:C29]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 963, + "fullStart": 960 + }, + "value": { + "context": { + "id": "node@@@[L56:C31, L56:C50]", + "snippet": "<- my_schema.events" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L56:C34, L56:C50]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L56:C34, L56:C43]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L56:C34, L56:C43]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L56:C34, L56:C43]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 977, + "fullStart": 968 + } + }, + "fullEnd": 977, + "fullStart": 968 + }, + "op": { + "context": { + "id": "token@@.@[L56:C43, L56:C44]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L56:C44, L56:C50]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L56:C44, L56:C50]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L56:C44, L56:C50]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 984, + "fullStart": 978 + } + }, + "fullEnd": 984, + "fullStart": 978 + } + }, + "fullEnd": 984, + "fullStart": 968 + }, + "op": { + "context": { + "id": "token@@<-@[L56:C31, L56:C33]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 984, + "fullStart": 965 + } + }, + "fullEnd": 984, + "fullStart": 960 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L56:C50, L56:C51]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L56:C25, L56:C26]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 986, + "fullStart": 959 + }, + "body": { + "context": { + "id": "node@@@[L56:C52, L56:C62]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L56:C61, L56:C62]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L56:C52, L56:C53]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L56:C54, L56:C60]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L56:C57, L56:C60]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L56:C57, L56:C60]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L56:C57, L56:C60]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 995, + "fullStart": 991 + } + }, + "fullEnd": 995, + "fullStart": 991 + } + ], + "callee": { + "context": { + "id": "node@@id@[L56:C54, L56:C56]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L56:C54, L56:C56]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L56:C54, L56:C56]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 991, + "fullStart": 988 + } + }, + "fullEnd": 991, + "fullStart": 988 + } + }, + "fullEnd": 995, + "fullStart": 988 + } + ] + }, + "fullEnd": 997, + "fullStart": 986 + }, + "name": { + "context": { + "id": "node@@good_header_schema@[L56:C6, L56:C24]", + "snippet": "good_header_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_header_schema@[L56:C6, L56:C24]", + "snippet": "good_header_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_header_schema@[L56:C6, L56:C24]", + "snippet": "good_header_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_header_schema" + } + }, + "fullEnd": 959, + "fullStart": 940 + } + }, + "fullEnd": 959, + "fullStart": 940 + }, + "type": { + "context": { + "id": "token@@Table@[L56:C0, L56:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 997, + "fullStart": 934 + }, + { + "context": { + "id": "node@@bad_header_op@[L59:C0, L59:C45]", + "snippet": "Table bad_...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L59:C20, L59:C34]", + "snippet": "[dep: > users]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L59:C21, L59:C33]", + "snippet": "dep: > users" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L59:C24, L59:C25]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L59:C21, L59:C24]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L59:C21, L59:C24]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1101, + "fullStart": 1098 + }, + "value": { + "context": { + "id": "node@@@[L59:C26, L59:C33]", + "snippet": "> users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L59:C28, L59:C33]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L59:C28, L59:C33]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L59:C28, L59:C33]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1110, + "fullStart": 1105 + } + }, + "fullEnd": 1110, + "fullStart": 1105 + }, + "op": { + "context": { + "id": "token@@>@[L59:C26, L59:C27]", + "snippet": ">" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ">" + } + }, + "fullEnd": 1110, + "fullStart": 1103 + } + }, + "fullEnd": 1110, + "fullStart": 1098 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L59:C33, L59:C34]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L59:C20, L59:C21]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1112, + "fullStart": 1097 + }, + "body": { + "context": { + "id": "node@@@[L59:C35, L59:C45]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L59:C44, L59:C45]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L59:C35, L59:C36]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L59:C37, L59:C43]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L59:C40, L59:C43]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L59:C40, L59:C43]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L59:C40, L59:C43]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1121, + "fullStart": 1117 + } + }, + "fullEnd": 1121, + "fullStart": 1117 + } + ], + "callee": { + "context": { + "id": "node@@id@[L59:C37, L59:C39]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L59:C37, L59:C39]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L59:C37, L59:C39]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1117, + "fullStart": 1114 + } + }, + "fullEnd": 1117, + "fullStart": 1114 + } + }, + "fullEnd": 1121, + "fullStart": 1114 + } + ] + }, + "fullEnd": 1123, + "fullStart": 1112 + }, + "name": { + "context": { + "id": "node@@bad_header_op@[L59:C6, L59:C19]", + "snippet": "bad_header_op" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_header_op@[L59:C6, L59:C19]", + "snippet": "bad_header_op" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_header_op@[L59:C6, L59:C19]", + "snippet": "bad_header_op" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_header_op" + } + }, + "fullEnd": 1097, + "fullStart": 1083 + } + }, + "fullEnd": 1097, + "fullStart": 1083 + }, + "type": { + "context": { + "id": "token@@Table@[L59:C0, L59:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on table header with invalid dep value (INVALID_TABLE_SETTING_VALUE)\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1123, + "fullStart": 997 + }, + { + "context": { + "id": "node@@bad_header_literal@[L60:C0, L60:C54]", + "snippet": "Table bad_...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L60:C25, L60:C43]", + "snippet": "[dep: 'not_a_dep']" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L60:C26, L60:C42]", + "snippet": "dep: 'not_a_dep'" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L60:C29, L60:C30]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L60:C26, L60:C29]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L60:C26, L60:C29]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1152, + "fullStart": 1149 + }, + "value": { + "context": { + "id": "node@@not_a_dep@[L60:C31, L60:C42]", + "snippet": "'not_a_dep'" + }, + "children": { + "expression": { + "context": { + "id": "node@@not_a_dep@[L60:C31, L60:C42]", + "snippet": "'not_a_dep'" + }, + "children": { + "literal": { + "context": { + "id": "token@@not_a_dep@[L60:C31, L60:C42]", + "snippet": "'not_a_dep'" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "not_a_dep" + } + }, + "fullEnd": 1165, + "fullStart": 1154 + } + }, + "fullEnd": 1165, + "fullStart": 1154 + } + }, + "fullEnd": 1165, + "fullStart": 1149 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L60:C42, L60:C43]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L60:C25, L60:C26]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1167, + "fullStart": 1148 + }, + "body": { + "context": { + "id": "node@@@[L60:C44, L60:C54]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L60:C53, L60:C54]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L60:C44, L60:C45]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L60:C46, L60:C52]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L60:C49, L60:C52]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L60:C49, L60:C52]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L60:C49, L60:C52]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1176, + "fullStart": 1172 + } + }, + "fullEnd": 1176, + "fullStart": 1172 + } + ], + "callee": { + "context": { + "id": "node@@id@[L60:C46, L60:C48]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L60:C46, L60:C48]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L60:C46, L60:C48]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1172, + "fullStart": 1169 + } + }, + "fullEnd": 1172, + "fullStart": 1169 + } + }, + "fullEnd": 1176, + "fullStart": 1169 + } + ] + }, + "fullEnd": 1178, + "fullStart": 1167 + }, + "name": { + "context": { + "id": "node@@bad_header_literal@[L60:C6, L60:C24]", + "snippet": "bad_header_literal" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_header_literal@[L60:C6, L60:C24]", + "snippet": "bad_header_literal" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_header_literal@[L60:C6, L60:C24]", + "snippet": "bad_header_literal" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_header_literal" + } + }, + "fullEnd": 1148, + "fullStart": 1129 + } + }, + "fullEnd": 1148, + "fullStart": 1129 + }, + "type": { + "context": { + "id": "token@@Table@[L60:C0, L60:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1178, + "fullStart": 1123 + }, + { + "context": { + "id": "node@@good_col_bare@[L64:C0, L64:C49]", + "snippet": "Table good...sers.id] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L64:C20, L64:C49]", + "snippet": "{ id int [...sers.id] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L64:C48, L64:C49]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L64:C20, L64:C21]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L64:C22, L64:C47]", + "snippet": "id int [de... users.id]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L64:C25, L64:C28]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L64:C25, L64:C28]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L64:C25, L64:C28]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1240, + "fullStart": 1236 + } + }, + "fullEnd": 1240, + "fullStart": 1236 + }, + { + "context": { + "id": "node@@@[L64:C29, L64:C47]", + "snippet": "[dep: -> users.id]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L64:C30, L64:C46]", + "snippet": "dep: -> users.id" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L64:C33, L64:C34]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L64:C30, L64:C33]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L64:C30, L64:C33]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1244, + "fullStart": 1241 + }, + "value": { + "context": { + "id": "node@@@[L64:C35, L64:C46]", + "snippet": "-> users.id" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L64:C38, L64:C46]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L64:C38, L64:C43]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L64:C38, L64:C43]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L64:C38, L64:C43]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1254, + "fullStart": 1249 + } + }, + "fullEnd": 1254, + "fullStart": 1249 + }, + "op": { + "context": { + "id": "token@@.@[L64:C43, L64:C44]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L64:C44, L64:C46]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L64:C44, L64:C46]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L64:C44, L64:C46]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "id" + } + }, + "fullEnd": 1257, + "fullStart": 1255 + } + }, + "fullEnd": 1257, + "fullStart": 1255 + } + }, + "fullEnd": 1257, + "fullStart": 1249 + }, + "op": { + "context": { + "id": "token@@->@[L64:C35, L64:C37]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 1257, + "fullStart": 1246 + } + }, + "fullEnd": 1257, + "fullStart": 1241 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L64:C46, L64:C47]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L64:C29, L64:C30]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1259, + "fullStart": 1240 + } + ], + "callee": { + "context": { + "id": "node@@id@[L64:C22, L64:C24]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L64:C22, L64:C24]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L64:C22, L64:C24]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1236, + "fullStart": 1233 + } + }, + "fullEnd": 1236, + "fullStart": 1233 + } + }, + "fullEnd": 1259, + "fullStart": 1233 + } + ] + }, + "fullEnd": 1261, + "fullStart": 1231 + }, + "name": { + "context": { + "id": "node@@good_col_bare@[L64:C6, L64:C19]", + "snippet": "good_col_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_col_bare@[L64:C6, L64:C19]", + "snippet": "good_col_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_col_bare@[L64:C6, L64:C19]", + "snippet": "good_col_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_col_bare" + } + }, + "fullEnd": 1231, + "fullStart": 1217 + } + }, + "fullEnd": 1231, + "fullStart": 1217 + }, + "type": { + "context": { + "id": "token@@Table@[L64:C0, L64:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on column\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1261, + "fullStart": 1178 + }, + { + "context": { + "id": "node@@good_col_schema@[L65:C0, L65:C62]", + "snippet": "Table good...ents.id] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L65:C22, L65:C62]", + "snippet": "{ id int [...ents.id] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L65:C61, L65:C62]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L65:C22, L65:C23]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L65:C24, L65:C60]", + "snippet": "id int [de...events.id]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L65:C27, L65:C30]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L65:C27, L65:C30]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L65:C27, L65:C30]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1292, + "fullStart": 1288 + } + }, + "fullEnd": 1292, + "fullStart": 1288 + }, + { + "context": { + "id": "node@@@[L65:C31, L65:C60]", + "snippet": "[dep: -> m...events.id]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L65:C32, L65:C59]", + "snippet": "dep: -> my....events.id" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L65:C35, L65:C36]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L65:C32, L65:C35]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L65:C32, L65:C35]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1296, + "fullStart": 1293 + }, + "value": { + "context": { + "id": "node@@@[L65:C37, L65:C59]", + "snippet": "-> my_sche....events.id" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L65:C40, L65:C59]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L65:C40, L65:C56]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L65:C40, L65:C49]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L65:C40, L65:C49]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L65:C40, L65:C49]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1310, + "fullStart": 1301 + } + }, + "fullEnd": 1310, + "fullStart": 1301 + }, + "op": { + "context": { + "id": "token@@.@[L65:C49, L65:C50]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L65:C50, L65:C56]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L65:C50, L65:C56]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L65:C50, L65:C56]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 1317, + "fullStart": 1311 + } + }, + "fullEnd": 1317, + "fullStart": 1311 + } + }, + "fullEnd": 1317, + "fullStart": 1301 + }, + "op": { + "context": { + "id": "token@@.@[L65:C56, L65:C57]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L65:C57, L65:C59]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L65:C57, L65:C59]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L65:C57, L65:C59]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "id" + } + }, + "fullEnd": 1320, + "fullStart": 1318 + } + }, + "fullEnd": 1320, + "fullStart": 1318 + } + }, + "fullEnd": 1320, + "fullStart": 1301 + }, + "op": { + "context": { + "id": "token@@->@[L65:C37, L65:C39]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 1320, + "fullStart": 1298 + } + }, + "fullEnd": 1320, + "fullStart": 1293 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L65:C59, L65:C60]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L65:C31, L65:C32]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1322, + "fullStart": 1292 + } + ], + "callee": { + "context": { + "id": "node@@id@[L65:C24, L65:C26]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L65:C24, L65:C26]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L65:C24, L65:C26]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1288, + "fullStart": 1285 + } + }, + "fullEnd": 1288, + "fullStart": 1285 + } + }, + "fullEnd": 1322, + "fullStart": 1285 + } + ] + }, + "fullEnd": 1324, + "fullStart": 1283 + }, + "name": { + "context": { + "id": "node@@good_col_schema@[L65:C6, L65:C21]", + "snippet": "good_col_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@good_col_schema@[L65:C6, L65:C21]", + "snippet": "good_col_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@good_col_schema@[L65:C6, L65:C21]", + "snippet": "good_col_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "good_col_schema" + } + }, + "fullEnd": 1283, + "fullStart": 1267 + } + }, + "fullEnd": 1283, + "fullStart": 1267 + }, + "type": { + "context": { + "id": "token@@Table@[L65:C0, L65:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1324, + "fullStart": 1261 + }, + { + "context": { + "id": "node@@@[L69:C0, L69:C27]", + "snippet": "Dep: users...nown_table" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L69:C5, L69:C27]", + "snippet": "users -> u...nown_table" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L69:C5, L69:C27]", + "snippet": "users -> u...nown_table" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L69:C5, L69:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L69:C5, L69:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L69:C5, L69:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 1362, + "fullStart": 1356 + } + }, + "fullEnd": 1362, + "fullStart": 1356 + }, + "op": { + "context": { + "id": "token@@->@[L69:C11, L69:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@unknown_table@[L69:C14, L69:C27]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L69:C14, L69:C27]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L69:C14, L69:C27]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_table" + } + }, + "fullEnd": 1379, + "fullStart": 1365 + } + }, + "fullEnd": 1379, + "fullStart": 1365 + } + }, + "fullEnd": 1379, + "fullStart": 1356 + } + }, + "fullEnd": 1379, + "fullStart": 1356 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L69:C3, L69:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L69:C0, L69:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n short-form\n no error\n", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 1379, + "fullStart": 1324 + }, + { + "context": { + "id": "node@@@[L70:C0, L70:C34]", + "snippet": "Dep: users...> users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L70:C5, L70:C34]", + "snippet": "users.unkn...> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L70:C5, L70:C34]", + "snippet": "users.unkn...> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L70:C5, L70:C22]", + "snippet": "users.unknown_col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L70:C5, L70:C10]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L70:C5, L70:C10]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L70:C5, L70:C10]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1389, + "fullStart": 1384 + } + }, + "fullEnd": 1389, + "fullStart": 1384 + }, + "op": { + "context": { + "id": "token@@.@[L70:C10, L70:C11]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_col@[L70:C11, L70:C22]", + "snippet": "unknown_col" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_col@[L70:C11, L70:C22]", + "snippet": "unknown_col" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_col@[L70:C11, L70:C22]", + "snippet": "unknown_col" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "unknown_col" + } + }, + "fullEnd": 1402, + "fullStart": 1390 + } + }, + "fullEnd": 1402, + "fullStart": 1390 + } + }, + "fullEnd": 1402, + "fullStart": 1384 + }, + "op": { + "context": { + "id": "token@@->@[L70:C23, L70:C25]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L70:C26, L70:C34]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L70:C26, L70:C31]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L70:C26, L70:C31]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L70:C26, L70:C31]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1410, + "fullStart": 1405 + } + }, + "fullEnd": 1410, + "fullStart": 1405 + }, + "op": { + "context": { + "id": "token@@.@[L70:C31, L70:C32]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L70:C32, L70:C34]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L70:C32, L70:C34]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L70:C32, L70:C34]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 1414, + "fullStart": 1411 + } + }, + "fullEnd": 1414, + "fullStart": 1411 + } + }, + "fullEnd": 1414, + "fullStart": 1405 + } + }, + "fullEnd": 1414, + "fullStart": 1384 + } + }, + "fullEnd": 1414, + "fullStart": 1384 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L70:C3, L70:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L70:C0, L70:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 1414, + "fullStart": 1379 + }, + { + "context": { + "id": "node@@@[L71:C0, L71:C35]", + "snippet": "Dep: unkno...s -> users" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L71:C5, L71:C35]", + "snippet": "unknown_sc...s -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L71:C5, L71:C35]", + "snippet": "unknown_sc...s -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L71:C5, L71:C26]", + "snippet": "unknown_sc...ema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_schema@[L71:C5, L71:C19]", + "snippet": "unknown_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_schema@[L71:C5, L71:C19]", + "snippet": "unknown_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_schema@[L71:C5, L71:C19]", + "snippet": "unknown_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_schema" + } + }, + "fullEnd": 1433, + "fullStart": 1419 + } + }, + "fullEnd": 1433, + "fullStart": 1419 + }, + "op": { + "context": { + "id": "token@@.@[L71:C19, L71:C20]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L71:C20, L71:C26]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L71:C20, L71:C26]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L71:C20, L71:C26]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 1441, + "fullStart": 1434 + } + }, + "fullEnd": 1441, + "fullStart": 1434 + } + }, + "fullEnd": 1441, + "fullStart": 1419 + }, + "op": { + "context": { + "id": "token@@->@[L71:C27, L71:C29]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L71:C30, L71:C35]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L71:C30, L71:C35]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L71:C30, L71:C35]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 1450, + "fullStart": 1444 + } + }, + "fullEnd": 1450, + "fullStart": 1444 + } + }, + "fullEnd": 1450, + "fullStart": 1419 + } + }, + "fullEnd": 1450, + "fullStart": 1419 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L71:C3, L71:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L71:C0, L71:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 1450, + "fullStart": 1414 + }, + { + "context": { + "id": "node@@@[L72:C0, L72:C37]", + "snippet": "Dep: my_sc...e -> users" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L72:C5, L72:C37]", + "snippet": "my_schema....e -> users" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L72:C5, L72:C37]", + "snippet": "my_schema....e -> users" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L72:C5, L72:C28]", + "snippet": "my_schema....nown_table" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L72:C5, L72:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L72:C5, L72:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L72:C5, L72:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1464, + "fullStart": 1455 + } + }, + "fullEnd": 1464, + "fullStart": 1455 + }, + "op": { + "context": { + "id": "token@@.@[L72:C14, L72:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_table@[L72:C15, L72:C28]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L72:C15, L72:C28]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L72:C15, L72:C28]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "unknown_table" + } + }, + "fullEnd": 1479, + "fullStart": 1465 + } + }, + "fullEnd": 1479, + "fullStart": 1465 + } + }, + "fullEnd": 1479, + "fullStart": 1455 + }, + "op": { + "context": { + "id": "token@@->@[L72:C29, L72:C31]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@users@[L72:C32, L72:C37]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L72:C32, L72:C37]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L72:C32, L72:C37]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "users" + } + }, + "fullEnd": 1488, + "fullStart": 1482 + } + }, + "fullEnd": 1488, + "fullStart": 1482 + } + }, + "fullEnd": 1488, + "fullStart": 1455 + } + }, + "fullEnd": 1488, + "fullStart": 1455 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L72:C3, L72:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L72:C0, L72:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 1488, + "fullStart": 1450 + }, + { + "context": { + "id": "node@@@[L73:C0, L73:C45]", + "snippet": "Dep: my_sc...> users.id" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L73:C5, L73:C45]", + "snippet": "my_schema....> users.id" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L73:C5, L73:C45]", + "snippet": "my_schema....> users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L73:C5, L73:C33]", + "snippet": "my_schema....nknown_col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L73:C5, L73:C21]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L73:C5, L73:C14]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L73:C5, L73:C14]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L73:C5, L73:C14]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1502, + "fullStart": 1493 + } + }, + "fullEnd": 1502, + "fullStart": 1493 + }, + "op": { + "context": { + "id": "token@@.@[L73:C14, L73:C15]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L73:C15, L73:C21]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L73:C15, L73:C21]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L73:C15, L73:C21]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 1509, + "fullStart": 1503 + } + }, + "fullEnd": 1509, + "fullStart": 1503 + } + }, + "fullEnd": 1509, + "fullStart": 1493 + }, + "op": { + "context": { + "id": "token@@.@[L73:C21, L73:C22]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_col@[L73:C22, L73:C33]", + "snippet": "unknown_col" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_col@[L73:C22, L73:C33]", + "snippet": "unknown_col" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_col@[L73:C22, L73:C33]", + "snippet": "unknown_col" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "unknown_col" + } + }, + "fullEnd": 1522, + "fullStart": 1510 + } + }, + "fullEnd": 1522, + "fullStart": 1510 + } + }, + "fullEnd": 1522, + "fullStart": 1493 + }, + "op": { + "context": { + "id": "token@@->@[L73:C34, L73:C36]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L73:C37, L73:C45]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L73:C37, L73:C42]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L73:C37, L73:C42]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L73:C37, L73:C42]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1530, + "fullStart": 1525 + } + }, + "fullEnd": 1530, + "fullStart": 1525 + }, + "op": { + "context": { + "id": "token@@.@[L73:C42, L73:C43]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L73:C43, L73:C45]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L73:C43, L73:C45]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L73:C43, L73:C45]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "id" + } + }, + "fullEnd": 1534, + "fullStart": 1531 + } + }, + "fullEnd": 1534, + "fullStart": 1531 + } + }, + "fullEnd": 1534, + "fullStart": 1525 + } + }, + "fullEnd": 1534, + "fullStart": 1493 + } + }, + "fullEnd": 1534, + "fullStart": 1493 + }, + "bodyColon": { + "context": { + "id": "token@@:@[L73:C3, L73:C4]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "type": { + "context": { + "id": "token@@Dep@[L73:C0, L73:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "Dep" + } + }, + "fullEnd": 1534, + "fullStart": 1488 + }, + { + "context": { + "id": "node@@@[L77:C0, L82:C1]", + "snippet": "Dep {\n us...nknown_d\n}" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L77:C4, L82:C1]", + "snippet": "{\n users ...nknown_d\n}" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L82:C0, L82:C1]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L77:C4, L77:C5]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@@[L78:C2, L78:C20]", + "snippet": "users -> unknown_a" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L78:C2, L78:C20]", + "snippet": "users -> unknown_a" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L78:C2, L78:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L78:C2, L78:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L78:C2, L78:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": " ", + "value": "users" + } + }, + "fullEnd": 1574, + "fullStart": 1566 + } + }, + "fullEnd": 1574, + "fullStart": 1566 + }, + "op": { + "context": { + "id": "token@@->@[L78:C8, L78:C10]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@unknown_a@[L78:C11, L78:C20]", + "snippet": "unknown_a" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_a@[L78:C11, L78:C20]", + "snippet": "unknown_a" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_a@[L78:C11, L78:C20]", + "snippet": "unknown_a" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_a" + } + }, + "fullEnd": 1587, + "fullStart": 1577 + } + }, + "fullEnd": 1587, + "fullStart": 1577 + } + }, + "fullEnd": 1587, + "fullStart": 1566 + } + }, + "fullEnd": 1587, + "fullStart": 1566 + }, + { + "context": { + "id": "node@@@[L79:C2, L79:C31]", + "snippet": "my_schema.... unknown_b" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L79:C2, L79:C31]", + "snippet": "my_schema.... unknown_b" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L79:C2, L79:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L79:C2, L79:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L79:C2, L79:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L79:C2, L79:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1598, + "fullStart": 1587 + } + }, + "fullEnd": 1598, + "fullStart": 1587 + }, + "op": { + "context": { + "id": "token@@.@[L79:C11, L79:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L79:C12, L79:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L79:C12, L79:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L79:C12, L79:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "events" + } + }, + "fullEnd": 1606, + "fullStart": 1599 + } + }, + "fullEnd": 1606, + "fullStart": 1599 + } + }, + "fullEnd": 1606, + "fullStart": 1587 + }, + "op": { + "context": { + "id": "token@@->@[L79:C19, L79:C21]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@unknown_b@[L79:C22, L79:C31]", + "snippet": "unknown_b" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_b@[L79:C22, L79:C31]", + "snippet": "unknown_b" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_b@[L79:C22, L79:C31]", + "snippet": "unknown_b" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_b" + } + }, + "fullEnd": 1619, + "fullStart": 1609 + } + }, + "fullEnd": 1619, + "fullStart": 1609 + } + }, + "fullEnd": 1619, + "fullStart": 1587 + } + }, + "fullEnd": 1619, + "fullStart": 1587 + }, + { + "context": { + "id": "node@@@[L80:C2, L80:C27]", + "snippet": "users.id -...nown_c.col" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L80:C2, L80:C27]", + "snippet": "users.id -...nown_c.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L80:C2, L80:C10]", + "snippet": "users.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L80:C2, L80:C7]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L80:C2, L80:C7]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L80:C2, L80:C7]", + "snippet": "users" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1626, + "fullStart": 1619 + } + }, + "fullEnd": 1626, + "fullStart": 1619 + }, + "op": { + "context": { + "id": "token@@.@[L80:C7, L80:C8]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L80:C8, L80:C10]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L80:C8, L80:C10]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L80:C8, L80:C10]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1630, + "fullStart": 1627 + } + }, + "fullEnd": 1630, + "fullStart": 1627 + } + }, + "fullEnd": 1630, + "fullStart": 1619 + }, + "op": { + "context": { + "id": "token@@->@[L80:C11, L80:C13]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L80:C14, L80:C27]", + "snippet": "unknown_c.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_c@[L80:C14, L80:C23]", + "snippet": "unknown_c" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_c@[L80:C14, L80:C23]", + "snippet": "unknown_c" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_c@[L80:C14, L80:C23]", + "snippet": "unknown_c" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_c" + } + }, + "fullEnd": 1642, + "fullStart": 1633 + } + }, + "fullEnd": 1642, + "fullStart": 1633 + }, + "op": { + "context": { + "id": "token@@.@[L80:C23, L80:C24]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@col@[L80:C24, L80:C27]", + "snippet": "col" + }, + "children": { + "expression": { + "context": { + "id": "node@@col@[L80:C24, L80:C27]", + "snippet": "col" + }, + "children": { + "variable": { + "context": { + "id": "token@@col@[L80:C24, L80:C27]", + "snippet": "col" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "col" + } + }, + "fullEnd": 1647, + "fullStart": 1643 + } + }, + "fullEnd": 1647, + "fullStart": 1643 + } + }, + "fullEnd": 1647, + "fullStart": 1633 + } + }, + "fullEnd": 1647, + "fullStart": 1619 + } + }, + "fullEnd": 1647, + "fullStart": 1619 + }, + { + "context": { + "id": "node@@@[L81:C2, L81:C40]", + "snippet": "my_schema.....unknown_d" + }, + "children": { + "callee": { + "context": { + "id": "node@@@[L81:C2, L81:C40]", + "snippet": "my_schema.....unknown_d" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L81:C2, L81:C21]", + "snippet": "my_schema.events.id" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L81:C2, L81:C18]", + "snippet": "my_schema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L81:C2, L81:C11]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L81:C2, L81:C11]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L81:C2, L81:C11]", + "snippet": "my_schema" + }, + "leadingTrivia": " ", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1658, + "fullStart": 1647 + } + }, + "fullEnd": 1658, + "fullStart": 1647 + }, + "op": { + "context": { + "id": "token@@.@[L81:C11, L81:C12]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L81:C12, L81:C18]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L81:C12, L81:C18]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L81:C12, L81:C18]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 1665, + "fullStart": 1659 + } + }, + "fullEnd": 1665, + "fullStart": 1659 + } + }, + "fullEnd": 1665, + "fullStart": 1647 + }, + "op": { + "context": { + "id": "token@@.@[L81:C18, L81:C19]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@id@[L81:C19, L81:C21]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L81:C19, L81:C21]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L81:C19, L81:C21]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1669, + "fullStart": 1666 + } + }, + "fullEnd": 1669, + "fullStart": 1666 + } + }, + "fullEnd": 1669, + "fullStart": 1647 + }, + "op": { + "context": { + "id": "token@@->@[L81:C22, L81:C24]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + }, + "rightExpression": { + "context": { + "id": "node@@@[L81:C25, L81:C40]", + "snippet": "users.unknown_d" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@users@[L81:C25, L81:C30]", + "snippet": "users" + }, + "children": { + "expression": { + "context": { + "id": "node@@users@[L81:C25, L81:C30]", + "snippet": "users" + }, + "children": { + "variable": { + "context": { + "id": "token@@users@[L81:C25, L81:C30]", + "snippet": "users" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "users" + } + }, + "fullEnd": 1677, + "fullStart": 1672 + } + }, + "fullEnd": 1677, + "fullStart": 1672 + }, + "op": { + "context": { + "id": "token@@.@[L81:C30, L81:C31]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_d@[L81:C31, L81:C40]", + "snippet": "unknown_d" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_d@[L81:C31, L81:C40]", + "snippet": "unknown_d" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_d@[L81:C31, L81:C40]", + "snippet": "unknown_d" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "unknown_d" + } + }, + "fullEnd": 1688, + "fullStart": 1678 + } + }, + "fullEnd": 1688, + "fullStart": 1678 + } + }, + "fullEnd": 1688, + "fullStart": 1672 + } + }, + "fullEnd": 1688, + "fullStart": 1647 + } + }, + "fullEnd": 1688, + "fullStart": 1647 + } + ] + }, + "fullEnd": 1690, + "fullStart": 1564 + }, + "type": { + "context": { + "id": "token@@Dep@[L77:C0, L77:C3]", + "snippet": "Dep" + }, + "leadingTrivia": "\n full-form\n no error\n", + "trailingTrivia": " ", + "value": "Dep" + } + }, + "fullEnd": 1690, + "fullStart": 1534 + }, + { + "context": { + "id": "node@@bad_header_bare@[L86:C0, L86:C57]", + "snippet": "Table bad_...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L86:C22, L86:C46]", + "snippet": "[dep: <- u...wn_source]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L86:C23, L86:C45]", + "snippet": "dep: <- un...own_source" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L86:C26, L86:C27]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L86:C23, L86:C26]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L86:C23, L86:C26]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1755, + "fullStart": 1752 + }, + "value": { + "context": { + "id": "node@@@[L86:C28, L86:C45]", + "snippet": "<- unknown_source" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_source@[L86:C31, L86:C45]", + "snippet": "unknown_source" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_source@[L86:C31, L86:C45]", + "snippet": "unknown_source" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_source@[L86:C31, L86:C45]", + "snippet": "unknown_source" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_source" + } + }, + "fullEnd": 1774, + "fullStart": 1760 + } + }, + "fullEnd": 1774, + "fullStart": 1760 + }, + "op": { + "context": { + "id": "token@@<-@[L86:C28, L86:C30]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 1774, + "fullStart": 1757 + } + }, + "fullEnd": 1774, + "fullStart": 1752 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L86:C45, L86:C46]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L86:C22, L86:C23]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1776, + "fullStart": 1751 + }, + "body": { + "context": { + "id": "node@@@[L86:C47, L86:C57]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L86:C56, L86:C57]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L86:C47, L86:C48]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L86:C49, L86:C55]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L86:C52, L86:C55]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L86:C52, L86:C55]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L86:C52, L86:C55]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1785, + "fullStart": 1781 + } + }, + "fullEnd": 1785, + "fullStart": 1781 + } + ], + "callee": { + "context": { + "id": "node@@id@[L86:C49, L86:C51]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L86:C49, L86:C51]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L86:C49, L86:C51]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1781, + "fullStart": 1778 + } + }, + "fullEnd": 1781, + "fullStart": 1778 + } + }, + "fullEnd": 1785, + "fullStart": 1778 + } + ] + }, + "fullEnd": 1787, + "fullStart": 1776 + }, + "name": { + "context": { + "id": "node@@bad_header_bare@[L86:C6, L86:C21]", + "snippet": "bad_header_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_header_bare@[L86:C6, L86:C21]", + "snippet": "bad_header_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_header_bare@[L86:C6, L86:C21]", + "snippet": "bad_header_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_header_bare" + } + }, + "fullEnd": 1751, + "fullStart": 1735 + } + }, + "fullEnd": 1751, + "fullStart": 1735 + }, + "type": { + "context": { + "id": "token@@Table@[L86:C0, L86:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on table header\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1787, + "fullStart": 1690 + }, + { + "context": { + "id": "node@@bad_header_schema@[L87:C0, L87:C66]", + "snippet": "Table bad_...{ id int }" + }, + "children": { + "attributeList": { + "context": { + "id": "node@@@[L87:C24, L87:C55]", + "snippet": "[dep: <- u...ma.events]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L87:C25, L87:C54]", + "snippet": "dep: <- un...ema.events" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L87:C28, L87:C29]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L87:C25, L87:C28]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L87:C25, L87:C28]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1815, + "fullStart": 1812 + }, + "value": { + "context": { + "id": "node@@@[L87:C30, L87:C54]", + "snippet": "<- unknown...ema.events" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L87:C33, L87:C54]", + "snippet": "unknown_sc...ema.events" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_schema@[L87:C33, L87:C47]", + "snippet": "unknown_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_schema@[L87:C33, L87:C47]", + "snippet": "unknown_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_schema@[L87:C33, L87:C47]", + "snippet": "unknown_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_schema" + } + }, + "fullEnd": 1834, + "fullStart": 1820 + } + }, + "fullEnd": 1834, + "fullStart": 1820 + }, + "op": { + "context": { + "id": "token@@.@[L87:C47, L87:C48]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@events@[L87:C48, L87:C54]", + "snippet": "events" + }, + "children": { + "expression": { + "context": { + "id": "node@@events@[L87:C48, L87:C54]", + "snippet": "events" + }, + "children": { + "variable": { + "context": { + "id": "token@@events@[L87:C48, L87:C54]", + "snippet": "events" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "events" + } + }, + "fullEnd": 1841, + "fullStart": 1835 + } + }, + "fullEnd": 1841, + "fullStart": 1835 + } + }, + "fullEnd": 1841, + "fullStart": 1820 + }, + "op": { + "context": { + "id": "token@@<-@[L87:C30, L87:C32]", + "snippet": "<-" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "<-" + } + }, + "fullEnd": 1841, + "fullStart": 1817 + } + }, + "fullEnd": 1841, + "fullStart": 1812 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L87:C54, L87:C55]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L87:C24, L87:C25]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1843, + "fullStart": 1811 + }, + "body": { + "context": { + "id": "node@@@[L87:C56, L87:C66]", + "snippet": "{ id int }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L87:C65, L87:C66]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L87:C56, L87:C57]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L87:C58, L87:C64]", + "snippet": "id int" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L87:C61, L87:C64]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L87:C61, L87:C64]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L87:C61, L87:C64]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1852, + "fullStart": 1848 + } + }, + "fullEnd": 1852, + "fullStart": 1848 + } + ], + "callee": { + "context": { + "id": "node@@id@[L87:C58, L87:C60]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L87:C58, L87:C60]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L87:C58, L87:C60]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1848, + "fullStart": 1845 + } + }, + "fullEnd": 1848, + "fullStart": 1845 + } + }, + "fullEnd": 1852, + "fullStart": 1845 + } + ] + }, + "fullEnd": 1854, + "fullStart": 1843 + }, + "name": { + "context": { + "id": "node@@bad_header_schema@[L87:C6, L87:C23]", + "snippet": "bad_header_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_header_schema@[L87:C6, L87:C23]", + "snippet": "bad_header_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_header_schema@[L87:C6, L87:C23]", + "snippet": "bad_header_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_header_schema" + } + }, + "fullEnd": 1811, + "fullStart": 1793 + } + }, + "fullEnd": 1811, + "fullStart": 1793 + }, + "type": { + "context": { + "id": "token@@Table@[L87:C0, L87:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1854, + "fullStart": 1787 + }, + { + "context": { + "id": "node@@bad_col_bare@[L91:C0, L91:C57]", + "snippet": "Table bad_...ble.col] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L91:C19, L91:C57]", + "snippet": "{ id int [...ble.col] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L91:C56, L91:C57]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L91:C19, L91:C20]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L91:C21, L91:C55]", + "snippet": "id int [de...table.col]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L91:C24, L91:C27]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L91:C24, L91:C27]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L91:C24, L91:C27]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1915, + "fullStart": 1911 + } + }, + "fullEnd": 1915, + "fullStart": 1911 + }, + { + "context": { + "id": "node@@@[L91:C28, L91:C55]", + "snippet": "[dep: -> u...table.col]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L91:C29, L91:C54]", + "snippet": "dep: -> un..._table.col" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L91:C32, L91:C33]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L91:C29, L91:C32]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L91:C29, L91:C32]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1919, + "fullStart": 1916 + }, + "value": { + "context": { + "id": "node@@@[L91:C34, L91:C54]", + "snippet": "-> unknown_table.col" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L91:C37, L91:C54]", + "snippet": "unknown_table.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@unknown_table@[L91:C37, L91:C50]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L91:C37, L91:C50]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L91:C37, L91:C50]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_table" + } + }, + "fullEnd": 1937, + "fullStart": 1924 + } + }, + "fullEnd": 1937, + "fullStart": 1924 + }, + "op": { + "context": { + "id": "token@@.@[L91:C50, L91:C51]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@col@[L91:C51, L91:C54]", + "snippet": "col" + }, + "children": { + "expression": { + "context": { + "id": "node@@col@[L91:C51, L91:C54]", + "snippet": "col" + }, + "children": { + "variable": { + "context": { + "id": "token@@col@[L91:C51, L91:C54]", + "snippet": "col" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "col" + } + }, + "fullEnd": 1941, + "fullStart": 1938 + } + }, + "fullEnd": 1941, + "fullStart": 1938 + } + }, + "fullEnd": 1941, + "fullStart": 1924 + }, + "op": { + "context": { + "id": "token@@->@[L91:C34, L91:C36]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 1941, + "fullStart": 1921 + } + }, + "fullEnd": 1941, + "fullStart": 1916 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L91:C54, L91:C55]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L91:C28, L91:C29]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 1943, + "fullStart": 1915 + } + ], + "callee": { + "context": { + "id": "node@@id@[L91:C21, L91:C23]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L91:C21, L91:C23]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L91:C21, L91:C23]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1911, + "fullStart": 1908 + } + }, + "fullEnd": 1911, + "fullStart": 1908 + } + }, + "fullEnd": 1943, + "fullStart": 1908 + } + ] + }, + "fullEnd": 1945, + "fullStart": 1906 + }, + "name": { + "context": { + "id": "node@@bad_col_bare@[L91:C6, L91:C18]", + "snippet": "bad_col_bare" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_col_bare@[L91:C6, L91:C18]", + "snippet": "bad_col_bare" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_col_bare@[L91:C6, L91:C18]", + "snippet": "bad_col_bare" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_col_bare" + } + }, + "fullEnd": 1906, + "fullStart": 1893 + } + }, + "fullEnd": 1906, + "fullStart": 1893 + }, + "type": { + "context": { + "id": "token@@Table@[L91:C0, L91:C5]", + "snippet": "Table" + }, + "leadingTrivia": "\n inline on column\n no error\n", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 1945, + "fullStart": 1854 + }, + { + "context": { + "id": "node@@bad_col_schema@[L92:C0, L92:C69]", + "snippet": "Table bad_...ble.col] }" + }, + "children": { + "body": { + "context": { + "id": "node@@@[L92:C21, L92:C69]", + "snippet": "{ id int [...ble.col] }" + }, + "children": { + "blockCloseBrace": { + "context": { + "id": "token@@}@[L92:C68, L92:C69]", + "snippet": "}" + }, + "leadingTrivia": "", + "trailingTrivia": "\n", + "value": "}" + }, + "blockOpenBrace": { + "context": { + "id": "token@@{@[L92:C21, L92:C22]", + "snippet": "{" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "{" + }, + "body": [ + { + "context": { + "id": "node@@id@[L92:C23, L92:C67]", + "snippet": "id int [de...table.col]" + }, + "children": { + "args": [ + { + "context": { + "id": "node@@int@[L92:C26, L92:C29]", + "snippet": "int" + }, + "children": { + "expression": { + "context": { + "id": "node@@int@[L92:C26, L92:C29]", + "snippet": "int" + }, + "children": { + "variable": { + "context": { + "id": "token@@int@[L92:C26, L92:C29]", + "snippet": "int" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "int" + } + }, + "fullEnd": 1975, + "fullStart": 1971 + } + }, + "fullEnd": 1975, + "fullStart": 1971 + }, + { + "context": { + "id": "node@@@[L92:C30, L92:C67]", + "snippet": "[dep: -> m...table.col]" + }, + "children": { + "elementList": [ + { + "context": { + "id": "node@@@[L92:C31, L92:C66]", + "snippet": "dep: -> my..._table.col" + }, + "children": { + "colon": { + "context": { + "id": "token@@:@[L92:C34, L92:C35]", + "snippet": ":" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": ":" + }, + "name": { + "context": { + "id": "node@@@[L92:C31, L92:C34]", + "snippet": "dep" + }, + "children": { + "identifiers": [ + { + "context": { + "id": "token@@dep@[L92:C31, L92:C34]", + "snippet": "dep" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "dep" + } + ] + }, + "fullEnd": 1979, + "fullStart": 1976 + }, + "value": { + "context": { + "id": "node@@@[L92:C36, L92:C66]", + "snippet": "-> my_sche..._table.col" + }, + "children": { + "expression": { + "context": { + "id": "node@@@[L92:C39, L92:C66]", + "snippet": "my_schema...._table.col" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@@[L92:C39, L92:C62]", + "snippet": "my_schema....nown_table" + }, + "children": { + "leftExpression": { + "context": { + "id": "node@@my_schema@[L92:C39, L92:C48]", + "snippet": "my_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@my_schema@[L92:C39, L92:C48]", + "snippet": "my_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@my_schema@[L92:C39, L92:C48]", + "snippet": "my_schema" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "my_schema" + } + }, + "fullEnd": 1993, + "fullStart": 1984 + } + }, + "fullEnd": 1993, + "fullStart": 1984 + }, + "op": { + "context": { + "id": "token@@.@[L92:C48, L92:C49]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@unknown_table@[L92:C49, L92:C62]", + "snippet": "unknown_table" + }, + "children": { + "expression": { + "context": { + "id": "node@@unknown_table@[L92:C49, L92:C62]", + "snippet": "unknown_table" + }, + "children": { + "variable": { + "context": { + "id": "token@@unknown_table@[L92:C49, L92:C62]", + "snippet": "unknown_table" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "unknown_table" + } + }, + "fullEnd": 2007, + "fullStart": 1994 + } + }, + "fullEnd": 2007, + "fullStart": 1994 + } + }, + "fullEnd": 2007, + "fullStart": 1984 + }, + "op": { + "context": { + "id": "token@@.@[L92:C62, L92:C63]", + "snippet": "." + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "." + }, + "rightExpression": { + "context": { + "id": "node@@col@[L92:C63, L92:C66]", + "snippet": "col" + }, + "children": { + "expression": { + "context": { + "id": "node@@col@[L92:C63, L92:C66]", + "snippet": "col" + }, + "children": { + "variable": { + "context": { + "id": "token@@col@[L92:C63, L92:C66]", + "snippet": "col" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "col" + } + }, + "fullEnd": 2011, + "fullStart": 2008 + } + }, + "fullEnd": 2011, + "fullStart": 2008 + } + }, + "fullEnd": 2011, + "fullStart": 1984 + }, + "op": { + "context": { + "id": "token@@->@[L92:C36, L92:C38]", + "snippet": "->" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "->" + } + }, + "fullEnd": 2011, + "fullStart": 1981 + } + }, + "fullEnd": 2011, + "fullStart": 1976 + } + ], + "listCloseBracket": { + "context": { + "id": "token@@]@[L92:C66, L92:C67]", + "snippet": "]" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "]" + }, + "listOpenBracket": { + "context": { + "id": "token@@[@[L92:C30, L92:C31]", + "snippet": "[" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "[" + } + }, + "fullEnd": 2013, + "fullStart": 1975 + } + ], + "callee": { + "context": { + "id": "node@@id@[L92:C23, L92:C25]", + "snippet": "id" + }, + "children": { + "expression": { + "context": { + "id": "node@@id@[L92:C23, L92:C25]", + "snippet": "id" + }, + "children": { + "variable": { + "context": { + "id": "token@@id@[L92:C23, L92:C25]", + "snippet": "id" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "id" + } + }, + "fullEnd": 1971, + "fullStart": 1968 + } + }, + "fullEnd": 1971, + "fullStart": 1968 + } + }, + "fullEnd": 2013, + "fullStart": 1968 + } + ] + }, + "fullEnd": 2015, + "fullStart": 1966 + }, + "name": { + "context": { + "id": "node@@bad_col_schema@[L92:C6, L92:C20]", + "snippet": "bad_col_schema" + }, + "children": { + "expression": { + "context": { + "id": "node@@bad_col_schema@[L92:C6, L92:C20]", + "snippet": "bad_col_schema" + }, + "children": { + "variable": { + "context": { + "id": "token@@bad_col_schema@[L92:C6, L92:C20]", + "snippet": "bad_col_schema" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "bad_col_schema" + } + }, + "fullEnd": 1966, + "fullStart": 1951 + } + }, + "fullEnd": 1966, + "fullStart": 1951 + }, + "type": { + "context": { + "id": "token@@Table@[L92:C0, L92:C5]", + "snippet": "Table" + }, + "leadingTrivia": "", + "trailingTrivia": " ", + "value": "Table" + } + }, + "fullEnd": 2015, + "fullStart": 1945 + } + ], + "eof": { + "context": { + "id": "token@@@[L93:C0, L93:C0]", + "snippet": "" + }, + "leadingTrivia": "", + "trailingTrivia": "", + "value": "" + } + }, + "fullEnd": 2015, + "fullStart": 0 + } +} \ No newline at end of file diff --git a/packages/dbml-parse/package.json b/packages/dbml-parse/package.json index 8b620c11b..7631b9df9 100644 --- a/packages/dbml-parse/package.json +++ b/packages/dbml-parse/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/parse", - "version": "8.4.0-alpha.1", + "version": "9.0.0-alpha.21", "description": "DBML parser v2", "author": "Holistics ", "license": "Apache-2.0", diff --git a/packages/dbml-parse/src/compiler/index.ts b/packages/dbml-parse/src/compiler/index.ts index df2dfead3..435ebd648 100644 --- a/packages/dbml-parse/src/compiler/index.ts +++ b/packages/dbml-parse/src/compiler/index.ts @@ -44,22 +44,19 @@ import { } from './queries/resolutionIndex'; import { symbolUses } from './queries/symbol/symbolUses'; import { - type DiagramViewBlock, - findDiagramViewBlocks, - renameTable, syncDiagramView, + renameTable, syncDiagramView, syncDep, updateElementSetting, updateElementSettingEdit, } from './queries/transform'; import { - addDoubleQuoteIfNeeded, escapeString, formatRecordValue, isValidIdentifier, splitQualifiedIdentifier, unescapeString, + addDoubleQuoteIfNeeded, escapeString, formatRecordValue, isValidIdentifier, normalizeQualifiedName, splitQualifiedIdentifier, unescapeString, } from './queries/utils'; import { DEFAULT_ENTRY } from '@/constants'; // Re-export types export { ScopeKind } from './types'; -export type { DiagramViewBlock, TableNameInput, TextEdit } from './queries/transform'; +export type { TextEdit } from './queries/transform'; // Re-export utilities export { - addDoubleQuoteIfNeeded, escapeString, formatRecordValue, isValidIdentifier, splitQualifiedIdentifier, unescapeString, - findDiagramViewBlocks, + addDoubleQuoteIfNeeded, escapeString, formatRecordValue, isValidIdentifier, normalizeQualifiedName, splitQualifiedIdentifier, unescapeString, }; // Sentinel placed in the cache while a query is being computed. @@ -387,10 +384,10 @@ export default class Compiler { // transform queries renameTable = renameTable.bind(this); + updateElementSetting = updateElementSetting.bind(this); + updateElementSettingEdit = updateElementSettingEdit.bind(this); syncDiagramView = syncDiagramView.bind(this); - findDiagramViewBlocks (filepath: Filepath): DiagramViewBlock[] { - return findDiagramViewBlocks(this.getSource(filepath) ?? ''); - } + syncDep = syncDep.bind(this); // @deprecated - legacy APIs for services compatibility readonly token = { diff --git a/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts b/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts index 55e2d48f6..1bcb87433 100644 --- a/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts +++ b/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts @@ -19,6 +19,8 @@ export function containerScopeKind (this: Compiler, filepath: Filepath, offset: return ScopeKind.ENUM; case 'ref': return ScopeKind.REF; + case 'dep': + return ScopeKind.DEP; case 'tablegroup': return ScopeKind.TABLEGROUP; case 'indexes': diff --git a/packages/dbml-parse/src/compiler/queries/transform/index.ts b/packages/dbml-parse/src/compiler/queries/transform/index.ts index bf41f40a3..6d726fc4d 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/index.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/index.ts @@ -1,9 +1,30 @@ export { renameTable } from './renameTable'; +export { updateElementSetting, updateElementSettingEdit } from './updateElementSetting'; export { syncDiagramView, findDiagramViewBlocks, type DiagramViewSyncOperation, type DiagramViewBlock, } from './syncDiagramView'; +export { + syncDep, + findDepBlocks, + generateDepBlock, + type DepSyncOperation, + type DepSyncEdge, + type DepEndpointRef, + type DepBlock, +} from './syncDep'; export { applyTextEdits, type TextEdit } from './applyTextEdits'; -export { type TableNameInput } from './utils'; +export type { + ElementIdentifier, + SchemaIdentifier, + TableIdentifier, + ColumnIdentifier, + EnumIdentifier, + EndpointRef, + RefIdentifier, + DepIdentifier, + NoteIdentifier, + TableGroupIdentifier, +} from './types'; diff --git a/packages/dbml-parse/src/compiler/queries/transform/renameTable.ts b/packages/dbml-parse/src/compiler/queries/transform/renameTable.ts index 5ebce8093..681936202 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/renameTable.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/renameTable.ts @@ -6,9 +6,8 @@ import { ElementDeclarationNode, SyntaxNode, UseSpecifierNode } from '@/core/typ import { AliasSymbol, NodeSymbol, UseSymbol } from '@/core/types/symbol'; import type Compiler from '../../index'; import { TextEdit, applyTextEdits } from './applyTextEdits'; -import { - type TableNameInput, lookupTableSymbol, normalizeTableName, stripQuotes, -} from './utils'; +import type { TableIdentifier } from './types'; +import { lookupElementSymbol, normalizeTableName, stripQuotes } from './utils'; interface FormattedTableName { schema: string; @@ -60,7 +59,7 @@ function checkForNameCollision ( newTable: string, ): boolean { if (oldSchema === newSchema && oldTable === newTable) return false; - return lookupTableSymbol(compiler, filepath, newSchema, newTable) !== null; + return lookupElementSymbol(compiler, filepath, newSchema, newTable) !== undefined; } /** @@ -180,8 +179,8 @@ function findReplacements ( export function renameTable ( this: Compiler, filepath: Filepath, - oldName: TableNameInput, - newName: TableNameInput, + oldName: string | TableIdentifier, + newName: string | TableIdentifier, ): Map { const normalizedOld = normalizeTableName(oldName); const normalizedNew = normalizeTableName(newName); @@ -190,7 +189,7 @@ export function renameTable ( const newSchema = normalizedNew.schema; const newTable = normalizedNew.table; - const tableSymbol = lookupTableSymbol(this, filepath, oldSchema, oldTable); + const tableSymbol = lookupElementSymbol(this, filepath, oldSchema, oldTable); if (!tableSymbol) return new Map(); // Inline table aliases (e.g. `Table users as U`) resolve to an AliasSymbol. diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts new file mode 100644 index 000000000..ff1cd3b21 --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts @@ -0,0 +1,343 @@ +/** + * Dep block transform - read and write `Dep` blocks in DBML source. + * + * Parallel to {@link ./syncDiagramView.ts}, but a Dep block has no name to + * key on - a block is identified by its edge endpoints (upstream/downstream + * schema + table + fields). The color picker uses this to write a dep's + * `[color]`: + * - `update` an existing block's `[color]`, or + * - `create` a direct `Dep { a -> b } [color: ]` when the picked + * (table-level) line has no backing block, or + * - `remove` an existing block's `[color]` so the dep falls back to its + * upstream -> group -> grey default. + * `create` treats an already-matching block as an `update`, so it never + * produces a duplicate `Dep`. `remove` on an edge with no block is a no-op. + */ + +import { DEFAULT_ENTRY } from '@/constants'; +import Lexer from '@/core/lexer/lexer'; +import Parser from '@/core/parser/parser'; +import type { Filepath } from '@/core/types/filepath'; +import { ElementKind, SettingName } from '@/core/types/keywords'; +import { DEP_DOWNSTREAM, DEP_UPSTREAM } from '@/core/types/schemaJson'; +import { + ElementDeclarationNode, + FunctionApplicationNode, + InfixExpressionNode, + ListExpressionNode, + PrefixExpressionNode, + ProgramNode, + SyntaxNodeIdGenerator, +} from '@/core/types/nodes'; +import { destructureComplexVariable, extractSettingName } from '@/core/utils/expression'; +import type Compiler from '../../index'; +import { endpointMatches, formatEndpoint } from './utils'; +import { TextEdit, applyTextEdits } from './applyTextEdits'; +import { updateNoteEdit, removeNoteEdit, addNoteEdit } from '@/core/utils/note'; +import { updateSettingEdit, removeSettingEdit } from '@/core/utils/setting'; + +// Types + +export interface DepEndpointRef { + schemaName?: string | null; + tableName: string; + fieldNames?: string[]; +} + +export interface DepSyncEdge { + upstream: DepEndpointRef; + downstream: DepEndpointRef; +} + +export interface DepSyncOperation { + operation: 'create' | 'update' | 'remove'; + /** The edge that identifies the target block (table-level: empty fields). */ + edge: DepSyncEdge; + /** Set to a value to create/update, null to remove, undefined to leave unchanged. */ + color?: string | null; + /** Set to a value to create/update, null to remove, undefined to leave unchanged. */ + note?: string | null; +} + +export interface DepBlock { + startIndex: number; + endIndex: number; + declaration: ElementDeclarationNode; + edges: DepSyncEdge[]; +} + +export interface InlineDep { + edge: DepSyncEdge; + fullStart: number; + fullEnd: number; +} + +/** + * Synchronizes `Dep` blocks in the DBML source at `filepath`. + * Applies create/update/remove operations and returns the rewritten source. + */ +export function syncDep ( + this: Compiler, + filepath: Filepath, + operations: DepSyncOperation[], + blocks?: DepBlock[], +): { + newDbml: string; + edits: TextEdit[]; +} { + const dbml = this.getSource(filepath) ?? ''; + const program = this.parseFile(filepath).getValue().ast; + const originalBlocks = blocks ?? depBlocksFromProgram(program); + const inlineDeps = inlineDepsFromProgram(dbml, program); + const allEdits: TextEdit[] = []; + + for (const op of operations) { + allEdits.push(...applyOperation(dbml, op, originalBlocks, inlineDeps)); + } + + allEdits.sort((a, b) => b.start - a.start); + const newDbml = applyTextEdits(dbml, allEdits, true); + return { newDbml, edits: allEdits }; +} + +/** + * Returns every standalone `Dep` block in `source`. + * On lex/parse error returns [] (mirrors findDiagramViewBlocks). + */ +export function findDepBlocks (source: string): DepBlock[] { + const lexerResult = new Lexer(source, DEFAULT_ENTRY).lex(); + if (lexerResult.getErrors().length > 0) return []; + + const ast = new Parser(source, lexerResult.getValue(), new SyntaxNodeIdGenerator(), DEFAULT_ENTRY).parse(); + if (ast.getErrors().length > 0) return []; + + return depBlocksFromProgram(ast.getValue().ast); +} + +/** Returns every inline `[dep: -> target]` setting on columns in `source`. */ +export function findInlineDeps (source: string): InlineDep[] { + const lexerResult = new Lexer(source, DEFAULT_ENTRY).lex(); + if (lexerResult.getErrors().length > 0) return []; + + const ast = new Parser(source, lexerResult.getValue(), new SyntaxNodeIdGenerator(), DEFAULT_ENTRY).parse(); + if (ast.getErrors().length > 0) return []; + + return inlineDepsFromProgram(source, ast.getValue().ast); +} + +/** Emit a `Dep` block string for one edge with a `[color]` setting. */ +export function generateDepBlock (edge: DepSyncEdge, color: string): string { + const up = formatEndpoint(edge.upstream); + const down = formatEndpoint(edge.downstream); + return `Dep [color: ${color}] {\n ${up} -> ${down}\n}`; +} + +/** Extract `DepBlock[]` from an already-parsed program AST. */ +export function depBlocksFromProgram (program: ProgramNode): DepBlock[] { + const blocks: DepBlock[] = []; + + for (const element of program.declarations) { + if (!(element instanceof ElementDeclarationNode) || !element.isKind(ElementKind.Dep)) continue; + + const edges: DepSyncEdge[] = []; + + const body = element.body; + if (body instanceof FunctionApplicationNode) { + if (body.callee instanceof InfixExpressionNode) { + const edge = edgeFromInfix(body.callee); + if (edge) edges.push(edge); + } + } else if (body) { + for (const field of body.body) { + if (field instanceof FunctionApplicationNode && field.callee instanceof InfixExpressionNode) { + const edge = edgeFromInfix(field.callee); + if (edge) edges.push(edge); + } + } + } + + blocks.push({ + startIndex: element.start, + endIndex: element.end, + declaration: element, + edges, + }); + } + + return blocks; +} + +/** Extract `InlineDep[]` from an already-parsed program AST. */ +export function inlineDepsFromProgram (source: string, program: ProgramNode): InlineDep[] { + const result: InlineDep[] = []; + + for (const element of program.declarations) { + if (!(element instanceof ElementDeclarationNode) || !element.isKind(ElementKind.Table)) continue; + const tableFragments = element.name ? destructureComplexVariable(element.name) ?? [] : []; + if (tableFragments.length === 0) continue; + + const tableName = tableFragments[tableFragments.length - 1]; + const schemaName = tableFragments.length > 1 ? tableFragments[tableFragments.length - 2] : undefined; + + const body = element.body; + if (!body || body instanceof FunctionApplicationNode) continue; + for (const field of body.body) { + if (!(field instanceof FunctionApplicationNode) || !field.callee) continue; + const columnName = destructureComplexVariable(field.callee)?.at(-1); + if (!columnName) continue; + + const host: DepEndpointRef = { + schemaName, + tableName, + fieldNames: [ + columnName, + ], + }; + const edit = removeSettingEdit(field, SettingName.Dep, source); + if (!edit) continue; + for (const dep of extractInlineDepEdges(field, host)) { + result.push({ edge: dep, fullStart: edit.start, fullEnd: edit.end }); + } + } + } + + return result; +} + +// Private helpers + +/** Parse an `a -> b` / `a <- b` infix node into a DepSyncEdge. */ +function edgeFromInfix (infix: InfixExpressionNode): DepSyncEdge | undefined { + const op = infix.op?.value; + if (op !== DEP_DOWNSTREAM && op !== DEP_UPSTREAM) return undefined; + const left = infix.leftExpression; + const right = infix.rightExpression; + if (!left || !right) return undefined; + + const upstreamNode = op === DEP_UPSTREAM ? right : left; + const downstreamNode = op === DEP_UPSTREAM ? left : right; + + const upFragments = destructureComplexVariable(upstreamNode); + const downFragments = destructureComplexVariable(downstreamNode); + if (!upFragments || !downFragments) return undefined; + + const hasFields = upFragments.length > 1 && downFragments.length > 1; + const upstream = fragmentsToEndpoint(upFragments, hasFields); + const downstream = fragmentsToEndpoint(downFragments, hasFields); + if (!upstream || !downstream) return undefined; + return { upstream, downstream }; +} + +/** + * Split complex-variable fragments into schema/table/fields. + * `hasFields` resolves the `a.b` ambiguity (schema.table vs table.field). + */ +function fragmentsToEndpoint (fragments: string[], hasFields: boolean): DepEndpointRef | undefined { + if (fragments.length === 0) return undefined; + if (!hasFields) { + const tableName = fragments[fragments.length - 1]; + const schemaName = fragments.length > 1 ? fragments[fragments.length - 2] : null; + return { schemaName, tableName, fieldNames: [] }; + } + const fieldNames = [ + fragments[fragments.length - 1], + ]; + const tableName = fragments[fragments.length - 2]; + const schemaName = fragments.length > 2 ? fragments[fragments.length - 3] : null; + return { schemaName, tableName, fieldNames }; +} + +/** Extract dep edges from inline `[dep: -> target]` settings on a column. */ +function extractInlineDepEdges (field: FunctionApplicationNode, host: DepEndpointRef): DepSyncEdge[] { + const list = field.args.find((a) => a instanceof ListExpressionNode) as ListExpressionNode | undefined; + if (!list) return []; + + const edges: DepSyncEdge[] = []; + for (const attr of list.elementList) { + if (extractSettingName(attr) !== SettingName.Dep) continue; + const value = attr.value; + if (!(value instanceof PrefixExpressionNode) || !value.expression) continue; + const dir = value.op?.value; + if (dir !== DEP_DOWNSTREAM && dir !== DEP_UPSTREAM) continue; + const targetFragments = destructureComplexVariable(value.expression); + if (!targetFragments) continue; + const target = fragmentsToEndpoint(targetFragments, targetFragments.length > 1); + if (!target) continue; + edges.push(dir === DEP_DOWNSTREAM + ? { upstream: host, downstream: target } + : { upstream: target, downstream: host }); + } + return edges; +} + +function edgesMatch (candidate: DepSyncEdge, target: DepSyncEdge): boolean { + return endpointMatches(candidate.upstream, target.upstream) && endpointMatches(candidate.downstream, target.downstream); +} + +function blockHasEdge (block: DepBlock, edge: DepSyncEdge): boolean { + return block.edges.some((e) => edgesMatch(e, edge)); +} + +function findBlockForEdge (blocks: DepBlock[], edge: DepSyncEdge): DepBlock | undefined { + return blocks.find((b) => blockHasEdge(b, edge)); +} + +/** Dispatch a single sync operation to the appropriate edit strategy. */ +function applyOperation (dbml: string, operation: DepSyncOperation, blocks: DepBlock[], inlineDeps: InlineDep[]): TextEdit[] { + switch (operation.operation) { + case 'create': + return computeCreateEdit(dbml, operation, blocks, inlineDeps); + case 'update': { + const block = findBlockForEdge(blocks, operation.edge); + return block ? computeUpdateEdit(operation, block, dbml) : []; + } + case 'remove': { + const block = findBlockForEdge(blocks, operation.edge); + return block ? computeUpdateEdit({ ...operation, color: undefined, note: undefined }, block, dbml) : []; + } + default: + return []; + } +} + +/** Compute edits to update an existing block's color and/or note. */ +function computeUpdateEdit (operation: DepSyncOperation, block: DepBlock, source: string): TextEdit[] { + const edits: TextEdit[] = []; + + if (operation.color !== undefined) { + const edit = updateSettingEdit(block.declaration, SettingName.Color, operation.color, source); + if (edit) edits.push(edit); + } + + if (operation.note === null) { + const edit = removeNoteEdit(block.declaration); + if (edit) edits.push(edit); + } else if (operation.note !== undefined) { + const edit = updateNoteEdit(block.declaration, operation.note) ?? addNoteEdit(block.declaration, operation.note); + if (edit) edits.push(edit); + } + + return edits; +} + +/** Compute edits to create a new Dep block (or update if one already matches). */ +function computeCreateEdit (dbml: string, operation: DepSyncOperation, blocks: DepBlock[], inlineDeps: InlineDep[]): TextEdit[] { + const existing = findBlockForEdge(blocks, operation.edge); + if (existing) return computeUpdateEdit(operation, existing, dbml); + + const newBlock = generateDepBlock(operation.edge, operation.color ?? ''); + const createEdit: TextEdit = { start: dbml.length, end: dbml.length, newText: '\n\n' + newBlock + '\n' }; + + // If the edge is authored inline, strip the inline setting to avoid duplication. + const inline = inlineDeps.find((d) => edgesMatch(d.edge, operation.edge)); + if (inline) { + return [ + { start: inline.fullStart, end: inline.fullEnd, newText: '' }, + createEdit, + ]; + } + + return [ + createEdit, + ]; +} diff --git a/packages/dbml-parse/src/compiler/queries/transform/types.ts b/packages/dbml-parse/src/compiler/queries/transform/types.ts new file mode 100644 index 000000000..227c0c7cc --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/types.ts @@ -0,0 +1,69 @@ +import { SymbolKind } from '@/core/types/symbol'; +import { MetadataKind } from '@/core/types/symbol/metadata'; + +// Identifies any DBML element. `kind` is required to discriminate variants. +// Individual interfaces allow `kind` to be optional for standalone use (e.g. renameTable). +export type ElementIdentifier = ( + SchemaIdentifier + | TableIdentifier + | ColumnIdentifier + | EnumIdentifier + | RefIdentifier + | DepIdentifier + | NoteIdentifier + | TableGroupIdentifier +) & { kind: string }; + +export interface SchemaIdentifier { + kind?: typeof SymbolKind.Schema; + schema: string; +} + +export interface TableIdentifier { + kind?: typeof SymbolKind.Table; + schema?: string; + table: string; +} + +export interface ColumnIdentifier { + kind?: typeof SymbolKind.Column; + schema?: string; + table: string; + column: string; +} + +export interface EnumIdentifier { + kind?: typeof SymbolKind.Enum; + schema?: string; + name: string; +} + +// A table endpoint, optionally narrowed to specific fields. +// Shared by RefIdentifier and DepIdentifier. +export interface EndpointRef { + schemaName?: string | null; + tableName: string; + fieldNames?: string[]; +} + +export interface RefIdentifier { + kind?: typeof MetadataKind.Ref; + endpoints: [EndpointRef, EndpointRef]; +} + +export interface DepIdentifier { + kind?: typeof MetadataKind.Dep; + upstream: EndpointRef; + downstream: EndpointRef; +} + +export interface NoteIdentifier { + kind?: typeof SymbolKind.StickyNote; + name: string; +} + +export interface TableGroupIdentifier { + kind?: typeof SymbolKind.TableGroup; + schema?: string; + name: string; +} diff --git a/packages/dbml-parse/src/compiler/queries/transform/updateElementSetting.ts b/packages/dbml-parse/src/compiler/queries/transform/updateElementSetting.ts new file mode 100644 index 000000000..6c9da8615 --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/updateElementSetting.ts @@ -0,0 +1,153 @@ +import { DEFAULT_SCHEMA_NAME } from '@/constants'; +import { Filepath } from '@/core/types/filepath'; +import { SymbolKind } from '@/core/types/symbol'; +import type Compiler from '../../index'; +import { applyTextEdits, type TextEdit } from './applyTextEdits'; +import { updateSettingEdit } from '@/core/utils/setting'; +import type { ElementIdentifier, DepIdentifier, RefIdentifier } from './types'; +import { + endpointsEqual, endpointMatches, lookupElementSymbol, + formatEndpoint, formatSetting, findRefDefinition, +} from './utils'; +import { depBlocksFromProgram, inlineDepsFromProgram } from './syncDep'; +import { FunctionApplicationNode, type SyntaxNode } from '@/core/types/nodes'; + +export function updateElementSettingEdit ( + this: Compiler, + filepath: Filepath, + target: ElementIdentifier, + settingName: string, + value: string | null | undefined, +): TextEdit[] { + if (target.kind === 'dep') { + return updateDepSettingEdit(this, filepath, target, settingName, value); + } + + if (target.kind === 'ref') { + return updateRefSettingEdit(this, filepath, target, settingName, value); + } + + const declaration = findNamedElementDeclaration(this, filepath, target); + if (!declaration) return []; + + const source = this.getSource(filepath) ?? ''; + const edit = updateSettingEdit(declaration, settingName, value, source); + return edit + ? [ + edit, + ] + : []; +} + +export function updateElementSetting ( + this: Compiler, + filepath: Filepath, + target: ElementIdentifier, + settingName: string, + value: string | null | undefined, +): string { + const source = this.getSource(filepath) ?? ''; + const edits = this.updateElementSettingEdit(filepath, target, settingName, value); + if (edits.length === 0) return source; + return applyTextEdits(source, edits); +} + +function findNamedElementDeclaration (compiler: Compiler, filepath: Filepath, target: ElementIdentifier): SyntaxNode | undefined { + const schema = ('schema' in target ? target.schema : undefined) ?? DEFAULT_SCHEMA_NAME; + + switch (target.kind) { + case SymbolKind.Column: { + const tableSymbol = lookupElementSymbol(compiler, filepath, schema, target.table, SymbolKind.Table); + return tableSymbol ? compiler.lookupMembers(tableSymbol, SymbolKind.Column, target.column)?.declaration : undefined; + } + case SymbolKind.Table: + return lookupElementSymbol(compiler, filepath, schema, target.table)?.declaration; + case SymbolKind.Enum: + case SymbolKind.StickyNote: + case SymbolKind.TableGroup: + return lookupElementSymbol(compiler, filepath, schema, target.name, target.kind)?.declaration; + case SymbolKind.Schema: + return lookupElementSymbol(compiler, filepath, target.schema, '', SymbolKind.Schema)?.declaration; + default: + return undefined; + } +} + +function updateDepSettingEdit (compiler: Compiler, filepath: Filepath, target: DepIdentifier, settingName: string, value: string | null | undefined): TextEdit[] { + const source = compiler.getSource(filepath) ?? ''; + const program = compiler.parseFile(filepath).getValue().ast; + const block = depBlocksFromProgram(program).find((b) => + b.edges.some((e) => endpointsEqual(e.upstream, target.upstream) && endpointsEqual(e.downstream, target.downstream)), + ); + + if (!block) { + if (value === null) return []; + + const inline = inlineDepsFromProgram(source, program).find((d) => + endpointMatches(d.edge.upstream, target.upstream) && endpointMatches(d.edge.downstream, target.downstream), + ); + if (!inline) return []; + + const up = formatEndpoint(target.upstream); + const down = formatEndpoint(target.downstream); + const setting = formatSetting(settingName, value); + + const depBlock = setting + ? `Dep [${setting}] {\n ${up} -> ${down}\n}` + : `Dep {\n ${up} -> ${down}\n}`; + return [ + { start: inline.fullStart, end: inline.fullEnd, newText: '' }, + { start: source.length, end: source.length, newText: '\n\n' + depBlock + '\n' }, + ]; + } + + const { declaration } = block; + const { body } = declaration; + + if (body instanceof FunctionApplicationNode) { + const headerEdit = updateSettingEdit(declaration, settingName, value, source); + if (headerEdit) return [ + headerEdit, + ]; + const edgeEdit = updateSettingEdit(body, settingName, value, source); + if (edgeEdit) return [ + edgeEdit, + ]; + return []; + } + + const edit = updateSettingEdit(declaration, settingName, value, source); + return edit + ? [ + edit, + ] + : []; +} + +function updateRefSettingEdit (compiler: Compiler, filepath: Filepath, target: RefIdentifier, settingName: string, value: string | null | undefined): TextEdit[] { + const source = compiler.getSource(filepath) ?? ''; + const result = findRefDefinition(compiler, filepath, target); + + if (!result) return []; + + if (result.kind === 'inline') { + if (value === null) return []; + const left = formatEndpoint(target.endpoints[0]); + const right = formatEndpoint(target.endpoints[1]); + const setting = formatSetting(settingName, value); + const refLine = setting + ? `Ref: ${left} ${result.op} ${right} [${setting}]` + : `Ref: ${left} ${result.op} ${right}`; + return [ + { start: result.fullStart, end: result.fullEnd, newText: '' }, + { start: source.length, end: source.length, newText: '\n\n' + refLine + '\n' }, + ]; + } + + const edit = updateSettingEdit(result.node, settingName, value, source); + return edit + ? [ + edit, + ] + : []; +} diff --git a/packages/dbml-parse/src/compiler/queries/transform/utils.ts b/packages/dbml-parse/src/compiler/queries/transform/utils.ts deleted file mode 100644 index 315bcedfa..000000000 --- a/packages/dbml-parse/src/compiler/queries/transform/utils.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { DEFAULT_SCHEMA_NAME } from '@/constants'; -import { Filepath } from '@/core/types/filepath'; -import { UNHANDLED } from '@/core/types/module'; -import { NodeSymbol, SymbolKind } from '@/core/types/symbol'; -import type Compiler from '../../index'; -import { splitQualifiedIdentifier } from '../utils'; - -export type TableNameInput = string | { schema?: string; - table: string; }; - -/** - * Normalizes a table name input to { schema, table } format. - * Properly handles quoted identifiers with dots inside. - */ -export function normalizeTableName (input: TableNameInput): { - schema: string; - table: string; -} { - if (typeof input !== 'string') { - return { - schema: input.schema ?? DEFAULT_SCHEMA_NAME, - table: input.table, - }; - } - - const parts = splitQualifiedIdentifier(input); - - if (parts.length === 0) { - return { - schema: DEFAULT_SCHEMA_NAME, - table: '', - }; - } - - if (parts.length === 1) { - return { - schema: DEFAULT_SCHEMA_NAME, - table: parts[0], - }; - } - - if (parts.length === 2) { - return { - schema: parts[0], - table: parts[1], - }; - } - - // More than 2 parts - treat the last as table, rest as schema - const tablePart = parts[parts.length - 1]; - const schemaPart = parts.slice(0, -1).join('.'); - return { - schema: schemaPart, - table: tablePart, - }; -} - -/** - * Looks up a table symbol by matching its full qualified name. - */ -export function lookupTableSymbol ( - compiler: Compiler, - filepath: Filepath, - schema: string, - table: string, -): NodeSymbol | null { - const ast = compiler.parseFile(filepath).getValue().ast; - const astSymbol = compiler.nodeSymbol(ast).getFiltered(UNHANDLED); - if (!astSymbol) return null; - - if (schema === DEFAULT_SCHEMA_NAME) { - const symbol = compiler.lookupMembers( - astSymbol, - SymbolKind.Table, - table, - ); - return symbol ?? null; - } - - const schemaSymbol = compiler.lookupMembers( - astSymbol, - SymbolKind.Schema, - schema, - ); - if (!schemaSymbol) return null; - - const tableSymbol = compiler.lookupMembers( - schemaSymbol, - SymbolKind.Table, - table, - ); - - return tableSymbol ?? null; -} - -/** - * Removes surrounding double quotes from a string if present. - */ -export function stripQuotes (str: string): string { - if (str.startsWith('"') && str.endsWith('"') && str.length >= 2) { - return str.slice(1, -1); - } - return str; -} diff --git a/packages/dbml-parse/src/compiler/queries/transform/utils/index.ts b/packages/dbml-parse/src/compiler/queries/transform/utils/index.ts new file mode 100644 index 000000000..2624c00c4 --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/utils/index.ts @@ -0,0 +1,139 @@ +import { DEFAULT_SCHEMA_NAME } from '@/constants'; +import { Filepath } from '@/core/types/filepath'; +import { UNHANDLED } from '@/core/types/module'; +import { NodeSymbol, SymbolKind } from '@/core/types/symbol'; +import type Compiler from '../../../index'; +import { addDoubleQuoteIfNeeded, splitQualifiedIdentifier } from '../../utils'; +import type { EndpointRef } from '../types'; +export type { ElementIdentifier } from '../types'; +export { findRefDefinition, type InlineRef, type StandaloneRef } from './ref'; + +export function normalizeSchema (schema?: string | null): string { + return schema && schema.length > 0 ? schema : DEFAULT_SCHEMA_NAME; +} + +export function endpointsEqual (left: EndpointRef, right: EndpointRef): boolean { + if (normalizeSchema(left.schemaName) !== normalizeSchema(right.schemaName)) + return false; + if (left.tableName !== right.tableName) return false; + const leftFieldNames = left.fieldNames ?? []; + const rightFieldNames = right.fieldNames ?? []; + if (leftFieldNames.length !== rightFieldNames.length) return false; + return leftFieldNames.every((leftField, i) => leftField === rightFieldNames[i]); +} + +export function endpointMatches ( + candidate: EndpointRef, + target: EndpointRef, +): boolean { + if (normalizeSchema(candidate.schemaName) !== normalizeSchema(target.schemaName)) return false; + if (candidate.tableName !== target.tableName) return false; + const targetFieldNames = target.fieldNames ?? []; + if (targetFieldNames.length === 0) return true; + const candidateFieldNames = candidate.fieldNames ?? []; + if (candidateFieldNames.length !== targetFieldNames.length) return false; + return candidateFieldNames.every((f, i) => f === targetFieldNames[i]); +} + +export function normalizeTableName ( + input: string | { schema?: string; table: string }, +): { + schema: string; + table: string; +} { + if (typeof input !== 'string') { + return { + schema: input.schema ?? DEFAULT_SCHEMA_NAME, + table: input.table, + }; + } + + const parts = splitQualifiedIdentifier(input); + + if (parts.length === 0) { + return { + schema: DEFAULT_SCHEMA_NAME, + table: '', + }; + } + + if (parts.length === 1) { + return { + schema: DEFAULT_SCHEMA_NAME, + table: parts[0], + }; + } + + if (parts.length === 2) { + return { + schema: parts[0], + table: parts[1], + }; + } + + // More than 2 parts - treat the last as table, rest as schema + const tablePart = parts[parts.length - 1]; + const schemaPart = parts.slice(0, -1).join('.'); + return { + schema: schemaPart, + table: tablePart, + }; +} + +/** + * Looks up an element symbol by its qualified name and kind. + */ +export function lookupElementSymbol ( + compiler: Compiler, + filepath: Filepath, + schema: string, + name: string, + kind: SymbolKind = SymbolKind.Table, +): NodeSymbol | undefined { + const ast = compiler.parseFile(filepath).getValue().ast; + const astSymbol = compiler.nodeSymbol(ast).getFiltered(UNHANDLED); + if (!astSymbol) return undefined; + + if (schema === DEFAULT_SCHEMA_NAME) { + return compiler.lookupMembers(astSymbol, kind, name) ?? undefined; + } + + const schemaSymbol = compiler.lookupMembers( + astSymbol, + SymbolKind.Schema, + schema, + ); + if (!schemaSymbol) return undefined; + + return compiler.lookupMembers(schemaSymbol, kind, name) ?? undefined; +} + +/** + * Removes surrounding double quotes from a string if present. + */ +export function stripQuotes (str: string): string { + if (str.startsWith('"') && str.endsWith('"') && str.length >= 2) { + return str.slice(1, -1); + } + return str; +} + +// Serialize endpoint to a nice string +export function formatEndpoint (ep: EndpointRef): string { + const parts: string[] = []; + if (ep.schemaName && ep.schemaName !== DEFAULT_SCHEMA_NAME) + parts.push(addDoubleQuoteIfNeeded(ep.schemaName)); + parts.push(addDoubleQuoteIfNeeded(ep.tableName)); + for (const f of ep.fieldNames ?? []) parts.push(addDoubleQuoteIfNeeded(f)); + return parts.join('.'); +} + +// Serialize setting to a nice string +export function formatSetting ( + name: string, + value: string | null | undefined, +): string { + if (value === undefined) return name; + if (value === null) return ''; + return `${name}: ${value}`; +} diff --git a/packages/dbml-parse/src/compiler/queries/transform/utils/ref.ts b/packages/dbml-parse/src/compiler/queries/transform/utils/ref.ts new file mode 100644 index 000000000..6473253a3 --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/utils/ref.ts @@ -0,0 +1,75 @@ +import { SettingName } from '@/core/types/keywords'; +import { AttributeNode, ElementDeclarationNode, FunctionApplicationNode } from '@/core/types/nodes'; +import { Filepath } from '@/core/types/filepath'; +import { UNHANDLED } from '@/core/types/module'; +import { removeSettingEdit } from '@/core/utils/setting'; +import { getBody } from '@/core/utils/expression'; +import { RefMetadata } from '@/core/types/symbol/metadata'; +import type { TableSymbol, ColumnSymbol } from '@/core/types/symbol'; +import type Compiler from '../../../index'; +import type { EndpointRef, RefIdentifier } from '../types'; + +export interface InlineRef { + kind: 'inline'; + op: string; + fullStart: number; + fullEnd: number; +} + +export interface StandaloneRef { + kind: 'standalone'; + node: FunctionApplicationNode; +} + +export function findRefDefinition (compiler: Compiler, filepath: Filepath, target: RefIdentifier): InlineRef | StandaloneRef | undefined { + const ast = compiler.parseFile(filepath).getValue().ast; + const programSymbol = compiler.nodeSymbol(ast).getFiltered(UNHANDLED); + if (!programSymbol) return undefined; + + const source = compiler.getSource(filepath) ?? ''; + + for (const meta of compiler.symbolMetadata(programSymbol)) { + if (!(meta instanceof RefMetadata)) continue; + if (!refMetadataMatches(compiler, meta, target)) continue; + + if (meta.declaration instanceof AttributeNode) { + const op = meta.op(compiler); + if (!op) continue; + const columnField = meta.declaration.parentOfKind(FunctionApplicationNode); + if (!columnField) continue; + const fullEdit = removeSettingEdit(columnField, SettingName.Ref, source); + if (!fullEdit) continue; + return { kind: 'inline', op, fullStart: fullEdit.start, fullEnd: fullEdit.end }; + } + + if (meta.declaration instanceof ElementDeclarationNode) { + const edgeNode = getBody(meta.declaration)[0]; + if (edgeNode instanceof FunctionApplicationNode) return { kind: 'standalone', node: edgeNode }; + } + } + + return undefined; +} + +function endpointMatches (table: TableSymbol | undefined, columns: ColumnSymbol[], ep: EndpointRef): boolean { + if (!table?.name || table.name !== ep.tableName) return false; + const fields = ep.fieldNames ?? []; + if (fields.length === 0) return true; + if (columns.length !== fields.length) return false; + return columns.every((col, i) => col.name === fields[i]); +} + +function refMetadataMatches (compiler: Compiler, meta: RefMetadata, target: RefIdentifier): boolean { + const leftTable = meta.leftTable(compiler); + const leftColumns = meta.leftColumns(compiler); + const rightTable = meta.rightTable(compiler); + const rightColumns = meta.rightColumns(compiler); + + const [ + ep0, + ep1, + ] = target.endpoints; + + return (endpointMatches(leftTable, leftColumns, ep0) && endpointMatches(rightTable, rightColumns, ep1)) + || (endpointMatches(leftTable, leftColumns, ep1) && endpointMatches(rightTable, rightColumns, ep0)); +} diff --git a/packages/dbml-parse/src/compiler/queries/utils.ts b/packages/dbml-parse/src/compiler/queries/utils.ts index 4e7af5648..5b677ac5e 100644 --- a/packages/dbml-parse/src/compiler/queries/utils.ts +++ b/packages/dbml-parse/src/compiler/queries/utils.ts @@ -57,6 +57,11 @@ export function addDoubleQuoteIfNeeded (identifier: string): string { return `"${escapeString(identifier)}"`; } +// Build a quoted qualified name string from name parts. +export function normalizeQualifiedName (...parts: string[]): string { + return parts.map(addDoubleQuoteIfNeeded).join('.'); +} + /** * Unescapes a string by processing escape sequences. * Handles escaped quotes (\"), common escape sequences, unicode (\uHHHH), and arbitrary escapes. @@ -349,3 +354,14 @@ export function allVisibleMembers (compiler: Compiler): Report { return new Report(members, errors, warnings); } + +/** + * Wraps a note value in the appropriate DBML quote style. + * Uses triple quotes for multiline content, single quotes otherwise. + */ +export function quoteNoteValue (value: string): string { + if (value.includes('\n')) { + return `'''\n${value}\n'''`; + } + return `'${escapeString(value)}'`; +} diff --git a/packages/dbml-parse/src/compiler/types.ts b/packages/dbml-parse/src/compiler/types.ts index eb2270654..fe78ad7f3 100644 --- a/packages/dbml-parse/src/compiler/types.ts +++ b/packages/dbml-parse/src/compiler/types.ts @@ -5,6 +5,7 @@ export const enum ScopeKind { INDEXES, NOTE, REF, + DEP, PROJECT, CUSTOM, TOPLEVEL, diff --git a/packages/dbml-parse/src/core/global_modules/dep/bind.ts b/packages/dbml-parse/src/core/global_modules/dep/bind.ts new file mode 100644 index 000000000..702fc9038 --- /dev/null +++ b/packages/dbml-parse/src/core/global_modules/dep/bind.ts @@ -0,0 +1,129 @@ +import { partition } from 'lodash-es'; +import Compiler from '@/compiler'; +import { CompileError, CompileErrorCode } from '@/core/types/errors'; +import { + BlockExpressionNode, + ElementDeclarationNode, + FunctionApplicationNode, + InfixExpressionNode, + ProgramNode, +} from '@/core/types/nodes'; +import type { SyntaxNode } from '@/core/types/nodes'; +import { SyntaxToken } from '@/core/types/tokens'; +import { ElementKind } from '@/core/types/keywords'; +import { UNHANDLED } from '@/core/types/module'; +import { SymbolKind, type NodeSymbol } from '@/core/types/symbol'; +import { destructureComplexVariableTuple } from '@/core/utils/expression'; +import { scanNonListNodeForBinding } from '../utils'; + +export default class DepBinder { + private compiler: Compiler; + private declarationNode: ElementDeclarationNode & { type: SyntaxToken }; + + constructor (compiler: Compiler, declarationNode: ElementDeclarationNode & { type: SyntaxToken }) { + this.compiler = compiler; + this.declarationNode = declarationNode; + } + + bind (): CompileError[] { + if (!(this.declarationNode.parent instanceof ProgramNode) && !this.declarationNode.parent?.isKind(ElementKind.Project)) { + return []; + } + return this.bindBody(this.declarationNode.body); + } + + private bindBody (body?: FunctionApplicationNode | BlockExpressionNode): CompileError[] { + if (!body) return []; + if (body instanceof FunctionApplicationNode) { + return this.bindFields([ + body, + ]); + } + + const [ + fields, + subs, + ] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + return [ + ...this.bindFields(fields as FunctionApplicationNode[]), + ...this.bindSubElements(subs as ElementDeclarationNode[]), + ]; + } + + private bindFields (fields: FunctionApplicationNode[]): CompileError[] { + return fields.flatMap((field) => { + if (!field.callee) return []; + const args = [ + field.callee, + ...field.args, + ]; + const bindees = args.flatMap(scanNonListNodeForBinding); + + const bindErrors = bindees.flatMap((bindee) => { + const nodes = [ + ...bindee.variables, + ...bindee.tupleElements, + ]; + return nodes.flatMap((b) => this.compiler.nodeReferee(b).getErrors()); + }); + + const selfLoopErrors = this.checkColumnSelfLoop(field.callee); + + return bindErrors.concat(selfLoopErrors); + }); + } + + // Column-level self-ref is not allowed + private checkColumnSelfLoop (callee: SyntaxNode): CompileError[] { + if (!(callee instanceof InfixExpressionNode)) return []; + + if (this.endpointLevel(callee.leftExpression) !== 'column' || this.endpointLevel(callee.rightExpression) !== 'column') return []; + + const leftSymbol = this.endpointSymbol(callee.leftExpression); + const rightSymbol = this.endpointSymbol(callee.rightExpression); + if (leftSymbol && rightSymbol && leftSymbol === rightSymbol) { + return [ + new CompileError( + CompileErrorCode.DEP_SELF_LOOP, + 'A column cannot depend on itself', + callee, + ), + ]; + } + + return []; + } + + private endpointSymbol (expr: SyntaxNode | undefined): NodeSymbol | undefined { + if (!expr) return undefined; + const fragments = destructureComplexVariableTuple(expr); + if (!fragments) return undefined; + const lastVar = fragments.variables.at(-1); + if (!lastVar) return undefined; + return this.compiler.nodeReferee(lastVar).getFiltered(UNHANDLED); + } + + private endpointLevel (expr: SyntaxNode | undefined): 'table' | 'column' | undefined { + if (!expr) return undefined; + const fragments = destructureComplexVariableTuple(expr); + if (!fragments) return undefined; + + if (fragments.tupleElements.length > 0) return 'column'; + + const lastVar = fragments.variables.at(-1); + if (!lastVar) return undefined; + + const symbol = this.compiler.nodeReferee(lastVar).getFiltered(UNHANDLED); + if (symbol?.isKind(SymbolKind.Column)) return 'column'; + if (symbol?.isKind(SymbolKind.Table)) return 'table'; + + return undefined; + } + + private bindSubElements (subs: ElementDeclarationNode[]): CompileError[] { + return subs.flatMap((sub) => { + if (!sub.type) return []; + return this.compiler.bindNode(sub).getErrors(); + }); + } +} diff --git a/packages/dbml-parse/src/core/global_modules/dep/index.ts b/packages/dbml-parse/src/core/global_modules/dep/index.ts new file mode 100644 index 000000000..284d14713 --- /dev/null +++ b/packages/dbml-parse/src/core/global_modules/dep/index.ts @@ -0,0 +1,159 @@ +import type Compiler from '@/compiler/index'; +import { CompileError, CompileErrorCode } from '@/core/types/errors'; +import type { Filepath } from '@/core/types/filepath'; +import { ElementKind, SettingName } from '@/core/types/keywords'; +import { DepMetadata } from '@/core/types/symbol/metadata'; +import type { NodeMetadata } from '@/core/types/symbol/metadata'; +import { PASS_THROUGH, type PassThrough, UNHANDLED } from '@/core/types/module'; +import { + AttributeNode, + ElementDeclarationNode, + FunctionApplicationNode, + IdentifierStreamNode, + InfixExpressionNode, +} from '@/core/types/nodes'; +import type { SyntaxNode } from '@/core/types/nodes'; +import Report from '@/core/types/report'; +import type { SchemaElement } from '@/core/types/schemaJson'; +import { SymbolKind, type NodeSymbol } from '@/core/types/symbol'; +import type { SyntaxToken } from '@/core/types/tokens'; +import { extractStringFromIdentifierStream, extractVarNameFromPrimaryVariable, getBody } from '@/core/utils/expression'; +import { isAccessExpression, isElementNode, isExpressionAVariableNode } from '@/core/utils/validate'; +import { getDefaultSchemaSymbol } from '../ref'; +import { nodeRefereeOfLeftExpression } from '../utils'; +import type { GlobalModule } from '../types'; +import DepBinder from './bind'; +import { DepInterpreter } from './interpret'; + +function isInsideDepBody (node: SyntaxNode): boolean { + let current: SyntaxNode | undefined = node.parent; + while (current) { + if (current instanceof ElementDeclarationNode && current.isKind(ElementKind.Dep)) { + return current.body?.containsEq(node) ?? false; + } + current = current.parent; + } + return false; +} + +export const depModule: GlobalModule = { + nodeReferee (compiler: Compiler, node: SyntaxNode): Report | Report { + if (!isExpressionAVariableNode(node) && !isAccessExpression(node)) return Report.create(PASS_THROUGH); + if (!isInsideDepBody(node)) return Report.create(PASS_THROUGH); + if (node.parentOfKind(AttributeNode)) return Report.create(PASS_THROUGH); + + const programNode = compiler.parseFile(node.filepath).getValue().ast; + const globalSymbol = compiler.nodeSymbol(programNode).getValue(); + if (globalSymbol === UNHANDLED) return Report.create(undefined); + + return nodeRefereeOfDepEndpoint(compiler, globalSymbol, node); + }, + + nodeMetadata (compiler: Compiler, node: SyntaxNode): Report | Report { + if (isElementNode(node, ElementKind.Dep)) { + const fields = getBody(node); + if (fields.length === 0) return Report.create(PASS_THROUGH); + const hasAnyEdge = fields.some((f) => f instanceof FunctionApplicationNode && f.callee instanceof InfixExpressionNode); + if (!hasAnyEdge) return Report.create(PASS_THROUGH); + return Report.create(new DepMetadata(node)); + } + + if (node instanceof AttributeNode) { + if (!(node.name instanceof IdentifierStreamNode)) return Report.create(PASS_THROUGH); + const name = extractStringFromIdentifierStream(node.name)?.toLowerCase(); + if (name !== SettingName.Dep) return Report.create(PASS_THROUGH); + return Report.create(new DepMetadata(node)); + } + + return Report.create(PASS_THROUGH); + }, + + bindNode (compiler: Compiler, node: SyntaxNode): Report | Report { + if (!isElementNode(node, ElementKind.Dep)) return Report.create(PASS_THROUGH); + + return Report.create( + undefined, + new DepBinder(compiler, node as ElementDeclarationNode & { type: SyntaxToken }).bind(), + ); + }, + + interpretMetadata (compiler: Compiler, metadata: NodeMetadata, filepath: Filepath): Report | Report { + if (metadata instanceof DepMetadata) { + return new DepInterpreter(compiler, metadata, filepath).interpret(); + } + return Report.create(PASS_THROUGH); + }, +}; + +function nodeRefereeOfDepEndpoint (compiler: Compiler, globalSymbol: NodeSymbol, node: SyntaxNode): Report { + if (!isExpressionAVariableNode(node)) return new Report(undefined); + const name = extractVarNameFromPrimaryVariable(node) ?? ''; + + // Standalone variable (no access parent): resolve as Table + if (!isAccessExpression(node.parentNode)) { + const defaultSchema = getDefaultSchemaSymbol(compiler, globalSymbol); + if (defaultSchema) { + const symbol = compiler.lookupMembers(defaultSchema, SymbolKind.Table, name); + if (symbol) return Report.create(symbol); + } + const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Table, name); + if (symbol) return Report.create(symbol); + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Table '${name}' does not exist in Schema 'public'`, node), + ]); + } + + const parent = node.parentNode as InfixExpressionNode; + + // Rightmost: try resolve as Table or Column based on what the left resolved as + if (parent.rightExpression === node) { + const left = nodeRefereeOfLeftExpression(compiler, node); + if (left?.isKind(SymbolKind.Schema)) { + const symbol = compiler.lookupMembers(left, SymbolKind.Table, name); + if (symbol) return Report.create(symbol); + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Table '${name}' does not exist in Schema '${left.name}'`, node), + ]); + } + if (left?.isKind(SymbolKind.Table)) { + const symbol = compiler.lookupMembers(left, SymbolKind.Column, name); + if (symbol) return Report.create(symbol); + const fullname = left.declaration + ? compiler.nodeFullname(left.declaration).getFiltered(UNHANDLED)?.join('.') ?? left.name + : left.name; + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Column '${name}' does not exist in Table '${fullname}'`, node), + ]); + } + return new Report(undefined); + } + + // Leftmost: resolve as Table or Schema + if (parent.leftExpression === node) { + // Deeply nested leftmost (a in a.b.c): must be Schema + if (isAccessExpression(parent.parentNode) && (parent.parentNode as InfixExpressionNode).leftExpression === parent) { + const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Schema, name); + if (symbol) return Report.create(symbol); + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Schema '${name}' does not exist`, node), + ]); + } + // Non-nested leftmost (a in a.b): try Schema first, then Table + const schemaSymbol = compiler.lookupMembers(globalSymbol, SymbolKind.Schema, name); + if (schemaSymbol) return Report.create(schemaSymbol); + + const defaultSchema = getDefaultSchemaSymbol(compiler, globalSymbol); + if (defaultSchema) { + const tableSymbol = compiler.lookupMembers(defaultSchema, SymbolKind.Table, name); + if (tableSymbol) return Report.create(tableSymbol); + } + const tableSymbol = compiler.lookupMembers(globalSymbol, SymbolKind.Table, name); + if (tableSymbol) return Report.create(tableSymbol); + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Schema or Table '${name}' does not exist`, node), + ]); + } + + return new Report(undefined); +} diff --git a/packages/dbml-parse/src/core/global_modules/dep/interpret.ts b/packages/dbml-parse/src/core/global_modules/dep/interpret.ts new file mode 100644 index 000000000..42a529246 --- /dev/null +++ b/packages/dbml-parse/src/core/global_modules/dep/interpret.ts @@ -0,0 +1,207 @@ +import { + destructureComplexVariable, + extractNumericLiteral, + extractQuotedStringToken, + extractStringFromIdentifierStream, + extractVariableFromExpression, +} from '@/core/utils/expression'; +import { aggregateSettingList } from '@/core/utils/validate'; +import { CompileError, CompileErrorCode } from '@/core/types/errors'; +import { DEFAULT_SCHEMA_NAME } from '@/constants'; +import { ElementKind, SettingName } from '@/core/types/keywords'; +import { + AttributeNode, + BlockExpressionNode, + ElementDeclarationNode, + FunctionApplicationNode, + IdentifierStreamNode, + ListExpressionNode, +} from '@/core/types/nodes'; +import type { Dep, DepEdge } from '@/core/types/schemaJson'; +import { DepMetadata, type Filepath } from '@/core/types'; +import type Compiler from '@/compiler'; +import { extractColor, getTokenPosition, normalizeNote } from '@/core/utils/interpret'; +import Report from '@/core/types/report'; + +export class DepInterpreter { + private compiler: Compiler; + private metadata: DepMetadata; + private declarationNode: ElementDeclarationNode | AttributeNode; + private filepath: Filepath; + private dep: Partial; + + constructor (compiler: Compiler, metadata: DepMetadata, filepath: Filepath) { + this.compiler = compiler; + this.filepath = filepath; + this.metadata = metadata; + this.declarationNode = metadata.declaration; + this.dep = {}; + } + + interpret (): Report { + this.dep.token = getTokenPosition(this.declarationNode); + const errors = [ + ...this.interpretName(), + ...this.interpretEdges(), + ...this.interpretSettings(), + ]; + return Report.create(this.dep as Dep, errors); + } + + private interpretName (): CompileError[] { + if (!(this.declarationNode instanceof ElementDeclarationNode)) { + this.dep.name = null; + this.dep.schemaName = null; + return []; + } + const errors: CompileError[] = []; + const fragments = this.declarationNode.name ? destructureComplexVariable(this.declarationNode.name) ?? [] : []; + this.dep.name = fragments.pop() || null; + if (fragments.length > 1) { + errors.push(new CompileError(CompileErrorCode.UNSUPPORTED, 'Nested schema is not supported', this.declarationNode.name!)); + } + this.dep.schemaName = fragments.join('.') || null; + return errors; + } + + private interpretEdges (): CompileError[] { + const upstreamColsList = this.metadata.upstreamColumns(this.compiler); + const downstreamColsList = this.metadata.downstreamColumns(this.compiler); + const upstreamTables = this.metadata.upstreamTables(this.compiler); + const downstreamTables = this.metadata.downstreamTables(this.compiler); + + const edges: DepEdge[] = []; + const errors: CompileError[] = []; + const count = Math.max(upstreamTables.length, downstreamTables.length); + for (let i = 0; i < count; i++) { + const upTable = upstreamTables[i]; + const downTable = downstreamTables[i]; + const upColumns = upstreamColsList[i] ?? []; + const downColumns = downstreamColsList[i] ?? []; + + const upTableName = upTable?.interpretedName(this.compiler, this.filepath); + const downTableName = downTable?.interpretedName(this.compiler, this.filepath); + + const upSchema = upTableName?.schema ?? null; + const downSchema = downTableName?.schema ?? null; + + edges.push({ + upstream: { + schemaName: upTableName?.schema ?? null, + tableName: upTableName?.name ?? '', + fieldNames: upColumns.map((c) => c.name ?? ''), + token: this.dep.token!, + }, + downstream: { + schemaName: downTableName?.schema ?? null, + tableName: downTableName?.name ?? '', + fieldNames: downColumns.map((c) => c.name ?? ''), + token: this.dep.token!, + }, + token: this.dep.token!, + }); + } + this.dep.edges = edges; + return errors; + } + + private interpretSettings (): CompileError[] { + const metadata: Record = {}; + + if (this.declarationNode instanceof ElementDeclarationNode) { + if (this.declarationNode.attributeList) { + this.consumeSettings(this.declarationNode.attributeList, metadata); + } + + const body = this.declarationNode.body; + if (body) { + const fields = body instanceof FunctionApplicationNode + ? [ + body, + ] + : body.body.filter((e): e is FunctionApplicationNode => e instanceof FunctionApplicationNode); + for (const field of fields) { + const settingsList = field.args.find((arg): arg is ListExpressionNode => arg instanceof ListExpressionNode); + if (settingsList) { + this.consumeSettings(settingsList, metadata); + } + } + + if (!(body instanceof FunctionApplicationNode)) { + const subs = body.body.filter((e): e is ElementDeclarationNode => e instanceof ElementDeclarationNode); + for (const sub of subs) { + const key = sub.type?.value?.toLowerCase(); + if (!key) continue; + const subBody = sub.body; + // Note { 'text' } block form - extract from the inner FunctionApplicationNode + if (sub.isKind(ElementKind.Note) && subBody instanceof BlockExpressionNode) { + const inner = subBody.body[0]; + if (inner instanceof FunctionApplicationNode && inner.callee) { + const rawValue = this.extractAttrValue(inner.callee); + if (rawValue !== undefined) { + this.dep.note = { value: normalizeNote(String(rawValue)), token: getTokenPosition(sub) }; + } + } + continue; + } + if (!(subBody instanceof FunctionApplicationNode)) continue; + if (key === SettingName.Color) { + const color = extractColor(subBody.callee); + if (color !== undefined) this.dep.color = color; + continue; + } + const rawValue = this.extractAttrValue(subBody.callee); + if (rawValue === undefined) continue; + if (sub.isKind(ElementKind.Note)) { + this.dep.note = { value: normalizeNote(String(rawValue)), token: getTokenPosition(sub) }; + } else { + metadata[key] = rawValue; + } + } + } + } + } + + if (Object.keys(metadata).length > 0) { + this.dep.metadata = metadata; + } + return []; + } + + private consumeSettings (settings: ListExpressionNode, metadata: Record): void { + const settingMap = aggregateSettingList(settings).getValue(); + for (const [ + name, + attrs, + ] of Object.entries(settingMap)) { + const attr = attrs[0]; + if (!attr) continue; + + if (name === SettingName.Color) { + const color = extractColor(attr.value); + if (color !== undefined) this.dep.color = color; + continue; + } + + const rawValue = this.extractAttrValue(attr.value); + if (rawValue === undefined) continue; + + if (name === SettingName.Note) { + this.dep.note = { value: normalizeNote(String(rawValue)), token: getTokenPosition(attr) }; + } else { + metadata[name] = rawValue; + } + } + } + + private extractAttrValue (value: unknown): string | number | null | undefined { + if (value instanceof IdentifierStreamNode) { + return extractStringFromIdentifierStream(value); + } + const numericValue = extractNumericLiteral(value as any); + if (numericValue !== null) return numericValue; + const stringValue = extractQuotedStringToken(value as any); + if (stringValue !== undefined) return stringValue; + return extractVariableFromExpression(value as any); + } +} diff --git a/packages/dbml-parse/src/core/global_modules/index.ts b/packages/dbml-parse/src/core/global_modules/index.ts index 893cca485..6cc1a8d6f 100644 --- a/packages/dbml-parse/src/core/global_modules/index.ts +++ b/packages/dbml-parse/src/core/global_modules/index.ts @@ -9,6 +9,7 @@ import type { NodeMetadata } from '@/core/types/symbol/metadata'; import type { SchemaElement } from '@/core/types/schemaJson'; import type { NodeSymbol } from '@/core/types/symbol'; import { checksModule } from './checks'; +import { depModule } from './dep'; import { diagramViewModule } from './diagramView'; import { enumModule } from './enum'; import { indexesModule } from './indexes'; @@ -33,6 +34,7 @@ export const modules: GlobalModule[] = [ indexesModule, checksModule, refModule, + depModule, projectModule, tableGroupModule, tablePartialModule, diff --git a/packages/dbml-parse/src/core/global_modules/program/interpret.ts b/packages/dbml-parse/src/core/global_modules/program/interpret.ts index 7f6f0ab04..d20dd8416 100644 --- a/packages/dbml-parse/src/core/global_modules/program/interpret.ts +++ b/packages/dbml-parse/src/core/global_modules/program/interpret.ts @@ -6,7 +6,7 @@ import { UNHANDLED } from '@/core/types/module'; import { ProgramNode } from '@/core/types/nodes'; import Report from '@/core/types/report'; import type { - Alias, Database, DiagramView, Enum, Note, Project, Ref, RefEndpoint, SchemaElement, Table, TableGroup, TablePartial, TableRecord, + Alias, Database, Dep, DiagramView, Enum, Note, Project, Ref, SchemaElement, Table, TableGroup, TablePartial, TableRecord, } from '@/core/types/schemaJson'; import { AliasKind } from '@/core/types/schemaJson'; import { @@ -16,20 +16,12 @@ import { SchemaSymbol, SymbolKind, } from '@/core/types/symbol'; -import { MetadataKind, PartialRefMetadata, RecordsMetadata } from '@/core/types/symbol/metadata'; +import { MetadataKind, RecordsMetadata, DepMetadata } from '@/core/types/symbol/metadata'; import { TableSymbol } from '@/core/types/symbol'; -import type { InternedNodeSymbol } from '@/core/types/symbol/symbols'; -import { - InjectedColumnSymbol, - TablePartialSymbol, - UseSymbol, -} from '@/core/types/symbol/symbols'; -import { pushExternal } from './utils'; +import { UseSymbol } from '@/core/types/symbol/symbols'; +import { pushExternal, validateRecords, validateDepBlocks } from './utils'; import type { ElementRef } from '@/core/types/schemaJson'; -import { validateForeignKeys, validatePrimaryKey, validateUnique } from '../records/utils/constraints'; -import type { TableInfo } from '../records/utils/constraints/fk'; import { getTokenPosition } from '@/core/utils/interpret'; -import { getMultiplicities } from '../utils'; export default class ProgramInterpreter { private compiler: Compiler; @@ -50,6 +42,7 @@ export default class ProgramInterpreter { tables: [], notes: [], refs: [], + deps: [], enums: [], tableGroups: [], aliases: [], @@ -71,7 +64,7 @@ export default class ProgramInterpreter { this.interpretAllSymbols(); this.interpretAllMetadata(); this.interpretAllAliases(); - this.warnings.push(...this.validateRecords()); + this.warnings.push(...validateRecords(this.compiler, this.programSymbol, this.db.refs, this.filepath)); return new Report(this.db, this.errors, this.warnings); } @@ -157,6 +150,7 @@ export default class ProgramInterpreter { private interpretAllMetadata () { const metadatas = this.compiler.symbolMetadata(this.programSymbol) ?? []; const seenRefEndpoints = new Set(); + const seenDepEndpoints = new Set(); // Pre-scan: count records blocks per table to detect duplicates const recordsTableCount = new Map b` nodes, index-aligned with dep.edges, for a precise error location. + const edgeNodes = meta instanceof DepMetadata ? meta.edgeExpressions() : []; + // Directed src-target uniqueness: a -> b and b -> a are distinct, so no reverse key. + let duplicateEdgeIndex = -1; + (dep.edges ?? []).some((edge, i) => { + const { upstream: up, downstream: down } = edge; + const key = [ + up.schemaName, + up.tableName, + up.fieldNames.join(','), + down.schemaName, + down.tableName, + down.fieldNames.join(','), + ].join('|'); + if (seenDepEndpoints.has(key)) { + duplicateEdgeIndex = i; + return true; + } + seenDepEndpoints.add(key); + return false; + }); + if (duplicateEdgeIndex >= 0) { + // Point at the duplicate `a -> b` line; fall back to the whole declaration (inline form). + const errorNode = edgeNodes[duplicateEdgeIndex] ?? meta.declaration; + this.errors.push(new CompileError(CompileErrorCode.SAME_ENDPOINT, 'Dep with same endpoints already exists', errorNode)); + break; + } + const depErrors = validateDepBlocks(dep, meta); + if (depErrors.length > 0) { + this.errors.push(...depErrors); + break; + } + this.db.deps.push(dep); + break; + } case MetadataKind.Records: { if (meta instanceof RecordsMetadata) { const tableSymbol = meta.table(this.compiler); @@ -261,130 +292,6 @@ export default class ProgramInterpreter { } } - private validateRecords (): CompileWarning[] { - const warnings: CompileWarning[] = []; - const fkTableMap = new Map(); - - // Seed fkTableMap with ALL table symbols (record = undefined) - const schemas = this.compiler.symbolMembers(this.programSymbol).getFiltered(UNHANDLED) ?? []; - for (const schema of schemas) { - if (!(schema instanceof SchemaSymbol)) continue; - const members = this.compiler.symbolMembers(schema).getFiltered(UNHANDLED) ?? []; - for (const member of members) { - if (!member.isKind(SymbolKind.Table)) continue; - const original = member.originalSymbol; - if (!(original instanceof TableSymbol)) continue; - const key = original.intern(); - if (!fkTableMap.has(key)) { - fkTableMap.set(key, { - tableSymbol: original, - record: undefined, - recordBlock: original.declaration, - }); - } - } - } - - // Fill in records and run PK/unique validation - const metadatas = this.compiler.symbolMetadata(this.programSymbol); - for (const meta of metadatas) { - if (!(meta instanceof RecordsMetadata)) continue; - const tableSymbol = meta.table(this.compiler); - if (!(tableSymbol instanceof TableSymbol)) continue; - - const result = this.compiler.interpretMetadata(meta, this.filepath); - if (result.hasValue(UNHANDLED)) continue; - const record = result.getValue() as TableRecord | undefined; - if (!record) continue; - - warnings.push(...validatePrimaryKey(this.compiler, tableSymbol, meta.declaration, record)); - warnings.push(...validateUnique(this.compiler, tableSymbol, record)); - - const key = tableSymbol.originalSymbol.intern(); - const entry = fkTableMap.get(key); - if (entry) entry.record = record; - else fkTableMap.set(key, { - tableSymbol, - record, - recordBlock: meta.declaration, - }); - } - - const partialRefs = this.collectPartialRefs(fkTableMap); - warnings.push(...validateForeignKeys(this.compiler, [ - ...this.db.refs, - ...partialRefs, - ], fkTableMap, this.filepath)); - return warnings; - } - - private collectPartialRefs (fkTableMap: Map): Ref[] { - const partialMetas = this.compiler.symbolMetadata(this.programSymbol) - .filter((m): m is PartialRefMetadata => m instanceof PartialRefMetadata); - - const refs: Ref[] = []; - for (const { - tableSymbol, - } of fkTableMap.values()) { - for (const partialSymbol of tableSymbol.resolvedPartials(this.compiler)) { - for (const meta of partialMetas) { - const container = meta.leftTablePartial(this.compiler); - if (container?.originalSymbol !== partialSymbol.originalSymbol) continue; - - const leftColumns = meta.leftColumns(this.compiler); - const rightTableOrPartial = meta.rightTable(this.compiler); - const rightColumns = meta.rightColumns(this.compiler); - const op = meta.op(this.compiler); - if (!rightTableOrPartial || !op || leftColumns.length === 0 || rightColumns.length === 0) continue; - - // Skip if the column from the partial was not actually injected into this table - // (e.g., overridden by a column defined earlier in the table) - const mergedCols = tableSymbol.mergedColumns(this.compiler); - const anyInjected = leftColumns.some((leftColumn) => - mergedCols.some((mergedColumns) => mergedColumns instanceof InjectedColumnSymbol && mergedColumns.declaration === leftColumn.declaration), - ); - if (!anyInjected) continue; - - const multiplicities = getMultiplicities(op); - if (!multiplicities) continue; - - // When rightTable is the partial itself (inline self-reference with bare column and no table prefix), - // resolve it to the concrete table being expanded. - const rightTable = rightTableOrPartial instanceof TablePartialSymbol - && rightTableOrPartial.originalSymbol === partialSymbol.originalSymbol - ? tableSymbol - : rightTableOrPartial; - - const leftName = tableSymbol.interpretedName(this.compiler, this.filepath); - const rightName = rightTable.interpretedName(this.compiler, this.filepath); - - const ep1: RefEndpoint = { - schemaName: leftName.schema, - tableName: leftName.name, - fieldNames: leftColumns.map((c) => c.name ?? ''), - relation: multiplicities[0], - token: getTokenPosition(meta.leftToken()), - }; - const ep2: RefEndpoint = { - schemaName: rightName.schema, - tableName: rightName.name, - fieldNames: rightColumns.map((c) => c.name ?? ''), - relation: multiplicities[1], - token: getTokenPosition(meta.rightToken()), - }; - refs.push({ - token: getTokenPosition(meta.declaration), - endpoints: [ - ep1, - ep2, - ], - } as Ref); - } - } - } - return refs; - } - private pushElement (symbol: NodeSymbol, value: SchemaElement | SchemaElement[]) { if (Array.isArray(value)) return; switch (symbol.kind) { diff --git a/packages/dbml-parse/src/core/global_modules/program/utils/dep.ts b/packages/dbml-parse/src/core/global_modules/program/utils/dep.ts new file mode 100644 index 000000000..8b4fde563 --- /dev/null +++ b/packages/dbml-parse/src/core/global_modules/program/utils/dep.ts @@ -0,0 +1,58 @@ +import { CompileError, CompileErrorCode } from '@/core/types/errors'; +import type { Dep } from '@/core/types/schemaJson'; +import { BlockExpressionNode, ElementDeclarationNode } from '@/core/types/nodes'; +import { DepMetadata } from '@/core/types/symbol/metadata'; +import type { NodeMetadata } from '@/core/types/symbol/metadata'; +import { makeTableKey } from '../../records/utils/constraints/helper'; + +function isComplexDep (meta: NodeMetadata): boolean { + return meta instanceof DepMetadata + && meta.declaration instanceof ElementDeclarationNode + && meta.declaration.body instanceof BlockExpressionNode; +} + +// A complex Dep block must have all edges targeting the same downstream table. +export function validateDepBlocks ( + dep: Dep, + meta: NodeMetadata, +): CompileError[] { + const edges = dep.edges ?? []; + if (edges.length === 0) return []; + + if (!isComplexDep(meta)) return []; + + const errors: CompileError[] = []; + + // All edges in a complex dep block must share the same downstream table + const firstDownstream = makeTableKey(edges[0].downstream.schemaName, edges[0].downstream.tableName); + + for (let i = 1; i < edges.length; i++) { + const curDownstream = makeTableKey(edges[i].downstream.schemaName, edges[i].downstream.tableName); + + if (curDownstream !== firstDownstream) { + errors.push(new CompileError( + CompileErrorCode.DEP_MIXED_DOWNSTREAM_TABLES, + `All edges in a Dep block must target the same downstream table, but found '${firstDownstream}' and '${curDownstream}'`, + meta.declaration, + )); + break; + } + } + + // All edges in a block must be at the same level (all table-level or all column-level) + const isColumnLevel = (e: typeof edges[0]) => e.upstream.fieldNames.length > 0 || e.downstream.fieldNames.length > 0; + const firstLevel = isColumnLevel(edges[0]); + + for (let i = 1; i < edges.length; i++) { + if (isColumnLevel(edges[i]) !== firstLevel) { + errors.push(new CompileError( + CompileErrorCode.DEP_MIXED_LEVEL, + 'All edges in a Dep block must be at the same level (all table-level or all column-level)', + meta.declaration, + )); + break; + } + } + + return errors; +} diff --git a/packages/dbml-parse/src/core/global_modules/program/utils.ts b/packages/dbml-parse/src/core/global_modules/program/utils/external.ts similarity index 100% rename from packages/dbml-parse/src/core/global_modules/program/utils.ts rename to packages/dbml-parse/src/core/global_modules/program/utils/external.ts diff --git a/packages/dbml-parse/src/core/global_modules/program/utils/index.ts b/packages/dbml-parse/src/core/global_modules/program/utils/index.ts new file mode 100644 index 000000000..50fb5c727 --- /dev/null +++ b/packages/dbml-parse/src/core/global_modules/program/utils/index.ts @@ -0,0 +1,3 @@ +export { pushExternal } from './external'; +export { validateRecords } from './records'; +export { validateDepBlocks } from './dep'; diff --git a/packages/dbml-parse/src/core/global_modules/program/utils/records.ts b/packages/dbml-parse/src/core/global_modules/program/utils/records.ts new file mode 100644 index 000000000..1c76f9b63 --- /dev/null +++ b/packages/dbml-parse/src/core/global_modules/program/utils/records.ts @@ -0,0 +1,149 @@ +import type Compiler from '@/compiler/index'; +import type { CompileWarning } from '@/core/types/errors'; +import type { Filepath } from '@/core/types/filepath'; +import { UNHANDLED } from '@/core/types/module'; +import type { Ref, RefEndpoint, TableRecord } from '@/core/types/schemaJson'; +import { + ProgramSymbol, + SchemaSymbol, + SymbolKind, + TableSymbol, +} from '@/core/types/symbol'; +import type { InternedNodeSymbol } from '@/core/types/symbol/symbols'; +import { InjectedColumnSymbol, TablePartialSymbol } from '@/core/types/symbol/symbols'; +import { PartialRefMetadata, RecordsMetadata } from '@/core/types/symbol/metadata'; +import { validateForeignKeys, validatePrimaryKey, validateUnique } from '../../records/utils/constraints'; +import type { TableInfo } from '../../records/utils/constraints/fk'; +import { getTokenPosition } from '@/core/utils/interpret'; +import { getMultiplicities } from '../../utils'; + +export function validateRecords ( + compiler: Compiler, + programSymbol: ProgramSymbol, + refs: Ref[], + filepath: Filepath, +): CompileWarning[] { + const warnings: CompileWarning[] = []; + const fkTableMap = new Map(); + + // Seed fkTableMap with ALL table symbols + const schemas = compiler.symbolMembers(programSymbol).getFiltered(UNHANDLED) ?? []; + for (const schema of schemas) { + if (!(schema instanceof SchemaSymbol)) continue; + const members = compiler.symbolMembers(schema).getFiltered(UNHANDLED) ?? []; + for (const member of members) { + if (!member.isKind(SymbolKind.Table)) continue; + const original = member.originalSymbol; + if (!(original instanceof TableSymbol)) continue; + const key = original.intern(); + if (!fkTableMap.has(key)) { + fkTableMap.set(key, { + tableSymbol: original, + record: undefined, + recordBlock: original.declaration, + }); + } + } + } + + // Fill in records and run PK/unique validation + const metadatas = compiler.symbolMetadata(programSymbol); + for (const meta of metadatas) { + if (!(meta instanceof RecordsMetadata)) continue; + const tableSymbol = meta.table(compiler); + if (!(tableSymbol instanceof TableSymbol)) continue; + + const result = compiler.interpretMetadata(meta, filepath); + if (result.hasValue(UNHANDLED)) continue; + const record = result.getValue() as TableRecord | undefined; + if (!record) continue; + + warnings.push(...validatePrimaryKey(compiler, tableSymbol, meta.declaration, record)); + warnings.push(...validateUnique(compiler, tableSymbol, record)); + + const key = tableSymbol.originalSymbol.intern(); + const entry = fkTableMap.get(key); + if (entry) entry.record = record; + else fkTableMap.set(key, { + tableSymbol, + record, + recordBlock: meta.declaration, + }); + } + + const partialRefs = collectPartialRefs(compiler, programSymbol, fkTableMap, filepath); + warnings.push(...validateForeignKeys(compiler, [ + ...refs, + ...partialRefs, + ], fkTableMap, filepath)); + return warnings; +} + +function collectPartialRefs ( + compiler: Compiler, + programSymbol: ProgramSymbol, + fkTableMap: Map, + filepath: Filepath, +): Ref[] { + const partialMetas = compiler.symbolMetadata(programSymbol) + .filter((m): m is PartialRefMetadata => m instanceof PartialRefMetadata); + + const refs: Ref[] = []; + for (const { tableSymbol } of fkTableMap.values()) { + for (const partialSymbol of tableSymbol.resolvedPartials(compiler)) { + for (const meta of partialMetas) { + const container = meta.leftTablePartial(compiler); + if (container?.originalSymbol !== partialSymbol.originalSymbol) continue; + + const leftColumns = meta.leftColumns(compiler); + const rightTableOrPartial = meta.rightTable(compiler); + const rightColumns = meta.rightColumns(compiler); + const op = meta.op(compiler); + if (!rightTableOrPartial || !op || leftColumns.length === 0 || rightColumns.length === 0) continue; + + // Skip if the column from the partial was not actually injected into this table + const mergedCols = tableSymbol.mergedColumns(compiler); + const anyInjected = leftColumns.some((leftColumn) => + mergedCols.some((mergedColumns) => mergedColumns instanceof InjectedColumnSymbol && mergedColumns.declaration === leftColumn.declaration), + ); + if (!anyInjected) continue; + + const multiplicities = getMultiplicities(op); + if (!multiplicities) continue; + + // When rightTable is the partial itself (inline self-reference), + // resolve it to the concrete table being expanded. + const rightTable = rightTableOrPartial instanceof TablePartialSymbol + && rightTableOrPartial.originalSymbol === partialSymbol.originalSymbol + ? tableSymbol + : rightTableOrPartial; + + const leftName = tableSymbol.interpretedName(compiler, filepath); + const rightName = rightTable.interpretedName(compiler, filepath); + + const ep1: RefEndpoint = { + schemaName: leftName.schema, + tableName: leftName.name, + fieldNames: leftColumns.map((c) => c.name ?? ''), + relation: multiplicities[0], + token: getTokenPosition(meta.leftToken()), + }; + const ep2: RefEndpoint = { + schemaName: rightName.schema, + tableName: rightName.name, + fieldNames: rightColumns.map((c) => c.name ?? ''), + relation: multiplicities[1], + token: getTokenPosition(meta.rightToken()), + }; + refs.push({ + token: getTokenPosition(meta.declaration), + endpoints: [ + ep1, + ep2, + ], + } as Ref); + } + } + } + return refs; +} diff --git a/packages/dbml-parse/src/core/global_modules/ref/index.ts b/packages/dbml-parse/src/core/global_modules/ref/index.ts index 00ccd12e3..56518c3cf 100644 --- a/packages/dbml-parse/src/core/global_modules/ref/index.ts +++ b/packages/dbml-parse/src/core/global_modules/ref/index.ts @@ -106,7 +106,7 @@ export const refModule: GlobalModule = { }, }; -function getDefaultSchemaSymbol (compiler: Compiler, globalSymbol: NodeSymbol): NodeSymbol | undefined { +export function getDefaultSchemaSymbol (compiler: Compiler, globalSymbol: NodeSymbol): NodeSymbol | undefined { const membersList = compiler.symbolMembers(globalSymbol).getFiltered(UNHANDLED); if (!membersList) return undefined; diff --git a/packages/dbml-parse/src/core/global_modules/table/bind.ts b/packages/dbml-parse/src/core/global_modules/table/bind.ts index 128c1427a..f5ed32473 100644 --- a/packages/dbml-parse/src/core/global_modules/table/bind.ts +++ b/packages/dbml-parse/src/core/global_modules/table/bind.ts @@ -25,11 +25,30 @@ export default class TableBinder { } bind (): CompileError[] { - if (!(this.declarationNode.body instanceof BlockExpressionNode)) { - return []; + const errors: CompileError[] = []; + if (this.declarationNode.attributeList) { + errors.push(...this.bindHeaderSettings(this.declarationNode.attributeList)); + } + if (this.declarationNode.body instanceof BlockExpressionNode) { + errors.push(...this.bindBody(this.declarationNode.body)); } + return errors; + } - return this.bindBody(this.declarationNode.body); + private bindHeaderSettings (settings: ListExpressionNode): CompileError[] { + const settingsMap = aggregateSettingList(settings).getValue(); + return settingsMap.dep?.flatMap((d) => (d.value ? this.bindInlineDepValue(d.value) : [])) ?? []; + } + + private bindInlineDepValue (value: SyntaxNode): CompileError[] { + const bindees = scanNonListNodeForBinding(value); + return bindees.flatMap((bindee) => { + const nodes = [ + ...bindee.variables, + ...bindee.tupleElements, + ]; + return nodes.flatMap((b) => this.compiler.nodeReferee(b).getErrors()); + }); } private bindBody (body?: FunctionApplicationNode | BlockExpressionNode): CompileError[] { @@ -73,6 +92,7 @@ export default class TableBinder { const settingsMap = aggregateSettingList(listExpression).getValue(); errors.push(...(settingsMap.ref?.flatMap((ref) => (ref.value ? this.bindInlineRef(ref.value) : [])) || [])); + errors.push(...(settingsMap.dep?.flatMap((dep) => (dep.value ? this.bindInlineDepValue(dep.value) : [])) || [])); errors.push(...(settingsMap.default?.flatMap((def) => (def.value ? this.tryToBindEnumFieldRef(def.value) : [])) || [])); args.pop(); } diff --git a/packages/dbml-parse/src/core/global_modules/table/index.ts b/packages/dbml-parse/src/core/global_modules/table/index.ts index bf9d44a6c..28e0dd19c 100644 --- a/packages/dbml-parse/src/core/global_modules/table/index.ts +++ b/packages/dbml-parse/src/core/global_modules/table/index.ts @@ -35,7 +35,7 @@ import { isValidPartialInjection, } from '@/core/utils/validate'; import type { GlobalModule } from '../types'; -import { nodeRefereeOfLeftExpression } from '../utils'; +import { nodeRefereeOfEndpoint, nodeRefereeOfLeftExpression } from '../utils'; import TableBinder from './bind'; import { TableInterpreter } from './interpret'; @@ -193,6 +193,19 @@ export const tableModule: GlobalModule = { }, nodeReferee (compiler: Compiler, node: SyntaxNode): Report | Report { + // Case 0.0: Table dep settings + if ( + isInsideSettingValue(node, SettingName.Dep) + && node.parent instanceof ElementDeclarationNode + && node.parent.isKind(ElementKind.Table) + && !isInsideElementBody(node, ElementKind.Table) + ) { + const programNode = compiler.parseFile(node.filepath).getValue().ast; + const globalSymbol = compiler.nodeSymbol(programNode).getValue(); + if (globalSymbol === UNHANDLED) return Report.create(undefined); + return nodeRefereeOfInlineDep(compiler, globalSymbol, node); + } + if (!isInsideElementBody(node, ElementKind.Table)) { return Report.create(PASS_THROUGH); } @@ -204,24 +217,29 @@ export const tableModule: GlobalModule = { return Report.create(undefined); } - // Case 0: Partial injection (~partial_name) + // Case 1.0: Partial injection (~partial_name) if (isExpressionAVariableNode(node) && node.parentNode instanceof PrefixExpressionNode && node.parentNode.op?.value === '~') { return nodeRefereeOfPartialInjection(compiler, globalSymbol, node); } - // Case 1: Column's enum type + // Case 1.1: Column's enum type if (isWithinNthArgOfField(node, 1)) { return nodeRefereeOfEnumType(compiler, globalSymbol, node); } - // Case 2: Column's inline ref + // Case 1.2: Column's inline ref if (isInsideSettingValue(node, SettingName.Ref)) { return nodeRefereeOfInlineRef(compiler, globalSymbol, node); } - // Case 3: Column's default value being an enum value + // Case 1.3: Column's dep + if (isInsideSettingValue(node, SettingName.Dep)) { + return nodeRefereeOfInlineDep(compiler, globalSymbol, node); + } + + // Case 1.4: Column's default value being an enum value // Skip column name position (callee of the field's FunctionApplicationNode) if (isWithinNthArgOfField(node, 0)) { return Report.create(PASS_THROUGH); @@ -341,62 +359,52 @@ function nodeRefereeOfInlineRef (compiler: Compiler, globalSymbol: NodeSymbol, n ]); } - // Right side of access expression - resolve via left sibling - const left = nodeRefereeOfLeftExpression(compiler, node); - if (left) { - if (left.isKind(SymbolKind.Schema)) { - const symbol = compiler.lookupMembers(left, [ - SymbolKind.Table, - SymbolKind.Schema, - ], name); - if (symbol) { - return Report.create(symbol); - } + return nodeRefereeOfEndpoint(compiler, globalSymbol, node, SymbolKind.Column); +} - return new Report(undefined, [ - new CompileError(CompileErrorCode.BINDING_ERROR, `Table or schema '${name}' does not exist`, node), - ]); - } - if (left.isKind(SymbolKind.Table)) { - const symbol = compiler.lookupMembers(left, SymbolKind.Column, name); +// FIXME: extract reusable logic from inline ref +function nodeRefereeOfInlineDep (compiler: Compiler, globalSymbol: NodeSymbol, node: SyntaxNode): Report { + if (!isExpressionAVariableNode(node)) return new Report(undefined); + const isColumnDep = isInsideElementBody(node, ElementKind.Table); + + const name = extractVarNameFromPrimaryVariable(node) ?? ''; + + if (!isAccessExpression(node.parentNode)) { + // Standalone variable in inline column dep - look up as column in the enclosing table + if (isColumnDep) { + const enclosingTable = node.parent; + const tableSymbol = enclosingTable instanceof ElementDeclarationNode && enclosingTable.isKind(ElementKind.Table) + ? compiler.nodeSymbol(enclosingTable).getFiltered(UNHANDLED) + : undefined; + + if (tableSymbol) { + const symbol = compiler.lookupMembers(tableSymbol, SymbolKind.Column, name); + if (symbol) { + return Report.create(symbol); + } + } + const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Column, name); if (symbol) { return Report.create(symbol); } return new Report(undefined, [ - new CompileError(CompileErrorCode.BINDING_ERROR, `Column '${name}' does not exist in Table 'public.${left.name}'`, node), + new CompileError(CompileErrorCode.BINDING_ERROR, `Column '${name}' does not exist in Table 'public.${tableSymbol?.name ?? ''}'`, node), ]); - } - - return new Report(undefined); - } - - // Left side of access expression - look up as Table or Schema in program scope - const parent = node.parentNode as InfixExpressionNode; - if (parent.leftExpression === node) { - // If our parent is also a left side of another access, this is a schema - if (isAccessExpression(parent.parentNode) && (parent.parentNode as InfixExpressionNode).leftExpression === parent) { - const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Schema, name); + } else { + // Standalone variable in inline table dep - look up as table in the global scope + const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Table, name); if (symbol) { return Report.create(symbol); } return new Report(undefined, [ - new CompileError(CompileErrorCode.BINDING_ERROR, `Schema '${name}' does not exist in Schema 'public'`, node), + new CompileError(CompileErrorCode.BINDING_ERROR, `Table '${name}' does not exist in Schema 'public'`, node), ]); } - // First try by table name, then by alias - const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Table, name); - if (symbol) { - return Report.create(symbol); - } - - return new Report(undefined, [ - new CompileError(CompileErrorCode.BINDING_ERROR, `Table '${name}' does not exist in Schema 'public'`, node), - ]); } - return new Report(undefined); + return nodeRefereeOfEndpoint(compiler, globalSymbol, node, isColumnDep ? SymbolKind.Column : SymbolKind.Table); } // Default value: enum.field or schema.enum.field diff --git a/packages/dbml-parse/src/core/global_modules/table/interpret.ts b/packages/dbml-parse/src/core/global_modules/table/interpret.ts index b81fb4574..38494fd33 100644 --- a/packages/dbml-parse/src/core/global_modules/table/interpret.ts +++ b/packages/dbml-parse/src/core/global_modules/table/interpret.ts @@ -10,8 +10,9 @@ import { } from '@/core/types/nodes'; import Report from '@/core/types/report'; import { - Check, Column, Index, InlineRef, Ref, + Check, Column, DEP_DOWNSTREAM, DEP_UPSTREAM, Dep, Index, InlineDep, InlineRef, Ref, Table, TablePartialInjection, + type DepDirection, } from '@/core/types/schemaJson'; import type { Filepath } from '@/core/types/filepath'; import { SymbolKind } from '@/core/types/symbol'; @@ -311,6 +312,39 @@ export class TableInterpreter { return inlineRef; }); + const deps = settingMap[SettingName.Dep] || []; + column.inline_deps = deps.flatMap((depAttr) => { + const meta = this.compiler.nodeMetadata(depAttr).getFiltered(UNHANDLED); + if (!meta) return []; + + const owners = meta.owners(this.compiler); + if (programSymbol && owners.length > 0 && !owners.some((o) => o === programSymbol)) return []; + + const result = this.compiler.interpretMetadata(meta, this.filepath); + if (result.hasValue(UNHANDLED)) return []; + errors.push(...result.getErrors()); + + const value = result.getValue() as Dep | undefined; + if (!value?.edges?.[0]) return []; + + const prefix = (depAttr as any).value; + if (!(prefix instanceof PrefixExpressionNode)) return []; + const direction = prefix.op?.value as DepDirection | undefined; + if (direction !== DEP_DOWNSTREAM && direction !== DEP_UPSTREAM) return []; + + const edge = value.edges[0]; + const other = direction === DEP_DOWNSTREAM ? edge.downstream : edge.upstream; + + const inlineDep: InlineDep = { + schemaName: other.schemaName, + tableName: other.tableName, + fieldNames: other.fieldNames, + direction, + token: other.token, + }; + return inlineDep; + }); + const checkNodes = settingMap[SettingName.Check] || []; column.checks = checkNodes.map((checkNode) => { const token = getTokenPosition(checkNode); diff --git a/packages/dbml-parse/src/core/global_modules/tablePartial/interpret.ts b/packages/dbml-parse/src/core/global_modules/tablePartial/interpret.ts index 1ef959861..b76fa0ddb 100644 --- a/packages/dbml-parse/src/core/global_modules/tablePartial/interpret.ts +++ b/packages/dbml-parse/src/core/global_modules/tablePartial/interpret.ts @@ -221,6 +221,9 @@ export class TablePartialInterpreter { return inlineRef; }); + // Table partial columns do not support inline deps + column.inline_deps = []; + const checkNodes = settingMap[SettingName.Check] || []; column.checks = checkNodes.map((checkNode) => { const token = getTokenPosition(checkNode); diff --git a/packages/dbml-parse/src/core/global_modules/utils.ts b/packages/dbml-parse/src/core/global_modules/utils.ts index 94d19d100..597c225ea 100644 --- a/packages/dbml-parse/src/core/global_modules/utils.ts +++ b/packages/dbml-parse/src/core/global_modules/utils.ts @@ -1,14 +1,17 @@ import type Compiler from '@/compiler'; import { getMemberChain } from '@/core/parser/utils'; import type { RelationCardinality } from '@/core/types'; +import { CompileError, CompileErrorCode } from '@/core/types/errors'; import { UNHANDLED } from '@/core/types/module'; import { InfixExpressionNode, PostfixExpressionNode, PrefixExpressionNode, PrimaryExpressionNode, SyntaxNode, TupleExpressionNode, VariableNode, } from '@/core/types/nodes'; import Report from '@/core/types/report'; +import { SymbolKind } from '@/core/types/symbol'; import type { NodeSymbol } from '@/core/types/symbol'; +import { extractVarNameFromPrimaryVariable } from '@/core/utils/expression'; import { destructureComplexVariableTuple } from '@/core/utils/expression'; -import { isAccessExpression, isExpressionAVariableNode } from '../utils/validate'; +import { isAccessExpression, isExpressionAVariableNode, isTerminalAccessFragment } from '../utils/validate'; export function shouldInterpretNode (compiler: Compiler, node: SyntaxNode): boolean { return compiler.reachableFiles(node.filepath).every( @@ -111,6 +114,120 @@ export function nodeRefereeOfLeftExpression (compiler: Compiler, node: SyntaxNod return compiler.nodeReferee(leftExpr).getFiltered(UNHANDLED) ?? undefined; } +// Generic resolution for access expressions like schema.table.column or schema.table +export function nodeRefereeOfEndpoint ( + compiler: Compiler, + globalSymbol: NodeSymbol, + node: SyntaxNode, + terminalKind: SymbolKind.Column | SymbolKind.Table, // `terminalKind` determines what the rightmost fragment resolves as +): Report { + if (!isExpressionAVariableNode(node)) return new Report(undefined); + const name = extractVarNameFromPrimaryVariable(node) ?? ''; + + // Rightmost side of access expression - resolve as terminal kind + if ( + isAccessExpression(node.parentNode) + && node.parentNode.rightExpression === node + && isTerminalAccessFragment(node) + ) { + const left = nodeRefereeOfLeftExpression(compiler, node); + if (!left) return new Report(undefined); + + if (terminalKind === SymbolKind.Column) { + if (left.isKind(SymbolKind.Table)) { + const symbol = compiler.lookupMembers(left, SymbolKind.Column, name); + if (symbol) { + return Report.create(symbol); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Column '${name}' does not exist in Table 'public.${left.name}'`, node), + ]); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Column '${name}' does not exist`, node), + ]); + } else { + if (left.isKind(SymbolKind.Schema)) { + const symbol = compiler.lookupMembers(left, SymbolKind.Table, name); + if (symbol) { + return Report.create(symbol); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Table '${name}' does not exist in Schema 'public'`, node), + ]); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Table '${name}' does not exist`, node), + ]); + } + } + + // Non-terminal right side of access expression - resolve via left sibling as table or schema + const left = nodeRefereeOfLeftExpression(compiler, node); + if (left) { + if (left.isKind(SymbolKind.Schema)) { + const symbol = compiler.lookupMembers(left, [ + SymbolKind.Table, + SymbolKind.Schema, + ], name); + if (symbol) { + return Report.create(symbol); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Table or schema '${name}' does not exist`, node), + ]); + } + if (terminalKind === SymbolKind.Column && left.isKind(SymbolKind.Table)) { + const symbol = compiler.lookupMembers(left, SymbolKind.Column, name); + if (symbol) { + return Report.create(symbol); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Column '${name}' does not exist in Table 'public.${left.name}'`, node), + ]); + } + + return new Report(undefined); + } + + // Leftmost side of access expression - look up as Table or Schema in program scope + const parent = node.parentNode as InfixExpressionNode; + if (parent.leftExpression === node) { + // If parent is also left of another access, or this is a table-level endpoint, resolve as schema + if ( + (isAccessExpression(parent.parentNode) && (parent.parentNode as InfixExpressionNode).leftExpression === parent) + || terminalKind === SymbolKind.Table + ) { + const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Schema, name); + if (symbol) { + return Report.create(symbol); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Schema '${name}' does not exist in Schema 'public'`, node), + ]); + } + + // Otherwise resolve as table + const symbol = compiler.lookupMembers(globalSymbol, SymbolKind.Table, name); + if (symbol) { + return Report.create(symbol); + } + + return new Report(undefined, [ + new CompileError(CompileErrorCode.BINDING_ERROR, `Table '${name}' does not exist in Schema 'public'`, node), + ]); + } + + return new Report(undefined); +} + export function getMultiplicities ( op: string, ): [RelationCardinality, RelationCardinality] | undefined { diff --git a/packages/dbml-parse/src/core/lexer/lexer.ts b/packages/dbml-parse/src/core/lexer/lexer.ts index a12c70990..c88d95fe5 100644 --- a/packages/dbml-parse/src/core/lexer/lexer.ts +++ b/packages/dbml-parse/src/core/lexer/lexer.ts @@ -389,11 +389,15 @@ export default class Lexer { if ([ '>', '=', - ].includes(this.peek()!)) this.advance(); // <, >, <= + '-', + ].includes(this.peek()!)) this.advance(); // <, <>, <=, <- break; case '>': if (this.peek() === '=') this.advance(); // >, >= break; + case '-': + if (this.peek() === '>') this.advance(); // -, -> + break; case '=': if (this.peek() === '=') this.advance(); // =, == break; diff --git a/packages/dbml-parse/src/core/local_modules/custom/validate.ts b/packages/dbml-parse/src/core/local_modules/custom/validate.ts index 9862c627d..219ed4327 100644 --- a/packages/dbml-parse/src/core/local_modules/custom/validate.ts +++ b/packages/dbml-parse/src/core/local_modules/custom/validate.ts @@ -26,9 +26,9 @@ export default class CustomValidator { } private validateContext (): CompileError[] { - if (!(this.declarationNode.parent instanceof ElementDeclarationNode && this.declarationNode.parent.isKind(ElementKind.Project))) { + if (!(this.declarationNode.parent instanceof ElementDeclarationNode && this.declarationNode.parent.isKind(ElementKind.Project, ElementKind.Dep))) { return [ - new CompileError(CompileErrorCode.INVALID_CUSTOM_CONTEXT, 'A Custom element can only appear in a Project', this.declarationNode), + new CompileError(CompileErrorCode.INVALID_CUSTOM_CONTEXT, 'A Custom element can only appear in a Project or a Dep', this.declarationNode), ]; } return []; @@ -76,8 +76,10 @@ export default class CustomValidator { } const errors: CompileError[] = []; + const parentIsDep = this.declarationNode.parent instanceof ElementDeclarationNode + && this.declarationNode.parent.isKind(ElementKind.Dep); - if (!isExpressionAQuotedString(body.callee)) { + if (!parentIsDep && !isExpressionAQuotedString(body.callee)) { errors.push(new CompileError(CompileErrorCode.INVALID_CUSTOM_ELEMENT_VALUE, 'A Custom element value can only be a string', body)); } if (body.args.length > 0) { diff --git a/packages/dbml-parse/src/core/local_modules/dep/index.ts b/packages/dbml-parse/src/core/local_modules/dep/index.ts new file mode 100644 index 000000000..cfc42568d --- /dev/null +++ b/packages/dbml-parse/src/core/local_modules/dep/index.ts @@ -0,0 +1,76 @@ +import { last } from 'lodash-es'; +import type Compiler from '@/compiler'; +import { CompileError, CompileErrorCode } from '@/core/types/errors'; +import { ElementKind } from '@/core/types/keywords'; +import { PASS_THROUGH, type PassThrough } from '@/core/types/module'; +import { ListExpressionNode, SyntaxNode } from '@/core/types/nodes'; +import Report from '@/core/types/report'; +import { destructureComplexVariable } from '@/core/utils/expression'; +import { + Settings, isElementFieldNode, isElementNode, isSimpleName, +} from '@/core/utils/validate'; +import { type LocalModule } from '../types'; +import DepValidator, { validateDepSettings } from './validate'; + +export const depModule: LocalModule = { + validateNode (compiler: Compiler, node: SyntaxNode): Report | Report { + if (isElementNode(node, ElementKind.Dep)) { + return Report.create(undefined, new DepValidator(compiler, node).validate()); + } + return Report.create(PASS_THROUGH); + }, + + nodeFullname (compiler: Compiler, node: SyntaxNode): Report | Report { + if (isElementNode(node, ElementKind.Dep)) { + if (!node.name) return new Report(undefined); + if (!isSimpleName(node.name)) { + return new Report(undefined, [ + new CompileError(CompileErrorCode.INVALID_NAME, 'A Dep\'s name is optional or must be an identifier or a quoted identifier', node.name), + ]); + } + return new Report(destructureComplexVariable(node.name)); + } + if (isElementFieldNode(node, ElementKind.Dep)) { + return new Report(undefined); + } + return Report.create(PASS_THROUGH); + }, + + nodeAlias (compiler: Compiler, node: SyntaxNode): Report | Report { + if (isElementNode(node, ElementKind.Dep)) { + if (node.alias) { + return new Report(undefined, [ + new CompileError(CompileErrorCode.UNEXPECTED_ALIAS, 'A Dep shouldn\'t have an alias', node.alias), + ]); + } + return new Report(undefined); + } + if (isElementFieldNode(node, ElementKind.Dep)) { + return new Report(undefined); + } + return Report.create(PASS_THROUGH); + }, + + nodeSettings (compiler: Compiler, node: SyntaxNode): Report | Report { + if (isElementNode(node, ElementKind.Dep)) { + if (node.attributeList) { + return validateDepSettings(node.attributeList); + } + return new Report({}); + } + if (isElementFieldNode(node, ElementKind.Dep)) { + const args = [ + ...node.args, + ]; + let settingsList: ListExpressionNode | undefined; + if (last(args) instanceof ListExpressionNode) { + settingsList = last(args) as ListExpressionNode; + } else if (args[0] instanceof ListExpressionNode) { + settingsList = args[0]; + } + if (!settingsList) return new Report({}); + return validateDepSettings(settingsList); + } + return Report.create(PASS_THROUGH); + }, +}; diff --git a/packages/dbml-parse/src/core/local_modules/dep/validate.ts b/packages/dbml-parse/src/core/local_modules/dep/validate.ts new file mode 100644 index 000000000..c7562edd8 --- /dev/null +++ b/packages/dbml-parse/src/core/local_modules/dep/validate.ts @@ -0,0 +1,218 @@ +import { last, partition } from 'lodash-es'; +import Compiler from '@/compiler'; +import { CompileError, CompileErrorCode } from '@/core/types/errors'; +import { + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, InfixExpressionNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, +} from '@/core/types/nodes'; +import Report from '@/core/types/report'; +import { SettingName } from '@/core/types/keywords'; +import { DEP_DOWNSTREAM, DEP_UPSTREAM } from '@/core/types/schemaJson'; +import { destructureComplexVariableTuple } from '@/core/utils/expression'; +import { + Settings, aggregateSettingList, isSimpleName, isValidColor, isExpressionAQuotedString, + isExpressionASignedNumberExpression, +} from '@/core/utils/validate'; + +export default class DepValidator { + private declarationNode: ElementDeclarationNode; + private compiler: Compiler; + + constructor (compiler: Compiler, declarationNode: ElementDeclarationNode) { + this.compiler = compiler; + this.declarationNode = declarationNode; + } + + validate (): CompileError[] { + return [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettings(), + ...this.validateBody(this.declarationNode.body), + ]; + } + + private validateSettings (): CompileError[] { + const errors: CompileError[] = []; + // Header attribute list + if (this.declarationNode.attributeList) { + errors.push(...validateDepSettings(this.declarationNode.attributeList).getErrors()); + } + // Short-form body settings list + const body = this.declarationNode.body; + if (body instanceof FunctionApplicationNode) { + const settingsList = body.args.find((a) => a instanceof ListExpressionNode) as ListExpressionNode | undefined; + if (settingsList) { + errors.push(...validateDepSettings(settingsList).getErrors()); + } + } + return errors; + } + + private validateContext (): CompileError[] { + if (this.declarationNode.parent instanceof ProgramNode) { + return []; + } + return [ + new CompileError(CompileErrorCode.INVALID_DEP_CONTEXT, 'A Dep must appear top-level', this.declarationNode), + ]; + } + + private validateName (nameNode?: SyntaxNode): CompileError[] { + if (!nameNode) return []; + if (nameNode instanceof WildcardNode) { + return [ + new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Dep name', nameNode), + ]; + } + if (!isSimpleName(nameNode)) { + return [ + new CompileError(CompileErrorCode.INVALID_NAME, 'A Dep\'s name is optional or must be an identifier or a quoted identifier', nameNode), + ]; + } + return []; + } + + private validateAlias (aliasNode?: SyntaxNode): CompileError[] { + if (aliasNode) { + return [ + new CompileError(CompileErrorCode.UNEXPECTED_ALIAS, 'A Dep shouldn\'t have an alias', aliasNode), + ]; + } + return []; + } + + validateBody (body?: FunctionApplicationNode | BlockExpressionNode): CompileError[] { + if (!body) return []; + if (body instanceof FunctionApplicationNode) { + return this.validateFields([ + body, + ]); + } + + const [ + fields, + subs, + ] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + return [ + ...this.validateFields(fields as FunctionApplicationNode[]), + ...this.validateSubElements(subs as ElementDeclarationNode[]), + ]; + } + + validateFields (fields: FunctionApplicationNode[]): CompileError[] { + const errors: CompileError[] = []; + + fields.forEach((field) => { + if (!field.callee) { + errors.push(new CompileError(CompileErrorCode.INVALID_DEP_FIELD, 'A Dep field must be a binary relationship', field)); + return; + } + if (!(field.callee instanceof InfixExpressionNode)) { + errors.push(new CompileError(CompileErrorCode.INVALID_DEP_FIELD, 'A Dep field must be a binary relationship', field.callee)); + return; + } + + const infix = field.callee; + if (infix.op?.value !== DEP_DOWNSTREAM && infix.op?.value !== DEP_UPSTREAM) { + errors.push(new CompileError(CompileErrorCode.INVALID_DEP_FIELD, 'Dep edges must use the \'->\' or \'<-\' operator', field.callee)); + } + + const leftFragment = destructureComplexVariableTuple(infix.leftExpression) ?? { variables: [], tupleElements: [] }; + const rightFragment = destructureComplexVariableTuple(infix.rightExpression) ?? { variables: [], tupleElements: [] }; + if (leftFragment.variables.length === 0 && leftFragment.tupleElements.length === 0) { + errors.push(new CompileError(CompileErrorCode.INVALID_DEP_FIELD, 'Invalid Dep endpoint', infix.leftExpression || field.callee)); + } + if (rightFragment.variables.length === 0 && rightFragment.tupleElements.length === 0) { + errors.push(new CompileError(CompileErrorCode.INVALID_DEP_FIELD, 'Invalid Dep endpoint', infix.rightExpression || field.callee)); + } + + const args = [ + ...field.args, + ]; + if (last(args) instanceof ListExpressionNode) args.pop(); + else if (args[0] instanceof ListExpressionNode) args.shift(); + + if (args.length > 0) { + errors.push(...args.map((arg) => new CompileError(CompileErrorCode.INVALID_DEP_FIELD, 'A Dep field should only have a single binary relationship', arg))); + } + }); + + return errors; + } + + private validateSubElements (subs: ElementDeclarationNode[]): CompileError[] { + return subs.flatMap((sub) => { + if (!sub.type) return []; + const key = sub.type.value?.toLowerCase(); + const subBody = sub.body; + if (!key || !(subBody instanceof FunctionApplicationNode) || !subBody.callee) return []; + + switch (key) { + case SettingName.Color: + if (!isValidColor(subBody.callee)) { + return [ + new CompileError(CompileErrorCode.INVALID_SETTINGS, 'Invalid color value. Expected a hex color (e.g. #fff or #aabbcc)', sub), + ]; + } + return []; + case SettingName.Note: + if (!isExpressionAQuotedString(subBody.callee)) { + return [ + new CompileError(CompileErrorCode.INVALID_SETTINGS, 'Invalid note value. Expected a quoted string', sub), + ]; + } + return []; + default: + if (!isExpressionAQuotedString(subBody.callee) + && !isValidColor(subBody.callee) + && !isExpressionASignedNumberExpression(subBody.callee) + && !isSimpleName(subBody.callee)) { + return [ + new CompileError(CompileErrorCode.INVALID_SETTINGS, `Invalid value for '${key}'. Expected a string, number, color, or identifier`, sub), + ]; + } + return []; + } + }); + } +} + +export function validateDepSettings (settings: ListExpressionNode): Report { + const aggReport = aggregateSettingList(settings); + const errors: CompileError[] = [ + ...aggReport.getErrors(), + ]; + const settingMap = aggReport.getValue(); + + for (const [ + name, + attrs, + ] of Object.entries(settingMap)) { + for (const attr of attrs) { + switch (name) { + case SettingName.Color: + if (!isValidColor(attr.value)) { + errors.push(new CompileError(CompileErrorCode.INVALID_SETTINGS, 'Invalid color value. Expected a hex color (e.g. #fff or #aabbcc)', attr)); + } + break; + case SettingName.Note: + if (!isExpressionAQuotedString(attr.value)) { + errors.push(new CompileError(CompileErrorCode.INVALID_SETTINGS, 'Invalid note value. Expected a quoted string', attr)); + } + break; + default: + if (attr.value + && !isExpressionAQuotedString(attr.value) + && !isValidColor(attr.value) + && !isExpressionASignedNumberExpression(attr.value) + && !isSimpleName(attr.value)) { + errors.push(new CompileError(CompileErrorCode.INVALID_SETTINGS, `Invalid value for setting '${name}'. Expected a string, number, color, or identifier`, attr)); + } + break; + } + } + } + + return new Report(settingMap, errors); +} diff --git a/packages/dbml-parse/src/core/local_modules/index.ts b/packages/dbml-parse/src/core/local_modules/index.ts index 56e16fcde..13a625930 100644 --- a/packages/dbml-parse/src/core/local_modules/index.ts +++ b/packages/dbml-parse/src/core/local_modules/index.ts @@ -6,6 +6,7 @@ import type { SyntaxNode } from '@/core/types/nodes'; import Report from '@/core/types/report'; import { checksModule } from './checks'; import { customModule } from './custom'; +import { depModule } from './dep'; import { diagramViewModule } from './diagramView'; import { enumModule } from './enum'; import { indexesModule } from './indexes'; @@ -28,6 +29,7 @@ export const modules: LocalModule[] = [ indexesModule, checksModule, refModule, + depModule, projectModule, tableGroupModule, tablePartialModule, diff --git a/packages/dbml-parse/src/core/local_modules/note/validate.ts b/packages/dbml-parse/src/core/local_modules/note/validate.ts index 1da945559..65699aee2 100644 --- a/packages/dbml-parse/src/core/local_modules/note/validate.ts +++ b/packages/dbml-parse/src/core/local_modules/note/validate.ts @@ -38,6 +38,7 @@ export default class NoteValidator { ElementKind.TableGroup, ElementKind.TablePartial, ElementKind.Project, + ElementKind.Dep, )) ) { return [ diff --git a/packages/dbml-parse/src/core/local_modules/table/validate.ts b/packages/dbml-parse/src/core/local_modules/table/validate.ts index 5b867eb38..d1a60c362 100644 --- a/packages/dbml-parse/src/core/local_modules/table/validate.ts +++ b/packages/dbml-parse/src/core/local_modules/table/validate.ts @@ -31,6 +31,7 @@ import { isValidDefaultValue, isValidName, isValidPartialInjection, + isUnaryDependency, } from '@/core/utils/validate'; export default class TableValidator { @@ -130,6 +131,13 @@ export default class TableValidator { } }); break; + case SettingName.Dep: + attrs.forEach((attr) => { + if (!isUnaryDependency(attr.value)) { + errors.push(new CompileError(CompileErrorCode.INVALID_TABLE_SETTING_VALUE, '\'dep\' must be `-> target` or `<- source`', attr.value || attr.name!)); + } + }); + break; default: errors.push(...attrs.map((attr) => new CompileError(CompileErrorCode.UNKNOWN_TABLE_SETTING, `Unknown '${name}' setting`, attr))); } @@ -360,6 +368,13 @@ export default class TableValidator { } }); break; + case SettingName.Dep: + attrs.forEach((attr) => { + if (!isUnaryDependency(attr.value)) { + errors.push(new CompileError(CompileErrorCode.INVALID_COLUMN_SETTING_VALUE, '\'dep\' must be `-> target.col` or `<- source.col`', attr.value || attr.name!)); + } + }); + break; default: attrs.forEach((attr) => errors.push(new CompileError(CompileErrorCode.UNKNOWN_COLUMN_SETTING, `Unknown column setting '${name}'`, attr))); @@ -412,6 +427,13 @@ export function validateTableSettings (settingList?: ListExpressionNode): Report } }); break; + case SettingName.Dep: + attrs.forEach((attr) => { + if (!isUnaryDependency(attr.value)) { + errors.push(new CompileError(CompileErrorCode.INVALID_TABLE_SETTING_VALUE, '\'dep\' must be `-> target` or `<- source`', attr.value || attr.name!)); + } + }); + break; default: errors.push(...attrs.map((attr) => new CompileError(CompileErrorCode.UNKNOWN_TABLE_SETTING, `Unknown '${name}' setting`, attr))); } @@ -575,6 +597,13 @@ export function validateFieldSetting (parts: ExpressionNode[]): Report } }); break; + case SettingName.Dep: + attrs.forEach((attr) => { + if (!isUnaryDependency(attr.value)) { + errors.push(new CompileError(CompileErrorCode.INVALID_COLUMN_SETTING_VALUE, '\'dep\' must be a valid unary relationship', attr.value || attr.name!)); + } + }); + break; default: attrs.forEach((attr) => errors.push(new CompileError(CompileErrorCode.UNKNOWN_COLUMN_SETTING, `Unknown column setting '${name}'`, attr))); diff --git a/packages/dbml-parse/src/core/parser/parser.ts b/packages/dbml-parse/src/core/parser/parser.ts index 20acd45a1..5e8ac1798 100644 --- a/packages/dbml-parse/src/core/parser/parser.ts +++ b/packages/dbml-parse/src/core/parser/parser.ts @@ -1545,6 +1545,14 @@ const infixBindingPowerMap: { left: 7, right: 8, }, + '->': { + left: 7, + right: 8, + }, + '<-': { + left: 7, + right: 8, + }, '=': { left: 2, right: 3, @@ -1600,6 +1608,14 @@ const prefixBindingPowerMap: { left: null, right: 15, }, + '->': { + left: null, + right: 15, + }, + '<-': { + left: null, + right: 15, + }, '!': { left: null, right: 15, diff --git a/packages/dbml-parse/src/core/types/errors.ts b/packages/dbml-parse/src/core/types/errors.ts index 3e3f24872..fb85d1e04 100644 --- a/packages/dbml-parse/src/core/types/errors.ts +++ b/packages/dbml-parse/src/core/types/errors.ts @@ -66,6 +66,9 @@ export enum CompileErrorCode { EMPTY_REF, REF_REDEFINED, + INVALID_DEP_CONTEXT, + INVALID_DEP_FIELD, + INVALID_NOTE_CONTEXT, INVALID_NOTE, NOTE_REDEFINED, @@ -134,6 +137,10 @@ export enum CompileErrorCode { UNSUPPORTED = 5000, CIRCULAR_REF, SAME_ENDPOINT, + DEP_SELF_LOOP, + DEP_MIXED_DOWNSTREAM_TABLES, + DEP_DUPLICATE_DOWNSTREAM_TABLE, + DEP_MIXED_LEVEL, UNEQUAL_FIELDS_BINARY_REF, CONFLICTING_SETTING, TABLE_REAPPEAR_IN_TABLEGROUP, diff --git a/packages/dbml-parse/src/core/types/keywords.ts b/packages/dbml-parse/src/core/types/keywords.ts index b9207a0d4..a710c314c 100644 --- a/packages/dbml-parse/src/core/types/keywords.ts +++ b/packages/dbml-parse/src/core/types/keywords.ts @@ -2,6 +2,7 @@ export enum ElementKind { Table = 'table', Enum = 'enum', Ref = 'ref', + Dep = 'dep', Note = 'note', Project = 'project', Indexes = 'indexes', @@ -25,6 +26,7 @@ export enum SettingName { PrimaryKey = 'primary key', Unique = 'unique', Ref = 'ref', + Dep = 'dep', NotNull = 'not null', Null = 'null', Increment = 'increment', diff --git a/packages/dbml-parse/src/core/types/schemaJson.ts b/packages/dbml-parse/src/core/types/schemaJson.ts index 4713e6479..b7958f018 100644 --- a/packages/dbml-parse/src/core/types/schemaJson.ts +++ b/packages/dbml-parse/src/core/types/schemaJson.ts @@ -65,6 +65,7 @@ export interface Database { tables: Table[]; notes: Note[]; refs: Ref[]; + deps: Dep[]; enums: Enum[]; tableGroups: TableGroup[]; aliases: Alias[]; @@ -122,6 +123,7 @@ export interface Column { type: ColumnType; token: TokenPosition; inline_refs: InlineRef[]; + inline_deps: InlineDep[]; checks: Check[]; pk?: boolean; dbdefault?: { @@ -191,6 +193,44 @@ export interface RefEndpoint { export type RelationCardinality = '1' | '*'; +export interface Dep { + schemaName: string | null; + name: string | null; + edges: DepEdge[]; + color?: Color; + note?: { + value: string; + token: TokenPosition; + }; + metadata?: Record; + token: TokenPosition; +} + +export interface DepEdge { + upstream: DepEndpoint; + downstream: DepEndpoint; + token: TokenPosition; +} + +export interface DepEndpoint { + schemaName: string | null; + tableName: string; + fieldNames: string[]; + token: TokenPosition; +} + +export const DEP_DOWNSTREAM = '->' as const; +export const DEP_UPSTREAM = '<-' as const; +export type DepDirection = typeof DEP_DOWNSTREAM | typeof DEP_UPSTREAM; + +export interface InlineDep { + schemaName: string | null; + tableName: string; + fieldNames: string[]; + direction: DepDirection; + token: TokenPosition; +} + export interface Enum { name: string; schemaName: string | null; @@ -295,8 +335,12 @@ export type SchemaElement = | Index | Check | InlineRef + | InlineDep | Ref | RefEndpoint + | Dep + | DepEdge + | DepEndpoint | Enum | EnumField | TableGroup diff --git a/packages/dbml-parse/src/core/types/symbol/metadata.ts b/packages/dbml-parse/src/core/types/symbol/metadata.ts index 5b209af48..f5647450a 100644 --- a/packages/dbml-parse/src/core/types/symbol/metadata.ts +++ b/packages/dbml-parse/src/core/types/symbol/metadata.ts @@ -8,6 +8,7 @@ import { PrefixExpressionNode, type SyntaxNode, } from '../nodes'; +import { DEP_DOWNSTREAM, DEP_UPSTREAM } from '../schemaJson'; import type { ColumnSymbol, NodeSymbol, @@ -15,6 +16,7 @@ import type { TablePartialSymbol, TableSymbol, } from '../symbol'; +import { SymbolKind } from '../symbol'; import type { Internable } from '../internable'; import { UNHANDLED } from '../module'; import { ElementKind, SettingName } from '../keywords'; @@ -26,6 +28,7 @@ import { export enum MetadataKind { Ref = 'ref', + Dep = 'dep', PartialRef = 'partial ref', TableChecks = 'table checks', Indexes = 'indexes', @@ -225,6 +228,158 @@ export class RefMetadata extends NodeMetadata { } } +export class DepMetadata extends NodeMetadata { + declare declaration: ElementDeclarationNode | AttributeNode; + + readonly kind = MetadataKind.Dep; + + constructor (declaration: ElementDeclarationNode | AttributeNode) { + super(declaration); + } + + container (compiler: Compiler): TableSymbol | undefined { + const parent = this.declaration.parentOfKind(ElementDeclarationNode); + if (parent?.isKind(ElementKind.Table)) { + return compiler.nodeSymbol(parent).getFiltered(UNHANDLED) as TableSymbol | undefined; + } + return undefined; + } + + edgeExpressions (): InfixExpressionNode[] { + if (!(this.declaration instanceof ElementDeclarationNode)) return []; + return getBody(this.declaration) + .filter((f): f is FunctionApplicationNode => f instanceof FunctionApplicationNode) + .map((f) => f.callee) + .filter((e): e is InfixExpressionNode => e instanceof InfixExpressionNode); + } + + upstreamColumns (compiler: Compiler): ColumnSymbol[][] { + if (this.declaration instanceof ElementDeclarationNode) { + return this.edgeExpressions().map((infix) => { + const upstream = infix.op?.value === DEP_UPSTREAM ? infix.rightExpression : infix.leftExpression; + return extractColumnsFromEndpoint(compiler, upstream); + }); + } + if (this.declaration instanceof AttributeNode) { + const prefix = this.declaration.value; + if (!(prefix instanceof PrefixExpressionNode)) return []; + const op = prefix.op?.value; + const hostCol = this.hostColumn(compiler); + const otherCols = extractColumnsFromEndpoint(compiler, prefix.expression); + if (op === DEP_DOWNSTREAM) return [ + hostCol + ? [ + hostCol, + ] + : [], + ]; + if (op === DEP_UPSTREAM) return [ + otherCols, + ]; + } + return []; + } + + downstreamColumns (compiler: Compiler): ColumnSymbol[][] { + if (this.declaration instanceof ElementDeclarationNode) { + return this.edgeExpressions().map((infix) => { + const downstream = infix.op?.value === DEP_UPSTREAM ? infix.leftExpression : infix.rightExpression; + return extractColumnsFromEndpoint(compiler, downstream); + }); + } + if (this.declaration instanceof AttributeNode) { + const prefix = this.declaration.value; + if (!(prefix instanceof PrefixExpressionNode)) return []; + const op = prefix.op?.value; + const hostCol = this.hostColumn(compiler); + const otherCols = extractColumnsFromEndpoint(compiler, prefix.expression); + if (op === DEP_DOWNSTREAM) return [ + otherCols, + ]; + if (op === DEP_UPSTREAM) return [ + hostCol + ? [ + hostCol, + ] + : [], + ]; + } + return []; + } + + upstreamTables (compiler: Compiler): (TableSymbol | undefined)[] { + if (this.declaration instanceof ElementDeclarationNode) { + return this.edgeExpressions().map((infix) => { + const upstream = infix.op?.value === DEP_UPSTREAM ? infix.rightExpression : infix.leftExpression; + return extractTableFromDepEndpoint(compiler, upstream); + }); + } + if (this.declaration instanceof AttributeNode) { + const prefix = this.declaration.value; + if (!(prefix instanceof PrefixExpressionNode)) return []; + const op = prefix.op?.value; + const otherTbl = extractTableFromDepEndpoint(compiler, prefix.expression); + if (op === DEP_DOWNSTREAM) return [ + this.container(compiler), + ]; + if (op === DEP_UPSTREAM) return [ + otherTbl, + ]; + } + return []; + } + + downstreamTables (compiler: Compiler): (TableSymbol | undefined)[] { + if (this.declaration instanceof ElementDeclarationNode) { + return this.edgeExpressions().map((infix) => { + const downstream = infix.op?.value === DEP_UPSTREAM ? infix.leftExpression : infix.rightExpression; + return extractTableFromDepEndpoint(compiler, downstream); + }); + } + if (this.declaration instanceof AttributeNode) { + const prefix = this.declaration.value; + if (!(prefix instanceof PrefixExpressionNode)) return []; + const op = prefix.op?.value; + const otherTbl = extractTableFromDepEndpoint(compiler, prefix.expression); + if (op === DEP_DOWNSTREAM) return [ + otherTbl, + ]; + if (op === DEP_UPSTREAM) return [ + this.container(compiler), + ]; + } + return []; + } + + private hostColumn (compiler: Compiler): ColumnSymbol | undefined { + if (!(this.declaration instanceof AttributeNode)) return undefined; + const colNode = this.declaration.parentOfKind(FunctionApplicationNode); + if (!colNode) return undefined; + return compiler.nodeSymbol(colNode).getFiltered(UNHANDLED) as ColumnSymbol | undefined; + } + + override owners (compiler: Compiler): NodeSymbol[] { + const upstreamTbls = this.upstreamTables(compiler); + const downstreamTbls = this.downstreamTables(compiler); + + const tableSymbols = [ + ...upstreamTbls, + ...downstreamTbls, + ].filter((t): t is TableSymbol => !!t); + if (tableSymbols.length === 0) return []; + + const declarationFilepath = this.declaration.filepath; + const reachableFiles = compiler.reachableFiles(); + return reachableFiles + .flatMap((f) => compiler.nodeSymbol(compiler.parseFile(f).getValue().ast).getFiltered(UNHANDLED) || []) + .filter((s) => { + const reachableFromProgram = compiler.reachableFiles(s.filepath); + return reachableFromProgram.some((f) => f.equals(declarationFilepath)) + && tableSymbols.every((t) => (s as ProgramSymbol).inNestedSchema(compiler, t)); + }); + } +} + export class PartialRefMetadata extends NodeMetadata { readonly kind = MetadataKind.PartialRef; @@ -421,10 +576,10 @@ function extractColumnsFromEndpoint (compiler: Compiler, expr: SyntaxNode | unde ] : []; return colNodes.flatMap((n) => { - const sym = compiler.nodeReferee(n).getFiltered(UNHANDLED) as ColumnSymbol | undefined; - return sym + const sym = compiler.nodeReferee(n).getFiltered(UNHANDLED); + return sym?.isKind(SymbolKind.Column) ? [ - sym, + sym as ColumnSymbol, ] : []; }); @@ -441,3 +596,26 @@ function extractTableFromEndpoint (compiler: Compiler, expr: SyntaxNode | undefi if (!tableNode) return undefined; return compiler.nodeReferee(tableNode).getFiltered(UNHANDLED) as TableSymbol | undefined; } + +function extractTableFromDepEndpoint (compiler: Compiler, expr: SyntaxNode | undefined): TableSymbol | undefined { + if (!expr) return undefined; + const fragments = destructureComplexVariableTuple(expr); + if (!fragments) return undefined; + + if (fragments.tupleElements.length > 0) { + const tableNode = fragments.variables.at(-1); + if (!tableNode) return undefined; + return compiler.nodeReferee(tableNode).getFiltered(UNHANDLED) as TableSymbol | undefined; + } + + const last = fragments.variables.at(-1); + if (last) { + const lastSym = compiler.nodeReferee(last).getFiltered(UNHANDLED); + if (lastSym?.isKind(SymbolKind.Table)) return lastSym as TableSymbol; + } + const secondLast = fragments.variables.at(-2); + if (secondLast) { + return compiler.nodeReferee(secondLast).getFiltered(UNHANDLED) as TableSymbol | undefined; + } + return undefined; +} diff --git a/packages/dbml-parse/src/core/utils/expression.ts b/packages/dbml-parse/src/core/utils/expression.ts index 9c2168873..a61b82e9a 100644 --- a/packages/dbml-parse/src/core/utils/expression.ts +++ b/packages/dbml-parse/src/core/utils/expression.ts @@ -7,6 +7,7 @@ import { isValidIndexName, } from '@/core/utils/validate'; import { + AttributeNode, BlockExpressionNode, CallExpressionNode, ElementDeclarationNode, @@ -223,3 +224,13 @@ export function extractVarNameFromPrimaryVariable ( return value === undefined ? undefined : value; } + +// Extracts setting name from an attribute node +export function extractSettingName (attr: AttributeNode): string | undefined { + if (attr.name instanceof IdentifierStreamNode) { + return extractStringFromIdentifierStream(attr.name)?.toLowerCase(); + } else if (attr.name instanceof PrimaryExpressionNode) { + return extractVariableFromExpression(attr.name); + } + return undefined; +} diff --git a/packages/dbml-parse/src/core/utils/note.ts b/packages/dbml-parse/src/core/utils/note.ts new file mode 100644 index 000000000..49d1e8c58 --- /dev/null +++ b/packages/dbml-parse/src/core/utils/note.ts @@ -0,0 +1,166 @@ +/** + * General-purpose note edit utilities for any element (table, table group, dep, etc.). + * + * Notes appear in three forms: + * - Body sub-element: `Note { 'text' }` or `Note: 'text'` (tables, table groups) + * - Body sub-declaration: `note: 'text'` (deps) + * - Setting attribute: `[note: 'text']` in a `[...]` list + * + * These utilities prioritize body notes over setting notes. + */ + +import { + BlockExpressionNode, + ElementDeclarationNode, + FunctionApplicationNode, + ListExpressionNode, +} from '@/core/types/nodes'; +import type { SyntaxNode } from '@/core/types/nodes'; +import { SyntaxNodeKind } from '@/core/types/nodes'; +import { ElementKind, SettingName } from '@/core/types/keywords'; +import { aggregateSettingList } from '@/core/utils/validate'; +import type { TextEdit } from '@/compiler/queries/transform/applyTextEdits'; +import { quoteNoteValue } from '@/compiler/queries/utils'; + +interface NoteSetting { + kind: SyntaxNodeKind.ELEMENT_DECLARATION | SyntaxNodeKind.ATTRIBUTE; + noteValueStart: number; + noteValueEnd: number; + fullNoteStart: number; + fullNoteEnd: number; +} + +/** + * Produces a TextEdit that updates an existing note's value. + * Returns undefined if no note exists. + */ +export function updateNoteEdit (declaration: SyntaxNode, newValue: string): TextEdit | undefined { + const note = findNote(declaration); + if (!note) return undefined; + return { start: note.noteValueStart, end: note.noteValueEnd, newText: quoteNoteValue(newValue) }; +} + +/** + * Produces a TextEdit that removes an existing note. + * Returns undefined if no note exists. + */ +export function removeNoteEdit (declaration: SyntaxNode): TextEdit | undefined { + const note = findNote(declaration); + if (!note) return undefined; + return { start: note.fullNoteStart, end: note.fullNoteEnd, newText: '' }; +} + +/** + * Produces a TextEdit that adds a note to an element. + * Prioritizes body note form for block elements, falls back to setting attribute. + * Returns undefined if a note already exists. + */ +export function addNoteEdit (declaration: SyntaxNode, value: string): TextEdit | undefined { + const existing = findNote(declaration); + if (existing) return undefined; + + if (!(declaration instanceof ElementDeclarationNode)) return undefined; + + // For block-form elements, insert as a body sub-element + const insertAt = findBodyInsertionPoint(declaration); + if (insertAt !== undefined) { + return { start: insertAt, end: insertAt, newText: ` note: ${quoteNoteValue(value)}\n` }; + } + + // For short-form elements, append as a setting attribute + const body = declaration.body; + if (body instanceof FunctionApplicationNode) { + const settingsList = body.args.find((a): a is ListExpressionNode => a instanceof ListExpressionNode); + if (settingsList && settingsList.listCloseBracket) { + const sep = settingsList.elementList.length === 0 ? '' : ', '; + return { start: settingsList.listCloseBracket.start, end: settingsList.listCloseBracket.start, newText: `${sep}note: ${quoteNoteValue(value)}` }; + } + return { start: declaration.end, end: declaration.end, newText: ` [note: ${quoteNoteValue(value)}]` }; + } + + return undefined; +} + +// Private helpers + +function findNote (declaration: SyntaxNode): NoteSetting | undefined { + if (!(declaration instanceof ElementDeclarationNode)) return undefined; + + const body = declaration.body; + + // Body form: Note { 'text' } or Note: 'text' or note: 'text' + if (body && !(body instanceof FunctionApplicationNode)) { + for (const child of body.body) { + if (!(child instanceof ElementDeclarationNode) || !child.isKind(ElementKind.Note)) continue; + const valueNode = extractNoteValueNode(child); + if (valueNode) { + return { + kind: SyntaxNodeKind.ELEMENT_DECLARATION, + noteValueStart: valueNode.start, + noteValueEnd: valueNode.end, + fullNoteStart: child.start, + fullNoteEnd: child.end, + }; + } + } + } + + // Setting attribute: [note: 'text'] + const settingsList = declaration.attributeList + ?? (body instanceof FunctionApplicationNode + ? body.args.find((a): a is ListExpressionNode => a instanceof ListExpressionNode) + : undefined); + + if (settingsList) { + return findNoteInSettingsList(settingsList); + } + + return undefined; +} + +function findBodyInsertionPoint (declaration: ElementDeclarationNode): number | undefined { + const body = declaration.body; + if (!body || body instanceof FunctionApplicationNode) return undefined; + if (body.blockCloseBrace) return body.blockCloseBrace.start; + return undefined; +} + +function extractNoteValueNode (noteElement: ElementDeclarationNode): SyntaxNode | undefined { + const subBody = noteElement.body; + if (subBody instanceof FunctionApplicationNode) return subBody.callee ?? undefined; + if (subBody instanceof BlockExpressionNode && subBody.body[0] instanceof FunctionApplicationNode) { + return subBody.body[0].callee ?? undefined; + } + return undefined; +} + +function findNoteInSettingsList (settingsList: ListExpressionNode): NoteSetting | undefined { + const settings = aggregateSettingList(settingsList).getValue(); + const noteAttrs = settings[SettingName.Note]; + if (!noteAttrs?.length) return undefined; + + const attr = noteAttrs[0]; + if (!attr.value) return undefined; + + const elements = settingsList.elementList; + const i = elements.indexOf(attr); + + let fullNoteStart = attr.start; + let fullNoteEnd = attr.end; + if (elements.length === 1) { + fullNoteStart = settingsList.start; + fullNoteEnd = settingsList.end; + } else if (i < elements.length - 1) { + fullNoteEnd = elements[i + 1].start; + } else { + fullNoteStart = elements[i - 1].end; + } + + return { + kind: SyntaxNodeKind.ATTRIBUTE, + noteValueStart: attr.value.start, + noteValueEnd: attr.value.end, + fullNoteStart, + fullNoteEnd, + }; +} diff --git a/packages/dbml-parse/src/core/utils/setting.ts b/packages/dbml-parse/src/core/utils/setting.ts new file mode 100644 index 000000000..0817c6102 --- /dev/null +++ b/packages/dbml-parse/src/core/utils/setting.ts @@ -0,0 +1,174 @@ +import { + FunctionApplicationNode, ListExpressionNode, AttributeNode, ElementDeclarationNode, +} from '@/core/types/nodes'; +import type { SyntaxNode } from '@/core/types/nodes'; +import type { TextEdit } from '@/compiler/queries/transform/applyTextEdits'; +import { extractSettingName } from './expression'; +import { hasSimpleBody } from './validate'; + +// The form a setting appears in +export type SettingLocation = + // [key: value] inside a [...] attribute list + | { + kind: 'attribute'; + settingsList: ListExpressionNode; + settingNode: AttributeNode; + settingIndex: number; + } + // key: value as a body sub-declaration (short form) + | { + kind: 'body'; + declaration: ElementDeclarationNode; + } + // not found + | undefined; + +// Locates a setting on a declaration node and returns its form +export function findSetting (declaration: SyntaxNode, settingName: string): SettingLocation { + // check [...] attribute list + const settingsList = findSettingsList(declaration); + if (settingsList) { + const elements = settingsList.elementList ?? []; + + const settingIndex = elements.findIndex((element) => { + if (!(element instanceof AttributeNode)) return false; + return extractSettingName(element)?.toLowerCase() === settingName.toLowerCase(); + }); + + if (settingIndex !== -1) { + return { + kind: 'attribute', + settingsList, + settingNode: elements[settingIndex] as AttributeNode, + settingIndex, + }; + } + } + + if (declaration instanceof ElementDeclarationNode) { + // check body sub-declaration (e.g. `color: #hex` or `note { 'text' }`) + const sub = findBodySetting(declaration, settingName); + if (sub) { + return { kind: 'body', declaration: sub }; + } + } + + return undefined; +} + +// Adds a setting to a declaration that doesn't have it yet. +// If the declaration has a [...] list, appends to it. +// Otherwise inserts a new [...] block. +export function addSettingEdit (declaration: SyntaxNode, setting: string): TextEdit | undefined { + const settingsList = findSettingsList(declaration); + if (settingsList) { + const insertOffset = settingsList.end - 1; + return { start: insertOffset, end: insertOffset, newText: `, ${setting}` }; + } + + // for block-form elements, insert [setting] before the body { + if (declaration instanceof ElementDeclarationNode) { + const body = declaration.body; + if (body && !(body instanceof FunctionApplicationNode)) { + return { start: body.start, end: body.start, newText: `[${setting}] ` }; + } + } + + return { start: declaration.end, end: declaration.end, newText: ` [${setting}]` }; +} + +// Updates, creates, or removes a setting on a declaration. +// - value: string -> update or create with "name: value" +// - value: undefined -> name-only setting (e.g. [pk]) +// - value: null -> remove the setting +// If the setting does not exist, adds it. +export function updateSettingEdit ( + declaration: SyntaxNode, + settingName: string, + value: string | null | undefined, + source: string, +): TextEdit | undefined { + if (value === null) { + return removeSettingEdit(declaration, settingName, source); + } + + // "name: value" or just "name" for name-only + const settingText = value !== undefined + ? `${settingName}: ${value}` + : settingName; + + const located = findSetting(declaration, settingName); + if (located) { + const node = located.kind === 'attribute' ? located.settingNode : located.declaration; + return { start: node.start, end: node.end, newText: settingText }; + } + + // setting not present - add it + return addSettingEdit(declaration, settingText); +} + +// Removes a setting from a declaration. +// If it's the only setting in a [...] list, removes the entire list. +export function removeSettingEdit ( + declaration: SyntaxNode, + settingName: string, + source: string, +): TextEdit | undefined { + const located = findSetting(declaration, settingName); + if (!located) return undefined; + + if (located.kind === 'body') { + return { start: located.declaration.start, end: located.declaration.end, newText: '' }; + } + + // attribute form - handle separator cleanup + const { settingsList, settingIndex } = located; + const elements = settingsList.elementList ?? []; + + // only setting - remove the entire [...] block including surrounding whitespace + if (elements.length === 1) { + let removeStart = settingsList.start; + while (removeStart > 0 && source[removeStart - 1] === ' ') removeStart--; + return { start: removeStart, end: settingsList.end, newText: '' }; + } + + // multiple settings - remove the setting and its separator + const settingNode = elements[settingIndex]; + if (settingIndex === 0) { + const nextSetting = elements[settingIndex + 1]; + return { start: settingNode.start, end: nextSetting.start, newText: '' }; + } + + const prevSetting = elements[settingIndex - 1]; + return { start: prevSetting.end, end: settingNode.end, newText: '' }; +} + +// Finds the [...] attribute list on a declaration or its inline body +function findSettingsList (declaration: SyntaxNode): ListExpressionNode | undefined { + if (declaration instanceof FunctionApplicationNode) { + return declaration.args.find((a): a is ListExpressionNode => a instanceof ListExpressionNode); + } + if (declaration instanceof ElementDeclarationNode) { + // check header attribute list first, then body's inline settings + if (declaration.attributeList) return declaration.attributeList; + const body = declaration.body; + if (body instanceof FunctionApplicationNode) { + return body.args.find((a): a is ListExpressionNode => a instanceof ListExpressionNode); + } + } + return undefined; +} + +// Find a body sub-declaration by name (e.g. `color: #hex` or `note { 'text' }`) +export function findBodySetting (declaration: ElementDeclarationNode, settingName: string): ElementDeclarationNode | undefined { + const body = declaration.body; + + if (!body || hasSimpleBody(declaration) || body instanceof FunctionApplicationNode) return undefined; + + for (const child of body.body) { + if (!(child instanceof ElementDeclarationNode)) continue; + if (child.type?.value?.toLowerCase() !== settingName.toLowerCase()) continue; + return child; + } + return undefined; +} diff --git a/packages/dbml-parse/src/core/utils/validate.ts b/packages/dbml-parse/src/core/utils/validate.ts index d73fbb8ce..f467eff49 100644 --- a/packages/dbml-parse/src/core/utils/validate.ts +++ b/packages/dbml-parse/src/core/utils/validate.ts @@ -88,6 +88,10 @@ export function isRelationshipOp (op?: string): boolean { return op === '-' || op === '<>' || op === '>' || op === '<'; } +export function isDependencyOp (op?: string): boolean { + return op === '<-' || op === '->'; +} + export function isValidColor (value?: SyntaxNode): boolean { if ( !(value instanceof PrimaryExpressionNode) @@ -184,6 +188,20 @@ export function isUnaryRelationship (value?: SyntaxNode): value is PrefixExpress return variables !== undefined && variables.length > 0; } +export function isUnaryDependency (value?: SyntaxNode): value is PrefixExpressionNode { + if (!(value instanceof PrefixExpressionNode)) { + return false; + } + + if (!isDependencyOp(value.op?.value)) { + return false; + } + + const variables = destructureComplexVariable(value.expression); + + return variables !== undefined && variables.length > 0; +} + export function isTupleOfVariables (value?: SyntaxNode): value is TupleExpressionNode & { elementList: (PrimaryExpressionNode & { expression: VariableNode })[]; } { diff --git a/packages/dbml-parse/src/index.ts b/packages/dbml-parse/src/index.ts index 45b7e8efc..3d4144419 100644 --- a/packages/dbml-parse/src/index.ts +++ b/packages/dbml-parse/src/index.ts @@ -17,6 +17,8 @@ export { SymbolKind, } from '@/core/types/symbol'; +export { MetadataKind } from '@/core/types/symbol/metadata'; + export * from '@/core/global_modules/records/utils'; export * from '@/core/types/nodes'; @@ -51,6 +53,7 @@ export { formatRecordValue, isValidIdentifier, addDoubleQuoteIfNeeded, + normalizeQualifiedName, } from '@/compiler/index'; // Export interpreted types for structured data @@ -93,6 +96,27 @@ export type { TextEdit, } from '@/compiler/queries/transform'; +export { findDiagramViewBlocks, findDepBlocks } from '@/compiler/queries/transform'; + +// Dep transform types +export type { + DepSyncOperation, DepSyncEdge, DepEndpointRef, DepBlock, +} from '@/compiler/queries/transform'; + +// Element identifier types +export type { + ElementIdentifier, + SchemaIdentifier, + TableIdentifier, + ColumnIdentifier, + EnumIdentifier, + EndpointRef, + RefIdentifier, + DepIdentifier, + NoteIdentifier, + TableGroupIdentifier, +} from '@/compiler/queries/transform'; + export { dbmlLanguageConfig, dbmlMonarchTokensProvider, diff --git a/packages/dbml-parse/src/services/monarch.ts b/packages/dbml-parse/src/services/monarch.ts index fec2864e2..c0333ac91 100644 --- a/packages/dbml-parse/src/services/monarch.ts +++ b/packages/dbml-parse/src/services/monarch.ts @@ -109,6 +109,7 @@ const dbmlMonarchTokensProvider: MonarchLanguage = { 'table', 'enum', 'ref', + 'dep', 'note', 'tablepartial', 'records', @@ -180,6 +181,7 @@ const dbmlMonarchTokensProvider: MonarchLanguage = { settings: [ 'indexes', 'ref', + 'dep', 'note', 'headercolor', 'pk', diff --git a/packages/dbml-parse/src/services/suggestions/provider.ts b/packages/dbml-parse/src/services/suggestions/provider.ts index 04751d674..e5e0afdca 100644 --- a/packages/dbml-parse/src/services/suggestions/provider.ts +++ b/packages/dbml-parse/src/services/suggestions/provider.ts @@ -3,6 +3,7 @@ import { DEFAULT_SCHEMA_NAME, NONE_COLOR } from '@/constants'; import { isComment } from '@/core/lexer/utils'; import { Filepath } from '@/core/types/filepath'; import { ElementKind, SettingName } from '@/core/types/keywords'; +import { DEP_DOWNSTREAM, DEP_UPSTREAM } from '@/core/types/schemaJson'; import { UNHANDLED } from '@/core/types/module'; import { AttributeNode, @@ -136,6 +137,8 @@ export default class DBMLCompletionItemProvider implements CompletionItemProvide case '<': case '<>': case '-': + case DEP_DOWNSTREAM: + case DEP_UPSTREAM: return suggestOnRelOp( this.compiler, filepath, @@ -156,6 +159,8 @@ export default class DBMLCompletionItemProvider implements CompletionItemProvide case '<': case '<>': case '-': + case DEP_DOWNSTREAM: + case DEP_UPSTREAM: return suggestOnRelOp( this.compiler, filepath, @@ -221,6 +226,7 @@ function suggestOnRelOp ( if ([ ScopeKind.REF, + ScopeKind.DEP, ScopeKind.TABLE, ScopeKind.TABLEPARTIAL, ].includes(scopeKind)) { @@ -367,6 +373,7 @@ function suggestInTuple (compiler: Compiler, filepath: Filepath, offset: number, case ScopeKind.INDEXES: return suggestColumnNameInIndexes(compiler, filepath, offset); case ScopeKind.REF: + case ScopeKind.DEP: { while (containers.length > 0) { const container = containers.pop()!; @@ -511,6 +518,7 @@ function suggestAttributeName (compiler: Compiler, filepath: Filepath, offset: n })), ...[ SettingName.Ref, + SettingName.Dep, SettingName.Default, SettingName.Note, SettingName.Check, @@ -586,6 +594,19 @@ function suggestAttributeName (compiler: Compiler, filepath: Filepath, offset: n })), ], }; + case ScopeKind.DEP: + return { + suggestions: [ + SettingName.Note, + SettingName.Color, + ].map((name) => ({ + label: name, + insertText: `${name}: `, + kind: CompletionItemKind.Property, + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + range: undefined as any, + })), + }; case ScopeKind.CHECKS: return { suggestions: [ @@ -753,7 +774,8 @@ function suggestInSubField ( return suggestInIndex(compiler, filepath, offset); case ScopeKind.ENUM: return suggestInEnumField(compiler, filepath, offset, container); - case ScopeKind.REF: { + case ScopeKind.REF: + case ScopeKind.DEP: { const suggestions = suggestInRefField(compiler, filepath, offset); return ( @@ -791,6 +813,7 @@ function suggestTopLevelElementType (): CompletionList { 'Enum', 'Project', 'Ref', + 'Dep', 'TablePartial', 'Records', 'DiagramView', @@ -884,6 +907,7 @@ function suggestInProjectField ( 'Enum', 'Note', 'Ref', + 'Dep', 'TablePartial', ]; if (!container?.callee) {