mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-09 21:19:45 -06:00
fix: treat PostgreSQL decimal as synonym for numeric (#1020)
* fix: treat PostgreSQL decimal as synonym for numeric * fix
This commit is contained in:
@@ -21,23 +21,23 @@ export const postgresDataTypes: readonly DataTypeData[] = [
|
||||
// Level 2 - Second most common types
|
||||
{ name: 'bigint', id: 'bigint', usageLevel: 2 },
|
||||
// { name: 'int8', id: 'int8', usageLevel: 2 },
|
||||
{
|
||||
name: 'decimal',
|
||||
id: 'decimal',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 131072,
|
||||
min: 0,
|
||||
default: 10,
|
||||
},
|
||||
scale: {
|
||||
max: 16383,
|
||||
min: 0,
|
||||
default: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: 'decimal',
|
||||
// id: 'decimal',
|
||||
// usageLevel: 2,
|
||||
// fieldAttributes: {
|
||||
// precision: {
|
||||
// max: 131072,
|
||||
// min: 0,
|
||||
// default: 10,
|
||||
// },
|
||||
// scale: {
|
||||
// max: 16383,
|
||||
// min: 0,
|
||||
// default: 2,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
{ name: 'serial', id: 'serial', usageLevel: 2 },
|
||||
{ name: 'json', id: 'json', usageLevel: 2 },
|
||||
{ name: 'jsonb', id: 'jsonb', usageLevel: 2 },
|
||||
@@ -53,6 +53,7 @@ export const postgresDataTypes: readonly DataTypeData[] = [
|
||||
{
|
||||
name: 'numeric',
|
||||
id: 'numeric',
|
||||
usageLevel: 2,
|
||||
fieldAttributes: {
|
||||
precision: {
|
||||
max: 131072,
|
||||
@@ -171,6 +172,9 @@ const synonymMap: Record<string, string> = {
|
||||
|
||||
// Bit types
|
||||
'bit varying': 'varbit',
|
||||
|
||||
// Numeric types
|
||||
decimal: 'numeric',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('SQL Export - Array Fields (Fantasy RPG Theme)', () => {
|
||||
{
|
||||
id: generateId(),
|
||||
name: 'skill_levels',
|
||||
type: { id: 'decimal', name: 'decimal' },
|
||||
type: { id: 'numeric', name: 'numeric' },
|
||||
primaryKey: false,
|
||||
unique: false,
|
||||
nullable: true,
|
||||
@@ -176,7 +176,7 @@ describe('SQL Export - Array Fields (Fantasy RPG Theme)', () => {
|
||||
expect(sql).toContain('CREATE TABLE "game"."heroes"');
|
||||
expect(sql).toContain('"abilities" varchar(100)[]');
|
||||
expect(sql).toContain('"inventory_slots" integer[]');
|
||||
expect(sql).toContain('"skill_levels" decimal(5, 2)[]');
|
||||
expect(sql).toContain('"skill_levels" numeric(5, 2)[]');
|
||||
});
|
||||
|
||||
it('should export non-array fields normally when isArray is false or undefined', () => {
|
||||
|
||||
@@ -4,9 +4,12 @@ import { importDBMLToDiagram } from '../../dbml-import/dbml-import';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
|
||||
describe('DBML Self-Referencing Relationships', () => {
|
||||
it('should preserve self-referencing relationships in DBML export', async () => {
|
||||
// Create a DBML with self-referencing relationship (general_ledger example)
|
||||
const inputDBML = `
|
||||
it(
|
||||
'should preserve self-referencing relationships in DBML export',
|
||||
{ timeout: 30000 },
|
||||
async () => {
|
||||
// Create a DBML with self-referencing relationship (general_ledger example)
|
||||
const inputDBML = `
|
||||
Table "finance"."general_ledger" {
|
||||
"ledger_id" bigint [pk]
|
||||
"account_name" varchar(100)
|
||||
@@ -16,35 +19,36 @@ Table "finance"."general_ledger" {
|
||||
}
|
||||
`;
|
||||
|
||||
// Import the DBML
|
||||
const diagram = await importDBMLToDiagram(inputDBML, {
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
});
|
||||
// Import the DBML
|
||||
const diagram = await importDBMLToDiagram(inputDBML, {
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
});
|
||||
|
||||
// Verify the relationship was imported
|
||||
expect(diagram.relationships).toBeDefined();
|
||||
expect(diagram.relationships?.length).toBe(1);
|
||||
// Verify the relationship was imported
|
||||
expect(diagram.relationships).toBeDefined();
|
||||
expect(diagram.relationships?.length).toBe(1);
|
||||
|
||||
// Verify it's a self-referencing relationship
|
||||
const relationship = diagram.relationships![0];
|
||||
expect(relationship.sourceTableId).toBe(relationship.targetTableId);
|
||||
// Verify it's a self-referencing relationship
|
||||
const relationship = diagram.relationships![0];
|
||||
expect(relationship.sourceTableId).toBe(relationship.targetTableId);
|
||||
|
||||
// Export back to DBML
|
||||
const exportResult = generateDBMLFromDiagram(diagram);
|
||||
// Export back to DBML
|
||||
const exportResult = generateDBMLFromDiagram(diagram);
|
||||
|
||||
// Check inline format
|
||||
expect(exportResult.inlineDbml).toContain('reversal_id');
|
||||
// The DBML parser correctly interprets FK as: target < source
|
||||
expect(exportResult.inlineDbml).toMatch(
|
||||
/ref:\s*<\s*"finance"\."general_ledger"\."ledger_id"/
|
||||
);
|
||||
// Check inline format
|
||||
expect(exportResult.inlineDbml).toContain('reversal_id');
|
||||
// The DBML parser correctly interprets FK as: target < source
|
||||
expect(exportResult.inlineDbml).toMatch(
|
||||
/ref:\s*<\s*"finance"\."general_ledger"\."ledger_id"/
|
||||
);
|
||||
|
||||
// Check standard format
|
||||
expect(exportResult.standardDbml).toContain('Ref ');
|
||||
expect(exportResult.standardDbml).toMatch(
|
||||
/"finance"\."general_ledger"\."ledger_id"\s*<\s*"finance"\."general_ledger"\."reversal_id"/
|
||||
);
|
||||
});
|
||||
// Check standard format
|
||||
expect(exportResult.standardDbml).toContain('Ref ');
|
||||
expect(exportResult.standardDbml).toMatch(
|
||||
/"finance"\."general_ledger"\."ledger_id"\s*<\s*"finance"\."general_ledger"\."reversal_id"/
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('should handle self-referencing relationships in employee hierarchy', async () => {
|
||||
// Create an employee table with manager relationship
|
||||
|
||||
@@ -88,7 +88,7 @@ Table "heroes" {
|
||||
(f) => f.name === 'skill_levels'
|
||||
);
|
||||
expect(skillLevels?.isArray).toBe(true);
|
||||
expect(skillLevels?.type.name).toBe('decimal');
|
||||
expect(skillLevels?.type.name).toBe('numeric');
|
||||
expect(skillLevels?.precision).toBe(5);
|
||||
expect(skillLevels?.scale).toBe(2);
|
||||
|
||||
@@ -227,7 +227,7 @@ Table "guilds"."members" {
|
||||
// Verify exported DBML has correct array syntax with types
|
||||
expect(exportedDbml).toContain('varchar(50)[]');
|
||||
expect(exportedDbml).toContain('int[]');
|
||||
expect(exportedDbml).toContain('decimal(3,1)[]');
|
||||
expect(exportedDbml).toContain('numeric(3,1)[]');
|
||||
expect(exportedDbml).toContain('text[]');
|
||||
|
||||
// Re-import
|
||||
|
||||
@@ -324,6 +324,12 @@ export const importDBMLToDiagram = async (
|
||||
enums,
|
||||
});
|
||||
|
||||
// Also check the preferred synonym for field attributes (e.g., decimal → numeric)
|
||||
const preferredType = options.databaseType
|
||||
? getPreferredSynonym(dataType.name, options.databaseType)
|
||||
: null;
|
||||
const effectiveType = preferredType ?? dataType;
|
||||
|
||||
// Check if this is a character type that should have a max length
|
||||
const baseTypeName = typeName
|
||||
.replace(/\(.*\)/, '')
|
||||
@@ -340,8 +346,8 @@ export const importDBMLToDiagram = async (
|
||||
characterMaximumLength: args[0],
|
||||
};
|
||||
} else if (
|
||||
dataType.fieldAttributes?.precision &&
|
||||
dataType.fieldAttributes?.scale
|
||||
effectiveType.fieldAttributes?.precision &&
|
||||
effectiveType.fieldAttributes?.scale
|
||||
) {
|
||||
const precisionNum = args?.[0] ? parseInt(args[0]) : undefined;
|
||||
const scaleNum = args?.[1] ? parseInt(args[1]) : undefined;
|
||||
|
||||
Reference in New Issue
Block a user