Skip to content

Commit 887d7d0

Browse files
test(bigtable): implement ExecuteQuery_FailsOnTypeMismatch* tests (#15772)
1 parent c7ec2dc commit 887d7d0

File tree

6 files changed

+465
-14
lines changed

6 files changed

+465
-14
lines changed

google/cloud/bigtable/ci/run_conformance_tests_proxy_bazel.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ exit_status=$?
6666
#exit_status=$?
6767

6868
# Response/Metadata mismatches b/461233335
69-
#go test -v \
70-
# -run "FailsOnTypeMismatch|FailsOnTypeMismatchWithinMap|FailsOnTypeMismatchWithinArray|FailsOnTypeMismatchWithinStruct|FailsOnStructMissingField" \
71-
# -proxy_addr=:9999
72-
#exit_status=$?
69+
go test -v \
70+
-run "FailsOnTypeMismatch|FailsOnTypeMismatchWithinMap|FailsOnTypeMismatchWithinArray|FailsOnTypeMismatchWithinStruct|FailsOnStructMissingField" \
71+
-proxy_addr=:9999
72+
exit_status=$?
7373

7474
# QueryPlan refresh tests b/461233613
7575
#go test -v \

google/cloud/bigtable/internal/data_connection_impl.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ bool IsStatusMetadataIndicatingRetryPolicyExhausted(Status const& status) {
111111
"retry-policy-exhausted"));
112112
}
113113

