Skip to content

CXX-3233 add bsoncxx v1 implementations and tests #1430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9fc87c6
v1: impls
eramongodb Jul 28, 2025
b034251
v1: tests
eramongodb Jul 28, 2025
e51551b
Merge remote-tracking branch 'upstream/master' into cxx-abi-v1-impls
eramongodb Jul 30, 2025
f258f76
Add some basic find() and subscript assertions
eramongodb Aug 6, 2025
25354e4
Fix doc comments for BSON array data
eramongodb Aug 6, 2025
b2a4393
Do not violate precondition in our own test cases
eramongodb Aug 6, 2025
1bc5302
Assert resulting document length in invalid_length assertions
eramongodb Aug 6, 2025
877cd50
Remove unused values array
eramongodb Aug 6, 2025
9e3d254
Fix view vs. value in btype_vs_value static assertions
eramongodb Aug 6, 2025
2cfc2d7
Move T -> T::impl conversion functions into T::impl
eramongodb Aug 6, 2025
8de4ee1
ClangFormat
eramongodb Aug 6, 2025
a3fd459
Merge remote-tracking branch 'upstream/master' into cxx-abi-v1-impls
eramongodb Aug 8, 2025
1a94bf7
Merge remote-tracking branch 'upstream/master' into HEAD
eramongodb Aug 14, 2025
2b76d2c
Distinguish "missing" vs. "invalid" after calling bson_iter_*
eramongodb Aug 14, 2025
4c6b735
Use std::array instead of raw arrays
eramongodb Aug 14, 2025
81fa5c0
BSON binary subtype values 128-255 are "user defined"
eramongodb Aug 14, 2025
22c67c2
Minor mem-list-init improvement
eramongodb Aug 14, 2025
4258c04
Make WSAGuard Windows-only and static local
eramongodb Aug 14, 2025
116890e
Use bsoncxx::make_unique_for_overwrite
eramongodb Aug 14, 2025
3de63ff
Fix and cleanup include directives
eramongodb Aug 14, 2025
e5c7fa9
Use common format for unknown error codes
eramongodb Aug 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/bsoncxx/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,8 @@ set_dist_list(src_bsoncxx_lib_DIST
bsoncxx/v_noabi/bsoncxx/types/bson_value/value.hh
bsoncxx/v1/config/config.hpp.in
bsoncxx/v1/config/version.hpp.in
bsoncxx/v1/document/view.hh
bsoncxx/v1/element/view.hh
bsoncxx/v1/types/value.hh
bsoncxx/v1/types/view.hh
)
35 changes: 35 additions & 0 deletions src/bsoncxx/lib/bsoncxx/v1/array/view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,24 @@

//

#include <bsoncxx/v1/exception.hpp>

#include <bsoncxx/v1/document/view.hh>
#include <bsoncxx/v1/element/view.hh>

#include <cstdint>

#include <bsoncxx/private/bson.hh>
#include <bsoncxx/private/immortal.hh>
#include <bsoncxx/private/itoa.hh>
#include <bsoncxx/private/type_traits.hh>

namespace bsoncxx {
namespace v1 {
namespace array {

using code = v1::document::view::errc;

static_assert(is_regular<view>::value, "bsoncxx::v1::array::view must be regular");
static_assert(is_semitrivial<view>::value, "bsoncxx::v1::array::view must be semitrivial");

Expand All @@ -30,6 +42,29 @@ static_assert(
is_nothrow_moveable<view::const_iterator>::value,
"bsoncxx::v1::array::view::const_iterator must be nothrow moveable");

view::const_iterator view::find(std::uint32_t i) const {
if (!_view) {
return this->cend();
}
Comment on lines +46 to +48
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is not present in v_noabi). This is a consequence of the new, well-defined "invalid" state of an array view.


bsoncxx::itoa key{i};

bson_t bson;

if (!bson_init_static(&bson, _view.data(), _view.length())) {
throw v1::exception{code::invalid_data};
}

bson_iter_t iter;

if (!bson_iter_init_find_w_len(&iter, &bson, key.c_str(), static_cast<int>(key.length()))) {
return this->end();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly different to v_noabi's bson_init_static -> bson_iter_init -> bson_iter_init_find implementation, but should still be equivalent in observable behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bson_iter_find functions may return false if they fail to find the element, but also may return false if they encounter a parse error (and set iter.err_off). Should this also throw invalid_data in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Yes, it should throw invalid_data.


return const_iterator::internal::make_const_iterator(
_view.data(), _view.length(), bson_iter_offset(&iter), bson_iter_key_len(&iter));
}

} // namespace array
} // namespace v1
} // namespace bsoncxx
104 changes: 104 additions & 0 deletions src/bsoncxx/lib/bsoncxx/v1/decimal128.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,117 @@

