From ce75656273b784bf7f24753032f6653472737476 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 11:11:40 +0000 Subject: [PATCH] fix(oracle): map NUMBER(p,0) by precision to avoid silent int narrowing Oracle catalog emits NUMBER(19,0)/NUMBER(38,0) for bigint-width columns, but parse always chose canonical integer, so cross-dialect migrate created Postgres/MySQL int columns and could truncate or reject values. Co-authored-by: huy.phan9 --- packages/sql/src/modules/type-mapping.test.ts | 7 ++++++- .../sql/src/providers/oracle/oracle.sql-dialect.ts | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/sql/src/modules/type-mapping.test.ts b/packages/sql/src/modules/type-mapping.test.ts index bf81139c..3e577ebd 100644 --- a/packages/sql/src/modules/type-mapping.test.ts +++ b/packages/sql/src/modules/type-mapping.test.ts @@ -55,7 +55,12 @@ describe('cross-dialect type translation', () => { it('Oracle → Postgres', () => { expect(xlate(oracle, pg, 'VARCHAR2(100)').sql).toBe('varchar(100)'); - expect(xlate(oracle, pg, 'NUMBER(10,0)').sql).toBe('integer'); + // NUMBER(p,0): width picks the integer family (int32 / int64 / decimal). + expect(xlate(oracle, pg, 'NUMBER(9,0)').sql).toBe('integer'); + expect(xlate(oracle, pg, 'NUMBER(10,0)').sql).toBe('bigint'); + expect(xlate(oracle, pg, 'NUMBER(18,0)').sql).toBe('bigint'); + expect(xlate(oracle, pg, 'NUMBER(19,0)').sql).toBe('numeric(19,0)'); + expect(xlate(oracle, pg, 'NUMBER(38,0)').sql).toBe('numeric(38,0)'); expect(xlate(oracle, pg, 'NUMBER(10,2)').sql).toBe('numeric(10,2)'); expect(xlate(oracle, pg, 'CLOB').sql).toBe('text'); }); diff --git a/packages/sql/src/providers/oracle/oracle.sql-dialect.ts b/packages/sql/src/providers/oracle/oracle.sql-dialect.ts index 1c6e52fc..b8f05c3b 100644 --- a/packages/sql/src/providers/oracle/oracle.sql-dialect.ts +++ b/packages/sql/src/providers/oracle/oracle.sql-dialect.ts @@ -5,8 +5,18 @@ import { makeDialectTypeFns, plain, sized, sizedOr, decimalAs, warn } from '../. const types = makeDialectTypeFns({ label: 'Oracle', parseMap: { - // NUMBER(p,0) is an integer; NUMBER(p,s>0) or bare NUMBER is decimal - number: (tok) => (tok.scale && tok.scale > 0 ? 'decimal' : tok.precision !== undefined ? 'integer' : 'decimal'), + // NUMBER(p,s>0) / bare NUMBER / NUMBER(p) → decimal. NUMBER(p,0) is an + // integer-scaled value, but precision decides the family: p≤9 fits int32, + // p≤18 fits int64, anything wider must stay decimal. Mapping every + // NUMBER(p,0) to `integer` used to emit Postgres/MySQL `integer`/`int` for + // NUMBER(19,0) and NUMBER(38,0) — silent narrowing on cross-dialect migrate. + number: (tok) => { + if ((tok.scale ?? 0) > 0) return 'decimal'; + if (tok.precision === undefined) return 'decimal'; + if (tok.precision <= 9) return 'integer'; + if (tok.precision <= 18) return 'bigint'; + return 'decimal'; + }, integer: 'integer', int: 'integer', smallint: 'integer',