SERVER-120937 Decouple non-const bindViewInfo() from const ViewPolicy callback in extensions (#49112)

GitOrigin-RevId: 5a9ec32b023a93fba6157beaaeb554231161f1d9
This commit is contained in:
Daniel Segel
2026-03-06 17:02:57 -08:00
committed by MongoDB Bot
parent 6f971ec981
commit 01b3f71df0
31 changed files with 137 additions and 318 deletions

View File

@@ -1,7 +1,8 @@
/**
* This test verifies that the $addViewName extension stage correctly passes the view name
* through the extension boundary (AstNode -> LogicalStage -> ExecAggStage) and adds
* it as a field in output documents. It also verifies that the view pipeline is correctly appended when the first stage's ViewPolicy is kDefaultPrepend, and not prepended if it's kDoNothing.
* This test verifies that the $addViewName extension stage correctly passes the view name through
* the extension boundary (AstNode -> LogicalStage -> ExecAggStage) and adds it as a field in output
* documents. It also verifies that the view pipeline is correctly appended when the first stage's
* FirstStageViewApplicationPolicy is kDefaultPrepend, and not prepended if it's kDoNothing.
*
* This test also verifies that the $disallowViews extension stage correctly uasserts when
* used in a view context.
@@ -98,7 +99,7 @@ function testNestedViewsWithViewNameStage(suffix, userPipeStage) {
const outerViewNss = `${db.getName()}.${outerViewName}`;
const userPipe = [userPipeStage];
const shouldPrepend = false; // ViewPolicy is kDoNothing
const shouldPrepend = false; // FirstStageViewApplicationPolicy is kDoNothing
const outerView = {view: db[outerViewName], viewName: outerViewName, viewNss: outerViewNss};
// Verify outer view name is present when running on outerView
@@ -213,7 +214,7 @@ describe("View policy extension stages", function () {
const viewPipe = [{$addFields: {a: 1}}];
const userPipe = [{$addViewName: {}}];
const viewName = "test_view";
const shouldPrepend = false; // ViewPolicy is kDoNothing
const shouldPrepend = false; // FirstStageViewApplicationPolicy is kDoNothing
testViewNameWithTemporaryView(viewName, viewPipe, userPipe, shouldPrepend);
});
@@ -221,7 +222,7 @@ describe("View policy extension stages", function () {
const viewPipe = [{$testFoo: {}}];
const userPipe = [{$addViewName: {}}];
const viewName = "foo_view";
const shouldPrepend = false; // ViewPolicy is kDoNothing
const shouldPrepend = false; // FirstStageViewApplicationPolicy is kDoNothing
testViewNameWithTemporaryView(viewName, viewPipe, userPipe, shouldPrepend);
});
@@ -229,7 +230,7 @@ describe("View policy extension stages", function () {
const viewPipe = [{$addFields: {a: 1}}];
const userPipe = [{$testFoo: {}}, {$addViewName: {}}];
const viewName = "test_view_later";
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameWithTemporaryView(viewName, viewPipe, userPipe, shouldPrepend);
});
@@ -237,7 +238,7 @@ describe("View policy extension stages", function () {
const viewPipe = [{$addFields: {a: 1}}];
const userPipe = [{$testFoo: {}}, {$desugarAddViewName: {}}];
const viewName = "test_view_desugar";
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameWithTemporaryView(viewName, viewPipe, userPipe, shouldPrepend);
});
@@ -277,14 +278,14 @@ describe("View policy extension stages", function () {
it("should not add view name when $addViewName is in view definition", function () {
const viewPipe = [{$addViewName: {}}];
const userPipe = [{$testFoo: {}}];
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameAbsentInView("add_viewname_in_view", viewPipe, userPipe, shouldPrepend);
});
it("should not add view name when view has $desugarAddViewName in definition and desugaring should work in view def", function () {
const viewPipe = [{$desugarAddViewName: {}}];
const userPipe = [{$addFields: {a: 1}}];
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameAbsentInView("desugar_add_viewname_in_view", viewPipe, userPipe, shouldPrepend);
});
@@ -292,21 +293,21 @@ describe("View policy extension stages", function () {
it("should not add view name when $addViewName is at first position in view definition", function () {
const viewPipe = [{$addViewName: {}}, {$addFields: {a: 1}}];
const userPipe = [{$addFields: {a: 1}}];
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameAbsentInView("add_viewname_first", viewPipe, userPipe, shouldPrepend);
});
it("should not add view name when $addViewName is at middle position in view definition", function () {
const viewPipe = [{$addFields: {a: 1}}, {$addViewName: {}}, {$addFields: {a: 1}}];
const userPipe = [{$addFields: {a: 1}}];
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameAbsentInView("add_viewname_middle", viewPipe, userPipe, shouldPrepend);
});
it("should not add view name when $addViewName is at last position in view definition", function () {
const viewPipe = [{$addFields: {a: 1}}, {$addViewName: {}}];
const userPipe = [{$addFields: {a: 1}}];
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameAbsentInView("add_viewname_last", viewPipe, userPipe, shouldPrepend);
});
});
@@ -314,7 +315,7 @@ describe("View policy extension stages", function () {
it("should not add view name when view has multiple extension stages in definition", function () {
const viewPipe = [{$desugarAddViewName: {}}, {$testFoo: {}}, {$addViewName: {}}];
const userPipe = [{$addFields: {a: 1}}];
const shouldPrepend = true; // ViewPolicy is kDefaultPrepend
const shouldPrepend = true; // FirstStageViewApplicationPolicy is kDefaultPrepend
testViewNameAbsentInView("multi_extension_view", viewPipe, userPipe, shouldPrepend);
});
});
@@ -440,7 +441,7 @@ describe("View policy extension stages", function () {
const viewPipe = [{$addViewName: {}}, {$addFields: {a: 1}}];
const userPipe = [{$addViewName: {}}];
const viewName = "view_with_add_viewname";
const shouldPrepend = false; // ViewPolicy is kDoNothing
const shouldPrepend = false; // FirstStageViewApplicationPolicy is kDoNothing
testViewNameWithTemporaryView(viewName, viewPipe, userPipe, shouldPrepend);
});
@@ -448,7 +449,7 @@ describe("View policy extension stages", function () {
const viewPipe = [{$desugarAddViewName: {}}, {$addFields: {a: 1}}];
const userPipe = [{$desugarAddViewName: {}}];
const viewName = "view_with_desugar";
const shouldPrepend = false; // ViewPolicy is kDoNothing
const shouldPrepend = false; // FirstStageViewApplicationPolicy is kDoNothing
testViewNameWithTemporaryView(viewName, viewPipe, userPipe, shouldPrepend);
});
});

View File

@@ -985,7 +985,7 @@ SecondParseRequirement maybeApplyViewPipeline(const AggExState& aggExState,
// For search queries on views don't do any of the pipeline stitching that is done for
// normal views.
// TODO SERVER-115069 Remove this once search queries are desugared at LiteParsed time and
// handle the view through a custom ViewPolicy.
// handle the view through a bindViewInfo() override.
if (search_helpers::isMongotLiteParsedPipeline(*desugaredLPP)) {
LOGV2_DEBUG(11856001, 4, "Skipping view application because this is a mongot query");
return currentRequirement;

View File

@@ -204,7 +204,7 @@ private:
}
static ::MongoExtensionStatus* _hostBindViewInfo(
const ::MongoExtensionAggStageAstNode* astNode,
::MongoExtensionAggStageAstNode* astNode,
const ::MongoExtensionViewInfo* viewInfo) noexcept {
return wrapCXXAndConvertExceptionToStatus([&]() {
tasserted(11507501,

View File

@@ -196,18 +196,24 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(RegisterStageExpanderForLiteParsedExtension
DocumentSourceExtensionOptimizable::LiteParsedExpandable::stageExpander);
}
// TODO SERVER-116021 Remove this check when the extension can do this through ViewPolicy.
// TODO SERVER-116021 Remove this check when the extension can do this through bindViewInfo().
bool DocumentSourceExtensionOptimizable::LiteParsedExpandable::hasExtensionVectorSearchStage()
const {
return search_helpers::isExtensionVectorSearchStage(getParseTimeName());
}
// TODO SERVER-116021 Remove this check when the extension can do this through ViewPolicy.
// TODO SERVER-116021 Remove this check when the extension can do this through bindViewInfo().
bool DocumentSourceExtensionOptimizable::LiteParsedExpanded::hasExtensionVectorSearchStage() const {
return search_helpers::isExtensionVectorSearchStage(getParseTimeName());
}
ViewPolicy DocumentSourceExtensionOptimizable::LiteParsedExpanded::getViewPolicy() const {
FirstStageViewApplicationPolicy
DocumentSourceExtensionOptimizable::LiteParsedExpanded::getFirstStageViewApplicationPolicy() const {
return view_util::toFirstStageApplicationPolicy(_astNode->getFirstStageViewApplicationPolicy());
}
void DocumentSourceExtensionOptimizable::LiteParsedExpanded::bindViewInfo(
const ViewInfo& viewInfo, const ResolvedNamespaceMap& resolvedNamespaces) {
if (!feature_flags::gFeatureFlagExtensionViewsAndUnionWith.isEnabled()) {
// If this is not a $vectorSearch stage, views are banned entirely with the feature flag
// disabled.
@@ -223,15 +229,8 @@ ViewPolicy DocumentSourceExtensionOptimizable::LiteParsedExpanded::getViewPolicy
"$vectorSearch-as-an-extension is not allowed against views."));
}
return ViewPolicy{.policy = view_util::toFirstStageApplicationPolicy(
_astNode->getFirstStageViewApplicationPolicy()),
.callback = [this](const ViewInfo& viewInfo,
StringData stageName,
const ResolvedNamespaceMap&) {
host_connector::ViewInfoAdapter viewInfoAdapter =
host_connector::ViewInfoAdapter::fromViewInfo(viewInfo);
_astNode->bindViewInfo(viewInfoAdapter.getAsBoundaryType());
}};
auto viewInfoAdapter = host_connector::ViewInfoAdapter::fromViewInfo(viewInfo);
_astNode->bindViewInfo(viewInfoAdapter.getAsBoundaryType());
}
// static

View File

@@ -191,7 +191,7 @@ public:
}
// TODO SERVER-116021 Remove this override when extensions can handle views through
// ViewPolicy.
// bindViewInfo().
bool hasExtensionVectorSearchStage() const override;
ReadConcernSupportResult supportsReadConcern(repl::ReadConcernLevel level,
@@ -317,7 +317,10 @@ public:
bool hasExtensionVectorSearchStage() const override;
ViewPolicy getViewPolicy() const override;
FirstStageViewApplicationPolicy getFirstStageViewApplicationPolicy() const override;
void bindViewInfo(const ViewInfo& viewInfo,
const ResolvedNamespaceMap& resolvedNamespaces) override;
ReadConcernSupportResult supportsReadConcern(repl::ReadConcernLevel level,
bool isImplicitDefault) const override {
@@ -330,7 +333,7 @@ public:
}
private:
const AggStageAstNodeHandle _astNode;
AggStageAstNodeHandle _astNode;
const MongoExtensionStaticProperties _properties;
const NamespaceString _nss;
};

View File

@@ -2433,7 +2433,7 @@ public:
return _viewPolicy;
}
void bindViewInfo(const sdk::ViewInfo& viewInfo) const override {
void bindViewInfo(const sdk::ViewInfo& viewInfo) override {
_boundViewName = std::string(viewInfo.viewName());
}
@@ -2448,12 +2448,12 @@ public:
private:
MongoExtensionFirstStageViewApplicationPolicy _viewPolicy;
mutable std::string _boundViewName;
std::string _boundViewName;
};
void testViewPolicyHelper(const NamespaceString& nss,
MongoExtensionFirstStageViewApplicationPolicy extensionPolicy,
ViewPolicy::kFirstStageApplicationPolicy expectedPolicy,
FirstStageViewApplicationPolicy expectedPolicy,
const std::string& expectedViewName) {
auto astNodeImpl = ConfigurableViewPolicyTestAstNode::make(extensionPolicy);
auto* astNodeImplPtr = static_cast<ConfigurableViewPolicyTestAstNode*>(astNodeImpl.get());
@@ -2463,16 +2463,13 @@ void testViewPolicyHelper(const NamespaceString& nss,
host::DocumentSourceExtensionOptimizable::LiteParsedExpanded liteParsed(
ConfigurableViewPolicyTestAstNode::kStageName, std::move(handle), nss);
auto viewPolicy = liteParsed.getViewPolicy();
ASSERT_EQ(viewPolicy.policy, expectedPolicy);
const auto viewNss = NamespaceString::createNamespaceString_forTest("test.view"_sd);
const auto resolvedNss = NamespaceString::createNamespaceString_forTest("test.collection"_sd);
std::vector<BSONObj> viewPipeline = {BSON("$match" << BSON("x" << 1))};
ViewInfo viewInfo(viewNss, resolvedNss, std::move(viewPipeline));
viewPolicy.callback(
viewInfo, ConfigurableViewPolicyTestAstNode::kStageName, ResolvedNamespaceMap{});
ASSERT_EQ(liteParsed.getFirstStageViewApplicationPolicy(), expectedPolicy);
liteParsed.bindViewInfo(viewInfo, {});
ASSERT_EQ(astNodeImplPtr->getBoundViewName(), expectedViewName);
}
@@ -2493,7 +2490,7 @@ public:
return std::make_unique<ViewPipelineValidatorTestAstNode>();
}
void bindViewInfo(const sdk::ViewInfo& viewInfo) const override {
void bindViewInfo(const sdk::ViewInfo& viewInfo) override {
for (const auto& stage : viewInfo.viewPipeline()) {
if (stage.isEmpty()) {
continue;
@@ -2518,14 +2515,12 @@ void runViewPipelineValidatorCallback(const std::vector<BSONObj>& viewPipeline)
host::DocumentSourceExtensionOptimizable::LiteParsedExpanded liteParsed(
ViewPipelineValidatorTestAstNode::kStageName, std::move(handle), nss);
auto viewPolicy = liteParsed.getViewPolicy();
const auto viewNss = NamespaceString::createNamespaceString_forTest("test.view"_sd);
const auto resolvedNss = NamespaceString::createNamespaceString_forTest("test.coll"_sd);
std::vector<BSONObj> pipelineCopy = viewPipeline;
ViewInfo viewInfo(viewNss, resolvedNss, std::move(pipelineCopy));
viewPolicy.callback(
viewInfo, ViewPipelineValidatorTestAstNode::kStageName, ResolvedNamespaceMap{});
liteParsed.bindViewInfo(viewInfo, {});
}
TEST_F(DocumentSourceExtensionOptimizableTest,
@@ -2535,7 +2530,7 @@ TEST_F(DocumentSourceExtensionOptimizableTest,
const auto viewNss = NamespaceString::createNamespaceString_forTest("test.view"_sd);
testViewPolicyHelper(_nss,
MongoExtensionFirstStageViewApplicationPolicy::kDefaultPrepend,
ViewPolicy::kFirstStageApplicationPolicy::kDefaultPrepend,
FirstStageViewApplicationPolicy::kDefaultPrepend,
viewNss.coll().data());
}
@@ -2546,7 +2541,7 @@ TEST_F(DocumentSourceExtensionOptimizableTest,
const auto viewNss = NamespaceString::createNamespaceString_forTest("test.view"_sd);
testViewPolicyHelper(_nss,
MongoExtensionFirstStageViewApplicationPolicy::kDoNothing,
ViewPolicy::kFirstStageApplicationPolicy::kDoNothing,
FirstStageViewApplicationPolicy::kDoNothing,
viewNss.coll().data());
}

View File

@@ -103,13 +103,13 @@ inline boost::optional<StageConstraints::HostTypeRequirement> toHostTypeRequirem
} // namespace static_properties_util
namespace view_util {
inline ViewPolicy::kFirstStageApplicationPolicy toFirstStageApplicationPolicy(
inline FirstStageViewApplicationPolicy toFirstStageApplicationPolicy(
MongoExtensionFirstStageViewApplicationPolicy policy) {
switch (policy) {
case MongoExtensionFirstStageViewApplicationPolicy::kDefaultPrepend:
return ViewPolicy::kFirstStageApplicationPolicy::kDefaultPrepend;
return FirstStageViewApplicationPolicy::kDefaultPrepend;
case MongoExtensionFirstStageViewApplicationPolicy::kDoNothing:
return ViewPolicy::kFirstStageApplicationPolicy::kDoNothing;
return FirstStageViewApplicationPolicy::kDoNothing;
}
MONGO_UNREACHABLE_TASSERT(11507600);
}

View File

@@ -696,7 +696,7 @@ typedef struct MongoExtensionAggStageAstNodeVTable {
* Ownership of the BSON members is not transferred over the API boundary, so the extension must
* copy if they need to persist beyond the scope of bind_view_info().
*/
MongoExtensionStatus* (*bind_view_info)(const MongoExtensionAggStageAstNode* astNode,
MongoExtensionStatus* (*bind_view_info)(MongoExtensionAggStageAstNode* astNode,
const MongoExtensionViewInfo* viewInfo);
} MongoExtensionAggStageAstNodeVTable;

View File

@@ -272,7 +272,7 @@ public:
// Note that viewInfo is non-owning, meaning if an extension wants to access the metadata
// outside of the call to bindViewInfo, it must make its own copy of it. There are no guarantees
// on the lifetime outside of the scope of this function.
virtual void bindViewInfo(const ViewInfo& viewInfo) const {
virtual void bindViewInfo(const ViewInfo& viewInfo) {
// Default implementation is a no-op.
}
@@ -383,7 +383,7 @@ private:
}
static ::MongoExtensionStatus* _extBindViewInfo(
const ::MongoExtensionAggStageAstNode* astNode,
::MongoExtensionAggStageAstNode* astNode,
const ::MongoExtensionViewInfo* viewInfo) noexcept {
return wrapCXXAndConvertExceptionToStatus([&]() {
sdk_tassert(11905600, "Provided view info was invalid", viewInfo != nullptr);
@@ -399,7 +399,7 @@ private:
byteViewAsStringView(viewInfo->viewNamespace.collectionName),
std::move(stages));
static_cast<const ExtensionAggStageAstNodeAdapter*>(astNode)->getImpl().bindViewInfo(
static_cast<ExtensionAggStageAstNodeAdapter*>(astNode)->getImpl().bindViewInfo(
viewInfoWrapper);
});
}

View File

@@ -1714,7 +1714,7 @@ public:
MONGO_UNIMPLEMENTED;
}
void bindViewInfo(const ViewInfo& viewInfo) const override {
void bindViewInfo(const ViewInfo& viewInfo) override {
_boundDbName = std::string(viewInfo.dbName());
_boundViewName = std::string(viewInfo.viewName());
_boundPipeline = viewInfo.viewPipeline();
@@ -1737,9 +1737,9 @@ public:
}
private:
mutable std::string _boundDbName;
mutable std::string _boundViewName;
mutable std::vector<BSONObj> _boundPipeline;
std::string _boundDbName;
std::string _boundViewName;
std::vector<BSONObj> _boundPipeline;
};
TEST_F(AggStageTest, ExtensionAstNodeCanReturnDefaultViewPolicy) {

View File

@@ -77,7 +77,7 @@ AggStageAstNodeAPI::getFirstStageViewApplicationPolicy() const {
return policy;
}
void AggStageAstNodeAPI::bindViewInfo(const ::MongoExtensionViewInfo& viewInfo) const {
void AggStageAstNodeAPI::bindViewInfo(const ::MongoExtensionViewInfo& viewInfo) {
invokeCAndConvertStatusToException(
[&]() { return _vtable().bind_view_info(get(), &viewInfo); });
}

View File

@@ -95,7 +95,7 @@ public:
/**
* Binds view information to this AST node.
*/
void bindViewInfo(const ::MongoExtensionViewInfo& viewInfo) const;
void bindViewInfo(const ::MongoExtensionViewInfo& viewInfo);
static void assertVTableConstraints(const VTable_t& vtable) {
tassert(11217601, "AggStageAstNode 'get_name' is null", vtable.get_name != nullptr);

View File

@@ -164,7 +164,7 @@ public:
}
}
void bindViewInfo(const sdk::ViewInfo& viewInfo) const override {
void bindViewInfo(const sdk::ViewInfo& viewInfo) override {
_viewName = std::string(viewInfo.viewName());
}
@@ -186,7 +186,7 @@ public:
private:
const mongo::BSONObj _arguments;
mutable std::string _viewName;
std::string _viewName;
};
DEFAULT_PARSE_NODE(AddViewName)

View File

@@ -47,7 +47,7 @@ public:
DisallowViewAstNode(std::string_view stageName, const mongo::BSONObj& arguments)
: sdk::AggStageAstNode(stageName), _arguments(arguments.getOwned()) {}
void bindViewInfo(const sdk::ViewInfo& viewInfo) const override {
void bindViewInfo(const sdk::ViewInfo& viewInfo) override {
std::string message = str::stream() << "Stage " << std::string(getName())
<< " does not support views. Attempted to use in view: "
<< std::string(viewInfo.viewName());

View File

@@ -147,7 +147,7 @@ public:
_arguments(arguments.getOwned()),
_storedViewPipeline(std::move(storedViewPipeline)) {}
void bindViewInfo(const sdk::ViewInfo& viewInfo) const override {
void bindViewInfo(const sdk::ViewInfo& viewInfo) override {
_storedViewPipeline.clear();
for (const auto& stage : viewInfo.viewPipeline()) {
if (stage.isEmpty()) {
@@ -187,7 +187,7 @@ private:
}
const BSONObj _arguments;
mutable std::vector<BSONObj> _storedViewPipeline;
std::vector<BSONObj> _storedViewPipeline;
};
DEFAULT_PARSE_NODE(ValidateViewPipeline)

View File

@@ -283,15 +283,4 @@ ViewInfo ViewInfo::clone() const {
return out;
}
DisallowViewsPolicy::DisallowViewsPolicy()
: ViewPolicy(kFirstStageApplicationPolicy::kDoNothing,
[](const ViewInfo&, StringData stageName, const ResolvedNamespaceMap&) {
uasserted(
ErrorCodes::CommandNotSupportedOnView,
std::string(str::stream() << stageName << " is not supported on views."));
}) {}
DisallowViewsPolicy::DisallowViewsPolicy(ViewPolicyCallbackFn&& fn)
: ViewPolicy(kFirstStageApplicationPolicy::kDoNothing, std::move(fn)) {}
} // namespace mongo

View File

@@ -167,61 +167,17 @@ public:
std::unique_ptr<LiteParsedPipeline> viewPipeline;
};
using ViewPolicyCallbackFn =
std::function<void(const ViewInfo&, StringData, const ResolvedNamespaceMap&)>;
/**
* Indicates how this stage will interact with a view. LiteParsedDocumentSources that
* wish to perform custom behavior for views should override the callback function.
* Describes what the pipeline as a whole should do with a view on the main aggregate
* collection, if this stage is at the front of the pipeline.
*/
struct ViewPolicy {
// Describes what the pipeline as a whole should do with a view on the main aggregate
// collection, if this stage is at the front of the pipeline.
enum class kFirstStageApplicationPolicy {
// If this stage is at the front of the pipeline, the pipeline should
// prepend the view.
kDefaultPrepend,
// If this stage is at the front of the pipeline, the pipeline should not
// prepend the view. The stage will apply the view pipeline itself internally.
kDoNothing,
} policy = kFirstStageApplicationPolicy::kDefaultPrepend;
// Offers a stage the chance to receive/bind to a view definition on the command's resolved view
// definitions. Receives resolved view information, the stage name, and the resolved namespaces
// map.
ViewPolicyCallbackFn callback = [](const ViewInfo&, StringData, const ResolvedNamespaceMap&) {
// Default callback is a no-op.
};
};
/**
* Default view policy for aggregation stages. This policy allows views to be used with the stage
* by prepending the view pipeline when the stage is at the front of the pipeline.
*
* When a stage uses DefaultViewPolicy:
* - If the stage is at the front of the pipeline and the main collection is a view, the view
* pipeline will be prepended to the aggregation pipeline.
* - The callback is a no-op, meaning the stage does not need to perform any special handling
* when a view is encountered.
*
* This is the default behavior for most aggregation stages that support views.
*/
struct DefaultViewPolicy : ViewPolicy {};
/**
* View policy that disallows views for aggregation stages. This policy prevents views from being
* used with the stage by throwing an error when a view is encountered.
*
* When a stage uses DisallowViewsPolicy:
* - The policy is set to kDoNothing, meaning the view pipeline will not be automatically prepended.
* - The callback throws a CommandNotSupportedOnView error (or a custom error if a custom callback
* is provided) when a view is detected.
*
* Use this policy for stages that cannot operate on views.
*/
struct DisallowViewsPolicy : public ViewPolicy {
DisallowViewsPolicy();
DisallowViewsPolicy(ViewPolicyCallbackFn&&);
enum class FirstStageViewApplicationPolicy {
// If this stage is at the front of the pipeline, the pipeline should
// prepend the view.
kDefaultPrepend,
// If this stage is at the front of the pipeline, the pipeline should not
// prepend the view. The stage will apply the view pipeline itself internally.
kDoNothing,
};
/**
@@ -443,11 +399,16 @@ public:
virtual std::unique_ptr<StageParams> getStageParams() const = 0;
virtual FirstStageViewApplicationPolicy getFirstStageViewApplicationPolicy() const {
return FirstStageViewApplicationPolicy::kDefaultPrepend;
}
/**
* Retrieve the ViewPolicy for this stage.
* Bind view information to this stage. Called by handleView() when running against a view.
*/
virtual ViewPolicy getViewPolicy() const {
return DefaultViewPolicy{};
virtual void bindViewInfo(const ViewInfo& viewInfo,
const ResolvedNamespaceMap& resolvedNamespaces) {
// Default implementation is a no-op.
}
/**
@@ -517,7 +478,8 @@ public:
/**
* Returns true if this is a vector search stage ($vectorSearch) or has a nested $vectorSearch.
* TODO SERVER-116021 Remove this override when extensions can handle views through ViewPolicy.
* TODO SERVER-116021 Remove this override when extensions can handle views through
* bindViewInfo().
*/
virtual bool hasExtensionVectorSearchStage() const {
return false;

View File

@@ -336,66 +336,19 @@ TEST(StageParams, CanGetStageParamsFromLiteParsed) {
ASSERT_NE(baseParams->getId(), 0);
}
TEST(ViewPolicy, CanSpecifyDefaultViewPolicyWithNoCustomReadFunction) {
TEST(ViewPolicy, DefaultPolicyReturnsDefaultPrepend) {
BSONObj spec = BSON("$testStage" << BSONObj());
auto lp = TestLiteParsed(spec.firstElement());
ASSERT_EQ(lp.getViewPolicy().policy, ViewPolicy::kFirstStageApplicationPolicy::kDefaultPrepend);
ASSERT_EQ(lp.getFirstStageViewApplicationPolicy(),
FirstStageViewApplicationPolicy::kDefaultPrepend);
}
TEST(ViewPolicy, CanSpecifyViewPolicyWithCustomReadFunction) {
// Make a custom view function that just sets a bool flag indicating that the function has been
// called successfully.
bool fired = false;
auto viewFunc = [&fired](const ViewInfo&, StringData stageName, const ResolvedNamespaceMap&) {
ASSERT_FALSE(fired);
fired = true;
};
TEST(ViewPolicy, CanSpecifyDoNothingPolicy) {
BSONObj spec = BSON("$testStage" << BSONObj());
auto lp =
TestLiteParsed(spec.firstElement(),
ViewPolicy{.policy = ViewPolicy::kFirstStageApplicationPolicy::kDoNothing,
.callback = viewFunc});
auto lp = TestLiteParsed(spec.firstElement(), FirstStageViewApplicationPolicy::kDoNothing);
auto viewPolicy = lp.getViewPolicy();
ASSERT_EQ(viewPolicy.policy, ViewPolicy::kFirstStageApplicationPolicy::kDoNothing);
// Make sure that we can actually call the custom view function.
viewPolicy.callback({}, "", ResolvedNamespaceMap{});
ASSERT_TRUE(fired);
}
TEST(ViewPolicy, CanSpecifyDisallowViewPolicyDefaultValues) {
BSONObj spec = BSON("$testStage" << BSONObj());
auto lp = TestLiteParsed(spec.firstElement(), DisallowViewsPolicy{});
auto viewPolicy = lp.getViewPolicy();
ASSERT_EQ(viewPolicy.policy, ViewPolicy::kFirstStageApplicationPolicy::kDoNothing);
ASSERT_THROWS_CODE_AND_WHAT(viewPolicy.callback({}, "$test", ResolvedNamespaceMap{}),
DBException,
ErrorCodes::CommandNotSupportedOnView,
"$test is not supported on views.");
}
TEST(ViewPolicy, CanSpecifyDisallowViewPolicyCustomValues) {
BSONObj spec = BSON("$testStage" << BSONObj());
const int kCustomErrorCode = 11505700;
const std::string kCustomErrorMsg = "test error message";
auto lp = TestLiteParsed(spec.firstElement(),
DisallowViewsPolicy{[&](const auto&, auto, const auto&) {
uasserted(kCustomErrorCode, kCustomErrorMsg);
}});
auto viewPolicy = lp.getViewPolicy();
ASSERT_EQ(viewPolicy.policy, ViewPolicy::kFirstStageApplicationPolicy::kDoNothing);
ASSERT_THROWS_CODE_AND_WHAT(viewPolicy.callback({}, "$test", ResolvedNamespaceMap{}),
DBException,
kCustomErrorCode,
kCustomErrorMsg);
ASSERT_EQ(lp.getFirstStageViewApplicationPolicy(), FirstStageViewApplicationPolicy::kDoNothing);
}
TEST(LiteParsedPipelineClone, CloneCreatesDeepCopy) {

View File

@@ -242,20 +242,14 @@ void LiteParsedPipeline::_stitchFront(LiteParsedPipeline&& prefix) {
void LiteParsedPipeline::handleView(const ViewInfo& viewInfo,
const ResolvedNamespaceMap& resolvedNamespaces) {
for (const auto& stage : _stageSpecs) {
// Let each stage bind to the view info.
auto thisPolicy = stage->getViewPolicy();
thisPolicy.callback(viewInfo, stage->getParseTimeName(), resolvedNamespaces);
for (auto& stage : _stageSpecs) {
stage->bindViewInfo(viewInfo, resolvedNamespaces);
}
// Determine whether the pipeline should automatically prepend the view pipeline. This decision
// is made solely by the first stage, if any. Other stages' first-stage policies are not
// consulted here.
const auto firstStagePolicy = _stageSpecs.empty()
? ViewPolicy::kFirstStageApplicationPolicy::kDefaultPrepend
: _stageSpecs.front()->getViewPolicy().policy;
if (firstStagePolicy == ViewPolicy::kFirstStageApplicationPolicy::kDefaultPrepend) {
? FirstStageViewApplicationPolicy::kDefaultPrepend
: _stageSpecs.front()->getFirstStageViewApplicationPolicy();
if (firstStagePolicy == FirstStageViewApplicationPolicy::kDefaultPrepend) {
// If the first stage doesn't explicitly disallow it, clone and prepend the desugared view
// pipeline to the current pipeline.
auto clonedViewPipe = viewInfo.getViewPipeline();

View File

@@ -398,15 +398,16 @@ public:
/**
* Applies view semantics to this pipeline.
*
* Each stage is given a chance to validate the view or modify itself with the view via its
* ViewPolicy callback. Whether the view pipeline is automatically prepended is determined
* solely by the first stage: if its policy is kDefaultPrepend (or the pipeline is empty), the
* desugared view pipeline is cloned and prepended; otherwise it is not.
* Each stage is given a chance to validate the view or modify itself via its bindViewInfo()
* override. Whether the view pipeline is automatically prepended is determined solely by the
* first stage's FirstStageViewApplicationPolicy: if it is kDefaultPrepend (or the
* pipeline is empty), the desugared view pipeline is cloned and prepended; otherwise it is not.
*
* The provided ViewInfo is not mutated. The resolvedNamespaces map is passed to each stage's
* ViewPolicy callback to provide access to all resolved namespaces in the aggregation. This
* bindViewInfo() to provide access to all resolved namespaces in the aggregation. This
* will be used for view resolution in secondary namespaces (e.g. `from` field in $unionWith or
* $lookup). */
* $lookup).
*/
void handleView(const ViewInfo& viewInfo, const ResolvedNamespaceMap& resolvedNamespaces);
private:

View File

@@ -32,7 +32,6 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/lite_parsed_document_source.h"
#include "mongo/db/pipeline/resolved_namespace.h"
#include "mongo/db/pipeline/test_lite_parsed.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
@@ -239,17 +238,14 @@ TEST(LiteParsedPipelineTest, ClonedPipelineWithViewStagesPreservesOwnership) {
} // namespace
// Parses { <stageName>: {} } into a TestLiteParsed whose ViewPolicy is fixed.
std::unique_ptr<LiteParsedDocumentSource> createViewPolicyDefaultParser(
const NamespaceString& nss, const BSONElement& spec, const LiteParserOptions& options) {
return std::make_unique<TestLiteParsed>(
spec, ViewPolicy{.policy = ViewPolicy::kFirstStageApplicationPolicy::kDefaultPrepend});
return std::make_unique<TestLiteParsed>(spec, FirstStageViewApplicationPolicy::kDefaultPrepend);
}
std::unique_ptr<LiteParsedDocumentSource> createViewPolicyDoNothingParser(
const NamespaceString& nss, const BSONElement& spec, const LiteParserOptions& options) {
return std::make_unique<TestLiteParsed>(
spec, ViewPolicy{.policy = ViewPolicy::kFirstStageApplicationPolicy::kDoNothing});
return std::make_unique<TestLiteParsed>(spec, FirstStageViewApplicationPolicy::kDoNothing);
}
class LiteParsedPipelineViewPolicyTest : public unittest::Test {
@@ -339,71 +335,4 @@ TEST_F(LiteParsedPipelineViewPolicyTest, PrependWhenDefaultPrependIsTrueForAllSt
ASSERT_EQ(out[2]->getParseTimeName(), _defaultStageName);
}
/**
* Test that handleView passes resolvedNamespaces to stage ViewPolicy callbacks when the pipeline
* has involved namespaces (e.g., from $lookup, $graphLookup).
*/
TEST_F(LiteParsedPipelineViewPolicyTest, HandleViewPassesResolvedNamespacesToStageCallbacks) {
const std::string kResolveNsCheckStage = "$resolveNsCheck";
ResolvedNamespaceMap receivedResolvedNamespaces;
auto createResolveNsCheckParser =
[&receivedResolvedNamespaces](
const NamespaceString& nss, const BSONElement& spec, const LiteParserOptions& options) {
return std::make_unique<TestLiteParsed>(
spec,
ViewPolicy{.policy = ViewPolicy::kFirstStageApplicationPolicy::kDefaultPrepend,
.callback = [&receivedResolvedNamespaces](
const ViewInfo&,
StringData,
const ResolvedNamespaceMap& resolvedNs) {
receivedResolvedNamespaces = resolvedNs;
}});
};
LiteParsedDocumentSource::registerParser(
kResolveNsCheckStage,
{.parser = createResolveNsCheckParser,
.allowedWithApiStrict = AllowedWithApiStrict::kAlways,
.allowedWithClientType = AllowedWithClientType::kAny});
auto guard = ScopeGuard([name = kResolveNsCheckStage] {
LiteParsedPipelineViewPolicyTest::unregisterParserByName(name);
});
// Build a ResolvedNamespaceMap simulating resolution of involved namespaces (e.g., a $lookup
// foreign collection "test.foreign" resolved to a view "test.otherView" with its pipeline).
const auto kForeignNss = NamespaceString::createNamespaceString_forTest("test.foreign"_sd);
const auto kUnderlyingNss =
NamespaceString::createNamespaceString_forTest("test.underlyingColl"_sd);
ResolvedNamespaceMap resolvedNamespaces;
resolvedNamespaces[kForeignNss] =
ResolvedNamespace(kUnderlyingNss, {BSON("$match" << BSON("x" << 1)), BSON("$limit" << 10)});
resolvedNamespaces[kResolvedNss] = ResolvedNamespace(kResolvedNss, {});
std::vector<BSONObj> userStages = {BSON(kResolveNsCheckStage << BSONObj())};
LiteParsedPipeline pipeline(kTestNss, userStages);
std::vector<BSONObj> viewStages = {BSON("$match" << BSON("a" << 1))};
const auto viewInfo = createTestViewInfo(std::move(viewStages));
pipeline.handleView(viewInfo, resolvedNamespaces);
// Verify the stage's ViewPolicy callback received the resolved namespaces.
ASSERT_EQ(receivedResolvedNamespaces.size(), 2U);
ASSERT_TRUE(receivedResolvedNamespaces.contains(kForeignNss));
ASSERT_TRUE(receivedResolvedNamespaces.contains(kResolvedNss));
ASSERT_EQ(receivedResolvedNamespaces.at(kForeignNss).ns, kUnderlyingNss);
ASSERT_EQ(receivedResolvedNamespaces.at(kForeignNss).pipeline.size(), 2U);
ASSERT_EQ(receivedResolvedNamespaces.at(kResolvedNss).ns, kResolvedNss);
ASSERT_TRUE(receivedResolvedNamespaces.at(kResolvedNss).pipeline.empty());
// Also verify view pipeline was prepended as usual.
const auto& stages = pipeline.getStages();
ASSERT_EQ(stages.size(), 2U);
ASSERT_EQ(stages[0]->getParseTimeName(), "$match");
ASSERT_EQ(stages[1]->getParseTimeName(), kResolveNsCheckStage);
}
} // namespace mongo

View File

@@ -227,8 +227,7 @@ std::unique_ptr<Pipeline> makePipelineFromViewDefinition(
subPipelineExpCtx->addResolvedNamespaces(viewLiteParsedPipeline.getInvolvedNamespaces());
}
// Create a LiteParsedPipeline for the user pipeline and apply view handling with ViewPolicy
// callbacks.
// Create a LiteParsedPipeline for the user pipeline and apply view handling via bindViewInfo().
LiteParsedPipeline userLiteParsedPipeline(
makeLiteParsedPipeline(subPipelineExpCtx, currentPipeline));
if (opts.desugar) {

View File

@@ -586,14 +586,12 @@ TEST_F(InternalSearchIdLookupBuildDocumentSourceTest,
auto liteParsed =
LiteParsedInternalSearchIdLookUp::parse(kTestNss, spec.firstElement(), LiteParserOptions{});
// Simulate what handleView() does: invoke the ViewPolicy callback with a view pipeline.
// Simulate what handleView() does: invoke bindViewInfo with a view pipeline.
std::vector<BSONObj> viewPipeline = {BSON("$match" << BSON("active" << true)),
BSON("$sort" << BSON("createdAt" << -1))};
ViewInfo viewInfo(kViewNss, kResolvedNss, viewPipeline);
auto viewPolicy = liteParsed->getViewPolicy();
viewPolicy.callback(
viewInfo, DocumentSourceInternalSearchIdLookUp::kStageName, ResolvedNamespaceMap{});
liteParsed->bindViewInfo(viewInfo, {});
// Now use the registry to build the DocumentSource from the LiteParsed.
auto docSources = buildDocumentSource(*liteParsed, getExpCtx());
@@ -641,7 +639,7 @@ TEST_F(InternalSearchIdLookupBuildDocumentSourceTest,
auto liteParsed =
LiteParsedInternalSearchIdLookUp::parse(kTestNss, spec.firstElement(), LiteParserOptions{});
// Don't invoke the ViewPolicy callback.
// Don't invoke bindViewInfo.
auto docSources = buildDocumentSource(*liteParsed, getExpCtx());

View File

@@ -86,12 +86,12 @@ public:
return _ownedSpec;
}
ViewPolicy getViewPolicy() const final {
return ViewPolicy{
.policy = ViewPolicy::kFirstStageApplicationPolicy::kDoNothing,
.callback = [this](const ViewInfo& viewInfo, StringData, const ResolvedNamespaceMap&) {
_ownedSpec.setViewPipeline(viewInfo.getSerializedViewPipeline());
}};
FirstStageViewApplicationPolicy getFirstStageViewApplicationPolicy() const override {
return FirstStageViewApplicationPolicy::kDoNothing;
}
void bindViewInfo(const ViewInfo& viewInfo, const ResolvedNamespaceMap&) override {
_ownedSpec.setViewPipeline(viewInfo.getSerializedViewPipeline());
}
LiteParsedInternalSearchIdLookUp(DocumentSourceIdLookupSpec spec)
@@ -99,7 +99,7 @@ public:
_ownedSpec(std::move(spec)) {}
private:
mutable DocumentSourceIdLookupSpec _ownedSpec;
DocumentSourceIdLookupSpec _ownedSpec;
};
} // namespace mongo

View File

@@ -45,23 +45,22 @@ const NamespaceString kResolvedNss =
NamespaceString::createNamespaceString_forTest("unittests.resolved_coll");
/**
* Tests for LiteParsed::getViewPolicy() and LiteParsed::getStageParams() to verify the
* ViewPolicy callback correctly stores view pipeline BSON for use in desugaring.
* Tests for LiteParsed::bindViewInfo() and LiteParsed::getStageParams() to verify that
* bindViewInfo correctly stores view pipeline BSON for use in desugaring.
*/
class LiteParsedInternalSearchIdLookUpTest : public unittest::Test {};
TEST_F(LiteParsedInternalSearchIdLookUpTest, GetViewPolicyReturnsDoNothingPolicy) {
TEST_F(LiteParsedInternalSearchIdLookUpTest, GetFirstStageViewApplicationPolicyReturnsDoNothing) {
BSONObj spec = BSON(LiteParsedInternalSearchIdLookUp::kStageName << BSON("limit" << 100LL));
auto liteParsed =
LiteParsedInternalSearchIdLookUp::parse(kTestNss, spec.firstElement(), LiteParserOptions{});
auto viewPolicy = liteParsed->getViewPolicy();
// The policy should be kDoNothing since IdLookup handles view resolution itself.
ASSERT_EQ(viewPolicy.policy, ViewPolicy::kFirstStageApplicationPolicy::kDoNothing);
ASSERT_EQ(liteParsed->getFirstStageViewApplicationPolicy(),
FirstStageViewApplicationPolicy::kDoNothing);
}
TEST_F(LiteParsedInternalSearchIdLookUpTest, ViewPolicyCallbackStoresViewPipelineBson) {
TEST_F(LiteParsedInternalSearchIdLookUpTest, BindViewInfoStoresViewPipelineBson) {
BSONObj spec = BSON(LiteParsedInternalSearchIdLookUp::kStageName << BSON("limit" << 100LL));
auto liteParsed =
LiteParsedInternalSearchIdLookUp::parse(kTestNss, spec.firstElement(), LiteParserOptions{});
@@ -71,10 +70,7 @@ TEST_F(LiteParsedInternalSearchIdLookUpTest, ViewPolicyCallbackStoresViewPipelin
BSON("$project" << BSON("name" << 1 << "status" << 1))};
ViewInfo viewInfo(kViewNss, kResolvedNss, viewPipeline);
// Invoke the callback.
auto viewPolicy = liteParsed->getViewPolicy();
viewPolicy.callback(
viewInfo, LiteParsedInternalSearchIdLookUp::kStageName, ResolvedNamespaceMap{});
liteParsed->bindViewInfo(viewInfo, {});
// Now getStageParams should return params with the view pipeline BSON.
auto stageParams = liteParsed->getStageParams();
@@ -100,7 +96,7 @@ TEST_F(LiteParsedInternalSearchIdLookUpTest, GetStageParamsReturnsLimitFromSpec)
// Verify the limit was extracted correctly from the spec.
ASSERT(typedParams->ownedSpec.getLimit());
ASSERT_EQ(typedParams->ownedSpec.getLimit().get(), 42);
// Without view callback, the view pipeline should be empty.
// Without bindViewInfo call, the view pipeline should be empty.
ASSERT_FALSE(typedParams->ownedSpec.getViewPipeline());
}
@@ -117,7 +113,7 @@ TEST_F(LiteParsedInternalSearchIdLookUpTest, GetStageParamsReturnsNothingWhenNot
ASSERT_FALSE(typedParams->ownedSpec.getViewPipeline());
}
TEST_F(LiteParsedInternalSearchIdLookUpTest, ViewPolicyCallbackWithEmptyViewPipeline) {
TEST_F(LiteParsedInternalSearchIdLookUpTest, BindViewInfoWithEmptyViewPipeline) {
BSONObj spec = BSON(LiteParsedInternalSearchIdLookUp::kStageName << BSON("limit" << 10LL));
auto liteParsed =
LiteParsedInternalSearchIdLookUp::parse(kTestNss, spec.firstElement(), LiteParserOptions{});
@@ -125,9 +121,7 @@ TEST_F(LiteParsedInternalSearchIdLookUpTest, ViewPolicyCallbackWithEmptyViewPipe
// Create an empty view pipeline.
ViewInfo viewInfo(kViewNss, kResolvedNss, {});
auto viewPolicy = liteParsed->getViewPolicy();
viewPolicy.callback(
viewInfo, LiteParsedInternalSearchIdLookUp::kStageName, ResolvedNamespaceMap{});
liteParsed->bindViewInfo(viewInfo, {});
auto stageParams = liteParsed->getStageParams();
auto* typedParams = dynamic_cast<InternalSearchIdLookupStageParams*>(stageParams.get());
@@ -212,7 +206,7 @@ TEST_F(LiteParsedInternalSearchIdLookUpTest,
ASSERT_EQ(stages.size(), 1U);
ASSERT_EQ(stages[0]->getParseTimeName(), LiteParsedInternalSearchIdLookUp::kStageName);
// The IdLookup stage should now carry the desugared view pipeline via its ViewPolicy callback.
// The IdLookup stage should now carry the desugared view pipeline via bindViewInfo().
auto* idLookup = dynamic_cast<LiteParsedInternalSearchIdLookUp*>(stages[0].get());
ASSERT_TRUE(idLookup != nullptr);

View File

@@ -319,7 +319,7 @@ bool isMongotStage(DocumentSource* stage) {
dynamic_cast<mongo::DocumentSourceSearchMeta*>(stage));
}
// TODO SERVER-116021 Remove this function when the extension can do this through ViewPolicy.
// TODO SERVER-116021 Remove this function when the extension can do this through bindViewInfo().
bool isExtensionVectorSearchStage(std::string stageName) {
return stageName == kExtensionVectorSearchStageName ||
stageName == DocumentSourceVectorSearch::kStageName;

View File

@@ -114,7 +114,7 @@ bool isMongotPipeline(const Pipeline* pipeline);
* stage that will rely on calls to mongot.
*
* TODO SERVER-115069 Remove this once search queries are desugared at LiteParsed time and handle
* the view through a custom ViewPolicy.
* the view through a bindViewInfo() override.
*/
bool isMongotLiteParsedPipeline(const LiteParsedPipeline& lpp);
@@ -135,7 +135,7 @@ bool isMongotStage(DocumentSource* stage);
/**
* Check if this is a $vectorSearch-as-an-extension stage.
* TODO SERVER-116021 Remove this function when the extension can do this through ViewPolicy.
* TODO SERVER-116021 Remove this function when the extension can do this through bindViewInfo().
*/
bool isExtensionVectorSearchStage(std::string stageName);

View File

@@ -45,8 +45,10 @@ DECLARE_STAGE_PARAMS_DERIVED_DEFAULT(Test);
*/
class TestLiteParsed final : public LiteParsedDocumentSourceDefault<TestLiteParsed> {
public:
TestLiteParsed(const BSONElement& originalBson, ViewPolicy viewPolicy = DefaultViewPolicy{})
: LiteParsedDocumentSourceDefault(originalBson), _viewPolicy(viewPolicy) {}
TestLiteParsed(
const BSONElement& originalBson,
FirstStageViewApplicationPolicy policy = FirstStageViewApplicationPolicy::kDefaultPrepend)
: LiteParsedDocumentSourceDefault(originalBson), _policy(policy) {}
stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
return stdx::unordered_set<NamespaceString>();
@@ -60,11 +62,11 @@ public:
return std::make_unique<TestStageParams>(_originalBson);
}
ViewPolicy getViewPolicy() const final {
return _viewPolicy;
FirstStageViewApplicationPolicy getFirstStageViewApplicationPolicy() const override {
return _policy;
}
ViewPolicy _viewPolicy;
FirstStageViewApplicationPolicy _policy;
};
} // namespace mongo

View File

@@ -104,7 +104,7 @@ buildResolvedPipelineForRegularView(OperationContext* opCtx,
auto lpp = LiteParsedPipeline(request, true, LiteParserOptions{.ifrContext = ifrContext});
lpp.makeOwned();
// Desugar and call handleView() to invoke ViewPolicy callbacks for extension stages.
// Desugar and call handleView() to invoke bindViewInfo() for extension stages.
LiteParsedDesugarer::desugar(&lpp);
auto resolvedNamespaces = helpers.resolveInvolvedNamespaces(lpp.getInvolvedNamespaces());

View File

@@ -105,7 +105,7 @@ public:
/**
* Builds a resolved aggregation request from a view for mongos, handling special cases for
* mongot pipelines, timeseries views, and invoking ViewPolicy callbacks for extension stages.
* mongot pipelines, timeseries views, and invoking bindViewInfo() for extension stages.
*
* This is the single entrypoint for mongos view resolution. It returns a fully resolved
* request with the pipeline set, and optionally a LiteParsedPipeline that was created during

View File

@@ -1165,7 +1165,7 @@ PipelineResolver::MongosViewRequestResult buildResolvedViewAggregateRequest(
// We populated a view through the kickback retry logic. Build a new resolved request
// from this view, handling special cases for
// mongot pipelines, timeseries views, and invoking ViewPolicy callbacks for
// mongot pipelines, timeseries views, and invoking bindViewInfo() for
// extension stages.
PipelineResolver::MongosPipelineHelpers helpers{makeExpressionContext,
resolveInvolvedNamespaces};