Skip to content

Commit 3926c4f

Browse files
committed
feat(schema): run schema anaylsis with secondaryPreferred when read pref not set COMPASS-8854
1 parent e41df6b commit 3926c4f

File tree

2 files changed

+105
-40
lines changed

2 files changed

+105
-40
lines changed

packages/compass-schema/src/stores/reducer.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Schema } from 'mongodb-schema';
22
import type { Action, AnyAction, Reducer } from 'redux';
3+
import ConnectionString from 'mongodb-connection-string-url';
4+
import type { AggregateOptions } from 'mongodb';
35
import { type AnalysisState } from '../constants/analysis-states';
46
import {
57
ANALYSIS_STATE_ANALYZING,
@@ -212,6 +214,12 @@ export const stopAnalysis = (): SchemaThunkAction<void> => {
212214
};
213215
};
214216

217+
function isReadPreferenceSet(connectionString: string): boolean {
218+
return !!new ConnectionString(connectionString).searchParams.get(
219+
'readPreference'
220+
);
221+
}
222+
215223
export const startAnalysis = (): SchemaThunkAction<
216224
Promise<void>,
217225
AnalysisStartedAction | AnalysisFinishedAction | AnalysisFailedAction
@@ -250,8 +258,18 @@ export const startAnalysis = (): SchemaThunkAction<
250258
fields: query.project ?? undefined,
251259
};
252260

253-
const driverOptions = {
261+
const driverOptions: AggregateOptions = {
254262
maxTimeMS: capMaxTimeMSAtPreferenceLimit(preferences, query.maxTimeMS),
263+
// When the read preference isn't set in the connection string explicitly,
264+
// then we default to secondaryPreferred to avoid using the primary for
265+
// schema analysis.
266+
...(isReadPreferenceSet(
267+
connectionInfoRef.current.connectionOptions.connectionString
268+
)
269+
? {}
270+
: {
271+
readPreference: 'secondaryPreferred',
272+
}),
255273
};
256274

257275
try {

packages/compass-schema/src/stores/store.spec.ts

Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { activateSchemaPlugin, type SchemaStore } from './store';
1+
import { activateSchemaPlugin } from './store';
2+
import type { SchemaStore, SchemaPluginServices } from './store';
23
import AppRegistry, { createActivateHelpers } from 'hadron-app-registry';
34
import { expect } from 'chai';
45

@@ -7,7 +8,6 @@ import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
78
import { createNoopLogger } from '@mongodb-js/compass-logging/provider';
89
import type { FieldStoreService } from '@mongodb-js/compass-field-store';
910
import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider';
10-
import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider';
1111
import { startAnalysis, stopAnalysis } from './reducer';
1212
import Sinon from 'sinon';
1313

@@ -26,46 +26,61 @@ const mockQueryBar = {
2626
};
2727

2828
describe('Schema Store', function () {
29-
describe('#configureStore', function () {
30-
let store: SchemaStore;
31-
let deactivate: () => void;
32-
let sandbox: Sinon.SinonSandbox;
33-
const localAppRegistry = new AppRegistry();
34-
const globalAppRegistry = new AppRegistry();
35-
const namespace = 'db.coll';
29+
let store: SchemaStore;
30+
let deactivate: () => void;
31+
let sandbox: Sinon.SinonSandbox;
32+
const localAppRegistry = new AppRegistry();
33+
const globalAppRegistry = new AppRegistry();
34+
const namespace = 'db.coll';
35+
let sampleStub: Sinon.SinonStub;
36+
let isCancelErrorStub: Sinon.SinonStub;
37+
38+
beforeEach(function () {
39+
sandbox = Sinon.createSandbox();
40+
sampleStub = sandbox.stub();
41+
isCancelErrorStub = sandbox.stub();
42+
});
43+
44+
async function createStore(services: Partial<SchemaPluginServices> = {}) {
45+
const dataService = {
46+
sample: sampleStub,
47+
isCancelError: isCancelErrorStub,
48+
};
3649
const connectionInfoRef = {
37-
current: {},
38-
} as ConnectionInfoRef;
39-
let sampleStub: Sinon.SinonStub;
40-
let isCancelErrorStub: Sinon.SinonStub;
50+
current: {
51+
connectionOptions: {
52+
connectionString: 'mongodb://localhost:27017',
53+
},
54+
title: 'test',
55+
id: 'test',
56+
},
57+
};
58+
59+
const plugin = activateSchemaPlugin(
60+
{
61+
namespace: namespace,
62+
},
63+
{
64+
localAppRegistry: localAppRegistry,
65+
globalAppRegistry: globalAppRegistry,
66+
dataService,
67+
logger: dummyLogger,
68+
track: dummyTrack,
69+
preferences: await createSandboxFromDefaultPreferences(),
70+
fieldStoreService: mockFieldStoreService,
71+
queryBar: mockQueryBar as any,
72+
connectionInfoRef,
73+
...services,
74+
},
75+
createActivateHelpers()
76+
);
77+
store = plugin.store;
78+
deactivate = () => plugin.deactivate();
79+
}
4180

81+
describe('#configureStore', function () {
4282
beforeEach(async function () {
43-
sandbox = Sinon.createSandbox();
44-
sampleStub = sandbox.stub();
45-
isCancelErrorStub = sandbox.stub();
46-
const dataService = {
47-
sample: sampleStub,
48-
isCancelError: isCancelErrorStub,
49-
};
50-
const plugin = activateSchemaPlugin(
51-
{
52-
namespace: namespace,
53-
},
54-
{
55-
localAppRegistry: localAppRegistry,
56-
globalAppRegistry: globalAppRegistry,
57-
dataService: dataService as any,
58-
logger: dummyLogger,
59-
track: dummyTrack,
60-
preferences: await createSandboxFromDefaultPreferences(),
61-
fieldStoreService: mockFieldStoreService,
62-
queryBar: mockQueryBar as any,
63-
connectionInfoRef,
64-
},
65-
createActivateHelpers()
66-
);
67-
store = plugin.store;
68-
deactivate = () => plugin.deactivate();
83+
await createStore();
6984
});
7085

7186
afterEach(function () {
@@ -107,5 +122,37 @@ describe('Schema Store', function () {
107122
await analysisPromise;
108123
expect(store.getState().analysisState).to.equal('initial');
109124
});
125+
126+
it('runs the analysis with read pref secondaryPreferred', async function () {
127+
sampleStub.resolves([{ name: 'Hans' }, { name: 'Greta' }]);
128+
await store.dispatch(startAnalysis());
129+
expect(sampleStub.getCall(0).args[2])
130+
.property('readPreference')
131+
.to.equal('secondaryPreferred');
132+
});
133+
});
134+
135+
describe('with a connection string with explicit read preference set', function () {
136+
beforeEach(async function () {
137+
await createStore({
138+
connectionInfoRef: {
139+
current: {
140+
connectionOptions: {
141+
connectionString:
142+
'mongodb://localhost:27017/?readPreference=primary',
143+
},
144+
title: 'test',
145+
id: 'test',
146+
},
147+
},
148+
});
149+
});
150+
151+
it('does not set read preference to secondaryPreferred', async function () {
152+
await store.dispatch(startAnalysis());
153+
expect(sampleStub.getCall(0).args[2]).not.to.have.property(
154+
'readPreference'
155+
);
156+
});
110157
});
111158
});

0 commit comments

Comments
 (0)