summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/exec/sbe/SConscript7
-rw-r--r--src/mongo/db/exec/sbe/values/slot.cpp207
-rw-r--r--src/mongo/db/exec/sbe/values/slot.h11
-rw-r--r--src/mongo/db/exec/sbe/values/value.cpp2
-rw-r--r--src/mongo/db/exec/sbe/values/value_builder.h220
-rw-r--r--src/mongo/db/exec/sbe/values/value_serialization_test.cpp444
-rw-r--r--src/mongo/db/exec/sbe/values/value_serialize_for_sorter_test.cpp199
-rw-r--r--src/mongo/db/storage/key_string.cpp84
-rw-r--r--src/mongo/db/storage/key_string.h12
9 files changed, 920 insertions, 266 deletions
diff --git a/src/mongo/db/exec/sbe/SConscript b/src/mongo/db/exec/sbe/SConscript
index dd83422caf7..b3bb5358e9d 100644
--- a/src/mongo/db/exec/sbe/SConscript
+++ b/src/mongo/db/exec/sbe/SConscript
@@ -63,6 +63,7 @@ sbeEnv.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/concurrency/write_conflict_exception',
'$BUILD_DIR/mongo/db/exec/js_function',
'$BUILD_DIR/mongo/db/exec/scoped_timer',
'$BUILD_DIR/mongo/db/mongohasher',
@@ -119,6 +120,8 @@ env.Library(
'query_sbe',
],
LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/auth/authmocks',
+ '$BUILD_DIR/mongo/db/service_context_d_test_fixture',
'$BUILD_DIR/mongo/db/service_context_test_fixture',
],
)
@@ -168,13 +171,15 @@ env.CppUnitTest(
'sbe_test.cpp',
'sbe_trial_run_tracker_test.cpp',
'sbe_unique_test.cpp',
- 'values/value_serialize_for_sorter_test.cpp',
+ 'values/value_serialization_test.cpp',
"values/value_test.cpp",
'values/write_value_to_stream_test.cpp'
],
LIBDEPS=[
+ '$BUILD_DIR/mongo/db/auth/authmocks',
'$BUILD_DIR/mongo/db/concurrency/lock_manager',
'$BUILD_DIR/mongo/db/query/collation/collator_interface_mock',
+ '$BUILD_DIR/mongo/db/service_context_d_test_fixture',
'$BUILD_DIR/mongo/db/service_context_test_fixture',
'query_sbe_parser',
'sbe_plan_stage_test',
diff --git a/src/mongo/db/exec/sbe/values/slot.cpp b/src/mongo/db/exec/sbe/values/slot.cpp
index d827efd1747..607e4dadb59 100644
--- a/src/mongo/db/exec/sbe/values/slot.cpp
+++ b/src/mongo/db/exec/sbe/values/slot.cpp
@@ -31,9 +31,12 @@
#include "mongo/db/exec/sbe/values/slot.h"
+#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/util/builder.h"
#include "mongo/db/exec/js_function.h"
+#include "mongo/db/exec/sbe/values/bson.h"
#include "mongo/db/exec/sbe/values/sort_spec.h"
+#include "mongo/db/exec/sbe/values/value_builder.h"
#include "mongo/db/storage/key_string.h"
#include "mongo/util/bufreader.h"
@@ -363,6 +366,184 @@ static void serializeValue(BufBuilder& buf, TypeTags tag, Value val) {
}
}
+static void serializeValueIntoKeyString(KeyString::Builder& buf, TypeTags tag, Value val) {
+ switch (tag) {
+ case TypeTags::Nothing: {
+ buf.appendBool(false);
+ break;
+ }
+ case TypeTags::NumberInt32: {
+ buf.appendBool(true);
+ buf.appendNumberInt(bitcastTo<int32_t>(val));
+ break;
+ }
+ case TypeTags::NumberInt64: {
+ buf.appendBool(true);
+ buf.appendNumberLong(bitcastTo<int64_t>(val));
+ break;
+ }
+ case TypeTags::NumberDouble: {
+ buf.appendBool(true);
+ buf.appendNumberDouble(bitcastTo<double>(val));
+ break;
+ }
+ case TypeTags::NumberDecimal: {
+ buf.appendBool(true);
+ buf.appendNumberDecimal(value::bitcastTo<Decimal128>(val));
+ break;
+ }
+ case TypeTags::Date: {
+ buf.appendBool(true);
+ buf.appendDate(Date_t::fromMillisSinceEpoch(value::bitcastTo<int64_t>(val)));
+ break;
+ }
+ case TypeTags::Timestamp: {
+ buf.appendBool(true);
+ buf.appendTimestamp(Timestamp(value::bitcastTo<uint64_t>(val)));
+ break;
+ }
+ case TypeTags::Boolean: {
+ buf.appendBool(true);
+ buf.appendBool(bitcastTo<bool>(val));
+ break;
+ }
+ case TypeTags::Null: {
+ buf.appendBool(true);
+ buf.appendNull();
+ break;
+ }
+ case TypeTags::MinKey:
+ case TypeTags::MaxKey: {
+ BSONObjBuilder bob;
+ if (tag == value::TypeTags::MinKey) {
+ bob.appendMinKey("");
+ } else {
+ bob.appendMaxKey("");
+ }
+ buf.appendBool(true);
+ buf.appendBSONElement(bob.obj().firstElement(), nullptr);
+ break;
+ }
+ case TypeTags::bsonUndefined: {
+ buf.appendBool(true);
+ buf.appendUndefined();
+ break;
+ }
+ case TypeTags::StringSmall: {
+ // Small strings cannot contain null bytes, so it is safe to serialize them as plain
+ // C-strings with a null terminator.
+ buf.appendBool(true);
+ buf.appendString(getStringView(tag, val));
+ break;
+ }
+ case TypeTags::StringBig:
+ case TypeTags::bsonString: {
+ buf.appendBool(true);
+ buf.appendString(getStringOrSymbolView(tag, val));
+ break;
+ }
+ case TypeTags::bsonSymbol: {
+ buf.appendBool(true);
+ buf.appendSymbol(getStringOrSymbolView(tag, val));
+ break;
+ }
+ case TypeTags::ArraySet:
+ case TypeTags::Array: {
+ // TODO SERVER-61629: convert this to serialize the 'arr' directly instead of
+ // constructing a BSONArray.
+ BSONArrayBuilder builder;
+ bson::convertToBsonObj(builder, getArrayView(val));
+ buf.appendBool(true);
+ buf.appendArray(BSONArray(builder.done()));
+ break;
+ }
+ case TypeTags::Object: {
+ // TODO SERVER-61629: convert this to serialize the 'obj' directly instead of
+ // constructing a BSONObj.
+ BSONObjBuilder builder;
+ bson::convertToBsonObj(builder, getObjectView(val));
+ buf.appendBool(true);
+ buf.appendObject(builder.done());
+ break;
+ }
+ case TypeTags::ObjectId: {
+ buf.appendBool(true);
+ buf.appendBytes(getObjectIdView(val), sizeof(ObjectIdType));
+ break;
+ }
+ case TypeTags::bsonObject: {
+ buf.appendBool(true);
+ buf.appendObject(BSONObj(getRawPointerView(val)));
+ break;
+ }
+ case TypeTags::bsonArray: {
+ buf.appendBool(true);
+ buf.appendArray(BSONArray(BSONObj(getRawPointerView(val))));
+ break;
+ }
+ case TypeTags::bsonObjectId: {
+ buf.appendBool(true);
+ buf.appendOID(OID::from(getRawPointerView(val)));
+ break;
+ }
+ case TypeTags::bsonBinData: {
+ BufBuilder innerBinDataBuf;
+ innerBinDataBuf.appendUChar(static_cast<uint8_t>(tag));
+ innerBinDataBuf.appendBuf(getRawPointerView(val),
+ getBSONBinDataSize(tag, val) + sizeof(uint32_t) + 1);
+ buf.appendBool(true);
+ buf.appendBinData(
+ BSONBinData(innerBinDataBuf.buf(), innerBinDataBuf.len(), BinDataGeneral));
+ break;
+ }
+ case TypeTags::bsonRegex: {
+ auto regex = getBsonRegexView(val);
+ buf.appendBool(true);
+ buf.appendRegex(BSONRegEx(regex.pattern, regex.flags));
+ break;
+ }
+ case TypeTags::bsonJavascript: {
+ buf.appendBool(true);
+ buf.appendCode(getBsonJavascriptView(val));
+ break;
+ }
+ case TypeTags::bsonDBPointer: {
+ auto dbptr = getBsonDBPointerView(val);
+ buf.appendBool(true);
+ buf.appendDBRef(BSONDBRef(dbptr.ns, OID::from(dbptr.id)));
+ break;
+ }
+ case TypeTags::bsonCodeWScope: {
+ auto cws = getBsonCodeWScopeView(val);
+ buf.appendBool(true);
+ buf.appendCodeWString(BSONCodeWScope(cws.code, BSONObj(cws.scope)));
+ break;
+ }
+ case TypeTags::ksValue: {
+ auto ks = getKeyStringView(val);
+ BufBuilder innerBinDataBuf;
+ innerBinDataBuf.appendUChar(static_cast<uint8_t>(tag));
+ ks->serialize(innerBinDataBuf);
+ buf.appendBool(true);
+ buf.appendBinData(
+ BSONBinData(innerBinDataBuf.buf(), innerBinDataBuf.len(), BinDataGeneral));
+ break;
+ }
+ case TypeTags::RecordId: {
+ // TODO SERVER-61630: Support RecordId strings when sbe also supports this.
+ BufBuilder innerBinDataBuf;
+ innerBinDataBuf.appendUChar(static_cast<uint8_t>(tag));
+ innerBinDataBuf.appendNum(bitcastTo<int64_t>(val));
+ buf.appendBool(true);
+ buf.appendBinData(BSONBinData(
+ innerBinDataBuf.buf(), innerBinDataBuf.len(), BinDataType::BinDataGeneral));
+ break;
+ }
+ default:
+ MONGO_UNREACHABLE;
+ }
+}
+
void MaterializedRow::serializeForSorter(BufBuilder& buf) const {
buf.appendNum(size());
@@ -372,6 +553,32 @@ void MaterializedRow::serializeForSorter(BufBuilder& buf) const {
}
}
+void MaterializedRow::serializeIntoKeyString(KeyString::Builder& buf) const {
+ for (size_t idx = 0; idx < size(); ++idx) {
+ auto [tag, val] = getViewOfValue(idx);
+ serializeValueIntoKeyString(buf, tag, val);
+ }
+}
+
+MaterializedRow MaterializedRow::deserializeFromKeyString(const KeyString::Value& keyString,
+ BufBuilder* valueBufferBuilder) {
+ BufReader reader(keyString.getBuffer(), keyString.getSize());
+ KeyString::TypeBits typeBits(keyString.getTypeBits());
+ KeyString::TypeBits::Reader typeBitsReader(typeBits);
+
+ MaterializedRowValueBuilder valBuilder(valueBufferBuilder);
+ auto keepReading = true;
+ do {
+ keepReading = KeyString::readSBEValue(
+ &reader, &typeBitsReader, false /* inverted */, typeBits.version, &valBuilder);
+ } while (keepReading);
+
+ MaterializedRow result{valBuilder.numValues()};
+ valBuilder.readValues(result);
+
+ return result;
+}
+
int getApproximateSize(TypeTags tag, Value val) {
int result = sizeof(tag) + sizeof(val);
switch (tag) {
diff --git a/src/mongo/db/exec/sbe/values/slot.h b/src/mongo/db/exec/sbe/values/slot.h
index f0838a90730..1978ba1c959 100644
--- a/src/mongo/db/exec/sbe/values/slot.h
+++ b/src/mongo/db/exec/sbe/values/slot.h
@@ -474,6 +474,17 @@ public:
return result;
}
+ /**
+ * With these functions, an SBE value can be saved as a KeyString. This functionality is
+ * intended for spilling key values used in the HashAgg stage. The format is not guaranteed to
+ * be stable between versions, so it should not be used for long-term storage or communication
+ * between instances.
+ */
+ static MaterializedRow deserializeFromKeyString(const KeyString::Value& keyString,
+
+ BufBuilder* valueBufferBuilder);
+ void serializeIntoKeyString(KeyString::Builder& builder) const;
+
private:
static size_t sizeInBytes(size_t count) {
return count * (sizeof(value::Value) + sizeof(value::TypeTags) + sizeof(bool));
diff --git a/src/mongo/db/exec/sbe/values/value.cpp b/src/mongo/db/exec/sbe/values/value.cpp
index 449c6b2a0d7..84fd7eebc60 100644
--- a/src/mongo/db/exec/sbe/values/value.cpp
+++ b/src/mongo/db/exec/sbe/values/value.cpp
@@ -1313,7 +1313,7 @@ void readKeyStringValueIntoAccessors(const KeyString::Value& keyString,
BufBuilder* valueBufferBuilder,
std::vector<OwnedValueAccessor>* accessors,
boost::optional<IndexKeysInclusionSet> indexKeysToInclude) {
- ValueBuilder valBuilder(valueBufferBuilder);
+ OwnedValueAccessorValueBuilder valBuilder(valueBufferBuilder);
invariant(!indexKeysToInclude || indexKeysToInclude->count() == accessors->size());
BufReader reader(keyString.getBuffer(), keyString.getSize());
diff --git a/src/mongo/db/exec/sbe/values/value_builder.h b/src/mongo/db/exec/sbe/values/value_builder.h
index 3abb0ec3190..f1e2119fa88 100644
--- a/src/mongo/db/exec/sbe/values/value_builder.h
+++ b/src/mongo/db/exec/sbe/values/value_builder.h
@@ -33,6 +33,8 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/exec/sbe/values/slot.h"
+#include "mongo/db/storage/key_string.h"
+#include "mongo/util/bufreader.h"
namespace mongo::sbe::value {
@@ -42,10 +44,11 @@ namespace mongo::sbe::value {
* sbe::value::Value. During construction, these pairs are stored in the parallel '_tagList' and
* '_valList' arrays, as a "structure of arrays."
*
- * After constructing the array, use the 'readValues()' method to populate a OwnedValueAccessor
- * vector. Some "views" (values that are pointers into other memory) are constructed by appending
- * them to the 'valueBufferBuilder' provided to the constructor, and the internal buffer in that
- * 'valueBufferBuilder' must be kept alive for as long as the accessors are to remain valid.
+ * After constructing the array, an implementer of ValueBuilder must provide a 'readValues()' method
+ * to populate the tags/vals into a container or an sbe SlotAccessor. Some "views" (values that are
+ * pointers into other memory) are constructed by appending them to the 'valueBufferBuilder'
+ * provided to the constructor, and the internal buffer in that 'valueBufferBuilder' must be kept
+ * alive for as long as the accessors are to remain valid.
*
* Note that, in addition to destroying the 'valueBufferBuilder' or calling its 'reset()' or
* 'release()' function, appending more values to the buffer (either directly or via this
@@ -58,15 +61,14 @@ namespace mongo::sbe::value {
* operates by appending results to a BSONObjBuilder, to instead convert to SBE values. It is not
* intended as a general-purpose tool for populating SBE accessors, and no new code should construct
* or use a ValueBuilder.
- *
- * Also note that some data types are not yet supported by SBE and appending them will throw a
- * query-fatal error.
*/
class ValueBuilder {
public:
ValueBuilder(BufBuilder* valueBufferBuilder) : _valueBufferBuilder(valueBufferBuilder) {}
ValueBuilder(ValueBuilder& other) = delete;
+ ~ValueBuilder() = default;
+
void append(const MinKeyLabeler& id) {
appendValue(TypeTags::MinKey, 0);
}
@@ -184,66 +186,41 @@ public:
}
/**
- * Remove the last value that was streamed to this ValueBuilder.
+ * Returns the number of sbe tag/value pairs appended to this ValueBuilder.
*/
- void popValue() {
- // If the removed value was a view of a string, object or array in the '_valueBufferBuilder'
- // buffer, this value will remain in that buffer, even though we've removed it from the
- // list. It will still get deallocated along with everything else when that buffer gets
- // cleared or deleted, though, so there is no leak.
- --_numValues;
- }
-
- size_t numValues() const {
- return _numValues;
- }
-
- /**
- * Populate the given list of accessors with TypeTags and Values. Some Values may be "views"
- * into the memory constructed by the '_valueBufferBuilder' object, which is a caller-owned
- * object that must remain valid for as long as these accessors are to remain valid.
- */
- void readValues(std::vector<OwnedValueAccessor>* accessors) {
- auto bufferLen = _valueBufferBuilder->len();
- for (size_t i = 0; i < _numValues; ++i) {
- auto tag = _tagList[i];
- auto val = _valList[i];
-
- switch (tag) {
- // As noted in the comments for the 'appendValueBufferOffset' function, some values
- // are stored as offsets into the buffer during construction. This is where we
- // convert those offsets into pointers.
- case TypeTags::ObjectId:
- case TypeTags::StringBig:
- case TypeTags::bsonSymbol:
- case TypeTags::NumberDecimal:
- case TypeTags::bsonObject:
- case TypeTags::bsonArray:
- case TypeTags::bsonBinData:
- case TypeTags::bsonRegex:
- case TypeTags::bsonJavascript:
- case TypeTags::bsonDBPointer:
- case TypeTags::bsonCodeWScope: {
- auto offset = bitcastTo<decltype(bufferLen)>(val);
- invariant(offset < bufferLen);
- val = bitcastFrom<const char*>(_valueBufferBuilder->buf() + offset);
- break;
- }
- default:
- // 'val' is already set correctly.
- break;
+ virtual size_t numValues() const = 0;
+
+protected:
+ std::pair<TypeTags, Value> getValue(size_t index, int bufferLen) {
+ invariant(index < _numValues);
+ auto tag = _tagList[index];
+ auto val = _valList[index];
+
+ switch (tag) {
+ // As noted in the comments for the 'appendValueBufferOffset' function, some values
+ // are stored as offsets into the buffer during construction. This is where we
+ // convert those offsets into pointers.
+ case TypeTags::ObjectId:
+ case TypeTags::StringBig:
+ case TypeTags::bsonSymbol:
+ case TypeTags::NumberDecimal:
+ case TypeTags::bsonObject:
+ case TypeTags::bsonArray:
+ case TypeTags::bsonBinData:
+ case TypeTags::bsonRegex:
+ case TypeTags::bsonJavascript:
+ case TypeTags::bsonDBPointer:
+ case TypeTags::bsonCodeWScope: {
+ auto offset = bitcastTo<decltype(bufferLen)>(val);
+ invariant(offset < bufferLen);
+ val = bitcastFrom<const char*>(_valueBufferBuilder->buf() + offset);
+ break;
}
-
- invariant(i < accessors->size());
- (*accessors)[i].reset(false, tag, val);
+ default:
+ // 'val' is already set correctly.
+ break;
}
- }
-
-private:
- void unsupportedType(const char* typeDescription) {
- uasserted(4935100,
- str::stream() << "SBE does not support type present in index entry: "
- << typeDescription);
+ return {tag, val};
}
void appendValue(TypeTags tag, Value val) noexcept {
@@ -276,9 +253,122 @@ private:
BufBuilder* _valueBufferBuilder;
};
+/**
+ * Allows sbe tag/values to be read into a vector of OwnedValueAccessors.
+ */
+class OwnedValueAccessorValueBuilder : public ValueBuilder {
+public:
+ OwnedValueAccessorValueBuilder(BufBuilder* valueBufferBuilder, bool fromKeyString = false)
+ : ValueBuilder(valueBufferBuilder) {}
+ OwnedValueAccessorValueBuilder(OwnedValueAccessorValueBuilder& other) = delete;
+
+ /*
+ * Remove the last value that was streamed to this ValueBuilder.
+ */
+ void popValue() {
+ // If the removed value was a view of a string, object or array in the '_valueBufferBuilder'
+ // buffer, this value will remain in that buffer, even though we've removed it from the
+ // list. It will still get deallocated along with everything else when that buffer gets
+ // cleared or deleted, though, so there is no leak.
+ --_numValues;
+ }
+
+ size_t numValues() const override {
+ return _numValues;
+ }
+
+ /**
+ * Populate the given list of accessors with TypeTags and Values. Some Values may be "views"
+ * into the memory constructed by the '_valueBufferBuilder' object, which is a caller-owned
+ * object that must remain valid for as long as these accessors are to remain valid.
+ */
+ void readValues(std::vector<OwnedValueAccessor>* accessors) {
+ auto bufferLen = _valueBufferBuilder->len();
+ for (size_t i = 0; i < _numValues; ++i) {
+ auto [tag, val] = getValue(i, bufferLen);
+ invariant(i < accessors->size());
+ (*accessors)[i].reset(false, tag, val);
+ }
+ }
+};
+
+/**
+ * A ValueBuilder that supports reading of sbe tag/values into a MaterializedRow.
+ */
+class MaterializedRowValueBuilder : public ValueBuilder {
+public:
+ MaterializedRowValueBuilder(BufBuilder* valueBufferBuilder)
+ : ValueBuilder(valueBufferBuilder) {}
+ MaterializedRowValueBuilder(MaterializedRowValueBuilder& other) = delete;
+
+ size_t numValues() const override {
+ size_t nVals = 0;
+ size_t bufIdx = 0;
+ while (bufIdx < _numValues) {
+ auto tag = _tagList[bufIdx];
+ auto val = _valList[bufIdx];
+ if (tag == TypeTags::Boolean && !bitcastTo<bool>(val)) {
+ // Nothing case.
+ bufIdx++;
+ } else {
+ // Skip the next value
+ bufIdx += 2;
+ }
+ nVals++;
+ }
+ return nVals;
+ }
+
+ void readValues(MaterializedRow& row) {
+ auto bufferLen = _valueBufferBuilder->len();
+ size_t bufIdx = 0;
+ size_t rowIdx = 0;
+ while (bufIdx < _numValues) {
+ invariant(rowIdx < row.size());
+ auto [tagNothing, valNothing] = getValue(bufIdx++, bufferLen);
+ if (tagNothing == TypeTags::Boolean && !bitcastTo<bool>(valNothing)) {
+ row.reset(rowIdx++, false, TypeTags::Nothing, 0);
+ } else {
+ auto [tag, val] = getValue(bufIdx++, bufferLen);
+ row.reset(rowIdx++, false, tag, val);
+ }
+ }
+ }
+
+private:
+ std::pair<TypeTags, Value> getValue(size_t index, int bufferLen) {
+ auto [tag, val] = ValueBuilder::getValue(index, bufferLen);
+ if (tag == TypeTags::bsonBinData) {
+ auto binData = getBSONBinData(tag, val);
+ BufReader buf(binData, getBSONBinDataSize(tag, val));
+ auto sbeTag = buf.read<TypeTags>();
+ switch (sbeTag) {
+ case TypeTags::bsonBinData: {
+ // Return a pointer to one byte past the sbeTag in the inner BinData.
+ return {TypeTags::bsonBinData, bitcastFrom<uint8_t*>(binData + 1)};
+ }
+ case TypeTags::ksValue: {
+ // Read the KeyString size after the 'sbeTag' byte. This gets written to the
+ // buffer in 'KeyString::Value::serialize'.
+ auto ks =
+ KeyString::Value::deserialize(buf, KeyString::Version::kLatestVersion);
+ auto [ksTag, ksVal] = makeCopyKeyString(ks);
+ return {ksTag, ksVal};
+ }
+ case TypeTags::RecordId: {
+ auto ridValue = buf.read<int64_t>();
+ return {TypeTags::RecordId, bitcastFrom<int64_t>(ridValue)};
+ }
+ default:
+ MONGO_UNREACHABLE;
+ }
+ }
+ return {tag, val};
+ }
+};
+
template <typename T>
void operator<<(ValueBuilder& valBuilder, T operand) {
valBuilder.append(operand);
}
-
} // namespace mongo::sbe::value
diff --git a/src/mongo/db/exec/sbe/values/value_serialization_test.cpp b/src/mongo/db/exec/sbe/values/value_serialization_test.cpp
new file mode 100644
index 00000000000..9373750fcfd
--- /dev/null
+++ b/src/mongo/db/exec/sbe/values/value_serialization_test.cpp
@@ -0,0 +1,444 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/exec/sbe/values/bson.h"
+#include "mongo/db/exec/sbe/values/slot.h"
+#include "mongo/db/query/sbe_stage_builder_helpers.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo::sbe {
+/**
+ * This file contains tests for sbe::value::writeValueToStream.
+ */
+TEST(ValueSerializeForSorter, Serialize) {
+ auto [testDataTag, testDataVal] = sbe::value::makeNewArray();
+ sbe::value::ValueGuard testDataGuard{testDataTag, testDataVal};
+ auto testData = sbe::value::getArrayView(testDataVal);
+
+ testData->push_back(value::TypeTags::Nothing, 0);
+ testData->push_back(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(33550336));
+ testData->push_back(value::TypeTags::RecordId, value::bitcastFrom<int64_t>(8589869056));
+ testData->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(137438691328));
+ testData->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(2.305e18));
+
+ auto [decimalTag, decimalVal] =
+ value::makeCopyDecimal(Decimal128("2658455991569831744654692615953842176"));
+ testData->push_back(decimalTag, decimalVal);
+
+ testData->push_back(value::TypeTags::Date, value::bitcastFrom<int64_t>(1234));
+ testData->push_back(value::TypeTags::Timestamp, value::bitcastFrom<uint64_t>(5678));
+ testData->push_back(value::TypeTags::Boolean, value::bitcastFrom<bool>(true));
+ testData->push_back(value::TypeTags::Null, 0);
+ testData->push_back(value::TypeTags::MinKey, 0);
+ testData->push_back(value::TypeTags::MaxKey, 0);
+ testData->push_back(value::TypeTags::bsonUndefined, 0);
+
+ StringData smallString = "perfect"_sd;
+ invariant(sbe::value::canUseSmallString(smallString));
+ StringData bigString = "too big string to fit into value"_sd;
+ invariant(!sbe::value::canUseSmallString(bigString));
+ StringData smallStringWithNull = "a\0b"_sd;
+ invariant(smallStringWithNull.size() <= sbe::value::kSmallStringMaxLength);
+ StringData bigStringWithNull = "too big string \0 to fit into value"_sd;
+ invariant(bigStringWithNull.size() > sbe::value::kSmallStringMaxLength);
+
+ std::vector<StringData> stringCases = {
+ smallString,
+ smallStringWithNull,
+ bigString,
+ bigStringWithNull,
+ ""_sd,
+ "a"_sd,
+ "a\0"_sd,
+ "\0"_sd,
+ "\0\0\0"_sd,
+ };
+
+ for (const auto& stringCase : stringCases) {
+ auto [stringTag, stringVal] = value::makeNewString(stringCase);
+ testData->push_back(stringTag, stringVal);
+ }
+
+ for (const auto& stringCase : stringCases) {
+ auto [symbolTag, symbolVal] = value::makeNewBsonSymbol(stringCase);
+ testData->push_back(symbolTag, symbolVal);
+ }
+
+ auto [objectTag, objectVal] = value::makeNewObject();
+ testData->push_back(objectTag, objectVal);
+
+ auto object = value::getObjectView(objectVal);
+ object->push_back("num", value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1));
+
+ auto [arrayTag, arrayVal] = value::makeNewArray();
+ object->push_back("arr", arrayTag, arrayVal);
+
+ auto array = value::getArrayView(arrayVal);
+ array->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2));
+ array->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(3));
+
+ auto [arraySetTag, arraySetVal] = value::makeNewArraySet();
+ object->push_back("set", arraySetTag, arraySetVal);
+
+ auto arraySet = value::getArraySetView(arraySetVal);
+ arraySet->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(4));
+ arraySet->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(5));
+
+ auto [oidTag, oidVal] = value::makeCopyObjectId({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
+ testData->push_back(oidTag, oidVal);
+
+ uint8_t byteArray[] = {8, 7, 6, 5, 4, 3, 2, 1};
+ auto bson =
+ BSON("obj" << BSON("a" << 1 << "b" << 2) << "arr" << BSON_ARRAY(1 << 2 << 3) //
+ << "binDataGeneral" << BSONBinData(byteArray, sizeof(byteArray), BinDataGeneral)
+ << "binDataDeprecated"
+ << BSONBinData(byteArray, sizeof(byteArray), ByteArrayDeprecated)
+ << "malformedBinDataDeprecated" << BSONBinData(nullptr, 0, ByteArrayDeprecated));
+
+ auto [bsonObjTag, bsonObjVal] = value::copyValue(
+ value::TypeTags::bsonObject, value::bitcastFrom<const char*>(bson["obj"].value()));
+ testData->push_back(bsonObjTag, bsonObjVal);
+
+ auto [bsonArrayTag, bsonArrayVal] = value::copyValue(
+ value::TypeTags::bsonArray, value::bitcastFrom<const char*>(bson["arr"].value()));
+ testData->push_back(bsonArrayTag, bsonArrayVal);
+
+ auto [bsonBinDataGeneralTag, bsonBinDataGeneralVal] =
+ value::copyValue(value::TypeTags::bsonBinData,
+ value::bitcastFrom<const char*>(bson["binDataGeneral"].value()));
+ testData->push_back(bsonBinDataGeneralTag, bsonBinDataGeneralVal);
+
+ auto [bsonBinDataDeprecatedTag, bsonBinDataDeprecatedVal] =
+ value::copyValue(value::TypeTags::bsonBinData,
+ value::bitcastFrom<const char*>(bson["binDataDeprecated"].value()));
+ testData->push_back(bsonBinDataDeprecatedTag, bsonBinDataDeprecatedVal);
+
+ KeyString::Builder keyStringBuilder(KeyString::Version::V1);
+ keyStringBuilder.appendNumberLong(1);
+ keyStringBuilder.appendNumberLong(2);
+ keyStringBuilder.appendNumberLong(3);
+ auto [keyStringTag, keyStringVal] = value::makeCopyKeyString(keyStringBuilder.getValueCopy());
+ testData->push_back(keyStringTag, keyStringVal);
+
+ auto [plainCodeTag, plainCodeVal] =
+ value::makeCopyBsonJavascript("function test() { return 'Hello world!'; }"_sd);
+ testData->push_back(value::TypeTags::bsonJavascript, plainCodeVal);
+
+ auto [codeWithNullTag, codeWithNullVal] =
+ value::makeCopyBsonJavascript("function test() { return 'Danger\0us!'; }"_sd);
+ testData->push_back(value::TypeTags::bsonJavascript, codeWithNullVal);
+
+ auto regexBson =
+ BSON("noOptions" << BSONRegEx("[a-z]+") << "withOptions" << BSONRegEx(".*", "i")
+ << "emptyPatternNoOptions" << BSONRegEx("") << "emptyPatternWithOptions"
+ << BSONRegEx("", "s"));
+
+ for (const auto& element : regexBson) {
+ auto [copyTag, copyVal] = value::copyValue(
+ value::TypeTags::bsonRegex, value::bitcastFrom<const char*>(element.value()));
+ testData->push_back(copyTag, copyVal);
+ }
+
+ auto [dbptrTag, dbptrVal] = value::makeNewBsonDBPointer(
+ "db.c", value::ObjectIdType{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}.data());
+ testData->push_back(dbptrTag, dbptrVal);
+
+ auto [cwsTag1, cwsVal1] = value::makeNewBsonCodeWScope(
+ "function test() { return 'Hello world!'; }", BSONObj().objdata());
+ testData->push_back(cwsTag1, cwsVal1);
+
+ auto [cwsTag2, cwsVal2] = value::makeNewBsonCodeWScope(
+ "function test() { return 'Danger\0us!'; }", BSON("a" << 1).objdata());
+ testData->push_back(cwsTag2, cwsVal2);
+
+ auto [cwsTag3, cwsVal3] =
+ value::makeNewBsonCodeWScope("", BSON("b" << 2 << "c" << BSON_ARRAY(3 << 4)).objdata());
+ testData->push_back(cwsTag3, cwsVal3);
+
+ value::MaterializedRow originalRow{testData->size()};
+ for (size_t i = 0; i < testData->size(); i++) {
+ auto [tag, value] = testData->getAt(i);
+ originalRow.reset(i, false, tag, value);
+ }
+
+ BufBuilder builder;
+ originalRow.serializeForSorter(builder);
+ auto buffer = builder.release();
+
+ BufReader reader(buffer.get(), buffer.capacity());
+ value::MaterializedRow roundTripRow = value::MaterializedRow::deserializeForSorter(reader, {});
+
+ ASSERT(value::MaterializedRowEq()(originalRow, roundTripRow));
+}
+
+class ValueSerializeForKeyString : public mongo::unittest::Test {
+protected:
+ void runTest(const std::vector<std::pair<sbe::value::TypeTags, sbe::value::Value>>& inputData) {
+ value::MaterializedRow sourceRow{inputData.size()};
+ auto idx = 0;
+ for (auto& [tag, val] : inputData) {
+ sourceRow.reset(idx++, false, tag, val);
+ }
+
+ KeyString::Builder kb{KeyString::Version::kLatestVersion};
+ sourceRow.serializeIntoKeyString(kb);
+
+ auto ks = kb.getValueCopy();
+
+ BufBuilder buf;
+ value::MaterializedRow roundTripRow =
+ value::MaterializedRow::deserializeFromKeyString(ks, &buf);
+
+ ASSERT(value::MaterializedRowEq()(sourceRow, roundTripRow));
+ }
+};
+
+TEST_F(ValueSerializeForKeyString, Numerics) {
+ runTest({{value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(1)},
+ {value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2)},
+ {value::TypeTags::NumberDouble, value::bitcastFrom<double>(3.0)}});
+}
+
+TEST_F(ValueSerializeForKeyString, RecordIdMinKeyMaxKey) {
+ runTest({{value::TypeTags::MinKey, 0},
+ {value::TypeTags::MaxKey, 0},
+ {value::TypeTags::RecordId, value::bitcastFrom<int64_t>(8589869056)}});
+}
+
+TEST_F(ValueSerializeForKeyString, BoolNullAndNothing) {
+ runTest({{value::TypeTags::Nothing, 0},
+ {value::TypeTags::Null, 0},
+ {value::TypeTags::Boolean, value::bitcastFrom<bool>(false)},
+ {value::TypeTags::bsonUndefined, 0},
+ {value::TypeTags::Boolean, value::bitcastFrom<bool>(true)}});
+}
+
+TEST_F(ValueSerializeForKeyString, AllNothing) {
+ runTest({{value::TypeTags::Nothing, 0},
+ {value::TypeTags::Nothing, 0},
+ {value::TypeTags::Nothing, 0}});
+}
+
+TEST_F(ValueSerializeForKeyString, BsonArray) {
+ auto [inputTag, inputVal] = stage_builder::makeValue(
+ BSON_ARRAY(12LL << "yar" << BSON_ARRAY(2.5) << 7.5 << BSON("foo" << 23)));
+ sbe::value::ValueGuard testDataGuard{inputTag, inputVal};
+
+ runTest({{inputTag, inputVal}, {value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(0)}});
+}
+
+TEST_F(ValueSerializeForKeyString, SbeArray) {
+ auto [testDataTag, testDataVal] = sbe::value::makeNewArray();
+ sbe::value::ValueGuard testDataGuard{testDataTag, testDataVal};
+ auto testData = sbe::value::getArrayView(testDataVal);
+
+ testData->push_back(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(1));
+ testData->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2));
+ testData->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(3.0));
+
+ runTest({{testDataTag, testDataVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, DateTime) {
+ runTest({{value::TypeTags::Date, value::bitcastFrom<int64_t>(1234)},
+ {value::TypeTags::Timestamp, value::bitcastFrom<uint64_t>(5678)}});
+}
+
+TEST_F(ValueSerializeForKeyString, SmallString) {
+ StringData smallString = "perfect"_sd;
+ ASSERT(sbe::value::canUseSmallString(smallString));
+ StringData smallStringWithNull = "a\0b"_sd;
+ ASSERT(smallStringWithNull.size() <= sbe::value::kSmallStringMaxLength);
+}
+
+TEST_F(ValueSerializeForKeyString, BigString) {
+ StringData bigString = "too big string to fit into value"_sd;
+ ASSERT(!sbe::value::canUseSmallString(bigString));
+ StringData bigStringWithNull = "too big string \0 to fit into value"_sd;
+ ASSERT(bigStringWithNull.size() > sbe::value::kSmallStringMaxLength);
+
+ auto [bigStringTag, bigStringVal] = value::makeNewString(bigString);
+ sbe::value::ValueGuard testDataGuard{bigStringTag, bigStringVal};
+
+ auto [bigStringSymbolTag, bigStringSymbolVal] = value::makeNewBsonSymbol(bigString);
+ sbe::value::ValueGuard testDataGuard2{bigStringSymbolTag, bigStringSymbolVal};
+
+ auto [bigStringWithNullTag, bigStringWithNullVal] = value::makeNewString(bigStringWithNull);
+ sbe::value::ValueGuard testDataGuard3{bigStringWithNullTag, bigStringWithNullVal};
+
+ auto [bigStringSymbolNullTag, bigStringSymbolNullVal] =
+ value::makeNewBsonSymbol(bigStringWithNull);
+ sbe::value::ValueGuard testDataGuard4{bigStringSymbolNullTag, bigStringSymbolNullVal};
+
+ runTest({{bigStringTag, bigStringVal},
+ {bigStringSymbolTag, bigStringSymbolVal},
+ {bigStringWithNullTag, bigStringWithNullVal},
+ {bigStringSymbolNullTag, bigStringSymbolNullVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, EmptyAndNullTerminatedStrings) {
+
+ auto aString = "a"_sd;
+ auto aStringNullTerm = "a\0"_sd;
+ auto nullTerm = "\0"_sd;
+ auto nullTerms = "\0\0\0"_sd;
+
+ auto [aStringTag, aStringVal] = value::makeNewString(aString);
+ sbe::value::ValueGuard testDataGuard{aStringTag, aStringVal};
+
+ auto [aStringSymbolNullTag, aStringSymbolNullVal] = value::makeNewBsonSymbol(aString);
+ sbe::value::ValueGuard testDataGuard2{aStringSymbolNullTag, aStringSymbolNullVal};
+
+ auto [aStringNullTermTag, aStringNullTermVal] = value::makeNewString(aStringNullTerm);
+ sbe::value::ValueGuard testDataGuard3{aStringNullTermTag, aStringNullTermVal};
+
+ auto [aStringSymbolNullTermTag, aStringSymbolNullTermVal] =
+ value::makeNewBsonSymbol(aStringNullTerm);
+ sbe::value::ValueGuard testDataGuard4{aStringSymbolNullTermTag, aStringSymbolNullTermVal};
+
+ auto [nullTermTag, nullTermVal] = value::makeNewString(nullTerm);
+ sbe::value::ValueGuard testDataGuard5{nullTermTag, nullTermVal};
+
+ auto [symbolNullTermTag, symbolNullTermVal] = value::makeNewBsonSymbol(nullTerm);
+ sbe::value::ValueGuard testDataGuard6{symbolNullTermTag, symbolNullTermVal};
+
+ auto [nullTermsTag, nullTermsVal] = value::makeNewString(nullTerms);
+ sbe::value::ValueGuard testDataGuard7{nullTermsTag, nullTermsVal};
+
+ auto [symbolNullTermsTag, symbolNullTermsVal] = value::makeNewBsonSymbol(nullTerms);
+ sbe::value::ValueGuard testDataGuard8{symbolNullTermsTag, symbolNullTermsVal};
+
+ runTest({{aStringTag, aStringVal},
+ {aStringSymbolNullTag, aStringSymbolNullVal},
+ {aStringNullTermTag, aStringNullTermVal},
+ {aStringSymbolNullTermTag, aStringSymbolNullTermVal},
+ {nullTermTag, nullTermVal},
+ {symbolNullTermTag, symbolNullTermVal},
+ {nullTermsTag, nullTermsVal},
+ {symbolNullTermsTag, symbolNullTermsVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, SbeObject) {
+ auto [testDataTag, testDataVal] = sbe::value::makeNewObject();
+ sbe::value::ValueGuard testDataGuard{testDataTag, testDataVal};
+ auto testData = sbe::value::getObjectView(testDataVal);
+
+ testData->push_back("A", value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(1));
+ testData->push_back("b", value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2));
+ testData->push_back("C", value::TypeTags::NumberDouble, value::bitcastFrom<double>(3.0));
+
+ runTest({{testDataTag, testDataVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, BsonBinData) {
+ uint8_t byteArray[] = {8, 7, 6, 5, 4, 3, 2, 1};
+ auto bson = BSON_ARRAY(BSONBinData(byteArray, sizeof(byteArray), BinDataGeneral)
+ << BSONBinData(byteArray, sizeof(byteArray), ByteArrayDeprecated));
+
+ auto [binDataTag, binDataVal] = value::copyValue(
+ value::TypeTags::bsonBinData, value::bitcastFrom<const char*>(bson[0].value()));
+ sbe::value::ValueGuard testDataGuard{binDataTag, binDataVal};
+
+ auto [binDataTagDeprecated, binDataValDeprecated] = value::copyValue(
+ value::TypeTags::bsonBinData, value::bitcastFrom<const char*>(bson[0].value()));
+ sbe::value::ValueGuard testDataGuardDep{binDataTagDeprecated, binDataValDeprecated};
+
+ runTest({{binDataTag, binDataVal}, {binDataTagDeprecated, binDataValDeprecated}});
+}
+
+TEST_F(ValueSerializeForKeyString, KeyString) {
+ KeyString::Builder keyStringBuilder(KeyString::Version::V1);
+ keyStringBuilder.appendNumberLong(1);
+ keyStringBuilder.appendNumberLong(2);
+ keyStringBuilder.appendNumberLong(3);
+ keyStringBuilder.appendString("aaa");
+ auto ks = keyStringBuilder.getValueCopy();
+ auto [keyStringTag, keyStringVal] = value::makeCopyKeyString(ks);
+ sbe::value::ValueGuard testGuard{keyStringTag, keyStringVal};
+
+ runTest({{keyStringTag, keyStringVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, BsonJavaScript) {
+ auto [plainCodeTag, plainCodeVal] =
+ value::makeCopyBsonJavascript("function test() { return 'Hello world!'; }"_sd);
+ sbe::value::ValueGuard testDataGuard{plainCodeTag, plainCodeVal};
+
+ auto [codeWithNullTag, codeWithNullVal] =
+ value::makeCopyBsonJavascript("function test() { return 'Danger\0us!'; }"_sd);
+ sbe::value::ValueGuard testDataGuard2{codeWithNullTag, codeWithNullVal};
+
+ runTest({{plainCodeTag, plainCodeVal}, {codeWithNullTag, codeWithNullVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, BsonRegex) {
+ auto [noFlagsTag, noFlagsVal] = value::makeNewBsonRegex("[a-z]+"_sd, ""_sd);
+ sbe::value::ValueGuard testDataGuard{noFlagsTag, noFlagsVal};
+
+ auto [withFlagsTag, withFlagsVal] = value::makeNewBsonRegex(".*"_sd, "i"_sd);
+ sbe::value::ValueGuard testDataGuard2{withFlagsTag, withFlagsVal};
+
+ auto [empPatterNoFlagsTag, empPatterNoFlagsVal] = value::makeNewBsonRegex(""_sd, ""_sd);
+ sbe::value::ValueGuard testDataGuard3{empPatterNoFlagsTag, empPatterNoFlagsVal};
+
+ auto [empPatterWithFlagsTag, empPatterWithFlagsVal] = value::makeNewBsonRegex(""_sd, "s"_sd);
+ sbe::value::ValueGuard testDataGuard4{empPatterWithFlagsTag, empPatterWithFlagsVal};
+
+ runTest({{noFlagsTag, noFlagsVal},
+ {withFlagsTag, withFlagsVal},
+ {empPatterNoFlagsTag, empPatterNoFlagsVal},
+ {empPatterWithFlagsTag, empPatterWithFlagsVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, BsonDBPointer) {
+ auto [dbptrTag, dbptrVal] = value::makeNewBsonDBPointer(
+ "db.c", value::ObjectIdType{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}.data());
+ sbe::value::ValueGuard testDataGuard{dbptrTag, dbptrVal};
+
+ runTest({{dbptrTag, dbptrVal}});
+}
+
+TEST_F(ValueSerializeForKeyString, BsonCodeWScope) {
+ auto [cwsTag1, cwsVal1] = value::makeNewBsonCodeWScope(
+ "function test() { return 'Hello world!'; }", BSONObj().objdata());
+ sbe::value::ValueGuard testDataGuard{cwsTag1, cwsVal1};
+
+ auto [cwsTag2, cwsVal2] = value::makeNewBsonCodeWScope(
+ "function test() { return 'Danger\0us!'; }", BSON("a" << 1).objdata());
+ sbe::value::ValueGuard testDataGuard2{cwsTag2, cwsVal2};
+
+ auto [cwsTag3, cwsVal3] =
+ value::makeNewBsonCodeWScope("", BSON("b" << 2 << "c" << BSON_ARRAY(3 << 4)).objdata());
+ sbe::value::ValueGuard testDataGuard3{cwsTag3, cwsVal3};
+
+ runTest({{cwsTag1, cwsVal1}, {cwsTag2, cwsVal2}, {cwsTag3, cwsVal3}});
+}
+} // namespace mongo::sbe
diff --git a/src/mongo/db/exec/sbe/values/value_serialize_for_sorter_test.cpp b/src/mongo/db/exec/sbe/values/value_serialize_for_sorter_test.cpp
deleted file mode 100644
index 4e14317213e..00000000000
--- a/src/mongo/db/exec/sbe/values/value_serialize_for_sorter_test.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-/**
- * Copyright (C) 2020-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the Server Side Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/db/exec/sbe/values/slot.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo::sbe {
-/**
- * This file contains tests for sbe::value::writeValueToStream.
- */
-TEST(ValueSerializeForSorter, Serialize) {
- auto [testDataTag, testDataVal] = sbe::value::makeNewArray();
- sbe::value::ValueGuard testDataGuard{testDataTag, testDataVal};
- auto testData = sbe::value::getArrayView(testDataVal);
-
- testData->push_back(value::TypeTags::Nothing, 0);
- testData->push_back(value::TypeTags::NumberInt32, value::bitcastFrom<int32_t>(33550336));
- testData->push_back(value::TypeTags::RecordId, value::bitcastFrom<int64_t>(8589869056));
- testData->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(137438691328));
- testData->push_back(value::TypeTags::NumberDouble, value::bitcastFrom<double>(2.305e18));
-
- auto [decimalTag, decimalVal] =
- value::makeCopyDecimal(Decimal128("2658455991569831744654692615953842176"));
- testData->push_back(decimalTag, decimalVal);
-
- testData->push_back(value::TypeTags::Date, value::bitcastFrom<int64_t>(1234));
- testData->push_back(value::TypeTags::Timestamp, value::bitcastFrom<uint64_t>(5678));
- testData->push_back(value::TypeTags::Boolean, value::bitcastFrom<bool>(true));
- testData->push_back(value::TypeTags::Null, 0);
- testData->push_back(value::TypeTags::MinKey, 0);
- testData->push_back(value::TypeTags::MaxKey, 0);
- testData->push_back(value::TypeTags::bsonUndefined, 0);
-
- StringData smallString = "perfect"_sd;
- invariant(sbe::value::canUseSmallString(smallString));
- StringData bigString = "too big string to fit into value"_sd;
- invariant(!sbe::value::canUseSmallString(bigString));
- StringData smallStringWithNull = "a\0b"_sd;
- invariant(smallStringWithNull.size() <= sbe::value::kSmallStringMaxLength);
- StringData bigStringWithNull = "too big string \0 to fit into value"_sd;
- invariant(bigStringWithNull.size() > sbe::value::kSmallStringMaxLength);
-
- std::vector<StringData> stringCases = {
- smallString,
- smallStringWithNull,
- bigString,
- bigStringWithNull,
- ""_sd,
- "a"_sd,
- "a\0"_sd,
- "\0"_sd,
- "\0\0\0"_sd,
- };
-
- for (const auto& stringCase : stringCases) {
- auto [stringTag, stringVal] = value::makeNewString(stringCase);
- testData->push_back(stringTag, stringVal);
- }
-
- for (const auto& stringCase : stringCases) {
- auto [symbolTag, symbolVal] = value::makeNewBsonSymbol(stringCase);
- testData->push_back(symbolTag, symbolVal);
- }
-
- auto [objectTag, objectVal] = value::makeNewObject();
- testData->push_back(objectTag, objectVal);
-
- auto object = value::getObjectView(objectVal);
- object->push_back("num", value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(1));
-
- auto [arrayTag, arrayVal] = value::makeNewArray();
- object->push_back("arr", arrayTag, arrayVal);
-
- auto array = value::getArrayView(arrayVal);
- array->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(2));
- array->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(3));
-
- auto [arraySetTag, arraySetVal] = value::makeNewArraySet();
- object->push_back("set", arraySetTag, arraySetVal);
-
- auto arraySet = value::getArraySetView(arraySetVal);
- arraySet->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(4));
- arraySet->push_back(value::TypeTags::NumberInt64, value::bitcastFrom<int64_t>(5));
-
- auto [oidTag, oidVal] = value::makeCopyObjectId({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
- testData->push_back(oidTag, oidVal);
-
- uint8_t byteArray[] = {8, 7, 6, 5, 4, 3, 2, 1};
- auto bson =
- BSON("obj" << BSON("a" << 1 << "b" << 2) << "arr" << BSON_ARRAY(1 << 2 << 3) //
- << "binDataGeneral" << BSONBinData(byteArray, sizeof(byteArray), BinDataGeneral)
- << "binDataDeprecated"
- << BSONBinData(byteArray, sizeof(byteArray), ByteArrayDeprecated)
- << "malformedBinDataDeprecated" << BSONBinData(nullptr, 0, ByteArrayDeprecated));
-
- auto [bsonObjTag, bsonObjVal] = value::copyValue(
- value::TypeTags::bsonObject, value::bitcastFrom<const char*>(bson["obj"].value()));
- testData->push_back(bsonObjTag, bsonObjVal);
-
- auto [bsonArrayTag, bsonArrayVal] = value::copyValue(
- value::TypeTags::bsonArray, value::bitcastFrom<const char*>(bson["arr"].value()));
- testData->push_back(bsonArrayTag, bsonArrayVal);
-
- auto [bsonBinDataGeneralTag, bsonBinDataGeneralVal] =
- value::copyValue(value::TypeTags::bsonBinData,
- value::bitcastFrom<const char*>(bson["binDataGeneral"].value()));
- testData->push_back(bsonBinDataGeneralTag, bsonBinDataGeneralVal);
-
- auto [bsonBinDataDeprecatedTag, bsonBinDataDeprecatedVal] =
- value::copyValue(value::TypeTags::bsonBinData,
- value::bitcastFrom<const char*>(bson["binDataDeprecated"].value()));
- testData->push_back(bsonBinDataDeprecatedTag, bsonBinDataDeprecatedVal);
-
- KeyString::Builder keyStringBuilder(KeyString::Version::V1);
- keyStringBuilder.appendNumberLong(1);
- keyStringBuilder.appendNumberLong(2);
- keyStringBuilder.appendNumberLong(3);
- auto [keyStringTag, keyStringVal] = value::makeCopyKeyString(keyStringBuilder.getValueCopy());
- testData->push_back(keyStringTag, keyStringVal);
-
- auto [plainCodeTag, plainCodeVal] =
- value::makeCopyBsonJavascript("function test() { return 'Hello world!'; }"_sd);
- testData->push_back(value::TypeTags::bsonJavascript, plainCodeVal);
-
- auto [codeWithNullTag, codeWithNullVal] =
- value::makeCopyBsonJavascript("function test() { return 'Danger\0us!'; }"_sd);
- testData->push_back(value::TypeTags::bsonJavascript, codeWithNullVal);
-
- auto regexBson =
- BSON("noOptions" << BSONRegEx("[a-z]+") << "withOptions" << BSONRegEx(".*", "i")
- << "emptyPatternNoOptions" << BSONRegEx("") << "emptyPatternWithOptions"
- << BSONRegEx("", "s"));
-
- for (const auto& element : regexBson) {
- auto [copyTag, copyVal] = value::copyValue(
- value::TypeTags::bsonRegex, value::bitcastFrom<const char*>(element.value()));
- testData->push_back(copyTag, copyVal);
- }
-
- auto [dbptrTag, dbptrVal] = value::makeNewBsonDBPointer(
- "db.c", value::ObjectIdType{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}.data());
- testData->push_back(dbptrTag, dbptrVal);
-
- auto [cwsTag1, cwsVal1] = value::makeNewBsonCodeWScope(
- "function test() { return 'Hello world!'; }", BSONObj().objdata());
- testData->push_back(cwsTag1, cwsVal1);
-
- auto [cwsTag2, cwsVal2] = value::makeNewBsonCodeWScope(
- "function test() { return 'Danger\0us!'; }", BSON("a" << 1).objdata());
- testData->push_back(cwsTag2, cwsVal2);
-
- auto [cwsTag3, cwsVal3] =
- value::makeNewBsonCodeWScope("", BSON("b" << 2 << "c" << BSON_ARRAY(3 << 4)).objdata());
- testData->push_back(cwsTag3, cwsVal3);
-
- value::MaterializedRow originalRow{testData->size()};
- for (size_t i = 0; i < testData->size(); i++) {
- auto [tag, value] = testData->getAt(i);
- originalRow.reset(i, false, tag, value);
- }
-
- BufBuilder builder;
- originalRow.serializeForSorter(builder);
- auto buffer = builder.release();
-
- BufReader reader(buffer.get(), buffer.capacity());
- value::MaterializedRow roundTripRow = value::MaterializedRow::deserializeForSorter(reader, {});
-
- ASSERT(value::MaterializedRowEq()(originalRow, roundTripRow));
-}
-} // namespace mongo::sbe
diff --git a/src/mongo/db/storage/key_string.cpp b/src/mongo/db/storage/key_string.cpp
index 9d1d88121b2..2bd3934bb03 100644
--- a/src/mongo/db/storage/key_string.cpp
+++ b/src/mongo/db/storage/key_string.cpp
@@ -359,6 +359,13 @@ void BuilderBase<BufferT>::appendBSONElement(const BSONElement& elem, const Stri
}
template <class BufferT>
+void BuilderBase<BufferT>::appendBool(bool val) {
+ _verifyAppendingState();
+ _appendBool(val, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
void BuilderBase<BufferT>::appendString(StringData val) {
_verifyAppendingState();
_appendString(val, _shouldInvertOnAppend(), nullptr);
@@ -366,6 +373,20 @@ void BuilderBase<BufferT>::appendString(StringData val) {
}
template <class BufferT>
+void BuilderBase<BufferT>::appendSymbol(StringData val) {
+ _verifyAppendingState();
+ _appendSymbol(val, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
+void BuilderBase<BufferT>::appendCode(StringData val) {
+ _verifyAppendingState();
+ _appendCode(val, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
void BuilderBase<BufferT>::appendNumberDouble(double num) {
_verifyAppendingState();
_appendNumberDouble(num, _shouldInvertOnAppend());
@@ -380,6 +401,20 @@ void BuilderBase<BufferT>::appendNumberLong(long long num) {
}
template <class BufferT>
+void BuilderBase<BufferT>::appendNumberInt(int num) {
+ _verifyAppendingState();
+ _appendNumberInt(num, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
+void BuilderBase<BufferT>::appendNumberDecimal(Decimal128 num) {
+ _verifyAppendingState();
+ _appendNumberDecimal(num, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
void BuilderBase<BufferT>::appendNull() {
_verifyAppendingState();
_append(CType::kNullish, _shouldInvertOnAppend());
@@ -394,6 +429,13 @@ void BuilderBase<BufferT>::appendUndefined() {
}
template <class BufferT>
+void BuilderBase<BufferT>::appendCodeWString(const BSONCodeWScope& val) {
+ _verifyAppendingState();
+ _appendCodeWString(val, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
void BuilderBase<BufferT>::appendBinData(const BSONBinData& data) {
_verifyAppendingState();
_appendBinData(data, _shouldInvertOnAppend());
@@ -401,6 +443,13 @@ void BuilderBase<BufferT>::appendBinData(const BSONBinData& data) {
}
template <class BufferT>
+void BuilderBase<BufferT>::appendRegex(const BSONRegEx& val) {
+ _verifyAppendingState();
+ _appendRegex(val, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
void BuilderBase<BufferT>::appendSetAsArray(const BSONElementSet& set, const StringTransformFn& f) {
_verifyAppendingState();
_appendSetAsArray(set, _shouldInvertOnAppend(), nullptr);
@@ -422,6 +471,41 @@ void BuilderBase<BufferT>::appendDate(Date_t date) {
}
template <class BufferT>
+void BuilderBase<BufferT>::appendTimestamp(Timestamp val) {
+ _verifyAppendingState();
+ _appendTimestamp(val, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
+void BuilderBase<BufferT>::appendBytes(const void* source, size_t bytes) {
+ _verifyAppendingState();
+ _appendBytes(source, bytes, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
+void BuilderBase<BufferT>::appendDBRef(const BSONDBRef& val) {
+ _verifyAppendingState();
+ _appendDBRef(val, _shouldInvertOnAppend());
+ _elemCount++;
+}
+
+template <class BufferT>
+void BuilderBase<BufferT>::appendObject(const BSONObj& val, const StringTransformFn& f) {
+ _verifyAppendingState();
+ _appendObject(val, _shouldInvertOnAppend(), f);
+ _elemCount++;
+}
+
+template <class BufferT>
+void BuilderBase<BufferT>::appendArray(const BSONArray& val, const StringTransformFn& f) {
+ _verifyAppendingState();
+ _appendArray(val, _shouldInvertOnAppend(), f);
+ _elemCount++;
+}
+
+template <class BufferT>
void BuilderBase<BufferT>::appendDiscriminator(const Discriminator discriminator) {
// The discriminator forces this KeyString to compare Less/Greater than any KeyString with
// the same prefix of keys. As an example, this can be used to land on the first key in the
diff --git a/src/mongo/db/storage/key_string.h b/src/mongo/db/storage/key_string.h
index f0e1441004c..fc042c45bb9 100644
--- a/src/mongo/db/storage/key_string.h
+++ b/src/mongo/db/storage/key_string.h
@@ -560,15 +560,27 @@ public:
*/
void appendBSONElement(const BSONElement& elem, const StringTransformFn& f = nullptr);
+ void appendBool(bool val);
void appendString(StringData val);
+ void appendSymbol(StringData val);
void appendNumberDouble(double num);
void appendNumberLong(long long num);
+ void appendNumberInt(int num);
+ void appendNumberDecimal(Decimal128 num);
void appendNull();
void appendUndefined();
+ void appendCodeWString(const BSONCodeWScope& val);
void appendBinData(const BSONBinData& data);
+ void appendRegex(const BSONRegEx& val);
void appendSetAsArray(const BSONElementSet& set, const StringTransformFn& f = nullptr);
void appendOID(OID oid);
void appendDate(Date_t date);
+ void appendTimestamp(Timestamp val);
+ void appendBytes(const void* source, size_t bytes);
+ void appendDBRef(const BSONDBRef& val);
+ void appendObject(const BSONObj& val, const StringTransformFn& f = nullptr);
+ void appendArray(const BSONArray& val, const StringTransformFn& f = nullptr);
+ void appendCode(StringData val);
/**
* Appends a Discriminator byte and kEnd byte to a key string.