Skip to content

Commit 01fb63e

Browse files
feat(bigtable): add support for STRUCT (#15568)
* feat(bigtable): add support for STRUCT * chore: rebase * chore: address cognitive load warning * chore: address comments * test: use references instead of value copies in value tests * test: remove debug strings
1 parent 859f506 commit 01fb63e

File tree

3 files changed

+610
-31
lines changed

3 files changed

+610
-31
lines changed

google/cloud/bigtable/value.cc

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ std::string AsString(T&& s) {
5050
std::move(s)); // NOLINT(bugprone-move-forwarding-reference)
5151
}
5252

53+
// Forward declarations for mutually recursive functions.
54+
bool Equal(google::bigtable::v2::Type const& pt1, // NOLINT(misc-no-recursion)
55+
google::bigtable::v2::Value const& pv1,
56+
google::bigtable::v2::Type const& pt2,
57+
google::bigtable::v2::Value const& pv2);
58+
59+
bool ArrayEqual( // NOLINT(misc-no-recursion)
60+
google::bigtable::v2::Type const& pt1,
61+
google::bigtable::v2::Value const& pv1,
62+
google::bigtable::v2::Type const& pt2,
63+
google::bigtable::v2::Value const& pv2);
64+
65+
bool StructEqual( // NOLINT(misc-no-recursion)
66+
google::bigtable::v2::Type const& pt1,
67+
google::bigtable::v2::Value const& pv1,
68+
google::bigtable::v2::Type const& pt2,
69+
google::bigtable::v2::Value const& pv2);
70+
5371
// Compares two sets of Type and Value protos for equality. This method calls
5472
// itself recursively to compare subtypes and subvalues.
5573
bool Equal(google::bigtable::v2::Type const& pt1, // NOLINT(misc-no-recursion)
@@ -83,24 +101,61 @@ bool Equal(google::bigtable::v2::Type const& pt1, // NOLINT(misc-no-recursion)
83101
pv1.date_value().year() == pv2.date_value().year();
84102
}
85103
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()) {
104+
return ArrayEqual(pt1, pv1, pt2, pv2);
105+
}
106+
if (pt1.has_struct_type()) {
107+
return StructEqual(pt1, pv1, pt2, pv2);
108+
}
109+
return false;
110+
}
111+
112+
// Compares two sets of Type and Value protos that represent an ARRAY for
113+
// equality.
114+
bool ArrayEqual( // NOLINT(misc-no-recursion)
115+
google::bigtable::v2::Type const& pt1,
116+
google::bigtable::v2::Value const& pv1,
117+
google::bigtable::v2::Type const& pt2,
118+
google::bigtable::v2::Value const& pv2) {
119+
auto const& vec1 = pv1.array_value().values();
120+
auto const& vec2 = pv2.array_value().values();
121+
if (vec1.size() != vec2.size()) {
122+
return false;
123+
}
124+
auto const& el_type1 = pt1.array_type().element_type();
125+
auto const& el_type2 = pt2.array_type().element_type();
126+
if (el_type1.kind_case() != el_type2.kind_case()) {
127+
return false;
128+
}
129+
for (int i = 0; i < vec1.size(); ++i) {
130+
if (!Equal(el_type1, vec1.Get(i), el_type2, vec2.Get(i))) {
89131
return false;
90132
}
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()) {
133+
}
134+
return true;
135+
}
136+
137+
// Compares two sets of Type and Value protos that represent a STRUCT for
138+
// equality.
139+
bool StructEqual( // NOLINT(misc-no-recursion)
140+
google::bigtable::v2::Type const& pt1,
141+
google::bigtable::v2::Value const& pv1,
142+
google::bigtable::v2::Type const& pt2,
143+
google::bigtable::v2::Value const& pv2) {
144+
auto const& fields1 = pt1.struct_type().fields();
145+
auto const& fields2 = pt2.struct_type().fields();
146+
if (fields1.size() != fields2.size()) return false;
147+
auto const& v1 = pv1.array_value().values();
148+
auto const& v2 = pv2.array_value().values();
149+
if (fields1.size() != v1.size() || v1.size() != v2.size()) return false;
150+
for (int i = 0; i < fields1.size(); ++i) {
151+
auto const& f1 = fields1.Get(i);
152+
auto const& f2 = fields2.Get(i);
153+
if (f1.field_name() != f2.field_name()) return false;
154+
if (!Equal(f1.type(), v1.Get(i), f2.type(), v2.Get(i))) {
94155
return false;
95156
}
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;
102157
}
103-
return false;
158+
return true;
104159
}
105160

