Skip to content

Commit 97b036b

Browse files
feat(bigtable): add support for byte type (#15556)
Unlike spanner, bigtable is not supposed to hold base64 conversions internally. Moreover, `bytes_value()` is simply a string. Otherwise, most of spanner's implementation and testing has been ported in strict terms.
1 parent 7d383d7 commit 97b036b

File tree

9 files changed

+436
-1
lines changed

9 files changed

+436
-1
lines changed

google/cloud/bigtable/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ add_library(
109109
app_profile_config.cc
110110
app_profile_config.h
111111
async_row_reader.h
112+
bytes.cc
113+
bytes.h
112114
cell.h
113115
client_options.cc
114116
client_options.h
@@ -419,6 +421,7 @@ if (BUILD_TESTING)
419421
app_profile_config_test.cc
420422
async_read_stream_test.cc
421423
bigtable_version_test.cc
424+
bytes_test.cc
422425
cell_test.cc
423426
client_options_test.cc
424427
cluster_config_test.cc

google/cloud/bigtable/bigtable_client_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ bigtable_client_unit_tests = [
2121
"app_profile_config_test.cc",
2222
"async_read_stream_test.cc",
2323
"bigtable_version_test.cc",
24+
"bytes_test.cc",
2425
"cell_test.cc",
2526
"client_options_test.cc",
2627
"cluster_config_test.cc",

google/cloud/bigtable/bytes.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/bigtable/bytes.h"
16+
#include <array>
17+
18+
namespace google {
19+
namespace cloud {
20+
namespace bigtable {
21+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
22+
23+
// Prints the bytes in the form B"...", where printable bytes are output
24+
// normally, double quotes are backslash escaped, and non-printable characters
25+
// are printed as a 3-digit octal escape sequence.
26+
std::ostream& operator<<(std::ostream& os, Bytes const& bytes) {
27+
os << R"(B")";
28+
for (auto const byte : bytes.bytes_) {
29+
if (byte == '"') {
30+
os << R"(\")";
31+
} else if (std::isprint(byte)) {
32+
os << byte;
33+
} else {
34+
// This uses snprintf rather than iomanip so we don't mess up the
35+
// formatting on `os` for other streaming operations.
36+
std::array<char, sizeof(R"(\000)")> buf;
37+
auto n = std::snprintf(buf.data(), buf.size(), R"(\%03o)",
38+
static_cast<unsigned char>(byte));
39+
if (n == static_cast<int>(buf.size() - 1)) {
40+
os << buf.data();
41+
} else {
42+
os << R"(\?)";
43+
}
44+
}
45+
}
46+
// Can't use raw string literal here because of a doxygen bug.
47+
return os << "\"";
48+
}
49+
50+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
51+
} // namespace bigtable
52+
53+
namespace bigtable_internal {
54+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
55+
struct BytesInternals {
56+
static bigtable::Bytes Create(std::string rep) {
57+
bigtable::Bytes bytes;
58+
bytes.bytes_ = std::move(rep);
59+
return bytes;
60+
}
61+
62+
static std::string GetRep(bigtable::Bytes&& bytes) {
63+
return std::move(bytes.bytes_);
64+
}
65+
};
66+
67+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
68+
} // namespace bigtable_internal
69+
} // namespace cloud
70+
} // namespace google

google/cloud/bigtable/bytes.h

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BYTES_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BYTES_H
17+
18+
#include "google/cloud/bigtable/version.h"
19+
#include "google/cloud/status_or.h"
20+
#include <array>
21+
#include <cstddef>
22+
#include <ostream>
23+
#include <string>
24+
25+
namespace google {
26+
namespace cloud {
27+
namespace bigtable_internal {
28+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
29+
struct BytesInternals;
30+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
31+
} // namespace bigtable_internal
32+
33+
namespace bigtable {
34+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
35+
36+
/**
37+
* A representation of the Bigtable BYTES type: variable-length binary data.
38+
*
39+
* A `Bytes` value can be constructed from, and converted to any sequence of
40+
* octets. `Bytes` values can be compared for equality.
41+
*/
42+
class Bytes {
43+
public:
44+
/// An empty sequence.
45+
Bytes() = default;
46+
47+
/// @name Construction from a sequence of octets.
48+
///@{
49+
template <typename InputIt>
50+
Bytes(InputIt first, InputIt last) {
51+
bytes_ = std::string(first, last);
52+
}
53+
template <typename Container>
54+
explicit Bytes(Container const& c) : Bytes(std::begin(c), std::end(c)) {}
55+
///@}
56+
57+
/// Conversion to a sequence of octets. The `Container` must support
58+
/// construction from a range specified as a pair of input iterators.
59+
template <typename Container>
60+
Container get() const {
61+
return bytes_;
62+
}
63+
64+
/// @name Relational operators
65+
///@{
66+
friend bool operator==(Bytes const& a, Bytes const& b) {
67+
return a.bytes_ == b.bytes_;
68+
}
69+
friend bool operator!=(Bytes const& a, Bytes const& b) { return !(a == b); }
70+
///@}
71+
72+
/**
73+
* Outputs string representation of the Bytes to the provided stream.
74+
*
75+
* @warning This is intended for debugging and human consumption only, not
76+
* machine consumption, as the output format may change without notice.
77+
*/
78+
friend std::ostream& operator<<(std::ostream& os, Bytes const& bytes);
79+
80+
private:
81+
friend struct bigtable_internal::BytesInternals;
82+
83+
std::string bytes_; // raw bytes
84+
};
85+
86+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
87+
} // namespace bigtable
88+
89+
} // namespace cloud
90+
} // namespace google
91+
92+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BYTES_H
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/bigtable/bytes.h"
16+
#include <gmock/gmock.h>
17+
#include <cstdint>
18+
#include <deque>
19+
#include <limits>
20+
#include <sstream>
21+
#include <string>
22+
#include <utility>
23+
#include <vector>
24+
25+
namespace google {
26+
namespace cloud {
27+
namespace bigtable {
28+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
29+
namespace {
30+
31+
TEST(Bytes, RoundTrip) {
32+
char c = std::numeric_limits<char>::min();
33+
std::string chars(1, c);
34+
while (c != std::numeric_limits<char>::max()) {
35+
chars.push_back(++c);
36+
}
37+
38+
// Empty string.
39+
std::string data;
40+
Bytes bytes(data);
41+
EXPECT_EQ(data, bytes.get<std::string>());
42+
43+
// All 1-char strings.
44+
data.resize(1);
45+
for (auto c : chars) {
46+
data[0] = c;
47+
Bytes bytes(data);
48+
EXPECT_EQ(data, bytes.get<std::string>());
49+
}
50+
51+
// All 2-char strings.
52+
data.resize(2);
53+
for (auto c0 : chars) {
54+
data[0] = c0;
55+
for (auto c1 : chars) {
56+
data[1] = c1;
57+
Bytes bytes(data);
58+
EXPECT_EQ(data, bytes.get<std::string>());
59+
}
60+
}
61+
62+
// Some 3-char strings.
63+
data.resize(3);
64+
for (auto c0 : {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}) {
65+
data[0] = c0;
66+
for (auto c1 : chars) {
67+
data[1] = c1;
68+
for (auto c2 : chars) {
69+
data[2] = c2;
70+
Bytes bytes(data);
71+
EXPECT_EQ(data, bytes.get<std::string>());
72+
}
73+
}
74+
}
75+
}
76+
77+
TEST(Bytes, RelationalOperators) {
78+
std::string const s_plain = "The quick brown fox jumps over the lazy dog.";
79+
std::deque<char> const d_plain(s_plain.begin(), s_plain.end());
80+
std::vector<std::uint8_t> const v_plain(s_plain.begin(), s_plain.end());
81+
82+
auto s_bytes = Bytes(s_plain.begin(), s_plain.end());
83+
auto d_bytes = Bytes(d_plain.begin(), d_plain.end());
84+
auto v_bytes = Bytes(v_plain.begin(), v_plain.end());
85+
EXPECT_EQ(s_bytes, d_bytes);
86+
EXPECT_EQ(d_bytes, v_bytes);
87+
EXPECT_EQ(v_bytes, s_bytes);
88+
89+
auto x_bytes = Bytes(s_plain + " How vexingly quick daft zebras jump!");
90+
EXPECT_NE(x_bytes, s_bytes);
91+
EXPECT_NE(x_bytes, d_bytes);
92+
EXPECT_NE(x_bytes, v_bytes);
93+
}
94+
95+
TEST(Bytes, OutputStream) {
96+
struct TestCase {
97+
Bytes bytes;
98+
std::string expected;
99+
};
100+
101+
std::vector<TestCase> test_cases = {
102+
{Bytes(std::string("")), R"(B"")"},
103+
{Bytes(std::string("foo")), R"(B"foo")"},
104+
{Bytes(std::string("a\011B")), R"(B"a\011B")"},
105+
{Bytes(std::string("a\377B")), R"(B"a\377B")"},
106+
{Bytes(std::string("!@#$%^&*()-.")), R"(B"!@#$%^&*()-.")"},
107+
{Bytes(std::string(3, '\0')), R"(B"\000\000\000")"},
108+
{Bytes(""), R"(B"\000")"},
109+
{Bytes("foo"), R"(B"foo\000")"},
110+
};
111+
112+
for (auto const& tc : test_cases) {
113+
std::ostringstream ss;
114+
ss << tc.bytes;
115+
EXPECT_EQ(ss.str(), tc.expected);
116+
}
117+
}
118+
119+
TEST(Bytes, OutputStreamEscapingCannotFail) {
120+
using ByteType = unsigned char;
121+
for (int i = 0; i < std::numeric_limits<ByteType>::max() + 1; ++i) {
122+
auto const b = static_cast<ByteType>(i);
123+
std::ostringstream ss;
124+
ss << Bytes(std::array<ByteType, 1>{b});
125+
EXPECT_NE(ss.str(), R"(B"\?")") << "i=" << i;
126+
}
127+
}
128+
129+
} // namespace
130+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
131+
} // namespace bigtable
132+
} // namespace cloud
133+
} // namespace google

google/cloud/bigtable/google_cloud_cpp_bigtable.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ google_cloud_cpp_bigtable_hdrs = [
4848
"admin_client.h",
4949
"app_profile_config.h",
5050
"async_row_reader.h",
51+
"bytes.h",
5152
"cell.h",
5253
"client_options.h",
5354
"cluster_config.h",
@@ -164,6 +165,7 @@ google_cloud_cpp_bigtable_srcs = [
164165
"admin/internal/bigtable_table_admin_tracing_stub.cc",
165166
"admin_client.cc",
166167
"app_profile_config.cc",
168+
"bytes.cc",
167169
"client_options.cc",
168170
"cluster_config.cc",
169171
"data_client.cc",

0 commit comments

Comments
 (0)