Skip to content
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

refactor!: format sort in cursor and in sort builder #2573

Merged
merged 11 commits into from
Oct 19, 2020
Merged
3 changes: 2 additions & 1 deletion src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
EstimatedDocumentCountOperation,
EstimatedDocumentCountOptions
} from './operations/estimated_document_count';
import { FindOperation, FindOptions, Sort } from './operations/find';
import { FindOperation, FindOptions } from './operations/find';
import { FindOneOperation } from './operations/find_one';
import {
FindAndModifyOperation,
Expand Down Expand Up @@ -86,6 +86,7 @@ import type { PkFactory } from './mongo_client';
import type { Topology } from './sdam/topology';
import type { Logger, LoggerOptions } from './logger';
import type { OperationParent } from './operations/command';
import type { Sort } from './sort';

/** @public */
export interface Collection {
Expand Down
2 changes: 1 addition & 1 deletion src/cursor/aggregation_cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Cursor, CursorOptions } from './cursor';
import { CursorState } from './core_cursor';
import type { AggregateOperation, AggregateOptions } from '../operations/aggregate';
import type { Document } from '../bson';
import type { Sort } from '../operations/find';
import type { Sort } from '../sort';
import type { Topology } from '../sdam/topology';

/** @public */
Expand Down
49 changes: 8 additions & 41 deletions src/cursor/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ import {
CursorCloseOptions,
DocumentTransforms
} from './core_cursor';
import { maybePromise, formattedOrderClause, Callback } from '../utils';
import { maybePromise, Callback } from '../utils';
import { executeOperation } from '../operations/execute_operation';
import { each, EachCallback } from '../operations/cursor_ops';
import { CountOperation, CountOptions } from '../operations/count';
import type { Logger } from '../logger';
import type { Topology } from '../sdam/topology';
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
import type { Sort, SortDirection } from '../operations/find';
import type { Hint, OperationBase } from '../operations/operation';
import type { Document } from '../bson';
import { Sort, SortDirection, formatSort } from '../sort';

/** @public Flags allowed for cursor */
export const FLAGS = [
Expand Down Expand Up @@ -52,6 +52,7 @@ export interface CursorOptions extends CoreCursorOptions {
topology?: Topology;
/** Session to use for the operation */
numberOfRetries?: number;
sort?: Sort;
}

const kCursor = Symbol('cursor');
Expand Down Expand Up @@ -237,6 +238,10 @@ export class Cursor<
this.addCursorFlag('noCursorTimeout', true);
}

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

// Set the batch size
this.cursorBatchSize = batchSize;
}
Expand Down Expand Up @@ -303,14 +308,6 @@ export class Cursor<
return;
}

if (this.s.state === CursorState.INIT && this.cmd.sort) {
try {
this.cmd.sort = formattedOrderClause(this.cmd.sort);
} catch (err) {
return cb(err);
}
}

this._next((err, doc) => {
if (err) return cb(err);
this.s.state = CursorState.OPEN;
Expand Down Expand Up @@ -555,37 +552,7 @@ export class Cursor<
throw new MongoError('Cursor is closed');
}

let order = sort;

// We have an array of arrays, we need to preserve the order of the sort
// so we will us a Map
if (Array.isArray(order) && Array.isArray(order[0])) {
this.cmd.sort = new Map<string, unknown>(
(order as [string, SortDirection][]).map(([key, dir]) => {
if (dir === 'asc') {
return [key, 1];
} else if (dir === 'desc') {
return [key, -1];
} else if (dir === 1 || dir === -1 || dir.$meta) {
return [key, dir];
} else {
throw new MongoError(
"Illegal sort clause, must be of the form [['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
);
}

return [key, null];
})
);

return this;
}

if (direction != null) {
order = [[sort as string, direction]];
}

this.cmd.sort = order;
this.cmd.sort = formatSort(sort, direction);
return this;
}

