Skip to content

Commit 437945a

Browse files
feat(bigtable): add support for MAP type' (#15594)
* feat(bigtable): add support for MAP type * test: prepare for tests * test: finish tests * chore: format and clang-tidy * chore: fix self nits * chore: remove debug statement * fix: use unordered_map, simplify comparison * fix: adapt Equal to unordered_map * chore: use single-expression comparison operators * chore: fix recursive comparison, add comparison tests * chore: use one statement to return the result * chore: use b < a
1 parent c0a041c commit 437945a

4 files changed

Lines changed: 580 additions & 27 deletions

File tree

google/cloud/bigtable/bytes.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ class Bytes {
6767
return a.bytes_ == b.bytes_;
6868
}
6969
friend bool operator!=(Bytes const& a, Bytes const& b) { return !(a == b); }
70+
friend bool operator<(Bytes const& a, Bytes const& b) {
71+
return a.bytes_ < b.bytes_;
72+
}
73+
friend bool operator>=(Bytes const& a, Bytes const& b) { return !(a < b); }
74+
friend bool operator>(Bytes const& a, Bytes const& b) { return b < a; }
75+
friend bool operator<=(Bytes const& a, Bytes const& b) { return !(b < a); }
7076
///@}
7177

7278
/**
@@ -89,4 +95,12 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
8995
} // namespace cloud
9096
} // namespace google
9197

98+
template <>
99+
struct std::hash<google::cloud::bigtable::Bytes> {
100+
std::size_t operator()(
101+
google::cloud::bigtable::Bytes const& b) const noexcept {
102+
return std::hash<std::string>()(b.get<std::string>());
103+
}
104+
};
105+
92106
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BYTES_H

google/cloud/bigtable/value.cc

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <google/protobuf/descriptor.h>
2121
#include <google/protobuf/message.h>
2222
#include <google/type/date.pb.h>
23+
#include <algorithm>
2324

2425
namespace google {
2526
namespace cloud {
@@ -68,6 +69,12 @@ bool StructEqual( // NOLINT(misc-no-recursion)
6869
google::bigtable::v2::Type const& pt2,
6970
google::bigtable::v2::Value const& pv2);
7071

72+
bool MapEqual( // NOLINT(misc-no-recursion)
73+
google::bigtable::v2::Type const& pt1,
74+
google::bigtable::v2::Value const& pv1,
75+
google::bigtable::v2::Type const& pt2,
76+
google::bigtable::v2::Value const& pv2);
77+
7178
// Compares two sets of Type and Value protos for equality. This method calls
7279
// itself recursively to compare subtypes and subvalues.
7380
bool Equal(google::bigtable::v2::Type const& pt1, // NOLINT(misc-no-recursion)
@@ -106,6 +113,9 @@ bool Equal(google::bigtable::v2::Type const& pt1, // NOLINT(misc-no-recursion)
106113
if (pt1.has_struct_type()) {
107114
return StructEqual(pt1, pv1, pt2, pv2);
108115
}
116+
if (pt1.has_map_type()) {
117+
return MapEqual(pt1, pv1, pt2, pv2);
118+
}
109119
return false;
110120
}
111121

@@ -158,6 +168,44 @@ bool StructEqual( // NOLINT(misc-no-recursion)
158168
return true;
159169
}
160170

171+
// Compares two sets of Type and Value protos that represent a MAP for
172+
// equality.
173+
bool MapEqual( // NOLINT(misc-no-recursion)
174+
google::bigtable::v2::Type const& pt1,
175+
google::bigtable::v2::Value const& pv1,
176+
google::bigtable::v2::Type const& pt2,
177+
google::bigtable::v2::Value const& pv2) {
178+
auto const& kt1 = pt1.map_type().key_type();
179+
auto const& kt2 = pt2.map_type().key_type();
180+
auto const& vt1 = pt1.map_type().value_type();
181+
auto const& vt2 = pt2.map_type().value_type();
182+
if (kt1.kind_case() != kt2.kind_case()) return false;
183+
if (vt1.kind_case() != vt2.kind_case()) return false;
184+
185+
auto const& mv1 = pv1.array_value().values();
186+
auto const& mv2 = pv2.array_value().values();
187+
if (mv1.size() != mv2.size()) return false;
188+
// We double-check that all subarrays are of size 2;
189+
for (int i = 0; i < mv1.size(); ++i) {
190+
auto const& f1 = mv1.Get(i);
191+
auto const& f2 = mv2.Get(i);
192+
if (f1.array_value().values_size() != 2) return false;
193+
if (f2.array_value().values_size() != 2) return false;
194+
}
195+
// NOLINTNEXTLINE(misc-no-recursion)
196+
auto comparison_function = [&kt1, &kt2, &vt1, &vt2](
197+
google::bigtable::v2::Value const& f1,
198+
google::bigtable::v2::Value const& f2) {
199+
auto const& k1 = f1.array_value().values(0);
200+
auto const& k2 = f2.array_value().values(0);
201+
auto const& v1 = f1.array_value().values(1);
202+
auto const& v2 = f2.array_value().values(1);
203+
return Equal(kt1, k1, kt2, k2) && Equal(vt1, v1, vt2, v2);
204+
};
205+
return std::is_permutation(mv1.begin(), mv1.end(), mv2.begin(), mv2.end(),
206+
comparison_function);
207+
}
208+
161209
// From the proto description, `NULL` values are represented by having a kind
162210
// equal to KIND_NOT_SET
163211
bool IsNullValue(google::bigtable::v2::Value const& value) {
@@ -180,6 +228,11 @@ std::ostream& EscapeQuotes(std::ostream& os, std::string const& s) {
180228
// format themselves differently in each case.
181229
enum class StreamMode { kScalar, kAggregate };
182230

231+
std::ostream& MapStreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
232+
google::bigtable::v2::Value const& v,
233+
google::bigtable::v2::Type const& t,
234+
StreamMode mode);
235+
183236
std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
184237
google::bigtable::v2::Value const& v,
185238
google::bigtable::v2::Type const& t,
@@ -188,17 +241,16 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
188241
return os << "NULL";
189242
}
190243

191-
if (t.kind_case() == google::bigtable::v2::Type::kBoolType) {
244+
if (t.has_bool_type()) {
192245
return os << v.bool_value();
193246
}
194-
if (t.kind_case() == google::bigtable::v2::Type::kInt64Type) {
247+
if (t.has_int64_type()) {
195248
return os << v.int_value();
196249
}
197-
if (t.kind_case() == google::bigtable::v2::Type::kFloat32Type ||
198-
t.kind_case() == google::bigtable::v2::Type::kFloat64Type) {
250+
if (t.has_float32_type() || t.has_float64_type()) {
199251
return os << v.float_value();
200252
}
201-
if (t.kind_case() == google::bigtable::v2::Type::kStringType) {
253+
if (t.has_string_type()) {
202254
switch (mode) {
203255
case StreamMode::kScalar:
204256
return os << v.string_value();
@@ -209,23 +261,23 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
209261
}
210262
return os; // Unreachable, but quiets warning.
211263
}
212-
if (t.kind_case() == google::bigtable::v2::Type::kBytesType) {
264+
if (t.has_bytes_type()) {
213265
return os << Bytes(AsString(v.bytes_value()));
214266
}
215-
if (t.kind_case() == google::bigtable::v2::Type::kTimestampType) {
267+
if (t.has_timestamp_type()) {
216268
auto ts = MakeTimestamp(v.timestamp_value());
217269
if (!ts) {
218270
internal::ThrowStatus(ts.status());
219271
}
220272
return os << ts.value();
221273
}
222-
if (t.kind_case() == google::bigtable::v2::Type::kDateType) {
274+
if (t.has_date_type()) {
223275
auto date =
224276
bigtable_internal::FromProto(t, v).get<absl::CivilDay>().value();
225277
return os << date;
226278
}
227-
if (t.kind_case() == google::bigtable::v2::Type::kArrayType) {
228-
char const* delimiter = "";
279+
if (t.has_array_type()) {
280+
auto const* delimiter = "";
229281
os << '[';
230282
for (auto&& val : v.array_value().values()) {
231283
os << delimiter;
@@ -235,8 +287,8 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
235287
}
236288
return os << ']';
237289
}
238-
if (t.kind_case() == google::bigtable::v2::Type::kStructType) {
239-
char const* delimiter = "";
290+
if (t.has_struct_type()) {
291+
auto const* delimiter = "";
240292
os << '(';
241293
for (int i = 0; i < v.array_value().values_size(); ++i) {
242294
os << delimiter;
@@ -251,9 +303,36 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
251303
}
252304
return os << ')';
253305
}
306+
if (t.has_map_type()) {
307+
return MapStreamHelper(os, v, t, mode);
308+
}
254309
// this should include type name
255310
return os << "Error: unknown value type code " << t.kind_case();
256311
}
312+
std::ostream& MapStreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
313+
google::bigtable::v2::Value const& v,
314+
google::bigtable::v2::Type const& t, StreamMode) {
315+
auto const* delimiter = "";
316+
os << '{';
317+
for (int i = 0; i < v.array_value().values_size(); ++i) {
318+
os << delimiter;
319+
os << "{";
320+
auto const& kv = v.array_value().values(i);
321+
if (!kv.has_array_value() || kv.array_value().values_size() != 2) {
322+
os << "malformed key-value pair";
323+
delimiter = ", ";
324+
continue;
325+
}
326+
StreamHelper(os, kv.array_value().values(0), t.map_type().key_type(),
327+
StreamMode::kAggregate);
328+
os << " : ";
329+
StreamHelper(os, kv.array_value().values(1), t.map_type().value_type(),
330+
StreamMode::kAggregate);
331+
os << "}";
332+
delimiter = ", ";
333+
}
334+
return os << '}';
335+
}
257336
} // namespace
258337

259338
bool operator==(Value const& a, Value const& b) {

0 commit comments

Comments
 (0)