All files / src/operations delete.ts

96.49% Statements 55/57
88.88% Branches 32/36
100% Functions 11/11
100% Lines 49/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185    147x             147x 147x                                                                     147x         30338x 30302x 30302x 30302x       30830x       22998x 717x     22625x               30566x 30566x 30566x           30566x 176x         30566x 448x     30566x 30566x 483x   64x       30502x           29066x       147x   3492x               3834x 2997x 2777x 2777x   2733x           147x   25230x               25150x 24643x 24159x 24159x   24077x             147x       30841x         30841x 312x     30841x 488x     30841x     147x 147x           147x          
import type { Document } from '../bson';
import type { Collection } from '../collection';
import { MongoCompatibilityError, MongoServerError } from '../error';
import { type TODO_NODE_3286 } from '../mongo_types';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { type TimeoutContext } from '../timeout';
import { type MongoDBNamespace } from '../utils';
import { type WriteConcernOptions } from '../write_concern';
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';
import { Aspect, defineAspects, type Hint } from './operation';
 
/** @public */
export interface DeleteOptions extends CommandOperationOptions, WriteConcernOptions {
  /** If true, when an insert fails, don't execute the remaining writes. If false, continue with remaining inserts when one fails. */
  ordered?: boolean;
  /** Specifies the collation to use for the operation */
  collation?: CollationOptions;
  /** Specify that the update query should only consider plans using the hinted index */
  hint?: string | Document;
  /** Map of parameter names and values that can be accessed using $$var (requires MongoDB 5.0). */
  let?: Document;
}
 
/** @public */
export interface DeleteResult {
  /** Indicates whether this write result was acknowledged. If not, then all other members of this result will be undefined. */
  acknowledged: boolean;
  /** The number of documents that were deleted */
  deletedCount: number;
}
 
/** @public */
export interface DeleteStatement {
  /** The query that matches documents to delete. */
  q: Document;
  /** The number of matching documents to delete. */
  limit: number;
  /** Specifies the collation to use for the operation. */
  collation?: CollationOptions;
  /** A document or string that specifies the index to use to support the query predicate. */
  hint?: Hint;
}
 
/** @internal */
export class DeleteOperation extends CommandOperation<DeleteResult> {
  override options: DeleteOptions;
  statements: DeleteStatement[];
 
  constructor(ns: MongoDBNamespace, statements: DeleteStatement[], options: DeleteOptions) {
    super(undefined, options);
    this.options = options;
    this.ns = ns;
    this.statements = statements;
  }
 
  override get commandName() {
    return 'delete' as const;
  }
 
  override get canRetryWrite(): boolean {
    if (super.canRetryWrite === false) {
      return false;
    }
 
    return this.statements.every(op => (op.limit != null ? op.limit > 0 : true));
  }
 
  override async execute(
    server: Server,
    session: ClientSession | undefined,
    timeoutContext: TimeoutContext
  ): Promise<DeleteResult> {
    const options = this.options ?? {};
    const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
    const command: Document = {
      delete: this.ns.collection,
      deletes: this.statements,
      ordered
    };
 
    if (options.let) {
      command.let = options.let;
    }
 
    // we check for undefined specifically here to allow falsy values
    // eslint-disable-next-line no-restricted-syntax
    if (options.comment !== undefined) {
      command.comment = options.comment;
    }
 
    const unacknowledgedWrite = this.writeConcern && this.writeConcern.w === 0;
    if (unacknowledgedWrite) {
      if (this.statements.find((o: Document) => o.hint)) {
        // TODO(NODE-3541): fix error for hint with unacknowledged writes
        throw new MongoCompatibilityError(`hint is not supported with unacknowledged writes`);
      }
    }
 
    const res: TODO_NODE_3286 = await super.executeCommand(
      server,
      session,
      command,
      timeoutContext
    );
    return res;
  }
}
 
export class DeleteOneOperation extends DeleteOperation {
  constructor(collection: Collection, filter: Document, options: DeleteOptions) {
    super(collection.s.namespace, [makeDeleteStatement(filter, { ...options, limit: 1 })], options);
  }
 
  override async execute(
    server: Server,
    session: ClientSession | undefined,
    timeoutContext: TimeoutContext
  ): Promise<DeleteResult> {
    const res: TODO_NODE_3286 = await super.execute(server, session, timeoutContext);
    if (this.explain) return res;
    Iif (res.code) throw new MongoServerError(res);
    if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]);
 
    return {
      acknowledged: this.writeConcern?.w !== 0,
      deletedCount: res.n
    };
  }
}
export class DeleteManyOperation extends DeleteOperation {
  constructor(collection: Collection, filter: Document, options: DeleteOptions) {
    super(collection.s.namespace, [makeDeleteStatement(filter, options)], options);
  }
 
  override async execute(
    server: Server,
    session: ClientSession | undefined,
    timeoutContext: TimeoutContext
  ): Promise<DeleteResult> {
    const res: TODO_NODE_3286 = await super.execute(server, session, timeoutContext);
    if (this.explain) return res;
    Iif (res.code) throw new MongoServerError(res);
    if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]);
 
    return {
      acknowledged: this.writeConcern?.w !== 0,
      deletedCount: res.n
    };
  }
}
 
export function makeDeleteStatement(
  filter: Document,
  options: DeleteOptions & { limit?: number }
): DeleteStatement {
  const op: DeleteStatement = {
    q: filter,
    limit: typeof options.limit === 'number' ? options.limit : 0
  };
 
  if (options.collation) {
    op.collation = options.collation;
  }
 
  if (options.hint) {
    op.hint = options.hint;
  }
 
  return op;
}
 
defineAspects(DeleteOperation, [Aspect.RETRYABLE, Aspect.WRITE_OPERATION]);
defineAspects(DeleteOneOperation, [
  Aspect.RETRYABLE,
  Aspect.WRITE_OPERATION,
  Aspect.EXPLAINABLE,
  Aspect.SKIP_COLLATION
]);
defineAspects(DeleteManyOperation, [
  Aspect.WRITE_OPERATION,
  Aspect.EXPLAINABLE,
  Aspect.SKIP_COLLATION
]);