Expand Down
3 changes: 2 additions & 1 deletion src/gridfs-stream/download.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Readable } from 'stream';
import type { AnyError } from '../error';
import type { Document } from '../bson';
import type { FindOptions, Sort } from '../operations/find';
import type { FindOptions } from '../operations/find';
import type { Sort } from '../sort';
import type { Cursor } from './../cursor/cursor';
import type { Callback } from '../utils';
import type { Collection } from '../collection';
Expand Down
3 changes: 2 additions & 1 deletion src/gridfs-stream/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import type { Db } from '../db';
import type { ReadPreference } from '../read_preference';
import type { Collection } from '../collection';
import type { Cursor } from './../cursor/cursor';
import type { FindOptions, Sort } from './../operations/find';
import type { FindOptions } from './../operations/find';
import type { Sort } from '../sort';
import type { Logger } from '../logger';

const DEFAULT_GRIDFS_BUCKET_OPTIONS: {
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ export type { DistinctOptions } from './operations/distinct';
export type { DropCollectionOptions, DropDatabaseOptions } from './operations/drop';
export type { EstimatedDocumentCountOptions } from './operations/estimated_document_count';
export type { EvalOptions } from './operations/eval';
export type { FindOptions, Sort, SortDirection } from './operations/find';
export type { FindOptions } from './operations/find';
export type { Sort, SortDirection } from './sort';
export type { FindAndModifyOptions } from './operations/find_and_modify';
export type {
IndexSpecification,
Expand Down
31 changes: 1 addition & 30 deletions src/operations/common_functions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { MongoError } from '../error';
import { CursorState, Cursor } from '../cursor';
import {
applyRetryableWrites,
applyWriteConcern,
decorateWithCollation,
formattedOrderClause,
Callback
} from '../utils';
import { applyRetryableWrites, applyWriteConcern, decorateWithCollation, Callback } from '../utils';
import type { Document } from '../bson';
import type { Db } from '../db';
import type { ClientSession } from '../sessions';
Expand Down Expand Up @@ -106,28 +99,6 @@ export function prepareDocs(
});
}

// Get the next available document from the cursor, returns null if no more documents are available.
export function nextObject(cursor: Cursor, callback: Callback): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead code?

if (cursor.s.state === CursorState.CLOSED || (cursor.isDead && cursor.isDead())) {
return callback(MongoError.create({ message: 'Cursor is closed', driver: true }));
}

if (cursor.s.state === CursorState.INIT && cursor.cmd && cursor.cmd.sort) {
try {
cursor.cmd.sort = formattedOrderClause(cursor.cmd.sort);
} catch (err) {
return callback(err);
}
}

// Get the next object
cursor._next((err, doc) => {
cursor.s.state = CursorState.OPEN;
if (err) return callback(err);
callback(undefined, doc);
});
}

