Skip to content

refactor(NODE-3157)!: update find and modify interfaces for 4.0 #2799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 7, 2021
Merged
22 changes: 12 additions & 10 deletions src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ import {
FindOneAndDeleteOperation,
FindOneAndReplaceOperation,
FindOneAndUpdateOperation,
FindAndModifyOptions
FindOneAndDeleteOptions,
FindOneAndReplaceOptions,
FindOneAndUpdateOptions
} from './operations/find_and_modify';
import {
InsertOneOperation,
Expand Down Expand Up @@ -1098,15 +1100,15 @@ export class Collection {
*/
findOneAndDelete(filter: Document): Promise<Document>;
findOneAndDelete(filter: Document, callback: Callback<Document>): void;
findOneAndDelete(filter: Document, options: FindAndModifyOptions): Promise<Document>;
findOneAndDelete(filter: Document, options: FindOneAndDeleteOptions): Promise<Document>;
findOneAndDelete(
filter: Document,
options: FindAndModifyOptions,
options: FindOneAndDeleteOptions,
callback: Callback<Document>
): void;
findOneAndDelete(
filter: Document,
options?: FindAndModifyOptions | Callback<Document>,
options?: FindOneAndDeleteOptions | Callback<Document>,
callback?: Callback<Document>
): Promise<Document> | void {
if (typeof options === 'function') (callback = options), (options = {});
Expand All @@ -1131,18 +1133,18 @@ export class Collection {
findOneAndReplace(
filter: Document,
replacement: Document,
options: FindAndModifyOptions
options: FindOneAndReplaceOptions
): Promise<Document>;
findOneAndReplace(
filter: Document,
replacement: Document,
options: FindAndModifyOptions,
options: FindOneAndReplaceOptions,
callback: Callback<Document>
): void;
findOneAndReplace(
filter: Document,
replacement: Document,
options?: FindAndModifyOptions | Callback<Document>,
options?: FindOneAndReplaceOptions | Callback<Document>,
callback?: Callback<Document>
): Promise<Document> | void {
if (typeof options === 'function') (callback = options), (options = {});
Expand All @@ -1167,18 +1169,18 @@ export class Collection {
findOneAndUpdate(
filter: Document,
update: Document,
options: FindAndModifyOptions
options: FindOneAndUpdateOptions
): Promise<Document>;
findOneAndUpdate(
filter: Document,
update: Document,
options: FindAndModifyOptions,
options: FindOneAndUpdateOptions,
callback: Callback<Document>
): void;
findOneAndUpdate(
filter: Document,
update: Document,
options?: FindAndModifyOptions | Callback<Document>,
options?: FindOneAndUpdateOptions | Callback<Document>,
callback?: Callback<Document>
): Promise<Document> | void {
if (typeof options === 'function') (callback = options), (options = {});
Expand Down
8 changes: 6 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,12 @@ export type { DropCollectionOptions, DropDatabaseOptions } from './operations/dr
export type { EstimatedDocumentCountOptions } from './operations/estimated_document_count';
export type { EvalOptions } from './operations/eval';
export type { FindOptions } from './operations/find';
export type { Sort, SortDirection } from './sort';
export type { FindAndModifyOptions } from './operations/find_and_modify';
export type { Sort, SortDirection, SortDirectionForCmd, SortForCmd } from './sort';
export type {
FindOneAndDeleteOptions,
FindOneAndReplaceOptions,
FindOneAndUpdateOptions
} from './operations/find_and_modify';
export type {
IndexSpecification,
CreateIndexesOptions,
Expand Down
206 changes: 107 additions & 99 deletions src/operations/find_and_modify.ts
Original file line number Diff line number Diff line change
@@ -1,127 +1,146 @@
import { ReadPreference } from '../read_preference';
import {
maxWireVersion,
applyRetryableWrites,
decorateWithCollation,
hasAtomicOperators,
Callback
} from '../utils';
import { maxWireVersion, decorateWithCollation, hasAtomicOperators, Callback } from '../utils';
import { MongoError } from '../error';
import { CommandOperation, CommandOperationOptions } from './command';
import { defineAspects, Aspect } from './operation';
import type { Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import { Sort, formatSort } from '../sort';
import { Sort, SortForCmd, formatSort } from '../sort';
import type { ClientSession } from '../sessions';
import type { WriteConcern, WriteConcernSettings } from '../write_concern';

/** @public */
export interface FindAndModifyOptions extends CommandOperationOptions {
/** When false, returns the updated document rather than the original. The default is true. */
returnOriginal?: boolean;
/** Upsert the document if it does not exist. */
upsert?: boolean;
export interface FindOneAndDeleteOptions extends CommandOperationOptions {
/** An optional hint for query optimization. See the {@link https://docs.mongodb.com/manual/reference/command/update/#update-command-hint|update command} reference for more information.*/
hint?: Document;
/** Limits the fields to return for all matching documents. */
projection?: Document;
/** @deprecated use `projection` instead */
fields?: Document;
/** Determines which document the operation modifies if the query selects multiple documents. */
sort?: Sort;
}

/** @public */
export interface FindOneAndReplaceOptions extends CommandOperationOptions {
/** Allow driver to bypass schema validation in MongoDB 3.2 or higher. */
bypassDocumentValidation?: boolean;
/** An optional hint for query optimization. See the {@link https://docs.mongodb.com/manual/reference/command/update/#update-command-hint|update command} reference for more information.*/
hint?: Document;
/** Limits the fields to return for all matching documents. */
projection?: Document;
/** When false, returns the updated document rather than the original. The default is true. */
returnOriginal?: boolean;
/** Determines which document the operation modifies if the query selects multiple documents. */
sort?: Sort;
/** Upsert the document if it does not exist. */
upsert?: boolean;
}

/** @public */
export interface FindOneAndUpdateOptions extends CommandOperationOptions {
/** Optional list of array filters referenced in filtered positional operators */
arrayFilters?: Document[];
/** Allow driver to bypass schema validation in MongoDB 3.2 or higher. */
bypassDocumentValidation?: boolean;
/** An optional hint for query optimization. See the {@link https://docs.mongodb.com/manual/reference/command/update/#update-command-hint|update command} reference for more information.*/
hint?: Document;
/** Limits the fields to return for all matching documents. */
projection?: Document;
/** When false, returns the updated document rather than the original. The default is true. */
returnOriginal?: boolean;
/** Determines which document the operation modifies if the query selects multiple documents. */
sort?: Sort;
/** Upsert the document if it does not exist. */
upsert?: boolean;
}

// TODO: NODE-1812 to deprecate returnOriginal for returnDocument

/** @internal */
interface FindAndModifyCmdBase {
remove: boolean;
new: boolean;
upsert: boolean;
update?: Document;
sort?: SortForCmd;
fields?: Document;
bypassDocumentValidation?: boolean;
arrayFilters?: Document[];
maxTimeMS?: number;
writeConcern?: WriteConcern | WriteConcernSettings;
}

// NOTE: These types are a misuse of options, can we think of a way to remove them?
update?: boolean;
remove?: boolean;
new?: boolean;
function configureFindAndModifyCmdBaseUpdateOpts(
cmdBase: FindAndModifyCmdBase,
options: FindOneAndReplaceOptions | FindOneAndUpdateOptions
): FindAndModifyCmdBase {
cmdBase.new = options.returnOriginal === false;
cmdBase.upsert = options.upsert === true;

if (options.bypassDocumentValidation === true) {
cmdBase.bypassDocumentValidation = options.bypassDocumentValidation;
}
return cmdBase;
}

/** @internal */
export class FindAndModifyOperation extends CommandOperation<Document> {
options: FindAndModifyOptions;
class FindAndModifyOperation extends CommandOperation<Document> {
options: FindOneAndReplaceOptions | FindOneAndUpdateOptions | FindOneAndDeleteOptions;
cmdBase: FindAndModifyCmdBase;
collection: Collection;
query: Document;
sort?: Sort;
doc?: Document;

constructor(
collection: Collection,
query: Document,
sort: Sort | undefined,
doc: Document | undefined,
options?: FindAndModifyOptions
options: FindOneAndReplaceOptions | FindOneAndUpdateOptions | FindOneAndDeleteOptions
) {
super(collection, options);
this.options = options ?? {};
this.cmdBase = {
remove: false,
new: false,
upsert: false
};

const sort = formatSort(options.sort);
if (sort) {
this.cmdBase.sort = sort;
}

if (options.projection) {
this.cmdBase.fields = options.projection;
}

if (options.maxTimeMS) {
this.cmdBase.maxTimeMS = options.maxTimeMS;
}

// Decorate the findAndModify command with the write Concern
if (options.writeConcern) {
this.cmdBase.writeConcern = options.writeConcern;
}

// force primary read preference
this.readPreference = ReadPreference.primary;

this.collection = collection;
this.query = query;
this.sort = sort;
this.doc = doc;
}

execute(server: Server, session: ClientSession, callback: Callback<Document>): void {
const coll = this.collection;
const query = this.query;
const sort = formatSort(this.sort);
const doc = this.doc;
let options = { ...this.options, ...this.bsonOptions };
const options = { ...this.options, ...this.bsonOptions };

// Create findAndModify command object
const cmd: Document = {
findAndModify: coll.collectionName,
query: query
query: query,
...this.cmdBase
};

if (sort) {
cmd.sort = sort;
}

cmd.new = options.new ? true : false;
cmd.remove = options.remove ? true : false;
cmd.upsert = options.upsert ? true : false;

const projection = options.projection || options.fields;

if (projection) {
cmd.fields = projection;
}

if (options.arrayFilters) {
cmd.arrayFilters = options.arrayFilters;
}

if (doc && !options.remove) {
cmd.update = doc;
}

if (options.maxTimeMS) {
cmd.maxTimeMS = options.maxTimeMS;
}

// No check on the documents
options.checkKeys = false;

// Final options for retryable writes
options = applyRetryableWrites(options, coll.s.db);

// Decorate the findAndModify command with the write Concern
if (options.writeConcern) {
cmd.writeConcern = options.writeConcern;
}

// Have we specified bypassDocumentValidation
if (options.bypassDocumentValidation === true) {
cmd.bypassDocumentValidation = options.bypassDocumentValidation;
}

// Have we specified collation
try {
decorateWithCollation(cmd, coll, options);
Expand Down Expand Up @@ -159,18 +178,14 @@ export class FindAndModifyOperation extends CommandOperation<Document> {

/** @internal */
export class FindOneAndDeleteOperation extends FindAndModifyOperation {
constructor(collection: Collection, filter: Document, options: FindAndModifyOptions) {
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.remove = true;

constructor(collection: Collection, filter: Document, options: FindOneAndDeleteOptions) {
// Basic validation
if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}

super(collection, filter, finalOptions.sort, undefined, finalOptions);
super(collection, filter, options);
this.cmdBase.remove = true;
}
}

Expand All @@ -180,15 +195,8 @@ export class FindOneAndReplaceOperation extends FindAndModifyOperation {
collection: Collection,
filter: Document,
replacement: Document,
options: FindAndModifyOptions
options: FindOneAndReplaceOptions
) {
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.update = true;
finalOptions.new = options.returnOriginal !== void 0 ? !options.returnOriginal : false;
finalOptions.upsert = options.upsert !== void 0 ? !!options.upsert : false;

if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
Expand All @@ -201,7 +209,9 @@ export class FindOneAndReplaceOperation extends FindAndModifyOperation {
throw new TypeError('Replacement document must not contain atomic operators');
}

super(collection, filter, finalOptions.sort, replacement, finalOptions);
super(collection, filter, options);
this.cmdBase.update = replacement;
configureFindAndModifyCmdBaseUpdateOpts(this.cmdBase, options);
}
}

Expand All @@ -211,16 +221,8 @@ export class FindOneAndUpdateOperation extends FindAndModifyOperation {
collection: Collection,
filter: Document,
update: Document,
options: FindAndModifyOptions
options: FindOneAndUpdateOptions
) {
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.update = true;
finalOptions.new =
typeof options.returnOriginal === 'boolean' ? !options.returnOriginal : false;
finalOptions.upsert = typeof options.upsert === 'boolean' ? options.upsert : false;

if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
Expand All @@ -233,7 +235,13 @@ export class FindOneAndUpdateOperation extends FindAndModifyOperation {
throw new TypeError('Update document requires atomic operators');
}

super(collection, filter, finalOptions.sort, update, finalOptions);
super(collection, filter, options);
this.cmdBase.update = update;
configureFindAndModifyCmdBaseUpdateOpts(this.cmdBase, options);

if (options.arrayFilters) {
this.cmdBase.arrayFilters = options.arrayFilters;
}
}
}

Expand Down
Loading