fix: serial ddl import in pg (#1033)

* fix: serial ddl import in pg

* fix
This commit is contained in:
Guy Ben-Aharon
2025-12-23 16:00:07 +02:00
committed by GitHub
parent 674ed2249e
commit 6eae4b0fc3
9 changed files with 353 additions and 325 deletions

View File

@@ -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',

View File

@@ -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');

View File

@@ -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 &&

View File

@@ -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');
});
});

View File

@@ -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)');
});
});

View File

@@ -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 () => {

View File

@@ -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');
});
});

View File

@@ -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);
});

View File

@@ -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})`;
}
}