diff --git a/google-cloud-bigquery/pom.xml b/google-cloud-bigquery/pom.xml index 8c65d2160b70..d79e81df190c 100644 --- a/google-cloud-bigquery/pom.xml +++ b/google-cloud-bigquery/pom.xml @@ -31,7 +31,7 @@ com.google.apis google-api-services-bigquery - v2-rev303-1.22.0 + v2-rev330-1.22.0 compile diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java index 1549f7bc9be4..0d0b65156eef 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Field.java @@ -72,19 +72,15 @@ public static class Type implements Serializable { private static final long serialVersionUID = 2841484762609576959L; - public enum Value { - BYTES, STRING, INTEGER, FLOAT, BOOLEAN, TIMESTAMP, RECORD - } - - private final Value value; + private final LegacySQLTypeName value; private final List fields; - private Type(Value value) { + private Type(LegacySQLTypeName value) { this.value = checkNotNull(value); this.fields = null; } - private Type(Value value, List fields) { + private Type(LegacySQLTypeName value, List fields) { checkArgument(fields.size() > 0, "Record must have at least one field"); this.value = value; this.fields = fields; @@ -97,7 +93,7 @@ private Type(Value value, List fields) { * Data Types */ @Deprecated - public Value value() { + public LegacySQLTypeName value() { return getValue(); } @@ -107,13 +103,13 @@ public Value value() { * @see * Data Types */ - public Value getValue() { + public LegacySQLTypeName getValue() { return value; } /** - * Returns the list of sub-fields if {@link #value()} is set to {@link Value#RECORD}. Returns - * {@code null} otherwise. + * Returns the list of sub-fields if {@link #value()} is set to {@link + * LegacySQLTypeName#RECORD}. Returns {@code null} otherwise. */ @Deprecated public List fields() { @@ -121,67 +117,67 @@ public List fields() { } /** - * Returns the list of sub-fields if {@link #value()} is set to {@link Value#RECORD}. Returns - * {@code null} otherwise. + * Returns the list of sub-fields if {@link #value()} is set to {@link + * LegacySQLTypeName#RECORD}. Returns {@code null} otherwise. */ public List getFields() { return fields; } /** - * Returns a {@link Value#BYTES} field value. + * Returns a {@link LegacySQLTypeName#BYTES} field value. */ public static Type bytes() { - return new Type(Value.BYTES); + return new Type(LegacySQLTypeName.BYTES); } /** - * Returns a {@link Value#STRING} field value. + * Returns a {@link LegacySQLTypeName#STRING} field value. */ public static Type string() { - return new Type(Value.STRING); + return new Type(LegacySQLTypeName.STRING); } /** - * Returns an {@link Value#INTEGER} field value. + * Returns an {@link LegacySQLTypeName#INTEGER} field value. */ public static Type integer() { - return new Type(Value.INTEGER); + return new Type(LegacySQLTypeName.INTEGER); } /** - * Returns a {@link Value#FLOAT} field value. + * Returns a {@link LegacySQLTypeName#FLOAT} field value. */ public static Type floatingPoint() { - return new Type(Value.FLOAT); + return new Type(LegacySQLTypeName.FLOAT); } /** - * Returns a {@link Value#BOOLEAN} field value. + * Returns a {@link LegacySQLTypeName#BOOLEAN} field value. */ public static Type bool() { - return new Type(Value.BOOLEAN); + return new Type(LegacySQLTypeName.BOOLEAN); } /** - * Returns a {@link Value#TIMESTAMP} field value. + * Returns a {@link LegacySQLTypeName#TIMESTAMP} field value. */ public static Type timestamp() { - return new Type(Value.TIMESTAMP); + return new Type(LegacySQLTypeName.TIMESTAMP); } /** - * Returns a {@link Value#RECORD} field value with associated list of sub-fields. + * Returns a {@link LegacySQLTypeName#RECORD} field value with associated list of sub-fields. */ public static Type record(Field... fields) { - return new Type(Value.RECORD, ImmutableList.copyOf(fields)); + return new Type(LegacySQLTypeName.RECORD, ImmutableList.copyOf(fields)); } /** - * Returns a {@link Value#RECORD} field value with associated list of sub-fields. + * Returns a {@link LegacySQLTypeName#RECORD} field value with associated list of sub-fields. */ public static Type record(List fields) { - return new Type(Value.RECORD, ImmutableList.copyOf(checkNotNull(fields))); + return new Type(LegacySQLTypeName.RECORD, ImmutableList.copyOf(checkNotNull(fields))); } @Override @@ -389,8 +385,8 @@ public String getDescription() { } /** - * Returns the list of sub-fields if {@link #type()} is a {@link Type.Value#RECORD}. Returns - * {@code null} otherwise. + * Returns the list of sub-fields if {@link #type()} is a {@link LegacySQLTypeName#RECORD}. + * Returns {@code null} otherwise. */ @Deprecated public List fields() { @@ -398,8 +394,8 @@ public List fields() { } /** - * Returns the list of sub-fields if {@link #type()} is a {@link Type.Value#RECORD}. Returns - * {@code null} otherwise. + * Returns the list of sub-fields if {@link #type()} is a {@link LegacySQLTypeName#RECORD}. + * Returns {@code null} otherwise. */ public List getFields() { return type.getFields(); @@ -474,7 +470,7 @@ public static Builder newBuilder(String name, Type type) { static Field fromPb(TableFieldSchema fieldSchemaPb) { Builder fieldBuilder = new Builder(); fieldBuilder.setName(fieldSchemaPb.getName()); - Type.Value enumValue = Type.Value.valueOf(fieldSchemaPb.getType()); + LegacySQLTypeName enumValue = LegacySQLTypeName.valueOf(fieldSchemaPb.getType()); if (fieldSchemaPb.getMode() != null) { fieldBuilder.setMode(Mode.valueOf(fieldSchemaPb.getMode())); } diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/LegacySQLTypeName.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/LegacySQLTypeName.java new file mode 100644 index 000000000000..a5da8f66e3d3 --- /dev/null +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/LegacySQLTypeName.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery; + +/** + * A type used in legacy SQL contexts. NOTE: some contexts use a mix of types; for example, + * for queries that use standard SQL, the return types are the legacy SQL types. + * + * @see https://cloud.google.com/bigquery/data-types + */ +public enum LegacySQLTypeName { + /** Variable-length binary data. */ + BYTES(StandardSQLTypeName.BYTES), + /** Variable-length character (Unicode) data. */ + STRING(StandardSQLTypeName.STRING), + /** A 64-bit signed integer value. */ + INTEGER(StandardSQLTypeName.INT64), + /** A 64-bit IEEE binary floating-point value. */ + FLOAT(StandardSQLTypeName.FLOAT64), + /** A Boolean value (true or false). */ + BOOLEAN(StandardSQLTypeName.BOOL), + /** Represents an absolute point in time, with microsecond precision. */ + TIMESTAMP(StandardSQLTypeName.TIMESTAMP), + /** Represents a logical calendar date. Note, support for this type is limited in legacy SQL. */ + DATE(StandardSQLTypeName.DATE), + /** + * Represents a time, independent of a specific date, to microsecond precision. Note, support for + * this type is limited in legacy SQL. + */ + TIME(StandardSQLTypeName.TIME), + /** + * Represents a year, month, day, hour, minute, second, and subsecond (microsecond precision). + * Note, support for this type is limited in legacy SQL. + */ + DATETIME(StandardSQLTypeName.DATETIME), + /** A record type with a nested schema. */ + RECORD(StandardSQLTypeName.STRUCT); + + private StandardSQLTypeName equivalent; + + LegacySQLTypeName(StandardSQLTypeName equivalent) { + this.equivalent = equivalent; + } + + /** + * Provides the standard SQL type name equivalent to this type name. + */ + public StandardSQLTypeName getStandardType() { + return equivalent; + } +} diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java new file mode 100644 index 000000000000..a19b07a0be0d --- /dev/null +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryParameterValue.java @@ -0,0 +1,439 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.services.bigquery.model.QueryParameterType; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.io.BaseEncoding; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * A value for a QueryParameter along with its type. + * + *

A static factory method is provided for each of the possible types (e.g. {@link #int64(Long)} + * for StandardSQLTypeName.INT64). Alternatively, an instance can be constructed by calling {@link + * #of(Object, Class)} with the value and a Class object, which will use these mappings: + * + *

+ * + *

    + *
  • Boolean: StandardSQLTypeName.BOOL + *
  • String: StandardSQLTypeName.STRING + *
  • Integer: StandardSQLTypeName.INT64 + *
  • Long: StandardSQLTypeName.INT64 + *
  • Double: StandardSQLTypeName.FLOAT64 + *
  • Float: StandardSQLTypeName.FLOAT64 + *
+ * + *

No other types are supported through that entry point. The other types can be created by + * calling {@link #of(Object, StandardSQLTypeName)} with the value and a particular + * StandardSQLTypeName enum value. + * + *

Struct parameters are currently not supported. + */ +public class QueryParameterValue implements Serializable { + + private static final DateTimeFormatter timestampFormatter = + DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSSZZ").withZone(DateTimeZone.UTC); + private static final DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd"); + private static final DateTimeFormatter timeFormatter = + DateTimeFormat.forPattern("HH:mm:ss.SSSSSS"); + private static final DateTimeFormatter datetimeFormatter = + DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"); + + static final Function< + QueryParameterValue, com.google.api.services.bigquery.model.QueryParameterValue> + TO_VALUE_PB_FUNCTION = + new Function< + QueryParameterValue, com.google.api.services.bigquery.model.QueryParameterValue>() { + @Override + public com.google.api.services.bigquery.model.QueryParameterValue apply( + QueryParameterValue value) { + return value.toValuePb(); + } + }; + private static final long serialVersionUID = -5620695863123562896L; + + private final String value; + private final StandardSQLTypeName type; + private final List arrayValues; + private final StandardSQLTypeName arrayType; + + public static final class Builder { + + private String value; + private List arrayValues; + private StandardSQLTypeName type; + private StandardSQLTypeName arrayType; + + private Builder() {} + + private Builder(QueryParameterValue queryParameterValue) { + this.value = queryParameterValue.value; + this.arrayValues = + queryParameterValue.arrayValues == null + ? null + : Lists.newArrayList(queryParameterValue.arrayValues); + this.type = queryParameterValue.type; + this.arrayType = queryParameterValue.arrayType; + } + + /** Sets the value to the given scalar value. */ + public Builder setValue(String value) { + this.value = value; + return this; + } + + /** Sets array values. The type must set to ARRAY. */ + public Builder setArrayValues(List arrayValues) { + this.arrayValues = arrayValues == null ? null : Lists.newArrayList(arrayValues); + return this; + } + + /** Sets the parameter data type. */ + public Builder setType(StandardSQLTypeName type) { + this.type = checkNotNull(type); + return this; + } + + /** Sets the data type of the array elements. The type must set to ARRAY. */ + public Builder setArrayType(StandardSQLTypeName arrayType) { + this.arrayType = arrayType; + return this; + } + + /** Creates a {@code QueryParameterValue} object. */ + public QueryParameterValue build() { + return new QueryParameterValue(this); + } + } + + private QueryParameterValue(Builder builder) { + if (builder.arrayValues != null) { + checkArgument( + StandardSQLTypeName.ARRAY.equals(builder.type), + "type must be ARRAY if arrayValues is set"); + checkArgument(builder.arrayType != null, "arrayType must be set if arrayValues is set"); + checkArgument(builder.value == null, "value can't be set if arrayValues is set"); + this.arrayValues = ImmutableList.copyOf(builder.arrayValues); + } else { + checkArgument( + !StandardSQLTypeName.ARRAY.equals(builder.type), + "type can't be ARRAY if arrayValues is not set"); + checkArgument(builder.arrayType == null, "arrayType can't be set if arrayValues is not set"); + checkArgument(builder.value != null, "value must be set if arrayValues is not set"); + this.arrayValues = null; + } + this.type = checkNotNull(builder.type); + this.value = builder.value; + this.arrayType = builder.arrayType; + } + + /** Returns the value of this parameter. */ + public String getValue() { + return value; + } + + /** Returns the array values of this parameter. */ + public List getArrayValues() { + return arrayValues; + } + + /** Returns the data type of this parameter. */ + public StandardSQLTypeName getType() { + return type; + } + + /** Returns the data type of the array elements. */ + public StandardSQLTypeName getArrayType() { + return arrayType; + } + + /** Creates a {@code QueryParameterValue} object with the given value and type. */ + public static QueryParameterValue of(T value, Class type) { + return of(value, classToType(type)); + } + + /** Creates a {@code QueryParameterValue} object with the given value and type. */ + public static QueryParameterValue of(T value, StandardSQLTypeName type) { + return QueryParameterValue.newBuilder() + .setValue(valueToStringOrNull(value, type)) + .setType(type) + .build(); + } + + /** Creates a {@code QueryParameterValue} object with a type of BOOL. */ + public static QueryParameterValue bool(Boolean value) { + return of(value, StandardSQLTypeName.BOOL); + } + + /** Creates a {@code QueryParameterValue} object with a type of INT64. */ + public static QueryParameterValue int64(Long value) { + return of(value, StandardSQLTypeName.INT64); + } + + /** Creates a {@code QueryParameterValue} object with a type of INT64. */ + public static QueryParameterValue int64(Integer value) { + return of(value, StandardSQLTypeName.INT64); + } + + /** Creates a {@code QueryParameterValue} object with a type of FLOAT64. */ + public static QueryParameterValue float64(Double value) { + return of(value, StandardSQLTypeName.FLOAT64); + } + + /** Creates a {@code QueryParameterValue} object with a type of FLOAT64. */ + public static QueryParameterValue float64(Float value) { + return of(value, StandardSQLTypeName.FLOAT64); + } + + /** Creates a {@code QueryParameterValue} object with a type of STRING. */ + public static QueryParameterValue string(String value) { + return of(value, StandardSQLTypeName.STRING); + } + + /** Creates a {@code QueryParameterValue} object with a type of BYTES. */ + public static QueryParameterValue bytes(byte[] value) { + return of(value, StandardSQLTypeName.BYTES); + } + + /** Creates a {@code QueryParameterValue} object with a type of TIMESTAMP. */ + public static QueryParameterValue timestamp(Long value) { + return of(value, StandardSQLTypeName.TIMESTAMP); + } + + /** + * Creates a {@code QueryParameterValue} object with a type of TIMESTAMP. Must be in the format + * "yyyy-MM-dd HH:mm:ss.SSSSSSZZ", e.g. "2014-08-19 12:41:35.220000+00:00". + */ + public static QueryParameterValue timestamp(String value) { + return of(value, StandardSQLTypeName.TIMESTAMP); + } + + /** + * Creates a {@code QueryParameterValue} object with a type of DATE. Must be in the format + * "yyyy-MM-dd", e.g. "2014-08-19". + */ + public static QueryParameterValue date(String value) { + return of(value, StandardSQLTypeName.DATE); + } + + /** + * Creates a {@code QueryParameterValue} object with a type of TIME. Must be in the format + * "HH:mm:ss.SSSSSS", e.g. "12:41:35.220000". + */ + public static QueryParameterValue time(String value) { + return of(value, StandardSQLTypeName.TIME); + } + + /** Creates a {@code QueryParameterValue} object with a type of DATETIME. + * Must be in the format "yyyy-MM-dd HH:mm:ss.SSSSSS", e.g. ""2014-08-19 12:41:35.220000". */ + public static QueryParameterValue dateTime(String value) { + return of(value, StandardSQLTypeName.DATETIME); + } + + /** + * Creates a {@code QueryParameterValue} object with a type of ARRAY, and an array element type + * based on the given class. + */ + public static QueryParameterValue array(T[] array, Class clazz) { + return array(array, classToType(clazz)); + } + + /** + * Creates a {@code QueryParameterValue} object with a type of ARRAY the given array element type. + */ + public static QueryParameterValue array(T[] array, StandardSQLTypeName type) { + List listValues = new ArrayList<>(); + for (T obj : array) { + listValues.add(QueryParameterValue.of(obj, type)); + } + return QueryParameterValue.newBuilder() + .setArrayValues(listValues) + .setType(StandardSQLTypeName.ARRAY) + .setArrayType(type) + .build(); + } + + private static StandardSQLTypeName classToType(Class type) { + if (Boolean.class.isAssignableFrom(type)) { + return StandardSQLTypeName.BOOL; + } else if (String.class.isAssignableFrom(type)) { + return StandardSQLTypeName.STRING; + } else if (Integer.class.isAssignableFrom(type)) { + return StandardSQLTypeName.INT64; + } else if (Long.class.isAssignableFrom(type)) { + return StandardSQLTypeName.INT64; + } else if (Double.class.isAssignableFrom(type)) { + return StandardSQLTypeName.FLOAT64; + } else if (Float.class.isAssignableFrom(type)) { + return StandardSQLTypeName.FLOAT64; + } + throw new IllegalArgumentException("Unsupported object type for QueryParameter: " + type); + } + + private static String valueToStringOrNull(T value, StandardSQLTypeName type) { + if (value == null) { + return null; + } + switch (type) { + case BOOL: + if (value instanceof Boolean) { + return value.toString(); + } + break; + case INT64: + if (value instanceof Integer || value instanceof Long) { + return value.toString(); + } + break; + case FLOAT64: + if (value instanceof Double || value instanceof Float) { + return value.toString(); + } + break; + case BYTES: + if (value instanceof byte[]) { + return BaseEncoding.base64().encode((byte[]) value); + } + break; + case STRING: + return value.toString(); + case STRUCT: + throw new IllegalArgumentException("Cannot convert STRUCT to String value"); + case ARRAY: + throw new IllegalArgumentException("Cannot convert ARRAY to String value"); + case TIMESTAMP: + if (value instanceof Long) { + return timestampFormatter.print(((Long) value) / 1000); + } else if (value instanceof String) { + // verify that the String is in the right format + timestampFormatter.parseMillis((String) value); + return (String) value; + } + break; + case DATE: + if (value instanceof String) { + // verify that the String is in the right format + dateFormatter.parseMillis((String) value); + return (String) value; + } + break; + case TIME: + if (value instanceof String) { + // verify that the String is in the right format + timeFormatter.parseMillis((String) value); + return (String) value; + } + break; + case DATETIME: + if (value instanceof String) { + // verify that the String is in the right format + datetimeFormatter.parseMillis((String) value); + return (String) value; + } + break; + default: + throw new UnsupportedOperationException("Implementation error - Unsupported type: " + type); + } + throw new IllegalArgumentException( + "Type " + type + " incompatible with " + value.getClass().getCanonicalName()); + } + + /** Returns a builder for the {@code QueryParameterValue} object. */ + public Builder toBuilder() { + return new Builder(this); + } + + /** Returns a builder for a QueryParameterValue object with given value. */ + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("value", value) + .add("arrayValues", arrayValues) + .add("type", type) + .add("arrayType", arrayType) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(value, arrayValues); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof QueryParameterValue + && Objects.equals(toValuePb(), ((QueryParameterValue) obj).toValuePb()) + && Objects.equals(toTypePb(), ((QueryParameterValue) obj).toTypePb()); + } + + com.google.api.services.bigquery.model.QueryParameterValue toValuePb() { + com.google.api.services.bigquery.model.QueryParameterValue valuePb = + new com.google.api.services.bigquery.model.QueryParameterValue(); + valuePb.setValue(value); + if (arrayValues != null && !arrayValues.isEmpty()) { + valuePb.setArrayValues( + Lists.transform(arrayValues, QueryParameterValue.TO_VALUE_PB_FUNCTION)); + } + return valuePb; + } + + QueryParameterType toTypePb() { + QueryParameterType typePb = new QueryParameterType(); + typePb.setType(type.toString()); + if (arrayType != null) { + QueryParameterType arrayTypePb = new QueryParameterType(); + arrayTypePb.setType(arrayType.toString()); + typePb.setArrayType(arrayTypePb); + } + return typePb; + } + + static QueryParameterValue fromPb( + com.google.api.services.bigquery.model.QueryParameterValue valuePb, + QueryParameterType typePb) { + Builder valueBuilder = new Builder(); + valueBuilder.setValue(valuePb.getValue()); + if (valuePb.getArrayValues() != null && valuePb.getArrayValues().size() > 0) { + List arrayValues = new ArrayList<>(); + for (com.google.api.services.bigquery.model.QueryParameterValue elementValuePb : + valuePb.getArrayValues()) { + arrayValues.add(fromPb(elementValuePb, typePb.getArrayType())); + } + } + valueBuilder.setType(StandardSQLTypeName.valueOf(typePb.getType())); + + return valueBuilder.build(); + } +} diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryRequest.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryRequest.java index 9e3f57ba5ba8..d0197e484532 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryRequest.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/QueryRequest.java @@ -16,11 +16,20 @@ package com.google.cloud.bigquery; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.api.services.bigquery.model.QueryParameter; +import com.google.common.base.Function; import com.google.common.base.MoreObjects; - +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import java.io.Serializable; +import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -66,6 +75,8 @@ public final class QueryRequest implements Serializable { private static final long serialVersionUID = -8727328332415880852L; private final String query; + private final ImmutableList positionalParameters; + private final ImmutableMap namedParameters; private final Long pageSize; private final DatasetId defaultDataset; private final Long maxWaitTime; @@ -76,6 +87,8 @@ public final class QueryRequest implements Serializable { public static final class Builder { private String query; + private List positionalParameters = Lists.newArrayList(); + private Map namedParameters = Maps.newHashMap(); private Long pageSize; private DatasetId defaultDataset; private Long maxWaitTime; @@ -101,6 +114,88 @@ public Builder setQuery(String query) { return this; } + /** + * Adds a positional query parameter to the list of query parameters. See + * {@link #setPositionalParameters(Iterable)} for more details on the input requirements. + * + *

A positional parameter cannot be added after named parameters have been added. + */ + public Builder addPositionalParameter(QueryParameterValue value) { + checkNotNull(value); + if (!namedParameters.isEmpty()) { + throw new IllegalStateException( + "Positional parameters can't be combined with named parameters"); + } + positionalParameters.add(value); + return this; + } + + /** + * Sets the query parameters to a list of positional query parameters to use in the query. + * + *

The set of query parameters must either be all positional or all named parameters. + * Positional parameters are denoted in the query with a question mark (?). + * + *

Additionally, useLegacySql must be set to false; query parameters cannot be used with + * legacy SQL. + * + *

The values parameter can be set to null to clear out the positional + * parameters so that named parameters can be used instead. + */ + public Builder setPositionalParameters(Iterable values) { + if (values == null || Iterables.isEmpty(values)) { + positionalParameters = Lists.newArrayList(); + } else { + if (!this.namedParameters.isEmpty()) { + throw new IllegalStateException( + "Positional parameters can't be combined with named parameters"); + } + this.positionalParameters = Lists.newArrayList(values); + } + return this; + } + + /** + * Adds a named query parameter to the set of query parameters. See + * {@link #setNamedParameters(Map)} for more details on the input requirements. + * + *

A named parameter cannot be added after positional parameters have been added. + */ + public Builder addNamedParameter(String name, QueryParameterValue value) { + checkNotNull(value); + if (!this.positionalParameters.isEmpty()) { + throw new IllegalStateException( + "Named parameters can't be combined with positional parameters"); + } + namedParameters.put(name, value); + return this; + } + + /** + * Sets the query parameters to a set of named query parameters to use in the query. + * + *

The set of query parameters must either be all positional or all named parameters. Named + * parameters are denoted using an @ prefix, e.g. @myParam for a parameter named "myParam". + * + *

Additionally, useLegacySql must be set to false; query parameters cannot be used with + * legacy SQL. + * + *

The values parameter can be set to null to clear out the named parameters so that + * positional parameters can be used instead. + */ + public Builder setNamedParameters(Map values) { + if (values == null || values.isEmpty()) { + namedParameters = Maps.newHashMap(); + } else { + if (!this.positionalParameters.isEmpty()) { + throw new IllegalStateException( + "Named parameters can't be combined with positional parameters"); + } + this.namedParameters = Maps.newHashMap(values); + } + return this; + } + /** * Sets the maximum number of rows of data to return per page of results. Setting this flag to a * small value such as 1000 and then paging through results might improve reliability when the @@ -251,6 +346,16 @@ public QueryRequest build() { private QueryRequest(Builder builder) { query = builder.query; + checkNotNull(builder.positionalParameters); + checkNotNull(builder.namedParameters); + if (!builder.positionalParameters.isEmpty()) { + checkArgument(builder.namedParameters.isEmpty()); + } + if (!builder.namedParameters.isEmpty()) { + checkArgument(builder.positionalParameters.isEmpty()); + } + positionalParameters = ImmutableList.copyOf(builder.positionalParameters); + namedParameters = ImmutableMap.copyOf(builder.namedParameters); pageSize = builder.pageSize; defaultDataset = builder.defaultDataset; maxWaitTime = builder.maxWaitTime; @@ -274,6 +379,20 @@ public String getQuery() { return query; } + /** + * Returns the positional query parameters to use for the query. + */ + public List getPositionalParameters() { + return positionalParameters; + } + + /** + * Returns the named query parameters to use for the query. + */ + public Map getNamedParameters() { + return namedParameters; + } + /** * Returns the maximum number of rows of data to return per page of results. */ @@ -367,6 +486,8 @@ public Boolean useLegacySql() { public Builder toBuilder() { return new Builder() .setQuery(query) + .setPositionalParameters(positionalParameters) + .setNamedParameters(namedParameters) .setPageSize(pageSize) .setDefaultDataset(defaultDataset) .setMaxWaitTime(maxWaitTime) @@ -379,6 +500,8 @@ public Builder toBuilder() { public String toString() { return MoreObjects.toStringHelper(this) .add("query", query) + .add("positionalParameters", positionalParameters) + .add("namedParameters", namedParameters) .add("pageSize", pageSize) .add("defaultDataset", defaultDataset) .add("maxWaitTime", maxWaitTime) @@ -390,7 +513,15 @@ public String toString() { @Override public int hashCode() { - return Objects.hash(query, pageSize, defaultDataset, maxWaitTime, dryRun, useQueryCache, + return Objects.hash( + query, + positionalParameters, + namedParameters, + pageSize, + defaultDataset, + maxWaitTime, + dryRun, + useQueryCache, useLegacySql); } @@ -412,6 +543,15 @@ QueryRequest setProjectId(String projectId) { com.google.api.services.bigquery.model.QueryRequest toPb() { com.google.api.services.bigquery.model.QueryRequest queryRequestPb = new com.google.api.services.bigquery.model.QueryRequest().setQuery(query); + if (!positionalParameters.isEmpty()) { + List queryParametersPb + = Lists.transform(positionalParameters, POSITIONAL_PARAMETER_TO_PB_FUNCTION); + queryRequestPb.setQueryParameters(queryParametersPb); + } else if (!namedParameters.isEmpty()) { + List queryParametersPb + = Lists.transform(namedParameters.entrySet().asList(), NAMED_PARAMETER_TO_PB_FUNCTION); + queryRequestPb.setQueryParameters(queryParametersPb); + } if (pageSize != null) { queryRequestPb.setMaxResults(pageSize); } @@ -457,6 +597,21 @@ public static QueryRequest of(String query) { static QueryRequest fromPb(com.google.api.services.bigquery.model.QueryRequest queryRequestPb) { Builder builder = newBuilder(queryRequestPb.getQuery()); + if (queryRequestPb.getQueryParameters() != null && !queryRequestPb.getQueryParameters().isEmpty()) { + if (queryRequestPb.getQueryParameters().get(0).getName() == null) { + builder.setPositionalParameters( + Lists.transform(queryRequestPb.getQueryParameters(), POSITIONAL_PARAMETER_FROM_PB_FUNCTION)); + } else { + Map values = Maps.newHashMap(); + for (QueryParameter queryParameterPb : queryRequestPb.getQueryParameters()) { + checkNotNull(queryParameterPb.getName()); + QueryParameterValue value = QueryParameterValue.fromPb( + queryParameterPb.getParameterValue(), queryParameterPb.getParameterType()); + values.put(queryParameterPb.getName(), value); + } + builder.setNamedParameters(values); + } + } if (queryRequestPb.getMaxResults() != null) { builder.setPageSize(queryRequestPb.getMaxResults()); } @@ -477,4 +632,54 @@ static QueryRequest fromPb(com.google.api.services.bigquery.model.QueryRequest q } return builder.build(); } + + static QueryParameter namedParameterToPb(Map.Entry entry) { + QueryParameter queryParameterPb = + new QueryParameter(); + queryParameterPb.setName(entry.getKey()); + queryParameterPb.setParameterValue(entry.getValue().toValuePb()); + queryParameterPb.setParameterType(entry.getValue().toTypePb()); + return queryParameterPb; + } + + static QueryParameter positionalParameterToPb(QueryParameterValue value) { + QueryParameter queryParameterPb = + new QueryParameter(); + queryParameterPb.setParameterValue(value.toValuePb()); + queryParameterPb.setParameterType(value.toTypePb()); + return queryParameterPb; + } + + static QueryParameterValue positionalParameterFromPb(QueryParameter queryParameterPb) { + checkArgument(queryParameterPb.getName() == null); + return QueryParameterValue.fromPb( + queryParameterPb.getParameterValue(), queryParameterPb.getParameterType()); + } + + static final Function + POSITIONAL_PARAMETER_FROM_PB_FUNCTION = + new Function() { + @Override + public QueryParameterValue apply(QueryParameter pb) { + return positionalParameterFromPb(pb); + } + }; + static final Function + POSITIONAL_PARAMETER_TO_PB_FUNCTION = + new Function() { + @Override + public QueryParameter apply( + QueryParameterValue value) { + return positionalParameterToPb(value); + } + }; + static final Function, QueryParameter> + NAMED_PARAMETER_TO_PB_FUNCTION = + new Function, QueryParameter>() { + @Override + public QueryParameter apply( + Map.Entry value) { + return namedParameterToPb(value); + } + }; } diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLTypeName.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLTypeName.java new file mode 100644 index 000000000000..a1d0f7154845 --- /dev/null +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLTypeName.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery; + +/** + * A type used in standard SQL contexts. For example, these types are used in queries + * with query parameters, which requires usage of standard SQL. + * + * @see https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types + */ +public enum StandardSQLTypeName { + /** A Boolean value (true or false). */ + BOOL, + /** A 64-bit signed integer value. */ + INT64, + /** A 64-bit IEEE binary floating-point value. */ + FLOAT64, + /** Variable-length character (Unicode) data. */ + STRING, + /** Variable-length binary data. */ + BYTES, + /** Container of ordered fields each with a type (required) and field name (optional). */ + STRUCT, + /** Ordered list of zero or more elements of any non-array type. */ + ARRAY, + /** + * Represents an absolute point in time, with microsecond precision. Values range between the + * years 1 and 9999, inclusive. + */ + TIMESTAMP, + /** Represents a logical calendar date. Values range between the years 1 and 9999, inclusive. */ + DATE, + /** Represents a time, independent of a specific date, to microsecond precision. */ + TIME, + /** Represents a year, month, day, hour, minute, second, and subsecond (microsecond precision). */ + DATETIME +} diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java new file mode 100644 index 000000000000..36a0634f0d1e --- /dev/null +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryParameterValueTest.java @@ -0,0 +1,252 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.List; +import org.junit.Test; + +public class QueryParameterValueTest { + + @Test + public void testBool() { + QueryParameterValue value = QueryParameterValue.bool(true); + assertEquals("true", value.getValue()); + assertEquals(StandardSQLTypeName.BOOL, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testInt64() { + QueryParameterValue value = QueryParameterValue.int64(8L); + assertEquals("8", value.getValue()); + assertEquals(StandardSQLTypeName.INT64, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testInt64FromInteger() { + QueryParameterValue value = QueryParameterValue.int64(7); + assertEquals("7", value.getValue()); + assertEquals(StandardSQLTypeName.INT64, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testFloat64() { + QueryParameterValue value = QueryParameterValue.float64(1.2); + assertEquals("1.2", value.getValue()); + assertEquals(StandardSQLTypeName.FLOAT64, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testFloat64FromFloat() { + QueryParameterValue value = QueryParameterValue.float64(1.2f); + assertEquals("1.2", value.getValue()); + assertEquals(StandardSQLTypeName.FLOAT64, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testString() { + QueryParameterValue value = QueryParameterValue.string("foo"); + assertEquals("foo", value.getValue()); + assertEquals(StandardSQLTypeName.STRING, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testBytes() { + QueryParameterValue value = QueryParameterValue.bytes(new byte[] {1, 3}); + assertEquals("AQM=", value.getValue()); + assertEquals(StandardSQLTypeName.BYTES, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testBoolArray() { + QueryParameterValue value = QueryParameterValue.array(new Boolean[] {true, false}, Boolean.class); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.BOOL, value.getArrayType()); + assertArrayDataEquals(new String[]{"true", "false"}, StandardSQLTypeName.BOOL, value.getArrayValues()); + } + + @Test + public void testInt64Array() { + QueryParameterValue value = QueryParameterValue.array(new Long[] {2L, 5L}, Long.class); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.INT64, value.getArrayType()); + assertArrayDataEquals(new String[]{"2", "5"}, StandardSQLTypeName.INT64, value.getArrayValues()); + } + + @Test + public void testInt64ArrayFromIntegers() { + QueryParameterValue value = QueryParameterValue.array(new Integer[] {2, 5}, Integer.class); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.INT64, value.getArrayType()); + assertArrayDataEquals(new String[]{"2", "5"}, StandardSQLTypeName.INT64, value.getArrayValues()); + } + + @Test + public void testFloat64Array() { + QueryParameterValue value = QueryParameterValue.array(new Double[] {2.6, 5.4}, Double.class); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.FLOAT64, value.getArrayType()); + assertArrayDataEquals(new String[]{"2.6", "5.4"}, StandardSQLTypeName.FLOAT64, value.getArrayValues()); + } + + @Test + public void testFloat64ArrayFromFloats() { + QueryParameterValue value = QueryParameterValue.array(new Float[] {2.6f, 5.4f}, Float.class); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.FLOAT64, value.getArrayType()); + assertArrayDataEquals(new String[]{"2.6", "5.4"}, StandardSQLTypeName.FLOAT64, value.getArrayValues()); + } + + @Test + public void testStringArray() { + QueryParameterValue value = QueryParameterValue.array(new String[] {"Ana", "Marv"}, String.class); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.STRING, value.getArrayType()); + assertArrayDataEquals(new String[]{"Ana", "Marv"}, StandardSQLTypeName.STRING, value.getArrayValues()); + } + + @Test + public void testTimestampFromLong() { + QueryParameterValue value = QueryParameterValue.timestamp(1408452095220000L); + assertEquals("2014-08-19 12:41:35.220000+00:00", value.getValue()); + assertEquals(StandardSQLTypeName.TIMESTAMP, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test + public void testTimestamp() { + QueryParameterValue value = QueryParameterValue.timestamp("2014-08-19 12:41:35.220000+00:00"); + assertEquals("2014-08-19 12:41:35.220000+00:00", value.getValue()); + assertEquals(StandardSQLTypeName.TIMESTAMP, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidTimestamp() { + // missing the time + QueryParameterValue.timestamp("2014-08-19"); + } + + @Test + public void testDate() { + QueryParameterValue value = QueryParameterValue.date("2014-08-19"); + assertEquals("2014-08-19", value.getValue()); + assertEquals(StandardSQLTypeName.DATE, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidDate() { + // not supposed to have the time + QueryParameterValue.date("2014-08-19 12:41:35.220000"); + } + + @Test + public void testTime() { + QueryParameterValue value = QueryParameterValue.time("05:41:35.220000"); + assertEquals("05:41:35.220000", value.getValue()); + assertEquals(StandardSQLTypeName.TIME, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidTime() { + // not supposed to have the date + QueryParameterValue.time("2014-08-19 12:41:35.220000"); + } + + @Test + public void testDateTime() { + QueryParameterValue value = QueryParameterValue.dateTime("2014-08-19 05:41:35.220000"); + assertEquals("2014-08-19 05:41:35.220000", value.getValue()); + assertEquals(StandardSQLTypeName.DATETIME, value.getType()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidDateTime() { + // missing the time + QueryParameterValue.dateTime("2014-08-19"); + } + + @Test + public void testTimestampArrayFromLongs() { + QueryParameterValue value = + QueryParameterValue.array(new Long[] {1408452095220000L, 1481041545110000L}, StandardSQLTypeName.TIMESTAMP); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.TIMESTAMP, value.getArrayType()); + assertArrayDataEquals( + new String[]{"2014-08-19 12:41:35.220000+00:00", "2016-12-06 16:25:45.110000+00:00"}, + StandardSQLTypeName.TIMESTAMP, + value.getArrayValues()); + } + + @Test + public void testTimestampArray() { + QueryParameterValue value = + QueryParameterValue.array( + new String[] {"2014-08-19 12:41:35.220000+00:00", "2016-12-06 16:25:45.110000+00:00"}, + StandardSQLTypeName.TIMESTAMP); + assertNull(value.getValue()); + assertEquals(StandardSQLTypeName.ARRAY, value.getType()); + assertEquals(StandardSQLTypeName.TIMESTAMP, value.getArrayType()); + assertArrayDataEquals( + new String[]{"2014-08-19 12:41:35.220000+00:00", "2016-12-06 16:25:45.110000+00:00"}, + StandardSQLTypeName.TIMESTAMP, + value.getArrayValues()); + } + + private static void assertArrayDataEquals(String[] expectedValues, + StandardSQLTypeName expectedType, List actualValues) { + assertEquals(expectedValues.length, actualValues.size()); + for (int i = 0; i < expectedValues.length; i++) { + QueryParameterValue value = actualValues.get(i); + assertEquals(expectedType, value.getType()); + assertEquals(expectedValues[i], value.getValue()); + assertNull(value.getArrayType()); + assertNull(value.getArrayValues()); + } + } +} diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryRequestTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryRequestTest.java index 92a331bc0180..82f37aba50df 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryRequestTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/QueryRequestTest.java @@ -16,10 +16,17 @@ package com.google.cloud.bigquery; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -32,7 +39,11 @@ public class QueryRequestTest { private static final Boolean DRY_RUN = false; private static final Long PAGE_SIZE = 42L; private static final Long MAX_WAIT_TIME = 42000L; - private static final Boolean USE_LEGACY_SQL = true; + private static final Boolean USE_LEGACY_SQL = false; + private static final String QUERY_PARAMETER_NAME = "paramName"; + private static final QueryParameterValue QUERY_PARAMETER_VALUE = QueryParameterValue.int64(7); + private static final List POSITIONAL_PARAMETERS = + Arrays.asList(QUERY_PARAMETER_VALUE); private static final QueryRequest QUERY_REQUEST = QueryRequest.newBuilder(QUERY) .setUseQueryCache(USE_QUERY_CACHE) .setDefaultDataset(DATASET_ID) @@ -40,6 +51,7 @@ public class QueryRequestTest { .setPageSize(PAGE_SIZE) .setMaxWaitTime(MAX_WAIT_TIME) .setUseLegacySql(USE_LEGACY_SQL) + .setPositionalParameters(POSITIONAL_PARAMETERS) .build(); private static final QueryRequest DEPRECATED_QUERY_REQUEST = QueryRequest.builder(QUERY) .useQueryCache(USE_QUERY_CACHE) @@ -78,7 +90,10 @@ public void testBuilder() { assertEquals(DRY_RUN, QUERY_REQUEST.dryRun()); assertEquals(PAGE_SIZE, QUERY_REQUEST.getPageSize()); assertEquals(MAX_WAIT_TIME, QUERY_REQUEST.getMaxWaitTime()); - assertTrue(QUERY_REQUEST.useLegacySql()); + assertEquals(1, QUERY_REQUEST.getPositionalParameters().size()); + assertEquals(QUERY_PARAMETER_VALUE, QUERY_REQUEST.getPositionalParameters().get(0)); + assertTrue(QUERY_REQUEST.getNamedParameters().isEmpty()); + assertFalse(QUERY_REQUEST.useLegacySql()); thrown.expect(NullPointerException.class); QueryRequest.newBuilder(null); } @@ -91,11 +106,55 @@ public void testBuilderDeprecated() { assertEquals(DRY_RUN, DEPRECATED_QUERY_REQUEST.dryRun()); assertEquals(PAGE_SIZE, DEPRECATED_QUERY_REQUEST.pageSize()); assertEquals(MAX_WAIT_TIME, DEPRECATED_QUERY_REQUEST.maxWaitTime()); - assertTrue(DEPRECATED_QUERY_REQUEST.useLegacySql()); + assertTrue(DEPRECATED_QUERY_REQUEST.getNamedParameters().isEmpty()); + assertTrue(DEPRECATED_QUERY_REQUEST.getPositionalParameters().isEmpty()); + assertFalse(DEPRECATED_QUERY_REQUEST.useLegacySql()); thrown.expect(NullPointerException.class); QueryRequest.builder(null); } + @Test + public void testNamedParameters() { + QueryRequest queryRequestNamedParams = + QueryRequest.newBuilder(QUERY) + .addNamedParameter(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE) + .build(); + assertEquals(1, queryRequestNamedParams.getNamedParameters().size()); + assertTrue(queryRequestNamedParams.getNamedParameters().containsKey(QUERY_PARAMETER_NAME)); + assertEquals( + QUERY_PARAMETER_VALUE, + queryRequestNamedParams.getNamedParameters().get(QUERY_PARAMETER_NAME)); + assertTrue(queryRequestNamedParams.getPositionalParameters().isEmpty()); + } + + @Test(expected = IllegalStateException.class) + public void testAddNamedAfterAddPositional() { + QueryRequest.newBuilder(QUERY) + .addPositionalParameter(QUERY_PARAMETER_VALUE) + .addNamedParameter(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE); + } + + @Test(expected = IllegalStateException.class) + public void testSetNamedAfterAddPositional() { + QueryRequest.newBuilder(QUERY) + .addPositionalParameter(QUERY_PARAMETER_VALUE) + .setNamedParameters(Collections.singletonMap(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE)); + } + + @Test(expected = IllegalStateException.class) + public void testAddPositionalAfterNamed() { + QueryRequest.newBuilder(QUERY) + .addNamedParameter(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE) + .addPositionalParameter(QUERY_PARAMETER_VALUE); + } + + @Test(expected = IllegalStateException.class) + public void testSetPositionalAfterNamed() { + QueryRequest.newBuilder(QUERY) + .addNamedParameter(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE) + .setPositionalParameters(Arrays.asList(QUERY_PARAMETER_VALUE)); + } + @Test public void testOf() { QueryRequest request = QueryRequest.of(QUERY); @@ -106,6 +165,10 @@ public void testOf() { assertNull(request.getPageSize()); assertNull(request.getMaxWaitTime()); assertNull(request.useLegacySql()); + assertNotNull(request.getPositionalParameters()); + assertTrue(request.getPositionalParameters().isEmpty()); + assertNotNull(request.getNamedParameters()); + assertTrue(request.getNamedParameters().isEmpty()); thrown.expect(NullPointerException.class); QueryRequest.of(null); } @@ -117,6 +180,17 @@ public void testToPbAndFromPb() { compareQueryRequest(queryRequest, QueryRequest.fromPb(queryRequest.toPb())); } + @Test + public void testToPbAndFromPbWithNamedParameters() { + QueryRequest withNamedParams = + QUERY_REQUEST + .toBuilder() + .setPositionalParameters(null) + .addNamedParameter(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE) + .build(); + compareQueryRequest(withNamedParams, QueryRequest.fromPb(withNamedParams.toPb())); + } + @Test public void testSetProjectId() { assertEquals("p", QUERY_REQUEST.setProjectId("p").getDefaultDataset().getProject()); @@ -131,5 +205,13 @@ private void compareQueryRequest(QueryRequest expected, QueryRequest value) { assertEquals(expected.getPageSize(), value.getPageSize()); assertEquals(expected.getMaxWaitTime(), value.getMaxWaitTime()); assertEquals(expected.useLegacySql(), value.useLegacySql()); + assertArrayEquals(expected.getPositionalParameters().toArray(new QueryParameterValue[0]), + value.getPositionalParameters().toArray(new QueryParameterValue[0])); + assertEquals(expected.getNamedParameters().size(), value.getNamedParameters().size()); + for (Map.Entry entry : expected.getNamedParameters().entrySet()) { + QueryParameterValue paramValue = value.getNamedParameters().get(entry.getKey()); + assertNotNull(paramValue); + assertEquals(entry.getValue(), paramValue); + } } } diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java index 084a9be94c3a..3ba8d75ae524 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java @@ -52,6 +52,7 @@ import com.google.cloud.bigquery.JobStatistics.LoadStatistics; import com.google.cloud.bigquery.LoadJobConfiguration; import com.google.cloud.bigquery.QueryJobConfiguration; +import com.google.cloud.bigquery.QueryParameterValue; import com.google.cloud.bigquery.QueryRequest; import com.google.cloud.bigquery.QueryResponse; import com.google.cloud.bigquery.Schema; @@ -73,14 +74,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.io.BaseEncoding; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Timeout; - import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -93,6 +88,11 @@ import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; public class ITBigQueryTest { @@ -112,10 +112,10 @@ public class ITBigQueryTest { .setMode(Field.Mode.NULLABLE) .setDescription("StringDescription") .build(); - private static final Field INTEGER_FIELD_SCHEMA = - Field.newBuilder("IntegerField", Field.Type.integer()) + private static final Field INTEGER_ARRAY_FIELD_SCHEMA = + Field.newBuilder("IntegerArrayField", Field.Type.integer()) .setMode(Field.Mode.REPEATED) - .setDescription("IntegerDescription") + .setDescription("IntegerArrayDescription") .build(); private static final Field BOOLEAN_FIELD_SCHEMA = Field.newBuilder("BooleanField", Field.Type.bool()) @@ -129,12 +129,24 @@ public class ITBigQueryTest { .build(); private static final Field RECORD_FIELD_SCHEMA = Field.newBuilder("RecordField", Field.Type.record(TIMESTAMP_FIELD_SCHEMA, - STRING_FIELD_SCHEMA, INTEGER_FIELD_SCHEMA, BOOLEAN_FIELD_SCHEMA, BYTES_FIELD_SCHEMA)) + STRING_FIELD_SCHEMA, INTEGER_ARRAY_FIELD_SCHEMA, BOOLEAN_FIELD_SCHEMA, + BYTES_FIELD_SCHEMA)) .setMode(Field.Mode.REQUIRED) .setDescription("RecordDescription") .build(); + private static final Field INTEGER_FIELD_SCHEMA = + Field.newBuilder("IntegerField", Field.Type.integer()) + .setMode(Field.Mode.NULLABLE) + .setDescription("IntegerDescription") + .build(); + private static final Field FLOAT_FIELD_SCHEMA = + Field.newBuilder("FloatField", Field.Type.floatingPoint()) + .setMode(Field.Mode.NULLABLE) + .setDescription("FloatDescription") + .build(); private static final Schema TABLE_SCHEMA = Schema.of(TIMESTAMP_FIELD_SCHEMA, STRING_FIELD_SCHEMA, - INTEGER_FIELD_SCHEMA, BOOLEAN_FIELD_SCHEMA, BYTES_FIELD_SCHEMA, RECORD_FIELD_SCHEMA); + INTEGER_ARRAY_FIELD_SCHEMA, BOOLEAN_FIELD_SCHEMA, BYTES_FIELD_SCHEMA, RECORD_FIELD_SCHEMA, + INTEGER_FIELD_SCHEMA, FLOAT_FIELD_SCHEMA); private static final Schema SIMPLE_SCHEMA = Schema.of(STRING_FIELD_SCHEMA); private static final Schema QUERY_RESULT_SCHEMA = Schema.newBuilder() .addField(Field.newBuilder("TimestampField", Field.Type.timestamp()) @@ -154,32 +166,36 @@ public class ITBigQueryTest { private static final TableId TABLE_ID = TableId.of(DATASET, "testing_table"); private static final String CSV_CONTENT = "StringValue1\nStringValue2\n"; private static final String JSON_CONTENT = "{" - + "\"TimestampField\": \"2014-08-19 07:41:35.220 -05:00\"," - + "\"StringField\": \"stringValue\"," - + "\"IntegerField\": [\"0\", \"1\"]," - + "\"BooleanField\": \"false\"," - + "\"BytesField\": \"" + BYTES_BASE64 + "\"," - + "\"RecordField\": {" - + "\"TimestampField\": \"1969-07-20 20:18:04 UTC\"," - + "\"StringField\": null," - + "\"IntegerField\": [\"1\",\"0\"]," - + "\"BooleanField\": \"true\"," - + "\"BytesField\": \"" + BYTES_BASE64 + "\"" - + "}" + + " \"TimestampField\": \"2014-08-19 07:41:35.220 -05:00\"," + + " \"StringField\": \"stringValue\"," + + " \"IntegerArrayField\": [\"0\", \"1\"]," + + " \"BooleanField\": \"false\"," + + " \"BytesField\": \"" + BYTES_BASE64 + "\"," + + " \"RecordField\": {" + + " \"TimestampField\": \"1969-07-20 20:18:04 UTC\"," + + " \"StringField\": null," + + " \"IntegerArrayField\": [\"1\",\"0\"]," + + " \"BooleanField\": \"true\"," + + " \"BytesField\": \"" + BYTES_BASE64 + "\"" + + " }," + + " \"IntegerField\": \"3\"," + + " \"FloatField\": \"1.2\"" + "}\n" + "{" - + "\"TimestampField\": \"2014-08-19 07:41:35.220 -05:00\"," - + "\"StringField\": \"stringValue\"," - + "\"IntegerField\": [\"0\", \"1\"]," - + "\"BooleanField\": \"false\"," - + "\"BytesField\": \"" + BYTES_BASE64 + "\"," - + "\"RecordField\": {" - + "\"TimestampField\": \"1969-07-20 20:18:04 UTC\"," - + "\"StringField\": null," - + "\"IntegerField\": [\"1\",\"0\"]," - + "\"BooleanField\": \"true\"," - + "\"BytesField\": \"" + BYTES_BASE64 + "\"" - + "}" + + " \"TimestampField\": \"2014-08-19 07:41:35.220 -05:00\"," + + " \"StringField\": \"stringValue\"," + + " \"IntegerArrayField\": [\"0\", \"1\"]," + + " \"BooleanField\": \"false\"," + + " \"BytesField\": \"" + BYTES_BASE64 + "\"," + + " \"RecordField\": {" + + " \"TimestampField\": \"1969-07-20 20:18:04 UTC\"," + + " \"StringField\": null," + + " \"IntegerArrayField\": [\"1\",\"0\"]," + + " \"BooleanField\": \"true\"," + + " \"BytesField\": \"" + BYTES_BASE64 + "\"" + + " }," + + " \"IntegerField\": \"3\"," + + " \"FloatField\": \"1.2\"" + "}"; private static final Set PUBLIC_DATASETS = ImmutableSet.of("github_repos", "hacker_news", @@ -387,7 +403,7 @@ public void testCreateExternalTable() throws InterruptedException { assertEquals(createdTable.getTableId(), remoteTable.getTableId()); assertEquals(TABLE_SCHEMA, remoteTable.getDefinition().getSchema()); QueryRequest request = QueryRequest.newBuilder( - "SELECT TimestampField, StringField, IntegerField, BooleanField FROM " + DATASET + "." + "SELECT TimestampField, StringField, IntegerArrayField, BooleanField FROM " + DATASET + "." + tableName) .setDefaultDataset(DatasetId.of(DATASET)) .setMaxWaitTime(60000L) @@ -562,25 +578,29 @@ public void testInsertAll() throws IOException { ImmutableMap.Builder builder1 = ImmutableMap.builder(); builder1.put("TimestampField", "2014-08-19 07:41:35.220 -05:00"); builder1.put("StringField", "stringValue"); - builder1.put("IntegerField", ImmutableList.of(0, 1)); + builder1.put("IntegerArrayField", ImmutableList.of(0, 1)); builder1.put("BooleanField", false); builder1.put("BytesField", BYTES_BASE64); builder1.put("RecordField", ImmutableMap.of( "TimestampField", "1969-07-20 20:18:04 UTC", - "IntegerField", ImmutableList.of(1, 0), + "IntegerArrayField", ImmutableList.of(1, 0), "BooleanField", true, "BytesField", BYTES_BASE64)); + builder1.put("IntegerField", 5); + builder1.put("FloatField", 1.2); ImmutableMap.Builder builder2 = ImmutableMap.builder(); builder2.put("TimestampField", "2014-08-19 07:41:35.220 -05:00"); builder2.put("StringField", "stringValue"); - builder2.put("IntegerField", ImmutableList.of(0, 1)); + builder2.put("IntegerArrayField", ImmutableList.of(0, 1)); builder2.put("BooleanField", false); builder2.put("BytesField", BYTES_BASE64); builder2.put("RecordField", ImmutableMap.of( "TimestampField", "1969-07-20 20:18:04 UTC", - "IntegerField", ImmutableList.of(1, 0), + "IntegerArrayField", ImmutableList.of(1, 0), "BooleanField", true, "BytesField", BYTES_BASE64)); + builder2.put("IntegerField", 5); + builder2.put("FloatField", 1.2); InsertAllRequest request = InsertAllRequest.newBuilder(tableInfo.getTableId()) .addRow(builder1.build()) .addRow(builder2.build()) @@ -600,25 +620,29 @@ public void testInsertAllWithSuffix() throws InterruptedException { ImmutableMap.Builder builder1 = ImmutableMap.builder(); builder1.put("TimestampField", "2014-08-19 07:41:35.220 -05:00"); builder1.put("StringField", "stringValue"); - builder1.put("IntegerField", ImmutableList.of(0, 1)); + builder1.put("IntegerArrayField", ImmutableList.of(0, 1)); builder1.put("BooleanField", false); builder1.put("BytesField", BYTES_BASE64); builder1.put("RecordField", ImmutableMap.of( "TimestampField", "1969-07-20 20:18:04 UTC", - "IntegerField", ImmutableList.of(1, 0), + "IntegerArrayField", ImmutableList.of(1, 0), "BooleanField", true, "BytesField", BYTES_BASE64)); + builder1.put("IntegerField", 5); + builder1.put("FloatField", 1.2); ImmutableMap.Builder builder2 = ImmutableMap.builder(); builder2.put("TimestampField", "2014-08-19 07:41:35.220 -05:00"); builder2.put("StringField", "stringValue"); - builder2.put("IntegerField", ImmutableList.of(0, 1)); + builder2.put("IntegerArrayField", ImmutableList.of(0, 1)); builder2.put("BooleanField", false); builder2.put("BytesField", BYTES_BASE64); builder2.put("RecordField", ImmutableMap.of( "TimestampField", "1969-07-20 20:18:04 UTC", - "IntegerField", ImmutableList.of(1, 0), + "IntegerArrayField", ImmutableList.of(1, 0), "BooleanField", true, "BytesField", BYTES_BASE64)); + builder2.put("IntegerField", 5); + builder2.put("FloatField", 1.2); InsertAllRequest request = InsertAllRequest.newBuilder(tableInfo.getTableId()) .addRow(builder1.build()) .addRow(builder2.build()) @@ -647,29 +671,33 @@ public void testInsertAllWithErrors() { ImmutableMap.Builder builder1 = ImmutableMap.builder(); builder1.put("TimestampField", "2014-08-19 07:41:35.220 -05:00"); builder1.put("StringField", "stringValue"); - builder1.put("IntegerField", ImmutableList.of(0, 1)); + builder1.put("IntegerArrayField", ImmutableList.of(0, 1)); builder1.put("BooleanField", false); builder1.put("BytesField", BYTES_BASE64); builder1.put("RecordField", ImmutableMap.of( "TimestampField", "1969-07-20 20:18:04 UTC", - "IntegerField", ImmutableList.of(1, 0), + "IntegerArrayField", ImmutableList.of(1, 0), "BooleanField", true, "BytesField", BYTES_BASE64)); + builder1.put("IntegerField", 5); + builder1.put("FloatField", 1.2); ImmutableMap.Builder builder2 = ImmutableMap.builder(); builder2.put("TimestampField", "invalidDate"); builder2.put("StringField", "stringValue"); - builder2.put("IntegerField", ImmutableList.of(0, 1)); + builder2.put("IntegerArrayField", ImmutableList.of(0, 1)); builder2.put("BooleanField", false); builder2.put("BytesField", BYTES_BASE64); builder2.put("RecordField", ImmutableMap.of( "TimestampField", "1969-07-20 20:18:04 UTC", - "IntegerField", ImmutableList.of(1, 0), + "IntegerArrayField", ImmutableList.of(1, 0), "BooleanField", true, "BytesField", BYTES_BASE64)); + builder2.put("IntegerField", 5); + builder2.put("FloatField", 1.2); ImmutableMap.Builder builder3 = ImmutableMap.builder(); builder3.put("TimestampField", "2014-08-19 07:41:35.220 -05:00"); builder3.put("StringField", "stringValue"); - builder3.put("IntegerField", ImmutableList.of(0, 1)); + builder3.put("IntegerArrayField", ImmutableList.of(0, 1)); builder3.put("BooleanField", false); builder3.put("BytesField", BYTES_BASE64); InsertAllRequest request = InsertAllRequest.newBuilder(tableInfo.getTableId()) @@ -693,20 +721,24 @@ public void testListAllTableData() { for (List row : rows.getValues()) { FieldValue timestampCell = row.get(0); FieldValue stringCell = row.get(1); - FieldValue integerCell = row.get(2); + FieldValue integerArrayCell = row.get(2); FieldValue booleanCell = row.get(3); FieldValue bytesCell = row.get(4); FieldValue recordCell = row.get(5); + FieldValue integerCell = row.get(6); + FieldValue floatCell = row.get(7); assertEquals(FieldValue.Attribute.PRIMITIVE, timestampCell.getAttribute()); assertEquals(FieldValue.Attribute.PRIMITIVE, stringCell.getAttribute()); - assertEquals(FieldValue.Attribute.REPEATED, integerCell.getAttribute()); + assertEquals(FieldValue.Attribute.REPEATED, integerArrayCell.getAttribute()); assertEquals(FieldValue.Attribute.PRIMITIVE, booleanCell.getAttribute()); assertEquals(FieldValue.Attribute.PRIMITIVE, bytesCell.getAttribute()); assertEquals(FieldValue.Attribute.RECORD, recordCell.getAttribute()); + assertEquals(FieldValue.Attribute.PRIMITIVE, integerCell.getAttribute()); + assertEquals(FieldValue.Attribute.PRIMITIVE, floatCell.getAttribute()); assertEquals(1408452095220000L, timestampCell.getTimestampValue()); assertEquals("stringValue", stringCell.getStringValue()); - assertEquals(0, integerCell.getRepeatedValue().get(0).getLongValue()); - assertEquals(1, integerCell.getRepeatedValue().get(1).getLongValue()); + assertEquals(0, integerArrayCell.getRepeatedValue().get(0).getLongValue()); + assertEquals(1, integerArrayCell.getRepeatedValue().get(1).getLongValue()); assertEquals(false, booleanCell.getBooleanValue()); assertArrayEquals(BYTES, bytesCell.getBytesValue()); assertEquals(-14182916000000L, recordCell.getRecordValue().get(0).getTimestampValue()); @@ -714,11 +746,22 @@ public void testListAllTableData() { assertEquals(1, recordCell.getRecordValue().get(2).getRepeatedValue().get(0).getLongValue()); assertEquals(0, recordCell.getRecordValue().get(2).getRepeatedValue().get(1).getLongValue()); assertEquals(true, recordCell.getRecordValue().get(3).getBooleanValue()); + assertEquals(3, integerCell.getLongValue()); + assertEquals(1.2, floatCell.getDoubleValue(), 0.0001); rowCount++; } assertEquals(2, rowCount); } + private QueryResponse queryAndWaitForResponse(QueryRequest request) throws InterruptedException { + QueryResponse response = bigquery.query(request); + while (!response.jobCompleted()) { + Thread.sleep(1000); + response = bigquery.getQueryResults(response.getJobId()); + } + return response; + } + @Test public void testQuery() throws InterruptedException { String query = new StringBuilder() @@ -730,11 +773,7 @@ public void testQuery() throws InterruptedException { .setMaxWaitTime(60000L) .setPageSize(1000L) .build(); - QueryResponse response = bigquery.query(request); - while (!response.jobCompleted()) { - Thread.sleep(1000); - response = bigquery.getQueryResults(response.getJobId()); - } + QueryResponse response = queryAndWaitForResponse(request); assertEquals(QUERY_RESULT_SCHEMA, response.getResult().getSchema()); int rowCount = 0; for (List row : response.getResult().getValues()) { @@ -755,6 +794,86 @@ public void testQuery() throws InterruptedException { assertNotNull(statistics.getQueryPlan()); } + @Test + public void testPositionalQueryParameters() throws InterruptedException { + String query = new StringBuilder() + .append("SELECT TimestampField, StringField, BooleanField FROM ") + .append(TABLE_ID.getTable()) + .append(" WHERE StringField = ?") + .append(" AND TimestampField > ?") + .append(" AND IntegerField IN UNNEST(?)") + .append(" AND IntegerField < ?") + .append(" AND FloatField > ?") + .toString(); + QueryParameterValue stringParameter = QueryParameterValue.string("stringValue"); + QueryParameterValue timestampParameter = + QueryParameterValue.timestamp("2014-01-01 07:00:00.000000+00:00"); + QueryParameterValue intArrayParameter = + QueryParameterValue.array(new Integer[] {3, 4}, Integer.class); + QueryParameterValue int64Parameter = QueryParameterValue.int64(5); + QueryParameterValue float64Parameter = QueryParameterValue.float64(0.5); + QueryRequest request = QueryRequest.newBuilder(query) + .setDefaultDataset(DatasetId.of(DATASET)) + .setMaxWaitTime(60000L) + .setPageSize(1000L) + .setUseLegacySql(false) + .addPositionalParameter(stringParameter) + .addPositionalParameter(timestampParameter) + .addPositionalParameter(intArrayParameter) + .addPositionalParameter(int64Parameter) + .addPositionalParameter(float64Parameter) + .build(); + QueryResponse response = queryAndWaitForResponse(request); + assertEquals(QUERY_RESULT_SCHEMA, response.getResult().getSchema()); + assertEquals(2, Iterables.size(response.getResult().getValues())); + } + + @Test + public void testNamedQueryParameters() throws InterruptedException { + String query = new StringBuilder() + .append("SELECT TimestampField, StringField, BooleanField FROM ") + .append(TABLE_ID.getTable()) + .append(" WHERE StringField = @stringParam") + .append(" AND IntegerField IN UNNEST(@integerList)") + .toString(); + QueryParameterValue stringParameter = QueryParameterValue.string("stringValue"); + QueryParameterValue intArrayParameter = + QueryParameterValue.array(new Integer[]{3, 4}, Integer.class); + QueryRequest request = QueryRequest.newBuilder(query) + .setDefaultDataset(DatasetId.of(DATASET)) + .setMaxWaitTime(60000L) + .setPageSize(1000L) + .setUseLegacySql(false) + .addNamedParameter("stringParam", stringParameter) + .addNamedParameter("integerList", intArrayParameter) + .build(); + QueryResponse response = queryAndWaitForResponse(request); + assertEquals(QUERY_RESULT_SCHEMA, response.getResult().getSchema()); + assertEquals(2, Iterables.size(response.getResult().getValues())); + } + + @Test + public void testBytesParameter() throws Exception { + String query = new StringBuilder() + .append("SELECT BYTE_LENGTH(@p) AS length") + .toString(); + QueryParameterValue bytesParameter = QueryParameterValue.bytes(new byte[] { 1, 3 }); + QueryRequest request = QueryRequest.newBuilder(query) + .setDefaultDataset(DatasetId.of(DATASET)) + .setMaxWaitTime(60000L) + .setPageSize(1000L) + .setUseLegacySql(false) + .addNamedParameter("p", bytesParameter) + .build(); + QueryResponse response = queryAndWaitForResponse(request); + int rowCount = 0; + for (List row : response.getResult().getValues()) { + rowCount++; + assertEquals(2, row.get(0).getLongValue()); + } + assertEquals(1, rowCount); + } + @Test public void testListJobs() { Page jobs = bigquery.listJobs(); @@ -996,20 +1115,24 @@ public void testInsertFromFile() throws InterruptedException, IOException, Timeo for (List row : rows.getValues()) { FieldValue timestampCell = row.get(0); FieldValue stringCell = row.get(1); - FieldValue integerCell = row.get(2); + FieldValue integerArrayCell = row.get(2); FieldValue booleanCell = row.get(3); FieldValue bytesCell = row.get(4); FieldValue recordCell = row.get(5); + FieldValue integerCell = row.get(6); + FieldValue floatCell = row.get(7); assertEquals(FieldValue.Attribute.PRIMITIVE, timestampCell.getAttribute()); assertEquals(FieldValue.Attribute.PRIMITIVE, stringCell.getAttribute()); - assertEquals(FieldValue.Attribute.REPEATED, integerCell.getAttribute()); + assertEquals(FieldValue.Attribute.REPEATED, integerArrayCell.getAttribute()); assertEquals(FieldValue.Attribute.PRIMITIVE, booleanCell.getAttribute()); assertEquals(FieldValue.Attribute.PRIMITIVE, bytesCell.getAttribute()); assertEquals(FieldValue.Attribute.RECORD, recordCell.getAttribute()); + assertEquals(FieldValue.Attribute.PRIMITIVE, integerCell.getAttribute()); + assertEquals(FieldValue.Attribute.PRIMITIVE, floatCell.getAttribute()); assertEquals(1408452095220000L, timestampCell.getTimestampValue()); assertEquals("stringValue", stringCell.getStringValue()); - assertEquals(0, integerCell.getRepeatedValue().get(0).getLongValue()); - assertEquals(1, integerCell.getRepeatedValue().get(1).getLongValue()); + assertEquals(0, integerArrayCell.getRepeatedValue().get(0).getLongValue()); + assertEquals(1, integerArrayCell.getRepeatedValue().get(1).getLongValue()); assertEquals(false, booleanCell.getBooleanValue()); assertArrayEquals(BYTES, bytesCell.getBytesValue()); assertEquals(-14182916000000L, recordCell.getRecordValue().get(0).getTimestampValue()); @@ -1017,6 +1140,8 @@ public void testInsertFromFile() throws InterruptedException, IOException, Timeo assertEquals(1, recordCell.getRecordValue().get(2).getRepeatedValue().get(0).getLongValue()); assertEquals(0, recordCell.getRecordValue().get(2).getRepeatedValue().get(1).getLongValue()); assertEquals(true, recordCell.getRecordValue().get(3).getBooleanValue()); + assertEquals(3, integerCell.getLongValue()); + assertEquals(1.2, floatCell.getDoubleValue(), 0.0001); rowCount++; } assertEquals(2, rowCount);