Skip to content

DBX-6689 Support Diagram View as code syntax#849

Open
huyphung1602 wants to merge 26 commits intomasterfrom
diagram-view-final-solution-2
Open

DBX-6689 Support Diagram View as code syntax#849
huyphung1602 wants to merge 26 commits intomasterfrom
diagram-view-final-solution-2

Conversation

@huyphung1602
Copy link
Copy Markdown
Collaborator

Summary

Support Diagram View as code syntaxes

// Show all entities
DiagramView view_name { * }

// Equivalent — explicitly show all per sub-block
DiagramView view_name {
  Tables { * }
  Notes { * }
  TableGroups { * }
  Schemas { * }
}
// Show nothing (empty body)
DiagramView view_name {}

// Equivalent — explicitly hide all per sub-block
DiagramView view_name {
  Tables {}
  Notes {}
  TableGroups {}
  Schemas {}
}

// Omitting is the same as hiding — this also shows nothing
DiagramView view_name {
  Tables {}
}
// Specific entities with schema-qualified table names
DiagramView view_name_1 {
  Tables {
    users
    articles
    ecom.orders
  }
}

// Mixed wildcards and specific items
DiagramView view_name_2 {
  Tables {
    * // all tables
  }

  // Visible notes
  Notes {
    reminder_note
  }

  // Visible table groups
  TableGroups {
    group_2
  }

  // Visible schemas
  Schemas {
    core
  }
}

Issue

(issue link here)

Lasting Changes (Technical)

  • Add gitnexus skills, use gitnexus to improve codebase query
  • New DBML element: DiagramView — defines named, filtered views controlling which tables, notes, table groups, and schemas are visible
  • Validator — enforces top-level context, unique names, valid sub-blocks (Tables, Notes, TableGroups, Schemas), wildcard rules
  • Binder — resolves entity references in sub-blocks to their declared symbols (tables, notes, groups, schemas)
  • Interpreter — produces FilterConfig output with tri-state visibility ([] = show all, [...] = specific, null = hide all) and Trinity union rule
  • syncDiagramView API — programmatic create/update/rename/delete of DiagramView blocks in DBML source text
  • Monaco integration — Monarch token provider for syntax highlighting + autocomplete suggestions for DiagramView blocks and sub-blocks
  • Parser — * wildcard support as a valid identifier
  • Symbol system — new DiagramViewSymbol, DiagramViewFieldSymbol, StickyNoteSymbol classes
  • Public exports — syncDiagramView, dbmlMonarchTokensProvider, types DiagramView, FilterConfig, DiagramViewSyncOperation from both @dbml/parse and @dbml/core
  • Snapshot updates — all existing interpreter outputs gain diagramViews: []

Checklist

Please check directly on the box once each of these are done

  • Documentation (if necessary)
  • Lint Checks Passed
  • Unit Tests Passed
  • Coverage Tests Passed
  • Integration Tests Passed
  • Code Review

huyphung1602 and others added 23 commits March 12, 2026 17:16
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new symbol classes for DiagramView support:
- DiagramViewSymbol: Represents a DiagramView block with its own symbol table
- DiagramViewFieldSymbol: Represents DiagramView fields (table/note/group/schema references)

Follows the same pattern as TableGroupSymbol and TableGroupFieldSymbol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add DiagramView and DiagramViewField to SymbolKind enum
- Add createDiagramViewSymbolIndex and createDiagramViewFieldSymbolIndex
- Update createNodeSymbolIndex to handle new symbol kinds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add DiagramViewValidator class with validation for:
  - Name requirements (required, single identifier)
  - Top-level context validation
  - Allowed sub-blocks (Tables, Notes, TableGroups, Schemas)
  - Wildcard usage warning
  - Duplicate field detection
