Skip to content

Commit

Permalink
test: Add test proxy implementation for ExecuteQuery api (#2360)
Browse files Browse the repository at this point in the history
* test: Test proxy support SSL backend and ExecuteQuery

Change-Id: I39f81dcf098b93eff79973648c57515e8908fde3

* test: Support query params in test proxy

Change-Id: I53122fdf0301fb41d4f24881dc8de65d174dee35

* Remove security options for a separate PR

Change-Id: Ibe2fb899f83744d4e38debc58379e9942350a83f

---------

Co-authored-by: Patrick Wrobel <[email protected]>
  • Loading branch information
jackdingilian and Patrick Wrobel authored Oct 15, 2024
1 parent 013c22d commit be62968
Show file tree
Hide file tree
Showing 4 changed files with 504 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Google LLC
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -41,6 +41,7 @@
import com.google.cloud.bigtable.data.v2.models.ReadModifyWriteRow;
import com.google.cloud.bigtable.data.v2.models.RowCell;
import com.google.cloud.bigtable.data.v2.models.RowMutation;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSet;
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings;
import com.google.cloud.bigtable.testproxy.CloudBigtableV2TestProxyGrpc.CloudBigtableV2TestProxyImplBase;
import com.google.common.base.Preconditions;
Expand All @@ -50,6 +51,7 @@
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
Expand All @@ -65,6 +67,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -159,6 +162,8 @@ private static BigtableDataSettings.Builder overrideTimeoutSetting(
settingsBuilder.stubSettings().readModifyWriteRowSettings().retrySettings(), newTimeout);
updateTimeout(
settingsBuilder.stubSettings().sampleRowKeysSettings().retrySettings(), newTimeout);
updateTimeout(
settingsBuilder.stubSettings().executeQuerySettings().retrySettings(), newTimeout);

return settingsBuilder;
}
Expand Down Expand Up @@ -698,6 +703,64 @@ public void readModifyWriteRow(
responseObserver.onCompleted();
}

@Override
public void executeQuery(
ExecuteQueryRequest request, StreamObserver<ExecuteQueryResult> responseObserver) {
CbtClient client;
try {
client = getClient(request.getClientId());
} catch (StatusException e) {
responseObserver.onError(e);
return;
}
try (ResultSet resultSet =
client.dataClient().executeQuery(StatementDeserializer.toStatement(request))) {
responseObserver.onNext(ResultSetSerializer.toExecuteQueryResult(resultSet));
} catch (InterruptedException e) {
responseObserver.onError(e);
return;
} catch (ExecutionException e) {
responseObserver.onError(e);
return;
} catch (ApiException e) {
responseObserver.onNext(
ExecuteQueryResult.newBuilder()
.setStatus(
com.google.rpc.Status.newBuilder()
.setCode(e.getStatusCode().getCode().ordinal())
.setMessage(e.getMessage())
.build())
.build());
responseObserver.onCompleted();
return;
} catch (StatusRuntimeException e) {
responseObserver.onNext(
ExecuteQueryResult.newBuilder()
.setStatus(
com.google.rpc.Status.newBuilder()
.setCode(e.getStatus().getCode().value())
.setMessage(e.getStatus().getDescription())
.build())
.build());
responseObserver.onCompleted();
return;
} catch (RuntimeException e) {
// If client encounters problem, don't return any results.
responseObserver.onNext(
ExecuteQueryResult.newBuilder()
.setStatus(
com.google.rpc.Status.newBuilder()
.setCode(Code.INTERNAL.getNumber())
.setMessage(e.getMessage())
.build())
.build());
responseObserver.onCompleted();
return;
}
responseObserver.onCompleted();
return;
}

