mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-09 13:14:31 -06:00
fix: serial ddl import in pg (#1033)
* fix: serial ddl import in pg * fix
This commit is contained in:
@@ -165,6 +165,7 @@ const synonymMap: Record<string, string> = {
|
||||
// Timestamp types
|
||||
'timestamp with time zone': 'timestamptz',
|
||||
'timestamp without time zone': 'timestamp',
|
||||
datetime: 'timestamp',
|
||||
|
||||
// Time types
|
||||
'time with time zone': 'timetz',
|
||||
|
||||
@@ -382,10 +382,10 @@ CREATE TABLE playlists (
|
||||
expect(fieldNames).toContain('items_count');
|
||||
expect(fieldNames).toContain('units_sold');
|
||||
|
||||
// Verify primary key
|
||||
// Verify primary key - serial is preserved (not converted to int)
|
||||
const pkField = fields?.find((f) => f.name === 'order_id');
|
||||
expect(pkField?.primaryKey).toBe(true);
|
||||
expect(pkField?.type.name).toBe('int');
|
||||
expect(pkField?.type.name).toBe('serial');
|
||||
|
||||
// Verify decimal fields (decimal is normalized to numeric in PostgreSQL)
|
||||
const totalAmountField = fields?.find((f) => f.name === 'total_amount');
|
||||
|
||||
@@ -423,6 +423,10 @@ export const typeAffinity: Record<string, Record<string, string>> = {
|
||||
int2: 'smallint',
|
||||
bigint: 'bigint',
|
||||
int8: 'bigint',
|
||||
// Serial types - map to themselves (they're valid PostgreSQL types)
|
||||
serial: 'serial',
|
||||
smallserial: 'smallserial',
|
||||
bigserial: 'bigserial',
|
||||
decimal: 'decimal',
|
||||
numeric: 'numeric',
|
||||
real: 'real',
|
||||
@@ -706,6 +710,32 @@ export function convertToChartDBDiagram(
|
||||
name: column.type,
|
||||
};
|
||||
}
|
||||
// Handle PostgreSQL-specific types (not in genericDataTypes)
|
||||
else if (
|
||||
sourceDatabaseType === DatabaseType.POSTGRESQL &&
|
||||
targetDatabaseType === DatabaseType.POSTGRESQL
|
||||
) {
|
||||
const normalizedType = column.type.toLowerCase();
|
||||
|
||||
// Preserve PostgreSQL-specific types that don't exist in genericDataTypes
|
||||
// Serial types are PostgreSQL-specific syntax (not true data types)
|
||||
if (
|
||||
normalizedType === 'serial' ||
|
||||
normalizedType === 'smallserial' ||
|
||||
normalizedType === 'bigserial' ||
|
||||
normalizedType === 'jsonb' ||
|
||||
normalizedType === 'timestamptz' ||
|
||||
normalizedType === 'timetz'
|
||||
) {
|
||||
mappedType = { id: normalizedType, name: normalizedType };
|
||||
} else {
|
||||
// Use the standard mapping for other types
|
||||
mappedType = mapSQLTypeToGenericType(
|
||||
column.type,
|
||||
sourceDatabaseType
|
||||
);
|
||||
}
|
||||
}
|
||||
// Handle SQL Server types specifically
|
||||
else if (
|
||||
sourceDatabaseType === DatabaseType.SQL_SERVER &&
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('PostgreSQL ALTER TABLE ADD COLUMN Tests', () => {
|
||||
// Check that the id column is present
|
||||
const idColumn = locationTable.columns.find((col) => col.name === 'id');
|
||||
expect(idColumn).toBeDefined();
|
||||
expect(idColumn?.type).toBe('BIGINT');
|
||||
expect(idColumn?.type).toBe('bigint');
|
||||
expect(idColumn?.primaryKey).toBe(true);
|
||||
|
||||
// Check some of the added columns
|
||||
@@ -51,19 +51,19 @@ describe('PostgreSQL ALTER TABLE ADD COLUMN Tests', () => {
|
||||
(col) => col.name === 'country_id'
|
||||
);
|
||||
expect(countryIdColumn).toBeDefined();
|
||||
expect(countryIdColumn?.type).toBe('INTEGER');
|
||||
expect(countryIdColumn?.type).toBe('integer');
|
||||
|
||||
const streetColumn = locationTable.columns.find(
|
||||
(col) => col.name === 'street'
|
||||
);
|
||||
expect(streetColumn).toBeDefined();
|
||||
expect(streetColumn?.type).toBe('TEXT');
|
||||
expect(streetColumn?.type).toBe('text');
|
||||
|
||||
const remarksColumn = locationTable.columns.find(
|
||||
(col) => col.name === 'remarks'
|
||||
);
|
||||
expect(remarksColumn).toBeDefined();
|
||||
expect(remarksColumn?.type).toBe('TEXT');
|
||||
expect(remarksColumn?.type).toBe('text');
|
||||
});
|
||||
|
||||
it('should handle ALTER TABLE ADD COLUMN with schema qualification', async () => {
|
||||
@@ -87,13 +87,13 @@ describe('PostgreSQL ALTER TABLE ADD COLUMN Tests', () => {
|
||||
(col) => col.name === 'email'
|
||||
);
|
||||
expect(emailColumn).toBeDefined();
|
||||
expect(emailColumn?.type).toBe('VARCHAR(255)');
|
||||
expect(emailColumn?.type).toBe('varchar(255)');
|
||||
|
||||
const createdAtColumn = usersTable.columns.find(
|
||||
(col) => col.name === 'created_at'
|
||||
);
|
||||
expect(createdAtColumn).toBeDefined();
|
||||
expect(createdAtColumn?.type).toBe('TIMESTAMP');
|
||||
expect(createdAtColumn?.type).toBe('timestamp');
|
||||
});
|
||||
|
||||
it('should handle ALTER TABLE ADD COLUMN with constraints', async () => {
|
||||
@@ -156,7 +156,7 @@ describe('PostgreSQL ALTER TABLE ADD COLUMN Tests', () => {
|
||||
(col) => col.name === 'name'
|
||||
);
|
||||
expect(nameColumns).toHaveLength(1);
|
||||
expect(nameColumns[0].type).toBe('VARCHAR(100)'); // Should keep original type
|
||||
expect(nameColumns[0].type).toBe('varchar(100)'); // Should keep original type
|
||||
});
|
||||
|
||||
it('should use default schema when not specified', async () => {
|
||||
@@ -204,12 +204,12 @@ describe('PostgreSQL ALTER TABLE ADD COLUMN Tests', () => {
|
||||
(col) => col.name === 'my-column'
|
||||
);
|
||||
expect(myColumn).toBeDefined();
|
||||
expect(myColumn?.type).toBe('VARCHAR(50)');
|
||||
expect(myColumn?.type).toBe('varchar(50)');
|
||||
|
||||
const anotherColumn = myTable.columns.find(
|
||||
(col) => col.name === 'another-column'
|
||||
);
|
||||
expect(anotherColumn).toBeDefined();
|
||||
expect(anotherColumn?.type).toBe('INTEGER');
|
||||
expect(anotherColumn?.type).toBe('integer');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,15 +30,15 @@ ALTER TABLE table_12 ALTER COLUMN field3 TYPE VARCHAR(254);
|
||||
// Check that the columns have the updated type
|
||||
const field1 = table.columns.find((col) => col.name === 'field1');
|
||||
expect(field1).toBeDefined();
|
||||
expect(field1?.type).toBe('VARCHAR(254)'); // Should be updated from 200 to 254
|
||||
expect(field1?.type).toBe('varchar(254)'); // Should be updated from 200 to 254
|
||||
|
||||
const field2 = table.columns.find((col) => col.name === 'field2');
|
||||
expect(field2).toBeDefined();
|
||||
expect(field2?.type).toBe('VARCHAR(254)');
|
||||
expect(field2?.type).toBe('varchar(254)');
|
||||
|
||||
const field3 = table.columns.find((col) => col.name === 'field3');
|
||||
expect(field3).toBeDefined();
|
||||
expect(field3?.type).toBe('VARCHAR(254)');
|
||||
expect(field3?.type).toBe('varchar(254)');
|
||||
});
|
||||
|
||||
it('should handle various ALTER COLUMN TYPE scenarios', async () => {
|
||||
@@ -65,13 +65,13 @@ ALTER TABLE test_table ALTER COLUMN score TYPE NUMERIC(10,4);
|
||||
const table = result.tables[0];
|
||||
|
||||
const nameCol = table.columns.find((col) => col.name === 'name');
|
||||
expect(nameCol?.type).toBe('VARCHAR(100)');
|
||||
expect(nameCol?.type).toBe('varchar(100)');
|
||||
|
||||
const ageCol = table.columns.find((col) => col.name === 'age');
|
||||
expect(ageCol?.type).toBe('INTEGER');
|
||||
expect(ageCol?.type).toBe('integer');
|
||||
|
||||
const scoreCol = table.columns.find((col) => col.name === 'score');
|
||||
expect(scoreCol?.type).toBe('NUMERIC(10,4)');
|
||||
expect(scoreCol?.type).toBe('numeric(10,4)');
|
||||
});
|
||||
|
||||
it('should handle multiple type changes on the same column', async () => {
|
||||
@@ -101,18 +101,18 @@ ALTER TABLE table_12 ALTER COLUMN field1 TYPE BIGINT;
|
||||
expect(table.schema).toBe('public');
|
||||
expect(table.columns).toHaveLength(4);
|
||||
|
||||
// Check that field1 has the final type (BIGINT), not the intermediate VARCHAR(254)
|
||||
// Check that field1 has the final type (bigint), not the intermediate varchar(254)
|
||||
const field1 = table.columns.find((col) => col.name === 'field1');
|
||||
expect(field1).toBeDefined();
|
||||
expect(field1?.type).toBe('BIGINT'); // Should be BIGINT, not VARCHAR(254)
|
||||
expect(field1?.type).toBe('bigint'); // Should be bigint, not varchar(254)
|
||||
|
||||
// Check that field2 and field3 still have VARCHAR(254)
|
||||
// Check that field2 and field3 still have varchar(254)
|
||||
const field2 = table.columns.find((col) => col.name === 'field2');
|
||||
expect(field2).toBeDefined();
|
||||
expect(field2?.type).toBe('VARCHAR(254)');
|
||||
expect(field2?.type).toBe('varchar(254)');
|
||||
|
||||
const field3 = table.columns.find((col) => col.name === 'field3');
|
||||
expect(field3).toBeDefined();
|
||||
expect(field3?.type).toBe('VARCHAR(254)');
|
||||
expect(field3?.type).toBe('varchar(254)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('PostgreSQL Parser', () => {
|
||||
expect(result.tables[0].name).toBe('wizards');
|
||||
expect(result.tables[0].columns).toHaveLength(4);
|
||||
expect(result.tables[0].columns[0].name).toBe('id');
|
||||
expect(result.tables[0].columns[0].type).toBe('INTEGER');
|
||||
expect(result.tables[0].columns[0].type).toBe('integer');
|
||||
expect(result.tables[0].columns[0].primaryKey).toBe(true);
|
||||
});
|
||||
|
||||
@@ -81,9 +81,9 @@ describe('PostgreSQL Parser', () => {
|
||||
|
||||
expect(result.tables).toHaveLength(1);
|
||||
const columns = result.tables[0].columns;
|
||||
expect(columns.find((c) => c.name === 'id')?.type).toBe('UUID');
|
||||
expect(columns.find((c) => c.name === 'data')?.type).toBe('JSONB');
|
||||
expect(columns.find((c) => c.name === 'tags')?.type).toBe('TEXT[]');
|
||||
expect(columns.find((c) => c.name === 'id')?.type).toBe('uuid');
|
||||
expect(columns.find((c) => c.name === 'data')?.type).toBe('jsonb');
|
||||
expect(columns.find((c) => c.name === 'tags')?.type).toBe('text[]');
|
||||
});
|
||||
|
||||
it('should handle numeric with precision', async () => {
|
||||
@@ -102,7 +102,7 @@ describe('PostgreSQL Parser', () => {
|
||||
const columns = result.tables[0].columns;
|
||||
// Parser limitation: scale on separate line is not captured
|
||||
const amountType = columns.find((c) => c.name === 'amount')?.type;
|
||||
expect(amountType).toMatch(/^NUMERIC/);
|
||||
expect(amountType).toMatch(/^numeric/);
|
||||
});
|
||||
|
||||
it('should handle multi-line numeric definitions', async () => {
|
||||
|
||||
@@ -27,55 +27,55 @@ CREATE TABLE public.activities (
|
||||
// Check each column
|
||||
const columns = table.columns;
|
||||
|
||||
// id column - serial4 should become INTEGER with auto-increment
|
||||
// id column - serial4 is preserved as serial with auto-increment
|
||||
const idCol = columns.find((c) => c.name === 'id');
|
||||
expect(idCol).toBeDefined();
|
||||
expect(idCol?.type).toBe('INTEGER');
|
||||
expect(idCol?.type).toBe('serial');
|
||||
expect(idCol?.primaryKey).toBe(true);
|
||||
expect(idCol?.increment).toBe(true);
|
||||
expect(idCol?.nullable).toBe(false);
|
||||
|
||||
// user_id column - int4 should become INTEGER
|
||||
// user_id column - int4 becomes integer
|
||||
const userIdCol = columns.find((c) => c.name === 'user_id');
|
||||
expect(userIdCol).toBeDefined();
|
||||
expect(userIdCol?.type).toBe('INTEGER');
|
||||
expect(userIdCol?.type).toBe('integer');
|
||||
expect(userIdCol?.nullable).toBe(false);
|
||||
|
||||
// workflow_id column - int4 NULL
|
||||
const workflowIdCol = columns.find((c) => c.name === 'workflow_id');
|
||||
expect(workflowIdCol).toBeDefined();
|
||||
expect(workflowIdCol?.type).toBe('INTEGER');
|
||||
expect(workflowIdCol?.type).toBe('integer');
|
||||
expect(workflowIdCol?.nullable).toBe(true);
|
||||
|
||||
// task_id column - int4 NULL
|
||||
const taskIdCol = columns.find((c) => c.name === 'task_id');
|
||||
expect(taskIdCol).toBeDefined();
|
||||
expect(taskIdCol?.type).toBe('INTEGER');
|
||||
expect(taskIdCol?.type).toBe('integer');
|
||||
expect(taskIdCol?.nullable).toBe(true);
|
||||
|
||||
// action column - character varying(50)
|
||||
// action column - character varying(50) becomes varchar(50)
|
||||
const actionCol = columns.find((c) => c.name === 'action');
|
||||
expect(actionCol).toBeDefined();
|
||||
expect(actionCol?.type).toBe('VARCHAR(50)');
|
||||
expect(actionCol?.type).toBe('varchar(50)');
|
||||
expect(actionCol?.nullable).toBe(false);
|
||||
|
||||
// description column - text
|
||||
const descriptionCol = columns.find((c) => c.name === 'description');
|
||||
expect(descriptionCol).toBeDefined();
|
||||
expect(descriptionCol?.type).toBe('TEXT');
|
||||
expect(descriptionCol?.type).toBe('text');
|
||||
expect(descriptionCol?.nullable).toBe(false);
|
||||
|
||||
// created_at column - timestamp with default
|
||||
const createdAtCol = columns.find((c) => c.name === 'created_at');
|
||||
expect(createdAtCol).toBeDefined();
|
||||
expect(createdAtCol?.type).toBe('TIMESTAMP');
|
||||
expect(createdAtCol?.type).toBe('timestamp');
|
||||
expect(createdAtCol?.nullable).toBe(false);
|
||||
expect(createdAtCol?.default).toContain('NOW');
|
||||
|
||||
// is_read column - bool with default
|
||||
// is_read column - bool becomes boolean with default
|
||||
const isReadCol = columns.find((c) => c.name === 'is_read');
|
||||
expect(isReadCol).toBeDefined();
|
||||
expect(isReadCol?.type).toBe('BOOLEAN');
|
||||
expect(isReadCol?.type).toBe('boolean');
|
||||
expect(isReadCol?.nullable).toBe(false);
|
||||
expect(isReadCol?.default).toBe('FALSE');
|
||||
});
|
||||
@@ -106,44 +106,46 @@ CREATE TABLE type_test (
|
||||
const table = result.tables[0];
|
||||
const cols = table.columns;
|
||||
|
||||
// Check serial types
|
||||
expect(cols.find((c) => c.name === 'id')?.type).toBe('INTEGER');
|
||||
// Check serial types - preserved as serial, smallserial, bigserial
|
||||
expect(cols.find((c) => c.name === 'id')?.type).toBe('serial');
|
||||
expect(cols.find((c) => c.name === 'id')?.increment).toBe(true);
|
||||
expect(cols.find((c) => c.name === 'small_id')?.type).toBe('SMALLINT');
|
||||
expect(cols.find((c) => c.name === 'small_id')?.type).toBe(
|
||||
'smallserial'
|
||||
);
|
||||
expect(cols.find((c) => c.name === 'small_id')?.increment).toBe(true);
|
||||
expect(cols.find((c) => c.name === 'big_id')?.type).toBe('BIGINT');
|
||||
expect(cols.find((c) => c.name === 'big_id')?.type).toBe('bigserial');
|
||||
expect(cols.find((c) => c.name === 'big_id')?.increment).toBe(true);
|
||||
|
||||
// Check integer types
|
||||
expect(cols.find((c) => c.name === 'int_col')?.type).toBe('INTEGER');
|
||||
expect(cols.find((c) => c.name === 'small_int')?.type).toBe('SMALLINT');
|
||||
expect(cols.find((c) => c.name === 'big_int')?.type).toBe('BIGINT');
|
||||
// Check integer types - normalized to lowercase
|
||||
expect(cols.find((c) => c.name === 'int_col')?.type).toBe('integer');
|
||||
expect(cols.find((c) => c.name === 'small_int')?.type).toBe('smallint');
|
||||
expect(cols.find((c) => c.name === 'big_int')?.type).toBe('bigint');
|
||||
|
||||
// Check boolean types
|
||||
expect(cols.find((c) => c.name === 'bool_col')?.type).toBe('BOOLEAN');
|
||||
// Check boolean types - normalized to lowercase
|
||||
expect(cols.find((c) => c.name === 'bool_col')?.type).toBe('boolean');
|
||||
expect(cols.find((c) => c.name === 'boolean_col')?.type).toBe(
|
||||
'BOOLEAN'
|
||||
'boolean'
|
||||
);
|
||||
|
||||
// Check string types
|
||||
// Check string types - normalized to lowercase
|
||||
expect(cols.find((c) => c.name === 'varchar_col')?.type).toBe(
|
||||
'VARCHAR(100)'
|
||||
'varchar(100)'
|
||||
);
|
||||
expect(cols.find((c) => c.name === 'char_col')?.type).toBe('CHAR(10)');
|
||||
expect(cols.find((c) => c.name === 'text_col')?.type).toBe('TEXT');
|
||||
expect(cols.find((c) => c.name === 'char_col')?.type).toBe('char(10)');
|
||||
expect(cols.find((c) => c.name === 'text_col')?.type).toBe('text');
|
||||
|
||||
// Check timestamp types
|
||||
// Check timestamp types - normalized to lowercase
|
||||
expect(cols.find((c) => c.name === 'timestamp_col')?.type).toBe(
|
||||
'TIMESTAMP'
|
||||
'timestamp'
|
||||
);
|
||||
expect(cols.find((c) => c.name === 'timestamptz_col')?.type).toBe(
|
||||
'TIMESTAMPTZ'
|
||||
'timestamptz'
|
||||
);
|
||||
|
||||
// Check other types
|
||||
expect(cols.find((c) => c.name === 'date_col')?.type).toBe('DATE');
|
||||
expect(cols.find((c) => c.name === 'time_col')?.type).toBe('TIME');
|
||||
expect(cols.find((c) => c.name === 'json_col')?.type).toBe('JSON');
|
||||
expect(cols.find((c) => c.name === 'jsonb_col')?.type).toBe('JSONB');
|
||||
// Check other types - normalized to lowercase
|
||||
expect(cols.find((c) => c.name === 'date_col')?.type).toBe('date');
|
||||
expect(cols.find((c) => c.name === 'time_col')?.type).toBe('time');
|
||||
expect(cols.find((c) => c.name === 'json_col')?.type).toBe('json');
|
||||
expect(cols.find((c) => c.name === 'jsonb_col')?.type).toBe('jsonb');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,14 +42,14 @@ CREATE TABLE book_spells (
|
||||
(c) => c.name === 'spell_book_id'
|
||||
);
|
||||
expect(spellBookIdColumn).toBeDefined();
|
||||
expect(spellBookIdColumn!.type).toBe('UUID');
|
||||
expect(spellBookIdColumn!.type).toBe('uuid');
|
||||
expect(spellBookIdColumn!.nullable).toBe(false);
|
||||
|
||||
const spellIdColumn = bookSpells!.columns.find(
|
||||
(c) => c.name === 'spell_id'
|
||||
);
|
||||
expect(spellIdColumn).toBeDefined();
|
||||
expect(spellIdColumn!.type).toBe('UUID');
|
||||
expect(spellIdColumn!.type).toBe('uuid');
|
||||
expect(spellIdColumn!.nullable).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -249,69 +249,159 @@ function splitSQLStatements(sql: string): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize PostgreSQL type aliases to standard types
|
||||
* Set of serial type names for O(1) lookup
|
||||
*/
|
||||
function normalizePostgreSQLType(type: string): string {
|
||||
const SERIAL_TYPES = new Set([
|
||||
'SERIAL',
|
||||
'SERIAL2',
|
||||
'SERIAL4',
|
||||
'SERIAL8',
|
||||
'BIGSERIAL',
|
||||
'SMALLSERIAL',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check if a type is a serial type
|
||||
*/
|
||||
function isSerialTypeName(typeName: string): boolean {
|
||||
return SERIAL_TYPES.has(typeName.toUpperCase().split('(')[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize PostgreSQL type syntax to lowercase canonical form.
|
||||
* This function handles parsing-level normalization only - it converts
|
||||
* verbose SQL syntax to the preferred short form that getPreferredSynonym
|
||||
* expects. It preserves semantic types like serial (does NOT convert to integer).
|
||||
*
|
||||
* The optional `length` parameter is used to resolve ambiguous types where
|
||||
* the SQL parser returns a base type with a length modifier (e.g., 'SERIAL'
|
||||
* with length=2 for 'serial2', or 'INT' with length=8 for 'int8').
|
||||
*
|
||||
* Type synonym resolution (e.g., integer→int) is handled by getPreferredSynonym.
|
||||
*/
|
||||
function normalizePostgreSQLType(
|
||||
type: string,
|
||||
length?: number | undefined
|
||||
): string {
|
||||
const upperType = type.toUpperCase();
|
||||
|
||||
// Handle types with parameters - more complex regex to handle CHARACTER VARYING
|
||||
// Handle types with parameters (e.g., VARCHAR(255), NUMERIC(10,2))
|
||||
const typeMatch = upperType.match(/^([\w\s]+?)(\(.+\))?$/);
|
||||
if (!typeMatch) return type;
|
||||
if (!typeMatch) return type.toLowerCase();
|
||||
|
||||
const baseType = typeMatch[1].trim();
|
||||
const params = typeMatch[2] || '';
|
||||
|
||||
let normalizedBase: string;
|
||||
switch (baseType) {
|
||||
// Serial types
|
||||
// Serial types - preserve as-is (they are valid PostgreSQL types)
|
||||
// Handle parser quirk: 'SERIAL' with length=2 means 'serial2' (smallserial)
|
||||
case 'SERIAL':
|
||||
if (length === 2) {
|
||||
normalizedBase = 'smallserial';
|
||||
} else if (length === 8) {
|
||||
normalizedBase = 'bigserial';
|
||||
} else {
|
||||
normalizedBase = 'serial';
|
||||
}
|
||||
break;
|
||||
case 'SERIAL4':
|
||||
normalizedBase = 'INTEGER';
|
||||
normalizedBase = 'serial';
|
||||
break;
|
||||
case 'BIGSERIAL':
|
||||
case 'SERIAL8':
|
||||
normalizedBase = 'BIGINT';
|
||||
normalizedBase = 'bigserial';
|
||||
break;
|
||||
case 'SMALLSERIAL':
|
||||
case 'SERIAL2':
|
||||
normalizedBase = 'SMALLINT';
|
||||
normalizedBase = 'smallserial';
|
||||
break;
|
||||
// Integer aliases
|
||||
// Integer types - normalize to lowercase canonical form
|
||||
// Handle parser quirk: 'INT' with length=2 means 'int2' (smallint)
|
||||
case 'INT':
|
||||
if (length === 2) {
|
||||
normalizedBase = 'smallint';
|
||||
} else if (length === 8) {
|
||||
normalizedBase = 'bigint';
|
||||
} else {
|
||||
normalizedBase = 'integer';
|
||||
}
|
||||
break;
|
||||
case 'INT4':
|
||||
normalizedBase = 'INTEGER';
|
||||
case 'INTEGER':
|
||||
normalizedBase = 'integer';
|
||||
break;
|
||||
case 'INT2':
|
||||
normalizedBase = 'SMALLINT';
|
||||
case 'SMALLINT':
|
||||
normalizedBase = 'smallint';
|
||||
break;
|
||||
case 'INT8':
|
||||
normalizedBase = 'BIGINT';
|
||||
case 'BIGINT':
|
||||
normalizedBase = 'bigint';
|
||||
break;
|
||||
// Boolean aliases
|
||||
// Boolean
|
||||
case 'BOOL':
|
||||
normalizedBase = 'BOOLEAN';
|
||||
case 'BOOLEAN':
|
||||
normalizedBase = 'boolean';
|
||||
break;
|
||||
// Character types - use common names
|
||||
// Character types - normalize verbose forms
|
||||
case 'CHARACTER VARYING':
|
||||
normalizedBase = 'varchar';
|
||||
break;
|
||||
case 'VARCHAR':
|
||||
normalizedBase = 'VARCHAR';
|
||||
normalizedBase = 'varchar';
|
||||
break;
|
||||
case 'CHARACTER':
|
||||
case 'CHAR':
|
||||
normalizedBase = 'CHAR';
|
||||
normalizedBase = 'char';
|
||||
break;
|
||||
// Timestamp aliases
|
||||
case 'CHAR':
|
||||
normalizedBase = 'char';
|
||||
break;
|
||||
// Timestamp types
|
||||
case 'TIMESTAMPTZ':
|
||||
case 'TIMESTAMP WITH TIME ZONE':
|
||||
normalizedBase = 'TIMESTAMPTZ';
|
||||
normalizedBase = 'timestamptz';
|
||||
break;
|
||||
case 'TIMESTAMP WITHOUT TIME ZONE':
|
||||
case 'TIMESTAMP':
|
||||
normalizedBase = 'timestamp';
|
||||
break;
|
||||
// Time types
|
||||
case 'TIMETZ':
|
||||
case 'TIME WITH TIME ZONE':
|
||||
normalizedBase = 'timetz';
|
||||
break;
|
||||
case 'TIME WITHOUT TIME ZONE':
|
||||
case 'TIME':
|
||||
normalizedBase = 'time';
|
||||
break;
|
||||
// Floating point
|
||||
case 'FLOAT4':
|
||||
case 'REAL':
|
||||
normalizedBase = 'real';
|
||||
break;
|
||||
case 'FLOAT8':
|
||||
case 'DOUBLE PRECISION':
|
||||
normalizedBase = 'double precision';
|
||||
break;
|
||||
// Bit types
|
||||
case 'BIT VARYING':
|
||||
normalizedBase = 'varbit';
|
||||
break;
|
||||
// Numeric types
|
||||
case 'DECIMAL':
|
||||
normalizedBase = 'numeric';
|
||||
break;
|
||||
case 'NUMERIC':
|
||||
normalizedBase = 'numeric';
|
||||
break;
|
||||
default:
|
||||
// For unknown types (like enums), preserve original case
|
||||
return type;
|
||||
// For unknown types (like enums, user-defined), preserve original in lowercase
|
||||
return type.toLowerCase();
|
||||
}
|
||||
|
||||
// Return normalized type with original parameters preserved
|
||||
return normalizedBase + params;
|
||||
// Return normalized type with parameters preserved (lowercase)
|
||||
return normalizedBase + params.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,17 +462,9 @@ function extractColumnsFromSQL(sql: string): SQLColumn[] {
|
||||
}
|
||||
|
||||
// Check if it's a serial type for increment flag
|
||||
const upperType = columnType.toUpperCase();
|
||||
const isSerialType = [
|
||||
'SERIAL',
|
||||
'SERIAL2',
|
||||
'SERIAL4',
|
||||
'SERIAL8',
|
||||
'BIGSERIAL',
|
||||
'SMALLSERIAL',
|
||||
].includes(upperType.split('(')[0]);
|
||||
const isSerialType = isSerialTypeName(columnType);
|
||||
|
||||
// Normalize the type
|
||||
// Normalize the type (preserves serial types)
|
||||
columnType = normalizePostgreSQLType(columnType);
|
||||
|
||||
// Check for common constraints
|
||||
@@ -820,121 +902,76 @@ export async function fromPostgres(
|
||||
}
|
||||
}
|
||||
|
||||
// First normalize the base type
|
||||
let normalizedBaseType = rawDataType;
|
||||
let isSerialType = false;
|
||||
|
||||
// Check if it's a serial type first
|
||||
const upperType = rawDataType.toUpperCase();
|
||||
const typeLength = definition?.length as
|
||||
// Check if it's a serial type
|
||||
const isSerialType = isSerialTypeName(rawDataType);
|
||||
const typeLength = columnDef.definition?.length as
|
||||
| number
|
||||
| undefined;
|
||||
|
||||
if (upperType === 'SERIAL') {
|
||||
// Use length to determine the actual serial type
|
||||
if (typeLength === 2) {
|
||||
normalizedBaseType = 'SMALLINT';
|
||||
isSerialType = true;
|
||||
} else if (typeLength === 8) {
|
||||
normalizedBaseType = 'BIGINT';
|
||||
isSerialType = true;
|
||||
} else {
|
||||
// Default serial or serial4
|
||||
normalizedBaseType = 'INTEGER';
|
||||
isSerialType = true;
|
||||
}
|
||||
} else if (upperType === 'SMALLSERIAL') {
|
||||
normalizedBaseType = 'SMALLINT';
|
||||
isSerialType = true;
|
||||
} else if (upperType === 'BIGSERIAL') {
|
||||
normalizedBaseType = 'BIGINT';
|
||||
isSerialType = true;
|
||||
} else if (upperType === 'INT') {
|
||||
// Use length to determine the actual int type
|
||||
if (typeLength === 2) {
|
||||
normalizedBaseType = 'SMALLINT';
|
||||
} else if (typeLength === 8) {
|
||||
normalizedBaseType = 'BIGINT';
|
||||
} else {
|
||||
// Default int or int4
|
||||
normalizedBaseType = 'INTEGER';
|
||||
}
|
||||
} else {
|
||||
// Apply normalization for other types
|
||||
normalizedBaseType =
|
||||
normalizePostgreSQLType(rawDataType);
|
||||
}
|
||||
// Normalize the type (pass length to handle parser quirks like INT with length=8)
|
||||
let finalDataType = normalizePostgreSQLType(
|
||||
rawDataType,
|
||||
typeLength
|
||||
);
|
||||
|
||||
// Now handle parameters - but skip for integer types that shouldn't have them
|
||||
let finalDataType = normalizedBaseType;
|
||||
|
||||
// Don't add parameters to INTEGER types that come from int4, int8, serial types, etc.
|
||||
const isNormalizedIntegerType =
|
||||
['INTEGER', 'BIGINT', 'SMALLINT'].includes(
|
||||
normalizedBaseType
|
||||
) &&
|
||||
[
|
||||
'INT',
|
||||
'SERIAL',
|
||||
'SMALLSERIAL',
|
||||
'BIGSERIAL',
|
||||
].includes(upperType);
|
||||
|
||||
if (!isSerialType && !isNormalizedIntegerType) {
|
||||
// Include precision/scale/length in the type string if available
|
||||
// Add type parameters for non-serial, non-integer types
|
||||
if (!isSerialType) {
|
||||
const precision =
|
||||
columnDef.definition?.precision;
|
||||
const scale = columnDef.definition?.scale;
|
||||
const length = columnDef.definition?.length;
|
||||
|
||||
// Also check if there's a suffix that includes the precision/scale
|
||||
const definition =
|
||||
const suffix = (
|
||||
columnDef.definition as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const suffix = definition?.suffix;
|
||||
>
|
||||
)?.suffix;
|
||||
|
||||
if (
|
||||
suffix &&
|
||||
Array.isArray(suffix) &&
|
||||
suffix.length > 0
|
||||
) {
|
||||
// The suffix contains the full type parameters like (10,2)
|
||||
const params = suffix
|
||||
.map((s: unknown) => {
|
||||
if (
|
||||
// Skip adding parameters to integer types (they don't have size params)
|
||||
const isIntegerType = [
|
||||
'integer',
|
||||
'bigint',
|
||||
'smallint',
|
||||
].includes(finalDataType);
|
||||
|
||||
if (!isIntegerType) {
|
||||
if (
|
||||
suffix &&
|
||||
Array.isArray(suffix) &&
|
||||
suffix.length > 0
|
||||
) {
|
||||
const params = suffix
|
||||
.map((s: unknown) =>
|
||||
typeof s === 'object' &&
|
||||
s !== null &&
|
||||
'value' in s
|
||||
) {
|
||||
return String(
|
||||
(s as { value: unknown })
|
||||
.value
|
||||
);
|
||||
}
|
||||
return String(s);
|
||||
})
|
||||
.join(',');
|
||||
finalDataType = `${normalizedBaseType}(${params})`;
|
||||
} else if (precision !== undefined) {
|
||||
if (scale !== undefined) {
|
||||
finalDataType = `${normalizedBaseType}(${precision},${scale})`;
|
||||
} else {
|
||||
finalDataType = `${normalizedBaseType}(${precision})`;
|
||||
? String(
|
||||
(
|
||||
s as {
|
||||
value: unknown;
|
||||
}
|
||||
).value
|
||||
)
|
||||
: String(s)
|
||||
)
|
||||
.join(',');
|
||||
finalDataType = `${finalDataType}(${params})`;
|
||||
} else if (precision !== undefined) {
|
||||
finalDataType =
|
||||
scale !== undefined
|
||||
? `${finalDataType}(${precision},${scale})`
|
||||
: `${finalDataType}(${precision})`;
|
||||
} else if (
|
||||
scale !== undefined &&
|
||||
typeLength !== undefined
|
||||
) {
|
||||
// For NUMERIC, node-sql-parser stores precision as 'length'
|
||||
finalDataType = `${finalDataType}(${typeLength},${scale})`;
|
||||
} else if (
|
||||
typeLength !== undefined &&
|
||||
typeLength !== null
|
||||
) {
|
||||
finalDataType = `${finalDataType}(${typeLength})`;
|
||||
}
|
||||
} else if (
|
||||
scale !== undefined &&
|
||||
length !== undefined
|
||||
) {
|
||||
// For DECIMAL/NUMERIC, node-sql-parser stores precision as 'length'
|
||||
finalDataType = `${normalizedBaseType}(${length},${scale})`;
|
||||
} else if (
|
||||
length !== undefined &&
|
||||
length !== null
|
||||
) {
|
||||
// For VARCHAR, CHAR, etc.
|
||||
finalDataType = `${normalizedBaseType}(${length})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1259,81 +1296,61 @@ export async function fromPostgres(
|
||||
const rawDataType = String(
|
||||
definition?.dataType || 'TEXT'
|
||||
);
|
||||
// console.log('expr:', JSON.stringify(expr, null, 2));
|
||||
|
||||
// Normalize the type
|
||||
let normalizedBaseType =
|
||||
normalizePostgreSQLType(rawDataType);
|
||||
|
||||
// Check if it's a serial type
|
||||
const upperType = rawDataType.toUpperCase();
|
||||
const isSerialType = [
|
||||
'SERIAL',
|
||||
'SERIAL2',
|
||||
'SERIAL4',
|
||||
'SERIAL8',
|
||||
'BIGSERIAL',
|
||||
'SMALLSERIAL',
|
||||
].includes(upperType.split('(')[0]);
|
||||
const isSerialType = isSerialTypeName(rawDataType);
|
||||
const typeLength = definition?.length as
|
||||
| number
|
||||
| undefined;
|
||||
|
||||
if (isSerialType) {
|
||||
const typeLength = definition?.length as
|
||||
| number
|
||||
| undefined;
|
||||
if (upperType === 'SERIAL') {
|
||||
if (typeLength === 2) {
|
||||
normalizedBaseType = 'SMALLINT';
|
||||
} else if (typeLength === 8) {
|
||||
normalizedBaseType = 'BIGINT';
|
||||
} else {
|
||||
normalizedBaseType = 'INTEGER';
|
||||
}
|
||||
}
|
||||
}
|
||||
// Normalize the type (pass length to handle parser quirks)
|
||||
let finalDataType = normalizePostgreSQLType(
|
||||
rawDataType,
|
||||
typeLength
|
||||
);
|
||||
|
||||
// Handle type parameters
|
||||
let finalDataType = normalizedBaseType;
|
||||
const isNormalizedIntegerType =
|
||||
['INTEGER', 'BIGINT', 'SMALLINT'].includes(
|
||||
normalizedBaseType
|
||||
) &&
|
||||
(upperType === 'INT' || upperType === 'SERIAL');
|
||||
|
||||
if (!isSerialType && !isNormalizedIntegerType) {
|
||||
// Add type parameters for non-serial, non-integer types
|
||||
if (!isSerialType) {
|
||||
const precision = definition?.precision;
|
||||
const scale = definition?.scale;
|
||||
const length = definition?.length;
|
||||
const suffix =
|
||||
(definition?.suffix as unknown[]) || [];
|
||||
|
||||
if (suffix.length > 0) {
|
||||
const params = suffix
|
||||
.map((s: unknown) => {
|
||||
if (
|
||||
const isIntegerType = [
|
||||
'integer',
|
||||
'bigint',
|
||||
'smallint',
|
||||
].includes(finalDataType);
|
||||
|
||||
if (!isIntegerType) {
|
||||
if (suffix.length > 0) {
|
||||
const params = suffix
|
||||
.map((s: unknown) =>
|
||||
typeof s === 'object' &&
|
||||
s !== null &&
|
||||
'value' in s
|
||||
) {
|
||||
return String(
|
||||
(s as { value: unknown })
|
||||
.value
|
||||
);
|
||||
}
|
||||
return String(s);
|
||||
})
|
||||
.join(',');
|
||||
finalDataType = `${normalizedBaseType}(${params})`;
|
||||
} else if (precision !== undefined) {
|
||||
if (scale !== undefined) {
|
||||
finalDataType = `${normalizedBaseType}(${precision},${scale})`;
|
||||
} else {
|
||||
finalDataType = `${normalizedBaseType}(${precision})`;
|
||||
? String(
|
||||
(
|
||||
s as {
|
||||
value: unknown;
|
||||
}
|
||||
).value
|
||||
)
|
||||
: String(s)
|
||||
)
|
||||
.join(',');
|
||||
finalDataType = `${finalDataType}(${params})`;
|
||||
} else if (precision !== undefined) {
|
||||
finalDataType =
|
||||
scale !== undefined
|
||||
? `${finalDataType}(${precision},${scale})`
|
||||
: `${finalDataType}(${precision})`;
|
||||
} else if (
|
||||
typeLength !== undefined &&
|
||||
typeLength !== null
|
||||
) {
|
||||
finalDataType = `${finalDataType}(${typeLength})`;
|
||||
}
|
||||
} else if (
|
||||
length !== undefined &&
|
||||
length !== null
|
||||
) {
|
||||
finalDataType = `${normalizedBaseType}(${length})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1429,84 +1446,62 @@ export async function fromPostgres(
|
||||
definition?.dataType || 'TEXT'
|
||||
);
|
||||
|
||||
// Normalize the type
|
||||
let normalizedBaseType =
|
||||
normalizePostgreSQLType(rawDataType);
|
||||
|
||||
// Check if it's a serial type
|
||||
const upperType = rawDataType.toUpperCase();
|
||||
const isSerialType = [
|
||||
'SERIAL',
|
||||
'SERIAL2',
|
||||
'SERIAL4',
|
||||
'SERIAL8',
|
||||
'BIGSERIAL',
|
||||
'SMALLSERIAL',
|
||||
].includes(upperType.split('(')[0]);
|
||||
const isSerialType =
|
||||
isSerialTypeName(rawDataType);
|
||||
const typeLength = definition?.length as
|
||||
| number
|
||||
| undefined;
|
||||
|
||||
if (isSerialType) {
|
||||
const typeLength = definition?.length as
|
||||
| number
|
||||
| undefined;
|
||||
if (upperType === 'SERIAL') {
|
||||
if (typeLength === 2) {
|
||||
normalizedBaseType = 'SMALLINT';
|
||||
} else if (typeLength === 8) {
|
||||
normalizedBaseType = 'BIGINT';
|
||||
} else {
|
||||
normalizedBaseType = 'INTEGER';
|
||||
}
|
||||
}
|
||||
}
|
||||
// Normalize the type (pass length to handle parser quirks)
|
||||
let finalDataType = normalizePostgreSQLType(
|
||||
rawDataType,
|
||||
typeLength
|
||||
);
|
||||
|
||||
// Handle type parameters
|
||||
let finalDataType = normalizedBaseType;
|
||||
const isNormalizedIntegerType =
|
||||
['INTEGER', 'BIGINT', 'SMALLINT'].includes(
|
||||
normalizedBaseType
|
||||
) &&
|
||||
(upperType === 'INT' ||
|
||||
upperType === 'SERIAL');
|
||||
|
||||
if (!isSerialType && !isNormalizedIntegerType) {
|
||||
// Add type parameters for non-serial, non-integer types
|
||||
if (!isSerialType) {
|
||||
const precision =
|
||||
columnDef.definition?.precision;
|
||||
const scale = columnDef.definition?.scale;
|
||||
const length = columnDef.definition?.length;
|
||||
const suffix =
|
||||
(definition?.suffix as unknown[]) || [];
|
||||
|
||||
if (suffix.length > 0) {
|
||||
const params = suffix
|
||||
.map((s: unknown) => {
|
||||
if (
|
||||
const isIntegerType = [
|
||||
'integer',
|
||||
'bigint',
|
||||
'smallint',
|
||||
].includes(finalDataType);
|
||||
|
||||
if (!isIntegerType) {
|
||||
if (suffix.length > 0) {
|
||||
const params = suffix
|
||||
.map((s: unknown) =>
|
||||
typeof s === 'object' &&
|
||||
s !== null &&
|
||||
'value' in s
|
||||
) {
|
||||
return String(
|
||||
(
|
||||
s as {
|
||||
value: unknown;
|
||||
}
|
||||
).value
|
||||
);
|
||||
}
|
||||
return String(s);
|
||||
})
|
||||
.join(',');
|
||||
finalDataType = `${normalizedBaseType}(${params})`;
|
||||
} else if (precision !== undefined) {
|
||||
if (scale !== undefined) {
|
||||
finalDataType = `${normalizedBaseType}(${precision},${scale})`;
|
||||
} else {
|
||||
finalDataType = `${normalizedBaseType}(${precision})`;
|
||||
? String(
|
||||
(
|
||||
s as {
|
||||
value: unknown;
|
||||
}
|
||||
).value
|
||||
)
|
||||
: String(s)
|
||||
)
|
||||
.join(',');
|
||||
finalDataType = `${finalDataType}(${params})`;
|
||||
} else if (precision !== undefined) {
|
||||
finalDataType =
|
||||
scale !== undefined
|
||||
? `${finalDataType}(${precision},${scale})`
|
||||
: `${finalDataType}(${precision})`;
|
||||
} else if (
|
||||
typeLength !== undefined &&
|
||||
typeLength !== null
|
||||
) {
|
||||
finalDataType = `${finalDataType}(${typeLength})`;
|
||||
}
|
||||
} else if (
|
||||
length !== undefined &&
|
||||
length !== null
|
||||
) {
|
||||
finalDataType = `${normalizedBaseType}(${length})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user