Skip to content

Commit fef647a

Browse files
feat(bigtable): add support for ARRAY (#15567)
* feat(bigtable): add support for ARRAY * chore: adapt to #15562
1 parent 1322718 commit fef647a

File tree

3 files changed

+403
-47
lines changed

3 files changed

+403
-47
lines changed

google/cloud/bigtable/value.cc

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ bool Equal(google::bigtable::v2::Type const& pt1, // NOLINT(misc-no-recursion)
8282
pv1.date_value().month() == pv2.date_value().month() &&
8383
pv1.date_value().year() == pv2.date_value().year();
8484
}
85+
if (pt1.has_array_type()) {
86+
auto const& vec1 = pv1.array_value().values();
87+
auto const& vec2 = pv2.array_value().values();
88+
if (vec1.size() != vec2.size()) {
89+
return false;
90+
}
91+
auto const& el_type1 = pt1.array_type().element_type();
92+
auto const& el_type2 = pt2.array_type().element_type();
93+
if (el_type1.kind_case() != el_type2.kind_case()) {
94+
return false;
95+
}
96+
for (int i = 0; i < vec1.size(); ++i) {
97+
if (!Equal(el_type1, vec1.Get(i), el_type2, vec2.Get(i))) {
98+
return false;
99+
}
100+
}
101+
return true;
102+
}
85103
return false;
86104
}
87105

@@ -91,14 +109,26 @@ bool IsNullValue(google::bigtable::v2::Value const& value) {
91109
return value.kind_case() == google::bigtable::v2::Value::KIND_NOT_SET;
92110
}
93111

112+
// A helper to escape all double quotes in the given string `s`. For example,
113+
// if given `"foo"`, outputs `\"foo\"`. This is useful when a caller needs to
114+
// wrap `s` itself in double quotes.
115+
std::ostream& EscapeQuotes(std::ostream& os, std::string const& s) {
116+
for (auto const& c : s) {
117+
if (c == '"') os << "\\";
118+
os << c;
119+
}
120+
return os;
121+
}
122+
94123
// An enum to tell StreamHelper() whether a value is being printed as a scalar
95124
// or as part of an aggregate type (i.e., a vector or tuple). Some types may
96125
// format themselves differently in each case.
97126
enum class StreamMode { kScalar, kAggregate };
98127

99128
std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
100129
google::bigtable::v2::Value const& v,
101-
google::bigtable::v2::Type const& t, StreamMode) {
130+
google::bigtable::v2::Type const& t,
131+
StreamMode mode) {
102132
if (IsNullValue(v)) {
103133
return os << "NULL";
104134
}
@@ -113,7 +143,15 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
113143
return os << v.float_value();
114144
}
115145
if (v.kind_case() == google::bigtable::v2::Value::kStringValue) {
116-
return os << v.string_value();
146+
switch (mode) {
147+
case StreamMode::kScalar:
148+
return os << v.string_value();
149+
case StreamMode::kAggregate:
150+
os << '"';
151+
EscapeQuotes(os, v.string_value());
152+
return os << '"';
153+
}
154+
return os; // Unreachable, but quiets warning.
117155
}
118156
if (v.kind_case() == google::bigtable::v2::Value::kBytesValue) {
119157
return os << Bytes(AsString(v.bytes_value()));
@@ -130,6 +168,18 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
130168
bigtable_internal::FromProto(t, v).get<absl::CivilDay>().value();
131169
return os << date;
132170
}
171+
if (v.kind_case() == google::bigtable::v2::Value::kArrayValue) {
172+
char const* delimiter = "";
173+
os << '[';
174+
for (auto&& val : v.array_value().values()) {
175+
os << delimiter;
176+
StreamHelper(os, val, t.array_type().element_type(),
177+
StreamMode::kAggregate);
178+
delimiter = ", ";
179+
}
180+
return os << ']';
181+
return os;
182+
}
133183
// this should include type name
134184
return os << "Error: unknown value type code ";
135185
}

