Skip to content

Commit 21257a3

Browse files
feat(mongodb-constants): add new utils for views COMPASS:9700 (#567)
* add pipeline utils * undo change to not related file * syntax fix Co-authored-by: Copilot <[email protected]> * comment method * export specific method * add missing dep * update index.spec.ts * add isVersionSearchCompatible methods * export as one * typo Co-authored-by: Copilot <[email protected]> * update view.spec file * change to peerDep and devDep * replace with bson import --------- Co-authored-by: Copilot <[email protected]>
1 parent 6aebdc0 commit 21257a3

File tree

6 files changed

+275
-0
lines changed

6 files changed

+275
-0
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mongodb-constants/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
"test-ci": "npm run test-cov",
5252
"reformat": "npm run prettier -- --write ."
5353
},
54+
"peerDependencies": {
55+
"bson": "^6.10.3"
56+
},
5457
"devDependencies": {
5558
"@mongodb-js/eslint-config-devtools": "0.9.11",
5659
"@mongodb-js/mocha-config-devtools": "^1.0.5",
@@ -60,6 +63,7 @@
6063
"@types/mocha": "^9.1.1",
6164
"@types/semver": "^7.7.0",
6265
"@types/sinon-chai": "^3.2.5",
66+
"bson": "^6.10.3",
6367
"acorn": "^8.14.1",
6468
"chai": "^4.5.0",
6569
"depcheck": "^1.4.7",

packages/mongodb-constants/src/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('constants', function () {
3232
'VALIDATION_TEMPLATE',
3333
'ATLAS_SEARCH_TEMPLATES',
3434
'ATLAS_VECTOR_SEARCH_TEMPLATE',
35+
'VIEW_PIPELINE_UTILS',
3536
]);
3637
});
3738
});