114+
bool IsStatusIndicatingInternalError(Status const& status) {
115+
return status.code() == StatusCode::kInternal;
116+
}
117+
114118
class DefaultPartialResultSetReader
115119
: public bigtable_internal::PartialResultSetReader {
116120
public:
@@ -962,6 +966,11 @@ bigtable::RowStream DataConnectionImpl::ExecuteQuery(
962966
}
963967
last_status = source.status();
964968

969+
if (IsStatusIndicatingInternalError(source.status())) {
970+
return bigtable::RowStream(std::make_unique<StatusOnlyResultSetSource>(
971+
std::move(last_status)));
972+
}
973+
965974
if (QueryPlanRefreshRetry::IsQueryPlanExpired(source.status())) {
966975
query_plan->Invalidate(source.status(),
967976
query_plan_data->prepared_query());

google/cloud/bigtable/internal/partial_result_set_source.cc

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ PartialResultSetSource::Create(
5252
// Do an initial read from the stream to determine the fate of the factory.
5353
auto status = source->ReadFromStream();
5454

55-
// If the initial read finished the stream, and `Finish()` failed, then
56-
// creating the `PartialResultSetSource` should fail with the same error.
57-
if (source->state_ == State::kFinished && !status.ok()) return status;
55+
// Any error during parsing will be returned.
56+
if (!status.ok()) {
57+
return status;
58+
}
5859

5960
return {std::move(source)};
6061
}
@@ -228,6 +229,11 @@ Status PartialResultSetSource::BufferProtoRows() {
228229

229230
while (parsed_value != proto_values.end()) {
230231
for (auto const& column : proto_schema.columns()) {
232+
auto type_value_match_result =
233+
bigtable::Value::TypeAndValuesMatch(column.type(), *parsed_value);
234+
if (!type_value_match_result.ok()) {
235+
return type_value_match_result;
236+
}
231237
auto value = FromProto(column.type(), *parsed_value);
232238
values.push_back(std::move(value));
233239
++parsed_value;

google/cloud/bigtable/value.cc

Lines changed: 157 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "google/cloud/internal/throw_delegate.h"
1818
#include "absl/container/flat_hash_set.h"
1919
#include "absl/strings/cord.h"
20+
#include "absl/strings/substitute.h"
2021
#include <google/bigtable/v2/types.pb.h>
2122
#include <google/protobuf/descriptor.h>
2223
#include <google/protobuf/message.h>
@@ -207,12 +208,6 @@ bool MapEqual( // NOLINT(misc-no-recursion)
207208
comparison_function);
208209
}
209210

210-
// From the proto description, `NULL` values are represented by having a kind
211-
// equal to KIND_NOT_SET
212-
bool IsNullValue(google::bigtable::v2::Value const& value) {
213-
return value.kind_case() == google::bigtable::v2::Value::KIND_NOT_SET;
214-
}
215-
216211
// A helper to escape all double quotes in the given string `s`. For example,
217212
// if given `"foo"`, outputs `\"foo\"`. This is useful when a caller needs to
218213
// wrap `s` itself in double quotes.
@@ -238,7 +233,7 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
238233
google::bigtable::v2::Value const& v,
239234
google::bigtable::v2::Type const& t,
240235
StreamMode mode) {
241-
if (IsNullValue(v)) {
236+
if (Value::IsNullValue(v)) {
242237
return os << "NULL";
243238
}
244239

@@ -344,6 +339,161 @@ std::ostream& operator<<(std::ostream& os, Value const& v) {
344339
return StreamHelper(os, v.value_, v.type_, StreamMode::kScalar);
345340
}
346341

342+
// NOLINTNEXTLINE(misc-no-recursion)
343+
Status Value::TypeAndArrayValuesMatch(
344+
google::bigtable::v2::Type const& type,
345+
google::bigtable::v2::Value const& value) {
346+
if (!value.has_array_value()) {
347+
return internal::InternalError(
348+
"Value kind must be ARRAY_VALUE for columns of type: MAP");
349+
}
350+
auto const& vals = value.array_value().values();
351+
for (auto const& val : vals) {
352+
auto const element_match_result =
353+
TypeAndValuesMatch(type.array_type().element_type(), val);
354+
if (!element_match_result.ok()) {
355+
return element_match_result;
356+
}
357+
}
358+
return Status{};
359+
}
360+
361+
// NOLINTNEXTLINE(misc-no-recursion)
362+
Status Value::TypeAndMapValuesMatch(google::bigtable::v2::Type const& type,
363+
google::bigtable::v2::Value const& value) {
364+
if (!value.has_array_value()) {
365+
return internal::InternalError(
366+
"Value kind must be ARRAY_VALUE for columns of type: MAP");
367+
}
368+
auto key_type = type.map_type().key_type();
369+
auto value_type = type.map_type().value_type();
370+
auto const& vals = value.array_value().values();
371+
for (auto const& val : vals) {
372+
if (!val.has_array_value() || val.array_value().values_size() != 2) {
373+
return internal::InternalError(
374+
"ARRAY_VALUE must contain entries of 2 values");
375+
}
376+
auto map_key = val.array_value().values(0);
377+
auto map_value = val.array_value().values(1);
378+
// NOLINTNEXTLINE(misc-no-recursion)
379+
auto key_match_result = TypeAndValuesMatch(key_type, map_key);
380+
if (!key_match_result.ok()) {
381+
return key_match_result;
382+
}
383+
// NOLINTNEXTLINE(misc-no-recursion)
384+
auto value_match_result = TypeAndValuesMatch(value_type, map_value);
385+
if (!value_match_result.ok()) {
386+
return value_match_result;
387+
}
388+
}
389+
return Status{};
390+
}
391+
392+
// NOLINTNEXTLINE(misc-no-recursion)
393+
Status Value::TypeAndStructValuesMatch(
394+
google::bigtable::v2::Type const& type,
395+
google::bigtable::v2::Value const& value) {
396+
if (!value.has_array_value()) {
397+
return internal::InternalError(
398+
"Value kind must be ARRAY_VALUE for columns of type: STRUCT");
399+
}
400+
auto fields = type.struct_type().fields();
401+
auto values = value.array_value().values();
402+
if (fields.size() != values.size()) {
403+
auto const message = absl::Substitute(
404+
"received Struct with $0 values, but metadata has $1 fields",
405+
values.size(), fields.size());
406+
return internal::InternalError(message);
407+
}
408+
for (int i = 0; i < fields.size(); ++i) {
409+
auto const& f1 = fields.Get(i);
410+
auto const& v = values[i];
411+
auto match_result = TypeAndValuesMatch(f1.type(), v);
412+
if (!match_result.ok()) {
413+
return match_result;
414+
}
415+
}
416+
return Status{};
417+
}
418+
419+
/**
420+
* Checks whether the declared type in column matches the value's contents.
421+
* Since the received values may or may not have type() set, we check against
422+
* the value contents themselves
423+
*/
424+
// NOLINTNEXTLINE(misc-no-recursion)
425+
Status Value::TypeAndValuesMatch(google::bigtable::v2::Type const& type,
426+
google::bigtable::v2::Value const& value) {
427+
using google::bigtable::v2::Type;
428+
auto make_mismatch_metadata_status = [&](std::string const& value_kind,
429+
std::string const& type_name) {
430+
auto const message = absl::Substitute(
431+
"Value kind must be $0 for columns of type: $1", value_kind, type_name);
432+
return internal::InternalError(message);
433+
};
434+
// Null values are allowed by default
435+
if (IsNullValue(value)) {
436+
return Status{};
437+
}
438+
Status result;
439+
switch (type.kind_case()) {
440+
case Type::kArrayType:
441+
result = TypeAndArrayValuesMatch(type, value);
442+
break;
443+
case Type::kMapType:
444+
result = TypeAndMapValuesMatch(type, value);
445+
break;
446+
case Type::kStructType:
447+
result = TypeAndStructValuesMatch(type, value);
448+
break;
449+
case Type::kBoolType:
450+
if (!value.has_bool_value()) {
451+
result = make_mismatch_metadata_status("BOOL_VALUE", "BOOL");
452+
}
453+
break;
454+
case Type::kBytesType:
455+
if (!value.has_bytes_value()) {
456+
result = make_mismatch_metadata_status("BYTES_VALUE", "BYTES");
457+
}
458+
break;
459+
case Type::kDateType:
460+
if (!value.has_date_value()) {
461+
result = make_mismatch_metadata_status("DATE_VALUE", "DATE");
462+
}
463+
break;
464+
case Type::kFloat32Type:
465+
if (!value.has_float_value()) {
466+
result = make_mismatch_metadata_status("FLOAT_VALUE", "FLOAT32");
467+
}
468+
break;
469+
case Type::kFloat64Type:
470+
if (!value.has_float_value()) {
471+
result = make_mismatch_metadata_status("FLOAT_VALUE", "FLOAT64");
472+
}
473+
break;
474+
case Type::kInt64Type:
475+
if (!value.has_int_value()) {
476+
result = make_mismatch_metadata_status("INT_VALUE", "INT64");
477+
}
478+
break;
479+
case Type::kStringType:
480+
if (!value.has_string_value()) {
481+
result = make_mismatch_metadata_status("STRING_VALUE", "STRING");
482+
}
483+
break;
484+
case Type::kTimestampType:
485+
if (!value.has_timestamp_value()) {
486+
result = make_mismatch_metadata_status("TIMESTAMP_VALUE", "TIMESTAMP");
487+
}
488+
break;
489+
default:
490+
result = internal::InternalError("Unsupported type");
491+
break;
492+
}
493+
// Nulls are allowed;
494+
return result;
495+
}
496+
347497
//
348498
// Value::TypeProtoIs
349499
//

google/cloud/bigtable/value.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,24 @@ class Value {
310310
*/
311311
friend std::ostream& operator<<(std::ostream& os, Value const& v);
312312

313+
// `NULL` values are represented by having a kind equal to KIND_NOT_SET
314+
static bool IsNullValue(google::bigtable::v2::Value const& value) {
315+
return value.kind_case() == google::bigtable::v2::Value::KIND_NOT_SET;
316+
}
317+
318+
static Status TypeAndValuesMatch(google::bigtable::v2::Type const& type,
319+
google::bigtable::v2::Value const& value);
320+
313321
private:
322+
static Status TypeAndArrayValuesMatch(
323+
google::bigtable::v2::Type const& type,
324+
google::bigtable::v2::Value const& value);
325+
static Status TypeAndMapValuesMatch(google::bigtable::v2::Type const& type,
326+
google::bigtable::v2::Value const& value);
327+
static Status TypeAndStructValuesMatch(
328+
google::bigtable::v2::Type const& type,
329+
google::bigtable::v2::Value const& value);
330+
314331
// Metafunction that returns true if `T` is an `absl::optional<U>`
315332
template <typename T>
316333
struct IsOptional : std::false_type {};

0 commit comments

Comments
 (0)