mirror of
				https://github.com/chartdb/chartdb.git
				synced 2025-11-04 05:53:15 +00:00 
			
		
		
		
	fix: resolve dbml increment & nullable attributes issue (#954)
* fix: resolve dbml increment attribute * fix nullable * fix
This commit is contained in:
		@@ -10,6 +10,8 @@ import {
 | 
			
		||||
    dataTypeDataToDataType,
 | 
			
		||||
    sortedDataTypeMap,
 | 
			
		||||
    supportsArrayDataType,
 | 
			
		||||
    autoIncrementAlwaysOn,
 | 
			
		||||
    requiresNotNull,
 | 
			
		||||
} from '@/lib/data/data-types/data-types';
 | 
			
		||||
import { generateDBFieldSuffix } from '@/lib/domain/db-field';
 | 
			
		||||
import type { DataTypeData } from '@/lib/data/data-types/data-types';
 | 
			
		||||
@@ -224,12 +226,17 @@ export const useUpdateTableField = (
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const newTypeName = dataType?.name ?? (value as string);
 | 
			
		||||
            const typeRequiresNotNull = requiresNotNull(newTypeName);
 | 
			
		||||
            const shouldForceIncrement = autoIncrementAlwaysOn(newTypeName);
 | 
			
		||||
 | 
			
		||||
            updateField(table.id, field.id, {
 | 
			
		||||
                characterMaximumLength,
 | 
			
		||||
                precision,
 | 
			
		||||
                scale,
 | 
			
		||||
                isArray,
 | 
			
		||||
                increment: undefined,
 | 
			
		||||
                ...(typeRequiresNotNull ? { nullable: false } : {}),
 | 
			
		||||
                increment: shouldForceIncrement ? true : undefined,
 | 
			
		||||
                default: undefined,
 | 
			
		||||
                type: dataTypeDataToDataType(
 | 
			
		||||
                    dataType ?? {
 | 
			
		||||
@@ -267,9 +274,16 @@ export const useUpdateTableField = (
 | 
			
		||||
    const debouncedNullableUpdate = useDebounce(
 | 
			
		||||
        useCallback(
 | 
			
		||||
            (value: boolean) => {
 | 
			
		||||
                updateField(table.id, field.id, { nullable: value });
 | 
			
		||||
                const updates: Partial<DBField> = { nullable: value };
 | 
			
		||||
 | 
			
		||||
                // If setting to nullable, clear increment (auto-increment requires NOT NULL)
 | 
			
		||||
                if (value && field.increment) {
 | 
			
		||||
                    updates.increment = undefined;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                updateField(table.id, field.id, updates);
 | 
			
		||||
            },
 | 
			
		||||
            [updateField, table.id, field.id]
 | 
			
		||||
            [updateField, table.id, field.id, field.increment]
 | 
			
		||||
        ),
 | 
			
		||||
        100 // 100ms debounce for toggle
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -167,6 +167,18 @@ export const supportsAutoIncrementDataType = (
 | 
			
		||||
    ].includes(dataTypeName.toLocaleLowerCase());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const autoIncrementAlwaysOn = (dataTypeName: string): boolean => {
 | 
			
		||||
    return ['serial', 'bigserial', 'smallserial'].includes(
 | 
			
		||||
        dataTypeName.toLowerCase()
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const requiresNotNull = (dataTypeName: string): boolean => {
 | 
			
		||||
    return ['serial', 'bigserial', 'smallserial'].includes(
 | 
			
		||||
        dataTypeName.toLowerCase()
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ARRAY_INCOMPATIBLE_TYPES = [
 | 
			
		||||
    'serial',
 | 
			
		||||
    'bigserial',
 | 
			
		||||
 
 | 
			
		||||
@@ -395,10 +395,27 @@ export const exportBaseSQL = ({
 | 
			
		||||
                sqlScript += ` UNIQUE`;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle AUTO INCREMENT - add as a comment for AI to process
 | 
			
		||||
            // Handle AUTO INCREMENT
 | 
			
		||||
            if (field.increment) {
 | 
			
		||||
                if (isDBMLFlow) {
 | 
			
		||||
                    // For DBML flow, generate proper database-specific syntax
 | 
			
		||||
                    if (
 | 
			
		||||
                        targetDatabaseType === DatabaseType.MYSQL ||
 | 
			
		||||
                        targetDatabaseType === DatabaseType.MARIADB
 | 
			
		||||
                    ) {
 | 
			
		||||
                        sqlScript += ` AUTO_INCREMENT`;
 | 
			
		||||
                    } else if (targetDatabaseType === DatabaseType.SQL_SERVER) {
 | 
			
		||||
                        sqlScript += ` IDENTITY(1,1)`;
 | 
			
		||||
                    } else if (targetDatabaseType === DatabaseType.SQLITE) {
 | 
			
		||||
                        // SQLite AUTOINCREMENT only works with INTEGER PRIMARY KEY
 | 
			
		||||
                        // Will be handled when PRIMARY KEY is added
 | 
			
		||||
                    }
 | 
			
		||||
                    // PostgreSQL/CockroachDB: increment attribute added by restoreIncrementAttribute in DBML export
 | 
			
		||||
                } else {
 | 
			
		||||
                    // For non-DBML flow, add as a comment for AI to process
 | 
			
		||||
                    sqlScript += ` /* AUTO_INCREMENT */`;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle DEFAULT value
 | 
			
		||||
            if (field.default && !field.increment) {
 | 
			
		||||
@@ -450,6 +467,17 @@ export const exportBaseSQL = ({
 | 
			
		||||
            const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
 | 
			
		||||
            if (field.primaryKey && !hasCompositePrimaryKey && !pkIndex?.name) {
 | 
			
		||||
                sqlScript += ' PRIMARY KEY';
 | 
			
		||||
 | 
			
		||||
                // For SQLite with DBML flow, add AUTOINCREMENT after PRIMARY KEY
 | 
			
		||||
                if (
 | 
			
		||||
                    isDBMLFlow &&
 | 
			
		||||
                    field.increment &&
 | 
			
		||||
                    targetDatabaseType === DatabaseType.SQLITE &&
 | 
			
		||||
                    (typeName.toLowerCase() === 'integer' ||
 | 
			
		||||
                        typeName.toLowerCase() === 'int')
 | 
			
		||||
                ) {
 | 
			
		||||
                    sqlScript += ' AUTOINCREMENT';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Add a comma after each field except the last one (or before PK constraint)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
Table "public"."orders" {
 | 
			
		||||
  "order_id" integer [pk, not null]
 | 
			
		||||
  "order_id" integer [pk, not null, increment]
 | 
			
		||||
  "customer_id" integer [not null]
 | 
			
		||||
  "order_date" date [not null, default: `CURRENT_DATE`]
 | 
			
		||||
  "total_amount" numeric [not null, default: 0]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								src/lib/dbml/dbml-export/__tests__/cases/6.dbml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/lib/dbml/dbml-export/__tests__/cases/6.dbml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
Table "users" {
 | 
			
		||||
  "id" integer [pk, not null, increment]
 | 
			
		||||
  "username" varchar(100) [unique, not null]
 | 
			
		||||
  "email" varchar(255) [not null]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Table "posts" {
 | 
			
		||||
  "post_id" bigint [pk, not null, increment]
 | 
			
		||||
  "user_id" integer [not null]
 | 
			
		||||
  "title" varchar(200) [not null]
 | 
			
		||||
  "order_num" integer [not null, increment]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Ref "fk_0_fk_posts_users":"users"."id" < "posts"."user_id"
 | 
			
		||||
							
								
								
									
										1
									
								
								src/lib/dbml/dbml-export/__tests__/cases/6.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/lib/dbml/dbml-export/__tests__/cases/6.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"id":"test_auto_increment","name":"Auto Increment Test (mysql)","createdAt":"2025-01-20T00:00:00.000Z","updatedAt":"2025-01-20T00:00:00.000Z","databaseType":"mysql","tables":[{"id":"table1","name":"users","order":1,"fields":[{"id":"field1","name":"id","type":{"id":"integer","name":"integer"},"nullable":false,"primaryKey":true,"unique":false,"default":"","increment":true,"createdAt":1705708800000},{"id":"field2","name":"username","type":{"id":"varchar","name":"varchar","fieldAttributes":{"hasCharMaxLength":true}},"nullable":false,"primaryKey":false,"unique":true,"default":"","increment":false,"characterMaximumLength":"100","createdAt":1705708800000},{"id":"field3","name":"email","type":{"id":"varchar","name":"varchar","fieldAttributes":{"hasCharMaxLength":true}},"nullable":false,"primaryKey":false,"unique":false,"default":"","increment":false,"characterMaximumLength":"255","createdAt":1705708800000}],"indexes":[],"x":100,"y":100,"color":"#8eb7ff","isView":false,"createdAt":1705708800000},{"id":"table2","name":"posts","order":2,"fields":[{"id":"field4","name":"post_id","type":{"id":"bigint","name":"bigint"},"nullable":false,"primaryKey":true,"unique":false,"default":"","increment":true,"createdAt":1705708800000},{"id":"field5","name":"user_id","type":{"id":"integer","name":"integer"},"nullable":false,"primaryKey":false,"unique":false,"default":"","increment":false,"createdAt":1705708800000},{"id":"field6","name":"title","type":{"id":"varchar","name":"varchar","fieldAttributes":{"hasCharMaxLength":true}},"nullable":false,"primaryKey":false,"unique":false,"default":"","increment":false,"characterMaximumLength":"200","createdAt":1705708800000},{"id":"field7","name":"order_num","type":{"id":"integer","name":"integer"},"nullable":false,"primaryKey":false,"unique":false,"default":"","increment":true,"createdAt":1705708800000}],"indexes":[],"x":300,"y":100,"color":"#8eb7ff","isView":false,"createdAt":1705708800000}],"relationships":[{"id":"rel1","name":"fk_posts_users","sourceTableId":"table2","targetTableId":"table1","sourceFieldId":"field5","targetFieldId":"field1","type":"one_to_many","sourceCardinality":"many","targetCardinality":"one","createdAt":1705708800000}],"dependencies":[],"storageMode":"project","areas":[],"creationMethod":"manual","customTypes":[]}
 | 
			
		||||
@@ -66,4 +66,12 @@ describe('DBML Export cases', () => {
 | 
			
		||||
    it('should handle case 5 diagram', { timeout: 30000 }, async () => {
 | 
			
		||||
        testCase('5');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it(
 | 
			
		||||
        'should handle case 6 diagram - auto increment',
 | 
			
		||||
        { timeout: 30000 },
 | 
			
		||||
        async () => {
 | 
			
		||||
            testCase('6');
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -583,6 +583,54 @@ const fixMultilineTableNames = (dbml: string): string => {
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Restore increment attribute for auto-incrementing fields
 | 
			
		||||
const restoreIncrementAttribute = (dbml: string, tables: DBTable[]): string => {
 | 
			
		||||
    if (!tables || tables.length === 0) return dbml;
 | 
			
		||||
 | 
			
		||||
    let result = dbml;
 | 
			
		||||
 | 
			
		||||
    tables.forEach((table) => {
 | 
			
		||||
        // Find fields with increment=true
 | 
			
		||||
        const incrementFields = table.fields.filter((f) => f.increment);
 | 
			
		||||
 | 
			
		||||
        incrementFields.forEach((field) => {
 | 
			
		||||
            // Build the table identifier pattern
 | 
			
		||||
            const tableIdentifier = table.schema
 | 
			
		||||
                ? `"${table.schema.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\."${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`
 | 
			
		||||
                : `"${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`;
 | 
			
		||||
 | 
			
		||||
            // Escape field name for regex
 | 
			
		||||
            const escapedFieldName = field.name.replace(
 | 
			
		||||
                /[.*+?^${}()|[\]\\]/g,
 | 
			
		||||
                '\\$&'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Pattern to match the field line with existing attributes in brackets
 | 
			
		||||
            // Matches: "field_name" type [existing, attributes]
 | 
			
		||||
            const fieldPattern = new RegExp(
 | 
			
		||||
                `(Table ${tableIdentifier} \\{[^}]*?^\\s*"${escapedFieldName}"[^\\[\\n]+)(\\[[^\\]]*\\])`,
 | 
			
		||||
                'gms'
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            result = result.replace(
 | 
			
		||||
                fieldPattern,
 | 
			
		||||
                (match, fieldPart, brackets) => {
 | 
			
		||||
                    // Check if increment already exists
 | 
			
		||||
                    if (brackets.includes('increment')) {
 | 
			
		||||
                        return match;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Add increment to the attributes
 | 
			
		||||
                    const newBrackets = brackets.replace(']', ', increment]');
 | 
			
		||||
                    return fieldPart + newBrackets;
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Restore composite primary key names in the DBML
 | 
			
		||||
const restoreCompositePKNames = (dbml: string, tables: DBTable[]): string => {
 | 
			
		||||
    if (!tables || tables.length === 0) return dbml;
 | 
			
		||||
@@ -888,6 +936,9 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
 | 
			
		||||
        // Restore composite primary key names
 | 
			
		||||
        standard = restoreCompositePKNames(standard, uniqueTables);
 | 
			
		||||
 | 
			
		||||
        // Restore increment attribute for auto-incrementing fields
 | 
			
		||||
        standard = restoreIncrementAttribute(standard, uniqueTables);
 | 
			
		||||
 | 
			
		||||
        // Prepend Enum DBML to the standard output
 | 
			
		||||
        if (enumsDBML) {
 | 
			
		||||
            standard = enumsDBML + '\n\n' + standard;
 | 
			
		||||
 
 | 
			
		||||
@@ -342,4 +342,85 @@ describe('DBML Import cases', () => {
 | 
			
		||||
        );
 | 
			
		||||
        expect(createdAtField?.default).toBe('now()');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle auto-increment fields correctly', async () => {
 | 
			
		||||
        const dbmlContent = `Table "public"."table_1" {
 | 
			
		||||
  "id" integer [pk, not null, increment]
 | 
			
		||||
  "field_2" bigint [increment]
 | 
			
		||||
  "field_3" serial [increment]
 | 
			
		||||
  "field_4" varchar(100) [not null]
 | 
			
		||||
}`;
 | 
			
		||||
 | 
			
		||||
        const result = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const table = result.tables![0];
 | 
			
		||||
        expect(table.name).toBe('table_1');
 | 
			
		||||
        expect(table.fields).toHaveLength(4);
 | 
			
		||||
 | 
			
		||||
        // field with [pk, not null, increment] - should be not null and increment
 | 
			
		||||
        const idField = table.fields.find((f) => f.name === 'id');
 | 
			
		||||
        expect(idField?.increment).toBe(true);
 | 
			
		||||
        expect(idField?.nullable).toBe(false);
 | 
			
		||||
        expect(idField?.primaryKey).toBe(true);
 | 
			
		||||
 | 
			
		||||
        // field with [increment] only - should be not null and increment
 | 
			
		||||
        // (auto-increment requires NOT NULL even if not explicitly stated)
 | 
			
		||||
        const field2 = table.fields.find((f) => f.name === 'field_2');
 | 
			
		||||
        expect(field2?.increment).toBe(true);
 | 
			
		||||
        expect(field2?.nullable).toBe(false); // CRITICAL: must be false!
 | 
			
		||||
 | 
			
		||||
        // SERIAL type with [increment] - should be not null and increment
 | 
			
		||||
        const field3 = table.fields.find((f) => f.name === 'field_3');
 | 
			
		||||
        expect(field3?.increment).toBe(true);
 | 
			
		||||
        expect(field3?.nullable).toBe(false);
 | 
			
		||||
        expect(field3?.type?.name).toBe('serial');
 | 
			
		||||
 | 
			
		||||
        // Regular field with [not null] - should be not null, no increment
 | 
			
		||||
        const field4 = table.fields.find((f) => f.name === 'field_4');
 | 
			
		||||
        expect(field4?.increment).toBeUndefined();
 | 
			
		||||
        expect(field4?.nullable).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle SERIAL types without increment attribute', async () => {
 | 
			
		||||
        const dbmlContent = `Table "public"."test_table" {
 | 
			
		||||
  "id" serial [pk]
 | 
			
		||||
  "counter" bigserial
 | 
			
		||||
  "small_counter" smallserial
 | 
			
		||||
  "regular" integer
 | 
			
		||||
}`;
 | 
			
		||||
 | 
			
		||||
        const result = await importDBMLToDiagram(dbmlContent, {
 | 
			
		||||
            databaseType: DatabaseType.POSTGRESQL,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        expect(result.tables).toHaveLength(1);
 | 
			
		||||
        const table = result.tables![0];
 | 
			
		||||
        expect(table.fields).toHaveLength(4);
 | 
			
		||||
 | 
			
		||||
        // SERIAL type without [increment] - should STILL be not null (type requires it)
 | 
			
		||||
        const idField = table.fields.find((f) => f.name === 'id');
 | 
			
		||||
        expect(idField?.type?.name).toBe('serial');
 | 
			
		||||
        expect(idField?.nullable).toBe(false); // CRITICAL: Type requires NOT NULL
 | 
			
		||||
        expect(idField?.primaryKey).toBe(true);
 | 
			
		||||
 | 
			
		||||
        // BIGSERIAL without [increment] - should be not null
 | 
			
		||||
        const counterField = table.fields.find((f) => f.name === 'counter');
 | 
			
		||||
        expect(counterField?.type?.name).toBe('bigserial');
 | 
			
		||||
        expect(counterField?.nullable).toBe(false); // CRITICAL: Type requires NOT NULL
 | 
			
		||||
 | 
			
		||||
        // SMALLSERIAL without [increment] - should be not null
 | 
			
		||||
        const smallCounterField = table.fields.find(
 | 
			
		||||
            (f) => f.name === 'small_counter'
 | 
			
		||||
        );
 | 
			
		||||
        expect(smallCounterField?.type?.name).toBe('smallserial');
 | 
			
		||||
        expect(smallCounterField?.nullable).toBe(false); // CRITICAL: Type requires NOT NULL
 | 
			
		||||
 | 
			
		||||
        // Regular INTEGER - should be nullable by default
 | 
			
		||||
        const regularField = table.fields.find((f) => f.name === 'regular');
 | 
			
		||||
        expect(regularField?.type?.name).toBe('integer');
 | 
			
		||||
        expect(regularField?.nullable).toBe(true); // No NOT NULL constraint
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,10 @@ import type { DBTable } from '@/lib/domain/db-table';
 | 
			
		||||
import type { Cardinality, DBRelationship } from '@/lib/domain/db-relationship';
 | 
			
		||||
import type { DBField } from '@/lib/domain/db-field';
 | 
			
		||||
import type { DataTypeData } from '@/lib/data/data-types/data-types';
 | 
			
		||||
import { findDataTypeDataById } from '@/lib/data/data-types/data-types';
 | 
			
		||||
import {
 | 
			
		||||
    findDataTypeDataById,
 | 
			
		||||
    requiresNotNull,
 | 
			
		||||
} from '@/lib/data/data-types/data-types';
 | 
			
		||||
import { defaultTableColor } from '@/lib/colors';
 | 
			
		||||
import { DatabaseType } from '@/lib/domain/database-type';
 | 
			
		||||
import type Field from '@dbml/core/types/model_structure/field';
 | 
			
		||||
@@ -552,7 +555,10 @@ export const importDBMLToDiagram = async (
 | 
			
		||||
                        ...options,
 | 
			
		||||
                        enums: extractedData.enums,
 | 
			
		||||
                    }),
 | 
			
		||||
                    nullable: !field.not_null,
 | 
			
		||||
                    nullable:
 | 
			
		||||
                        field.increment || requiresNotNull(field.type.type_name)
 | 
			
		||||
                            ? false
 | 
			
		||||
                            : !field.not_null,
 | 
			
		||||
                    primaryKey: field.pk || false,
 | 
			
		||||
                    unique: field.unique || field.pk || false, // Primary keys are always unique
 | 
			
		||||
                    createdAt: Date.now(),
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { SelectBox } from '@/components/select-box/select-box';
 | 
			
		||||
import { cn } from '@/lib/utils';
 | 
			
		||||
import { TableFieldToggle } from './table-field-toggle';
 | 
			
		||||
import { requiresNotNull } from '@/lib/data/data-types/data-types';
 | 
			
		||||
 | 
			
		||||
export interface TableEditModeFieldProps {
 | 
			
		||||
    table: DBTable;
 | 
			
		||||
@@ -41,6 +42,8 @@ export const TableEditModeField: React.FC<TableEditModeFieldProps> = React.memo(
 | 
			
		||||
 | 
			
		||||
        const inputRef = React.useRef<HTMLInputElement>(null);
 | 
			
		||||
 | 
			
		||||
        const typeRequiresNotNull = requiresNotNull(field.type.name);
 | 
			
		||||
 | 
			
		||||
        // Animate the highlight after mount if focused
 | 
			
		||||
        useEffect(() => {
 | 
			
		||||
            if (focused) {
 | 
			
		||||
@@ -135,6 +138,7 @@ export const TableEditModeField: React.FC<TableEditModeFieldProps> = React.memo(
 | 
			
		||||
                                <TableFieldToggle
 | 
			
		||||
                                    pressed={nullable}
 | 
			
		||||
                                    onPressedChange={handleNullableToggle}
 | 
			
		||||
                                    disabled={typeRequiresNotNull}
 | 
			
		||||
                                >
 | 
			
		||||
                                    N
 | 
			
		||||
                                </TableFieldToggle>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import {
 | 
			
		||||
    findDataTypeDataById,
 | 
			
		||||
    supportsAutoIncrementDataType,
 | 
			
		||||
    supportsArrayDataType,
 | 
			
		||||
    autoIncrementAlwaysOn,
 | 
			
		||||
} from '@/lib/data/data-types/data-types';
 | 
			
		||||
import {
 | 
			
		||||
    Popover,
 | 
			
		||||
@@ -111,6 +112,18 @@ export const TableFieldPopover: React.FC<TableFieldPopoverProps> = ({
 | 
			
		||||
        [field.type.name, databaseType]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Check if this is a SERIAL-type that is inherently auto-incrementing
 | 
			
		||||
    const forceAutoIncrement = useMemo(
 | 
			
		||||
        () => autoIncrementAlwaysOn(field.type.name) && !localField.nullable,
 | 
			
		||||
        [field.type.name, localField.nullable]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Auto-increment is disabled if the field is nullable (auto-increment requires NOT NULL)
 | 
			
		||||
    const isIncrementDisabled = useMemo(
 | 
			
		||||
        () => localField.nullable || readonly || forceAutoIncrement,
 | 
			
		||||
        [localField.nullable, readonly, forceAutoIncrement]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Popover
 | 
			
		||||
            open={isOpen}
 | 
			
		||||
@@ -166,10 +179,12 @@ export const TableFieldPopover: React.FC<TableFieldPopoverProps> = ({
 | 
			
		||||
                                    )}
 | 
			
		||||
                                </Label>
 | 
			
		||||
                                <Checkbox
 | 
			
		||||
                                    checked={localField.increment ?? false}
 | 
			
		||||
                                    disabled={
 | 
			
		||||
                                        !localField.primaryKey || readonly
 | 
			
		||||
                                    checked={
 | 
			
		||||
                                        forceAutoIncrement
 | 
			
		||||
                                            ? true
 | 
			
		||||
                                            : (localField.increment ?? false)
 | 
			
		||||
                                    }
 | 
			
		||||
                                    disabled={isIncrementDisabled}
 | 
			
		||||
                                    onCheckedChange={(value) =>
 | 
			
		||||
                                        setLocalField((current) => ({
 | 
			
		||||
                                            ...current,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ import { CSS } from '@dnd-kit/utilities';
 | 
			
		||||
import { SelectBox } from '@/components/select-box/select-box';
 | 
			
		||||
import { TableFieldPopover } from './table-field-modal/table-field-modal';
 | 
			
		||||
import type { DatabaseType, DBTable } from '@/lib/domain';
 | 
			
		||||
import { requiresNotNull } from '@/lib/data/data-types/data-types';
 | 
			
		||||
 | 
			
		||||
export interface TableFieldProps {
 | 
			
		||||
    table: DBTable;
 | 
			
		||||
@@ -55,6 +56,8 @@ export const TableField: React.FC<TableFieldProps> = ({
 | 
			
		||||
        transition,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const typeRequiresNotNull = requiresNotNull(field.type.name);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div
 | 
			
		||||
            className="flex flex-1 touch-none flex-row justify-between gap-2 p-1"
 | 
			
		||||
@@ -130,7 +133,7 @@ export const TableField: React.FC<TableFieldProps> = ({
 | 
			
		||||
                            <TableFieldToggle
 | 
			
		||||
                                pressed={nullable}
 | 
			
		||||
                                onPressedChange={handleNullableToggle}
 | 
			
		||||
                                disabled={readonly}
 | 
			
		||||
                                disabled={readonly || typeRequiresNotNull}
 | 
			
		||||
                            >
 | 
			
		||||
                                N
 | 
			
		||||
                            </TableFieldToggle>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user