Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 81bda14

Browse files
committed
fix: fix getLong on NUMERIC
1 parent c6ca732 commit 81bda14

6 files changed

Lines changed: 439 additions & 17 deletions

File tree

src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSet.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ public byte getByte(int columnIndex) throws SQLException {
307307
case ENUM:
308308
return isNull ? (byte) 0 : checkedCastToByte(spanner.getLong(spannerIndex));
309309
case NUMERIC:
310-
return isNull ? (byte) 0 : checkedCastToByte(spanner.getBigDecimal(spannerIndex));
310+
return isNull
311+
? (byte) 0
312+
: checkedCastToByte(spanner.getBigDecimal(spannerIndex).toBigInteger());
311313
case PG_NUMERIC:
312314
return isNull
313315
? (byte) 0
@@ -354,7 +356,9 @@ public short getShort(int columnIndex) throws SQLException {
354356
}
355357
return isNull ? 0 : checkedCastToShort(spanner.getLong(spannerIndex));
356358
case NUMERIC:
357-
return isNull ? 0 : checkedCastToShort(spanner.getBigDecimal(spannerIndex));
359+
return isNull
360+
? (short) 0
361+
: checkedCastToShort(spanner.getBigDecimal(spannerIndex).toBigInteger());
358362
case PG_NUMERIC:
359363
return isNull
360364
? 0
@@ -395,7 +399,7 @@ public int getInt(int columnIndex) throws SQLException {
395399
case ENUM:
396400
return isNull ? 0 : checkedCastToInt(spanner.getLong(spannerIndex));
397401
case NUMERIC:
398-
return isNull ? 0 : checkedCastToInt(spanner.getBigDecimal(spannerIndex));
402+
return isNull ? 0 : checkedCastToInt(spanner.getBigDecimal(spannerIndex).toBigInteger());
399403
case PG_NUMERIC:
400404
return isNull
401405
? 0
@@ -432,7 +436,7 @@ public long getLong(int columnIndex) throws SQLException {
432436
case ENUM:
433437
return isNull ? 0L : spanner.getLong(spannerIndex);
434438
case NUMERIC:
435-
return isNull ? 0 : checkedCastToLong(parseBigDecimal(spanner.getString(spannerIndex)));
439+
return isNull ? 0L : checkedCastToLong(spanner.getBigDecimal(spannerIndex).toBigInteger());
436440
case PG_NUMERIC:
437441
return isNull
438442
? 0L

src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,46 +116,65 @@ static Object convert(Object value, Type type, Class<?> targetType) throws SQLEx
116116
}
117117
if (type.getCode() == Code.FLOAT64) return (Double) value != 0d;
118118
if (type.getCode() == Code.NUMERIC) return !value.equals(BigDecimal.ZERO);
119+
if (type.getCode() == Code.PG_NUMERIC)
120+
return !AbstractJdbcWrapper.parseBigDecimal((String) value).equals(BigDecimal.ZERO);
119121
}
120122
if (targetType.equals(BigDecimal.class)) {
121123
if (type.getCode() == Code.BOOL) return (Boolean) value ? BigDecimal.ONE : BigDecimal.ZERO;
122124
if (type.getCode() == Code.INT64 || type.getCode() == Code.ENUM)
123125
return BigDecimal.valueOf((Long) value);
124126
if (type.getCode() == Code.NUMERIC) return value;
127+
if (type.getCode() == Code.PG_NUMERIC)
128+
return AbstractJdbcWrapper.parseBigDecimal((String) value);
125129
}
126130
if (targetType.equals(Long.class)) {
127131
if (type.getCode() == Code.BOOL) return (Boolean) value ? 1L : 0L;
128132
if (type.getCode() == Code.INT64 || type.getCode() == Code.ENUM) return value;
129133
if (type.getCode() == Code.NUMERIC)
130-
return AbstractJdbcWrapper.checkedCastToLong((BigDecimal) value);
134+
return AbstractJdbcWrapper.checkedCastToLong(((BigDecimal) value).toBigInteger());
135+
if (type.getCode() == Code.PG_NUMERIC)
136+
return AbstractJdbcWrapper.checkedCastToLong(
137+
AbstractJdbcWrapper.parseBigDecimal((String) value).toBigInteger());
131138
}
132139
if (targetType.equals(Integer.class)) {
133140
if (type.getCode() == Code.BOOL) return (Boolean) value ? 1 : 0;
134141
if (type.getCode() == Code.INT64 || type.getCode() == Code.ENUM)
135142
return AbstractJdbcWrapper.checkedCastToInt((Long) value);
136143
if (type.getCode() == Code.NUMERIC)
137-
return AbstractJdbcWrapper.checkedCastToInt((BigDecimal) value);
144+
return AbstractJdbcWrapper.checkedCastToInt(((BigDecimal) value).toBigInteger());
145+
if (type.getCode() == Code.PG_NUMERIC)
146+
return AbstractJdbcWrapper.checkedCastToInt(
147+
AbstractJdbcWrapper.parseBigDecimal((String) value).toBigInteger());
138148
}
139149
if (targetType.equals(Short.class)) {
140150
if (type.getCode() == Code.BOOL) return (Boolean) value ? 1 : 0;
141151
if (type.getCode() == Code.INT64 || type.getCode() == Code.ENUM)
142152
return AbstractJdbcWrapper.checkedCastToShort((Long) value);
143153
if (type.getCode() == Code.NUMERIC)
144-
return AbstractJdbcWrapper.checkedCastToShort((BigDecimal) value);
154+
return AbstractJdbcWrapper.checkedCastToShort(((BigDecimal) value).toBigInteger());
155+
if (type.getCode() == Code.PG_NUMERIC)
156+
return AbstractJdbcWrapper.checkedCastToShort(
157+
AbstractJdbcWrapper.parseBigDecimal((String) value).toBigInteger());
145158
}
146159
if (targetType.equals(Byte.class)) {
147160
if (type.getCode() == Code.BOOL) return (Boolean) value ? 1 : 0;
148161
if (type.getCode() == Code.INT64 || type.getCode() == Code.ENUM)
149162
return AbstractJdbcWrapper.checkedCastToByte((Long) value);
150163
if (type.getCode() == Code.NUMERIC)
151-
return AbstractJdbcWrapper.checkedCastToByte((BigDecimal) value);
164+
return AbstractJdbcWrapper.checkedCastToByte(((BigDecimal) value).toBigInteger());
165+
if (type.getCode() == Code.PG_NUMERIC)
166+
return AbstractJdbcWrapper.checkedCastToByte(
167+
AbstractJdbcWrapper.parseBigDecimal((String) value).toBigInteger());
152168
}
153169
if (targetType.equals(BigInteger.class)) {
154170
if (type.getCode() == Code.BOOL) return (Boolean) value ? BigInteger.ONE : BigInteger.ZERO;
155171
if (type.getCode() == Code.INT64 || type.getCode() == Code.ENUM)
156172
return BigInteger.valueOf((Long) value);
157173
if (type.getCode() == Code.NUMERIC)
158174
return AbstractJdbcWrapper.checkedCastToBigInteger((BigDecimal) value);
175+
if (type.getCode() == Code.PG_NUMERIC)
176+
return AbstractJdbcWrapper.checkedCastToBigInteger(
177+
AbstractJdbcWrapper.parseBigDecimal((String) value));
159178
}
160179
if (targetType.equals(Float.class)) {
161180
if (type.getCode() == Code.BOOL)
@@ -166,6 +185,8 @@ static Object convert(Object value, Type type, Class<?> targetType) throws SQLEx
166185
if (type.getCode() == Code.FLOAT64)
167186
return AbstractJdbcWrapper.checkedCastToFloat((Double) value);
168187
if (type.getCode() == Code.NUMERIC) return ((BigDecimal) value).floatValue();
188+
if (type.getCode() == Code.PG_NUMERIC)
189+
return AbstractJdbcWrapper.parseFloat((String) value);
169190
}
170191
if (targetType.equals(Double.class)) {
171192
if (type.getCode() == Code.BOOL)
@@ -174,6 +195,8 @@ static Object convert(Object value, Type type, Class<?> targetType) throws SQLEx
174195
return value;
175196
}
176197
if (type.getCode() == Code.NUMERIC) return ((BigDecimal) value).doubleValue();
198+
if (type.getCode() == Code.PG_NUMERIC)
199+
return AbstractJdbcWrapper.parseDouble((String) value);
177200
}
178201
if (targetType.equals(java.sql.Date.class)) {
179202
if (type.getCode() == Code.DATE) return value;

src/test/java/com/google/cloud/spanner/jdbc/ConcurrentTransactionOnEmulatorTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ public static void startEmulator() {
5959
.withExposedPorts(9010)
6060
.waitingFor(Wait.forListeningPorts(9010));
6161
emulator.start();
62+
try {
63+
Thread.sleep(1500); // Give gRPC server time to fully initialize
64+
} catch (InterruptedException e) {
65+
Thread.currentThread().interrupt();
66+
}
6267
properties = new Properties();
6368
properties.setProperty("autoConfigEmulator", "true");
6469
properties.setProperty(

src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetTest.java

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ public class JdbcResultSetTest {
141141
private static final BigDecimal NUMERIC_VALUE = new BigDecimal("3.14");
142142
private static final int NUMERIC_COLINDEX_NULL = 25;
143143
private static final int NUMERIC_COLINDEX_NOTNULL = 26;
144+
private static final String PG_NUMERIC_COL_NULL = "PG_NUMERIC_COL_NULL";
145+
private static final String PG_NUMERIC_COL_NOT_NULL = "PG_NUMERIC_COL_NOT_NULL";
146+
private static final String PG_NUMERIC_COL_NAN = "PG_NUMERIC_COL_NAN";
147+
private static final int PG_NUMERIC_COLINDEX_NULL = 44;
148+
private static final int PG_NUMERIC_COLINDEX_NOTNULL = 45;
149+
private static final int PG_NUMERIC_COLINDEX_NAN = 46;
144150
private static final String JSON_COL_NULL = "JSON_COL_NULL";
145151
private static final String JSON_COL_NOT_NULL = "JSON_COL_NOT_NULL";
146152
private static final int JSON_COLINDEX_NULL = 27;
@@ -237,7 +243,10 @@ static ResultSet getMockResultSet() {
237243
Type.array(Type.proto(SingerInfo.getDescriptor().getFullName()))),
238244
StructField.of(
239245
PROTO_ENUM_ARRAY_COL,
240-
Type.array(Type.protoEnum(Genre.getDescriptor().getFullName())))),
246+
Type.array(Type.protoEnum(Genre.getDescriptor().getFullName()))),
247+
StructField.of(PG_NUMERIC_COL_NULL, Type.pgNumeric()),
248+
StructField.of(PG_NUMERIC_COL_NOT_NULL, Type.pgNumeric()),
249+
StructField.of(PG_NUMERIC_COL_NAN, Type.pgNumeric())),
241250
Collections.singletonList(
242251
Struct.newBuilder()
243252
.set(STRING_COL_NULL)
@@ -327,6 +336,12 @@ static ResultSet getMockResultSet() {
327336
PROTO_MSG_ARRAY_VALUE, SingerInfo.getDescriptor().getFullName())
328337
.set(PROTO_ENUM_ARRAY_COL)
329338
.toProtoEnumArray(PROTO_ENUM_ARRAY_VALUE, Genre.getDescriptor().getFullName())
339+
.set(PG_NUMERIC_COL_NULL)
340+
.to(Value.pgNumeric((String) null))
341+
.set(PG_NUMERIC_COL_NOT_NULL)
342+
.to(Value.pgNumeric("3.14"))
343+
.set(PG_NUMERIC_COL_NAN)
344+
.to(Value.pgNumeric("NaN"))
330345
.build()));
331346
}
332347