//

#include <bsoncxx/v1/exception.hpp>

#include <climits>
#include <cstddef>
#include <string>
#include <system_error>

#include <bsoncxx/private/bson.hh>
#include <bsoncxx/private/immortal.hh>
#include <bsoncxx/private/type_traits.hh>

namespace bsoncxx {
namespace v1 {

using code = v1::decimal128::errc;

static_assert(is_regular<decimal128>::value, "bsoncxx::v1::decimal128 must be regular");
static_assert(is_semitrivial<decimal128>::value, "bsoncxx::v1::decimal128 must be semitrivial");

decimal128::decimal128(v1::stdx::string_view sv) {
if (sv.empty()) {
throw v1::exception{code::empty_string};
}

if (sv.size() > std::size_t{INT_MAX}) {
throw v1::exception{code::invalid_string_length};
}
Comment on lines +39 to +45
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These checks are not present in v_noabi.


bson_decimal128_t d128;

if (!bson_decimal128_from_string_w_len(sv.data(), static_cast<int>(sv.size()), &d128)) {
throw v1::exception{code::invalid_string_data};
}

_high = d128.high;
_low = d128.low;
}

std::string decimal128::to_string() const {
bson_decimal128_t d128;
d128.high = _high;
d128.low = _low;
char str[BSON_DECIMAL128_STRING];
bson_decimal128_to_string(&d128, str);
return {str};
}

std::error_category const& decimal128::error_category() {
class type final : public std::error_category {
char const* name() const noexcept override {
return "bsoncxx::v1::decimal128";
}

std::string message(int v) const noexcept override {
switch (static_cast<code>(v)) {
case code::zero:
return "zero";
case code::empty_string:
return "string must not be empty";
case code::invalid_string_length:
return "length of string is too long (exceeds INT_MAX)";
case code::invalid_string_data:
return "string is not a valid Decimal128 representation";
default:
return "unknown: " + std::to_string(v);
}
}

bool equivalent(int v, std::error_condition const& ec) const noexcept override {
if (ec.category() == v1::source_error_category()) {
using condition = v1::source_errc;

auto const source = static_cast<condition>(ec.value());

switch (static_cast<code>(v)) {
case code::empty_string:
case code::invalid_string_length:
case code::invalid_string_data:
return source == condition::bsoncxx;

case code::zero:
default:
return false;
}
}

if (ec.category() == v1::type_error_category()) {
using condition = v1::type_errc;

auto const type = static_cast<condition>(ec.value());

switch (static_cast<code>(v)) {
case code::empty_string:
case code::invalid_string_length:
case code::invalid_string_data:
return type == condition::invalid_argument;

case code::zero:
default:
return false;
}
}

return false;
}
};

static bsoncxx::immortal<type> const instance;

return instance.value();
}

} // namespace v1
} // namespace bsoncxx
2 changes: 2 additions & 0 deletions src/bsoncxx/lib/bsoncxx/v1/document/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace document {
static_assert(is_regular<value>::value, "bsoncxx::v1::document::value must be regular");
static_assert(is_nothrow_moveable<value>::value, "bsoncxx::v1::document::value must be nothrow moveable");

void value::noop_deleter(std::uint8_t*) { /* noop */ }

} // namespace document
} // namespace v1
} // namespace bsoncxx
176 changes: 175 additions & 1 deletion src/bsoncxx/lib/bsoncxx/v1/document/view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <bsoncxx/v1/document/view.hpp>
#include <bsoncxx/v1/document/view.hh>

//

#include <bsoncxx/v1/exception.hpp>

#include <bsoncxx/v1/element/view.hh>

#include <cstdint>
#include <string>
#include <system_error>

#include <bsoncxx/private/bson.hh>
#include <bsoncxx/private/immortal.hh>
#include <bsoncxx/private/itoa.hh>
#include <bsoncxx/private/type_traits.hh>

