summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDrew Paroski <drew.paroski@mongodb.com>2021-04-29 04:40:26 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-04-30 17:36:14 +0000
commit6db26f07a0f4a12a5e410ca42987142f3cb4753d (patch)
treefcb2e96c8f7a5881e03e27b631dba8e1dba8980f
parent1c2ae5a2203000534aae7f3fb5b6317b60a35d02 (diff)
downloadmongo-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.cpp5
-rw-r--r--src/mongo/db/exec/sbe/values/bson.cpp27
-rw-r--r--src/mongo/db/exec/sbe/values/slot.cpp26
-rw-r--r--src/mongo/db/exec/sbe/values/value.cpp67
-rw-r--r--src/mongo/db/exec/sbe/values/value.h50
-rw-r--r--src/mongo/db/exec/sbe/values/value_builder.h10
-rw-r--r--src/mongo/db/exec/sbe/values/value_serialize_for_sorter_test.cpp8
-rw-r--r--src/mongo/db/exec/sbe/values/write_value_to_stream_test.cpp21
-rw-r--r--src/mongo/db/exec/sbe/vm/vm.h18
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};