@Override
public synchronized void close() {
Iterator<Map.Entry<String, CbtClient>> it = idClientMap.entrySet().iterator();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* Copyright 2024 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
*
* https://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.bigtable.testproxy;

import com.google.bigtable.v2.ArrayValue;
import com.google.bigtable.v2.Type;
import com.google.bigtable.v2.Type.Array;
import com.google.bigtable.v2.Type.Bool;
import com.google.bigtable.v2.Type.Bytes;
import com.google.bigtable.v2.Type.Float32;
import com.google.bigtable.v2.Type.Float64;
import com.google.bigtable.v2.Type.Int64;
import com.google.bigtable.v2.Type.Map;
import com.google.bigtable.v2.Type.Struct;
import com.google.bigtable.v2.Type.Timestamp;
import com.google.bigtable.v2.Value;
import com.google.cloud.Date;
import com.google.cloud.bigtable.data.v2.models.sql.ColumnMetadata;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSet;
import com.google.cloud.bigtable.data.v2.models.sql.SqlType;
import com.google.cloud.bigtable.data.v2.models.sql.StructReader;
import com.google.protobuf.ByteString;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.threeten.bp.Instant;

public class ResultSetSerializer {
public static ExecuteQueryResult toExecuteQueryResult(ResultSet resultSet)
throws ExecutionException, InterruptedException {
ExecuteQueryResult.Builder resultBuilder = ExecuteQueryResult.newBuilder();
for (ColumnMetadata columnMetadata : resultSet.getMetadata().getColumns()) {
resultBuilder
.getMetadataBuilder()
.addColumnsBuilder()
.setName(columnMetadata.name())
.setType(toProtoType(columnMetadata.type()));
}

while (resultSet.next()) {
SqlRow.Builder rowBuilder = resultBuilder.addRowsBuilder();

for (int i = 0; i < resultSet.getMetadata().getColumns().size(); i++) {
SqlType<?> colType = resultSet.getMetadata().getColumnType(i);
rowBuilder.addValues(toProtoValue(getColumn(resultSet, i, colType), colType));
}
}

return resultBuilder.build();
}

private static Value toProtoValue(Object value, SqlType<?> type) {
if (value == null) {
return Value.getDefaultInstance();
}

Value.Builder valueBuilder = Value.newBuilder();
switch (type.getCode()) {
case BYTES:
valueBuilder.setBytesValue((ByteString) value);
break;
case STRING:
valueBuilder.setStringValue((String) value);
break;
case INT64:
valueBuilder.setIntValue((Long) value);
break;
case FLOAT32:
valueBuilder.setFloatValue((Float) value);
break;
case FLOAT64:
valueBuilder.setFloatValue((Double) value);
break;
case BOOL:
valueBuilder.setBoolValue((Boolean) value);
break;
case TIMESTAMP:
Instant ts = (Instant) value;
valueBuilder.setTimestampValue(
com.google.protobuf.Timestamp.newBuilder()
.setSeconds(ts.getEpochSecond())
.setNanos(ts.getNano())
.build());
break;
case DATE:
Date date = (Date) value;
valueBuilder.setDateValue(
com.google.type.Date.newBuilder()
.setYear(date.getYear())
.setMonth(date.getMonth())
.setDay(date.getDayOfMonth())
.build());
break;
case ARRAY:
SqlType<?> elementType = ((SqlType.Array<?>) type).getElementType();
ArrayValue.Builder arrayValue = ArrayValue.newBuilder();
for (Object item : (List<?>) value) {
arrayValue.addValues(toProtoValue(item, elementType));
}
valueBuilder.setArrayValue(arrayValue.build());
break;
case MAP:
SqlType.Map<?, ?> mapType = (SqlType.Map<?, ?>) type;
SqlType<?> mapKeyType = mapType.getKeyType();
SqlType<?> mapValueType = mapType.getValueType();

ArrayValue.Builder mapArrayValue = ArrayValue.newBuilder();
((java.util.Map<?, ?>) value)
.forEach(
(k, v) ->
mapArrayValue.addValues(
Value.newBuilder()
.setArrayValue(
ArrayValue.newBuilder()
.addValues(toProtoValue(k, mapKeyType))
.addValues(toProtoValue(v, mapValueType))
.build())));
valueBuilder.setArrayValue(mapArrayValue.build());
break;
case STRUCT:
StructReader structValue = (StructReader) value;
SqlType.Struct structType = (SqlType.Struct) type;
ArrayValue.Builder structArrayValue = ArrayValue.newBuilder();
for (int i = 0; i < structType.getFields().size(); ++i) {
SqlType<?> fieldType = structType.getType(i);
structArrayValue.addValues(toProtoValue(getColumn(structValue, i, fieldType), fieldType));
}
valueBuilder.setArrayValue(structArrayValue);
break;
default:
throw new IllegalStateException("Unexpected Type: " + type);
}

return valueBuilder.build();
}

private static Object getColumn(StructReader struct, int fieldIndex, SqlType<?> fieldType) {
if (struct.isNull(fieldIndex)) {
return null;
}

switch (fieldType.getCode()) {
case ARRAY:
return struct.getList(fieldIndex, (SqlType.Array<?>) fieldType);
case BOOL:
return struct.getBoolean(fieldIndex);
case BYTES:
return struct.getBytes(fieldIndex);
case DATE:
return struct.getDate(fieldIndex);
case FLOAT32:
return struct.getFloat(fieldIndex);
case FLOAT64:
return struct.getDouble(fieldIndex);
case INT64:
return struct.getLong(fieldIndex);
case MAP:
return struct.getMap(fieldIndex, (SqlType.Map<?, ?>) fieldType);
case STRING:
return struct.getString(fieldIndex);
case STRUCT:
return struct.getStruct(fieldIndex);
case TIMESTAMP:
return struct.getTimestamp(fieldIndex);
default:
throw new IllegalStateException("Unexpected Type: " + fieldType);
}
}

private static Type toProtoType(SqlType<?> type) {
switch (type.getCode()) {
case BYTES:
return Type.newBuilder().setBytesType(Bytes.getDefaultInstance()).build();
case STRING:
return Type.newBuilder()
.setStringType(com.google.bigtable.v2.Type.String.getDefaultInstance())
.build();
case INT64:
return Type.newBuilder().setInt64Type(Int64.getDefaultInstance()).build();
case FLOAT32:
return Type.newBuilder().setFloat32Type(Float32.getDefaultInstance()).build();
case FLOAT64:
return Type.newBuilder().setFloat64Type(Float64.getDefaultInstance()).build();
case BOOL:
return Type.newBuilder().setBoolType(Bool.getDefaultInstance()).build();
case TIMESTAMP:
return Type.newBuilder().setTimestampType(Timestamp.getDefaultInstance()).build();
case DATE:
return Type.newBuilder()
.setDateType(com.google.bigtable.v2.Type.Date.getDefaultInstance())
.build();
case ARRAY:
SqlType.Array<?> arrayType = (SqlType.Array<?>) type;
return Type.newBuilder()
.setArrayType(
Array.newBuilder().setElementType(toProtoType(arrayType.getElementType())))
.build();
case MAP:
SqlType.Map<?, ?> mapType = (SqlType.Map<?, ?>) type;
return Type.newBuilder()
.setMapType(
Map.newBuilder()
.setKeyType(toProtoType(mapType.getKeyType()))
.setValueType(toProtoType(mapType.getValueType())))
.build();
case STRUCT:
SqlType.Struct structType = (SqlType.Struct) type;
Struct.Builder structBuilder = Struct.newBuilder();
for (SqlType.Struct.Field field : structType.getFields()) {
structBuilder
.addFieldsBuilder()
.setFieldName(field.name())
.setType(toProtoType(field.type()));
}
return Type.newBuilder().setStructType(structBuilder.build()).build();

default:
throw new IllegalStateException("Unexpected Type: " + type);
}
}
}
Loading

0 comments on commit be62968

Please sign in to comment.