106161
// From the proto description, `NULL` values are represented by having a kind
@@ -133,16 +188,17 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
133188
return os << "NULL";
134189
}
135190

136-
if (v.kind_case() == google::bigtable::v2::Value::kBoolValue) {
191+
if (t.kind_case() == google::bigtable::v2::Type::kBoolType) {
137192
return os << v.bool_value();
138193
}
139-
if (v.kind_case() == google::bigtable::v2::Value::kIntValue) {
194+
if (t.kind_case() == google::bigtable::v2::Type::kInt64Type) {
140195
return os << v.int_value();
141196
}
142-
if (v.kind_case() == google::bigtable::v2::Value::kFloatValue) {
197+
if (t.kind_case() == google::bigtable::v2::Type::kFloat32Type ||
198+
t.kind_case() == google::bigtable::v2::Type::kFloat64Type) {
143199
return os << v.float_value();
144200
}
145-
if (v.kind_case() == google::bigtable::v2::Value::kStringValue) {
201+
if (t.kind_case() == google::bigtable::v2::Type::kStringType) {
146202
switch (mode) {
147203
case StreamMode::kScalar:
148204
return os << v.string_value();
@@ -153,22 +209,22 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
153209
}
154210
return os; // Unreachable, but quiets warning.
155211
}
156-
if (v.kind_case() == google::bigtable::v2::Value::kBytesValue) {
212+
if (t.kind_case() == google::bigtable::v2::Type::kBytesType) {
157213
return os << Bytes(AsString(v.bytes_value()));
158214
}
159-
if (v.kind_case() == google::bigtable::v2::Value::kTimestampValue) {
215+
if (t.kind_case() == google::bigtable::v2::Type::kTimestampType) {
160216
auto ts = MakeTimestamp(v.timestamp_value());
161217
if (!ts) {
162218
internal::ThrowStatus(ts.status());
163219
}
164220
return os << ts.value();
165221
}
166-
if (v.kind_case() == google::bigtable::v2::Value::kDateValue) {
222+
if (t.kind_case() == google::bigtable::v2::Type::kDateType) {
167223
auto date =
168224
bigtable_internal::FromProto(t, v).get<absl::CivilDay>().value();
169225
return os << date;
170226
}
171-
if (v.kind_case() == google::bigtable::v2::Value::kArrayValue) {
227+
if (t.kind_case() == google::bigtable::v2::Type::kArrayType) {
172228
char const* delimiter = "";
173229
os << '[';
174230
for (auto&& val : v.array_value().values()) {
@@ -178,15 +234,30 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion)
178234
delimiter = ", ";
179235
}
180236
return os << ']';
181-
return os;
237+
}
238+
if (t.kind_case() == google::bigtable::v2::Type::kStructType) {
239+
char const* delimiter = "";
240+
os << '(';
241+
for (int i = 0; i < v.array_value().values_size(); ++i) {
242+
os << delimiter;
243+
if (!t.struct_type().fields(i).field_name().empty()) {
244+
os << '"';
245+
EscapeQuotes(os, t.struct_type().fields(i).field_name());
246+
os << '"' << ": ";
247+
}
248+
StreamHelper(os, v.array_value().values(i),
249+
t.struct_type().fields(i).type(), StreamMode::kAggregate);
250+
delimiter = ", ";
251+
}
252+
return os << ')';
182253
}
183254
// this should include type name
184-
return os << "Error: unknown value type code ";
255+
return os << "Error: unknown value type code " << t.kind_case();
185256
}
186257
} // namespace
187258

188259
bool operator==(Value const& a, Value const& b) {
189-
return Equal(a.type_, a.value_, b.type_, b.value_);
260+
return bigtable::Equal(a.type_, a.value_, b.type_, b.value_);
190261
}
191262

192263
std::ostream& operator<<(std::ostream& os, Value const& v) {

0 commit comments

Comments
 (0)