diff options
author | Drew Paroski <drew.paroski@mongodb.com> | 2021-04-29 04:40:26 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-04-30 17:36:14 +0000 |
commit | 6db26f07a0f4a12a5e410ca42987142f3cb4753d (patch) | |
tree | fcb2e96c8f7a5881e03e27b631dba8e1dba8980f | |
parent | 1c2ae5a2203000534aae7f3fb5b6317b60a35d02 (diff) | |
download | mongo-6db26f07a0f4a12a5e410ca42987142f3cb4753d.tar.gz |
SERVER-56331 [SBE] Implement support for the CodeWScope BSON type
-rw-r--r-- | src/mongo/db/exec/sbe/sbe_key_string_test.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/bson.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/slot.cpp | 26 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value.cpp | 67 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value.h | 50 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value_builder.h | 10 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/value_serialize_for_sorter_test.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/values/write_value_to_stream_test.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/exec/sbe/vm/vm.h | 18 |
9 files changed, 212 insertions, 20 deletions
diff --git a/src/mongo/db/exec/sbe/sbe_key_string_test.cpp b/src/mongo/db/exec/sbe/sbe_key_string_test.cpp index 990e487a982..c485c6d2db0 100644 --- a/src/mongo/db/exec/sbe/sbe_key_string_test.cpp +++ b/src/mongo/db/exec/sbe/sbe_key_string_test.cpp @@ -86,8 +86,13 @@ TEST_F(SBEKeyStringTest, Basic) { APPEND_TWICE(bob, "date", Date_t::fromMillisSinceEpoch(123)); APPEND_TWICE(bob, "timestamp", Timestamp(123)); APPEND_TWICE(bob, "binData", BSONBinData("\xde\xad\xbe\xef", 4, BinDataGeneral)); + APPEND_TWICE(bob, "code", BSONCode("function test() { return 'Hello world!'; }")); APPEND_TWICE(bob, "dbref", BSONDBRef("db.c", OID("010203040506070809101112"))); + // Use an empty document for the CodeWScope's scope object. + auto cws = BSONCodeWScope("function test() { return 'Hello world!'; }", BSONObj()); + APPEND_TWICE(bob, "cws", cws); + bob.appendNull("null-ascending"); bob.appendNull("null-descending"); auto testValues = bob.done(); diff --git a/src/mongo/db/exec/sbe/values/bson.cpp b/src/mongo/db/exec/sbe/values/bson.cpp index 7ccb35d15d2..2d5a0cd9bcb 100644 --- a/src/mongo/db/exec/sbe/values/bson.cpp +++ b/src/mongo/db/exec/sbe/values/bson.cpp @@ -60,7 +60,7 @@ static uint8_t advanceTable[] = { 0x80, // DBPointer - Deprecated 0xff, // JavaScript code 0xff, // Symbol - Deprecated - 0x80, // JavaScript code with scope - Deprecated + 0xfe, // JavaScript code with scope - Deprecated 4, // 32-bit integer 8, // Timestamp 8, // 64-bit integer @@ -266,6 +266,13 @@ std::pair<value::TypeTags, value::Value> convertFrom(bool view, } return value::makeCopyBsonDBPointer(value::getBsonDBPointerView(value)); } + case BSONType::CodeWScope: { + auto value = value::bitcastFrom<const char*>(be); + if (view) { + return {value::TypeTags::bsonCodeWScope, value}; + } + return value::makeCopyBsonCodeWScope(value::getBsonCodeWScopeView(value)); + } default: return {value::TypeTags::Nothing, 0}; } @@ -366,8 +373,13 @@ void convertToBsonObj(ArrayBuilder& builder, value::ArrayEnumerator arr) { builder.appendCode(value::getBsonJavascriptView(val)); break; case value::TypeTags::bsonDBPointer: { - auto dbpointer = value::getBsonDBPointerView(val); - builder.append(BSONDBRef{dbpointer.ns, OID::from(dbpointer.id)}); + auto dbptr = value::getBsonDBPointerView(val); + builder.append(BSONDBRef{dbptr.ns, OID::from(dbptr.id)}); + break; + } + case value::TypeTags::bsonCodeWScope: { + auto cws = value::getBsonCodeWScopeView(val); + builder.append(BSONCodeWScope{cws.code, BSONObj(cws.scope)}); break; } default: @@ -501,8 +513,13 @@ void appendValueToBsonObj(ObjBuilder& builder, builder.appendCode(name, value::getBsonJavascriptView(val)); break; case value::TypeTags::bsonDBPointer: { - auto dbpointer = value::getBsonDBPointerView(val); - builder.appendDBRef(name, dbpointer.ns, OID::from(dbpointer.id)); + auto dbptr = value::getBsonDBPointerView(val); + builder.appendDBRef(name, dbptr.ns, OID::from(dbptr.id)); + break; + } + case value::TypeTags::bsonCodeWScope: { + auto cws = value::getBsonCodeWScopeView(val); + builder.appendCodeWScope(name, cws.code, BSONObj(cws.scope)); break; } default: diff --git a/src/mongo/db/exec/sbe/values/slot.cpp b/src/mongo/db/exec/sbe/values/slot.cpp index aa51509e551..4109e01c53e 100644 --- a/src/mongo/db/exec/sbe/values/slot.cpp +++ b/src/mongo/db/exec/sbe/values/slot.cpp @@ -183,6 +183,14 @@ static std::pair<TypeTags, Value> deserializeValue(BufReader& buf) { std::tie(tag, val) = makeNewBsonDBPointer({nsStart, nsLen}, id); break; } + case TypeTags::bsonCodeWScope: { + auto codeLen = buf.read<LittleEndian<uint32_t>>(); + auto codeStart = reinterpret_cast<const char*>(buf.skip(codeLen)); + auto scopeLen = buf.peek<LittleEndian<uint32_t>>(); + auto scope = reinterpret_cast<const char*>(buf.skip(scopeLen)); + std::tie(tag, val) = makeNewBsonCodeWScope({codeStart, codeLen}, scope); + break; + } default: MONGO_UNREACHABLE; } @@ -334,6 +342,14 @@ static void serializeValue(BufBuilder& buf, TypeTags tag, Value val) { buf.appendBuf(dbptr.id, sizeof(ObjectIdType)); break; } + case TypeTags::bsonCodeWScope: { + auto cws = getBsonCodeWScopeView(val); + buf.appendNum(static_cast<uint32_t>(cws.code.size())); + buf.appendStr(cws.code, false /* includeEndingNull */); + auto scopeLen = ConstDataView(cws.scope).read<LittleEndian<uint32_t>>(); + buf.appendBuf(cws.scope, scopeLen); + break; + } default: MONGO_UNREACHABLE; } @@ -415,11 +431,10 @@ int getApproximateSize(TypeTags tag, Value val) { result += ConstDataView(ptr).read<LittleEndian<uint32_t>>(); break; } - case TypeTags::bsonBinData: { - auto binData = getRawPointerView(val); - result += ConstDataView(binData).read<LittleEndian<uint32_t>>(); + case TypeTags::bsonBinData: + result += sizeof(uint32_t) + sizeof(char) + + ConstDataView(getRawPointerView(val)).read<LittleEndian<uint32_t>>(); break; - } case TypeTags::ksValue: { auto ks = getKeyStringView(val); result += ks->memUsageForSorter(); @@ -438,6 +453,9 @@ int getApproximateSize(TypeTags tag, Value val) { case TypeTags::bsonDBPointer: result += getBsonDBPointerView(val).byteSize(); break; + case TypeTags::bsonCodeWScope: + result += ConstDataView(getRawPointerView(val)).read<LittleEndian<uint32_t>>(); + break; default: MONGO_UNREACHABLE; } diff --git a/src/mongo/db/exec/sbe/values/value.cpp b/src/mongo/db/exec/sbe/values/value.cpp index 48a18a327c6..ea72e86f46a 100644 --- a/src/mongo/db/exec/sbe/values/value.cpp +++ b/src/mongo/db/exec/sbe/values/value.cpp @@ -100,6 +100,33 @@ std::pair<TypeTags, Value> makeNewBsonDBPointer(StringData ns, const uint8_t* id return {TypeTags::bsonDBPointer, bitcastFrom<char*>(buffer.release())}; } +std::pair<TypeTags, Value> makeNewBsonCodeWScope(StringData code, const char* scope) { + auto const codeLen = code.size(); + auto const codeLenWithNull = codeLen + sizeof(char); + auto const scopeLen = ConstDataView(scope).read<LittleEndian<uint32_t>>(); + auto const numBytes = 2 * sizeof(uint32_t) + codeLenWithNull + scopeLen; + auto buffer = std::make_unique<char[]>(numBytes); + char* ptr = buffer.get(); + + // Write length of 'numBytes' as a little-endian uint32_t. + DataView(ptr).write<LittleEndian<uint32_t>>(numBytes); + ptr += sizeof(uint32_t); + + // Write length of 'code' as a little-endian uint32_t. + DataView(ptr).write<LittleEndian<uint32_t>>(codeLenWithNull); + ptr += sizeof(uint32_t); + + // Write 'code' followed by a null terminator. + memcpy(ptr, code.rawData(), codeLen); + ptr[codeLen] = '\0'; + ptr += codeLenWithNull; + + // Write 'scope'. + memcpy(ptr, scope, scopeLen); + + return {TypeTags::bsonCodeWScope, bitcastFrom<char*>(buffer.release())}; +} + std::pair<TypeTags, Value> makeCopyKeyString(const KeyString::Value& inKey) { auto k = new KeyString::Value(inKey); return {TypeTags::ksValue, bitcastFrom<KeyString::Value*>(k)}; @@ -225,14 +252,13 @@ void releaseValue(TypeTags tag, Value val) noexcept { case TypeTags::bsonRegex: case TypeTags::bsonJavascript: case TypeTags::bsonDBPointer: + case TypeTags::bsonCodeWScope: delete[] getRawPointerView(val); break; - case TypeTags::bsonArray: - case TypeTags::bsonObject: { + case TypeTags::bsonObject: UniqueBuffer::reclaim(getRawPointerView(val)); break; - } case TypeTags::ksValue: delete getKeyStringView(val); break; @@ -361,6 +387,9 @@ void writeTagToStream(T& stream, const TypeTags tag) { case TypeTags::bsonDBPointer: stream << "bsonDBPointer"; break; + case TypeTags::bsonCodeWScope: + stream << "bsonCodeWScope"; + break; case TypeTags::ftsMatcher: stream << "ftsMatcher"; break; @@ -584,6 +613,13 @@ void writeValueToStream(T& stream, TypeTags tag, Value val) { stream << ')'; break; } + case TypeTags::bsonCodeWScope: { + const auto cws = getBsonCodeWScopeView(val); + stream << "CodeWScope(" << cws.code << ", "; + writeObjectToStream(stream, TypeTags::bsonObject, bitcastFrom<const char*>(cws.scope)); + stream << ')'; + break; + } case value::TypeTags::ftsMatcher: { auto ftsMatcher = getFtsMatcherView(val); stream << "FtsMatcher("; @@ -683,6 +719,8 @@ BSONType tagToType(TypeTags tag) noexcept { return BSONType::Code; case TypeTags::bsonDBPointer: return BSONType::DBRef; + case TypeTags::bsonCodeWScope: + return BSONType::CodeWScope; default: MONGO_UNREACHABLE; } @@ -845,6 +883,16 @@ std::size_t hashValue(TypeTags tag, Value val, const CollatorInterface* collator auto dbptr = getBsonDBPointerView(val); return hashCombine(hashCombine(hashInit(), abslHash(dbptr.ns)), hashObjectId(dbptr.id)); } + case TypeTags::bsonCodeWScope: { + auto cws = getBsonCodeWScopeView(val); + + // Collation semantics do not apply to strings nested inside the CodeWScope scope + // object, so we do not pass through the collator when computing the hash of the + // scope object. + return hashCombine( + hashCombine(hashInit(), abslHash(cws.code)), + hashValue(TypeTags::bsonObject, bitcastFrom<const char*>(cws.scope))); + } default: break; } @@ -1043,6 +1091,19 @@ std::pair<TypeTags, Value> compareValue(TypeTags lhsTag, auto result = memcmp(lhsDBPtr.id, rhsDBPtr.id, sizeof(ObjectIdType)); return {TypeTags::NumberInt32, bitcastFrom<int32_t>(compareHelper(result, 0))}; + } else if (lhsTag == TypeTags::bsonCodeWScope && rhsTag == TypeTags::bsonCodeWScope) { + auto lhsCws = getBsonCodeWScopeView(lhsValue); + auto rhsCws = getBsonCodeWScopeView(rhsValue); + if (auto result = lhsCws.code.compare(rhsCws.code); result != 0) { + return {TypeTags::NumberInt32, bitcastFrom<int32_t>(compareHelper(result, 0))}; + } + + // Special string comparison semantics do not apply to strings nested inside the + // CodeWScope scope object, so we do not pass through the string comparator. + return compareValue(TypeTags::bsonObject, + bitcastFrom<const char*>(lhsCws.scope), + TypeTags::bsonObject, + bitcastFrom<const char*>(rhsCws.scope)); } else { // Different types. auto lhsType = tagToType(lhsTag); diff --git a/src/mongo/db/exec/sbe/values/value.h b/src/mongo/db/exec/sbe/values/value.h index 41e3b6e0890..00da0de3531 100644 --- a/src/mongo/db/exec/sbe/values/value.h +++ b/src/mongo/db/exec/sbe/values/value.h @@ -120,6 +120,7 @@ enum class TypeTags : uint8_t { bsonRegex, bsonJavascript, bsonDBPointer, + bsonCodeWScope, // KeyString::Value ksValue, @@ -923,12 +924,12 @@ inline SortSpec* getSortSpecView(Value val) noexcept { struct BsonRegex { explicit BsonRegex(const char* rawValue) { pattern = rawValue; - // We add sizeof(char) to account NULL byte after pattern. + // Add sizeof(char) to account for the NULL byte after 'pattern'. flags = pattern.rawData() + pattern.size() + sizeof(char); } size_t byteSize() const { - // We add 2 * sizeof(char) to account NULL bytes after each string. + // Add 2 * sizeof(char) to account for the NULL bytes after 'pattern' and 'flags'. return pattern.size() + sizeof(char) + flags.size() + sizeof(char); } @@ -964,15 +965,16 @@ struct BsonDBPointer { explicit BsonDBPointer(const char* rawValue) { uint32_t lenWithNull = ConstDataView(rawValue).read<LittleEndian<uint32_t>>(); ns = {rawValue + sizeof(uint32_t), lenWithNull - sizeof(char)}; - id = reinterpret_cast<const uint8_t*>(rawValue) + 4 + lenWithNull; + id = reinterpret_cast<const uint8_t*>(rawValue) + sizeof(uint32_t) + lenWithNull; } size_t byteSize() const { + // Add sizeof(char) to account for the NULL byte after 'ns'. return sizeof(uint32_t) + ns.size() + sizeof(char) + sizeof(value::ObjectIdType); } StringData ns; - const uint8_t* id = nullptr; + const uint8_t* id{nullptr}; }; inline BsonDBPointer getBsonDBPointerView(Value val) noexcept { @@ -985,6 +987,44 @@ inline std::pair<TypeTags, Value> makeCopyBsonDBPointer(const BsonDBPointer& dbp return makeNewBsonDBPointer(dbptr.ns, dbptr.id); } +/** + * The BsonCodeWScope class is used to represent the CodeWScope BSON type. + * + * In BSON, a CodeWScope is encoded as a little-endian 32-bit integer ('numBytes'), followed by a + * bsonString ('code'), followed by a bsonObject ('scope'). + */ +struct BsonCodeWScope { + explicit BsonCodeWScope(const char* rawValue) { + auto dataView = ConstDataView(rawValue); + numBytes = dataView.read<LittleEndian<uint32_t>>(); + + uint32_t lenWithNull = dataView.read<LittleEndian<uint32_t>>(sizeof(uint32_t)); + code = {rawValue + 2 * sizeof(uint32_t), lenWithNull - sizeof(char)}; + scope = rawValue + 2 * sizeof(uint32_t) + lenWithNull; + } + + size_t byteSize() const { + auto scopeLen = ConstDataView(scope).read<LittleEndian<uint32_t>>(); + + // Add sizeof(char) to account for the NULL byte after 'code'. + return 2 * sizeof(uint32_t) + code.size() + sizeof(char) + scopeLen; + } + + uint32_t numBytes{0}; + StringData code; + const char* scope{nullptr}; +}; + +inline BsonCodeWScope getBsonCodeWScopeView(Value val) noexcept { + return BsonCodeWScope(getRawPointerView(val)); +} + +std::pair<TypeTags, Value> makeNewBsonCodeWScope(StringData code, const char* scope); + +inline std::pair<TypeTags, Value> makeCopyBsonCodeWScope(const BsonCodeWScope& cws) { + return makeNewBsonCodeWScope(cws.code, cws.scope); +} + std::pair<TypeTags, Value> makeCopyKeyString(const KeyString::Value& inKey); std::pair<TypeTags, Value> makeCopyJsFunction(const JsFunction&); @@ -1062,6 +1102,8 @@ inline std::pair<TypeTags, Value> copyValue(TypeTags tag, Value val) { return makeCopyBsonJavascript(getBsonJavascriptView(val)); case TypeTags::bsonDBPointer: return makeCopyBsonDBPointer(getBsonDBPointerView(val)); + case TypeTags::bsonCodeWScope: + return makeCopyBsonCodeWScope(getBsonCodeWScopeView(val)); case TypeTags::ftsMatcher: return makeCopyFtsMatcher(*getFtsMatcherView(val)); case TypeTags::sortSpec: diff --git a/src/mongo/db/exec/sbe/values/value_builder.h b/src/mongo/db/exec/sbe/values/value_builder.h index cee35b3f4e9..9ee9718aece 100644 --- a/src/mongo/db/exec/sbe/values/value_builder.h +++ b/src/mongo/db/exec/sbe/values/value_builder.h @@ -128,7 +128,12 @@ public: } void append(const BSONCodeWScope& in) { - unsupportedType("javascriptWithScope"); + appendValueBufferOffset(TypeTags::bsonCodeWScope); + _valueBufferBuilder->appendNum( + static_cast<int32_t>(4 + in.code.size() + 1 + in.scope.objsize())); + _valueBufferBuilder->appendNum(static_cast<int32_t>(in.code.size() + 1)); + _valueBufferBuilder->appendStr(in.code, true /* includeEndingNull */); + _valueBufferBuilder->appendBuf(in.scope.objdata(), in.scope.objsize()); } void append(const BSONBinData& in) { @@ -217,7 +222,8 @@ public: case TypeTags::bsonBinData: case TypeTags::bsonRegex: case TypeTags::bsonJavascript: - case TypeTags::bsonDBPointer: { + case TypeTags::bsonDBPointer: + case TypeTags::bsonCodeWScope: { auto offset = bitcastTo<decltype(bufferLen)>(val); invariant(offset < bufferLen); val = bitcastFrom<const char*>(_valueBufferBuilder->buf() + offset); 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 index 9078ef508c7..204e7b2a75a 100644 --- 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 @@ -165,9 +165,13 @@ TEST(ValueSerializeForSorter, Serialize) { testData->push_back(copyTag, copyVal); } - auto [dbpointerTag, dbpointerVal] = value::makeNewBsonDBPointer( + 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(dbpointerTag, dbpointerVal); + testData->push_back(dbptrTag, dbptrVal); + + auto [cwsTag, cwsVal] = value::makeNewBsonCodeWScope( + "function test() { return 'Hello world!'; }", BSONObj().objdata()); + testData->push_back(cwsTag, cwsVal); value::MaterializedRow originalRow{testData->size()}; for (size_t i = 0; i < testData->size(); i++) { diff --git a/src/mongo/db/exec/sbe/values/write_value_to_stream_test.cpp b/src/mongo/db/exec/sbe/values/write_value_to_stream_test.cpp index ee695903770..b63365a80e9 100644 --- a/src/mongo/db/exec/sbe/values/write_value_to_stream_test.cpp +++ b/src/mongo/db/exec/sbe/values/write_value_to_stream_test.cpp @@ -39,6 +39,7 @@ constexpr char kStringShort[] = "this is a short string!"; constexpr char kStringLong[] = "this is a super duper duper duper duper duper duper duper duper duper duper duper duper duper " "duper duper duper duper duper duper duper duper duper duper duper long string!"; +constexpr char kCode[] = "function test() { return 'Hello world!'; }"; namespace mongo::sbe { @@ -172,4 +173,24 @@ TEST(WriteValueToStream, LongBSONSymbolTest) { ASSERT_EQUALS(expectedString, oss.str()); } +TEST(WriteValueToStream, BSONCodeTest) { + auto bsonCode = BSON("code" << BSONCode(kCode)); + auto val = value::bitcastFrom<const char*>(bsonCode["code"].value()); + const std::pair<value::TypeTags, value::Value> value(value::TypeTags::bsonJavascript, val); + std::ostringstream oss; + writeToStream(oss, value); + auto expectedString = "Javascript(" + std::string(kCode) + ")"; + ASSERT_EQUALS(expectedString, oss.str()); +} + +TEST(WriteValueToStream, BSONCodeWScopeTest) { + auto bsonCodeWScope = BSON("cws" << BSONCodeWScope(kCode, BSONObj())); + auto val = value::bitcastFrom<const char*>(bsonCodeWScope["cws"].value()); + const std::pair<value::TypeTags, value::Value> value(value::TypeTags::bsonCodeWScope, val); + std::ostringstream oss; + writeToStream(oss, value); + auto expectedString = "CodeWScope(" + std::string(kCode) + ", {})"; + ASSERT_EQUALS(expectedString, oss.str()); +} + } // namespace mongo::sbe diff --git a/src/mongo/db/exec/sbe/vm/vm.h b/src/mongo/db/exec/sbe/vm/vm.h index 2b04c43f8b7..dca732a5966 100644 --- a/src/mongo/db/exec/sbe/vm/vm.h +++ b/src/mongo/db/exec/sbe/vm/vm.h @@ -154,6 +154,24 @@ std::pair<value::TypeTags, value::Value> genericCompare( auto rhsCode = value::getBsonJavascriptView(rhsValue); return {value::TypeTags::Boolean, value::bitcastFrom<bool>(op(lhsCode.compare(rhsCode), 0))}; + } else if (lhsTag == value::TypeTags::bsonCodeWScope && + rhsTag == value::TypeTags::bsonCodeWScope) { + auto lhsCws = value::getBsonCodeWScopeView(lhsValue); + auto rhsCws = value::getBsonCodeWScopeView(rhsValue); + if (auto threeWayResult = lhsCws.code.compare(rhsCws.code); threeWayResult != 0) { + return {value::TypeTags::Boolean, value::bitcastFrom<bool>(op(threeWayResult, 0))}; + } + + // Special string comparison semantics do not apply to strings nested inside the + // CodeWScope scope object, so we do not pass through the string comparator. + auto [tag, val] = value::compareValue(value::TypeTags::bsonObject, + value::bitcastFrom<const char*>(lhsCws.scope), + value::TypeTags::bsonObject, + value::bitcastFrom<const char*>(rhsCws.scope)); + if (tag == value::TypeTags::NumberInt32) { + auto result = op(value::bitcastTo<int32_t>(val), 0); + return {value::TypeTags::Boolean, value::bitcastFrom<bool>(result)}; + } } return {value::TypeTags::Nothing, 0}; |