Skip to content
Open
23 changes: 23 additions & 0 deletions dbml-homepage/docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,26 @@ records users(id, name, age, status, created_at) {
3, 'Charlie', , Status.pending, '2024-01-15'
}
```

### Example Records

Records blocks can be marked as **example records**. Example records are treated as sample data — they are preserved in the DBML output but excluded from SQL `INSERT` statements during export.

Example records are desirable if the records only serve as illustrative examples of real data.

```text
Table users {
id int [pk]
name varchar

records [example] {
1, 'Alice'
2, 'Bob'
}
}

records users(id, name) [example] {
1, 'Alice'
2, 'Bob'
}
```
63 changes: 63 additions & 0 deletions packages/dbml-core/__tests__/examples/exporter/exporter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,69 @@ describe('@dbml/core - ref inactive setting', () => {
});
});

const DBML_WITH_EXAMPLE_RECORDS = `
Table users {
id integer [pk]
name varchar
}

Records users(id, name) [example] {
1, 'Alice'
2, 'Bob'
}
`.trim();

const DBML_WITH_MIXED_RECORDS = `
Table users {
id integer [pk]
name varchar
}

Table posts {
id integer [pk]
title varchar
}

Records users(id, name) [example] {
1, 'Alice'
}

Records posts(id, title) {
1, 'First Post'
}
`.trim();

describe('@dbml/core - records example flag', () => {
test('exports example flag in DBML output', () => {
const res = exporter.export(DBML_WITH_EXAMPLE_RECORDS, 'dbml');
expect(res).toContain('[example]');
expect(res).toContain('Alice');
});

test('excludes example records from SQL output', () => {
for (const format of ['mysql', 'postgres', 'mssql', 'oracle'] as const) {
const res = exporter.export(DBML_WITH_EXAMPLE_RECORDS, format);
expect(res).not.toContain('INSERT');
expect(res).not.toContain('Alice');
}
});

test('exports only non-example records as SQL', () => {
for (const format of ['mysql', 'postgres', 'mssql', 'oracle'] as const) {
const res = exporter.export(DBML_WITH_MIXED_RECORDS, format);
expect(res).toContain('First Post');
expect(res).not.toContain('Alice');
}
});

test('preserves example flag in DBML roundtrip with mixed records', () => {
const res = exporter.export(DBML_WITH_MIXED_RECORDS, 'dbml');
expect(res).toContain('[example]');
expect(res).toContain('Alice');
expect(res).toContain('First Post');
});
});

describe('@dbml/core - exporter flags', () => {
describe('includeRecords', () => {
test('includes records by default', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/dbml-core/src/export/DbmlExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ class DbmlExporter {
` ${row.map(formatRecordValue).join(', ')}`,
);

return `Records ${tableRef}(${columnList}) {\n${rowStrs.join('\n')}\n}\n`;
const exampleFlag = groupRecords.some((r) => r.example) ? ' [example]' : '';
return `Records ${tableRef}(${columnList})${exampleFlag} {\n${rowStrs.join('\n')}\n}\n`;
});

return recordStrs.join('\n');
Expand Down
2 changes: 1 addition & 1 deletion packages/dbml-core/src/export/MysqlExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

class MySQLExporter {
static exportRecords (model) {
const records = Object.values(model.records || {});
const records = Object.values(model.records || {}).filter((r) => !r.example);
if (isEmpty(records)) {
return [];
}
Expand Down
2 changes: 1 addition & 1 deletion packages/dbml-core/src/export/OracleExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {

class OracleExporter {
static exportRecords (model) {
const records = Object.values(model.records || {});
const records = Object.values(model.records || {}).filter((r) => !r.example);
if (isEmpty(records)) {
return [];
}
Expand Down
2 changes: 1 addition & 1 deletion packages/dbml-core/src/export/PostgresExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ const POSTGRES_RESERVED_KEYWORDS = [

class PostgresExporter {
static exportRecords (model) {
const records = Object.values(model.records || {});
const records = Object.values(model.records || {}).filter((r) => !r.example);
if (isEmpty(records)) {
return [];
}
Expand Down
2 changes: 1 addition & 1 deletion packages/dbml-core/src/export/SqlServerExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

class SqlServerExporter {
static exportRecords (model) {
const records = Object.values(model.records || {});
const records = Object.values(model.records || {}).filter((r) => !r.example);
if (isEmpty(records)) {
return [];
}
Expand Down
3 changes: 2 additions & 1 deletion packages/dbml-core/src/model_structure/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ class Database extends Element {

processRecords (rawRecords) {
rawRecords.forEach(({
schemaName, tableName, columns, values, token,
schemaName, tableName, columns, values, example, token,
}) => {
this.records.push({
id: this.dbState.generateId('recordId'),
schemaName,
tableName,
columns,
values,
example,
token,
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/dbml-core/types/model_structure/database.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface RawTableRecord {
schemaName: string | undefined;
tableName: string;
columns: string[];
example?: boolean;
token: Token;
values: {
value: any;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
describe, expect, test,
} from 'vitest';
import {
interpret,
} from '@tests/utils';

describe('[example - record] example setting', () => {
test('should set example on top-level records with [example]', () => {
const source = `
Table users {
id int [pk]
name varchar
}
records users(id, name) [example] {
1, 'Alice'
}
`;
const result = interpret(source);
expect(result.getErrors().length).toBe(0);

const db = result.getValue()!;
expect(db.records[0].example).toBe(true);
});

test('should not set example on records without [example]', () => {
const source = `
Table users {
id int [pk]
name varchar
}
records users(id, name) {
1, 'Alice'
}
`;
const result = interpret(source);
expect(result.getErrors().length).toBe(0);

const db = result.getValue()!;
expect(db.records[0].example).toBeUndefined();
});

test('should set example on nested records with [example]', () => {
const source = `
Table users {
id int [pk]
name varchar

records [example] {
1, 'Alice'
}
}
`;
const result = interpret(source);
expect(result.getErrors().length).toBe(0);

const db = result.getValue()!;
expect(db.records[0].example).toBe(true);
});

test('should set example on nested records with column list and [example]', () => {
const source = `
Table users {
id int [pk]
name varchar

records (id) [example] {
1
}
}
`;
const result = interpret(source);
expect(result.getErrors().length).toBe(0);

const db = result.getValue()!;
expect(db.records[0].example).toBe(true);
});

test('should not set example on nested records without [example]', () => {
const source = `
Table users {
id int [pk]
name varchar

records {
1, 'Alice'
}
}
`;
const result = interpret(source);
expect(result.getErrors().length).toBe(0);

const db = result.getValue()!;
expect(db.records[0].example).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -488,3 +488,53 @@ describe('[example] Suggestions Utils - Records', () => {
});
});
});

describe('[example] CompletionItemProvider - Records settings', () => {
it('should suggest only example setting on nested records', () => {
const program = `Table users {
id int [pk]
name varchar

records [] {
1, 'Alice'
}
}
`;
const layout = new MemoryProjectLayout();
layout.setSource(DEFAULT_ENTRY, program);
const compiler = new Compiler(layout);
const model = createMockTextModel(program);
const provider = new DBMLCompletionItemProvider(compiler);
// " records [] {" - inside the brackets, line 5 col 12
const position = createPosition(5, 12);
const result = provider.provideCompletionItems(model, position);

expect(result.suggestions.length).toBe(1);
expect(result.suggestions[0].label).toBe('example');
expect(result.suggestions[0].insertText).toBe('example');
});

it('should suggest example setting on top-level records', () => {
const program = `Table users {
id int [pk]
name varchar
}

Records users(id, name) [] {
1, 'Alice'
}
`;
const layout = new MemoryProjectLayout();
layout.setSource(DEFAULT_ENTRY, program);
const compiler = new Compiler(layout);
const model = createMockTextModel(program);
const provider = new DBMLCompletionItemProvider(compiler);
// "Records users(id, name) [] {" - inside the brackets, line 6 col 26
const position = createPosition(6, 26);
const result = provider.provideCompletionItems(model, position);

const exampleSuggestion = result.suggestions.find((s) => s.label === 'example');
expect(exampleSuggestion).not.toBeUndefined();
expect(exampleSuggestion!.insertText).toBe('example');
});
});
Loading
Loading