namespace bsoncxx {
namespace v1 {
namespace document {

using code = v1::document::view::errc;

static_assert(is_regular<view>::value, "bsoncxx::v1::document::view must be regular");
static_assert(is_semitrivial<view>::value, "bsoncxx::v1::document::view must be semitrivial");

Expand All @@ -30,6 +43,167 @@ static_assert(
is_nothrow_moveable<view::const_iterator>::value,
"bsoncxx::v1::document::view::const_iterator must be nothrow moveable");

namespace {

constexpr std::uint8_t k_default_view[5] = {5u, 0u, 0u, 0u, 0u};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the special empty document representation that is used to avoid v1::document::value allocation (with a no-op deleter).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Can this be a std::array<u8, 5>? (clang-tidy nags about arrays)


} // namespace

view::view() : view{k_default_view} {}

view::view(std::uint8_t const* data, std::size_t length) : view{data} {
if (length < _empty_length || length < this->size()) {
throw v1::exception{v1::document::view::errc::invalid_length};
}
}

view::const_iterator view::cbegin() const {
if (!this->operator bool()) {
return this->cend();
}
Comment on lines +62 to +64
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check (and the one in find() below) is not present in v_noabi. This is a consequence of the new, well-defined "invalid" state of a document view.


bson_iter_t iter;

if (!bson_iter_init_from_data(&iter, _data, this->size())) {
throw v1::exception{code::invalid_data};
}

if (!bson_iter_next(&iter)) {
return this->cend();
}

return const_iterator::internal::make_const_iterator(
_data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter));
}

view::const_iterator view::find(v1::stdx::string_view key) const {
if (!this->operator bool()) {
return this->cend();
}

if (key.size() >= std::size_t{INT_MAX}) {
return this->cend();
}
Comment on lines +89 to +91
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is not present in v_noabi. This guards the cast to int in the later call to bson_iter_init_find_w_len (passing -1 does not help).


// Support null as equivalent to empty.
if (!key.data()) {
key = "";
}
Comment on lines +93 to +96
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preserves v_noabi behavior.


bson_t bson;

if (!bson_init_static(&bson, _data, this->size())) {
throw v1::exception{code::invalid_data};
}

bson_iter_t iter;

if (!bson_iter_init_find_w_len(&iter, &bson, key.data(), static_cast<int>(key.size()))) {
return this->end();
}

return const_iterator::internal::make_const_iterator(
_data, this->size(), bson_iter_offset(&iter), bson_iter_key_len(&iter));
}

std::error_category const& view::error_category() {
class type final : public std::error_category {
char const* name() const noexcept override {
return "bsoncxx::v1::document::view";
}

std::string message(int v) const noexcept override {
switch (static_cast<code>(v)) {
case code::zero:
return "zero";
case code::invalid_length:
return "length is invalid";
case code::invalid_data:
return "data is invalid";
default:
return "unknown: " + std::to_string(v);
}
}

bool equivalent(int v, std::error_condition const& ec) const noexcept override {
if (ec.category() == v1::source_error_category()) {
using condition = v1::source_errc;

auto const source = static_cast<condition>(ec.value());

switch (static_cast<code>(v)) {
case code::invalid_length:
case code::invalid_data:
return source == condition::bsoncxx;

case code::zero:
default:
return false;
}
}

if (ec.category() == v1::type_error_category()) {
using condition = v1::type_errc;

auto const type = static_cast<condition>(ec.value());

switch (static_cast<code>(v)) {
case code::invalid_length:
return type == condition::invalid_argument;

case code::invalid_data:
return type == condition::runtime_error;

case code::zero:
default:
return false;
}
}

return false;
}
};

static bsoncxx::immortal<type> const instance;

return instance.value();
}

view::const_iterator& view::const_iterator::operator++() {
if (!_element) {
return *this;
}

auto iter_opt = to_bson_iter(_element);
if (!iter_opt) {
throw v1::exception{code::invalid_data};
}
auto& iter = *iter_opt;

if (bson_iter_next(&iter)) {
_element = v1::element::view::internal::make(
_element.raw(), _element.length(), bson_iter_offset(&iter), bson_iter_key_len(&iter));
} else {
_element = {};
}

return *this;
}

view::const_iterator::const_iterator(v1::element::view element) : _element(element) {}

view::const_iterator view::const_iterator::internal::make_const_iterator(
std::uint8_t const* raw,
std::size_t length,
std::uint32_t offset,
std::uint32_t keylen) {
return const_iterator{v1::element::view::internal::make(
raw,
static_cast<std::uint32_t>(length), // Guarded by `bson_init_static`.
offset,
keylen)};
}

} // namespace document
} // namespace v1
} // namespace bsoncxx
Loading