diff options
-rw-r--r-- | src/mongo/db/exec/sbe/SConscript | 7 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/slot.cpp | 207 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/slot.h | 11 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value_builder.h | 220 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value_serialization_test.cpp | 444 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value_serialize_for_sorter_test.cpp | 199 | ||||
-rw-r--r-- | src/mongo/db/storage/key_string.cpp | 84 | ||||
-rw-r--r-- | src/mongo/db/storage/key_string.h | 12 |
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. |