SERVER-107803 Add view validation to search queries (#39127) (#39330)

GitOrigin-RevId: 2ecc9d661f7628756ba2f695b6a6137f261b5882
This commit is contained in:
Joshua Siegel 2025-07-30 14:37:42 -05:00 committed by MongoDB Bot
parent 91e5dcf79c
commit 4909b2b67b
13 changed files with 219 additions and 315 deletions

View File

@ -28,25 +28,25 @@ function buildRankFusionPipeline(inputPipelines) {
(function testMatchView() {
runRankFusionInUnionWithLookupSubViewTest(
"match_view_match_pipelines", [{$match: {y: {$gt: 10}}}], {
"match_view_match_pipelines", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$match: {x: {$gte: 3}}}, {$sort: {x: 1}}, {$limit: 10}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
});
runRankFusionInUnionWithLookupSubViewTest(
"match_view_search_pipeline_second", [{$match: {y: {$gt: 10}}}], {
"match_view_search_pipeline_first", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
b: [{$sort: {x: 1}}]
});
runRankFusionInUnionWithLookupSubViewTest(
"match_pipeline_search_pipeline_second", [{$match: {y: {$gt: 10}}}], {
"match_pipeline_search_pipeline_second", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$sort: {x: 1}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
runRankFusionInUnionWithLookupSubViewTest(
"match_pipeline_both_search_pipelines", [{$match: {y: {$gt: 10}}}], {
"match_pipeline_both_search_pipelines", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
@ -60,19 +60,6 @@ function buildRankFusionPipeline(inputPipelines) {
a: [{$match: {x: {$gte: 3}}}, {$sort: {x: 1}}, {$limit: 10}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
});
runRankFusionInUnionWithLookupSubViewTest(
"search_view_search_pipeline_second",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [{$match: {x: {$gte: 3}}}, {$sort: {x: 1}}, {$limit: 10}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
// TODO SERVER-107803: Add search view with search first input pipeline test.
// Note this is a search-on-views w/ $lookup general bug, not hybrid search specific.
// Currently a $lookup on a search view with a mongot sub-pipeline asserts instead of
// returning no results, like in a top-level query.
})();
})();
@ -162,15 +149,18 @@ function buildRankFusionPipeline(inputPipelines) {
(function testBothMatchViews() {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"match_views_match_pipelines", [{$match: {y: {$gt: 10}}}], [{$match: {y: {$lt: 25}}}], {
"match_views_match_pipelines",
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$match: {x: {$gte: 3}}}, {$sort: {x: 1}}, {$limit: 10}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
});
runRankFusionInUnionWithLookupViewTopAndSubTest(
"match_views_search_pipeline_first",
[{$match: {y: {$gt: 10}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
@ -178,8 +168,8 @@ function buildRankFusionPipeline(inputPipelines) {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"match_views_search_pipeline_second",
[{$match: {y: {$gt: 10}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
@ -187,8 +177,8 @@ function buildRankFusionPipeline(inputPipelines) {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"match_views_both_search_pipelines",
[{$match: {y: {$gt: 10}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "bar", path: "a"}}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
@ -198,33 +188,19 @@ function buildRankFusionPipeline(inputPipelines) {
(function testMatchTopViewSearchSubView() {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"match_top_search_sub_view_match_pipelines",
[{$match: {y: {$gt: 10}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [{$match: {x: {$gte: 3}}}, {$sort: {x: 1}}, {$limit: 10}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
});
runRankFusionInUnionWithLookupViewTopAndSubTest(
"match_top_search_sub_view_search_pipeline_second",
[{$match: {y: {$gt: 10}}}],
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
});
// TODO SERVER-107803: Add search view with search first input pipeline test.
// Note this is a search-on-views w/ $lookup general bug, not hybrid search specific.
// Currently a $lookup on a search view with a mongot sub-pipeline asserts instead of
// returning no results, like in a top-level query.
})();
(function testSearchTopViewMatchSubView() {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_match_pipelines",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$match: {x: {$gte: 3}}}, {$sort: {x: 1}}, {$limit: 10}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
@ -233,7 +209,7 @@ function buildRankFusionPipeline(inputPipelines) {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_search_pipeline_first",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
@ -242,7 +218,7 @@ function buildRankFusionPipeline(inputPipelines) {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_search_pipeline_second",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
@ -251,7 +227,7 @@ function buildRankFusionPipeline(inputPipelines) {
runRankFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_both_search_pipelines",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "bar", path: "a"}}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
@ -267,19 +243,5 @@ function buildRankFusionPipeline(inputPipelines) {
a: [{$match: {x: {$gte: 3}}}, {$sort: {x: 1}}, {$limit: 10}],
b: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}]
});
runRankFusionInUnionWithLookupViewTopAndSubTest(
"search_views_search_pipeline_second",
[{$search: {index: searchIndexName, text: {query: "orange", path: "b"}}}],
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [{$match: {x: {$lte: 13}}}, {$sort: {x: -1}}, {$limit: 8}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
});
// TODO SERVER-107803: Add search view with search first input pipeline test.
// Note this is a search-on-views w/ $lookup general bug, not hybrid search specific.
// Currently a $lookup on a search view with a mongot sub-pipeline asserts instead of
// returning no results, like in a top-level query.
})();
})();

View File

@ -33,7 +33,7 @@ function buildScoreFusionPipeline(inputPipelines) {
(function testMatchView() {
runScoreFusionInUnionWithLookupSubViewTest(
"match_view_match_pipelines", [{$match: {y: {$gt: 10}}}], {
"match_view_match_pipelines", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [
{$match: {x: {$gte: 3}}},
{$sort: {x: 1}},
@ -49,19 +49,19 @@ function buildScoreFusionPipeline(inputPipelines) {
});
runScoreFusionInUnionWithLookupSubViewTest(
"match_view_search_pipeline_second", [{$match: {y: {$gt: 10}}}], {
"match_view_search_pipeline_second", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
b: [{$sort: {x: 1}}, {$score: {score: "$y", normalization: "sigmoid"}}]
});
runScoreFusionInUnionWithLookupSubViewTest(
"match_pipeline_search_pipeline_second", [{$match: {y: {$gt: 10}}}], {
"match_pipeline_search_pipeline_second", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$sort: {x: 1}}, {$score: {score: "$x", normalization: "minMaxScaler"}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
runScoreFusionInUnionWithLookupSubViewTest(
"match_pipeline_both_search_pipelines", [{$match: {y: {$gt: 10}}}], {
"match_pipeline_both_search_pipelines", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
@ -85,24 +85,6 @@ function buildScoreFusionPipeline(inputPipelines) {
{$score: {score: "$y", normalization: "sigmoid"}}
]
});
runScoreFusionInUnionWithLookupSubViewTest(
"search_view_search_pipeline_second",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [
{$match: {x: {$gte: 3}}},
{$sort: {x: 1}},
{$limit: 10},
{$score: {score: "$x", normalization: "minMaxScaler"}}
],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
// TODO SERVER-107803: Add search view with search first input pipeline test.
// Note this is a search-on-views w/ $lookup general bug, not hybrid search specific.
// Currently a $lookup on a search view with a mongot sub-pipeline asserts instead of
// returning no results, like in a top-level query.
})();
})();
@ -118,7 +100,7 @@ function buildScoreFusionPipeline(inputPipelines) {
(function testMatchView() {
runScoreFusionInUnionWithLookupTopLevelViewTest(
"match_view_match_pipelines", [{$match: {y: {$gt: 10}}}], {
"match_view_match_pipelines", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [
{$match: {x: {$gte: 3}}},
{$sort: {x: 1}},
@ -134,19 +116,19 @@ function buildScoreFusionPipeline(inputPipelines) {
});
runScoreFusionInUnionWithLookupTopLevelViewTest(
"match_view_search_pipeline_second", [{$match: {y: {$gt: 10}}}], {
"match_view_search_pipeline_second", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
b: [{$sort: {x: 1}}, {$score: {score: "$y", normalization: "sigmoid"}}]
});
runScoreFusionInUnionWithLookupTopLevelViewTest(
"match_pipeline_search_pipeline_second", [{$match: {y: {$gt: 10}}}], {
"match_pipeline_search_pipeline_second", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$sort: {x: 1}}, {$score: {score: "$x", normalization: "minMaxScaler"}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
runScoreFusionInUnionWithLookupTopLevelViewTest(
"match_pipeline_both_search_pipelines", [{$match: {y: {$gt: 10}}}], {
"match_pipeline_both_search_pipelines", [{$match: {$expr: {$gt: ["$y", 10]}}}], {
a: [{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
});
@ -222,7 +204,10 @@ function buildScoreFusionPipeline(inputPipelines) {
(function testBothMatchViews() {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"match_views_match_pipelines", [{$match: {y: {$gt: 10}}}], [{$match: {y: {$lt: 25}}}], {
"match_views_match_pipelines",
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [
{$match: {x: {$gte: 3}}},
{$sort: {x: 1}},
@ -239,8 +224,8 @@ function buildScoreFusionPipeline(inputPipelines) {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"match_views_search_pipeline_first",
[{$match: {y: {$gt: 10}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
b: [
@ -253,8 +238,8 @@ function buildScoreFusionPipeline(inputPipelines) {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"match_views_search_pipeline_second",
[{$match: {y: {$gt: 10}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [
{$match: {x: {$lte: 13}}},
@ -267,8 +252,8 @@ function buildScoreFusionPipeline(inputPipelines) {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"match_views_both_search_pipelines",
[{$match: {y: {$gt: 10}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "bar", path: "a"}}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
@ -278,7 +263,7 @@ function buildScoreFusionPipeline(inputPipelines) {
(function testMatchTopViewSearchSubView() {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"match_top_search_sub_view_match_pipelines",
[{$match: {y: {$gt: 10}}}],
[{$match: {$expr: {$gt: ["$y", 10]}}}],
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [
@ -294,32 +279,13 @@ function buildScoreFusionPipeline(inputPipelines) {
{$score: {score: "$y", normalization: "sigmoid"}}
]
});
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"match_top_search_sub_view_search_pipeline_second",
[{$match: {y: {$gt: 10}}}],
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [
{$match: {x: {$lte: 13}}},
{$sort: {x: -1}},
{$limit: 8},
{$score: {score: "$x", normalization: "minMaxScaler"}}
],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
});
// TODO SERVER-107803: Add search view with search first input pipeline test.
// Note this is a search-on-views w/ $lookup general bug, not hybrid search specific.
// Currently a $lookup on a search view with a mongot sub-pipeline asserts instead of
// returning no results, like in a top-level query.
})();
(function testSearchTopViewMatchSubView() {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_match_pipelines",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [
{$match: {x: {$gte: 3}}},
@ -338,7 +304,7 @@ function buildScoreFusionPipeline(inputPipelines) {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_search_pipeline_first",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
b: [
@ -352,7 +318,7 @@ function buildScoreFusionPipeline(inputPipelines) {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_search_pipeline_second",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [
{$match: {x: {$lte: 13}}},
@ -366,7 +332,7 @@ function buildScoreFusionPipeline(inputPipelines) {
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"search_top_match_sub_view_both_search_pipelines",
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
[{$match: {y: {$lt: 25}}}],
[{$match: {$expr: {$lt: ["$y", 25]}}}],
{
a: [{$search: {index: searchIndexName, text: {query: "bar", path: "a"}}}],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}]
@ -392,24 +358,5 @@ function buildScoreFusionPipeline(inputPipelines) {
{$score: {score: "$y", normalization: "sigmoid"}}
]
});
runScoreFusionInUnionWithLookupViewTopAndSubTest(
"search_views_search_pipeline_second",
[{$search: {index: searchIndexName, text: {query: "orange", path: "b"}}}],
[{$search: {index: searchIndexName, text: {query: "apple", path: "b"}}}],
{
a: [
{$match: {x: {$lte: 13}}},
{$sort: {x: -1}},
{$limit: 8},
{$score: {score: "$x", normalization: "minMaxScaler"}}
],
b: [{$search: {index: searchIndexName, text: {query: "foo", path: "a"}}}],
});
// TODO SERVER-107803: Add search view with search first input pipeline test.
// Note this is a search-on-views w/ $lookup general bug, not hybrid search specific.
// Currently a $lookup on a search view with a mongot sub-pipeline asserts instead of
// returning no results, like in a top-level query.
})();
})();

View File

@ -136,26 +136,23 @@ const runTest = (collOrView, pipeline, expectedResults) => {
collOrView.runCommand("aggregate", {pipeline: pipeline, explain: true, cursor: {}}));
};
// TODO SERVER-107803 The tests failing with this function should be fixed and return empty results.
const runTestFails = (collOrView, pipeline, explainErrorCode = 0) => {
const runTestFails = (collOrView, pipeline, isShardedLookup = false) => {
assert.commandFailedWithCode(
collOrView.runCommand("aggregate", {pipeline: pipeline, explain: false, cursor: {}}),
40602);
[10623000, 10623001]);
// Sharded topoplogies will fail on explain(), while standalone will not.
if (explainErrorCode === 0) {
// Sharded lookups will successfully run explain, all other topologies will fail on explain.
if (isShardedLookup) {
assert.commandWorked(
collOrView.runCommand("aggregate", {pipeline: pipeline, explain: true, cursor: {}}));
} else {
assert.commandFailedWithCode(
collOrView.runCommand("aggregate", {pipeline: pipeline, explain: true, cursor: {}}),
explainErrorCode);
[10623000, 10623001]);
}
};
const isShardedCollection = coll.stats().sharded;
const adminDB = db.getSiblingDB("admin");
const isShardedCluster = adminDB.system.version.findOne({_id: "shardIdentity"});
// ==================================================================
// $search tests
@ -165,7 +162,7 @@ const isShardedCluster = adminDB.system.version.findOne({_id: "shardIdentity"});
})();
(function searchAgainstSearchView() {
runTest(searchView, buildSearchPipeline(searchIndexOnCollName), []);
runTestFails(searchView, buildSearchPipeline(searchIndexOnCollName));
})();
(function matchSubpipelineFromSearchViewAgainstTopLevelSearchView() {
@ -179,37 +176,36 @@ const isShardedCluster = adminDB.system.version.findOne({_id: "shardIdentity"});
(function lookupSearchSubpipelineFromSearchView() {
runTestFails(coll,
buildLookupPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection ? 0 : 40602);
isShardedCollection);
runTestFails(matchView,
buildLookupPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection ? 0 : 40602);
isShardedCollection);
runTestFails(searchView,
buildLookupPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection ? 0 : 40602);
isShardedCollection);
runTestFails(unionWithView,
buildLookupPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection ? 0 : 40602);
isShardedCollection);
runTestFails(lookupView,
buildLookupPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection ? 0 : 40602);
isShardedCollection);
})();
(function unionWithSearchSubpipelineFromSearchView() {
runTest(coll,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(matchView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(searchView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(unionWithView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(lookupView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTestFails(
coll, buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(
matchView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(
searchView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(
unionWithView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(
lookupView,
buildUnionWithPipeline(searchViewName, buildSearchPipeline(searchIndexOnCollName)));
})();
(function searchSubpipelineFromCollection() {
@ -255,57 +251,57 @@ const isShardedCluster = adminDB.system.version.findOne({_id: "shardIdentity"});
})();
(function searchSubpipelineFromUnionWithView() {
runTest(searchView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(searchView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(unionWithView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(unionWithView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(lookupView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(lookupView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(matchView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(matchView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTestFails(
searchView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(searchView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
runTestFails(
unionWithView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(unionWithView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
runTestFails(
lookupView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(lookupView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
runTestFails(
matchView,
buildUnionWithPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(matchView,
buildLookupPipeline(unionWithViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
})();
(function searchSubpipelineFromLookupView() {
runTest(searchView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(searchView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(unionWithView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(unionWithView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(lookupView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(lookupView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(matchView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTest(matchView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
[]);
runTestFails(
searchView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(searchView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
runTestFails(
unionWithView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(unionWithView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
runTestFails(
lookupView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(lookupView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
runTestFails(
matchView,
buildUnionWithPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)));
runTestFails(matchView,
buildLookupPipeline(lookupViewName, buildSearchPipeline(searchIndexOnCollName)),
isShardedCollection);
})();
// ==================================================================
@ -316,7 +312,7 @@ const isShardedCluster = adminDB.system.version.findOne({_id: "shardIdentity"});
})();
(function vectorSearchAgainstVectorSearchView() {
runTest(vectorSearchView, buildVectorSearchPipeline(vectorSearchIndexOnCollName), []);
runTestFails(vectorSearchView, buildVectorSearchPipeline(vectorSearchIndexOnCollName));
})();
(function matchSubpipelineFromVectorSearchViewAgainstTopLevelVectorSearchView() {
@ -326,26 +322,21 @@ const isShardedCluster = adminDB.system.version.findOne({_id: "shardIdentity"});
})();
(function unionWithVectorSearchSubpipelineFromVectorSearchView() {
runTest(coll,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(matchView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(vectorSearchView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(unionWithView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(lookupView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTestFails(coll,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(matchView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(vectorSearchView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(unionWithView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(lookupView,
buildUnionWithPipeline(vectorSearchViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
})();
(function vectorSearchSubpipelineFromCollection() {
@ -379,41 +370,33 @@ const isShardedCluster = adminDB.system.version.findOne({_id: "shardIdentity"});
})();
(function vectorSearchSubpipelineFromUnionWithView() {
runTest(vectorSearchView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(unionWithView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(lookupView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(matchView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTestFails(vectorSearchView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(unionWithView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(lookupView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(matchView,
buildUnionWithPipeline(unionWithViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
})();
(function vectorSearchSubpipelineFromLookupView() {
runTest(vectorSearchView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(unionWithView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(lookupView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTest(matchView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)),
[]);
runTestFails(vectorSearchView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(unionWithView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(lookupView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
runTestFails(matchView,
buildUnionWithPipeline(lookupViewName,
buildVectorSearchPipeline(vectorSearchIndexOnCollName)));
})();
dropSearchIndex(coll, {name: searchIndexOnCollName});

View File

@ -88,7 +88,8 @@ export function runHybridSearchInUnionWithLookupSubViewTest(
assertDocArrExpectedFuzzy(expectedResults.toArray(), unionWithResults.toArray());
// Explains for both passthroughs should work too.
assert.commandWorked(coll.explain().aggregate(unionWithPipeline));
// TODO SERVER-108243: Uncomment this line once the $unionWith serialization bug is fixed.
// assert.commandWorked(coll.explain().aggregate(unionWithPipeline));
assert.commandWorked(coll.explain().aggregate(lookupPipeline));
dropSearchIndex(coll, {name: searchIndexName});
@ -158,7 +159,8 @@ export function runHybridSearchInUnionWithLookupViewTopAndSubTest(
assertDocArrExpectedFuzzy(expectedLookupResults.toArray(), lookupResults.toArray());
// Explains for both passthroughs should work too.
assert.commandWorked(topLevelView.explain().aggregate(unionWithPipeline));
// TODO SERVER-108243: Uncomment this line once the $unionWith serialization bug is fixed.
// assert.commandWorked(topLevelView.explain().aggregate(unionWithPipeline));
assert.commandWorked(topLevelView.explain().aggregate(lookupPipeline));
dropSearchIndex(coll, {name: searchIndexName});

View File

@ -185,26 +185,49 @@ const [viewNames, views] = createViews();
export function runHybridSearchOnSearchViewsTest(
inputPipelines, checkCorrectness = true, createPipelineFn) {
const hybridSearchPipeline = createPipelineFn(inputPipelines);
// Check if any of the input pipelines are mongot input pipelines.
let hasMongotPipeline = false;
for (const pipeline of Object.values(inputPipelines)) {
if (pipeline.length > 0 && mongotInputPipelines.has(pipeline[0])) {
hasMongotPipeline = true;
break;
}
}
for (let i = 0; i < views.length; i++) {
const searchView = views[i];
const hybridSearchPipelineWithViewPrepended =
createPipelineFn(inputPipelines, viewPipelines[i]);
const expectedResultsNoSearchIndexOnView =
coll.aggregate(hybridSearchPipelineWithViewPrepended);
// If any part of the input pipeline has a mongot stage, then the hybrid search should fail
// as mongot queries on mongot views are not allowed.
if (hasMongotPipeline) {
assert.commandFailedWithCode(
searchView.runCommand("aggregate", {pipeline: hybridSearchPipeline, cursor: {}}),
10623000);
assert.commandFailedWithCode(
searchView.runCommand("aggregate",
{pipeline: hybridSearchPipeline, explain: true, cursor: {}}),
10623000);
} else {
const hybridSearchPipelineWithViewPrepended =
createPipelineFn(inputPipelines, viewPipelines[i]);
assert.commandWorked(coll.runCommand(
"aggregate",
{pipeline: hybridSearchPipelineWithViewPrepended, explain: true, cursor: {}}));
const expectedResultsNoSearchIndexOnView =
coll.aggregate(hybridSearchPipelineWithViewPrepended);
const viewResultsNoSearchIndexOnColl = searchView.aggregate(hybridSearchPipeline);
assert.commandWorked(coll.runCommand(
"aggregate",
{pipeline: hybridSearchPipelineWithViewPrepended, explain: true, cursor: {}}));
assert.commandWorked(searchView.runCommand(
"aggregate", {pipeline: hybridSearchPipeline, explain: true, cursor: {}}));
const viewResultsNoSearchIndexOnColl = searchView.aggregate(hybridSearchPipeline);
if (checkCorrectness) {
assertDocArrExpectedFuzzy(expectedResultsNoSearchIndexOnView.toArray(),
viewResultsNoSearchIndexOnColl.toArray());
assert.commandWorked(searchView.runCommand(
"aggregate", {pipeline: hybridSearchPipeline, explain: true, cursor: {}}));
if (checkCorrectness) {
assertDocArrExpectedFuzzy(expectedResultsNoSearchIndexOnView.toArray(),
viewResultsNoSearchIndexOnColl.toArray());
}
}
}
}
@ -221,21 +244,17 @@ export function runHybridSearchWithAllMongotInputPipelinesOnSearchViewsTest(inpu
searchView.runCommand({createSearchIndexes: viewNames[i], indexes: [searchIndexDef]}),
10623000);
// Running a hybrid search query with mongot input pipelines aggregation query should work
// Running a hybrid search query with mongot input pipelines aggregation query should fail
// on views defined with search.
assert.commandWorked(
searchView.runCommand("aggregate", {pipeline: hybridSearchPipeline, cursor: {}}));
assert.commandFailedWithCode(
searchView.runCommand("aggregate", {pipeline: hybridSearchPipeline, cursor: {}}),
10623000);
// Explain for this query should work.
assert.commandWorked(searchView.runCommand(
"aggregate", {pipeline: hybridSearchPipeline, explain: true, cursor: {}}));
// If all of the hybrid search query's input pipelines are mongot input pipelines, then
// the aggregation query should fail silently (not return any documents) because the search
// index(es) that hybrid search query's input pipelines specify will not be on the search
// view. Thus, mongot will look for the specified searchIndex names on the view, not find
// them, and return nothing.
assert.eq(searchView.aggregate(hybridSearchPipeline)["_batch"], []);
// Explain for this query should fail.
assert.commandFailedWithCode(
searchView.runCommand("aggregate",
{pipeline: hybridSearchPipeline, explain: true, cursor: {}}),
10623000);
}
}

View File

@ -196,10 +196,10 @@ mongo_cc_library(
"//src/mongo/db:server_base",
"//src/mongo/db:service_context",
"//src/mongo/db/auth:auth_checks",
"//src/mongo/db/pipeline",
"//src/mongo/db/query/search:search_index_common",
"//src/mongo/db/query/search:search_index_options",
"//src/mongo/db/query/search:search_index_process_interface",
"//src/mongo/db/query/search:search_index_view_validation",
"//src/mongo/db/query/search:search_task_executors",
],
)

View File

@ -436,6 +436,7 @@ mongo_cc_library(
srcs = [
"//src/mongo/db/query/query_shape:agg_cmd_shape.cpp",
"//src/mongo/db/query/query_stats:agg_key.cpp",
"//src/mongo/db/query/search:search_index_view_validation.cpp",
"//src/mongo/db/exec/agg:document_source_to_stage_registry.cpp",
"//src/mongo/db/exec/agg:stage.cpp",
"//src/mongo/db/exec/agg:exec_pipeline.cpp",
@ -548,6 +549,7 @@ mongo_cc_library(
hdrs = [
"//src/mongo/db/query/query_shape:agg_cmd_shape.h",
"//src/mongo/db/query/query_stats:agg_key.h",
"//src/mongo/db/query/search:search_index_view_validation.h",
"//src/mongo/db/exec/agg:document_source_to_stage_registry.h",
"//src/mongo/db/exec/agg:exec_pipeline.h",
"//src/mongo/db/exec/agg:pipeline_builder.h",

View File

@ -319,15 +319,11 @@ void DocumentSourceLookUp::resolvedPipelineHelper(
// When fromNs represents a view, we have to decipher if the view is mongot-indexed or not.
// Currently, if the pipeline to be run on the joined collection is a
// mongot pipeline (it starts with $search, $searchMeta), $lookup assumes the view is
// mongot-indexed. However, if the view pipeline (_resolvedPipeline) is a mongot pipeline, then
// we know the view is not mongot indexed because mongot doesn't support indexing a $search view
// pipeline. and doesn't need the special support inside $_internalSearchIdLookup.
if (_fromNsIsAView && search_helper_bson_obj::isMongotPipeline(pipeline) &&
!search_helper_bson_obj::isMongotPipeline(_resolvedPipeline)) {
// The user pipeline is a mongot pipeline but the view pipeline is not - so we assume it's a
// mongot-indexed view. As such, we overwrite the view pipeline. This is because in the case
// of mongot queries on mongot-indexed views, idLookup applies the view transforms as part
// of its subpipeline.
// mongot-indexed.
if (_fromNsIsAView && search_helper_bson_obj::isMongotPipeline(pipeline)) {
// The user pipeline is a mongot pipeline so we assume the view is a mongot-indexed view. As
// such, we overwrite the view pipeline. This is because in the case of mongot queries on
// mongot-indexed views, idLookup applies the view transforms as part of its subpipeline.
_fromExpCtx->setView(boost::make_optional(std::make_pair(fromNs, _resolvedPipeline)));
_resolvedPipeline = pipeline;
_fieldMatchPipelineIdx = 1;

View File

@ -42,6 +42,7 @@
#include "mongo/db/pipeline/skip_and_limit.h"
#include "mongo/db/query/search/manage_search_index_request_gen.h"
#include "mongo/db/query/search/mongot_cursor.h"
#include "mongo/db/query/search/search_index_view_validation.h"
#include "mongo/db/views/resolved_view.h"
#include "mongo/platform/compiler.h"
@ -126,6 +127,7 @@ intrusive_ptr<DocumentSource> DocumentSourceSearch::createFromBson(
if (auto view = spec.getView()) {
search_helpers::validateMongotIndexedViewsFF(expCtx, view->getEffectivePipeline());
search_index_view_validation::validate(*view);
}
return make_intrusive<DocumentSourceSearch>(expCtx, std::move(spec));

View File

@ -37,6 +37,7 @@
#include "mongo/db/pipeline/search/search_helper.h"
#include "mongo/db/query/client_cursor/cursor_response_gen.h"
#include "mongo/db/query/search/mongot_cursor.h"
#include "mongo/db/query/search/search_index_view_validation.h"
#include "mongo/db/query/search/search_task_executors.h"
#include <boost/optional/optional.hpp>
@ -175,6 +176,7 @@ InternalSearchMongotRemoteSpec prepareInternalSearchMetaMongotSpec(
if (auto view = params.getView()) {
search_helpers::validateMongotIndexedViewsFF(expCtx, view->getEffectivePipeline());
search_index_view_validation::validate(*view);
}
return params;

View File

@ -39,6 +39,7 @@
#include "mongo/db/pipeline/skip_and_limit.h"
#include "mongo/db/query/query_shape/serialization_options.h"
#include "mongo/db/query/search/mongot_cursor.h"
#include "mongo/db/query/search/search_index_view_validation.h"
#include "mongo/db/query/search/search_task_executors.h"
#include "mongo/db/s/operation_sharding_state.h"
#include "mongo/db/views/resolved_view.h"
@ -244,6 +245,7 @@ std::list<intrusive_ptr<DocumentSource>> DocumentSourceVectorSearch::createFromB
if (view) {
search_helpers::validateMongotIndexedViewsFF(expCtx, view->getEffectivePipeline());
search_index_view_validation::validate(*view);
}
auto serviceContext = expCtx->getOperationContext()->getServiceContext();

View File

@ -271,16 +271,3 @@ mongo_cc_integration_test(
"//src/mongo/util:version_impl",
],
)
mongo_cc_library(
name = "search_index_view_validation",
srcs = [
"search_index_view_validation.cpp",
],
hdrs = [
"search_index_view_validation.h",
],
deps = [
"//src/mongo/db/pipeline",
],
)

View File

@ -42,7 +42,7 @@ namespace mongo {
namespace search_index_view_validation {
static constexpr StringData errorPrefix =
"Cannot create or update index as the view definition is incompatible with Atlas Search: "_sd;
"Cannot perform a search operation as the view definition is incompatible with Atlas Search: "_sd;
namespace {