- Add error codes for DiagramView validation
- Register validator in pickValidator switch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add DiagramViewBinder for binding references in DiagramView blocks
- Add DiagramViewInterpreter for interpreting DiagramView syntax
- Add FilterConfig and DiagramView types to interpreter types
- Register binder in pickBinder function
- Register interpreter in main interpreter
- Update Database type to include diagramViews array
- Update snapshots

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add syncDiagramView function for synchronizing DiagramView blocks
- Support create, update, delete operations on DiagramView blocks
- Generate properly formatted DiagramView DBML syntax
- Export DiagramView, FilterConfig, and DiagramViewSyncOperation types
- Export syncDiagramView from package index

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When at least one Trinity dimension (tables/tableGroups/schemas) is
explicitly specified with a non-null value in a DiagramView body,
omitted Trinity dims are promoted from null → [] (show all). The Notes
dimension remains independent and stays null when omitted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a table has an alias (e.g., Table users as U), the DiagramView
interpreter now resolves the alias to the real table name via the
bound referee symbol. Previously, using an alias in Tables { U }
would output { name: 'U' } which the renderer couldn't find.

Now the interpreter checks field.callee's referee (set by the binder)
and extracts the real name from the original declaration, falling back
to the literal text for unbound references.
@linear
Copy link
Copy Markdown

linear bot commented Mar 27, 2026

@github-actions
Copy link
Copy Markdown

Coverage Report

Commit: 0301572

Overall Coverage

Metric Coverage
Lines ✅ 84.31% (5777/6852)
Statements ✅ 83.48% (6167/7387)
Functions ✅ 89.35% (1334/1493)
Branches ⚠️ 75.22% (3200/4254)

Package Coverage

Package Lines Statements Functions Branches
@dbml/dbml-cli ✅ 100.00% ✅ 100.00% ✅ 100.00% ✅ 100.00%
@dbml/dbml-connector ⚠️ 64.62% ⚠️ 64.36% ⚠️ 59.53% ⚠️ 59.09%
@dbml/dbml-core N/A N/A N/A N/A
@dbml/dbml-parse ✅ 87.46% ✅ 86.51% ✅ 93.25% ⚠️ 77.53%

⚠️ Coverage Warnings

The following packages have coverage below 80%:

  • @dbml/dbml-connector: 64.62% line coverage

Files with Coverage Below 80%

@dbml/dbml-connector

9 file(s) below 80% coverage
File Lines Statements Functions Branches
src/connectors/bigquery/index.ts 0.00% 0.00% 0.00% 0.00%
src/utils/credential-loader.ts 0.00% 0.00% 0.00% 0.00%
src/utils/helpers.ts 0.00% 0.00% 0.00% 0.00%
src/connectors/snowflake/index.ts 10.56% 10.31% 0.00% 0.00%
src/utils/parseSchema.ts 46.15% 42.85% 28.57% 27.27%
src/connectors/connector.ts 66.66% 66.66% 100.00% 57.14%
src/connectors/oracle/tables.ts 71.25% 67.39% 100.00% 56.96%
src/connectors/oracle/index.ts 80.00% 80.76% 100.00% 25.00%
src/connectors/oracle/utils.ts 85.71% 85.71% 100.00% 71.42%

@dbml/dbml-parse

