mirror of
https://github.com/chartdb/chartdb.git
synced 2025-12-30 16:09:33 -06:00
fix: mark primary key columns as NOT NULL when importing DBML (#1006)
* fix: mark primary key columns as NOT NULL when importing DBML * fix --------- Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
{"id":"mqqwkkodxt6p","name":"Diagram 3","createdAt":"2025-09-16T15:33:25.300Z","updatedAt":"2025-09-16T15:33:31.563Z","databaseType":"postgresql","tables":[{"id":"loyxg6mafzos5u971uirjs3zh","name":"table_3","schema":"","order":0,"fields":[{"id":"29e2p9bom0uxo1n0a9ze5auuy","name":"id","type":{"name":"bigint","id":"bigint","usageLevel":2},"nullable":true,"primaryKey":true,"unique":true,"createdAt":1758036805300}],"indexes":[{"id":"5gf0aeptch1uk1bxv0x89wxxe","name":"pk_table_3_id","fieldIds":["29e2p9bom0uxo1n0a9ze5auuy"],"unique":true,"isPrimaryKey":true,"createdAt":1758036811564}],"x":0,"y":0,"color":"#8eb7ff","isView":false,"createdAt":1758036805300,"diagramId":"mqqwkkodxt6p"}],"relationships":[],"dependencies":[],"areas":[],"customTypes":[]}
|
||||
{"id":"mqqwkkodxt6p","name":"Diagram 3","createdAt":"2025-09-16T15:33:25.300Z","updatedAt":"2025-09-16T15:33:31.563Z","databaseType":"postgresql","tables":[{"id":"loyxg6mafzos5u971uirjs3zh","name":"table_3","schema":"","order":0,"fields":[{"id":"29e2p9bom0uxo1n0a9ze5auuy","name":"id","type":{"name":"bigint","id":"bigint","usageLevel":2},"nullable":false,"primaryKey":true,"unique":true,"createdAt":1758036805300}],"indexes":[{"id":"5gf0aeptch1uk1bxv0x89wxxe","name":"pk_table_3_id","fieldIds":["29e2p9bom0uxo1n0a9ze5auuy"],"unique":true,"isPrimaryKey":true,"createdAt":1758036811564}],"x":0,"y":0,"color":"#8eb7ff","isView":false,"createdAt":1758036805300,"diagramId":"mqqwkkodxt6p"}],"relationships":[],"dependencies":[],"areas":[],"customTypes":[]}
|
||||
@@ -1 +1 @@
|
||||
{"id":"mqqwkkod6r09","name":"Diagram 10","createdAt":"2025-09-16T15:47:40.655Z","updatedAt":"2025-09-16T15:47:50.179Z","databaseType":"postgresql","tables":[{"id":"6xbco4ihmuiyv2heuw9fggbgx","name":"table_3","schema":"","order":0,"fields":[{"id":"rxftaey7uxvq5qg6ix1hbak1c","name":"id","type":{"name":"bigint","id":"bigint","usageLevel":2},"nullable":true,"primaryKey":true,"unique":true,"createdAt":1758037660654}],"indexes":[{"id":"vsyjjaq2l58urkh9qm2g9hqhd","name":"pk_table_3_id","fieldIds":["rxftaey7uxvq5qg6ix1hbak1c"],"unique":true,"isPrimaryKey":true,"createdAt":1758037660654}],"x":0,"y":0,"color":"#8eb7ff","isView":false,"createdAt":1758037660654,"diagramId":"mqqwkkod6r09"},{"id":"klu6k5ntddcxfdsu0fsfcwbiw","name":"table_2","schema":"","order":1,"fields":[{"id":"qq2415tivmtvun8vd727d9mr2","name":"id","type":{"name":"bigint","id":"bigint","usageLevel":2},"nullable":false,"primaryKey":true,"unique":true,"createdAt":1758037660655}],"indexes":[{"id":"cvv7sgmq07i9y54lz9a97nah5","name":"pk_table_2_id","fieldIds":["qq2415tivmtvun8vd727d9mr2"],"unique":true,"isPrimaryKey":true,"createdAt":1758037660655}],"x":300,"y":0,"color":"#8eb7ff","isView":false,"createdAt":1758037660655,"diagramId":"mqqwkkod6r09"}],"relationships":[{"id":"yw2pbcumsabuncc6rjnp3n87t","name":"table_3_id_table_2_id","sourceSchema":"","targetSchema":"","sourceTableId":"6xbco4ihmuiyv2heuw9fggbgx","targetTableId":"klu6k5ntddcxfdsu0fsfcwbiw","sourceFieldId":"rxftaey7uxvq5qg6ix1hbak1c","targetFieldId":"qq2415tivmtvun8vd727d9mr2","sourceCardinality":"one","targetCardinality":"one","createdAt":1758037660655,"diagramId":"mqqwkkod6r09"}],"dependencies":[],"areas":[],"customTypes":[]}
|
||||
{"id":"mqqwkkod6r09","name":"Diagram 10","createdAt":"2025-09-16T15:47:40.655Z","updatedAt":"2025-09-16T15:47:50.179Z","databaseType":"postgresql","tables":[{"id":"6xbco4ihmuiyv2heuw9fggbgx","name":"table_3","schema":"","order":0,"fields":[{"id":"rxftaey7uxvq5qg6ix1hbak1c","name":"id","type":{"name":"bigint","id":"bigint","usageLevel":2},"nullable":false,"primaryKey":true,"unique":true,"createdAt":1758037660654}],"indexes":[{"id":"vsyjjaq2l58urkh9qm2g9hqhd","name":"pk_table_3_id","fieldIds":["rxftaey7uxvq5qg6ix1hbak1c"],"unique":true,"isPrimaryKey":true,"createdAt":1758037660654}],"x":0,"y":0,"color":"#8eb7ff","isView":false,"createdAt":1758037660654,"diagramId":"mqqwkkod6r09"},{"id":"klu6k5ntddcxfdsu0fsfcwbiw","name":"table_2","schema":"","order":1,"fields":[{"id":"qq2415tivmtvun8vd727d9mr2","name":"id","type":{"name":"bigint","id":"bigint","usageLevel":2},"nullable":false,"primaryKey":true,"unique":true,"createdAt":1758037660655}],"indexes":[{"id":"cvv7sgmq07i9y54lz9a97nah5","name":"pk_table_2_id","fieldIds":["qq2415tivmtvun8vd727d9mr2"],"unique":true,"isPrimaryKey":true,"createdAt":1758037660655}],"x":300,"y":0,"color":"#8eb7ff","isView":false,"createdAt":1758037660655,"diagramId":"mqqwkkod6r09"}],"relationships":[{"id":"yw2pbcumsabuncc6rjnp3n87t","name":"table_3_id_table_2_id","sourceSchema":"","targetSchema":"","sourceTableId":"6xbco4ihmuiyv2heuw9fggbgx","targetTableId":"klu6k5ntddcxfdsu0fsfcwbiw","sourceFieldId":"rxftaey7uxvq5qg6ix1hbak1c","targetFieldId":"qq2415tivmtvun8vd727d9mr2","sourceCardinality":"one","targetCardinality":"one","createdAt":1758037660655,"diagramId":"mqqwkkod6r09"}],"dependencies":[],"areas":[],"customTypes":[]}
|
||||
64
src/lib/dbml/dbml-import/__tests__/dbml-pk-not-null.test.ts
Normal file
64
src/lib/dbml/dbml-import/__tests__/dbml-pk-not-null.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { importDBMLToDiagram } from '../dbml-import';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
|
||||
describe('DBML Import - Primary Key NOT NULL', () => {
|
||||
it('should mark primary key columns as NOT NULL', async () => {
|
||||
const dbml = `
|
||||
Table users {
|
||||
id int [pk]
|
||||
name varchar(100)
|
||||
}`;
|
||||
|
||||
const diagram = await importDBMLToDiagram(dbml, {
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
});
|
||||
|
||||
const usersTable = diagram.tables?.find((t) => t.name === 'users');
|
||||
expect(usersTable).toBeDefined();
|
||||
|
||||
const idField = usersTable?.fields.find((f) => f.name === 'id');
|
||||
expect(idField?.primaryKey).toBe(true);
|
||||
expect(idField?.nullable).toBe(false);
|
||||
|
||||
// Non-PK field should remain nullable by default
|
||||
const nameField = usersTable?.fields.find((f) => f.name === 'name');
|
||||
expect(nameField?.primaryKey).toBeFalsy();
|
||||
expect(nameField?.nullable).toBe(true);
|
||||
});
|
||||
|
||||
it('should mark composite primary key columns as NOT NULL', async () => {
|
||||
const dbml = `
|
||||
Table order_items {
|
||||
order_id int
|
||||
product_id int
|
||||
quantity int
|
||||
|
||||
indexes {
|
||||
(order_id, product_id) [pk]
|
||||
}
|
||||
}`;
|
||||
|
||||
const diagram = await importDBMLToDiagram(dbml, {
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
});
|
||||
|
||||
const table = diagram.tables?.find((t) => t.name === 'order_items');
|
||||
expect(table).toBeDefined();
|
||||
|
||||
const orderIdField = table?.fields.find((f) => f.name === 'order_id');
|
||||
expect(orderIdField?.primaryKey).toBe(true);
|
||||
expect(orderIdField?.nullable).toBe(false);
|
||||
|
||||
const productIdField = table?.fields.find(
|
||||
(f) => f.name === 'product_id'
|
||||
);
|
||||
expect(productIdField?.primaryKey).toBe(true);
|
||||
expect(productIdField?.nullable).toBe(false);
|
||||
|
||||
// Non-PK field should remain nullable
|
||||
const quantityField = table?.fields.find((f) => f.name === 'quantity');
|
||||
expect(quantityField?.primaryKey).toBeFalsy();
|
||||
expect(quantityField?.nullable).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -569,7 +569,9 @@ export const importDBMLToDiagram = async (
|
||||
name: field.name.replace(/['"]/g, ''),
|
||||
type: finalType,
|
||||
nullable:
|
||||
field.increment || requiresNotNull(field.type.type_name)
|
||||
field.increment ||
|
||||
field.pk ||
|
||||
requiresNotNull(field.type.type_name)
|
||||
? false
|
||||
: !field.not_null,
|
||||
primaryKey: field.pk || false,
|
||||
@@ -600,13 +602,14 @@ export const importDBMLToDiagram = async (
|
||||
if (dbmlIndex.name) {
|
||||
compositePKIndexName = dbmlIndex.name;
|
||||
}
|
||||
// Mark fields as primary keys
|
||||
// Mark fields as primary keys and NOT NULL
|
||||
dbmlIndex.columns.forEach((col) => {
|
||||
const columnName =
|
||||
typeof col === 'string' ? col : col.value;
|
||||
const field = fields.find((f) => f.name === columnName);
|
||||
if (field) {
|
||||
field.primaryKey = true;
|
||||
field.nullable = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user