Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/bulk/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { makeUpdateStatement, UpdateOperation, type UpdateStatement } from '../o
import type { Server } from '../sdam/server';
import type { Topology } from '../sdam/topology';
import type { ClientSession } from '../sessions';
import { type Sort } from '../sort';
import { type TimeoutContext } from '../timeout';
import {
applyRetryableWrites,
Expand Down Expand Up @@ -78,6 +79,8 @@ export interface ReplaceOneModel<TSchema extends Document = Document> {
hint?: Hint;
/** When true, creates a new document if no document matches the query. */
upsert?: boolean;
/** if the filter matches more than one document the first one matched by the sort order will be updated */
sort?: Sort;
}

/** @public */
Expand All @@ -98,6 +101,8 @@ export interface UpdateOneModel<TSchema extends Document = Document> {
hint?: Hint;
/** When true, creates a new document if no document matches the query. */
upsert?: boolean;
/** if the filter matches more than one document the first one matched by the sort order will be updated */
sort?: Sort;
}

/** @public */
Expand Down
3 changes: 2 additions & 1 deletion src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import {
} from './operations/update';
import { ReadConcern, type ReadConcernLike } from './read_concern';
import { ReadPreference, type ReadPreferenceLike } from './read_preference';
import { type Sort } from './sort';
import {
DEFAULT_PK_FACTORY,
MongoDBCollectionNamespace,
Expand Down Expand Up @@ -365,7 +366,7 @@ export class Collection<TSchema extends Document = Document> {
async updateOne(
filter: Filter<TSchema>,
update: UpdateFilter<TSchema> | Document[],
options?: UpdateOptions
options?: UpdateOptions & { sort?: Sort }
): Promise<UpdateResult<TSchema>> {
return await executeOperation(
this.client,
Expand Down
9 changes: 9 additions & 0 deletions src/operations/client_bulk_write/command_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DocumentSequence } from '../../cmap/commands';
import { MongoAPIError, MongoInvalidArgumentError } from '../../error';
import { type PkFactory } from '../../mongo_client';
import type { Filter, OptionalId, UpdateFilter, WithoutId } from '../../mongo_types';
import { formatSort, type SortForCmd } from '../../sort';
import { DEFAULT_PK_FACTORY, hasAtomicOperators } from '../../utils';
import { type CollationOptions } from '../command';
import { type Hint } from '../operation';
Expand Down Expand Up @@ -327,6 +328,7 @@ export interface ClientUpdateOperation {
upsert?: boolean;
arrayFilters?: Document[];
collation?: CollationOptions;
sort?: SortForCmd;
}

/**
Expand Down Expand Up @@ -398,6 +400,9 @@ function createUpdateOperation(
if (model.collation) {
document.collation = model.collation;
}
if (!multi && 'sort' in model && model.sort != null) {
document.sort = formatSort(model.sort);
}
return document;
}

Expand All @@ -410,6 +415,7 @@ export interface ClientReplaceOneOperation {
hint?: Hint;
upsert?: boolean;
collation?: CollationOptions;
sort?: SortForCmd;
}

/**
Expand Down Expand Up @@ -443,6 +449,9 @@ export const buildReplaceOneOperation = (
if (model.collation) {
document.collation = model.collation;
}
if (model.sort != null) {
document.sort = formatSort(model.sort);
}
return document;
};

Expand Down
5 changes: 5 additions & 0 deletions src/operations/client_bulk_write/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type Document } from '../../bson';
import type { Filter, OptionalId, UpdateFilter, WithoutId } from '../../mongo_types';
import type { CollationOptions, CommandOperationOptions } from '../../operations/command';
import type { Hint } from '../../operations/operation';
import { type Sort } from '../../sort';

/** @public */
export interface ClientBulkWriteOptions extends CommandOperationOptions {
Expand Down Expand Up @@ -89,6 +90,8 @@ export interface ClientReplaceOneModel<TSchema> extends ClientWriteModel {
hint?: Hint;
/** When true, creates a new document if no document matches the query. */
upsert?: boolean;
/** if the filter matches more than one document the first one matched by the sort order will be updated */
sort?: Sort;
}

/** @public */
Expand All @@ -113,6 +116,8 @@ export interface ClientUpdateOneModel<TSchema> extends ClientWriteModel {
hint?: Hint;
/** When true, creates a new document if no document matches the query. */
upsert?: boolean;
/** if the filter matches more than one document the first one matched by the sort order will be updated */
sort?: Sort;
}

/** @public */
Expand Down
11 changes: 10 additions & 1 deletion src/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MongoCompatibilityError, MongoInvalidArgumentError, MongoServerError }
import type { InferIdType, TODO_NODE_3286 } from '../mongo_types';
import type { Server } from '../sdam/server';
import type { ClientSession } from '../sessions';
import { formatSort, type Sort, type SortForCmd } from '../sort';
import { type TimeoutContext } from '../timeout';
import { hasAtomicOperators, type MongoDBNamespace } from '../utils';
import { type CollationOptions, CommandOperation, type CommandOperationOptions } from './command';
Expand Down Expand Up @@ -58,6 +59,8 @@ export interface UpdateStatement {
arrayFilters?: Document[];
/** A document or string that specifies the index to use to support the query predicate. */
hint?: Hint;
/** if the filter matches more than one document the first one matched by the sort order will be updated */
sort?: SortForCmd;
}

/**
Expand Down Expand Up @@ -214,6 +217,8 @@ export interface ReplaceOptions extends CommandOperationOptions {
upsert?: boolean;
/** Map of parameter names and values that can be accessed using $$var (requires MongoDB 5.0). */
let?: Document;
/** if the filter matches more than one document the first one matched by the sort order will be updated */
sort?: Sort;
}

/** @internal */
Expand Down Expand Up @@ -259,7 +264,7 @@ export class ReplaceOneOperation extends UpdateOperation {
export function makeUpdateStatement(
filter: Document,
update: Document | Document[],
options: UpdateOptions & { multi?: boolean }
options: UpdateOptions & { multi?: boolean } & { sort?: Sort }
): UpdateStatement {
if (filter == null || typeof filter !== 'object') {
throw new MongoInvalidArgumentError('Selector must be a valid JavaScript object');
Expand Down Expand Up @@ -290,6 +295,10 @@ export function makeUpdateStatement(
op.collation = options.collation;
}

if (!options.multi && options.sort != null) {
op.sort = formatSort(options.sort);
}

return op;
}

Expand Down
55 changes: 32 additions & 23 deletions src/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,24 @@ export type SortDirection =
| 'desc'
| 'ascending'
| 'descending'
| { $meta: string };
| { readonly $meta: string };

/** @public */
export type Sort =
| string
| Exclude<SortDirection, { $meta: string }>
| string[]
| { [key: string]: SortDirection }
| Map<string, SortDirection>
| [string, SortDirection][]
| [string, SortDirection];
| Exclude<SortDirection, { readonly $meta: string }>
| ReadonlyArray<string>
| { readonly [key: string]: SortDirection }
| ReadonlyMap<string, SortDirection>
| ReadonlyArray<readonly [string, SortDirection]>
| readonly [string, SortDirection];

/** Below stricter types were created for sort that correspond with type that the cmd takes */

/** @internal */
/** @public */
export type SortDirectionForCmd = 1 | -1 | { $meta: string };

/** @internal */
/** @public */
export type SortForCmd = Map<string, SortDirectionForCmd>;

/** @internal */
Expand Down Expand Up @@ -55,7 +55,7 @@ function isMeta(t: SortDirection): t is { $meta: string } {
}

/** @internal */
function isPair(t: Sort): t is [string, SortDirection] {
function isPair(t: Sort): t is readonly [string, SortDirection] {
if (Array.isArray(t) && t.length === 2) {
try {
prepareDirection(t[1]);
Expand All @@ -67,33 +67,37 @@ function isPair(t: Sort): t is [string, SortDirection] {
return false;
}

function isDeep(t: Sort): t is [string, SortDirection][] {
function isDeep(t: Sort): t is ReadonlyArray<readonly [string, SortDirection]> {
return Array.isArray(t) && Array.isArray(t[0]);
}

function isMap(t: Sort): t is Map<string, SortDirection> {
function isMap(t: Sort): t is ReadonlyMap<string, SortDirection> {
return t instanceof Map && t.size > 0;
}

function isReadonlyArray<T>(value: any): value is readonly T[] {
return Array.isArray(value);
}

/** @internal */
function pairToMap(v: [string, SortDirection]): SortForCmd {
function pairToMap(v: readonly [string, SortDirection]): SortForCmd {
return new Map([[`${v[0]}`, prepareDirection([v[1]])]]);
}

/** @internal */
function deepToMap(t: [string, SortDirection][]): SortForCmd {
function deepToMap(t: ReadonlyArray<readonly [string, SortDirection]>): SortForCmd {
const sortEntries: SortPairForCmd[] = t.map(([k, v]) => [`${k}`, prepareDirection(v)]);
return new Map(sortEntries);
}

/** @internal */
function stringsToMap(t: string[]): SortForCmd {
function stringsToMap(t: ReadonlyArray<string>): SortForCmd {
const sortEntries: SortPairForCmd[] = t.map(key => [`${key}`, 1]);
return new Map(sortEntries);
}

/** @internal */
function objectToMap(t: { [key: string]: SortDirection }): SortForCmd {
function objectToMap(t: { readonly [key: string]: SortDirection }): SortForCmd {
const sortEntries: SortPairForCmd[] = Object.entries(t).map(([k, v]) => [
`${k}`,
prepareDirection(v)
Expand All @@ -102,7 +106,7 @@ function objectToMap(t: { [key: string]: SortDirection }): SortForCmd {
}

/** @internal */
function mapToMap(t: Map<string, SortDirection>): SortForCmd {
function mapToMap(t: ReadonlyMap<string, SortDirection>): SortForCmd {
const sortEntries: SortPairForCmd[] = Array.from(t).map(([k, v]) => [
`${k}`,
prepareDirection(v)
Expand All @@ -116,17 +120,22 @@ export function formatSort(
direction?: SortDirection
): SortForCmd | undefined {
if (sort == null) return undefined;
if (typeof sort === 'string') return new Map([[sort, prepareDirection(direction)]]);

if (typeof sort === 'string') return new Map([[sort, prepareDirection(direction)]]); // 'fieldName'

if (typeof sort !== 'object') {
throw new MongoInvalidArgumentError(
`Invalid sort format: ${JSON.stringify(sort)} Sort must be a valid object`
);
}
if (!Array.isArray(sort)) {
return isMap(sort) ? mapToMap(sort) : Object.keys(sort).length ? objectToMap(sort) : undefined;

if (!isReadonlyArray(sort)) {
if (isMap(sort)) return mapToMap(sort); // Map<fieldName, SortDirection>
if (Object.keys(sort).length) return objectToMap(sort); // { [fieldName: string]: SortDirection }
return undefined;
}
if (!sort.length) return undefined;
if (isDeep(sort)) return deepToMap(sort);
if (isPair(sort)) return pairToMap(sort);
return stringsToMap(sort);
if (isDeep(sort)) return deepToMap(sort); // [ [fieldName, sortDir], [fieldName, sortDir] ... ]
if (isPair(sort)) return pairToMap(sort); // [ fieldName, sortDir ]
return stringsToMap(sort); // [ fieldName, fieldName ]
}
Loading