42 file(s) below 80% coverage
File Lines Statements Functions Branches
src/compiler/queries/container/scope.ts 0.00% 0.00% 0.00% 100.00%
src/services/diagnostics/provider.ts 0.00% 0.00% 0.00% 0.00%
src/core/interpreter/elementInterpreter/project.ts 51.42% 51.42% 100.00% 36.36%
src/core/analyzer/symbol/utils.ts 52.00% 52.00% 100.00% 45.45%
src/core/interpreter/records/utils/data/sqlTypes.ts 56.25% 58.82% 75.00% 46.55%
src/core/analyzer/binder/elementBinder/diagramView.ts 62.02% 64.28% 79.16% 48.57%
src/core/analyzer/binder/elementBinder/note.ts 62.50% 64.70% 83.33% 50.00%
src/compiler/queries/utils.ts 64.83% 65.95% 88.88% 47.43%
src/core/interpreter/records/utils/data/values.ts 65.13% 57.14% 72.72% 50.37%
src/compiler/queries/parse.ts 66.66% 66.66% 66.66% 100.00%
src/compiler/queries/token.ts 66.66% 66.66% 66.66% 100.00%
src/core/analyzer/validator/elementValidators/indexes.ts 72.00% 74.07% 90.90% 59.61%
src/core/analyzer/binder/elementBinder/enum.ts 72.22% 73.68% 100.00% 62.50%
src/core/analyzer/validator/elementValidators/note.ts 73.07% 71.42% 76.92% 68.75%
src/core/analyzer/symbol/symbolIndex.ts 73.91% 74.46% 93.75% 42.85%
src/core/analyzer/analyzer.ts 75.00% 66.66% 60.00% 100.00%
src/core/analyzer/utils.ts 77.77% 76.36% 88.88% 75.24%
src/core/analyzer/binder/elementBinder/project.ts 77.77% 78.94% 100.00% 50.00%
src/core/analyzer/validator/elementValidators/checks.ts 77.77% 79.31% 93.75% 71.87%
src/core/analyzer/validator/elementValidators/records.ts 78.08% 78.66% 93.75% 74.19%
src/core/analyzer/validator/elementValidators/project.ts 78.12% 78.78% 100.00% 56.25%
src/core/utils.ts 78.57% 77.41% 80.00% 60.71%
src/core/analyzer/binder/elementBinder/ref.ts 78.78% 80.00% 90.90% 75.00%
src/core/analyzer/binder/elementBinder/indexes.ts 79.06% 77.77% 90.90% 68.18%
src/core/analyzer/binder/elementBinder/tableGroup.ts 80.00% 80.64% 100.00% 50.00%
src/compiler/queries/transform/syncDiagramView.ts 81.11% 76.69% 82.35% 69.35%
src/core/interpreter/records/utils/constraints/pk.ts 82.00% 80.00% 92.30% 54.54%
src/services/suggestions/utils.ts 82.35% 76.78% 92.85% 70.37%
src/core/analyzer/binder/elementBinder/records.ts 82.79% 83.33% 93.75% 71.42%
src/core/analyzer/validator/elementValidators/diagramView.ts 82.95% 80.85% 89.47% 70.83%
src/compiler/queries/container/token.ts 83.33% 85.71% 100.00% 75.00%
src/core/analyzer/validator/elementValidators/tablePartial.ts 83.43% 80.72% 87.23% 64.28%
src/core/analyzer/binder/elementBinder/tablePartial.ts 86.00% 86.53% 100.00% 69.23%
src/core/interpreter/elementInterpreter/diagramView.ts 86.51% 82.69% 70.00% 67.94%
src/core/parser/parser.ts 86.93% 87.08% 100.00% 79.25%
src/services/suggestions/recordRowSnippet.ts 88.67% 84.61% 100.00% 78.00%
src/compiler/queries/symbol.ts 91.30% 92.00% 100.00% 78.57%
__tests__/utils/compiler.ts 92.92% 92.75% 100.00% 71.42%
src/core/interpreter/elementInterpreter/sticky_note.ts 95.00% 95.23% 100.00% 66.66%
src/core/interpreter/records/utils/constraints/unique.ts 96.15% 93.93% 100.00% 66.66%
src/core/analyzer/symbol/symbolTable.ts 100.00% 100.00% 100.00% 75.00%
src/services/definition/provider.ts 100.00% 100.00% 100.00% 75.00%

let leftExpression: NormalExpressionNode | undefined;

