Skip to content

Commit 754b1fc

Browse files
Elliott Brossardpongad
authored andcommitted
Add support for BigQuery's NUMERIC type (googleapis#3110)
1 parent 9360def commit 754b1fc

File tree

8 files changed

+115
-13
lines changed

8 files changed

+115
-13
lines changed

google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/FieldValue.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.common.base.MoreObjects;
2525
import com.google.common.io.BaseEncoding;
2626
import java.io.Serializable;
27+
import java.math.BigDecimal;
2728
import java.util.List;
2829
import java.util.Map;
2930
import java.util.Objects;
@@ -48,7 +49,8 @@ public enum Attribute {
4849
* A primitive field value. A {@code FieldValue} is primitive when the corresponding field has
4950
* type {@link LegacySQLTypeName#BYTES}, {@link LegacySQLTypeName#BOOLEAN},
5051
* {@link LegacySQLTypeName#STRING}, {@link LegacySQLTypeName#FLOAT},
51-
* {@link LegacySQLTypeName#INTEGER}, {@link LegacySQLTypeName#TIMESTAMP} or the value is set to
52+
* {@link LegacySQLTypeName#INTEGER}, {@link LegacySQLTypeName#NUMERIC},
53+
* {@link LegacySQLTypeName#TIMESTAMP}, or the value is set to
5254
* {@code null}.
5355
*/
5456
PRIMITIVE,
@@ -76,7 +78,8 @@ private FieldValue(Attribute attribute, Object value) {
7678
* @return {@link Attribute#PRIMITIVE} if the field is a primitive type
7779
* ({@link LegacySQLTypeName#BYTES}, {@link LegacySQLTypeName#BOOLEAN}, {@link LegacySQLTypeName#STRING},
7880
* {@link LegacySQLTypeName#FLOAT}, {@link LegacySQLTypeName#INTEGER},
79-
* {@link LegacySQLTypeName#TIMESTAMP}) or is {@code null}. Returns {@link Attribute#REPEATED} if
81+
* {@link LegacySQLTypeName#NUMERIC}, {@link LegacySQLTypeName#TIMESTAMP})
82+
* or is {@code null}. Returns {@link Attribute#REPEATED} if
8083
* the corresponding field has ({@link Field.Mode#REPEATED}) mode. Returns
8184
* {@link Attribute#RECORD} if the corresponding field is a
8285
* {@link LegacySQLTypeName#RECORD} type.
@@ -107,7 +110,7 @@ public Object getValue() {
107110
* corresponding field has primitive type ({@link LegacySQLTypeName#BYTES},
108111
* {@link LegacySQLTypeName#BOOLEAN}, {@link LegacySQLTypeName#STRING},
109112
* {@link LegacySQLTypeName#FLOAT}, {@link LegacySQLTypeName#INTEGER},
110-
* {@link LegacySQLTypeName#TIMESTAMP}).
113+
* {@link LegacySQLTypeName#NUMERIC} {@link LegacySQLTypeName#TIMESTAMP}).
111114
*
112115
* @throws ClassCastException if the field is not a primitive type
113116
* @throws NullPointerException if {@link #isNull()} returns {@code true}
@@ -198,6 +201,21 @@ public long getTimestampValue() {
198201
}
199202

200203

204+
/**
205+
* Returns this field's value as a {@link java.math.BigDecimal}. This method should only be used if the
206+
* corresponding field has {@link LegacySQLTypeName#NUMERIC} type.
207+
*
208+
* @throws ClassCastException if the field is not a primitive type
209+
* @throws NumberFormatException if the field's value could not be converted to
210+
* {@link java.math.BigDecimal}
211+
* @throws NullPointerException if {@link #isNull()} returns {@code true}
212+
*/
213+
@SuppressWarnings("unchecked")
214+
public BigDecimal getNumericValue() {
215+
return new BigDecimal(getStringValue());
216+
}
217+
218+
201219
/**
202220
* Returns this field's value as a list of {@link FieldValue}. This method should only be used if
203221
* the corresponding field has {@link Field.Mode#REPEATED} mode (i.e. {@link #getAttribute()} is

google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/LegacySQLTypeName.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public LegacySQLTypeName apply(String constant) {
4949
public static final LegacySQLTypeName INTEGER = type.createAndRegister("INTEGER").setStandardType(StandardSQLTypeName.INT64);
5050
/** A 64-bit IEEE binary floating-point value. */
5151
public static final LegacySQLTypeName FLOAT = type.createAndRegister("FLOAT").setStandardType(StandardSQLTypeName.FLOAT64);
52+
/**
53+
* A decimal value with 38 digits of precision and 9 digits of scale.
54+
* Note, support for this type is limited in legacy SQL.
55+
*/
56+
public static final LegacySQLTypeName NUMERIC = type.createAndRegister("NUMERIC").setStandardType(StandardSQLTypeName.NUMERIC);
5257
/** A Boolean value (true or false). */
5358
public static final LegacySQLTypeName BOOLEAN = type.createAndRegister("BOOLEAN").setStandardType(StandardSQLTypeName.BOOL);
5459
/** Represents an absolute point in time, with microsecond precision. */

google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.common.collect.Lists;
2525
import com.google.common.io.BaseEncoding;
2626
import java.io.Serializable;
27+
import java.math.BigDecimal;
2728
import java.util.ArrayList;
2829
import java.util.List;
2930
import javax.annotation.Nullable;
@@ -47,6 +48,7 @@
4748
* <li>Long: StandardSQLTypeName.INT64
4849
* <li>Double: StandardSQLTypeName.FLOAT64
4950
* <li>Float: StandardSQLTypeName.FLOAT64
51+
* <li>BigDecimal: StandardSQLTypeName.NUMERIC
5052
* </ul>
5153
*
5254
* <p>No other types are supported through that entry point. The other types can be created by
@@ -164,6 +166,11 @@ public static QueryParameterValue float64(Float value) {
164166
return of(value, StandardSQLTypeName.FLOAT64);
165167
}
166168

169+
/** Creates a {@code QueryParameterValue} object with a type of NUMERIC. */
170+
public static QueryParameterValue numeric(BigDecimal value) {
171+
return of(value, StandardSQLTypeName.NUMERIC);
172+
}
173+
167174
/** Creates a {@code QueryParameterValue} object with a type of STRING. */
168175
public static QueryParameterValue string(String value) {
169176
return of(value, StandardSQLTypeName.STRING);
@@ -245,6 +252,8 @@ private static <T> StandardSQLTypeName classToType(Class<T> type) {
245252
return StandardSQLTypeName.FLOAT64;
246253
} else if (Float.class.isAssignableFrom(type)) {
247254
return StandardSQLTypeName.FLOAT64;
255+
} else if (BigDecimal.class.isAssignableFrom(type)) {
256+
return StandardSQLTypeName.NUMERIC;
248257
}
249258
throw new IllegalArgumentException("Unsupported object type for QueryParameter: " + type);
250259
}
@@ -269,6 +278,11 @@ private static <T> String valueToStringOrNull(T value, StandardSQLTypeName type)
269278
return value.toString();
270279
}
271280
break;
281+
case NUMERIC:
282+
if (value instanceof BigDecimal) {
283+
return value.toString();
284+
}
285+
break;
272286
case BYTES:
273287
if (value instanceof byte[]) {
274288
return BaseEncoding.base64().encode((byte[]) value);

google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLTypeName.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public enum StandardSQLTypeName {
2929
INT64,
3030
/** A 64-bit IEEE binary floating-point value. */
3131
FLOAT64,
32+
/** A decimal value with 38 digits of precision and 9 digits of scale. */
33+
NUMERIC,
3234
/** Variable-length character (Unicode) data. */
3335
STRING,
3436
/** Variable-length binary data. */

google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FieldValueListTest.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ public class FieldValueListTest {
4848
"ninth",
4949
LegacySQLTypeName.RECORD,
5050
Field.of("first", LegacySQLTypeName.FLOAT),
51-
Field.of("second", LegacySQLTypeName.TIMESTAMP)));
51+
Field.of("second", LegacySQLTypeName.TIMESTAMP)),
52+
Field.of("tenth", LegacySQLTypeName.NUMERIC));
5253

5354
private final Map<String, String> integerPb = ImmutableMap.of("v", "1");
5455
private final Map<String, String> floatPb = ImmutableMap.of("v", "1.5");
@@ -60,6 +61,7 @@ public class FieldValueListTest {
6061
ImmutableMap.<String, Object>of("v", ImmutableList.<Object>of(integerPb, integerPb));
6162
private final Map<String, Object> recordPb =
6263
ImmutableMap.<String, Object>of("f", ImmutableList.<Object>of(floatPb, timestampPb));
64+
private final Map<String, String> numericPb = ImmutableMap.of("v", "123456789.123456789");
6365

6466
private final FieldValue booleanFv = FieldValue.of(Attribute.PRIMITIVE, "false");
6567
private final FieldValue integerFv = FieldValue.of(Attribute.PRIMITIVE, "1");
@@ -75,6 +77,7 @@ public class FieldValueListTest {
7577
Attribute.RECORD,
7678
FieldValueList.of(
7779
ImmutableList.of(floatFv, timestampFv), schema.get("ninth").getSubFields()));
80+
private final FieldValue numericFv = FieldValue.of(Attribute.PRIMITIVE, "123456789.123456789");
7881

7982
private final List<?> fieldValuesPb =
8083
ImmutableList.of(
@@ -86,7 +89,8 @@ public class FieldValueListTest {
8689
bytesPb,
8790
nullPb,
8891
repeatedPb,
89-
recordPb);
92+
recordPb,
93+
numericPb);
9094

9195
private final FieldValueList fieldValues =
9296
FieldValueList.of(
@@ -99,7 +103,8 @@ public class FieldValueListTest {
99103
bytesFv,
100104
nullFv,
101105
repeatedFv,
102-
recordFv),
106+
recordFv,
107+
numericFv),
103108
schema);
104109

105110
@Test
@@ -111,7 +116,7 @@ public void testFromPb() {
111116

112117
@Test
113118
public void testGetByIndex() {
114-
assertEquals(9, fieldValues.size());
119+
assertEquals(10, fieldValues.size());
115120
assertEquals(booleanFv, fieldValues.get(0));
116121
assertEquals(integerFv, fieldValues.get(1));
117122
assertEquals(floatFv, fieldValues.get(2));
@@ -127,11 +132,12 @@ public void testGetByIndex() {
127132
assertEquals(2, fieldValues.get(8).getRecordValue().size());
128133
assertEquals(floatFv, fieldValues.get(8).getRecordValue().get(0));
129134
assertEquals(timestampFv, fieldValues.get(8).getRecordValue().get(1));
135+
assertEquals(numericFv, fieldValues.get(9));
130136
}
131137

132138
@Test
133139
public void testGetByName() {
134-
assertEquals(9, fieldValues.size());
140+
assertEquals(10, fieldValues.size());
135141
assertEquals(booleanFv, fieldValues.get("first"));
136142
assertEquals(integerFv, fieldValues.get("second"));
137143
assertEquals(floatFv, fieldValues.get("third"));
@@ -147,6 +153,7 @@ public void testGetByName() {
147153
assertEquals(2, fieldValues.get("ninth").getRecordValue().size());
148154
assertEquals(floatFv, fieldValues.get("ninth").getRecordValue().get("first"));
149155
assertEquals(timestampFv, fieldValues.get("ninth").getRecordValue().get("second"));
156+
assertEquals(numericFv, fieldValues.get("tenth"));
150157
}
151158

152159
@Test
@@ -161,7 +168,8 @@ public void testNullSchema() {
161168
bytesFv,
162169
nullFv,
163170
repeatedFv,
164-
recordFv));
171+
recordFv,
172+
numericFv));
165173

166174
assertEquals(fieldValues, fieldValuesNoSchema);
167175

google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FieldValueTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import org.junit.Test;
3131

32+
import java.math.BigDecimal;
3233
import java.util.Map;
3334

3435
public class FieldValueTest {
@@ -38,6 +39,8 @@ public class FieldValueTest {
3839
private static final TableCell BOOLEAN_FIELD = new TableCell().setV("false");
3940
private static final Map<String, String> INTEGER_FIELD = ImmutableMap.of("v", "1");
4041
private static final Map<String, String> FLOAT_FIELD = ImmutableMap.of("v", "1.5");
42+
private static final Map<String, String> NUMERIC_FIELD =
43+
ImmutableMap.of("v", "123456789.123456789");
4144
private static final Map<String, String> STRING_FIELD = ImmutableMap.of("v", "string");
4245
private static final Map<String, String> TIMESTAMP_FIELD = ImmutableMap.of("v", "42");
4346
private static final Map<String, String> BYTES_FIELD = ImmutableMap.of("v", BYTES_BASE64);
@@ -59,6 +62,9 @@ public void testFromPb() {
5962
value = FieldValue.fromPb(FLOAT_FIELD);
6063
assertEquals(FieldValue.Attribute.PRIMITIVE, value.getAttribute());
6164
assertEquals(1.5, value.getDoubleValue(), 0);
65+
value = FieldValue.fromPb(NUMERIC_FIELD);
66+
assertEquals(FieldValue.Attribute.PRIMITIVE, value.getAttribute());
67+
assertEquals(new BigDecimal("123456789.123456789"), value.getNumericValue());
6268
value = FieldValue.fromPb(STRING_FIELD);
6369
assertEquals(FieldValue.Attribute.PRIMITIVE, value.getAttribute());
6470
assertEquals("string", value.getStringValue());
@@ -95,6 +101,11 @@ public void testEquals() {
95101
assertEquals(floatValue, FieldValue.fromPb(FLOAT_FIELD));
96102
assertEquals(floatValue.hashCode(), FieldValue.fromPb(FLOAT_FIELD).hashCode());
97103

104+
FieldValue numericValue =
105+
FieldValue.of(FieldValue.Attribute.PRIMITIVE, "123456789.123456789");
106+
assertEquals(numericValue, FieldValue.fromPb(NUMERIC_FIELD));
107+
assertEquals(numericValue.hashCode(), FieldValue.fromPb(NUMERIC_FIELD).hashCode());
108+
98109
FieldValue stringValue = FieldValue.of(FieldValue.Attribute.PRIMITIVE, "string");
99110
assertEquals(stringValue, FieldValue.fromPb(STRING_FIELD));
100111
assertEquals(stringValue.hashCode(), FieldValue.fromPb(STRING_FIELD).hashCode());

google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.google.common.truth.Truth.assertThat;
2020

2121
import com.google.api.services.bigquery.model.QueryParameterType;
22+
import java.math.BigDecimal;
2223
import java.util.List;
2324
import org.junit.Test;
2425

@@ -69,6 +70,15 @@ public void testFloat64FromFloat() {
6970
assertThat(value.getArrayValues()).isNull();
7071
}
7172

73+
@Test
74+
public void testNumeric() {
75+
QueryParameterValue value = QueryParameterValue.numeric(new BigDecimal("123.456"));
76+
assertThat(value.getValue()).isEqualTo("123.456");
77+
assertThat(value.getType()).isEqualTo(StandardSQLTypeName.NUMERIC);
78+
assertThat(value.getArrayType()).isNull();
79+
assertThat(value.getArrayValues()).isNull();
80+
}
81+
7282
@Test
7383
public void testString() {
7484
QueryParameterValue value = QueryParameterValue.string("foo");
@@ -132,6 +142,16 @@ public void testFloat64ArrayFromFloats() {
132142
assertArrayDataEquals(new String[]{"2.6", "5.4"}, StandardSQLTypeName.FLOAT64, value.getArrayValues());
133143
}
134144

145+
@Test
146+
public void testNumericArray() {
147+
QueryParameterValue value = QueryParameterValue.array(
148+
new BigDecimal[] {new BigDecimal("3.14"), new BigDecimal("1.59")}, BigDecimal.class);
149+
assertThat(value.getValue()).isNull();
150+
assertThat(value.getType()).isEqualTo(StandardSQLTypeName.ARRAY);
151+
assertThat(value.getArrayType()).isEqualTo(StandardSQLTypeName.NUMERIC);
152+
assertArrayDataEquals(new String[]{"3.14", "1.59"}, StandardSQLTypeName.NUMERIC, value.getArrayValues());
153+
}
154+
135155
@Test
136156
public void testStringArray() {
137157
QueryParameterValue value = QueryParameterValue.array(new String[] {"Ana", "Marv"}, String.class);

0 commit comments

Comments
 (0)