Skip to content

Commit

Permalink
Add support for BigQuery's NUMERIC type (#3110)
Browse files Browse the repository at this point in the history
  • Loading branch information
Elliott Brossard authored and pongad committed May 22, 2018
1 parent 9360def commit 754b1fc
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.io.BaseEncoding;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -48,7 +49,8 @@ public enum Attribute {
* A primitive field value. A {@code FieldValue} is primitive when the corresponding field has
* type {@link LegacySQLTypeName#BYTES}, {@link LegacySQLTypeName#BOOLEAN},
* {@link LegacySQLTypeName#STRING}, {@link LegacySQLTypeName#FLOAT},
* {@link LegacySQLTypeName#INTEGER}, {@link LegacySQLTypeName#TIMESTAMP} or the value is set to
* {@link LegacySQLTypeName#INTEGER}, {@link LegacySQLTypeName#NUMERIC},
* {@link LegacySQLTypeName#TIMESTAMP}, or the value is set to
* {@code null}.
*/
PRIMITIVE,
Expand Down Expand Up @@ -76,7 +78,8 @@ private FieldValue(Attribute attribute, Object value) {
* @return {@link Attribute#PRIMITIVE} if the field is a primitive type
* ({@link LegacySQLTypeName#BYTES}, {@link LegacySQLTypeName#BOOLEAN}, {@link LegacySQLTypeName#STRING},
* {@link LegacySQLTypeName#FLOAT}, {@link LegacySQLTypeName#INTEGER},
* {@link LegacySQLTypeName#TIMESTAMP}) or is {@code null}. Returns {@link Attribute#REPEATED} if
* {@link LegacySQLTypeName#NUMERIC}, {@link LegacySQLTypeName#TIMESTAMP})
* or is {@code null}. Returns {@link Attribute#REPEATED} if
* the corresponding field has ({@link Field.Mode#REPEATED}) mode. Returns
* {@link Attribute#RECORD} if the corresponding field is a
* {@link LegacySQLTypeName#RECORD} type.
Expand Down Expand Up @@ -107,7 +110,7 @@ public Object getValue() {
* corresponding field has primitive type ({@link LegacySQLTypeName#BYTES},
* {@link LegacySQLTypeName#BOOLEAN}, {@link LegacySQLTypeName#STRING},
* {@link LegacySQLTypeName#FLOAT}, {@link LegacySQLTypeName#INTEGER},
* {@link LegacySQLTypeName#TIMESTAMP}).
* {@link LegacySQLTypeName#NUMERIC} {@link LegacySQLTypeName#TIMESTAMP}).
*
* @throws ClassCastException if the field is not a primitive type
* @throws NullPointerException if {@link #isNull()} returns {@code true}
Expand Down Expand Up @@ -198,6 +201,21 @@ public long getTimestampValue() {
}


/**
* Returns this field's value as a {@link java.math.BigDecimal}. This method should only be used if the
* corresponding field has {@link LegacySQLTypeName#NUMERIC} type.
*
* @throws ClassCastException if the field is not a primitive type
* @throws NumberFormatException if the field's value could not be converted to
* {@link java.math.BigDecimal}
* @throws NullPointerException if {@link #isNull()} returns {@code true}
*/
@SuppressWarnings("unchecked")
public BigDecimal getNumericValue() {
return new BigDecimal(getStringValue());
}


/**
* Returns this field's value as a list of {@link FieldValue}. This method should only be used if
* the corresponding field has {@link Field.Mode#REPEATED} mode (i.e. {@link #getAttribute()} is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public LegacySQLTypeName apply(String constant) {
public static final LegacySQLTypeName INTEGER = type.createAndRegister("INTEGER").setStandardType(StandardSQLTypeName.INT64);
/** A 64-bit IEEE binary floating-point value. */
public static final LegacySQLTypeName FLOAT = type.createAndRegister("FLOAT").setStandardType(StandardSQLTypeName.FLOAT64);
/**
* A decimal value with 38 digits of precision and 9 digits of scale.
* Note, support for this type is limited in legacy SQL.
*/
public static final LegacySQLTypeName NUMERIC = type.createAndRegister("NUMERIC").setStandardType(StandardSQLTypeName.NUMERIC);
/** A Boolean value (true or false). */
public static final LegacySQLTypeName BOOLEAN = type.createAndRegister("BOOLEAN").setStandardType(StandardSQLTypeName.BOOL);
/** Represents an absolute point in time, with microsecond precision. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.common.collect.Lists;
import com.google.common.io.BaseEncoding;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
Expand All @@ -47,6 +48,7 @@
* <li>Long: StandardSQLTypeName.INT64
* <li>Double: StandardSQLTypeName.FLOAT64
* <li>Float: StandardSQLTypeName.FLOAT64
* <li>BigDecimal: StandardSQLTypeName.NUMERIC
* </ul>
*
* <p>No other types are supported through that entry point. The other types can be created by
Expand Down Expand Up @@ -164,6 +166,11 @@ public static QueryParameterValue float64(Float value) {
return of(value, StandardSQLTypeName.FLOAT64);
}

/** Creates a {@code QueryParameterValue} object with a type of NUMERIC. */
public static QueryParameterValue numeric(BigDecimal value) {
return of(value, StandardSQLTypeName.NUMERIC);
}

/** Creates a {@code QueryParameterValue} object with a type of STRING. */
public static QueryParameterValue string(String value) {
return of(value, StandardSQLTypeName.STRING);
Expand Down Expand Up @@ -245,6 +252,8 @@ private static <T> StandardSQLTypeName classToType(Class<T> type) {
return StandardSQLTypeName.FLOAT64;
} else if (Float.class.isAssignableFrom(type)) {
return StandardSQLTypeName.FLOAT64;
} else if (BigDecimal.class.isAssignableFrom(type)) {
return StandardSQLTypeName.NUMERIC;
}
throw new IllegalArgumentException("Unsupported object type for QueryParameter: " + type);
}
Expand All @@ -269,6 +278,11 @@ private static <T> String valueToStringOrNull(T value, StandardSQLTypeName type)
return value.toString();
}
break;
case NUMERIC:
if (value instanceof BigDecimal) {
return value.toString();
}
break;
case BYTES:
if (value instanceof byte[]) {
return BaseEncoding.base64().encode((byte[]) value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public enum StandardSQLTypeName {
INT64,
/** A 64-bit IEEE binary floating-point value. */
FLOAT64,
/** A decimal value with 38 digits of precision and 9 digits of scale. */
NUMERIC,
/** Variable-length character (Unicode) data. */
STRING,
/** Variable-length binary data. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public class FieldValueListTest {
"ninth",
LegacySQLTypeName.RECORD,
Field.of("first", LegacySQLTypeName.FLOAT),
Field.of("second", LegacySQLTypeName.TIMESTAMP)));
Field.of("second", LegacySQLTypeName.TIMESTAMP)),
Field.of("tenth", LegacySQLTypeName.NUMERIC));

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

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

private final List<?> fieldValuesPb =
ImmutableList.of(
Expand All @@ -86,7 +89,8 @@ public class FieldValueListTest {
bytesPb,
nullPb,
repeatedPb,
recordPb);
recordPb,
numericPb);

private final FieldValueList fieldValues =
FieldValueList.of(
Expand All @@ -99,7 +103,8 @@ public class FieldValueListTest {
bytesFv,
nullFv,
repeatedFv,
recordFv),
recordFv,
numericFv),
schema);

@Test
Expand All @@ -111,7 +116,7 @@ public void testFromPb() {

@Test
public void testGetByIndex() {
assertEquals(9, fieldValues.size());
assertEquals(10, fieldValues.size());
assertEquals(booleanFv, fieldValues.get(0));
assertEquals(integerFv, fieldValues.get(1));
assertEquals(floatFv, fieldValues.get(2));
Expand All @@ -127,11 +132,12 @@ public void testGetByIndex() {
assertEquals(2, fieldValues.get(8).getRecordValue().size());
assertEquals(floatFv, fieldValues.get(8).getRecordValue().get(0));
assertEquals(timestampFv, fieldValues.get(8).getRecordValue().get(1));
assertEquals(numericFv, fieldValues.get(9));
}

@Test
public void testGetByName() {
assertEquals(9, fieldValues.size());
assertEquals(10, fieldValues.size());
assertEquals(booleanFv, fieldValues.get("first"));
assertEquals(integerFv, fieldValues.get("second"));
assertEquals(floatFv, fieldValues.get("third"));
Expand All @@ -147,6 +153,7 @@ public void testGetByName() {
assertEquals(2, fieldValues.get("ninth").getRecordValue().size());
assertEquals(floatFv, fieldValues.get("ninth").getRecordValue().get("first"));
assertEquals(timestampFv, fieldValues.get("ninth").getRecordValue().get("second"));
assertEquals(numericFv, fieldValues.get("tenth"));
}

@Test
Expand All @@ -161,7 +168,8 @@ public void testNullSchema() {
bytesFv,
nullFv,
repeatedFv,
recordFv));
recordFv,
numericFv));

assertEquals(fieldValues, fieldValuesNoSchema);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import org.junit.Test;

import java.math.BigDecimal;
import java.util.Map;

public class FieldValueTest {
Expand All @@ -38,6 +39,8 @@ public class FieldValueTest {
private static final TableCell BOOLEAN_FIELD = new TableCell().setV("false");
private static final Map<String, String> INTEGER_FIELD = ImmutableMap.of("v", "1");
private static final Map<String, String> FLOAT_FIELD = ImmutableMap.of("v", "1.5");
private static final Map<String, String> NUMERIC_FIELD =
ImmutableMap.of("v", "123456789.123456789");
private static final Map<String, String> STRING_FIELD = ImmutableMap.of("v", "string");
private static final Map<String, String> TIMESTAMP_FIELD = ImmutableMap.of("v", "42");
private static final Map<String, String> BYTES_FIELD = ImmutableMap.of("v", BYTES_BASE64);
Expand All @@ -59,6 +62,9 @@ public void testFromPb() {
value = FieldValue.fromPb(FLOAT_FIELD);
assertEquals(FieldValue.Attribute.PRIMITIVE, value.getAttribute());
assertEquals(1.5, value.getDoubleValue(), 0);
value = FieldValue.fromPb(NUMERIC_FIELD);
assertEquals(FieldValue.Attribute.PRIMITIVE, value.getAttribute());
assertEquals(new BigDecimal("123456789.123456789"), value.getNumericValue());
value = FieldValue.fromPb(STRING_FIELD);
assertEquals(FieldValue.Attribute.PRIMITIVE, value.getAttribute());
assertEquals("string", value.getStringValue());
Expand Down Expand Up @@ -95,6 +101,11 @@ public void testEquals() {
assertEquals(floatValue, FieldValue.fromPb(FLOAT_FIELD));
assertEquals(floatValue.hashCode(), FieldValue.fromPb(FLOAT_FIELD).hashCode());

FieldValue numericValue =
FieldValue.of(FieldValue.Attribute.PRIMITIVE, "123456789.123456789");
assertEquals(numericValue, FieldValue.fromPb(NUMERIC_FIELD));
assertEquals(numericValue.hashCode(), FieldValue.fromPb(NUMERIC_FIELD).hashCode());

FieldValue stringValue = FieldValue.of(FieldValue.Attribute.PRIMITIVE, "string");
assertEquals(stringValue, FieldValue.fromPb(STRING_FIELD));
assertEquals(stringValue.hashCode(), FieldValue.fromPb(STRING_FIELD).hashCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;

import com.google.api.services.bigquery.model.QueryParameterType;
import java.math.BigDecimal;
import java.util.List;
import org.junit.Test;

Expand Down Expand Up @@ -69,6 +70,15 @@ public void testFloat64FromFloat() {
assertThat(value.getArrayValues()).isNull();
}

@Test
public void testNumeric() {
QueryParameterValue value = QueryParameterValue.numeric(new BigDecimal("123.456"));
assertThat(value.getValue()).isEqualTo("123.456");
assertThat(value.getType()).isEqualTo(StandardSQLTypeName.NUMERIC);
assertThat(value.getArrayType()).isNull();
assertThat(value.getArrayValues()).isNull();
}

@Test
public void testString() {
QueryParameterValue value = QueryParameterValue.string("foo");
Expand Down Expand Up @@ -132,6 +142,16 @@ public void testFloat64ArrayFromFloats() {
assertArrayDataEquals(new String[]{"2.6", "5.4"}, StandardSQLTypeName.FLOAT64, value.getArrayValues());
}

@Test
public void testNumericArray() {
QueryParameterValue value = QueryParameterValue.array(
new BigDecimal[] {new BigDecimal("3.14"), new BigDecimal("1.59")}, BigDecimal.class);
assertThat(value.getValue()).isNull();
assertThat(value.getType()).isEqualTo(StandardSQLTypeName.ARRAY);
assertThat(value.getArrayType()).isEqualTo(StandardSQLTypeName.NUMERIC);
assertArrayDataEquals(new String[]{"3.14", "1.59"}, StandardSQLTypeName.NUMERIC, value.getArrayValues());
}

@Test
public void testStringArray() {
QueryParameterValue value = QueryParameterValue.array(new String[] {"Ana", "Marv"}, String.class);
Expand Down
Loading

0 comments on commit 754b1fc

Please sign in to comment.