if (isOpToken(this.peek())) {
// Allow * as a wildcard identifier (e.g. DiagramView Tables { * })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem: This change means that we allow * as regular table names, which is likely unintended.

Image

Suggested fix:

  • Support in Lexer instead: Lex * as a WildcardToken instead of an operator token.
  • In parser, add WildcardNode and allow WildcardNode in function application node's callee.
  • In all validator except for DiagramViewValidator, flag an error when WildcardNode is encountered.

@@ -1,5 +1,5 @@
{
"version": "6.5.0",
"version": "7.0.0-alpha.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to revert the version back into 6.5.0 when we merge the PR nhe anh :v

);

if (hasWildcard && hasSpecificItems) {
errors.push(new CompileWarning(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompilerWarning should go into a separate warnings array. we do not mix warnings inside errors 👀

validateFields (fields: FunctionApplicationNode[]): (CompileError | CompileWarning)[] {
return fields.flatMap((field) => {
// Body-level {*} is valid shorthand for "show all entities"
if (isWildcardExpression(field.callee)) {
Copy link
Copy Markdown
Contributor

@huydo862003 huydo862003 Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* <name> <name> in DiagramView should be an error

Image

Should check that field.args is empty when field.callee is wildcard also 👀

return errors;
}

registerSubBlockFields (sub: ElementDeclarationNode): CompileError[] {
Copy link
Copy Markdown
Contributor

@huydo862003 huydo862003 Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can registerSubBlockFields be called inside validateSubblock instead of validateSubElement? 🤔 it makes more sense to me

if (symbol instanceof PartialInjectionSymbol) {
return SymbolKind.PartialInjection;
}
if (symbol instanceof StickyNoteSymbol) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these are missing DiagramViewSymbol and DiagramViewFieldSymbol

Comment on lines +180 to +190
return bindees.flatMap((bindee) => {
const schemaBindee = bindee.variables.pop();
if (!schemaBindee) {
return [];
}

return lookupAndBindInScope(this.ast, [
{ node: schemaBindee, kind: SymbolKind.Schema },
]);
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return bindees.flatMap((bindee) => {
const schemaBindee = bindee.variables.pop();
if (!schemaBindee) {
return [];
}
return lookupAndBindInScope(this.ast, [
{ node: schemaBindee, kind: SymbolKind.Schema },
]);
});
});
return lookupAndBindInScope(this.ast, bindees.map((b) => ({ node: b, kind: SymbolKind.Schema })));
});
});

something like this would be better, as in binder, schemas can be nested such as schema1.schema2.schema3.... We should bind all of them.

Comment on lines +32 to +36
/** Internal: tracks which dims used explicit wildcard (*). Stripped before output. */
_explicitWildcards?: Set<string>;
/** Internal: tracks which dims were explicitly declared in the DBML. Stripped before output. */
_explicitlySet?: Set<string>;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest in IntepreterDatabase, we have a Map<DiagramView, explicitWildcards/explicitlySet> instead. instead of DiagramView._explicitWildcards, we can access via map.get(DiagramView)

InterpreterDatabase is meant for dirty working state.

if (
element instanceof ElementDeclarationNode
&& element.parent instanceof ElementDeclarationNode
&& element.parent.type?.value.toLowerCase() === 'diagramview'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use ElementKind.DiagramView instead of diagramview here 👀

Comment on lines +855 to +870
suggestions: [...compiler.parse.publicSymbolTable().entries()].flatMap(([index]) => {
const res = destructureIndex(index).unwrap_or(undefined);
if (res === undefined) return [];
const { kind, name } = res;
if (kind !== SymbolKind.Table && kind !== SymbolKind.Schema) return [];
return {
label: name,
insertText: name,
insertTextRules: CompletionItemInsertTextRule.KeepWhitespace,
kind: pickCompletionItemKind(kind),
range: undefined as any,
};
}),
}).suggestions,
],
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have suggestNamesInScope for this. See suggestInRefField function for example usage. Similar to Tablegroups, Schemas suggestions below

type FilterConfig,
} from '@/core/interpreter/types';

// Export syncDiagramView transform
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our convention is that compiler queries should be exposed via Compiler's methods instead of bare function like this. You can check renameTable :v

lines.push(' Tables { * }');
} else {
const tableNames = visibleEntities.tables.map((t) =>
t.schemaName === 'public' ? t.name : `${t.schemaName}.${t.name}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have DEFAULT_SCHEMA_NAME for public nhe a

return applyTextEdits(dbml, edits);
}

function applyDelete (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can applyDelete reuse applyTextEdits khong ta 🤔

recordsElements: ElementDeclarationNode[];
cachedMergedTables: Map<Table, Table>; // map Table to Table that has been merged with table partials
source: string;
diagramViews?: Map<ElementDeclarationNode, DiagramView>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This need not be optional 🤔 just initialize it in the interpreter?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants