Skip to content

Commit 181d24b

Browse files
authored
Merge pull request #844 from holistics/chore/link-sample-id-with-table
feat: link sample id with table id
2 parents 1627dcb + 9e1ae70 commit 181d24b

28 files changed

Lines changed: 521 additions & 11 deletions
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import Parser from '../../../src/parse/Parser';
2+
import { test, beforeAll, describe, expect } from 'vitest';
3+
4+
const DBML = `
5+
Table users {
6+
id integer [pk]
7+
name varchar
8+
}
9+
10+
Table posts {
11+
id integer [pk]
12+
content text
13+
}
14+
15+
Records users(id, name) {
16+
1, "Alice"
17+
2, "Bob"
18+
}
19+
20+
Records posts(id, content) {
21+
1, "Hello World"
22+
}
23+
`;
24+
25+
describe('@dbml/core - model_structure - records', () => {
26+
let database: ReturnType<Parser['parse']>;
27+
28+
beforeAll(() => {
29+
database = new Parser().parse(DBML, 'dbmlv2');
30+
});
31+
32+
test('database.records contains all records', () => {
33+
expect(database.records.length).toBe(2);
34+
});
35+
36+
test('table.records is populated after linking', () => {
37+
const usersTable = database.schemas[0].tables.find((t) => t.name === 'users');
38+
const postsTable = database.schemas[0].tables.find((t) => t.name === 'posts');
39+
40+
expect(usersTable!.records.length).toBe(1);
41+
expect(postsTable!.records.length).toBe(1);
42+
});
43+
44+
test('table.records holds the same object references as database.records', () => {
45+
const usersTable = database.schemas[0].tables.find((t) => t.name === 'users');
46+
47+
expect(database.records).toContain(usersTable!.records[0]);
48+
});
49+
50+
test('linked record has correct tableName and values', () => {
51+
const usersTable = database.schemas[0].tables.find((t) => t.name === 'users');
52+
const record = usersTable!.records[0];
53+
54+
expect(record.tableName).toBe('users');
55+
expect(record.values.length).toBe(2);
56+
});
57+
58+
test('linked record has tableId set to the table id', () => {
59+
const usersTable = database.schemas[0].tables.find((t) => t.name === 'users');
60+
expect(usersTable!.records[0].tableId).toBe(usersTable!.id);
61+
});
62+
63+
test('table without records has empty records array', () => {
64+
const db = new Parser().parse(`
65+
Table empty_table {
66+
id integer [pk]
67+
}
68+
`, 'dbmlv2');
69+
70+
expect(db.schemas[0].tables[0].records).toEqual([]);
71+
});
72+
73+
describe('normalized model', () => {
74+
test('table has recordIds in normalized model', () => {
75+
const normalizedModel = database.normalize();
76+
const usersTable = database.schemas[0].tables.find((t) => t.name === 'users');
77+
const postsTable = database.schemas[0].tables.find((t) => t.name === 'posts');
78+
79+
expect(normalizedModel.tables[usersTable!.id].recordIds).toEqual([usersTable!.records[0].id]);
80+
expect(normalizedModel.tables[postsTable!.id].recordIds).toEqual([postsTable!.records[0].id]);
81+
});
82+
83+
test('normalized records contain tableId', () => {
84+
const normalizedModel = database.normalize();
85+
const usersTable = database.schemas[0].tables.find((t) => t.name === 'users');
86+
const recordId = usersTable!.records[0].id;
87+
88+
expect(normalizedModel.records[recordId].tableId).toBe(usersTable!.id);
89+
});
90+
91+
test('table without records has empty recordIds in normalized model', () => {
92+
const db = new Parser().parse(`
93+
Table empty_table {
94+
id integer [pk]
95+
}
96+
`, 'dbmlv2');
97+
98+
const normalizedModel = db.normalize();
99+
const table = db.schemas[0].tables[0];
100+
expect(normalizedModel.tables[table.id].recordIds).toEqual([]);
101+
});
102+
});
103+
});

packages/dbml-core/src/model_structure/check.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
1+
/// <reference path="../../types/model_structure/check.d.ts" />
2+
// @ts-check
13
import Element from './element';
24

5+
/**
6+
* @type Check
7+
*/
38
class Check extends Element {
49
constructor ({
510
token, name, expression, table, column = null, injectedPartial = null,
611
} = {}) {
712
super(token);
813
this.name = name;
14+
/** @type {string} */
915
this.expression = expression;
16+
/** @type {import('../../types/model_structure/table').default} */
1017
this.table = table;
18+
/** @type {import('../../types/model_structure/field').default} */
1119
this.column = column;
20+
/** @type {import('../../types/model_structure/tablePartial').default} */
1221
this.injectedPartial = injectedPartial;
22+
/** @type {import('../../types/model_structure/dbState').default} */
1323
this.dbState = this.table.dbState;
1424
this.generateId();
1525
}
1626

1727
generateId () {
28+
/** @type {number} */
1829
this.id = this.dbState.generateId('checkId');
1930
}
2031

@@ -39,6 +50,9 @@ class Check extends Element {
3950
};
4051
}
4152

53+
/**
54+
* @param {import('../../types/model_structure/database').NormalizedDatabase} model
55+
*/
4256
normalize (model) {
4357
model.checks[this.id] = {
4458
id: this.id,

packages/dbml-core/src/model_structure/database.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import DbState from './dbState';
1313
import TablePartial from './tablePartial';
1414

1515
class Database extends Element {
16+
/**
17+
* @param {import('../../types/model_structure/database').RawDatabase} param0
18+
*/
1619
constructor ({
1720
schemas = [],
1821
tables = [],
@@ -29,6 +32,7 @@ class Database extends Element {
2932
this.dbState = new DbState();
3033
this.generateId();
3134
this.hasDefaultSchema = false;
35+
/** @type {import('../../types/model_structure/schema').default[]} */
3236
this.schemas = [];
3337
this.notes = [];
3438
this.note = project.note ? get(project, 'note.value', project.note) : null;
@@ -51,6 +55,7 @@ class Database extends Element {
5155
this.processSchemas(schemas);
5256
this.processSchemaElements(enums, ENUM);
5357
this.processSchemaElements(tables, TABLE);
58+
this.linkRecordsToTables();
5459
this.processSchemaElements(notes, NOTE);
5560
this.processSchemaElements(refs, REF);
5661
this.processSchemaElements(tableGroups, TABLE_GROUP);
@@ -157,6 +162,28 @@ class Database extends Element {
157162
});
158163
}
159164

165+
linkRecordsToTables () {
166+
// Build a map of [schemaName][tableName] -> table for O(1) lookup
167+
const tableMap = {};
168+
this.schemas.forEach((schema) => {
169+
tableMap[schema.name] = {};
170+
schema.tables.forEach((table) => {
171+
tableMap[schema.name][table.name] = table;
172+
});
173+
});
174+
175+
// Link records to tables using the map
176+
this.records.forEach((record) => {
177+
// Fallback to 'public' if schemaName is null, undefined
178+
const schemaName = record.schemaName ?? DEFAULT_SCHEMA_NAME;
179+
const table = tableMap[schemaName]?.[record.tableName];
180+
if (!table) return;
181+
182+
record.tableId = table.id;
183+
table.records.push(record);
184+
});
185+
}
186+
160187
findOrCreateSchema (schemaName) {
161188
let schema = this.schemas.find((s) => s.name === schemaName || s.alias === schemaName);
162189
// create new schema if schema not found

packages/dbml-core/src/model_structure/dbState.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,41 @@
11
export default class DbState {
22
constructor () {
3+
/** @type {number} */
34
this.dbId = 1;
5+
/** @type {number} */
46
this.schemaId = 1;
7+
/** @type {number} */
58
this.enumId = 1;
9+
/** @type {number} */
610
this.tableGroupId = 1;
11+
/** @type {number} */
712
this.refId = 1;
13+
/** @type {number} */
814
this.tableId = 1;
15+
/** @type {number} */
916
this.noteId = 1;
17+
/** @type {number} */
1018
this.enumValueId = 1;
19+
/** @type {number} */
1120
this.endpointId = 1;
21+
/** @type {number} */
1222
this.indexId = 1;
23+
/** @type {number} */
1324
this.checkId = 1;
25+
/** @type {number} */
1426
this.fieldId = 1;
27+
/** @type {number} */
1528
this.indexColumnId = 1;
29+
/** @type {number} */
1630
this.recordId = 1;
31+
/** @type {number} */
1732
this.tablePartialId = 1;
1833
}
1934

35+
/**
36+
* @param {string} el
37+
* @returns {number}
38+
*/
2039
generateId (el) {
2140
const id = this[el];
2241
this[el] += 1;

packages/dbml-core/src/model_structure/element.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
11
class ElementError extends Error {
2+
/**
3+
* @param {string} message
4+
* @param {import('../../types/model_structure/element').Token} location
5+
*/
26
constructor (message, location = { start: { line: 1, column: 1 } }) {
37
super(message);
8+
/** @type {import('../../types/model_structure/element').Token} */
49
this.location = location;
10+
/** @type {string} */
511
this.error = 'error';
612
}
713
}
814

915
class Element {
1016
constructor (token) {
17+
/** @type {import('../../types/model_structure/element').Token} */
1118
this.token = token;
1219
}
1320

21+
/**
22+
* @param {any} selection
23+
*/
1424
bind (selection) {
25+
/** @type {any} */
1526
this.selection = selection;
1627
}
1728

29+
/**
30+
* @param {string} message
31+
*/
1832
error (message) {
1933
throw new ElementError(message, this.token);
2034
}

packages/dbml-core/src/model_structure/endpoint.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,27 @@ import { DEFAULT_SCHEMA_NAME } from './config';
33
import { shouldPrintSchema, shouldPrintSchemaName } from './utils';
44

55
class Endpoint extends Element {
6+
/**
7+
* @param {{ tableName: string, schemaName: string, fieldNames: string[], relation: any, token: import('../../types/model_structure/element').Token, ref: import('../../types/model_structure/ref').default }} param0
8+
*/
69
constructor ({
710
tableName, schemaName, fieldNames, relation, token, ref,
811
}) {
912
super(token);
13+
/** @type {any} */
1014
this.relation = relation;
1115

16+
/** @type {string} */
1217
this.schemaName = schemaName;
18+
/** @type {string} */
1319
this.tableName = tableName;
20+
/** @type {string[]} */
1421
this.fieldNames = fieldNames;
22+
/** @type {import('../../types/model_structure/field').default[]} */
1523
this.fields = [];
24+
/** @type {import('../../types/model_structure/ref').default} */
1625
this.ref = ref;
26+
/** @type {import('../../types/model_structure/dbState').default} */
1727
this.dbState = this.ref.dbState;
1828
this.generateId();
1929
// Use name of schema,table and field object
@@ -30,14 +40,23 @@ class Endpoint extends Element {
3040
}
3141

3242
generateId () {
43+
/** @type {number} */
3344
this.id = this.dbState.generateId('endpointId');
3445
}
3546

47+
/**
48+
* @param {import('../../types/model_structure/endpoint').default} endpoint
49+
* @returns {boolean}
50+
*/
3651
equals (endpoint) {
3752
if (this.fields.length !== endpoint.fields.length) return false;
3853
return this.compareFields(endpoint);
3954
}
4055

56+
/**
57+
* @param {import('../../types/model_structure/endpoint').default} endpoint
58+
* @returns {boolean}
59+
*/
4160
compareFields (endpoint) {
4261
const sortedThisFieldIds = this.fields.map((field) => field.id).sort();
4362
const sortedEndpointFieldIds = endpoint.fields.map((field) => field.id).sort();
@@ -69,6 +88,10 @@ class Endpoint extends Element {
6988
};
7089
}
7190

91+
/**
92+
* @param {string[]} fieldNames
93+
* @param {import('../../types/model_structure/table').default} table
94+
*/
7295
setFields (fieldNames, table) {
7396
let newFieldNames = (fieldNames && fieldNames.length) ? [...fieldNames] : [];
7497
if (!newFieldNames.length) {
@@ -96,6 +119,9 @@ class Endpoint extends Element {
96119
});
97120
}
98121

122+
/**
123+
* @param {import('../../types/model_structure/database').NormalizedDatabase} model
124+
*/
99125
normalize (model) {
100126
model.endpoints[this.id] = {
101127
id: this.id,

0 commit comments

Comments
 (0)