Skip to content

[DBX-6933] Feat: Support custom metadata element#932

Open
xuantho573 wants to merge 20 commits into
masterfrom
feat/custom-metadata
Open

[DBX-6933] Feat: Support custom metadata element#932
xuantho573 wants to merge 20 commits into
masterfrom
feat/custom-metadata

Conversation

@xuantho573

Copy link
Copy Markdown
Member

Summary

  • Short summary of the task, what have been done etc
  • Please include screenshots whenever possible (important).

Issue

(issue link here)

Lasting Changes (Technical)

(please list down: code changes/things that have wide-effect; new libraries/functions added that can be used by others; examples below)

  • (Added class EmailValidator to validate email address' validity)
  • (Added Tenant#is_trial? check)

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

@linear-code

linear-code Bot commented Jun 22, 2026

Copy link
Copy Markdown

DBX-6933

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Benchmark Result

dbml-parse

suite 🏠 master 🔀 this branch change
18k 1018.62ms ±6.26% 1032.327ms ±4.76% ⚪ +1.3%
25k 1407.832ms ±1.81% 1552.24ms ±6.14% 🔴 +10.3%

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Coverage Report

Commit: 448489d

Overall Coverage

Metric Coverage
Lines ✅ 84.95% (8848/10415)
Statements ✅ 82.23% (9583/11654)
Functions ✅ 87.56% (1844/2106)
Branches ⚠️ 73.17% (5712/7806)

Package Coverage

Package Lines Statements Functions Branches
dbml-cli ✅ 100.00% ✅ 100.00% ✅ 100.00% ✅ 100.00%
dbml-connector ⚠️ 64.62% ⚠️ 64.36% ⚠️ 59.53% ⚠️ 59.09%
dbml-core N/A N/A N/A N/A
dbml-parse ✅ 86.98% ✅ 83.92% ✅ 90.06% ⚠️ 74.20%

⚠️ Coverage Warnings

The following packages have coverage below 80%:

  • dbml-connector: 64.62% line coverage

Files with Coverage Below 80%

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-parse

82 file(s) below 80% coverage
File Lines Statements Functions Branches
src/compiler/queries/container/scope.ts 0.00% 0.00% 0.00% 0.00%
src/services/diagnostics/provider.ts 0.00% 0.00% 0.00% 0.00%
src/compiler/queries/pipeline/interpret.ts 11.76% 13.51% 66.66% 41.66%
src/services/suggestions/crossFile.ts 19.35% 19.44% 33.33% 10.00%
src/core/local_modules/enum/index.ts 48.00% 46.29% 57.14% 52.63%
src/core/global_modules/project/interpret.ts 52.50% 46.66% 83.33% 23.07%
src/compiler/projectLayout/layout.ts 54.16% 53.84% 75.00% 52.63%
src/core/local_modules/metadata/index.ts 55.55% 61.53% 75.00% 50.00%
src/compiler/queries/legacy/parse.ts 61.53% 53.33% 66.66% 25.00%
src/core/local_modules/ref/index.ts 62.50% 61.76% 100.00% 57.14%
src/core/types/report.ts 62.50% 55.55% 75.00% 57.14%
src/core/global_modules/records/utils/data/values.ts 63.30% 55.71% 72.72% 49.28%
src/core/types/symbol/symbols.ts 63.70% 56.00% 59.83% 45.00%
src/core/local_modules/tableGroup/validate.ts 64.55% 64.44% 71.42% 58.00%
src/compiler/queries/legacy/token.ts 66.66% 66.66% 66.66% 100.00%
src/core/global_modules/enum/bind.ts 66.66% 68.75% 83.33% 50.00%
src/core/local_modules/program/index.ts 66.66% 69.23% 75.00% 62.50%
src/core/global_modules/note/bind.ts 69.23% 71.42% 83.33% 50.00%
src/core/local_modules/checks/index.ts 69.23% 75.00% 100.00% 64.28%
src/core/local_modules/project/index.ts 69.23% 69.23% 100.00% 63.63%
src/core/local_modules/custom/index.ts 70.58% 71.42% 80.00% 68.75%
src/compiler/queries/utils.ts 73.10% 73.38% 90.90% 51.11%
src/core/local_modules/tablePartial/validate.ts 73.12% 71.83% 79.22% 57.67%
src/core/global_modules/project/bind.ts 73.33% 75.00% 100.00% 50.00%
src/core/local_modules/indexes/index.ts 73.68% 69.35% 63.63% 64.28%
src/core/local_modules/use/index.ts 73.91% 75.00% 75.00% 78.12%
src/core/local_modules/indexes/validate.ts 74.28% 74.66% 90.90% 56.00%
src/core/local_modules/note/index.ts 75.00% 75.86% 75.00% 72.72%
src/services/suggestions/utils/index.ts 75.00% 75.00% 100.00% 62.06%
src/core/parser/utils.ts 75.28% 75.56% 100.00% 81.67%
src/core/local_modules/records/index.ts 75.60% 76.19% 100.00% 68.08%
src/core/local_modules/project/validate.ts 75.86% 75.86% 100.00% 56.25%
src/core/global_modules/indexes/bind.ts 76.31% 74.35% 90.90% 68.96%
src/core/global_modules/records/utils/data/sqlTypes.ts 76.59% 80.64% 82.35% 72.22%
src/core/global_modules/tablePartial/interpret.ts 77.89% 70.64% 78.94% 53.73%
src/core/local_modules/note/validate.ts 78.00% 76.36% 83.33% 71.05%
src/core/types/filepath.ts 79.48% 78.57% 77.77% 70.37%
src/core/global_modules/utils.ts 79.54% 75.51% 72.72% 63.63%
src/core/global_modules/program/utils.ts 80.00% 80.00% 100.00% 70.00%
src/core/global_modules/tableGroup/bind.ts 80.00% 80.00% 100.00% 57.14%
__tests__/utils/compiler.ts 80.27% 77.27% 100.00% 54.68%
src/core/local_modules/records/validate.ts 80.28% 80.55% 93.75% 72.30%
src/core/global_modules/indexes/interpret.ts 81.39% 72.00% 100.00% 56.66%
src/core/local_modules/tablePartial/index.ts 81.48% 81.48% 100.00% 77.27%
src/core/local_modules/diagramView/validate.ts 81.57% 78.04% 84.21% 72.72%
src/core/local_modules/checks/validate.ts 81.63% 82.69% 93.75% 70.00%
src/core/local_modules/metadata/validate.ts 82.22% 84.00% 100.00% 71.79%
src/core/parser/parser.ts 82.72% 82.98% 100.00% 73.71%
src/core/global_modules/records/bind.ts 82.92% 83.72% 93.75% 67.30%
src/compiler/queries/container/token.ts 83.33% 85.71% 100.00% 75.00%
src/core/global_modules/ref/bind.ts 83.87% 83.87% 90.00% 75.00%
src/core/global_modules/records/utils/constraints/pk.ts 84.61% 81.42% 92.59% 56.52%
src/core/local_modules/table/validate.ts 84.92% 85.47% 90.24% 73.40%
src/core/local_modules/enum/validate.ts 85.48% 81.81% 81.25% 76.31%
src/services/suggestions/provider.ts 86.01% 82.16% 93.93% 74.18%
src/services/suggestions/utils/useMerger.ts 86.95% 81.13% 100.00% 46.34%
src/core/global_modules/tablePartial/bind.ts 87.03% 87.27% 100.00% 71.42%
src/core/global_modules/tableGroup/interpret.ts 88.37% 88.37% 100.00% 77.27%
src/services/suggestions/recordRowSnippet.ts 88.46% 87.09% 100.00% 73.80%
src/compiler/queries/nodeAtPosition.ts 88.88% 90.00% 100.00% 75.00%
src/core/global_modules/diagramView/index.ts 89.16% 86.23% 83.33% 78.18%
src/core/global_modules/metadata/resolve.ts 90.47% 78.57% 100.00% 72.00%
src/core/global_modules/table/interpret.ts 90.69% 84.72% 95.45% 67.70%
src/compiler/index.ts 90.76% 90.83% 75.00% 78.84%
src/core/global_modules/records/index.ts 90.90% 87.15% 100.00% 79.16%
src/core/global_modules/schema/index.ts 92.26% 82.46% 91.66% 70.07%
src/core/types/symbol/metadata.ts 92.46% 77.55% 97.72% 63.06%
src/core/global_modules/diagramView/interpret.ts 94.59% 90.40% 92.85% 75.75%
src/services/suggestions/use.ts 94.68% 87.61% 100.00% 77.45%
src/core/utils/interpret.ts 95.00% 91.25% 100.00% 74.64%
src/core/global_modules/note/interpret.ts 95.65% 95.65% 100.00% 75.00%
src/core/global_modules/records/interpret.ts 95.97% 92.59% 100.00% 79.54%
__tests__/examples/interpreter/multifile/utils.ts 96.00% 89.65% 100.00% 62.50%
src/compiler/queries/canonicalName.ts 96.96% 92.10% 100.00% 76.66%
src/core/global_modules/program/interpret.ts 97.46% 92.17% 100.00% 70.74%
src/core/types/errors.ts 98.55% 98.55% 66.66% 100.00%
src/compiler/queries/transform/renameTable.ts 99.05% 90.47% 100.00% 78.82%
src/core/global_modules/checks/interpret.ts 100.00% 88.88% 100.00% 60.00%
src/core/global_modules/metadata/interpret.ts 100.00% 90.47% 100.00% 76.66%
src/core/global_modules/program/index.ts 100.00% 85.71% 100.00% 75.00%
src/core/global_modules/records/utils/constraints/unique.ts 100.00% 95.00% 100.00% 62.50%
src/services/definition/provider.ts 100.00% 95.23% 100.00% 71.42%

@xuantho573 xuantho573 marked this pull request as ready for review June 24, 2026 02:18
@xuantho573 xuantho573 force-pushed the feat/custom-metadata branch from 5ed7ff2 to 15457bb Compare June 29, 2026 04:28
@xuantho573 xuantho573 force-pushed the feat/custom-metadata branch 12 times, most recently from 76bfb1a to 52c916e Compare July 8, 2026 01:50

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.

If you have time, please convert this file to .ts nhe :pepe-cuoi-dam

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.

convert this to .ts lun nho

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 one to ts lun nho :pepe-cuoi-guong. it's short btw

*/
constructor ({
name, alias, note, fields = [], indexes = [], checks = [], schema = {}, token, headerColor, noteToken = null, partials = [],
name, alias, note, fields = [], indexes = [], checks = [], schema = {}, token, headerColor, noteToken = null, partials = [], metadata,

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 one metadata doesn't have a default value like {} others? :* Although we use ?? {} below

note: string;
noteToken: Token;
color: Color;
constructor({ name, token, tables, schema, note, color }: RawTableGroup);

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.

No metadata in constructor? 🤔


// Programs that can both (a) reach this metadata's declaration file and
// (b) see ALL the given target symbols in their nested schema.
reachableOwners (compiler: Compiler, targets: NodeSymbol[]): NodeSymbol[] {

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 name is a bit hard to distinguished with owners :*

Comment on lines +417 to +419
const targetNode = nameNode instanceof InfixExpressionNode && nameNode.rightExpression
? nameNode.rightExpression
: nameNode;

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 can use getRightmostVariable here 🤔

// qualified `targetName` together identify the target). Hence no alias/settings.
// e.g. Metadata Table public.users { owner: 'scott' }
// e.g. Metadata Column public.users.id { pii: true }
export class MetadataDeclarationNode extends SyntaxNode {

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.

Consider merging with ElementDeclarationNode nho :3

SyntaxNodes are meant to group structurally similar nodes with similar roles. At least that's our design

getTargetKind (): MetadataTargetKind | undefined {
const value = this.targetKind?.value?.toLowerCase();
if (value === undefined) return undefined;
return ALLOWED_METADATA_TARGET_KINDS.find((k) => k === value);

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.

Reconsider METADATA_TARGET_KIND to not include schema? 👀

Comment on lines +278 to +290
// Intermediate, per-block interpreted form of a Metadata declaration. NOT part
// of the emitted Database: the interpreter merges every block targeting the same
// element and attaches the merged `values` onto that element's `metadata` field
// (Table/Column/TableGroup/Note). This shape only lives inside the metadata pass.
export interface MetadataElement {
target: {
kind: MetadataTargetKind;
name: string[];
};
valueWithTokens: Record<string, { value: string; token: TokenPosition }>;
token: TokenPosition;
}

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.

schemaJson is meant for consumer-visible only types only 👀

private useSpecifier (): UseSpecifierNode {
const args: {
importKind?: SyntaxToken;
subKind?: SyntaxToken;

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.

:3 delete delete

}

// consume the qualified target name (e.g. `public.users`)
if (!this.check(SyntaxTokenKind.COLON, SyntaxTokenKind.LBRACE, SyntaxTokenKind.LBRACKET)) {

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.

Would this cause parse errors:

Metadata Table t [...] {}
                  ^^^

return membersList.find((m: NodeSymbol) => m instanceof SchemaSymbol && m.isPublicSchema());
}

export function getGlobalSymbol (compiler: Compiler, filepath: Filepath): NodeSymbol | undefined {

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.

maybe getProgramSymbol is a better name, maybe we can tighten the return type?

I think this can be reused in reachableOwners

@xuantho573 xuantho573 force-pushed the feat/custom-metadata branch from 6049528 to b317acb Compare July 8, 2026 09:49
}

private validateSettingList (settingList?: ListExpressionNode): CompileError[] {
const aggReport = aggregateSettingList(settingList);

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.

help me migrate this function to use the below validate helper function lun nho :3

return [
new CompileError(
CompileErrorCode.INVALID_NAME,
'A Metadata target\'s name must be a schema-qualified 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.

Must be a schema-qualified name? 🤔

return [
new CompileError(
CompileErrorCode.INVALID_NAME,
'Invalid array as Metadata target\'s 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.

The point of this was to catch user's accidental errors like Metadata Table name[setting] {}. Can lookup the copywriting in other elements 👀

Comment on lines +172 to +175
// Only validate the value when a real callee is present. When a key has
// no value the parser emits INVALID_OPERAND via error recovery and
// synthesises a FunctionApplicationNode whose callee is an EmptyNode —
// we must not pile on with a second INVALID_METADATA_FIELD in that case.

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.

Shorter comment pls :3 Something like "Metadata's simple body without a field is already caught by the parser, so validator skips this case"

Comment on lines +126 to +133
// A key that names a builtin setting for this target kind is validated by
// the target element's own spec (it will be written onto the typed field).
// Color-/note-named keys on a target that does NOT support them are absent
// from the map and fall through to generic scalar validation, staying as
// custom metadata. The write pass routes through the same per-kind maps, so
// validation and writing agree by construction.
// `key` is a free-form lowercased string; a non-builtin key simply misses
// the map (undefined) and falls through to generic validation below.

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.

:V make this more digestible pls. What do we need to be aware of?

SymbolKind.Column,
],
[MetadataTargetKind.TableGroup]: [
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.

:V I dont think table group support schema

SymbolKind.TableGroup,
],
[MetadataTargetKind.Note]: [
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.

Note also doesnt support schema 👀

if (meta instanceof MetadataElementMetadata) {
// Unresolved targets already raise BINDING_ERROR at bind time; there
// is no element to attach to, so skip them here.
const targetSymbol = meta.target(this.compiler);

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.

possibly can we have a dedicated function for this custom metadata

Comment on lines +9 to +13
// A single builtin metadata field, bundling validation (used by both the inline
// setting-list path and the metadata-block path) and the dumb-writer assign
// (used only in the interpret pass to promote block-form values onto typed
// fields). Both fields are required so that validate/assign key parity is
// structural — impossible to violate.

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 we make this comment more concise :V

token: Token;
}

export type Metadata = CustomMetadata;

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.

Why not just re-export the type :* too many names cause confusion

expect(locations[0].range.startLineNumber).toBe(1);
});

// TODO(metadata): METADATA_TARGET_NOT_FOUND is raised by the binder, but

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.

Hmmm

@xuantho573 xuantho573 force-pushed the feat/custom-metadata branch from 8bbd6a6 to df6944d Compare July 10, 2026 04:05
@xuantho573 xuantho573 force-pushed the feat/custom-metadata branch from aeea30d to 4b6eed4 Compare July 10, 2026 11:05
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.

2 participants