mongodb 4.1.3 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -14
- package/lib/admin.js +5 -5
- package/lib/admin.js.map +1 -1
- package/lib/bson.js.map +1 -1
- package/lib/bulk/common.js +54 -56
- package/lib/bulk/common.js.map +1 -1
- package/lib/change_stream.js +13 -14
- package/lib/change_stream.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +1 -1
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/auth/mongocr.js +2 -2
- package/lib/cmap/auth/mongocr.js.map +1 -1
- package/lib/cmap/auth/mongodb_aws.js +3 -3
- package/lib/cmap/auth/mongodb_aws.js.map +1 -1
- package/lib/cmap/auth/plain.js +1 -1
- package/lib/cmap/auth/plain.js.map +1 -1
- package/lib/cmap/auth/scram.js +5 -5
- package/lib/cmap/auth/scram.js.map +1 -1
- package/lib/cmap/auth/x509.js +1 -1
- package/lib/cmap/auth/x509.js.map +1 -1
- package/lib/cmap/command_monitoring_events.js +15 -15
- package/lib/cmap/command_monitoring_events.js.map +1 -1
- package/lib/cmap/commands.js +10 -7
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connect.js +2 -2
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +15 -15
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +3 -3
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/cmap/message_stream.js +2 -2
- package/lib/cmap/message_stream.js.map +1 -1
- package/lib/cmap/stream_description.js +4 -4
- package/lib/cmap/stream_description.js.map +1 -1
- package/lib/cmap/wire_protocol/constants.js +4 -4
- package/lib/collection.js +42 -41
- package/lib/collection.js.map +1 -1
- package/lib/connection_string.js +73 -45
- package/lib/connection_string.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +16 -13
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/aggregation_cursor.js +14 -14
- package/lib/cursor/aggregation_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +23 -23
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/db.js +20 -20
- package/lib/db.js.map +1 -1
- package/lib/deps.js +1 -1
- package/lib/deps.js.map +1 -1
- package/lib/error.js +47 -24
- package/lib/error.js.map +1 -1
- package/lib/gridfs/index.js +3 -3
- package/lib/gridfs/index.js.map +1 -1
- package/lib/gridfs/upload.js +1 -1
- package/lib/gridfs/upload.js.map +1 -1
- package/lib/logger.js +5 -5
- package/lib/logger.js.map +1 -1
- package/lib/mongo_client.js +7 -7
- package/lib/mongo_client.js.map +1 -1
- package/lib/mongo_types.js.map +1 -1
- package/lib/operations/add_user.js +3 -3
- package/lib/operations/add_user.js.map +1 -1
- package/lib/operations/aggregate.js +3 -4
- package/lib/operations/aggregate.js.map +1 -1
- package/lib/operations/bulk_write.js +1 -1
- package/lib/operations/bulk_write.js.map +1 -1
- package/lib/operations/command.js +7 -3
- package/lib/operations/command.js.map +1 -1
- package/lib/operations/common_functions.js +1 -1
- package/lib/operations/common_functions.js.map +1 -1
- package/lib/operations/connect.js +1 -1
- package/lib/operations/connect.js.map +1 -1
- package/lib/operations/count.js +1 -1
- package/lib/operations/count.js.map +1 -1
- package/lib/operations/create_collection.js +1 -1
- package/lib/operations/create_collection.js.map +1 -1
- package/lib/operations/delete.js +6 -6
- package/lib/operations/delete.js.map +1 -1
- package/lib/operations/distinct.js +4 -4
- package/lib/operations/distinct.js.map +1 -1
- package/lib/operations/drop.js +2 -2
- package/lib/operations/drop.js.map +1 -1
- package/lib/operations/estimated_document_count.js +2 -2
- package/lib/operations/estimated_document_count.js.map +1 -1
- package/lib/operations/execute_operation.js +24 -7
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/find.js +8 -8
- package/lib/operations/find.js.map +1 -1
- package/lib/operations/find_and_modify.js +7 -7
- package/lib/operations/find_and_modify.js.map +1 -1
- package/lib/operations/get_more.js +28 -0
- package/lib/operations/get_more.js.map +1 -0
- package/lib/operations/indexes.js +14 -14
- package/lib/operations/indexes.js.map +1 -1
- package/lib/operations/insert.js +5 -5
- package/lib/operations/insert.js.map +1 -1
- package/lib/operations/list_collections.js +12 -7
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/operations/list_databases.js +1 -1
- package/lib/operations/list_databases.js.map +1 -1
- package/lib/operations/map_reduce.js +6 -6
- package/lib/operations/map_reduce.js.map +1 -1
- package/lib/operations/operation.js +4 -2
- package/lib/operations/operation.js.map +1 -1
- package/lib/operations/remove_user.js +1 -1
- package/lib/operations/remove_user.js.map +1 -1
- package/lib/operations/rename.js +2 -2
- package/lib/operations/rename.js.map +1 -1
- package/lib/operations/set_profiling_level.js +1 -1
- package/lib/operations/set_profiling_level.js.map +1 -1
- package/lib/operations/stats.js +2 -2
- package/lib/operations/stats.js.map +1 -1
- package/lib/operations/update.js +12 -12
- package/lib/operations/update.js.map +1 -1
- package/lib/read_preference.js +4 -1
- package/lib/read_preference.js.map +1 -1
- package/lib/sdam/monitor.js +14 -14
- package/lib/sdam/monitor.js.map +1 -1
- package/lib/sdam/server.js +19 -12
- package/lib/sdam/server.js.map +1 -1
- package/lib/sdam/server_description.js +3 -3
- package/lib/sdam/server_description.js.map +1 -1
- package/lib/sdam/server_selection.js +36 -1
- package/lib/sdam/server_selection.js.map +1 -1
- package/lib/sdam/srv_polling.js +9 -9
- package/lib/sdam/srv_polling.js.map +1 -1
- package/lib/sdam/topology.js +29 -21
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sdam/topology_description.js +34 -12
- package/lib/sdam/topology_description.js.map +1 -1
- package/lib/sessions.js +19 -19
- package/lib/sessions.js.map +1 -1
- package/lib/transactions.js.map +1 -1
- package/lib/utils.js +53 -46
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +60 -37
- package/mongodb.ts34.d.ts +61 -37
- package/package.json +26 -30
- package/src/bson.ts +1 -0
- package/src/bulk/common.ts +50 -37
- package/src/change_stream.ts +10 -8
- package/src/cmap/commands.ts +11 -8
- package/src/cmap/connection.ts +1 -0
- package/src/cmap/stream_description.ts +3 -3
- package/src/cmap/wire_protocol/constants.ts +4 -4
- package/src/collection.ts +40 -25
- package/src/connection_string.ts +75 -36
- package/src/cursor/abstract_cursor.ts +9 -11
- package/src/db.ts +5 -6
- package/src/error.ts +51 -23
- package/src/mongo_client.ts +12 -0
- package/src/mongo_types.ts +11 -14
- package/src/operations/aggregate.ts +1 -2
- package/src/operations/command.ts +5 -0
- package/src/operations/create_collection.ts +1 -1
- package/src/operations/execute_operation.ts +22 -2
- package/src/operations/get_more.ts +49 -0
- package/src/operations/list_collections.ts +12 -4
- package/src/operations/operation.ts +5 -1
- package/src/read_preference.ts +4 -1
- package/src/sdam/server.ts +8 -0
- package/src/sdam/server_selection.ts +43 -0
- package/src/sdam/srv_polling.ts +12 -11
- package/src/sdam/topology.ts +27 -10
- package/src/sdam/topology_description.ts +35 -11
- package/src/sessions.ts +2 -1
- package/src/transactions.ts +2 -2
- package/src/utils.ts +67 -56
|
@@ -15,6 +15,8 @@ const LIST_COLLECTIONS_WIRE_VERSION = 3;
|
|
|
15
15
|
export interface ListCollectionsOptions extends CommandOperationOptions {
|
|
16
16
|
/** Since 4.0: If true, will only return the collection name in the response, and will omit additional info */
|
|
17
17
|
nameOnly?: boolean;
|
|
18
|
+
/** Since 4.0: If true and nameOnly is true, allows a user without the required privilege (i.e. listCollections action on the database) to run the command when access control is enforced. */
|
|
19
|
+
authorizedCollections?: boolean;
|
|
18
20
|
/** The batchSize for the returned command cursor or if pre 2.8 the systems batch collection */
|
|
19
21
|
batchSize?: number;
|
|
20
22
|
}
|
|
@@ -25,6 +27,7 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
|
|
|
25
27
|
db: Db;
|
|
26
28
|
filter: Document;
|
|
27
29
|
nameOnly: boolean;
|
|
30
|
+
authorizedCollections: boolean;
|
|
28
31
|
batchSize?: number;
|
|
29
32
|
|
|
30
33
|
constructor(db: Db, filter: Document, options?: ListCollectionsOptions) {
|
|
@@ -34,6 +37,7 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
|
|
|
34
37
|
this.db = db;
|
|
35
38
|
this.filter = filter;
|
|
36
39
|
this.nameOnly = !!this.options.nameOnly;
|
|
40
|
+
this.authorizedCollections = !!this.options.authorizedCollections;
|
|
37
41
|
|
|
38
42
|
if (typeof this.options.batchSize === 'number') {
|
|
39
43
|
this.batchSize = this.options.batchSize;
|
|
@@ -90,14 +94,18 @@ export class ListCollectionsOperation extends CommandOperation<string[]> {
|
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
|
-
|
|
97
|
+
return super.executeCommand(server, session, this.generateCommand(), callback);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* This is here for the purpose of unit testing the final command that gets sent. */
|
|
101
|
+
generateCommand(): Document {
|
|
102
|
+
return {
|
|
94
103
|
listCollections: 1,
|
|
95
104
|
filter: this.filter,
|
|
96
105
|
cursor: this.batchSize ? { batchSize: this.batchSize } : {},
|
|
97
|
-
nameOnly: this.nameOnly
|
|
106
|
+
nameOnly: this.nameOnly,
|
|
107
|
+
authorizedCollections: this.authorizedCollections
|
|
98
108
|
};
|
|
99
|
-
|
|
100
|
-
return super.executeCommand(server, session, command, callback);
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
111
|
|
|
@@ -10,7 +10,8 @@ export const Aspect = {
|
|
|
10
10
|
RETRYABLE: Symbol('RETRYABLE'),
|
|
11
11
|
EXPLAINABLE: Symbol('EXPLAINABLE'),
|
|
12
12
|
SKIP_COLLATION: Symbol('SKIP_COLLATION'),
|
|
13
|
-
CURSOR_CREATING: Symbol('CURSOR_CREATING')
|
|
13
|
+
CURSOR_CREATING: Symbol('CURSOR_CREATING'),
|
|
14
|
+
CURSOR_ITERATING: Symbol('CURSOR_ITERATING')
|
|
14
15
|
} as const;
|
|
15
16
|
|
|
16
17
|
/** @public */
|
|
@@ -31,6 +32,7 @@ export interface OperationOptions extends BSONSerializeOptions {
|
|
|
31
32
|
|
|
32
33
|
/** @internal Hints to `executeOperation` that this operation should not unpin on an ended transaction */
|
|
33
34
|
bypassPinningCheck?: boolean;
|
|
35
|
+
omitReadPreference?: boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/** @internal */
|
|
@@ -49,6 +51,7 @@ export abstract class AbstractOperation<TResult = any> {
|
|
|
49
51
|
readPreference: ReadPreference;
|
|
50
52
|
server!: Server;
|
|
51
53
|
bypassPinningCheck: boolean;
|
|
54
|
+
trySecondaryWrite: boolean;
|
|
52
55
|
|
|
53
56
|
// BSON serialization options
|
|
54
57
|
bsonOptions?: BSONSerializeOptions;
|
|
@@ -72,6 +75,7 @@ export abstract class AbstractOperation<TResult = any> {
|
|
|
72
75
|
|
|
73
76
|
this.options = options;
|
|
74
77
|
this.bypassPinningCheck = !!options.bypassPinningCheck;
|
|
78
|
+
this.trySecondaryWrite = false;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
abstract execute(server: Server, session: ClientSession, callback: Callback<TResult>): void;
|
package/src/read_preference.ts
CHANGED
|
@@ -156,7 +156,10 @@ export class ReadPreference {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
if (typeof readPreference === 'string') {
|
|
159
|
-
return new ReadPreference(readPreference as ReadPreferenceMode, readPreferenceTags
|
|
159
|
+
return new ReadPreference(readPreference as ReadPreferenceMode, readPreferenceTags, {
|
|
160
|
+
maxStalenessSeconds: options.maxStalenessSeconds,
|
|
161
|
+
hedge: options.hedge
|
|
162
|
+
});
|
|
160
163
|
} else if (!(readPreference instanceof ReadPreference) && typeof readPreference === 'object') {
|
|
161
164
|
const mode = readPreference.mode || readPreference.preference;
|
|
162
165
|
if (mode && typeof mode === 'string') {
|
package/src/sdam/server.ts
CHANGED
|
@@ -299,6 +299,14 @@ export class Server extends TypedEventEmitter<ServerEvents> {
|
|
|
299
299
|
// Clone the options
|
|
300
300
|
const finalOptions = Object.assign({}, options, { wireProtocolCommand: false });
|
|
301
301
|
|
|
302
|
+
// There are cases where we need to flag the read preference not to get sent in
|
|
303
|
+
// the command, such as pre-5.0 servers attempting to perform an aggregate write
|
|
304
|
+
// with a non-primary read preference. In this case the effective read preference
|
|
305
|
+
// (primary) is not the same as the provided and must be removed completely.
|
|
306
|
+
if (finalOptions.omitReadPreference) {
|
|
307
|
+
delete finalOptions.readPreference;
|
|
308
|
+
}
|
|
309
|
+
|
|
302
310
|
// error if collation not supported
|
|
303
311
|
if (collationNotSupported(this, cmd)) {
|
|
304
312
|
callback(new MongoCompatibilityError(`Server ${this.name} does not support collation`));
|
|
@@ -8,6 +8,9 @@ import type { ServerDescription, TagSet } from './server_description';
|
|
|
8
8
|
const IDLE_WRITE_PERIOD = 10000;
|
|
9
9
|
const SMALLEST_MAX_STALENESS_SECONDS = 90;
|
|
10
10
|
|
|
11
|
+
// Minimum version to try writes on secondaries.
|
|
12
|
+
export const MIN_SECONDARY_WRITE_WIRE_VERSION = 13;
|
|
13
|
+
|
|
11
14
|
/** @public */
|
|
12
15
|
export type ServerSelector = (
|
|
13
16
|
topologyDescription: TopologyDescription,
|
|
@@ -28,6 +31,46 @@ export function writableServerSelector(): ServerSelector {
|
|
|
28
31
|
);
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The purpose of this selector is to select the same server, only
|
|
36
|
+
* if it is in a state that it can have commands sent to it.
|
|
37
|
+
*/
|
|
38
|
+
export function sameServerSelector(description?: ServerDescription): ServerSelector {
|
|
39
|
+
return (
|
|
40
|
+
topologyDescription: TopologyDescription,
|
|
41
|
+
servers: ServerDescription[]
|
|
42
|
+
): ServerDescription[] => {
|
|
43
|
+
if (!description) return [];
|
|
44
|
+
// Filter the servers to match the provided description only if
|
|
45
|
+
// the type is not unknown.
|
|
46
|
+
return servers.filter(sd => {
|
|
47
|
+
return sd.address === description.address && sd.type !== ServerType.Unknown;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns a server selector that uses a read preference to select a
|
|
54
|
+
* server potentially for a write on a secondary.
|
|
55
|
+
*/
|
|
56
|
+
export function secondaryWritableServerSelector(
|
|
57
|
+
wireVersion?: number,
|
|
58
|
+
readPreference?: ReadPreference
|
|
59
|
+
): ServerSelector {
|
|
60
|
+
// If server version < 5.0, read preference always primary.
|
|
61
|
+
// If server version >= 5.0...
|
|
62
|
+
// - If read preference is supplied, use that.
|
|
63
|
+
// - If no read preference is supplied, use primary.
|
|
64
|
+
if (
|
|
65
|
+
!readPreference ||
|
|
66
|
+
!wireVersion ||
|
|
67
|
+
(wireVersion && wireVersion < MIN_SECONDARY_WRITE_WIRE_VERSION)
|
|
68
|
+
) {
|
|
69
|
+
return readPreferenceServerSelector(ReadPreference.primary);
|
|
70
|
+
}
|
|
71
|
+
return readPreferenceServerSelector(readPreference);
|
|
72
|
+
}
|
|
73
|
+
|
|
31
74
|
/**
|
|
32
75
|
* Reduces the passed in array of servers by the rules of the "Max Staleness" specification
|
|
33
76
|
* found here: https://github.com/mongodb/specifications/blob/master/source/max-staleness/max-staleness.rst
|
package/src/sdam/srv_polling.ts
CHANGED
|
@@ -29,18 +29,15 @@ export class SrvPollingEvent {
|
|
|
29
29
|
this.srvRecords = srvRecords;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
return new
|
|
34
|
-
this.srvRecords.map(record => {
|
|
35
|
-
const host = new HostAddress(`${record.name}:${record.port}`);
|
|
36
|
-
return [host.toString(), host];
|
|
37
|
-
})
|
|
38
|
-
);
|
|
32
|
+
hostnames(): Set<string> {
|
|
33
|
+
return new Set(this.srvRecords.map(r => HostAddress.fromSrvRecord(r).toString()));
|
|
39
34
|
}
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
/** @internal */
|
|
43
38
|
export interface SrvPollerOptions extends LoggerOptions {
|
|
39
|
+
srvServiceName: string;
|
|
40
|
+
srvMaxHosts: number;
|
|
44
41
|
srvHost: string;
|
|
45
42
|
heartbeatFrequencyMS: number;
|
|
46
43
|
}
|
|
@@ -58,6 +55,8 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
|
|
|
58
55
|
logger: Logger;
|
|
59
56
|
haMode: boolean;
|
|
60
57
|
generation: number;
|
|
58
|
+
srvMaxHosts: number;
|
|
59
|
+
srvServiceName: string;
|
|
61
60
|
_timeout?: NodeJS.Timeout;
|
|
62
61
|
|
|
63
62
|
/** @event */
|
|
@@ -71,8 +70,10 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
this.srvHost = options.srvHost;
|
|
73
|
+
this.srvMaxHosts = options.srvMaxHosts ?? 0;
|
|
74
|
+
this.srvServiceName = options.srvServiceName ?? 'mongodb';
|
|
74
75
|
this.rescanSrvIntervalMS = 60000;
|
|
75
|
-
this.heartbeatFrequencyMS = options.heartbeatFrequencyMS
|
|
76
|
+
this.heartbeatFrequencyMS = options.heartbeatFrequencyMS ?? 10000;
|
|
76
77
|
this.logger = new Logger('srvPoller', options);
|
|
77
78
|
|
|
78
79
|
this.haMode = false;
|
|
@@ -82,7 +83,7 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
get srvAddress(): string {
|
|
85
|
-
return `
|
|
86
|
+
return `_${this.srvServiceName}._tcp.${this.srvHost}`;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
get intervalMS(): number {
|
|
@@ -143,13 +144,13 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
const finalAddresses: dns.SrvRecord[] = [];
|
|
146
|
-
|
|
147
|
+
for (const record of srvRecords) {
|
|
147
148
|
if (matchesParentDomain(record.name, this.srvHost)) {
|
|
148
149
|
finalAddresses.push(record);
|
|
149
150
|
} else {
|
|
150
151
|
this.parentDomainMismatch(record);
|
|
151
152
|
}
|
|
152
|
-
}
|
|
153
|
+
}
|
|
153
154
|
|
|
154
155
|
if (!finalAddresses.length) {
|
|
155
156
|
this.failure('No valid addresses found at host');
|
package/src/sdam/topology.ts
CHANGED
|
@@ -27,7 +27,8 @@ import {
|
|
|
27
27
|
HostAddress,
|
|
28
28
|
ns,
|
|
29
29
|
emitWarning,
|
|
30
|
-
EventEmitterWithState
|
|
30
|
+
EventEmitterWithState,
|
|
31
|
+
shuffle
|
|
31
32
|
} from '../utils';
|
|
32
33
|
import {
|
|
33
34
|
TopologyType,
|
|
@@ -140,6 +141,8 @@ export interface TopologyPrivate {
|
|
|
140
141
|
|
|
141
142
|
/** @public */
|
|
142
143
|
export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
|
|
144
|
+
srvMaxHosts: number;
|
|
145
|
+
srvServiceName: string;
|
|
143
146
|
hosts: HostAddress[];
|
|
144
147
|
retryWrites: boolean;
|
|
145
148
|
retryReads: boolean;
|
|
@@ -182,8 +185,8 @@ export type TopologyEvents = {
|
|
|
182
185
|
topologyOpening(event: TopologyOpeningEvent): void;
|
|
183
186
|
topologyDescriptionChanged(event: TopologyDescriptionChangedEvent): void;
|
|
184
187
|
error(error: Error): void;
|
|
185
|
-
/**
|
|
186
|
-
open(
|
|
188
|
+
/** @internal */
|
|
189
|
+
open(topology: Topology): void;
|
|
187
190
|
close(): void;
|
|
188
191
|
timeout(): void;
|
|
189
192
|
} & Omit<ServerEvents, 'connect'> &
|
|
@@ -290,8 +293,15 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
290
293
|
const topologyType = topologyTypeFromOptions(options);
|
|
291
294
|
const topologyId = globalTopologyCounter++;
|
|
292
295
|
|
|
296
|
+
const selectedHosts =
|
|
297
|
+
options.srvMaxHosts == null ||
|
|
298
|
+
options.srvMaxHosts === 0 ||
|
|
299
|
+
options.srvMaxHosts >= seedlist.length
|
|
300
|
+
? seedlist
|
|
301
|
+
: shuffle(seedlist, options.srvMaxHosts);
|
|
302
|
+
|
|
293
303
|
const serverDescriptions = new Map();
|
|
294
|
-
for (const hostAddress of
|
|
304
|
+
for (const hostAddress of selectedHosts) {
|
|
295
305
|
serverDescriptions.set(hostAddress.toString(), new ServerDescription(hostAddress));
|
|
296
306
|
}
|
|
297
307
|
|
|
@@ -339,7 +349,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
339
349
|
options.srvPoller ??
|
|
340
350
|
new SrvPoller({
|
|
341
351
|
heartbeatFrequencyMS: this.s.heartbeatFrequencyMS,
|
|
342
|
-
srvHost: options.srvHost
|
|
352
|
+
srvHost: options.srvHost,
|
|
353
|
+
srvMaxHosts: options.srvMaxHosts,
|
|
354
|
+
srvServiceName: options.srvServiceName
|
|
343
355
|
});
|
|
344
356
|
|
|
345
357
|
this.on(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
|
|
@@ -363,7 +375,10 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
363
375
|
|
|
364
376
|
private detectSrvRecords(ev: SrvPollingEvent) {
|
|
365
377
|
const previousTopologyDescription = this.s.description;
|
|
366
|
-
this.s.description = this.s.description.updateFromSrvPollingEvent(
|
|
378
|
+
this.s.description = this.s.description.updateFromSrvPollingEvent(
|
|
379
|
+
ev,
|
|
380
|
+
this.s.options.srvMaxHosts
|
|
381
|
+
);
|
|
367
382
|
if (this.s.description === previousTopologyDescription) {
|
|
368
383
|
// Nothing changed, so return
|
|
369
384
|
return;
|
|
@@ -456,8 +471,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
456
471
|
}
|
|
457
472
|
|
|
458
473
|
stateTransition(this, STATE_CONNECTED);
|
|
459
|
-
|
|
460
|
-
this.emit(Topology.OPEN, err, this);
|
|
474
|
+
this.emit(Topology.OPEN, this);
|
|
461
475
|
this.emit(Topology.CONNECT, this);
|
|
462
476
|
|
|
463
477
|
if (typeof callback === 'function') callback(undefined, this);
|
|
@@ -467,8 +481,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
467
481
|
}
|
|
468
482
|
|
|
469
483
|
stateTransition(this, STATE_CONNECTED);
|
|
470
|
-
|
|
471
|
-
this.emit(Topology.OPEN, err, this);
|
|
484
|
+
this.emit(Topology.OPEN, this);
|
|
472
485
|
this.emit(Topology.CONNECT, this);
|
|
473
486
|
|
|
474
487
|
if (typeof callback === 'function') callback(undefined, this);
|
|
@@ -797,6 +810,10 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
|
|
|
797
810
|
return result;
|
|
798
811
|
}
|
|
799
812
|
|
|
813
|
+
get commonWireVersion(): number | undefined {
|
|
814
|
+
return this.description.commonWireVersion;
|
|
815
|
+
}
|
|
816
|
+
|
|
800
817
|
get logicalSessionTimeoutMinutes(): number | undefined {
|
|
801
818
|
return this.description.logicalSessionTimeoutMinutes;
|
|
802
819
|
}
|
|
@@ -4,6 +4,7 @@ import { TopologyType, ServerType } from './common';
|
|
|
4
4
|
import type { ObjectId, Document } from '../bson';
|
|
5
5
|
import type { SrvPollingEvent } from './srv_polling';
|
|
6
6
|
import { MongoError, MongoRuntimeError } from '../error';
|
|
7
|
+
import { shuffle } from '../utils';
|
|
7
8
|
|
|
8
9
|
// constants related to compatibility checks
|
|
9
10
|
const MIN_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_SERVER_VERSION;
|
|
@@ -139,23 +140,46 @@ export class TopologyDescription {
|
|
|
139
140
|
* Returns a new TopologyDescription based on the SrvPollingEvent
|
|
140
141
|
* @internal
|
|
141
142
|
*/
|
|
142
|
-
updateFromSrvPollingEvent(ev: SrvPollingEvent): TopologyDescription {
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
updateFromSrvPollingEvent(ev: SrvPollingEvent, srvMaxHosts = 0): TopologyDescription {
|
|
144
|
+
/** The SRV addresses defines the set of addresses we should be using */
|
|
145
|
+
const incomingHostnames = ev.hostnames();
|
|
146
|
+
const currentHostnames = new Set(this.servers.keys());
|
|
147
|
+
|
|
148
|
+
const hostnamesToAdd = new Set<string>(incomingHostnames);
|
|
149
|
+
const hostnamesToRemove = new Set<string>();
|
|
150
|
+
for (const hostname of currentHostnames) {
|
|
151
|
+
// filter hostnamesToAdd (made from incomingHostnames) down to what is *not* present in currentHostnames
|
|
152
|
+
hostnamesToAdd.delete(hostname);
|
|
153
|
+
if (!incomingHostnames.has(hostname)) {
|
|
154
|
+
// If the SRV Records no longer include this hostname
|
|
155
|
+
// we have to stop using it
|
|
156
|
+
hostnamesToRemove.add(hostname);
|
|
150
157
|
}
|
|
151
158
|
}
|
|
152
159
|
|
|
153
|
-
if (
|
|
160
|
+
if (hostnamesToAdd.size === 0 && hostnamesToRemove.size === 0) {
|
|
161
|
+
// No new hosts to add and none to remove
|
|
154
162
|
return this;
|
|
155
163
|
}
|
|
156
164
|
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
const serverDescriptions = new Map(this.servers);
|
|
166
|
+
for (const removedHost of hostnamesToRemove) {
|
|
167
|
+
serverDescriptions.delete(removedHost);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (hostnamesToAdd.size > 0) {
|
|
171
|
+
if (srvMaxHosts === 0) {
|
|
172
|
+
// Add all!
|
|
173
|
+
for (const hostToAdd of hostnamesToAdd) {
|
|
174
|
+
serverDescriptions.set(hostToAdd, new ServerDescription(hostToAdd));
|
|
175
|
+
}
|
|
176
|
+
} else if (serverDescriptions.size < srvMaxHosts) {
|
|
177
|
+
// Add only the amount needed to get us back to srvMaxHosts
|
|
178
|
+
const selectedHosts = shuffle(hostnamesToAdd, srvMaxHosts - serverDescriptions.size);
|
|
179
|
+
for (const selectedHostToAdd of selectedHosts) {
|
|
180
|
+
serverDescriptions.set(selectedHostToAdd, new ServerDescription(selectedHostToAdd));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
159
183
|
}
|
|
160
184
|
|
|
161
185
|
return new TopologyDescription(
|
package/src/sessions.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
MongoError,
|
|
9
9
|
MongoInvalidArgumentError,
|
|
10
10
|
isRetryableError,
|
|
11
|
+
isRetryableEndTransactionError,
|
|
11
12
|
MongoCompatibilityError,
|
|
12
13
|
MongoNetworkError,
|
|
13
14
|
MongoWriteConcernError,
|
|
@@ -767,7 +768,7 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
|
|
|
767
768
|
session.unpin();
|
|
768
769
|
}
|
|
769
770
|
|
|
770
|
-
if (err &&
|
|
771
|
+
if (err && isRetryableEndTransactionError(err as MongoError)) {
|
|
771
772
|
// SPEC-1185: apply majority write concern when retrying commitTransaction
|
|
772
773
|
if (command.commitTransaction) {
|
|
773
774
|
// per txns spec, must unpin session in this case
|
package/src/transactions.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReadPreference } from './read_preference';
|
|
2
2
|
import { MongoRuntimeError, MongoTransactionError } from './error';
|
|
3
|
-
import { ReadConcern } from './read_concern';
|
|
3
|
+
import { ReadConcern, ReadConcernLike } from './read_concern';
|
|
4
4
|
import { WriteConcern } from './write_concern';
|
|
5
5
|
import type { Server } from './sdam/server';
|
|
6
6
|
import type { CommandOperationOptions } from './operations/command';
|
|
@@ -63,7 +63,7 @@ const COMMITTED_STATES: Set<TxnState> = new Set([
|
|
|
63
63
|
export interface TransactionOptions extends CommandOperationOptions {
|
|
64
64
|
// TODO(NODE-3344): These options use the proper class forms of these settings, it should accept the basic enum values too
|
|
65
65
|
/** A default read concern for commands in this transaction */
|
|
66
|
-
readConcern?:
|
|
66
|
+
readConcern?: ReadConcernLike;
|
|
67
67
|
/** A default writeConcern for commands in this transaction */
|
|
68
68
|
writeConcern?: WriteConcern;
|
|
69
69
|
/** A default read preference for commands in this transaction */
|
package/src/utils.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type { CommandOperationOptions, OperationParent } from './operations/comm
|
|
|
28
28
|
import { ReadPreference } from './read_preference';
|
|
29
29
|
import { URL } from 'url';
|
|
30
30
|
import { MAX_SUPPORTED_WIRE_VERSION } from './cmap/wire_protocol/constants';
|
|
31
|
+
import type { SrvRecord } from 'dns';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* MongoDB Driver style callback
|
|
@@ -41,23 +42,6 @@ export const MAX_JS_INT = Number.MAX_SAFE_INTEGER + 1;
|
|
|
41
42
|
|
|
42
43
|
export type AnyOptions = Document;
|
|
43
44
|
|
|
44
|
-
/**
|
|
45
|
-
* Add a readonly enumerable property.
|
|
46
|
-
* @internal
|
|
47
|
-
*/
|
|
48
|
-
export function getSingleProperty(
|
|
49
|
-
obj: AnyOptions,
|
|
50
|
-
name: string | number | symbol,
|
|
51
|
-
value: unknown
|
|
52
|
-
): void {
|
|
53
|
-
Object.defineProperty(obj, name, {
|
|
54
|
-
enumerable: true,
|
|
55
|
-
get() {
|
|
56
|
-
return value;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
45
|
/**
|
|
62
46
|
* Throws if collectionName is not a valid mongodb collection namespace.
|
|
63
47
|
* @internal
|
|
@@ -185,17 +169,6 @@ export function isObject(arg: unknown): arg is object {
|
|
|
185
169
|
return '[object Object]' === Object.prototype.toString.call(arg);
|
|
186
170
|
}
|
|
187
171
|
|
|
188
|
-
/** @internal */
|
|
189
|
-
export function decorateCommand(command: Document, options: Document, exclude: string[]): Document {
|
|
190
|
-
for (const name in options) {
|
|
191
|
-
if (!exclude.includes(name)) {
|
|
192
|
-
command[name] = options[name];
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return command;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
172
|
/** @internal */
|
|
200
173
|
export function mergeOptions<T, S>(target: T, source: S): T & S {
|
|
201
174
|
return { ...target, ...source };
|
|
@@ -657,11 +630,6 @@ export function databaseNamespace(ns: string): string {
|
|
|
657
630
|
return ns.split('.')[0];
|
|
658
631
|
}
|
|
659
632
|
|
|
660
|
-
/** @internal */
|
|
661
|
-
export function collectionNamespace(ns: string): string {
|
|
662
|
-
return ns.split('.').slice(1).join('.');
|
|
663
|
-
}
|
|
664
|
-
|
|
665
633
|
/**
|
|
666
634
|
* Synchronously Generate a UUIDv4
|
|
667
635
|
* @internal
|
|
@@ -987,7 +955,7 @@ export function makeInterruptibleAsyncInterval(
|
|
|
987
955
|
): InterruptibleAsyncInterval {
|
|
988
956
|
let timerId: NodeJS.Timeout | undefined;
|
|
989
957
|
let lastCallTime: number;
|
|
990
|
-
let
|
|
958
|
+
let cannotBeExpedited = false;
|
|
991
959
|
let stopped = false;
|
|
992
960
|
|
|
993
961
|
options = options ?? {};
|
|
@@ -998,10 +966,8 @@ export function makeInterruptibleAsyncInterval(
|
|
|
998
966
|
|
|
999
967
|
function wake() {
|
|
1000
968
|
const currentTime = clock();
|
|
1001
|
-
const
|
|
1002
|
-
const
|
|
1003
|
-
const timeUntilNextCall = interval - timeSinceLastCall;
|
|
1004
|
-
lastWakeTime = currentTime;
|
|
969
|
+
const nextScheduledCallTime = lastCallTime + interval;
|
|
970
|
+
const timeUntilNextCall = nextScheduledCallTime - currentTime;
|
|
1005
971
|
|
|
1006
972
|
// For the streaming protocol: there is nothing obviously stopping this
|
|
1007
973
|
// interval from being woken up again while we are waiting "infinitely"
|
|
@@ -1009,8 +975,17 @@ export function makeInterruptibleAsyncInterval(
|
|
|
1009
975
|
// never completes, the `timeUntilNextCall` will continue to grow
|
|
1010
976
|
// negatively unbounded, so it will never trigger a reschedule here.
|
|
1011
977
|
|
|
978
|
+
// This is possible in virtualized environments like AWS Lambda where our
|
|
979
|
+
// clock is unreliable. In these cases the timer is "running" but never
|
|
980
|
+
// actually completes, so we want to execute immediately and then attempt
|
|
981
|
+
// to reschedule.
|
|
982
|
+
if (timeUntilNextCall < 0) {
|
|
983
|
+
executeAndReschedule();
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
1012
987
|
// debounce multiple calls to wake within the `minInterval`
|
|
1013
|
-
if (
|
|
988
|
+
if (cannotBeExpedited) {
|
|
1014
989
|
return;
|
|
1015
990
|
}
|
|
1016
991
|
|
|
@@ -1018,14 +993,7 @@ export function makeInterruptibleAsyncInterval(
|
|
|
1018
993
|
// faster than the `minInterval`
|
|
1019
994
|
if (timeUntilNextCall > minInterval) {
|
|
1020
995
|
reschedule(minInterval);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
// This is possible in virtualized environments like AWS Lambda where our
|
|
1024
|
-
// clock is unreliable. In these cases the timer is "running" but never
|
|
1025
|
-
// actually completes, so we want to execute immediately and then attempt
|
|
1026
|
-
// to reschedule.
|
|
1027
|
-
if (timeUntilNextCall < 0) {
|
|
1028
|
-
executeAndReschedule();
|
|
996
|
+
cannotBeExpedited = true;
|
|
1029
997
|
}
|
|
1030
998
|
}
|
|
1031
999
|
|
|
@@ -1037,7 +1005,7 @@ export function makeInterruptibleAsyncInterval(
|
|
|
1037
1005
|
}
|
|
1038
1006
|
|
|
1039
1007
|
lastCallTime = 0;
|
|
1040
|
-
|
|
1008
|
+
cannotBeExpedited = false;
|
|
1041
1009
|
}
|
|
1042
1010
|
|
|
1043
1011
|
function reschedule(ms?: number) {
|
|
@@ -1050,7 +1018,7 @@ export function makeInterruptibleAsyncInterval(
|
|
|
1050
1018
|
}
|
|
1051
1019
|
|
|
1052
1020
|
function executeAndReschedule() {
|
|
1053
|
-
|
|
1021
|
+
cannotBeExpedited = false;
|
|
1054
1022
|
lastCallTime = clock();
|
|
1055
1023
|
|
|
1056
1024
|
fn(err => {
|
|
@@ -1128,8 +1096,9 @@ export function isSuperset(set: Set<any> | any[], subset: Set<any> | any[]): boo
|
|
|
1128
1096
|
return true;
|
|
1129
1097
|
}
|
|
1130
1098
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1099
|
+
/** Returns the items that are uniquely in setA */
|
|
1100
|
+
export function setDifference<T>(setA: Iterable<T>, setB: Iterable<T>): Set<T> {
|
|
1101
|
+
const difference = new Set<T>(setA);
|
|
1133
1102
|
for (const elem of setB) {
|
|
1134
1103
|
difference.delete(elem);
|
|
1135
1104
|
}
|
|
@@ -1179,11 +1148,11 @@ export function isRecord(
|
|
|
1179
1148
|
* but instead something that is good enough for the purposes of
|
|
1180
1149
|
* command monitoring.
|
|
1181
1150
|
*/
|
|
1182
|
-
export function deepCopy<T
|
|
1151
|
+
export function deepCopy<T>(value: T): T {
|
|
1183
1152
|
if (value == null) {
|
|
1184
1153
|
return value;
|
|
1185
1154
|
} else if (Array.isArray(value)) {
|
|
1186
|
-
return value.map(item => deepCopy(item)) as T;
|
|
1155
|
+
return value.map(item => deepCopy(item)) as unknown as T;
|
|
1187
1156
|
} else if (isRecord(value)) {
|
|
1188
1157
|
const res = {} as any;
|
|
1189
1158
|
for (const key in value) {
|
|
@@ -1198,11 +1167,11 @@ export function deepCopy<T extends any>(value: T): T {
|
|
|
1198
1167
|
case 'date':
|
|
1199
1168
|
return new ctor(Number(value));
|
|
1200
1169
|
case 'map':
|
|
1201
|
-
return new Map(value as any) as T;
|
|
1170
|
+
return new Map(value as any) as unknown as T;
|
|
1202
1171
|
case 'set':
|
|
1203
|
-
return new Set(value as any) as T;
|
|
1172
|
+
return new Set(value as any) as unknown as T;
|
|
1204
1173
|
case 'buffer':
|
|
1205
|
-
return Buffer.from(value as Buffer) as T;
|
|
1174
|
+
return Buffer.from(value as Buffer) as unknown as T;
|
|
1206
1175
|
}
|
|
1207
1176
|
}
|
|
1208
1177
|
|
|
@@ -1352,6 +1321,14 @@ export class HostAddress {
|
|
|
1352
1321
|
Object.freeze(this);
|
|
1353
1322
|
}
|
|
1354
1323
|
|
|
1324
|
+
[Symbol.for('nodejs.util.inspect.custom')](): string {
|
|
1325
|
+
return this.inspect();
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
inspect(): string {
|
|
1329
|
+
return `new HostAddress('${this.toString(true)}')`;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1355
1332
|
/**
|
|
1356
1333
|
* @param ipv6Brackets - optionally request ipv6 bracket notation required for connection strings
|
|
1357
1334
|
*/
|
|
@@ -1368,6 +1345,10 @@ export class HostAddress {
|
|
|
1368
1345
|
static fromString(s: string): HostAddress {
|
|
1369
1346
|
return new HostAddress(s);
|
|
1370
1347
|
}
|
|
1348
|
+
|
|
1349
|
+
static fromSrvRecord({ name, port }: SrvRecord): HostAddress {
|
|
1350
|
+
return HostAddress.fromString(`${name}:${port}`);
|
|
1351
|
+
}
|
|
1371
1352
|
}
|
|
1372
1353
|
|
|
1373
1354
|
export const DEFAULT_PK_FACTORY = {
|
|
@@ -1438,3 +1419,33 @@ export function parsePackageVersion({ version }: { version: string }): {
|
|
|
1438
1419
|
const [major, minor, patch] = version.split('.').map((n: string) => Number.parseInt(n, 10));
|
|
1439
1420
|
return { major, minor, patch };
|
|
1440
1421
|
}
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* Fisher–Yates Shuffle
|
|
1425
|
+
*
|
|
1426
|
+
* Reference: https://bost.ocks.org/mike/shuffle/
|
|
1427
|
+
* @param sequence - items to be shuffled
|
|
1428
|
+
* @param limit - Defaults to `0`. If nonzero shuffle will slice the randomized array e.g, `.slice(0, limit)` otherwise will return the entire randomized array.
|
|
1429
|
+
*/
|
|
1430
|
+
export function shuffle<T>(sequence: Iterable<T>, limit = 0): Array<T> {
|
|
1431
|
+
const items = Array.from(sequence); // shallow copy in order to never shuffle the input
|
|
1432
|
+
|
|
1433
|
+
if (limit > items.length) {
|
|
1434
|
+
throw new MongoRuntimeError('Limit must be less than the number of items');
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
let remainingItemsToShuffle = items.length;
|
|
1438
|
+
const lowerBound = limit % items.length === 0 ? 1 : items.length - limit;
|
|
1439
|
+
while (remainingItemsToShuffle > lowerBound) {
|
|
1440
|
+
// Pick a remaining element
|
|
1441
|
+
const randomIndex = Math.floor(Math.random() * remainingItemsToShuffle);
|
|
1442
|
+
remainingItemsToShuffle -= 1;
|
|
1443
|
+
|
|
1444
|
+
// And swap it with the current element
|
|
1445
|
+
const swapHold = items[remainingItemsToShuffle];
|
|
1446
|
+
items[remainingItemsToShuffle] = items[randomIndex];
|
|
1447
|
+
items[randomIndex] = swapHold;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
return limit % items.length === 0 ? items : items.slice(lowerBound);
|
|
1451
|
+
}
|