diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java index a566fbeb8421..9eb6b92d2325 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java @@ -151,6 +151,39 @@ public String getSelector() { } } + /** + * Fields of a BigQuery Routine resource. + * + * @see Routine + * Resource + */ + enum RoutineField implements FieldSelector { + ARGUMENTS("arguments"), + CREATION_TIME("creationTime"), + DEFINITION_BODY("definitionBody"), + ETAG("etag"), + IMPORTED_LIBRARIES("importedLibraries"), + LANGUAGE("language"), + LAST_MODIFIED_TIME("lastModifiedTime"), + RETURN_TYPE("returnType"), + ROUTINE_REFERENCE("routineReference"), + ROUTINE_TYPE("routineType"); + + static final List REQUIRED_FIELDS = + ImmutableList.of(ROUTINE_REFERENCE); + + private final String selector; + + RoutineField(String selector) { + this.selector = selector; + } + + @Override + public String getSelector() { + return selector; + } + } + /** * Fields of a BigQuery Job resource. * @@ -267,6 +300,27 @@ public static ModelListOption pageToken(String pageToken) { } } + /** Class for specifying routine list options. */ + class RoutineListOption extends Option { + + private static final long serialVersionUID = 8660294969063312498L; + + private RoutineListOption(BigQueryRpc.Option option, Object value) { + super(option, value); + } + + /** Returns an option to specify the maximum number of routines returned per page. */ + public static RoutineListOption pageSize(long pageSize) { + checkArgument(pageSize >= 0); + return new RoutineListOption(BigQueryRpc.Option.MAX_RESULTS, pageSize); + } + + /** Returns an option to specify the page token from which to start listing routines. */ + public static RoutineListOption pageToken(String pageToken) { + return new RoutineListOption(BigQueryRpc.Option.PAGE_TOKEN, pageToken); + } + } + /** Class for specifying table list options. */ class TableListOption extends Option { @@ -309,7 +363,7 @@ public static TableOption fields(TableField... fields) { } } - /** Class for specifying table get, create and update options. */ + /** Class for specifying model get, create and update options. */ class ModelOption extends Option { private static final long serialVersionUID = -1723870134095226772L; @@ -329,6 +383,26 @@ public static ModelOption fields(ModelField... fields) { } } + /** Class for specifying table get, create and update options. */ + class RoutineOption extends Option { + + private static final long serialVersionUID = -1723870122095226772L; + + private RoutineOption(BigQueryRpc.Option option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the routines's fields to be returned by the RPC call. If this + * option is not provided all model's fields are returned. {@code RoutineOption.fields} can be + * used to specify only the fields of interest. + */ + public static RoutineOption fields(RoutineField... fields) { + return new RoutineOption( + BigQueryRpc.Option.FIELDS, Helper.selector(RoutineField.REQUIRED_FIELDS, fields)); + } + } + /** Class for specifying table data list options. */ class TableDataListOption extends Option { @@ -583,6 +657,13 @@ public int hashCode() { */ Table create(TableInfo tableInfo, TableOption... options); + /** + * Creates a new routine. + * + * @throws BigQueryException upon failure + */ + Routine create(RoutineInfo routineInfo, RoutineOption... options); + /** * Creates a new job. * @@ -804,6 +885,29 @@ public int hashCode() { */ boolean delete(ModelId modelId); + /** + * Deletes the requested routine. + * + *

Example of deleting a routine. + * + *

{@code
+   * String projectId = "my_project_id";
+   * String datasetId = "my_dataset_id";
+   * String routineId = "my_routine_id";
+   * RoutineId routineId = RoutineId.of(projectId, datasetId, routineId);
+   * boolean deleted = bigquery.delete(routineId);
+   * if (deleted) {
+   *   // the routine was deleted
+   * } else {
+   *   // the routine was not found
+   * }
+   * 
+ * + * @return {@code true} if routine was deleted, {@code false} if it was not found + * @throws BigQueryException upon failure + */ + boolean delete(RoutineId routineId); + /** * Updates dataset information. * @@ -899,6 +1003,13 @@ public int hashCode() { */ Model update(ModelInfo modelInfo, ModelOption... options); + /** + * Updates routine information. + * + * @throws BigQueryException upon failure + */ + Routine update(RoutineInfo routineInfo, RoutineOption... options); + /** * Returns the requested table or {@code null} if not found. * @@ -955,6 +1066,26 @@ public int hashCode() { */ Model getModel(ModelId tableId, ModelOption... options); + /** + * Returns the requested routine or {@code null} if not found. + * + * @throws BigQueryException upon failure + */ + Routine getRoutine(String datasetId, String routineId, RoutineOption... options); + + /** + * Returns the requested routine or {@code null} if not found. + * + * @throws BigQueryException upon failure + */ + Routine getRoutine(RoutineId routineId, RoutineOption... options); + + /** Lists the routines in the specified dataset. */ + Page listRoutines(String datasetId, RoutineListOption... options); + + /** Lists the routines in the specified dataset. */ + Page listRoutines(DatasetId datasetId, RoutineListOption... options); + /** * Lists the tables in the dataset. This method returns partial information on each table: ({@link * Table#getTableId()}, {@link Table#getFriendlyName()}, {@link Table#getGeneratedId()} and type, diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java index 06b3f150b580..85fcadfdec14 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java @@ -125,6 +125,30 @@ public Page getNextPage() { } } + private static class RoutinePageFetcher implements NextPageFetcher { + + private static final long serialVersionUID = 8611242311504201187L; + private final Map requestOptions; + private final BigQueryOptions serviceOptions; + private final DatasetId datasetId; + + RoutinePageFetcher( + DatasetId datasetId, + BigQueryOptions serviceOptions, + String cursor, + Map optionMap) { + this.requestOptions = + PageImpl.nextRequestOptions(BigQueryRpc.Option.PAGE_TOKEN, cursor, optionMap); + this.serviceOptions = serviceOptions; + this.datasetId = datasetId; + } + + @Override + public Page getNextPage() { + return listRoutines(datasetId, serviceOptions, requestOptions); + } + } + private static class JobPageFetcher implements NextPageFetcher { private static final long serialVersionUID = 8536533282558245472L; @@ -226,6 +250,34 @@ public com.google.api.services.bigquery.model.Table call() { } } + @Override + public Routine create(RoutineInfo routineInfo, RoutineOption... options) { + final com.google.api.services.bigquery.model.Routine routinePb = + routineInfo + .setProjectId( + Strings.isNullOrEmpty(routineInfo.getRoutineId().getProject()) + ? getOptions().getProjectId() + : routineInfo.getRoutineId().getProject()) + .toPb(); + final Map optionsMap = optionMap(options); + try { + return Routine.fromPb( + this, + runWithRetries( + new Callable() { + @Override + public com.google.api.services.bigquery.model.Routine call() { + return bigQueryRpc.create(routinePb, optionsMap); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock())); + } catch (RetryHelper.RetryHelperException e) { + throw BigQueryException.translateAndThrow(e); + } + } + @Override public Job create(JobInfo jobInfo, JobOption... options) { Supplier idProvider = @@ -456,6 +508,32 @@ public Boolean call() { } } + @Override + public boolean delete(RoutineId routineId) { + final RoutineId completeRoutineId = + routineId.setProjectId( + Strings.isNullOrEmpty(routineId.getProject()) + ? getOptions().getProjectId() + : routineId.getProject()); + try { + return runWithRetries( + new Callable() { + @Override + public Boolean call() { + return bigQueryRpc.deleteRoutine( + completeRoutineId.getProject(), + completeRoutineId.getDataset(), + completeRoutineId.getRoutine()); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock()); + } catch (RetryHelper.RetryHelperException e) { + throw BigQueryException.translateAndThrow(e); + } + } + @Override public Dataset update(DatasetInfo datasetInfo, DatasetOption... options) { final com.google.api.services.bigquery.model.Dataset datasetPb = @@ -535,6 +613,34 @@ public com.google.api.services.bigquery.model.Model call() { } } + @Override + public Routine update(RoutineInfo routineInfo, RoutineOption... options) { + final com.google.api.services.bigquery.model.Routine routinePb = + routineInfo + .setProjectId( + Strings.isNullOrEmpty(routineInfo.getRoutineId().getProject()) + ? getOptions().getProjectId() + : routineInfo.getRoutineId().getProject()) + .toPb(); + final Map optionsMap = optionMap(options); + try { + return Routine.fromPb( + this, + runWithRetries( + new Callable() { + @Override + public com.google.api.services.bigquery.model.Routine call() { + return bigQueryRpc.update(routinePb, optionsMap); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock())); + } catch (RetryHelper.RetryHelperException e) { + throw BigQueryException.translateAndThrow(e); + } + } + @Override public Table getTable(final String datasetId, final String tableId, TableOption... options) { return getTable(TableId.of(datasetId, tableId), options); @@ -612,6 +718,44 @@ public com.google.api.services.bigquery.model.Model call() { } } + @Override + public Routine getRoutine(String datasetId, String routineId, RoutineOption... options) { + return getRoutine(RoutineId.of(datasetId, routineId), options); + } + + @Override + public Routine getRoutine(RoutineId routineId, RoutineOption... options) { + final RoutineId completeRoutineId = + routineId.setProjectId( + Strings.isNullOrEmpty(routineId.getProject()) + ? getOptions().getProjectId() + : routineId.getProject()); + final Map optionsMap = optionMap(options); + try { + com.google.api.services.bigquery.model.Routine answer = + runWithRetries( + new Callable() { + @Override + public com.google.api.services.bigquery.model.Routine call() { + return bigQueryRpc.getRoutine( + completeRoutineId.getProject(), + completeRoutineId.getDataset(), + completeRoutineId.getRoutine(), + optionsMap); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock()); + if (getOptions().getThrowNotFound() && answer == null) { + throw new BigQueryException(HTTP_NOT_FOUND, "Model not found"); + } + return answer == null ? null : Routine.fromPb(this, answer); + } catch (RetryHelper.RetryHelperException e) { + throw BigQueryException.translateAndThrow(e); + } + } + @Override public Page listTables(String datasetId, TableListOption... options) { return listTables( @@ -636,6 +780,18 @@ public Page listModels(DatasetId datasetId, ModelListOption... options) { return listModels(completeDatasetId, getOptions(), optionMap(options)); } + @Override + public Page listRoutines(String datasetId, RoutineListOption... options) { + return listRoutines( + DatasetId.of(getOptions().getProjectId(), datasetId), getOptions(), optionMap(options)); + } + + @Override + public Page listRoutines(DatasetId datasetId, RoutineListOption... options) { + DatasetId completeDatasetId = datasetId.setProjectId(getOptions().getProjectId()); + return listRoutines(completeDatasetId, getOptions(), optionMap(options)); + } + @Override public List listPartitions(TableId tableId) { List partitions = new ArrayList(); @@ -730,6 +886,43 @@ public Model apply(com.google.api.services.bigquery.model.Model model) { } } + private static Page listRoutines( + final DatasetId datasetId, + final BigQueryOptions serviceOptions, + final Map optionsMap) { + try { + Tuple> result = + runWithRetries( + new Callable< + Tuple>>() { + @Override + public Tuple> + call() { + return serviceOptions + .getBigQueryRpcV2() + .listRoutines(datasetId.getProject(), datasetId.getDataset(), optionsMap); + } + }, + serviceOptions.getRetrySettings(), + EXCEPTION_HANDLER, + serviceOptions.getClock()); + String cursor = result.x(); + Iterable routines = + Iterables.transform( + result.y(), + new Function() { + @Override + public Routine apply(com.google.api.services.bigquery.model.Routine routinePb) { + return Routine.fromPb(serviceOptions.getService(), routinePb); + } + }); + return new PageImpl<>( + new RoutinePageFetcher(datasetId, serviceOptions, cursor, optionsMap), cursor, routines); + } catch (RetryHelper.RetryHelperException e) { + throw BigQueryException.translateAndThrow(e); + } + } + @Override public InsertAllResponse insertAll(InsertAllRequest request) { final TableId tableId = diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java index ac6e4a85750a..388284c04aa3 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java @@ -317,6 +317,7 @@ public static class QueryStatistics extends JobStatistics { private final Boolean cacheHit; private final String ddlOperationPerformed; private final TableId ddlTargetTable; + private final RoutineId ddlTargetRoutine; private final Long estimatedBytesProcessed; private final Long numDmlAffectedRows; private final List referencedTables; @@ -355,8 +356,16 @@ public StatementType apply(String constant) { public static final StatementType CREATE_TABLE_AS_SELECT = type.createAndRegister("CREATE_TABLE_AS_SELECT"); public static final StatementType CREATE_VIEW = type.createAndRegister("CREATE_VIEW"); + public static final StatementType CREATE_MODEL = type.createAndRegister("CREATE_MODEL"); + public static final StatementType CREATE_FUNCTION = type.createAndRegister("CREATE_FUNCTION"); + public static final StatementType CREATE_PROCEDURE = + type.createAndRegister("CREATE_PROCEDURE"); + public static final StatementType ALTER_TABLE = type.createAndRegister("ALTER_TABLE"); + public static final StatementType ALTER_VIEW = type.createAndRegister("ALTER_VIEW"); public static final StatementType DROP_TABLE = type.createAndRegister("DROP_TABLE"); public static final StatementType DROP_VIEW = type.createAndRegister("DROP_VIEW"); + public static final StatementType DROP_FUNCTION = type.createAndRegister("DROP_FUNCTION"); + public static final StatementType DROP_PROCEDURE = type.createAndRegister("DROP_PROCEDURE"); public static final StatementType MERGE = type.createAndRegister("MERGE"); private StatementType(String constant) { @@ -388,6 +397,7 @@ static final class Builder extends JobStatistics.Builder referencedTables; @@ -411,6 +421,9 @@ private Builder(com.google.api.services.bigquery.model.JobStatistics statisticsP if (statisticsPb.getQuery().getDdlTargetTable() != null) { this.ddlTargetTable = TableId.fromPb(statisticsPb.getQuery().getDdlTargetTable()); } + if (statisticsPb.getQuery().getDdlTargetRoutine() != null) { + this.ddlTargetRoutine = RoutineId.fromPb(statisticsPb.getQuery().getDdlTargetRoutine()); + } this.estimatedBytesProcessed = statisticsPb.getQuery().getEstimatedBytesProcessed(); this.numDmlAffectedRows = statisticsPb.getQuery().getNumDmlAffectedRows(); this.totalBytesBilled = statisticsPb.getQuery().getTotalBytesBilled(); @@ -462,6 +475,11 @@ Builder setDDLTargetTable(TableId ddlTargetTable) { return self(); } + Builder setDDLTargetRoutine(RoutineId ddlTargetRoutine) { + this.ddlTargetRoutine = ddlTargetRoutine; + return self(); + } + Builder setEstimatedBytesProcessed(Long estimatedBytesProcessed) { this.estimatedBytesProcessed = estimatedBytesProcessed; return self(); @@ -534,6 +552,7 @@ private QueryStatistics(Builder builder) { this.cacheHit = builder.cacheHit; this.ddlOperationPerformed = builder.ddlOperationPerformed; this.ddlTargetTable = builder.ddlTargetTable; + this.ddlTargetRoutine = builder.ddlTargetRoutine; this.estimatedBytesProcessed = builder.estimatedBytesProcessed; this.numDmlAffectedRows = builder.numDmlAffectedRows; this.referencedTables = builder.referencedTables; @@ -571,6 +590,11 @@ public TableId getDdlTargetTable() { return ddlTargetTable; } + /** [BETA] For DDL queries, returns the RoutineId of the targeted routine. */ + public RoutineId getDdlTargetRoutine() { + return ddlTargetRoutine; + } + /** The original estimate of bytes processed for the job. */ public Long getEstimatedBytesProcessed() { return estimatedBytesProcessed; @@ -696,6 +720,9 @@ com.google.api.services.bigquery.model.JobStatistics toPb() { if (ddlTargetTable != null) { queryStatisticsPb.setDdlTargetTable(ddlTargetTable.toPb()); } + if (ddlTargetRoutine != null) { + queryStatisticsPb.setDdlTargetRoutine(ddlTargetRoutine.toPb()); + } if (referencedTables != null) { queryStatisticsPb.setReferencedTables( diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Routine.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Routine.java new file mode 100644 index 000000000000..61c8848fb188 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Routine.java @@ -0,0 +1,191 @@ +/* + * Copyright 2019 Google LLC + * + * 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.checkNotNull; + +import com.google.cloud.bigquery.BigQuery.RoutineOption; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.List; +import java.util.Objects; + +/** + * A Google BigQuery Routine. + * + *

Objects of this class are immutable. Operations that modify the routine like {@link #update} + * return a new object. To get a {@code routine} object with the most recent information use {@link + * #reload}. + */ +public class Routine extends RoutineInfo { + + private final BigQueryOptions options; + private transient BigQuery bigquery; + + public static class Builder extends RoutineInfo.Builder { + + private final BigQuery bigquery; + private final RoutineInfo.BuilderImpl infoBuilder; + + Builder(BigQuery bigquery, RoutineId routineId) { + this.bigquery = bigquery; + this.infoBuilder = new RoutineInfo.BuilderImpl(); + this.infoBuilder.setRoutineId(routineId); + } + + Builder(Routine routine) { + this.bigquery = routine.bigquery; + this.infoBuilder = new RoutineInfo.BuilderImpl(routine); + } + + @Override + Builder setRoutineId(RoutineId id) { + infoBuilder.setRoutineId(id); + return this; + } + + @Override + Builder setEtag(String etag) { + infoBuilder.setEtag(etag); + return this; + } + + @Override + public Builder setRoutineType(String routineType) { + infoBuilder.setRoutineType(routineType); + return this; + } + + @Override + Builder setCreationTime(Long creationMillis) { + infoBuilder.setCreationTime(creationMillis); + return this; + } + + @Override + Builder setLastModifiedTime(Long lastModifiedMillis) { + infoBuilder.setLastModifiedTime(lastModifiedMillis); + return this; + } + + @Override + public Builder setLanguage(String language) { + infoBuilder.setLanguage(language); + return this; + } + + @Override + public Builder setArguments(List arguments) { + infoBuilder.setArguments(arguments); + return this; + } + + @Override + public Builder setReturnType(StandardSQLDataType returnType) { + infoBuilder.setReturnType(returnType); + return this; + } + + @Override + public Builder setImportedLibraries(List libraries) { + infoBuilder.setImportedLibraries(libraries); + return this; + } + + @Override + public Builder setBody(String body) { + infoBuilder.setBody(body); + return this; + } + + @Override + public Routine build() { + return new Routine(bigquery, infoBuilder); + } + } + + Routine(BigQuery bigquery, RoutineInfo.BuilderImpl infoBuilder) { + super(infoBuilder); + this.bigquery = checkNotNull(bigquery); + this.options = bigquery.getOptions(); + } + + /** Checks if this routine exists. */ + public boolean exists() { + return bigquery.getRoutine(getRoutineId(), RoutineOption.fields()) != null; + } + + /** + * Fetches this routine's latest information. Returns {@code null} if the routine does not exist. + */ + public Routine reload(RoutineOption... options) { + return bigquery.getRoutine(getRoutineId(), options); + } + + /** + * Update's the routine's information with this Routine's information. This method does not allow + * changing the RoutineId identifier of the routine. A new {@code Routine} is returned. + */ + public Routine update(RoutineOption... options) { + return bigquery.update(this, options); + } + + /** + * Deletes this routine. + * + * @return {@code true} if routine was deleted, {@code false} if it was not found + * @throws BigQueryException upon failure + */ + public boolean delete() { + return bigquery.delete(getRoutineId()); + } + + /** Returns the routine's {@code BigQuery} object used to issue requests. */ + public BigQuery getBigQuery() { + return bigquery; + } + + @Override + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public final boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !obj.getClass().equals(Routine.class)) { + return false; + } + Routine other = (Routine) obj; + return Objects.equals(toPb(), other.toPb()) && Objects.equals(options, other.options); + } + + public final int hashCode() { + return Objects.hash(super.hashCode(), options); + } + + public void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + this.bigquery = options.getService(); + } + + static Routine fromPb( + BigQuery bigquery, com.google.api.services.bigquery.model.Routine routinePb) { + return new Routine(bigquery, new RoutineInfo.BuilderImpl(routinePb)); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineArgument.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineArgument.java new file mode 100644 index 000000000000..d36d1f229517 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineArgument.java @@ -0,0 +1,117 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.api.services.bigquery.model.Argument; +import com.google.auto.value.AutoValue; +import com.google.common.base.Function; +import javax.annotation.Nullable; + +/** An argument for a BigQuery Routine. */ +@AutoValue +public abstract class RoutineArgument { + + static final Function FROM_PB_FUNCTION = + new Function() { + @Override + public RoutineArgument apply(Argument pb) { + return RoutineArgument.fromPb(pb); + } + }; + static final Function TO_PB_FUNCTION = + new Function() { + @Override + public Argument apply(RoutineArgument argument) { + return argument.toPb(); + } + }; + + @AutoValue.Builder + public abstract static class Builder { + /** Sets the argument name. */ + public abstract Builder setName(String name); + + /** + * Sets the kind of argument. + * + *

A FIXED_TYPE argument is a fully specified type. It can be a struct or an array, but not a + * table. + * + *

An ANY_TYPE argument is any type. It can be a struct or an array, but not a table. + */ + public abstract Builder setKind(String kind); + + /** + * Optionally specifies the input/output mode of the argument. + * + *

An IN mode argument is input-only. An OUT mode argument is output-only. An INOUT mode + * argument is both an input and output. + */ + public abstract Builder setMode(String mode); + + /** + * Sets the data type specification for the argument. It is required except for ANY_TYPE + * argument kinds. + */ + public abstract Builder setDataType(StandardSQLDataType dataType); + + /** Creates a {@code RoutineArgument} object. */ + public abstract RoutineArgument build(); + } + + /** Returns the name of the argument. */ + @Nullable + public abstract String getName(); + + /** Returns the kind of the argument. */ + @Nullable + public abstract String getKind(); + + /** Returns the mode of the argument. */ + @Nullable + public abstract String getMode(); + + @Nullable + public abstract StandardSQLDataType getDataType(); + + /** Returns a builder pre-populated using the current values of this {@code RoutineArgument}. */ + public abstract Builder toBuilder(); + + /** Returns a builder for a {@Code RoutineArgument} object. */ + public static Builder newBuilder() { + return new AutoValue_RoutineArgument.Builder(); + } + + Argument toPb() { + Argument argumentPb = + new Argument().setName(getName()).setArgumentKind(getKind()).setMode(getMode()); + if (getDataType() != null) { + argumentPb.setDataType(getDataType().toPb()); + } + return argumentPb; + } + + static RoutineArgument fromPb(Argument argumentPb) { + Builder builder = newBuilder(); + builder.setName(argumentPb.getName()); + builder.setKind(argumentPb.getArgumentKind()); + builder.setMode(argumentPb.getMode()); + if (argumentPb.getDataType() != null) { + builder.setDataType(StandardSQLDataType.fromPb(argumentPb.getDataType())); + } + return builder.build(); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineId.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineId.java new file mode 100644 index 000000000000..d5d3c5bfe982 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineId.java @@ -0,0 +1,111 @@ +/* + * Copyright 2019 Google LLC + * + * 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.checkNotNull; + +import com.google.api.services.bigquery.model.RoutineReference; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import java.io.Serializable; +import java.util.Objects; + +/** RoutineId represents the identifier for a given Routine. */ +public final class RoutineId implements Serializable { + + static final Function FROM_PB_FUNCTION = + new Function() { + @Override + public RoutineId apply(RoutineReference pb) { + return RoutineId.fromPb(pb); + } + }; + static final Function TO_PB_FUNCTION = + new Function() { + @Override + public RoutineReference apply(RoutineId routineId) { + return routineId.toPb(); + } + }; + + private final String project; + private final String dataset; + private final String routine; + + /** Return corresponding project ID for this routine. * */ + public String getProject() { + return project; + } + + /** Return corresponding dataset ID for this routine. * */ + public String getDataset() { + return dataset; + } + + /** Return corresponding routine ID for this routine. * */ + public String getRoutine() { + return routine; + } + + private RoutineId(String project, String dataset, String routine) { + this.project = project; + this.dataset = dataset; + this.routine = routine; + } + + /** Creates a routine identity given project, dataset, and routine identifiers. * */ + public static RoutineId of(String project, String dataset, String routine) { + return new RoutineId(checkNotNull(project), checkNotNull(dataset), checkNotNull(routine)); + } + + /** Creates a routine identity given dataset and routine identifiers. * */ + public static RoutineId of(String dataset, String routine) { + return new RoutineId(null, checkNotNull(dataset), checkNotNull(routine)); + } + + @Override + public boolean equals(Object obj) { + return obj == this + || obj instanceof RoutineId && Objects.equals(toPb(), ((RoutineId) obj).toPb()); + } + + @Override + public int hashCode() { + return Objects.hash(project, dataset, routine); + } + + @Override + public String toString() { + return toPb().toString(); + } + + RoutineId setProjectId(String projectId) { + Preconditions.checkArgument( + !Strings.isNullOrEmpty(projectId), "Provided projectId is null or empty"); + return RoutineId.of(projectId, getDataset(), getRoutine()); + } + + RoutineReference toPb() { + return new RoutineReference().setProjectId(project).setDatasetId(dataset).setRoutineId(routine); + } + + static RoutineId fromPb(RoutineReference routineRef) { + return new RoutineId( + routineRef.getProjectId(), routineRef.getDatasetId(), routineRef.getRoutineId()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineInfo.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineInfo.java new file mode 100644 index 000000000000..1474b1994c9e --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/RoutineInfo.java @@ -0,0 +1,405 @@ +/* + * Copyright 2019 Google LLC + * + * 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.checkNotNull; + +import com.google.api.services.bigquery.model.Routine; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Google BigQuery routine information. A Routine is an API abstraction that encapsulates several + * related concepts inside the BigQuery service, including scalar user defined functions (UDFS) and + * stored procedures. + * + *

For more information about the REST representation of routines, see: + * https://cloud.google.com/bigquery/docs/reference/rest/v2/routines + * + *

For more information about working with scalar functions, see: + * https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions + */ +public class RoutineInfo implements Serializable { + + static final Function FROM_PB_FUNCTION = + new Function() { + @Override + public RoutineInfo apply(Routine pb) { + return RoutineInfo.fromPb(pb); + } + }; + static final Function TO_PB_FUNCTION = + new Function() { + @Override + public Routine apply(RoutineInfo routineInfo) { + return routineInfo.toPb(); + } + }; + + private final RoutineId routineId; + private final String etag; + private final String routineType; + private final Long creationTime; + private final Long lastModifiedTime; + private final String language; + private final List argumentList; + private final StandardSQLDataType returnType; + private final List importedLibrariesList; + private final String body; + + public abstract static class Builder { + + abstract Builder setRoutineId(RoutineId id); + + abstract Builder setEtag(String etag); + + /** + * Sets the routine type for the Builder (e.g. SCALAR_FUNCTION). + * + *

See https://cloud.google.com/bigquery/docs/reference/rest/v2/routines + */ + public abstract Builder setRoutineType(String routineType); + + abstract Builder setCreationTime(Long creationMillis); + + abstract Builder setLastModifiedTime(Long lastModifiedMillis); + + /** Sets the language for the routine (e.g. SQL or JAVASCRIPT) */ + public abstract Builder setLanguage(String language); + + /** Specifies the list of input/output arguments for the routine. */ + public abstract Builder setArguments(List argumentList); + + /** + * Sets the return type of the routine. + * + *

Optional if language = "SQL"; required otherwise. + * + *

If absent, the return type is inferred from definitionBody at query time in each query + * that references this routine. If present, then the evaluated result will be cast to the + * specified returned type at query time. + */ + public abstract Builder setReturnType(StandardSQLDataType returnType); + + /** + * Optional. If language = "JAVASCRIPT", this field stores the path of the imported JAVASCRIPT + * libraries as a list of gs:// URLs. + */ + public abstract Builder setImportedLibraries(List importedLibrariesList); + + /** + * Required. The body of the routine. + * + *

For functions, this is the expression in the AS clause. + * + *

If language=SQL, it is the substring inside (but excluding) the parentheses. For example, + * for the function created with the following statement: + * + *

CREATE FUNCTION JoinLines(x string, y string) as (concat(x, "\n", y)) + * + *

The definitionBody is concat(x, "\n", y) (\n is not replaced with linebreak). + * + *

If language=JAVASCRIPT, it is the evaluated string in the AS clause. For example, for the + * function created with the following statement: + * + *

CREATE FUNCTION f() RETURNS STRING LANGUAGE js AS 'return "\n";\n' + * + *

The definitionBody is + * + *

return "\n";\n + * + *

Note that both \n are replaced with linebreaks. + */ + public abstract Builder setBody(String body); + + /** Creates a {@code RoutineInfo} object. */ + public abstract RoutineInfo build(); + } + + static class BuilderImpl extends Builder { + private RoutineId routineId; + private String etag; + private String routineType; + private Long creationTime; + private Long lastModifiedTime; + private String language; + private List argumentList; + private StandardSQLDataType returnType; + private List importedLibrariesList; + private String body; + + BuilderImpl() {} + + BuilderImpl(RoutineInfo routineInfo) { + this.routineId = routineInfo.routineId; + this.etag = routineInfo.etag; + this.routineType = routineInfo.routineType; + this.creationTime = routineInfo.creationTime; + this.lastModifiedTime = routineInfo.lastModifiedTime; + this.language = routineInfo.language; + this.argumentList = routineInfo.argumentList; + this.returnType = routineInfo.returnType; + this.importedLibrariesList = routineInfo.importedLibrariesList; + this.body = routineInfo.body; + } + + BuilderImpl(Routine routinePb) { + this.routineId = RoutineId.fromPb(routinePb.getRoutineReference()); + this.etag = routinePb.getEtag(); + this.routineType = routinePb.getRoutineType(); + this.creationTime = routinePb.getCreationTime(); + this.lastModifiedTime = routinePb.getLastModifiedTime(); + this.language = routinePb.getLanguage(); + if (routinePb.getArguments() != null) { + this.argumentList = + Lists.transform(routinePb.getArguments(), RoutineArgument.FROM_PB_FUNCTION); + } + if (routinePb.getReturnType() != null) { + this.returnType = StandardSQLDataType.fromPb(routinePb.getReturnType()); + } + if (routinePb.getImportedLibraries() == null) { + this.importedLibrariesList = Collections.emptyList(); + } else { + this.importedLibrariesList = routinePb.getImportedLibraries(); + } + this.body = routinePb.getDefinitionBody(); + } + + @Override + Builder setRoutineId(RoutineId id) { + this.routineId = id; + return this; + } + + @Override + Builder setEtag(String etag) { + this.etag = etag; + return this; + } + + @Override + public Builder setRoutineType(String routineType) { + this.routineType = routineType; + return this; + } + + @Override + Builder setCreationTime(Long creationMillis) { + this.creationTime = creationMillis; + return this; + } + + @Override + Builder setLastModifiedTime(Long lastModifiedMillis) { + this.lastModifiedTime = lastModifiedMillis; + return this; + } + + @Override + public Builder setLanguage(String language) { + this.language = language; + return this; + } + + @Override + public Builder setArguments(List argumentList) { + this.argumentList = argumentList; + return this; + } + + @Override + public Builder setReturnType(StandardSQLDataType returnType) { + this.returnType = returnType; + return this; + } + + @Override + public Builder setImportedLibraries(List importedLibrariesList) { + this.importedLibrariesList = importedLibrariesList; + return this; + } + + @Override + public Builder setBody(String body) { + this.body = body; + return this; + } + + @Override + public RoutineInfo build() { + return new RoutineInfo(this); + } + } + + RoutineInfo(BuilderImpl builder) { + this.routineId = checkNotNull(builder.routineId); + this.etag = builder.etag; + this.routineType = builder.routineType; + this.creationTime = builder.creationTime; + this.lastModifiedTime = builder.lastModifiedTime; + this.language = builder.language; + this.argumentList = builder.argumentList; + this.returnType = builder.returnType; + this.importedLibrariesList = builder.importedLibrariesList; + this.body = builder.body; + } + + /** Returns the RoutineId identified for the routine resource. * */ + public RoutineId getRoutineId() { + return routineId; + } + + /** Returns the hash of the routine resource. */ + public String getEtag() { + return etag; + } + + /** Returns the type of the routine, e.g. SCALAR_FUNCTION. */ + public String getRoutineType() { + return routineType; + } + + /** Returns the creation time of the routine, represented as milliseconds since the epoch. */ + public Long getCreationTime() { + return creationTime; + } + + /** + * Returns the last modification time of the routine, represented as milliseconds since the epoch. + */ + public Long getLastModifiedTime() { + return lastModifiedTime; + } + + /** + * Returns the language of the routine. Currently supported languages include SQL and JAVASCRIPT. + */ + public String getLanguage() { + return language; + } + + /** Returns the list of arguments for the routine. */ + public List getArguments() { + return argumentList; + } + + /** If specified, returns the data type returned from the routine. */ + public StandardSQLDataType getReturnType() { + return returnType; + } + + /** + * Returns the list of imported libraries for the routine. Only relevant for routines implemented + * using the JAVASCRIPT language. + */ + public List getImportedLibraries() { + return importedLibrariesList; + } + + /** Returns the definition body of the routine. */ + public String getBody() { + return body; + } + + /** Returns a builder pre-populated using the current values of this routine. */ + public Builder toBuilder() { + return new BuilderImpl(this); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("routineId", routineId) + .add("etag", etag) + .add("routineType", routineType) + .add("creationTime", creationTime) + .add("lastModifiedTime", lastModifiedTime) + .add("language", language) + .add("arguments", argumentList) + .add("returnType", returnType) + .add("importedLibrariesList", importedLibrariesList) + .add("body", body) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash( + routineId, + etag, + routineType, + creationTime, + lastModifiedTime, + language, + argumentList, + returnType, + importedLibrariesList, + body); + } + + @Override + public boolean equals(Object obj) { + return obj == this + || obj != null + && obj.getClass().equals(RoutineInfo.class) + && Objects.equals(toPb(), ((RoutineInfo) obj).toPb()); + } + + /** Returns a builder for a {@code RoutineInfo} object given routine identity. */ + public static Builder newBuilder(RoutineId routineId) { + return new BuilderImpl().setRoutineId(routineId); + } + + /** Returns a {@code RoutineInfo} object given routine identity. */ + public static RoutineInfo of(RoutineId routineId) { + return newBuilder(routineId).build(); + } + + RoutineInfo setProjectId(String projectId) { + if (Strings.isNullOrEmpty(getRoutineId().getProject())) { + return toBuilder().setRoutineId(getRoutineId().setProjectId(projectId)).build(); + } + return this; + } + + Routine toPb() { + Routine routinePb = + new Routine() + .setEtag(getEtag()) + .setRoutineType(getRoutineType()) + .setDefinitionBody(getBody()) + .setCreationTime(getCreationTime()) + .setLastModifiedTime(getLastModifiedTime()) + .setLanguage(getLanguage()); + if (getRoutineId() != null) { + routinePb.setRoutineReference(getRoutineId().toPb()); + } + if (getArguments() != null) { + routinePb.setArguments(Lists.transform(getArguments(), RoutineArgument.TO_PB_FUNCTION)); + } + return routinePb; + } + + static RoutineInfo fromPb(Routine routinePb) { + return new BuilderImpl(routinePb).build(); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLDataType.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLDataType.java new file mode 100644 index 000000000000..99ed10af1461 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLDataType.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.api.services.bigquery.model.StandardSqlDataType; +import com.google.auto.value.AutoValue; +import java.io.Serializable; +import javax.annotation.Nullable; + +/** Represents Standard SQL data type information. */ +@AutoValue +public abstract class StandardSQLDataType implements Serializable { + + @AutoValue.Builder + public abstract static class Builder { + /** Sets the type of an array's elements, when the TypeKind is ARRAY. */ + public abstract Builder setArrayElementType(StandardSQLDataType arrayElementType); + + /** Sets the struct type definition (list of fields) when the TypeKind is STRUCT. */ + public abstract Builder setStructType(StandardSQLStructType structType); + + /** + * Sets the top-level type of this data type. Can be any standard SQL data type. For more + * information, see https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types + */ + public abstract Builder setTypeKind(String typeKind); + + /** Creates a {@code StandardSQLDataType} object. */ + public abstract StandardSQLDataType build(); + } + + /** + * Returns the type kind of the data type. + * + *

Can be any standard SQL data type. For more information, see + * https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types + */ + public abstract String getTypeKind(); + + /** Returns the type of an ARRAY's elements. */ + @Nullable + public abstract StandardSQLDataType getArrayElementType(); + + /** Returns the struct definition's list of fields for a STRUCT type. */ + @Nullable + public abstract StandardSQLStructType getStructType(); + + public abstract Builder toBuilder(); + + public static Builder newBuilder() { + return new AutoValue_StandardSQLDataType.Builder(); + } + + /** Returns a new builder initialized with the type kind. */ + public static Builder newBuilder(String typeKind) { + return newBuilder().setTypeKind(typeKind); + } + + /** Returns a new builder initialized with a StandardSQLTypeName as the type kind. */ + public static Builder newBuilder(StandardSQLTypeName typeName) { + return newBuilder().setTypeKind(typeName.toString()); + } + + StandardSqlDataType toPb() { + StandardSqlDataType dataTypePb = new StandardSqlDataType(); + dataTypePb.setTypeKind(getTypeKind()); + if (getArrayElementType() != null) { + dataTypePb.setArrayElementType(getArrayElementType().toPb()); + } + if (getStructType() != null) { + dataTypePb.setStructType(getStructType().toPb()); + } + return dataTypePb; + } + + static StandardSQLDataType fromPb(StandardSqlDataType dataTypePb) { + Builder builder = newBuilder(); + builder.setTypeKind(dataTypePb.getTypeKind()); + if (dataTypePb.getArrayElementType() != null) { + builder.setArrayElementType(StandardSQLDataType.fromPb(dataTypePb.getArrayElementType())); + } + if (dataTypePb.getStructType() != null) { + builder.setStructType(StandardSQLStructType.fromPb(dataTypePb.getStructType())); + } + return builder.build(); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLField.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLField.java new file mode 100644 index 000000000000..013742548720 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLField.java @@ -0,0 +1,101 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.api.services.bigquery.model.StandardSqlField; +import com.google.auto.value.AutoValue; +import com.google.common.base.Function; +import java.io.Serializable; +import javax.annotation.Nullable; + +/** A Google BigQuery SQL Field. */ +@AutoValue +public abstract class StandardSQLField implements Serializable { + + static final Function FROM_PB_FUNCTION = + new Function() { + @Override + public StandardSQLField apply(StandardSqlField pb) { + return StandardSQLField.fromPb(pb); + } + }; + static final Function TO_PB_FUNCTION = + new Function() { + @Override + public StandardSqlField apply(StandardSQLField field) { + return field.toPb(); + } + }; + + @AutoValue.Builder + public abstract static class Builder { + + /** Sets the name of the field. */ + public abstract Builder setName(String name); + + /** Sets the data type of the field. */ + public abstract Builder setDataType(StandardSQLDataType dataType); + + /** Creates a {@code StandardSQLField} object. */ + public abstract StandardSQLField build(); + } + + /** Returns the field name. */ + @Nullable + public abstract String getName(); + + /** Returns the field's data type. */ + public abstract StandardSQLDataType getDataType(); + + /** Returns a builder pre-populated using the current values of this field. */ + public abstract Builder toBuilder(); + + /** Returns a builder for a {@code StandardSQLField} object. */ + public static Builder newBuilder() { + return new AutoValue_StandardSQLField.Builder(); + } + + /** Returns a builder for a {@code StandardSQLField} object with the specified data type. */ + public static Builder newBuilder(StandardSQLDataType dataType) { + return newBuilder().setDataType(dataType); + } + + /** + * Returns a builder for a {@code StandardSQLField} object with the specified field name and data + * type. + */ + public static Builder newBuilder(String name, StandardSQLDataType dataType) { + return newBuilder().setName(name).setDataType(dataType); + } + + StandardSqlField toPb() { + StandardSqlField fieldPb = new StandardSqlField(); + fieldPb.setName(getName()); + if (getDataType() != null) { + fieldPb.setType(getDataType().toPb()); + } + return fieldPb; + } + + static StandardSQLField fromPb(StandardSqlField fieldPb) { + Builder builder = newBuilder(); + builder.setName(fieldPb.getName()); + if (fieldPb.getType() != null) { + builder.setDataType(StandardSQLDataType.fromPb(fieldPb.getType())); + } + return builder.build(); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLStructType.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLStructType.java new file mode 100644 index 000000000000..43844916ec12 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/StandardSQLStructType.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.api.services.bigquery.model.StandardSqlStructType; +import com.google.auto.value.AutoValue; +import com.google.common.collect.Lists; +import java.io.Serializable; +import java.util.List; + +/** A set of fields contained within a SQL STRUCT in Google BigQuery. */ +@AutoValue +public abstract class StandardSQLStructType implements Serializable { + + @AutoValue.Builder + public abstract static class Builder { + + /** Sets the fields of the struct type. */ + public abstract Builder setFields(List fields); + + /** Creates a {@code StandardSQLStructType} object. */ + public abstract StandardSQLStructType build(); + } + + /** Returns the list of fields within a struct type. */ + public abstract List getFields(); + + /** Returns a builder pre-populated using the current values of this field. */ + public abstract Builder toBuilder(); + + /** Returns a builder for a {@code StandardSQLStructType} object. */ + public static Builder newBuilder() { + return new AutoValue_StandardSQLStructType.Builder(); + } + + /** Returns a builder for a {@code StandardSQLStructType} object with the specified fields. */ + public static Builder newBuilder(List fieldList) { + return newBuilder().setFields(fieldList); + } + + static StandardSQLStructType fromPb( + com.google.api.services.bigquery.model.StandardSqlStructType structTypePb) { + Builder builder = newBuilder(); + if (structTypePb.getFields() != null) { + builder.setFields( + Lists.transform(structTypePb.getFields(), StandardSQLField.FROM_PB_FUNCTION)); + } + return builder.build(); + } + + StandardSqlStructType toPb() { + StandardSqlStructType structTypePb = new StandardSqlStructType(); + if (getFields() != null) { + structTypePb.setFields(Lists.transform(getFields(), StandardSQLField.TO_PB_FUNCTION)); + } + return structTypePb; + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java index 8255bcfd5a56..a0dde557d259 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/BigQueryRpc.java @@ -21,6 +21,7 @@ import com.google.api.services.bigquery.model.GetQueryResultsResponse; import com.google.api.services.bigquery.model.Job; import com.google.api.services.bigquery.model.Model; +import com.google.api.services.bigquery.model.Routine; import com.google.api.services.bigquery.model.Table; import com.google.api.services.bigquery.model.TableDataInsertAllRequest; import com.google.api.services.bigquery.model.TableDataInsertAllResponse; @@ -158,7 +159,7 @@ Tuple> listTables( boolean deleteTable(String projectId, String datasetId, String tableId); /** - * Updates table information. + * Updates model information. * * @throws BigQueryException upon failure */ @@ -187,6 +188,37 @@ Tuple> listModels( */ boolean deleteModel(String projectId, String datasetId, String modelId); + /** + * Creates the requested routine. + * + * @throws BigQueryException upon failure + */ + Routine create(Routine routine, Map options); + + /** + * Updates the requested routine. + * + * @throws BigQueryException upon failure + */ + Routine update(Routine routine, Map options); + + /** + * Returns the requested routine or {@code null} if not found. + * + * @throws BigQueryException upon failure + */ + Routine getRoutine(String projectId, String datasetId, String routineId, Map options); + + Tuple> listRoutines( + String projectId, String datasetId, Map options); + /** + * Deletes the requested routine. + * + * @return {@code true} if routine was deleted, {@code false} if it was not found + * @throws BigQueryException upon failure + */ + boolean deleteRoutine(String projectId, String datasetId, String routineId); + /** * Sends an insert all request. * diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java index 4a83eb32fdbc..0584ee1d2041 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/spi/v2/HttpBigQueryRpc.java @@ -42,8 +42,11 @@ import com.google.api.services.bigquery.model.JobList; import com.google.api.services.bigquery.model.JobStatus; import com.google.api.services.bigquery.model.ListModelsResponse; +import com.google.api.services.bigquery.model.ListRoutinesResponse; import com.google.api.services.bigquery.model.Model; import com.google.api.services.bigquery.model.ModelReference; +import com.google.api.services.bigquery.model.Routine; +import com.google.api.services.bigquery.model.RoutineReference; import com.google.api.services.bigquery.model.Table; import com.google.api.services.bigquery.model.TableDataInsertAllRequest; import com.google.api.services.bigquery.model.TableDataInsertAllResponse; @@ -172,6 +175,20 @@ public Table create(Table table, Map options) { } } + @Override + public Routine create(Routine routine, Map options) { + try { + RoutineReference reference = routine.getRoutineReference(); + return bigquery + .routines() + .insert(reference.getProjectId(), reference.getDatasetId(), routine) + .setFields(Option.FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + @Override public Job create(Job job, Map options) { try { @@ -368,6 +385,71 @@ public boolean deleteModel(String projectId, String datasetId, String modelId) { } } + @Override + public Routine update(Routine routine, Map options) { + try { + RoutineReference reference = routine.getRoutineReference(); + return bigquery + .routines() + .update( + reference.getProjectId(), reference.getDatasetId(), reference.getRoutineId(), routine) + .setFields(Option.FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public Routine getRoutine( + String projectId, String datasetId, String routineId, Map options) { + try { + return bigquery + .routines() + .get(projectId, datasetId, routineId) + .setFields(Option.FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + BigQueryException serviceException = translate(ex); + if (serviceException.getCode() == HTTP_NOT_FOUND) { + return null; + } + throw serviceException; + } + } + + @Override + public Tuple> listRoutines( + String projectId, String datasetId, Map options) { + try { + ListRoutinesResponse routineList = + bigquery + .routines() + .list(projectId, datasetId) + .setMaxResults(Option.MAX_RESULTS.getLong(options)) + .setPageToken(Option.PAGE_TOKEN.getString(options)) + .execute(); + Iterable routines = routineList.getRoutines(); + return Tuple.of(routineList.getNextPageToken(), routines); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public boolean deleteRoutine(String projectId, String datasetId, String routineId) { + try { + bigquery.routines().delete(projectId, datasetId, routineId).execute(); + return true; + } catch (IOException ex) { + BigQueryException serviceException = translate(ex); + if (serviceException.getCode() == HTTP_NOT_FOUND) { + return false; + } + throw serviceException; + } + } + @Override public TableDataInsertAllResponse insertAll( String projectId, String datasetId, String tableId, TableDataInsertAllRequest request) { diff --git a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/testing/RemoteBigQueryHelper.java b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/testing/RemoteBigQueryHelper.java index 672b7e7c74a1..784ca984fa09 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/testing/RemoteBigQueryHelper.java +++ b/google-cloud-clients/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/testing/RemoteBigQueryHelper.java @@ -43,6 +43,7 @@ public class RemoteBigQueryHelper { private static final Logger log = Logger.getLogger(RemoteBigQueryHelper.class.getName()); private static final String DATASET_NAME_PREFIX = "gcloud_test_dataset_temp_"; private static final String MODEL_NAME_PREFIX = "model_"; + private static final String ROUTINE_NAME_PREFIX = "routine_"; private final BigQueryOptions options; private static final int connectTimeout = 60000; @@ -76,6 +77,10 @@ public static String generateModelName() { return MODEL_NAME_PREFIX + UUID.randomUUID().toString().replace('-', '_'); } + public static String generateRoutineName() { + return ROUTINE_NAME_PREFIX + UUID.randomUUID().toString().replace('-', '_'); + } + /** * Creates a {@code RemoteBigQueryHelper} object for the given project id and JSON key input * stream. diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/JobStatisticsTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/JobStatisticsTest.java index 891deb0049b9..2113eb8f5410 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/JobStatisticsTest.java +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/JobStatisticsTest.java @@ -34,6 +34,7 @@ public class JobStatisticsTest { private static final Boolean CACHE_HIT = true; private static final String DDL_OPERATION_PERFORMED = "SKIP"; private static final TableId DDL_TARGET_TABLE = TableId.of("foo", "bar", "baz"); + private static final RoutineId DDL_TARGET_ROUTINE = RoutineId.of("alpha", "beta", "gamma"); private static final Long ESTIMATE_BYTES_PROCESSED = 101L; private static final Long NUM_DML_AFFECTED_ROWS = 88L; private static final QueryStatistics.StatementType STATEMENT_TYPE = @@ -136,6 +137,7 @@ public class JobStatisticsTest { .setCacheHit(CACHE_HIT) .setDDLOperationPerformed(DDL_OPERATION_PERFORMED) .setDDLTargetTable(DDL_TARGET_TABLE) + .setDDLTargetRoutine(DDL_TARGET_ROUTINE) .setEstimatedBytesProcessed(ESTIMATE_BYTES_PROCESSED) .setNumDmlAffectedRows(NUM_DML_AFFECTED_ROWS) .setReferenceTables(REFERENCED_TABLES) @@ -180,6 +182,7 @@ public void testBuilder() { assertEquals(CACHE_HIT, QUERY_STATISTICS.getCacheHit()); assertEquals(DDL_OPERATION_PERFORMED, QUERY_STATISTICS.getDdlOperationPerformed()); assertEquals(DDL_TARGET_TABLE, QUERY_STATISTICS.getDdlTargetTable()); + assertEquals(DDL_TARGET_ROUTINE, QUERY_STATISTICS.getDdlTargetRoutine()); assertEquals(ESTIMATE_BYTES_PROCESSED, QUERY_STATISTICS.getEstimatedBytesProcessed()); assertEquals(NUM_DML_AFFECTED_ROWS, QUERY_STATISTICS.getNumDmlAffectedRows()); assertEquals(REFERENCED_TABLES, QUERY_STATISTICS.getReferencedTables()); @@ -207,6 +210,7 @@ public void testBuilder() { assertEquals(CACHE_HIT, QUERY_STATISTICS_INCOMPLETE.getCacheHit()); assertEquals(null, QUERY_STATISTICS_INCOMPLETE.getDdlOperationPerformed()); assertEquals(null, QUERY_STATISTICS_INCOMPLETE.getDdlTargetTable()); + assertEquals(null, QUERY_STATISTICS_INCOMPLETE.getDdlTargetRoutine()); assertEquals(null, QUERY_STATISTICS_INCOMPLETE.getEstimatedBytesProcessed()); assertEquals(null, QUERY_STATISTICS_INCOMPLETE.getNumDmlAffectedRows()); assertEquals(null, QUERY_STATISTICS_INCOMPLETE.getTotalBytesBilled()); @@ -284,6 +288,7 @@ private void compareQueryStatistics(QueryStatistics expected, QueryStatistics va assertEquals(expected.getCacheHit(), value.getCacheHit()); assertEquals(expected.getDdlOperationPerformed(), value.getDdlOperationPerformed()); assertEquals(expected.getDdlTargetTable(), value.getDdlTargetTable()); + assertEquals(expected.getDdlTargetRoutine(), value.getDdlTargetRoutine()); assertEquals(expected.getEstimatedBytesProcessed(), value.getEstimatedBytesProcessed()); assertEquals(expected.getTotalBytesBilled(), value.getTotalBytesBilled()); assertEquals(expected.getTotalBytesProcessed(), value.getTotalBytesProcessed()); diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineArgumentTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineArgumentTest.java new file mode 100644 index 000000000000..909d5981d7f9 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineArgumentTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 Google LLC + * + * 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 org.junit.Test; + +public class RoutineArgumentTest { + + private static final String NAME = "foo"; + private static final String KIND = "SCALAR_FUNCTION"; + private static final String MODE = "IN"; + private static final StandardSQLDataType DATA_TYPE = + StandardSQLDataType.newBuilder("STRING").build(); + private static final RoutineArgument ARGUMENT = + RoutineArgument.newBuilder() + .setName(NAME) + .setKind(KIND) + .setMode(MODE) + .setDataType(DATA_TYPE) + .build(); + + @Test + public void testToBuilder() { + compareRoutineArguments(ARGUMENT, ARGUMENT.toBuilder().build()); + } + + @Test + public void testBuilder() { + assertEquals(NAME, ARGUMENT.getName()); + assertEquals(KIND, ARGUMENT.getKind()); + assertEquals(MODE, ARGUMENT.getMode()); + assertEquals(DATA_TYPE, ARGUMENT.getDataType()); + } + + @Test + public void testToPbAndFromPb() { + compareRoutineArguments(ARGUMENT, RoutineArgument.fromPb(ARGUMENT.toPb())); + } + + public void compareRoutineArguments(RoutineArgument expected, RoutineArgument value) { + assertEquals(expected, value); + assertEquals(expected.getName(), value.getName()); + assertEquals(expected.getKind(), value.getKind()); + assertEquals(expected.getMode(), value.getMode()); + assertEquals(expected.getDataType(), value.getDataType()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineIdTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineIdTest.java new file mode 100644 index 000000000000..94a19fbfd573 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineIdTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google LLC + * + * 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 org.junit.Test; + +public class RoutineIdTest { + + public static final RoutineId ROUTINE = RoutineId.of("dataset", "routine"); + public static final RoutineId ROUTINE_COMPLETE = RoutineId.of("project", "dataset", "routine"); + + @Test + public void testOf() { + assertEquals(null, ROUTINE.getProject()); + assertEquals("dataset", ROUTINE.getDataset()); + assertEquals("routine", ROUTINE.getRoutine()); + + assertEquals("project", ROUTINE_COMPLETE.getProject()); + assertEquals("dataset", ROUTINE_COMPLETE.getDataset()); + assertEquals("routine", ROUTINE_COMPLETE.getRoutine()); + } + + @Test + public void testEquals() { + compareRoutineIds(ROUTINE, RoutineId.of("dataset", "routine")); + compareRoutineIds(ROUTINE_COMPLETE, RoutineId.of("project", "dataset", "routine")); + } + + @Test + public void testToPbAndFromPb() { + compareRoutineIds(ROUTINE, RoutineId.fromPb(ROUTINE.toPb())); + compareRoutineIds(ROUTINE_COMPLETE, RoutineId.fromPb(ROUTINE_COMPLETE.toPb())); + } + + @Test + public void testSetProjectId() { + RoutineId differentProjectTable = RoutineId.of("differentProject", "dataset", "routine"); + assertEquals(differentProjectTable, ROUTINE.setProjectId("differentProject")); + } + + private void compareRoutineIds(RoutineId expected, RoutineId value) { + assertEquals(expected, value); + assertEquals(expected.getProject(), value.getProject()); + assertEquals(expected.getDataset(), value.getDataset()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineInfoTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineInfoTest.java new file mode 100644 index 000000000000..866daf82d0fa --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineInfoTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; + +public class RoutineInfoTest { + + private static final RoutineId ROUTINE_ID = RoutineId.of("dataset", "routine"); + private static final String ETAG = "etag"; + private static final String ROUTINE_TYPE = "SCALAR_FUNCTION"; + private static final Long CREATION_TIME = 10L; + private static final Long LAST_MODIFIED_TIME = 20L; + private static final String LANGUAGE = "SQL"; + + private static final RoutineArgument ARG_1 = + RoutineArgument.newBuilder() + .setDataType(StandardSQLDataType.newBuilder("STRING").build()) + .setName("arg1") + .build(); + + private static final List ARGUMENT_LIST = ImmutableList.of(ARG_1); + + private static final StandardSQLDataType RETURN_TYPE = + StandardSQLDataType.newBuilder("FLOAT64").build(); + + private static final List IMPORTED_LIBRARIES = + ImmutableList.of("gs://foo", "gs://bar", "gs://baz"); + + private static final String BODY = "body"; + + private static final RoutineInfo ROUTINE_INFO = + RoutineInfo.of(ROUTINE_ID) + .toBuilder() + .setEtag(ETAG) + .setRoutineType(ROUTINE_TYPE) + .setCreationTime(CREATION_TIME) + .setLastModifiedTime(LAST_MODIFIED_TIME) + .setLanguage(LANGUAGE) + .setArguments(ARGUMENT_LIST) + .setReturnType(RETURN_TYPE) + .setImportedLibraries(IMPORTED_LIBRARIES) + .setBody(BODY) + .build(); + + @Test + public void testToBuilder() { + compareRoutineInfo(ROUTINE_INFO, ROUTINE_INFO.toBuilder().build()); + } + + @Test + public void testBuilderIncomplete() { + RoutineInfo routineInfo = RoutineInfo.of(ROUTINE_ID); + assertEquals(routineInfo, routineInfo.toBuilder().build()); + } + + @Test + public void testBuilder() { + assertEquals(ROUTINE_ID, ROUTINE_INFO.getRoutineId()); + assertEquals(ETAG, ROUTINE_INFO.getEtag()); + assertEquals(ROUTINE_TYPE, ROUTINE_INFO.getRoutineType()); + assertEquals(CREATION_TIME, ROUTINE_INFO.getCreationTime()); + assertEquals(LAST_MODIFIED_TIME, ROUTINE_INFO.getLastModifiedTime()); + assertEquals(LANGUAGE, ROUTINE_INFO.getLanguage()); + assertEquals(ARGUMENT_LIST, ROUTINE_INFO.getArguments()); + assertEquals(RETURN_TYPE, ROUTINE_INFO.getReturnType()); + assertEquals(IMPORTED_LIBRARIES, ROUTINE_INFO.getImportedLibraries()); + assertEquals(BODY, ROUTINE_INFO.getBody()); + } + + @Test + public void testOf() { + RoutineInfo routineInfo = RoutineInfo.of(ROUTINE_ID); + assertEquals(ROUTINE_ID, ROUTINE_INFO.getRoutineId()); + assertNull(routineInfo.getEtag()); + assertNull(routineInfo.getRoutineType()); + assertNull(routineInfo.getCreationTime()); + assertNull(routineInfo.getLastModifiedTime()); + assertNull(routineInfo.getLanguage()); + assertNull(routineInfo.getArguments()); + assertNull(routineInfo.getReturnType()); + assertNull(routineInfo.getImportedLibraries()); + assertNull(routineInfo.getBody()); + } + + public void testToAndFromPb() { + compareRoutineInfo(ROUTINE_INFO, RoutineInfo.fromPb(ROUTINE_INFO.toPb())); + } + + @Test + public void testSetProjectId() { + assertEquals("project", ROUTINE_INFO.setProjectId("project").getRoutineId().getProject()); + } + + public void compareRoutineInfo(RoutineInfo expected, RoutineInfo value) { + assertEquals(expected, value); + assertEquals(expected.getRoutineId(), value.getRoutineId()); + assertEquals(expected.getEtag(), value.getEtag()); + assertEquals(expected.getRoutineType(), value.getRoutineType()); + assertEquals(expected.getCreationTime(), value.getCreationTime()); + assertEquals(expected.getLastModifiedTime(), value.getLastModifiedTime()); + assertEquals(expected.getLanguage(), value.getLanguage()); + assertEquals(expected.getArguments(), value.getArguments()); + assertEquals(expected.getReturnType(), value.getReturnType()); + assertEquals(expected.getImportedLibraries(), value.getImportedLibraries()); + assertEquals(expected.getBody(), value.getBody()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineTest.java new file mode 100644 index 000000000000..36301aae58fd --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/RoutineTest.java @@ -0,0 +1,226 @@ +/* + * Copyright 2019 Google LLC + * + * 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.easymock.EasyMock.*; +import static org.easymock.EasyMock.replay; +import static org.junit.Assert.*; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; + +public class RoutineTest { + + private BigQuery serviceMockReturnsOptions = createStrictMock(BigQuery.class); + private BigQueryOptions mockOptions = createMock(BigQueryOptions.class); + + private static final RoutineId ROUTINE_ID = RoutineId.of("dataset", "routine"); + private static final String ETAG = "etag"; + private static final String ROUTINE_TYPE = "SCALAR_FUNCTION"; + private static final Long CREATION_TIME = 10L; + private static final Long LAST_MODIFIED_TIME = 20L; + private static final String LANGUAGE = "SQL"; + + private static final RoutineArgument ARG_1 = + RoutineArgument.newBuilder() + .setDataType(StandardSQLDataType.newBuilder("STRING").build()) + .setName("arg1") + .build(); + + private static final List ARGUMENT_LIST = ImmutableList.of(ARG_1); + + private static final StandardSQLDataType RETURN_TYPE = + StandardSQLDataType.newBuilder("FLOAT64").build(); + + private static final List IMPORTED_LIBRARIES = + ImmutableList.of("gs://foo", "gs://bar", "gs://baz"); + + private static final String BODY = "body"; + + private static final RoutineInfo ROUTINE_INFO = + RoutineInfo.newBuilder(ROUTINE_ID) + .setEtag(ETAG) + .setRoutineType(ROUTINE_TYPE) + .setCreationTime(CREATION_TIME) + .setLastModifiedTime(LAST_MODIFIED_TIME) + .setLanguage(LANGUAGE) + .setArguments(ARGUMENT_LIST) + .setReturnType(RETURN_TYPE) + .setImportedLibraries(IMPORTED_LIBRARIES) + .setBody(BODY) + .build(); + + private BigQuery bigquery; + private Routine expectedRoutine; + private Routine routine; + + private void initializeExpectedRoutine(int optionsCalls) { + expect(serviceMockReturnsOptions.getOptions()).andReturn(mockOptions).times(optionsCalls); + replay(serviceMockReturnsOptions); + bigquery = createStrictMock(BigQuery.class); + expectedRoutine = + new Routine(serviceMockReturnsOptions, new RoutineInfo.BuilderImpl(ROUTINE_INFO)); + } + + private void initializeRoutine() { + routine = new Routine(bigquery, new RoutineInfo.BuilderImpl(ROUTINE_INFO)); + } + + private void tearDown() throws Exception { + verify(bigquery, serviceMockReturnsOptions); + } + + @Test + public void testBuilder() { + initializeExpectedRoutine(2); + replay(bigquery); + Routine builtRoutine = + new Routine.Builder(serviceMockReturnsOptions, ROUTINE_ID) + .setEtag(ETAG) + .setRoutineType(ROUTINE_TYPE) + .setCreationTime(CREATION_TIME) + .setLastModifiedTime(LAST_MODIFIED_TIME) + .setLanguage(LANGUAGE) + .setArguments(ARGUMENT_LIST) + .setReturnType(RETURN_TYPE) + .setImportedLibraries(IMPORTED_LIBRARIES) + .setBody(BODY) + .build(); + assertEquals(ETAG, builtRoutine.getEtag()); + assertSame(serviceMockReturnsOptions, builtRoutine.getBigQuery()); + } + + @Test + public void testToBuilder() { + initializeExpectedRoutine(2); + replay(bigquery); + compareRoutineInfo(expectedRoutine, expectedRoutine.toBuilder().build()); + } + + @Test + public void testExists_True() throws Exception { + initializeExpectedRoutine(1); + BigQuery.RoutineOption[] expectedOptions = {BigQuery.RoutineOption.fields()}; + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.getRoutine(ROUTINE_INFO.getRoutineId(), expectedOptions)).andReturn(null); + replay(bigquery); + initializeRoutine(); + assertFalse(routine.exists()); + } + + @Test + public void testExists_False() throws Exception { + initializeExpectedRoutine(1); + BigQuery.RoutineOption[] expectedOptions = {BigQuery.RoutineOption.fields()}; + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.getRoutine(ROUTINE_INFO.getRoutineId(), expectedOptions)) + .andReturn(expectedRoutine); + replay(bigquery); + initializeRoutine(); + assertTrue(routine.exists()); + } + + @Test + public void testReload() throws Exception { + initializeExpectedRoutine(4); + RoutineInfo updatedInfo = ROUTINE_INFO.toBuilder().setBody("body2").build(); + Routine expectedRoutine = + new Routine(serviceMockReturnsOptions, new RoutineInfo.BuilderImpl(updatedInfo)); + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.getRoutine(ROUTINE_INFO.getRoutineId())).andReturn(expectedRoutine); + replay(bigquery); + initializeRoutine(); + Routine updatedRoutine = routine.reload(); + compareRoutine(expectedRoutine, updatedRoutine); + } + + @Test + public void testReload_Null() throws Exception { + initializeExpectedRoutine(1); + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.getRoutine(ROUTINE_INFO.getRoutineId())).andReturn(null); + replay(bigquery); + initializeRoutine(); + assertNull(routine.reload()); + } + + @Test + public void testUpdate() { + initializeExpectedRoutine(4); + Routine expectedUpdatedRoutine = expectedRoutine.toBuilder().setBody("body2").build(); + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.update(eq(expectedRoutine))).andReturn(expectedUpdatedRoutine); + replay(bigquery); + initializeRoutine(); + Routine actualUpdatedRoutine = routine.update(); + compareRoutine(expectedUpdatedRoutine, actualUpdatedRoutine); + } + + @Test + public void testUpdateWithOptions() { + initializeExpectedRoutine(4); + Routine expectedUpdatedRoutine = expectedRoutine.toBuilder().setBody("body2").build(); + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.update(eq(expectedRoutine), eq(BigQuery.RoutineOption.fields()))) + .andReturn(expectedUpdatedRoutine); + replay(bigquery); + initializeRoutine(); + Routine actualUpdatedRoutine = routine.update(BigQuery.RoutineOption.fields()); + compareRoutine(expectedUpdatedRoutine, actualUpdatedRoutine); + } + + @Test + public void testDeleteTrue() { + initializeExpectedRoutine(1); + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.delete(ROUTINE_INFO.getRoutineId())).andReturn(true); + replay(bigquery); + initializeRoutine(); + assertTrue(routine.delete()); + } + + @Test + public void testDeleteFalse() { + initializeExpectedRoutine(1); + expect(bigquery.getOptions()).andReturn(mockOptions); + expect(bigquery.delete(ROUTINE_INFO.getRoutineId())).andReturn(false); + replay(bigquery); + initializeRoutine(); + assertFalse(routine.delete()); + } + + private void compareRoutine(Routine expected, Routine value) { + assertEquals(expected, value); + compareRoutineInfo(expected, value); + assertEquals(expected.getBigQuery().getOptions(), value.getBigQuery().getOptions()); + } + + public void compareRoutineInfo(RoutineInfo expected, RoutineInfo value) { + assertEquals(expected, value); + assertEquals(expected.getRoutineId(), value.getRoutineId()); + assertEquals(expected.getEtag(), value.getEtag()); + assertEquals(expected.getRoutineType(), value.getRoutineType()); + assertEquals(expected.getCreationTime(), value.getCreationTime()); + assertEquals(expected.getLastModifiedTime(), value.getLastModifiedTime()); + assertEquals(expected.getLanguage(), value.getLanguage()); + assertEquals(expected.getArguments(), value.getArguments()); + assertEquals(expected.getReturnType(), value.getReturnType()); + assertEquals(expected.getImportedLibraries(), value.getImportedLibraries()); + assertEquals(expected.getBody(), value.getBody()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLDataTypeTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLDataTypeTest.java new file mode 100644 index 000000000000..635a75612b94 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLDataTypeTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; + +public class StandardSQLDataTypeTest { + private static final String STRING_TYPEKIND = "STRING"; + private static final String ARRAY_TYPEKIND = "ARRAY"; + private static final String STRUCT_TYPEKIND = "STRUCT"; + + private static final StandardSQLDataType STRING_DATA_TYPE = + StandardSQLDataType.newBuilder(STRING_TYPEKIND).build(); + private static final StandardSQLDataType ARRAY_OF_STRING_DATA_TYPE = + StandardSQLDataType.newBuilder(ARRAY_TYPEKIND).setArrayElementType(STRING_DATA_TYPE).build(); + + private static final List FIELD_LIST = + ImmutableList.of( + StandardSQLField.newBuilder(STRING_DATA_TYPE).build(), + StandardSQLField.newBuilder(ARRAY_OF_STRING_DATA_TYPE).build()); + private static final StandardSQLStructType STRUCT_TYPE = + StandardSQLStructType.newBuilder(FIELD_LIST).build(); + + private static final StandardSQLDataType STRUCT_DATA_TYPE = + StandardSQLDataType.newBuilder(STRUCT_TYPEKIND).setStructType(STRUCT_TYPE).build(); + + @Test + public void testToBuilder() { + compareStandardSQLDataType(STRING_DATA_TYPE, STRING_DATA_TYPE.toBuilder().build()); + compareStandardSQLDataType( + ARRAY_OF_STRING_DATA_TYPE, ARRAY_OF_STRING_DATA_TYPE.toBuilder().build()); + compareStandardSQLDataType(STRUCT_DATA_TYPE, STRUCT_DATA_TYPE.toBuilder().build()); + } + + @Test + public void testBuilder() { + assertEquals(STRING_TYPEKIND, STRING_DATA_TYPE.getTypeKind()); + assertEquals(ARRAY_TYPEKIND, ARRAY_OF_STRING_DATA_TYPE.getTypeKind()); + assertEquals(STRING_DATA_TYPE, ARRAY_OF_STRING_DATA_TYPE.getArrayElementType()); + assertEquals(STRUCT_TYPE, STRUCT_DATA_TYPE.getStructType()); + } + + @Test + public void testToAndFromPb() { + compareStandardSQLDataType( + ARRAY_OF_STRING_DATA_TYPE, StandardSQLDataType.fromPb(ARRAY_OF_STRING_DATA_TYPE.toPb())); + } + + private void compareStandardSQLDataType(StandardSQLDataType expected, StandardSQLDataType value) { + assertEquals(expected, value); + assertEquals(expected.getTypeKind(), value.getTypeKind()); + assertEquals(expected.getArrayElementType(), value.getArrayElementType()); + assertEquals(expected.getStructType(), value.getStructType()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLFieldTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLFieldTest.java new file mode 100644 index 000000000000..904ed80280f6 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLFieldTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Google LLC + * + * 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 org.junit.Test; + +public class StandardSQLFieldTest { + + private static final String NAME = "field_name"; + private static final StandardSQLDataType STRING_DATA_TYPE = + StandardSQLDataType.newBuilder("STRING").build(); + private static final StandardSQLDataType ARRAY_OF_STRING_DATA_TYPE = + StandardSQLDataType.newBuilder("ARRAY").setArrayElementType(STRING_DATA_TYPE).build(); + private static final StandardSQLField STANDARD_SQL_FIELD_1 = + StandardSQLField.newBuilder(STRING_DATA_TYPE).build(); + private static final StandardSQLField STANDARD_SQL_FIELD_2 = + StandardSQLField.newBuilder(NAME, ARRAY_OF_STRING_DATA_TYPE).build(); + + @Test + public void testToBuilder() { + compareStandardSQLField(STANDARD_SQL_FIELD_1, STANDARD_SQL_FIELD_1.toBuilder().build()); + compareStandardSQLField(STANDARD_SQL_FIELD_2, STANDARD_SQL_FIELD_2.toBuilder().build()); + } + + @Test + public void testBuilder() { + assertEquals(null, STANDARD_SQL_FIELD_1.getName()); + assertEquals(STRING_DATA_TYPE, STANDARD_SQL_FIELD_1.getDataType()); + assertEquals(ARRAY_OF_STRING_DATA_TYPE, STANDARD_SQL_FIELD_2.getDataType()); + } + + @Test + public void testToAndFromPb() { + compareStandardSQLField( + STANDARD_SQL_FIELD_1, StandardSQLField.fromPb(STANDARD_SQL_FIELD_1.toPb())); + } + + private void compareStandardSQLField(StandardSQLField expected, StandardSQLField value) { + assertEquals(expected, value); + assertEquals(expected.getName(), value.getName()); + assertEquals(expected.getDataType(), value.getDataType()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLStructTypeTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLStructTypeTest.java new file mode 100644 index 000000000000..d4fa86950f02 --- /dev/null +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/StandardSQLStructTypeTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; + +public class StandardSQLStructTypeTest { + + private static final StandardSQLField FIELD_1 = + StandardSQLField.newBuilder("FIELD_1", StandardSQLDataType.newBuilder("STRING").build()) + .build(); + private static final StandardSQLField FIELD_2 = + StandardSQLField.newBuilder("FIELD_2", StandardSQLDataType.newBuilder("FLOAT64").build()) + .build(); + + private static final List FIELD_LIST = ImmutableList.of(FIELD_1, FIELD_2); + private static final StandardSQLStructType STRUCT_TYPE = + StandardSQLStructType.newBuilder(FIELD_LIST).build(); + + @Test + public void testToBuilder() { + compareStandardSQLStructType(STRUCT_TYPE, STRUCT_TYPE.toBuilder().build()); + } + + @Test + public void testBuilder() { + assertEquals(FIELD_1, STRUCT_TYPE.getFields().get(0)); + assertEquals(FIELD_2, STRUCT_TYPE.getFields().get(1)); + } + + @Test + public void testToAndFromPb() { + compareStandardSQLStructType(STRUCT_TYPE, StandardSQLStructType.fromPb(STRUCT_TYPE.toPb())); + } + + private void compareStandardSQLStructType( + StandardSQLStructType expected, StandardSQLStructType value) { + assertEquals(expected, value); + assertEquals(expected.getFields(), value.getFields()); + assertEquals(expected.hashCode(), value.hashCode()); + } +} diff --git a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java index 7ce38134d5a5..bb744d63ec59 100644 --- a/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java +++ b/google-cloud-clients/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java @@ -65,7 +65,12 @@ import com.google.cloud.bigquery.ModelInfo; import com.google.cloud.bigquery.QueryJobConfiguration; import com.google.cloud.bigquery.QueryParameterValue; +import com.google.cloud.bigquery.Routine; +import com.google.cloud.bigquery.RoutineArgument; +import com.google.cloud.bigquery.RoutineId; +import com.google.cloud.bigquery.RoutineInfo; import com.google.cloud.bigquery.Schema; +import com.google.cloud.bigquery.StandardSQLDataType; import com.google.cloud.bigquery.StandardTableDefinition; import com.google.cloud.bigquery.Table; import com.google.cloud.bigquery.TableDataWriteChannel; @@ -122,6 +127,7 @@ public class ITBigQueryTest { private static final String DESCRIPTION = "Test dataset"; private static final String OTHER_DATASET = RemoteBigQueryHelper.generateDatasetName(); private static final String MODEL_DATASET = RemoteBigQueryHelper.generateDatasetName(); + private static final String ROUTINE_DATASET = RemoteBigQueryHelper.generateDatasetName(); private static final Map LABELS = ImmutableMap.of( "example-label1", "example-value1", @@ -286,6 +292,9 @@ public static void beforeClass() throws InterruptedException, TimeoutException { DatasetInfo info2 = DatasetInfo.newBuilder(MODEL_DATASET).setDescription("java model lifecycle").build(); bigquery.create(info2); + DatasetInfo info3 = + DatasetInfo.newBuilder(ROUTINE_DATASET).setDescription("java routine lifecycle").build(); + bigquery.create(info3); LoadJobConfiguration configuration = LoadJobConfiguration.newBuilder( TABLE_ID, "gs://" + BUCKET + "/" + JSON_LOAD_FILE, FormatOptions.json()) @@ -302,6 +311,7 @@ public static void afterClass() throws ExecutionException, InterruptedException if (bigquery != null) { RemoteBigQueryHelper.forceDelete(bigquery, DATASET); RemoteBigQueryHelper.forceDelete(bigquery, MODEL_DATASET); + RemoteBigQueryHelper.forceDelete(bigquery, ROUTINE_DATASET); } if (storage != null) { boolean wasDeleted = RemoteStorageHelper.forceDelete(storage, BUCKET, 10, TimeUnit.SECONDS); @@ -1109,6 +1119,73 @@ public void testModelLifecycle() throws InterruptedException { assertTrue(bigquery.delete(modelId)); } + @Test + public void testRoutineLifecycle() throws InterruptedException { + + String routineName = RemoteBigQueryHelper.generateRoutineName(); + // Create a routine using SQL. + String sql = + "CREATE FUNCTION `" + ROUTINE_DATASET + "." + routineName + "`" + "(x INT64) AS (x * 3)"; + QueryJobConfiguration config = QueryJobConfiguration.newBuilder(sql).build(); + Job job = bigquery.create(JobInfo.of(JobId.of(), config)); + job.waitFor(); + assertNull(job.getStatus().getError()); + + // Routine is created. Fetch. + RoutineId routineId = RoutineId.of(ROUTINE_DATASET, routineName); + Routine routine = bigquery.getRoutine(routineId); + assertNotNull(routine); + assertEquals(routine.getRoutineType(), "SCALAR_FUNCTION"); + + // Mutate metadata. + RoutineInfo newInfo = + routine + .toBuilder() + .setBody("x * 4") + .setReturnType(routine.getReturnType()) + .setArguments(routine.getArguments()) + .setRoutineType(routine.getRoutineType()) + .build(); + Routine afterUpdate = bigquery.update(newInfo); + assertEquals(afterUpdate.getBody(), "x * 4"); + + // Ensure routine is present in listRoutines. + Page routines = bigquery.listRoutines(ROUTINE_DATASET); + Boolean found = false; + for (Routine r : routines.getValues()) { + if (r.getRoutineId().getRoutine().equals(routineName)) { + found = true; + break; + } + } + assertTrue(found); + + // Delete the routine. + assertTrue(bigquery.delete(routineId)); + } + + @Test + public void testRoutineAPICreation() { + String routineName = RemoteBigQueryHelper.generateRoutineName(); + RoutineId routineId = RoutineId.of(ROUTINE_DATASET, routineName); + RoutineInfo routineInfo = + RoutineInfo.newBuilder(routineId) + .setRoutineType("SCALAR_FUNCTION") + .setBody("x * 3") + .setLanguage("SQL") + .setArguments( + ImmutableList.of( + RoutineArgument.newBuilder() + .setName("x") + .setDataType(StandardSQLDataType.newBuilder("INT64").build()) + .build())) + .build(); + + Routine routine = bigquery.create(routineInfo); + assertNotNull(routine); + assertEquals(routine.getRoutineType(), "SCALAR_FUNCTION"); + } + @Test public void testQuery() throws InterruptedException { String query = "SELECT TimestampField, StringField, BooleanField FROM " + TABLE_ID.getTable();