export function removeDocuments(server: Server, coll: Collection, callback?: Callback): void;
export function removeDocuments(
server: Server,
Expand Down
21 changes: 4 additions & 17 deletions src/operations/find.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import { Aspect, defineAspects, Hint } from './operation';
import { ReadPreference } from '../read_preference';
import {
maxWireVersion,
MongoDBNamespace,
Callback,
formattedOrderClause,
normalizeHintField
} from '../utils';
import { maxWireVersion, MongoDBNamespace, Callback, normalizeHintField } from '../utils';
import { MongoError } from '../error';
import type { Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
import type { QueryOptions } from '../cmap/wire_protocol/query';
import { CommandOperation, CommandOperationOptions } from './command';

/** @public */
export type SortDirection = 1 | -1 | 'asc' | 'desc' | { $meta: string };
/** @public */
export type Sort =
| { [key: string]: SortDirection }
| [string, SortDirection][]
| [string, SortDirection];
import { Sort, formatSort } from '../sort';

/** @public */
export interface FindOptions extends QueryOptions, CommandOperationOptions {
Expand Down Expand Up @@ -138,9 +125,9 @@ export class FindOperation extends CommandOperation<FindOptions, Document> {
const findCommand: Document = Object.assign({}, this.cmd);

if (options.sort) {
findCommand.sort = formattedOrderClause(options.sort);
findCommand.sort = formatSort(options.sort);
}

if (options.projection) {
let projection = options.projection;
if (projection && !Buffer.isBuffer(projection) && Array.isArray(projection)) {
Expand Down
5 changes: 2 additions & 3 deletions src/operations/find_and_modify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
applyRetryableWrites,
decorateWithCollation,
applyWriteConcern,
formattedOrderClause,
hasAtomicOperators,
Callback
} from '../utils';
Expand All @@ -14,7 +13,7 @@ import { defineAspects, Aspect } from './operation';
import type { Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import type { Sort } from './find';
import { Sort, formatSort } from '../sort';

/** @public */
export interface FindAndModifyOptions extends CommandOperationOptions {
Expand Down Expand Up @@ -69,7 +68,7 @@ export class FindAndModifyOperation extends CommandOperation<FindAndModifyOption
execute(server: Server, callback: Callback<Document>): void {
const coll = this.collection;
const query = this.query;
const sort = formattedOrderClause(this.sort);
const sort = formatSort(this.sort);
const doc = this.doc;
let options = this.options;

Expand Down
2 changes: 1 addition & 1 deletion src/operations/map_reduce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ReadPreference, ReadPreferenceMode } from '../read_preference';
import { CommandOperation, CommandOperationOptions } from './command';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import type { Sort } from './find';
import type { Sort } from '../sort';
import { MongoError } from '../error';
import type { ObjectId } from '../bson';

Expand Down
110 changes: 110 additions & 0 deletions src/sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/** @public */
export type SortDirection =
| 1
| -1
| 'asc'
| 'desc'
| 'ascending'
| 'descending'
| { $meta: string };

/** @public */
export type Sort =
| string
| string[]
| { [key: string]: SortDirection }
| [string, SortDirection][]
| [string, SortDirection];

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

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

/** @internal */
type SortForCmd = { [key: string]: SortDirectionForCmd };

/** @internal */
function prepareDirection(direction: any = 1): SortDirectionForCmd {
const value = ('' + direction).toLowerCase();
if (isMeta(direction)) return direction;
switch (value) {
case 'ascending':
case 'asc':
case '1':
return 1;
case 'descending':
case 'desc':
case '-1':
return -1;
default:
throw new Error(`Invalid sort direction: ${JSON.stringify(direction)}`);
}
}

/** @internal */
function isMeta(t: SortDirection): t is { $meta: string } {
return typeof t === 'object' && t !== null && '$meta' in t && typeof t.$meta === 'string';
}

/** @internal */
function isPair(t: Sort): t is [string, SortDirection] {
if (Array.isArray(t) && t.length === 2) {
try {
prepareDirection(t[1]);
return true;
} catch (e) {
return false;
}
}
return false;
}

/** @internal */
function pairToObject(v: [string, SortDirection]): SortForCmd {
return { [v[0]]: prepareDirection(v[1]) };
}

/** @internal */
function isDeep(t: Sort): t is [string, SortDirection][] {
return Array.isArray(t) && Array.isArray(t[0]);
}

/** @internal */
function deepToObject(t: [string, SortDirection][]): SortForCmd {
const sortObject: SortForCmd = {};
for (const [name, value] of t) {
sortObject[name] = prepareDirection(value);
}
return sortObject;
}

/** @internal */
function stringsToObject(t: string[]): SortForCmd {
return t.reduce((acq, key) => {
return { ...acq, [key]: 1 };
}, {});
}

/** @internal */
function validate(t: { [key: string]: SortDirection }): SortForCmd {
return Object.keys(t).reduce((acq, key) => {
return { ...acq, [key]: prepareDirection(t[key]) };
}, {});
}
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

/** converts a Sort type into a type that is valid for the server (SortForCmd) */
export function formatSort(
sort: Sort | undefined,
direction?: SortDirection
): SortForCmd | undefined {
if (sort == null) return undefined;
if (Array.isArray(sort) && !sort.length) return undefined;
if (typeof sort === 'object' && !Object.keys(sort).length) return undefined;
if (typeof sort === 'string') return { [sort]: prepareDirection(direction) };
if (isPair(sort)) return pairToObject(sort);
if (isDeep(sort)) return deepToObject(sort);
if (Array.isArray(sort)) return stringsToObject(sort);
if (typeof sort === 'object') return validate(sort);
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(`Invalid sort format: ${JSON.stringify(sort)}`);
}