Skip to content

Commit

Permalink
Merge branch 'main' into beta-in-package-poc
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken committed Aug 7, 2024
2 parents f1da72b + 54efb7d commit 1d4ae58
Show file tree
Hide file tree
Showing 652 changed files with 79,328 additions and 44,948 deletions.
Empty file modified .evergreen/run-typescript.sh
100644 → 100755
Empty file.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ Change history can be found in [`HISTORY.md`](https://github.com/mongodb/node-mo

### Compatibility

For server and runtime version compatibility matrices, please refer to the following links:
The driver currently supports 3.6+ servers.

** 3.6 support is deprecated and support will be removed in a future version **

For exhaustive server and runtime version compatibility matrices, please refer to the following links:

- [MongoDB](https://www.mongodb.com/docs/drivers/node/current/compatibility/#mongodb-compatibility)
- [NodeJS](https://www.mongodb.com/docs/drivers/node/current/compatibility/#language-compatibility)
Expand Down
4 changes: 2 additions & 2 deletions src/bulk/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,8 @@ function handleMongoWriteConcernError(
callback(
new MongoBulkWriteError(
{
message: err.result?.writeConcernError.errmsg,
code: err.result?.writeConcernError.result
message: err.result.writeConcernError.errmsg,
code: err.result.writeConcernError.code
},
new BulkWriteResult(bulkResult, isOrdered)
)
Expand Down
20 changes: 17 additions & 3 deletions src/client-side-encryption/auto_encrypter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type MongoCryptConstructor,
type MongoCryptOptions
} from 'mongodb-client-encryption';
import * as net from 'net';

import { deserialize, type Document, serialize } from '../bson';
import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
Expand All @@ -11,6 +12,7 @@ import { getMongoDBClientEncryption } from '../deps';
import { MongoRuntimeError } from '../error';
import { MongoClient, type MongoClientOptions } from '../mongo_client';
import { MongoDBCollectionNamespace } from '../utils';
import { autoSelectSocketOptions } from './client_encryption';
import * as cryptoCallbacks from './crypto_callbacks';
import { MongoCryptInvalidArgumentError } from './errors';
import { MongocryptdManager } from './mongocryptd_manager';
Expand Down Expand Up @@ -297,10 +299,20 @@ export class AutoEncrypter {
serverSelectionTimeoutMS: 10000
};

if (options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') {
if (
(options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') &&
!net.getDefaultAutoSelectFamily
) {
// Only set family if autoSelectFamily options are not supported.
clientOptions.family = 4;
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: TS complains as this always returns true on versions where it is present.
if (net.getDefaultAutoSelectFamily) {
Object.assign(clientOptions, autoSelectSocketOptions(this._client.options));
}

this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions);
}
}
Expand Down Expand Up @@ -379,7 +391,8 @@ export class AutoEncrypter {
promoteValues: false,
promoteLongs: false,
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

return deserialize(await stateMachine.execute(this, context), {
Expand All @@ -399,7 +412,8 @@ export class AutoEncrypter {
const stateMachine = new StateMachine({
...options,
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

return await stateMachine.execute(this, context);
Expand Down
38 changes: 32 additions & 6 deletions src/client-side-encryption/client_encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { type Collection } from '../collection';
import { type FindCursor } from '../cursor/find_cursor';
import { type Db } from '../db';
import { getMongoDBClientEncryption } from '../deps';
import { type MongoClient } from '../mongo_client';
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
import { type Filter, type WithId } from '../mongo_types';
import { type CreateCollectionOptions } from '../operations/create_collection';
import { type DeleteResult } from '../operations/delete';
Expand All @@ -28,7 +28,11 @@ import {
type KMSProviders,
refreshKMSCredentials
} from './providers/index';
import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine';
import {
type ClientEncryptionSocketOptions,
type CSFLEKMSTlsOptions,
StateMachine
} from './state_machine';

/**
* @public
Expand Down Expand Up @@ -199,7 +203,8 @@ export class ClientEncryption {

const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey;
Expand Down Expand Up @@ -256,7 +261,8 @@ export class ClientEncryption {
const context = this._mongoCrypt.makeRewrapManyDataKeyContext(filterBson, keyEncryptionKeyBson);
const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

const { v: dataKeys } = deserialize(await stateMachine.execute(this, context));
Expand Down Expand Up @@ -637,7 +643,8 @@ export class ClientEncryption {

const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

const { v } = deserialize(await stateMachine.execute(this, context));
Expand Down Expand Up @@ -715,7 +722,8 @@ export class ClientEncryption {
const valueBuffer = serialize({ v: value });
const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);

Expand Down Expand Up @@ -957,3 +965,21 @@ export interface RangeOptions {
sparsity: Long;
precision?: number;
}

/**
* Get the socket options from the client.
* @param baseOptions - The mongo client options.
* @returns ClientEncryptionSocketOptions
*/
export function autoSelectSocketOptions(
baseOptions: MongoClientOptions
): ClientEncryptionSocketOptions {
const options: ClientEncryptionSocketOptions = { autoSelectFamily: true };
if ('autoSelectFamily' in baseOptions) {
options.autoSelectFamily = baseOptions.autoSelectFamily;
}
if ('autoSelectFamilyAttemptTimeout' in baseOptions) {
options.autoSelectFamilyAttemptTimeout = baseOptions.autoSelectFamilyAttemptTimeout;
}
return options;
}
34 changes: 28 additions & 6 deletions src/client-side-encryption/state_machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { type ProxyOptions } from '../cmap/connection';
import { getSocks, type SocksLib } from '../deps';
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
import { BufferPool, MongoDBCollectionNamespace, promiseWithResolvers } from '../utils';
import { type DataKey } from './client_encryption';
import { autoSelectSocketOptions, type DataKey } from './client_encryption';
import { MongoCryptError } from './errors';
import { type MongocryptdManager } from './mongocryptd_manager';
import { type KMSProviders } from './providers';
Expand Down Expand Up @@ -114,6 +114,16 @@ export type CSFLEKMSTlsOptions = {
[key: string]: ClientEncryptionTlsOptions | undefined;
};

/**
* @public
*
* Socket options to use for KMS requests.
*/
export type ClientEncryptionSocketOptions = Pick<
MongoClientOptions,
'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
>;

/**
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
Expand Down Expand Up @@ -153,6 +163,9 @@ export type StateMachineOptions = {

/** TLS options for KMS requests, if set. */
tlsOptions: CSFLEKMSTlsOptions;

/** Socket specific options we support. */
socketOptions: ClientEncryptionSocketOptions;
} & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;

/**
Expand Down Expand Up @@ -289,10 +302,17 @@ export class StateMachine {
async kmsRequest(request: MongoCryptKMSRequest): Promise<void> {
const parsedUrl = request.endpoint.split(':');
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
const options: tls.ConnectionOptions & { host: string; port: number } = {
const socketOptions = autoSelectSocketOptions(this.options.socketOptions || {});
const options: tls.ConnectionOptions & {
host: string;
port: number;
autoSelectFamily?: boolean;
autoSelectFamilyAttemptTimeout?: number;
} = {
host: parsedUrl[0],
servername: parsedUrl[0],
port
port,
...socketOptions
};
const message = request.message;
const buffer = new BufferPool();
Expand Down Expand Up @@ -351,10 +371,12 @@ export class StateMachine {

try {
if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
netSocket.connect({
const netSocketOptions = {
host: this.options.proxyOptions.proxyHost,
port: this.options.proxyOptions.proxyPort || 1080
});
port: this.options.proxyOptions.proxyPort || 1080,
...socketOptions
};
netSocket.connect(netSocketOptions);
await willConnect;

try {
Expand Down
4 changes: 3 additions & 1 deletion src/cmap/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export async function performInitialHandshake(
} catch (error) {
if (error instanceof MongoError) {
error.addErrorLabel(MongoErrorLabel.HandshakeError);
if (needsRetryableWriteLabel(error, response.maxWireVersion)) {
if (needsRetryableWriteLabel(error, response.maxWireVersion, conn.description.type)) {
error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
}
}
Expand Down Expand Up @@ -269,6 +269,8 @@ export const LEGAL_TLS_SOCKET_OPTIONS = [

/** @public */
export const LEGAL_TCP_SOCKET_OPTIONS = [
'autoSelectFamily',
'autoSelectFamilyAttemptTimeout',
'family',
'hints',
'localAddress',
Expand Down
7 changes: 7 additions & 0 deletions src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,13 @@ export const OPTIONS = {
autoEncryption: {
type: 'record'
},
autoSelectFamily: {
type: 'boolean',
default: true
},
autoSelectFamilyAttemptTimeout: {
type: 'uint'
},
bsonRegExp: {
type: 'boolean'
},
Expand Down
48 changes: 34 additions & 14 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Document } from './bson';
import type { ServerType } from './sdam/common';
import type { TopologyVersion } from './sdam/server_description';
import type { TopologyDescription } from './sdam/topology_description';

Expand Down Expand Up @@ -1158,6 +1159,23 @@ export class MongoServerSelectionError extends MongoSystemError {
}
}

/**
* The type of the result property of MongoWriteConcernError
* @public
*/
export interface WriteConcernErrorResult {
writeConcernError: {
code: number;
errmsg: string;
codeName?: string;
errInfo?: Document;
};
ok: number;
code?: number;
errorLabels?: string[];
[x: string | number]: unknown;
}

/**
* An error thrown when the server reports a writeConcernError
* @public
Expand All @@ -1178,16 +1196,8 @@ export class MongoWriteConcernError extends MongoServerError {
*
* @public
**/
constructor(result: {
writeConcernError: {
code: number;
errmsg: string;
codeName?: string;
errInfo?: Document;
};
errorLabels?: string[];
}) {
super({ ...result, ...result.writeConcernError });
constructor(result: WriteConcernErrorResult) {
super({ ...result.writeConcernError, ...result });
this.errInfo = result.writeConcernError.errInfo;
this.result = result;
}
Expand Down Expand Up @@ -1217,7 +1227,11 @@ const RETRYABLE_READ_ERROR_CODES = new Set<number>([
// see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
const RETRYABLE_WRITE_ERROR_CODES = RETRYABLE_READ_ERROR_CODES;

export function needsRetryableWriteLabel(error: Error, maxWireVersion: number): boolean {
export function needsRetryableWriteLabel(
error: Error,
maxWireVersion: number,
serverType: ServerType
): boolean {
// pre-4.4 server, then the driver adds an error label for every valid case
// execute operation will only inspect the label, code/message logic is handled here
if (error instanceof MongoNetworkError) {
Expand All @@ -1237,11 +1251,17 @@ export function needsRetryableWriteLabel(error: Error, maxWireVersion: number):
}

if (error instanceof MongoWriteConcernError) {
return RETRYABLE_WRITE_ERROR_CODES.has(error.result?.code ?? error.code ?? 0);
if (serverType === 'Mongos' && maxWireVersion < 9) {
// use original top-level code from server response
return RETRYABLE_WRITE_ERROR_CODES.has(error.result.code ?? 0);
}
return RETRYABLE_WRITE_ERROR_CODES.has(
error.result.writeConcernError.code ?? Number(error.code) ?? 0
);
}

if (error instanceof MongoError && typeof error.code === 'number') {
return RETRYABLE_WRITE_ERROR_CODES.has(error.code);
if (error instanceof MongoError) {
return RETRYABLE_WRITE_ERROR_CODES.has(Number(error.code));
}

const isNotWritablePrimaryError = LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE.test(error.message);
Expand Down
28 changes: 27 additions & 1 deletion src/gridfs/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,41 @@ export interface GridFSFile {

/** @internal */
export interface GridFSBucketReadStreamPrivate {
/**
* The running total number of bytes read from the chunks collection.
*/
bytesRead: number;
/**
* The number of bytes to remove from the last chunk read in the file. This is non-zero
* if `end` is not equal to the length of the document and `end` is not a multiple
* of the chunkSize.
*/
bytesToTrim: number;

/**
* The number of bytes to remove from the first chunk read in the file. This is non-zero
* if `start` is not equal to the 0 and `start` is not a multiple
* of the chunkSize.
*/
bytesToSkip: number;

files: Collection<GridFSFile>;
chunks: Collection<GridFSChunk>;
cursor?: FindCursor<GridFSChunk>;

/** The running total number of chunks read from the chunks collection. */
expected: number;
files: Collection<GridFSFile>;

/**
* The filter used to search in the _files_ collection (i.e., `{ _id: <> }`)
* This is not the same filter used when reading chunks from the chunks collection.
*/
filter: Document;

/** Indicates whether or not download has started. */
init: boolean;

/** The expected number of chunks to read, calculated from start, end, chunkSize and file length. */
expectedEnd: number;
file?: GridFSFile;
options: {
Expand Down
Loading

0 comments on commit 1d4ae58

Please sign in to comment.