packages/mongodb-constants/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export type {
1616
FilterOptions as CompletionFilterOptions,
1717
} from './filter';
1818
export * from './atlas-search-templates';
19+
export * from './views';
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { expect } from 'chai';
2+
import { VIEW_PIPELINE_UTILS } from './views';
3+
import type { Document } from 'bson';
4+
5+
describe('views', function () {
6+
describe('isPipelineSearchQueryable', function () {
7+
it('should return true for a valid pipeline with $addFields stage', function () {
8+
const pipeline: Document[] = [{ $addFields: { testField: 'testValue' } }];
9+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
10+
true,
11+
);
12+
});
13+
14+
it('should return true for a valid pipeline with $set stage', function () {
15+
const pipeline: Document[] = [{ $set: { testField: 'testValue' } }];
16+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
17+
true,
18+
);
19+
});
20+
21+
it('should return true for a valid pipeline with $match stage using $expr', function () {
22+
const pipeline: Document[] = [
23+
{ $match: { $expr: { $eq: ['$field', 'value'] } } },
24+
];
25+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
26+
true,
27+
);
28+
});
29+
30+
it('should return false for a pipeline with an unsupported stage', function () {
31+
const pipeline: Document[] = [{ $group: { _id: '$field' } }];
32+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
33+
false,
34+
);
35+
});
36+
37+
it('should return false for a $match stage without $expr', function () {
38+
const pipeline: Document[] = [{ $match: { nonExprKey: 'someValue' } }];
39+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
40+
false,
41+
);
42+
});
43+
44+
it('should return false for a $match stage with $expr and additional fields', function () {
45+
const pipeline: Document[] = [
46+
{
47+
$match: {
48+
$expr: { $eq: ['$field', 'value'] },
49+
anotherField: 'value',
50+
},
51+
},
52+
];
53+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
54+
false,
55+
);
56+
});
57+
58+
it('should return true for an empty pipeline', function () {
59+
const pipeline: Document[] = [];
60+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
61+
true,
62+
);
63+
});
64+
65+
it('should return false if any stage in the pipeline is invalid', function () {
66+
const pipeline: Document[] = [
67+
{ $addFields: { testField: 'testValue' } },
68+
{ $match: { $expr: { $eq: ['$field', 'value'] } } },
69+
{ $group: { _id: '$field' } },
70+
];
71+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
72+
false,
73+
);
74+
});
75+
76+
it('should handle a pipeline with multiple valid stages', function () {
77+
const pipeline: Document[] = [
78+
{ $addFields: { field1: 'value1' } },
79+
{ $match: { $expr: { $eq: ['$field', 'value'] } } },
80+
{ $set: { field2: 'value2' } },
81+
];
82+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
83+
true,
84+
);
85+
});
86+
87+
it('should return false for a $match stage with no conditions', function () {
88+
const pipeline: Document[] = [{ $match: {} }];
89+
expect(VIEW_PIPELINE_UTILS.isPipelineSearchQueryable(pipeline)).to.equal(
90+
false,
91+
);
92+
});
93+
});
94+
95+
describe('isVersionSearchCompatibleForViewsDataExplorer', function () {
96+
it('should return true for a version greater than or equal to 8.0.0', function () {
97+
expect(
98+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(
99+
'8.0.0',
100+
),
101+
).to.equal(true);
102+
expect(
103+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(
104+
'8.0.1',
105+
),
106+
).to.equal(true);
107+
expect(
108+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(
109+
'8.1.0',
110+
),
111+
).to.equal(true);
112+
});
113+
114+
it('should return false for a version less than 8.0.0', function () {
115+
expect(
116+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(
117+
'7.9.9',
118+
),
119+
).to.equal(false);
120+
expect(
121+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(
122+
'7.0.0',
123+
),
124+
).to.equal(false);
125+
});
126+
127+
it('should handle invalid version format by returning false', function () {
128+
expect(
129+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(
130+
'invalid-version',
131+
),
132+
).to.equal(false);
133+
expect(
134+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsDataExplorer(''),
135+
).to.equal(false);
136+
});
137+
});
138+
139+
describe('isVersionSearchCompatibleForViewsCompass', function () {
140+
it('should return true for a version greater than or equal to 8.1.0', function () {
141+
expect(
142+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass('8.1.0'),
143+
).to.equal(true);
144+
expect(
145+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass('8.1.1'),
146+
).to.equal(true);
147+
expect(
148+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass('8.2.0'),
149+
).to.equal(true);
150+
});
151+
152+
it('should return false for a version less than 8.1.0', function () {
153+
expect(
154+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass('8.0.9'),
155+
).to.equal(false);
156+
expect(
157+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass('8.0.0'),
158+
).to.equal(false);
159+
expect(
160+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass('7.9.9'),
161+
).to.equal(false);
162+
});
163+
164+
it('should handle invalid version format by returning false', function () {
165+
expect(
166+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass(
167+
'invalid-version',
168+
),
169+
).to.equal(false);
170+
expect(
171+
VIEW_PIPELINE_UTILS.isVersionSearchCompatibleForViewsCompass(''),
172+
).to.equal(false);
173+
});
174+
});
175+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/** utils related to view pipeline **/
2+
3+
import type { Document } from 'bson';
4+
import semver from 'semver';
5+
6+
const MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_DE = '8.0.0';
7+
const MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_COMPASS = '8.1.0';
8+
9+
/**
10+
* A view pipeline is searchQueryable (ie: a search index can be created on view) if
11+
* a pipeline consists of only addFields, set and match with expr stages
12+
*
13+
* @param pipeline the view pipeline
14+
* @returns whether pipeline is search queryable
15+
*/
16+
const isPipelineSearchQueryable = (pipeline: Document[]): boolean => {
17+
for (const stage of pipeline) {
18+
const stageKey = Object.keys(stage)[0];
19+
20+
// Check if the stage is $addFields, $set, or $match
21+
if (
22+
!(
23+
stageKey === '$addFields' ||
24+
stageKey === '$set' ||
25+
stageKey === '$match'
26+
)
27+
) {
28+
return false;
29+
}
30+
31+
// If the stage is $match, check if it uses $expr
32+
if (stageKey === '$match') {
33+
const matchStage = stage['$match'] as Document;
34+
const matchKeys = Object.keys(matchStage || {});
35+
36+
if (matchKeys.length !== 1 || !matchKeys.includes('$expr')) {
37+
return false;
38+
}
39+
}
40+
}
41+
42+
return true;
43+
};
44+
45+
/**
46+
* Views allow search indexes to be made on them in DE for server version 8.1+
47+
*
48+
* @param serverVersion the server version
49+
* @returns whether serverVersion is search compatible for views in DE
50+
*/
51+
const isVersionSearchCompatibleForViewsDataExplorer = (
52+
serverVersion: string,
53+
): boolean => {
54+
try {
55+
return semver.gte(
56+
serverVersion,
57+
MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_DE,
58+
);
59+
} catch {
60+
return false;
61+
}
62+
};
63+
64+
/**
65+
* Views allow search indexes to be made on them in compass for mongodb version 8.0+
66+
*
67+
* @param serverVersion the server version
68+
* @returns whether serverVersion is search compatible for views in Compass
69+
*/
70+
const isVersionSearchCompatibleForViewsCompass = (
71+
serverVersion: string,
72+
): boolean => {
73+
try {
74+
return semver.gte(
75+
serverVersion,
76+
MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_COMPASS,
77+
);
78+
} catch {
79+
return false;
80+
}
81+
};
82+
83+
export const VIEW_PIPELINE_UTILS = {
84+
MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_DE,
85+
MIN_VERSION_FOR_VIEW_SEARCH_COMPATIBILITY_COMPASS,
86+
isPipelineSearchQueryable,
87+
isVersionSearchCompatibleForViewsDataExplorer,
88+
isVersionSearchCompatibleForViewsCompass,
89+
};

0 commit comments

Comments
 (0)