diff options
author | Steve Tarzia <steve.tarzia@mongodb.com> | 2022-06-17 14:38:27 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-06-17 15:34:38 +0000 |
commit | 040ac37b03e9325cdc1de6dbae76172e0bb4a63a (patch) | |
tree | 03c133efe7c9649e69a4dc7fc0f172f3aa9c2bdf /src/mongo/db/pipeline/dependencies.cpp | |
parent | 4c3b1e42d06ed28752f23505956b71834c834935 (diff) | |
download | mongo-040ac37b03e9325cdc1de6dbae76172e0bb4a63a.tar.gz |
SERVER-66418 fix bug in projection created during dependency analysis
Diffstat (limited to 'src/mongo/db/pipeline/dependencies.cpp')
-rw-r--r-- | src/mongo/db/pipeline/dependencies.cpp | 51 |
1 files changed, 47 insertions, 4 deletions
diff --git a/src/mongo/db/pipeline/dependencies.cpp b/src/mongo/db/pipeline/dependencies.cpp index 8b60a31637c..d2a5563c7c7 100644 --- a/src/mongo/db/pipeline/dependencies.cpp +++ b/src/mongo/db/pipeline/dependencies.cpp @@ -37,6 +37,13 @@ namespace mongo { +std::list<std::string> DepsTracker::sortedFields() const { + // Use a special comparator to put parent fieldpaths before their children. + std::list<std::string> sortedFields(fields.begin(), fields.end()); + sortedFields.sort(PathPrefixComparator()); + return sortedFields; +} + BSONObj DepsTracker::toProjectionWithoutMetadata( TruncateToRootLevel truncationBehavior /*= TruncateToRootLevel::no*/) const { BSONObjBuilder bb; @@ -52,17 +59,21 @@ BSONObj DepsTracker::toProjectionWithoutMetadata( return bb.obj(); } + // Go through dependency fieldpaths to find the minimal set of projections that cover the + // dependencies. For example, the dependencies ["a.b", "a.b.c.g", "c", "c.d", "f"] would be + // minimally covered by the projection {"a.b": 1, "c": 1, "f": 1}. The key operation here is + // folding dependencies into ancestor dependencies, wherever possible. This is assisted by a + // special sort in DepsTracker::sortedFields that treats '.' as the first char and thus places + // parent paths directly before their children. bool idSpecified = false; std::string last; - for (const auto& field : fields) { + for (const auto& field : sortedFields()) { if (str::startsWith(field, "_id") && (field.size() == 3 || field[3] == '.')) { idSpecified = true; } if (!last.empty() && str::startsWith(field, last)) { - // we are including a parent of *it so we don't need to include this field - // explicitly. This logic relies on on set iterators going in lexicographic order so - // that a string is always directly before of all fields it prefixes. + // We are including a parent of this field, so we can skip this field. continue; } @@ -96,4 +107,36 @@ void DepsTracker::setNeedsMetadata(DocumentMetadataFields::MetaType type, bool r invariant(required || !_metadataDeps[type]); _metadataDeps[type] = required; } + +// Returns true if the lhs value should sort before the rhs, false otherwise. +bool PathPrefixComparator::operator()(const std::string& lhs, const std::string& rhs) const { + constexpr char dot = '.'; + + for (size_t pos = 0, len = std::min(lhs.size(), rhs.size()); pos < len; ++pos) { + // Below, we explicitly choose unsigned char because the usual const char& returned by + // operator[] is actually signed on x86 and will incorrectly order unicode characters. + unsigned char lchar = lhs[pos], rchar = rhs[pos]; + if (lchar == rchar) { + continue; + } + + // Consider the path delimiter '.' as being less than all other characters, so that + // paths sort directly before any paths they prefix and directly after any paths + // which prefix them. + if (lchar == dot) { + return true; + } else if (rchar == dot) { + return false; + } + + // Otherwise, default to normal character comparison. + return lchar < rchar; + } + + // If we get here, then we have reached the end of lhs and/or rhs and all of their path + // segments up to this point match. If lhs is shorter than rhs, then lhs prefixes rhs + // and should sort before it. + return lhs.size() < rhs.size(); +} + } // namespace mongo |