google/cloud/bigtable/value.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <google/bigtable/v2/data.pb.h>
2323
#include <google/bigtable/v2/types.pb.h>
2424
#include <cmath>
25+
#include <vector>
2526

2627
namespace google {
2728
namespace cloud {
@@ -73,12 +74,32 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
7374
* BYTES | `google::cloud::bigtable::Bytes`
7475
* TIMESTAMP | `google::cloud::bigtable::Timestamp`
7576
* DATE | `absl::CivilDay`
77+
* ARRAY | `std::vector<T>` // [1]
78+
*
79+
* [1] The type `T` may be any of the other supported types, except for
80+
* ARRAY/`std::vector`.
7681
*
7782
* Callers may create instances by passing any of the supported values
7883
* (shown in the table above) to the constructor. "Null" values are created
7984
* using the `MakeNullValue<T>()` factory function or by passing an empty
8085
* `absl::optional<T>` to the Value constructor.
8186
*
87+
* @par Bigtable Arrays
88+
*
89+
* Bigtable arrays are represented in C++ as a `std::vector<T>`, where the type
90+
* `T` may be any of the other allowed Bigtable types, such as `bool`,
91+
* `std::int64_t`, etc. The only exception is that arrays may not directly
92+
* contain another array; to achieve a similar result you could create an array
93+
* of a 1-element struct holding an array. The following examples show usage of
94+
* arrays.
95+
*
96+
* @code
97+
* std::vector<std::int64_t> vec = {1, 2, 3, 4, 5};
98+
* bigtable::Value v(vec);
99+
* auto copy = *v.get<std::vector<std::int64_t>>();
100+
* assert(vec == copy);
101+
* @endcode
102+
*
82103
*/
83104
class Value {
84105
public:
@@ -137,6 +158,24 @@ class Value {
137158
explicit Value(absl::optional<T> opt)
138159
: Value(PrivateConstructor{}, std::move(opt)) {}
139160

161+
/**
162+
* Constructs an instance from a Bigtable ARRAY of the specified type and
163+
* values.
164+
*
165+
* The type `T` may be any valid type shown above, except vectors of vectors
166+
* are not allowed.
167+
*
168+
* @warning If `T` is a `std::tuple` with field names (i.e., at least one of
169+
* its element types is a `std::pair<std::string, T>`) then, all of the
170+
* vector's elements must have exactly the same field names. Any mismatch
171+
* in in field names results in undefined behavior.
172+
*/
173+
template <typename T>
174+
explicit Value(std::vector<T> v) : Value(PrivateConstructor{}, std::move(v)) {
175+
static_assert(!IsVector<std::decay_t<T>>::value,
176+
"vector of vector not allowed. See value.h documentation.");
177+
}
178+
140179
// Copy and move.
141180
Value(Value const&) = default;
142181
Value(Value&&) = default;
@@ -197,6 +236,12 @@ class Value {
197236
template <typename T>
198237
struct IsOptional<absl::optional<T>> : std::true_type {};
199238

239+
// Metafunction that returns true if `T` is a std::vector<U>
240+
template <typename T>
241+
struct IsVector : std::false_type {};
242+
template <typename... Ts>
243+
struct IsVector<std::vector<Ts...>> : std::true_type {};
244+
200245
// Tag-dispatch overloads to check if a C++ type matches the type specified
201246
// by the given `Type` proto.
202247
static bool TypeProtoIs(bool, google::bigtable::v2::Type const&);
@@ -213,6 +258,12 @@ class Value {
213258
google::bigtable::v2::Type const& type) {
214259
return TypeProtoIs(T{}, type);
215260
}
261+
template <typename T>
262+
static bool TypeProtoIs(std::vector<T> const&,
263+
google::bigtable::v2::Type const& type) {
264+
return type.has_array_type() &&
265+
TypeProtoIs(T{}, type.array_type().element_type());
266+
}
216267

217268
// Tag-dispatch overloads to convert a C++ type to a `Type` protobuf. The
218269
// argument type is the tag, the argument value is ignored.
@@ -230,6 +281,22 @@ class Value {
230281
static google::bigtable::v2::Type MakeTypeProto(absl::optional<T> const&) {
231282
return MakeTypeProto(T{});
232283
}
284+
template <typename T>
285+
static google::bigtable::v2::Type MakeTypeProto(std::vector<T> const& v) {
286+
google::bigtable::v2::Type t;
287+
t.set_allocated_array_type(
288+
std::move(new google::bigtable::v2::Type_Array()));
289+
*t.mutable_array_type()->mutable_element_type() =
290+
MakeTypeProto(v.empty() ? T{} : v[0]);
291+
// Checks that vector elements have exactly the same proto Type, which
292+
// includes field names. This is documented UB.
293+
for (auto&& e : v) {
294+
google::bigtable::v2::Type vt = MakeTypeProto(e);
295+
if (t.array_type().element_type().kind_case() != vt.kind_case())
296+
internal::ThrowInvalidArgument("Mismatched types");
297+
}
298+
return t;
299+
}
233300

234301
// Encodes the argument as a protobuf according to the rules described in
235302
// https://github.com/googleapis/googleapis/blob/master/google/bigtable/v2/type.proto
@@ -251,6 +318,15 @@ class Value {
251318
v.clear_type();
252319
return v;
253320
}
321+
template <typename T>
322+
static google::bigtable::v2::Value MakeValueProto(std::vector<T> vec) {
323+
google::bigtable::v2::Value v;
324+
auto& list = *v.mutable_array_value();
325+
for (auto&& e : vec) {
326+
*list.add_values() = MakeValueProto(std::move(e));
327+
}
328+
return v;
329+
}
254330

255331
// Tag-dispatch overloads to extract a C++ value from a `Value` protobuf. The
256332
// first argument type is the tag, its value is ignored.
@@ -289,6 +365,36 @@ class Value {
289365
if (!value) return std::move(value).status();
290366
return absl::optional<T>{*std::move(value)};
291367
}
368+
template <typename T, typename V>
369+
static StatusOr<std::vector<T>> GetValue(
370+
std::vector<T> const&, V&& pv, google::bigtable::v2::Type const& pt) {
371+
if (pv.kind_case() != google::bigtable::v2::Value::kArrayValue) {
372+
return internal::UnknownError("missing ARRAY", GCP_ERROR_INFO());
373+
}
374+
std::vector<T> v;
375+
for (int i = 0; i < pv.array_value().values().size(); ++i) {
376+
auto&& e = GetProtoListValueElement(std::forward<V>(pv), i);
377+
using ET = decltype(e);
378+
auto value =
379+
GetValue(T{}, std::forward<ET>(e), pt.array_type().element_type());
380+
if (!value) return std::move(value).status();
381+
v.push_back(*std::move(value));
382+
}
383+
return v;
384+
}
385+
386+
// Protocol buffers are not friendly to generic programming, because they use
387+
// different syntax and different names for mutable and non-mutable
388+
// functions. To make GetValue(vector<T>, ...) (above) work, we need split
389+
// the different protobuf syntaxes into overloaded functions.
390+
static google::bigtable::v2::Value const& GetProtoListValueElement(
391+
google::bigtable::v2::Value const& pv, int pos) {
392+
return pv.array_value().values(pos);
393+
}
394+
static google::bigtable::v2::Value&& GetProtoListValueElement(
395+
google::bigtable::v2::Value&& pv, int pos) {
396+
return std::move(*pv.mutable_array_value()->mutable_values(pos));
397+
}
292398

293399
// A private templated constructor that is called by all the public
294400
// constructors to set the type_ and value_ members. The `PrivateConstructor`

0 commit comments

Comments
 (0)