@@ -594,14 +609,106 @@ public void testGetLongIndexForFloat64() throws SQLException {
594609
assertTrue(subject.wasNull());
595610
}
596611

612+
@Test
613+
public void testGetIntegerTypesOnNumeric() throws SQLException {
614+
assertEquals((byte) 0, subject.getByte(NUMERIC_COLINDEX_NULL));
615+
assertTrue(subject.wasNull());
616+
assertEquals((byte) 3, subject.getByte(NUMERIC_COLINDEX_NOTNULL));
617+
assertFalse(subject.wasNull());
618+
619+
assertEquals((short) 0, subject.getShort(NUMERIC_COLINDEX_NULL));
620+
assertTrue(subject.wasNull());
621+
assertEquals((short) 3, subject.getShort(NUMERIC_COLINDEX_NOTNULL));
622+
assertFalse(subject.wasNull());
623+
624+
assertEquals(0, subject.getInt(NUMERIC_COLINDEX_NULL));
625+
assertTrue(subject.wasNull());
626+
assertEquals(3, subject.getInt(NUMERIC_COLINDEX_NOTNULL));
627+
assertFalse(subject.wasNull());
628+
629+
assertEquals(0L, subject.getLong(NUMERIC_COLINDEX_NULL));
630+
assertTrue(subject.wasNull());
631+
assertEquals(3L, subject.getLong(NUMERIC_COLINDEX_NOTNULL));
632+
assertFalse(subject.wasNull());
633+
}
634+
635+
@Test
636+
public void testGetIntegerTypesOnPgNumeric() throws SQLException {
637+
assertEquals((byte) 0, subject.getByte(PG_NUMERIC_COLINDEX_NULL));
638+
assertTrue(subject.wasNull());
639+
assertEquals((byte) 3, subject.getByte(PG_NUMERIC_COLINDEX_NOTNULL));
640+
assertFalse(subject.wasNull());
641+
642+
assertEquals((short) 0, subject.getShort(PG_NUMERIC_COLINDEX_NULL));
643+
assertTrue(subject.wasNull());
644+
assertEquals((short) 3, subject.getShort(PG_NUMERIC_COLINDEX_NOTNULL));
645+
assertFalse(subject.wasNull());
646+
647+
assertEquals(0, subject.getInt(PG_NUMERIC_COLINDEX_NULL));
648+
assertTrue(subject.wasNull());
649+
assertEquals(3, subject.getInt(PG_NUMERIC_COLINDEX_NOTNULL));
650+
assertFalse(subject.wasNull());
651+
652+
assertEquals(0L, subject.getLong(PG_NUMERIC_COLINDEX_NULL));
653+
assertTrue(subject.wasNull());
654+
assertEquals(3L, subject.getLong(PG_NUMERIC_COLINDEX_NOTNULL));
655+
assertFalse(subject.wasNull());
656+
}
657+
658+
@Test
659+
public void testGetIntegerTypesOnPgNumericNaN() throws SQLException {
660+
assertTrue(Double.isNaN(subject.getDouble(PG_NUMERIC_COLINDEX_NAN)));
661+
assertTrue(Float.isNaN(subject.getFloat(PG_NUMERIC_COLINDEX_NAN)));
662+
assertEquals("NaN", subject.getString(PG_NUMERIC_COLINDEX_NAN));
663+
try {
664+
subject.getByte(PG_NUMERIC_COLINDEX_NAN);
665+
fail("missing expected SQLException");
666+
} catch (SQLException e) {
667+
assertTrue(e instanceof JdbcSqlException);
668+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
669+
}
670+
671+
try {
672+
subject.getShort(PG_NUMERIC_COLINDEX_NAN);
673+
fail("missing expected SQLException");
674+
} catch (SQLException e) {
675+
assertTrue(e instanceof JdbcSqlException);
676+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
677+
}
678+
679+
try {
680+
subject.getInt(PG_NUMERIC_COLINDEX_NAN);
681+
fail("missing expected SQLException");
682+
} catch (SQLException e) {
683+
assertTrue(e instanceof JdbcSqlException);
684+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
685+
}
686+
687+
try {
688+
subject.getLong(PG_NUMERIC_COLINDEX_NAN);
689+
fail("missing expected SQLException");
690+
} catch (SQLException e) {
691+
assertTrue(e instanceof JdbcSqlException);
692+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
693+
}
694+
695+
try {
696+
subject.getBigDecimal(PG_NUMERIC_COLINDEX_NAN);
697+
fail("missing expected SQLException");
698+
} catch (SQLException e) {
699+
assertTrue(e instanceof JdbcSqlException);
700+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
701+
}
702+
}
703+
597704
@Test
598705
public void testGetLongIndexForString() {
599706
try {
600707
subject.getLong(STRING_COLINDEX_NOTNULL);
601708
fail("missing expected SQLException");
602709
} catch (SQLException e) {
603710
assertTrue(e instanceof JdbcSqlException);
604-
assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
711+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
605712
}
606713
}
607714

@@ -625,7 +732,7 @@ public void testGetLongIndexForTimestamp() {
625732
fail("missing expected SQLException");
626733
} catch (SQLException e) {
627734
assertTrue(e instanceof JdbcSqlException);
628-
assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
735+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
629736
}
630737
}
631738

@@ -669,7 +776,7 @@ public void testGetDoubleIndexFromTimestamp() {
669776
fail("missing expected SQLException");
670777
} catch (SQLException e) {
671778
assertTrue(e instanceof JdbcSqlException);
672-
assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
779+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
673780
}
674781
}
675782

@@ -1392,7 +1499,7 @@ public void testGetFloatIndexFromTimestamp() {
13921499
fail("missing expected SQLException");
13931500
} catch (SQLException e) {
13941501
assertTrue(e instanceof JdbcSqlException);
1395-
assertEquals(((JdbcSqlException) e).getCode(), Code.INVALID_ARGUMENT);
1502+
assertEquals(Code.INVALID_ARGUMENT, ((JdbcSqlException) e).getCode());
13961503
}
13971504
}
13981505

src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -452,22 +452,63 @@ public void testConvertNumeric() throws SQLException {
452452
assertThat(convert(d, Type.numeric(), String.class)).isEqualTo(String.valueOf(d));
453453
assertThat(convert(d, Type.numeric(), Boolean.class)).isEqualTo(!d.equals(BigDecimal.ZERO));
454454
if (d.compareTo(BigDecimal.valueOf(Long.MAX_VALUE)) > 0
455-
|| d.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) < 0
456-
|| d.scale() > 0) {
455+
|| d.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) < 0) {
457456
assertConvertThrows(d, Type.numeric(), Long.class, Code.OUT_OF_RANGE);
458457
} else {
459458
assertThat(convert(d, Type.numeric(), Long.class)).isEqualTo(d.longValue());
460459
}
461460
if (d.compareTo(BigDecimal.valueOf(Integer.MAX_VALUE)) > 0
462-
|| d.compareTo(BigDecimal.valueOf(Integer.MIN_VALUE)) < 0
463-
|| d.scale() > 0) {
461+
|| d.compareTo(BigDecimal.valueOf(Integer.MIN_VALUE)) < 0) {
464462
assertConvertThrows(d, Type.numeric(), Integer.class, Code.OUT_OF_RANGE);
465463
} else {
466464
assertThat(convert(d, Type.numeric(), Integer.class)).isEqualTo(d.intValue());
467465
}
468466
}
469467
}
470468

469+
@Test
470+
public void testConvertPgNumeric() throws SQLException {
471+
BigDecimal[] testValues =
472+
new BigDecimal[] {
473+
BigDecimal.ZERO,
474+
BigDecimal.ONE.negate(),
475+
BigDecimal.ONE,
476+
BigDecimal.valueOf(Double.MIN_VALUE),
477+
BigDecimal.valueOf(Double.MAX_VALUE),
478+
BigDecimal.valueOf(Float.MIN_VALUE),
479+
BigDecimal.valueOf(Float.MAX_VALUE),
480+
BigDecimal.valueOf(Float.MAX_VALUE + 1D)
481+
};
482+
for (BigDecimal d : testValues) {
483+
String strVal = String.valueOf(d);
484+
assertThat(convert(strVal, Type.pgNumeric(), BigDecimal.class)).isEqualTo(d);
485+
assertThat(convert(strVal, Type.pgNumeric(), Double.class)).isEqualTo(d.doubleValue());
486+
assertThat(convert(strVal, Type.pgNumeric(), Float.class)).isEqualTo(d.floatValue());
487+
assertThat(convert(strVal, Type.pgNumeric(), String.class)).isEqualTo(strVal);
488+
assertThat(convert(strVal, Type.pgNumeric(), Boolean.class))
489+
.isEqualTo(!d.equals(BigDecimal.ZERO));
490+
if (d.compareTo(BigDecimal.valueOf(Long.MAX_VALUE)) > 0
491+
|| d.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) < 0) {
492+
assertConvertThrows(strVal, Type.pgNumeric(), Long.class, Code.OUT_OF_RANGE);
493+
} else {
494+
assertThat(convert(strVal, Type.pgNumeric(), Long.class)).isEqualTo(d.longValue());
495+
}
496+
if (d.compareTo(BigDecimal.valueOf(Integer.MAX_VALUE)) > 0
497+
|| d.compareTo(BigDecimal.valueOf(Integer.MIN_VALUE)) < 0) {
498+
assertConvertThrows(strVal, Type.pgNumeric(), Integer.class, Code.OUT_OF_RANGE);
499+
} else {
500+
assertThat(convert(strVal, Type.pgNumeric(), Integer.class)).isEqualTo(d.intValue());
501+
}
502+
}
503+
504+
assertThat(convert("NaN", Type.pgNumeric(), Float.class)).isEqualTo(Float.NaN);
505+
assertThat(convert("NaN", Type.pgNumeric(), Double.class)).isEqualTo(Double.NaN);
506+
assertThat(convert("NaN", Type.pgNumeric(), String.class)).isEqualTo("NaN");
507+
assertConvertThrows("NaN", Type.pgNumeric(), Long.class, Code.INVALID_ARGUMENT);
508+
assertConvertThrows("NaN", Type.pgNumeric(), Integer.class, Code.INVALID_ARGUMENT);
509+
assertConvertThrows("NaN", Type.pgNumeric(), BigDecimal.class, Code.INVALID_ARGUMENT);
510+
}
511+
471512
private void assertConvertThrows(Object t, Type type, Class<?> destinationType, Code code)
472513
throws SQLException {
473514
try {

0 commit comments

Comments
 (0)