fix: PostgreSQL unique/primary key import handling (#1062)

This commit is contained in:
Jonathan Fishner
2026-01-13 17:38:17 +02:00
committed by GitHub
parent 5314c88ae1
commit 5754538d7e
4 changed files with 200 additions and 5 deletions

View File

@@ -198,6 +198,142 @@ describe('SQL Export Tests', () => {
});
});
describe('Unique Constraint Index Export', () => {
it('should not generate CREATE UNIQUE INDEX for single-column unique fields in PostgreSQL', () => {
const fieldId = testId();
const diagram = createDiagram({
databaseType: DatabaseType.POSTGRESQL,
tables: [
createTable({
name: 'orders_copy',
schema: 'public',
fields: [
createField({
id: fieldId,
name: 'id',
type: { id: 'bigserial', name: 'bigserial' },
primaryKey: false,
nullable: false,
unique: true,
}),
createField({
name: 'created_at',
type: {
id: 'timestamptz',
name: 'timestamptz',
},
nullable: false,
}),
],
indexes: [
{
id: testId(),
name: 'orders_copy_id_key',
fieldIds: [fieldId],
unique: true,
isPrimaryKey: false,
createdAt: testTime,
},
],
}),
],
});
const sql = exportPostgreSQL({ diagram });
// Should have inline UNIQUE
expect(sql).toContain('"id" bigserial NOT NULL UNIQUE');
// Should NOT have separate CREATE UNIQUE INDEX for the unique field
expect(sql).not.toContain('CREATE UNIQUE INDEX');
});
it('should still generate CREATE UNIQUE INDEX for multi-column unique indexes in PostgreSQL', () => {
const fieldId1 = testId();
const fieldId2 = testId();
const diagram = createDiagram({
databaseType: DatabaseType.POSTGRESQL,
tables: [
createTable({
name: 'test_table',
schema: 'public',
fields: [
createField({
id: fieldId1,
name: 'col_a',
type: { id: 'text', name: 'text' },
nullable: false,
unique: false,
}),
createField({
id: fieldId2,
name: 'col_b',
type: { id: 'text', name: 'text' },
nullable: false,
unique: false,
}),
],
indexes: [
{
id: testId(),
name: 'test_table_unique_idx',
fieldIds: [fieldId1, fieldId2],
unique: true,
isPrimaryKey: false,
createdAt: testTime,
},
],
}),
],
});
const sql = exportPostgreSQL({ diagram });
// Should have CREATE UNIQUE INDEX for multi-column unique constraint
expect(sql).toContain('CREATE UNIQUE INDEX');
expect(sql).toContain('"col_a", "col_b"');
});
it('should generate CREATE UNIQUE INDEX for single-column unique index when field is not marked unique', () => {
const fieldId = testId();
const diagram = createDiagram({
databaseType: DatabaseType.POSTGRESQL,
tables: [
createTable({
name: 'test_table',
schema: 'public',
fields: [
createField({
id: fieldId,
name: 'email',
type: { id: 'text', name: 'text' },
nullable: false,
unique: false, // Field not marked as unique
}),
],
indexes: [
{
id: testId(),
name: 'test_table_email_key',
fieldIds: [fieldId],
unique: true,
isPrimaryKey: false,
createdAt: testTime,
},
],
}),
],
});
const sql = exportPostgreSQL({ diagram });
// Should NOT have inline UNIQUE (field.unique is false)
expect(sql).not.toContain('UNIQUE,');
expect(sql).not.toContain('NOT NULL UNIQUE');
// Should have CREATE UNIQUE INDEX since the field doesn't have inline UNIQUE
expect(sql).toContain('CREATE UNIQUE INDEX');
});
});
describe('exportBaseSQL with foreign key relationships', () => {
it('should export PostgreSQL diagram with two tables and a foreign key relationship', () => {
const diagram = createDiagram({

View File

@@ -402,6 +402,16 @@ export function exportPostgreSQL({
return '';
}
// Skip unique indexes on single columns that already have inline UNIQUE
// PostgreSQL automatically creates an index for UNIQUE constraints
if (
index.unique &&
indexFields.length === 1 &&
indexFields[0]?.unique
) {
return '';
}
// Create unique index name using table name and index name
// This ensures index names are unique across the database
const safeTableName = table.name.replace(

View File

@@ -196,4 +196,55 @@ CREATE TABLE patients(
expect(result.relationships).toHaveLength(14);
});
it('should preserve UNIQUE constraint and correct PRIMARY KEY on import', async () => {
// Regression test: When importing a table with:
// 1. A column with inline UNIQUE (not part of PK)
// 2. A composite PRIMARY KEY on different columns
// The import should correctly identify which fields are PK vs just UNIQUE
const inputSql = `
CREATE TABLE "public"."orders_copy" (
"id" bigserial NOT NULL UNIQUE,
"created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
"user_id" bigint NOT NULL,
"customer_id" bigint,
CONSTRAINT "orders_pkey" PRIMARY KEY ("user_id", "customer_id")
);`;
const result = await fromPostgres(inputSql);
expect(result.tables).toHaveLength(1);
const table = result.tables[0];
expect(table.name).toBe('orders_copy');
expect(table.columns).toBeDefined();
// Verify field properties
const idField = table.columns.find((f) => f.name === 'id');
const userIdField = table.columns.find((f) => f.name === 'user_id');
const customerIdField = table.columns.find(
(f) => f.name === 'customer_id'
);
expect(idField).toBeDefined();
expect(userIdField).toBeDefined();
expect(customerIdField).toBeDefined();
// id should be UNIQUE but NOT a primary key
expect(idField!.unique).toBe(true);
expect(idField!.primaryKey).toBe(false);
// user_id and customer_id should be primary keys
expect(userIdField!.primaryKey).toBe(true);
expect(customerIdField!.primaryKey).toBe(true);
// Verify no field other than user_id and customer_id is marked as primary key
const pkFields = table.columns.filter((f) => f.primaryKey);
expect(pkFields).toHaveLength(2);
expect(pkFields.map((f) => f.name).sort()).toEqual([
'customer_id',
'user_id',
]);
});
});

View File

@@ -1191,7 +1191,7 @@ export async function fromPostgres(
? false
: columnDef.nullable?.type !==
'not null',
primaryKey: isPrimaryKey || isSerialType,
primaryKey: isPrimaryKey,
unique: columnDef.unique === 'unique',
typeArgs: getTypeArgs(columnDef.definition),
default: isSerialType
@@ -1652,8 +1652,7 @@ export async function fromPostgres(
nullable: nullable,
primaryKey:
definition?.primary_key === 'primary key' ||
definition?.constraint === 'primary key' ||
isSerialType,
definition?.constraint === 'primary key',
unique: isUnique,
default: defaultValue,
increment:
@@ -1769,8 +1768,7 @@ export async function fromPostgres(
columnDef.primary_key ===
'primary key' ||
columnDef.definition?.constraint ===
'primary key' ||
isSerialType,
'primary key',
unique: columnDef.unique === 'unique',
typeArgs: getTypeArgs(columnDef.definition),
default: isSerialType