Skip to content

Commit 8aad134

Browse files
author
Thomas Reggi
authored
refactor!: format sort in cursor and in sort builder (#2573)
* force from master * multi-key test * fix lint * get rid of class / static * neals simpler deep to object * removed all reducers * linter issue * reduce document import
1 parent 2704ce8 commit 8aad134

File tree

12 files changed

+251
-177
lines changed

12 files changed

+251
-177
lines changed

src/collection.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
EstimatedDocumentCountOperation,
4242
EstimatedDocumentCountOptions
4343
} from './operations/estimated_document_count';
44-
import { FindOperation, FindOptions, Sort } from './operations/find';
44+
import { FindOperation, FindOptions } from './operations/find';
4545
import { FindOneOperation } from './operations/find_one';
4646
import {
4747
FindAndModifyOperation,
@@ -86,6 +86,7 @@ import type { PkFactory } from './mongo_client';
8686
import type { Topology } from './sdam/topology';
8787
import type { Logger, LoggerOptions } from './logger';
8888
import type { OperationParent } from './operations/command';
89+
import type { Sort } from './sort';
8990

9091
/** @public */
9192
export interface Collection {

src/cursor/aggregation_cursor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { MongoError } from '../error';
22
import { Cursor, CursorOptions, CursorState } from './cursor';
33
import type { AggregateOperation, AggregateOptions } from '../operations/aggregate';
44
import type { Document } from '../bson';
5-
import type { Sort } from '../operations/find';
5+
import type { Sort } from '../sort';
66
import type { Topology } from '../sdam/topology';
77

88
/** @public */

src/cursor/cursor.ts

+9-49
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,14 @@ import { Logger } from '../logger';
77
import { executeOperation } from '../operations/execute_operation';
88
import { CountOperation, CountOptions } from '../operations/count';
99
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
10-
import {
11-
Callback,
12-
emitDeprecatedOptionWarning,
13-
formattedOrderClause,
14-
maybePromise,
15-
MongoDBNamespace
16-
} from '../utils';
17-
10+
import { Callback, emitDeprecatedOptionWarning, maybePromise, MongoDBNamespace } from '../utils';
11+
import { Sort, SortDirection, formatSort } from '../sort';
1812
import type { OperationTime, ResumeToken } from '../change_stream';
1913
import type { CloseOptions } from '../cmap/connection_pool';
2014
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
15+
import type { Hint, OperationBase } from '../operations/operation';
2116
import type { Topology } from '../sdam/topology';
2217
import type { CommandOperationOptions } from '../operations/command';
23-
import type { Sort, SortDirection } from '../operations/find';
24-
import type { Hint, OperationBase } from '../operations/operation';
2518
import type { ReadConcern } from '../read_concern';
2619
import type { Server } from '../sdam/server';
2720
import type { ClientSession } from '../sessions';
@@ -99,6 +92,7 @@ export interface CursorOptions extends CommandOperationOptions {
9992
topology?: Topology;
10093
/** Session to use for the operation */
10194
numberOfRetries?: number;
95+
sort?: Sort;
10296
}
10397

10498
/** @public */
@@ -399,6 +393,10 @@ export class Cursor<
399393
this.addCursorFlag('noCursorTimeout', true);
400394
}
401395

396+
if (this.options.sort) {
397+
this.cmd.sort = formatSort(this.options.sort);
398+
}
399+
402400
// Set the batch size
403401
this._batchSize = batchSize;
404402
}
@@ -688,14 +686,6 @@ export class Cursor<
688686
return;
689687
}
690688

691-
if (this.s.state === CursorState.INIT && this.cmd.sort) {
692-
try {
693-
this.cmd.sort = formattedOrderClause(this.cmd.sort);
694-
} catch (err) {
695-
return cb(err);
696-
}
697-
}
698-
699689
nextFunction(this, (err, doc) => {
700690
if (err) return cb(err);
701691
this.s.state = CursorState.OPEN;
@@ -940,37 +930,7 @@ export class Cursor<
940930
throw new MongoError('Cursor is closed');
941931
}
942932

943-
let order = sort;
944-
945-
// We have an array of arrays, we need to preserve the order of the sort
946-
// so we will us a Map
947-
if (Array.isArray(order) && Array.isArray(order[0])) {
948-
this.cmd.sort = new Map<string, unknown>(
949-
(order as [string, SortDirection][]).map(([key, dir]) => {
950-
if (dir === 'asc') {
951-
return [key, 1];
952-
} else if (dir === 'desc') {
953-
return [key, -1];
954-
} else if (dir === 1 || dir === -1 || dir.$meta) {
955-
return [key, dir];
956-
} else {
957-
throw new MongoError(
958-
"Illegal sort clause, must be of the form [['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
959-
);
960-
}
961-
962-
return [key, null];
963-
})
964-
);
965-
966-
return this;
967-
}
968-
969-
if (direction != null) {
970-
order = [[sort as string, direction]];
971-
}
972-
973-
this.cmd.sort = order;
933+
this.cmd.sort = formatSort(sort, direction);
974934
return this;
975935
}
976936

src/gridfs-stream/download.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Readable } from 'stream';
22
import type { AnyError } from '../error';
33
import type { Document } from '../bson';
4-
import type { FindOptions, Sort } from '../operations/find';
4+
import type { FindOptions } from '../operations/find';
5+
import type { Sort } from '../sort';
56
import type { Cursor } from './../cursor/cursor';
67
import type { Callback } from '../utils';
78
import type { Collection } from '../collection';

src/gridfs-stream/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import type { Db } from '../db';
1313
import type { ReadPreference } from '../read_preference';
1414
import type { Collection } from '../collection';
1515
import type { Cursor } from './../cursor/cursor';
16-
import type { FindOptions, Sort } from './../operations/find';
16+
import type { FindOptions } from './../operations/find';
17+
import type { Sort } from '../sort';
1718
import type { Logger } from '../logger';
1819

1920
const DEFAULT_GRIDFS_BUCKET_OPTIONS: {

src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ export type { DistinctOptions } from './operations/distinct';
204204
export type { DropCollectionOptions, DropDatabaseOptions } from './operations/drop';
205205
export type { EstimatedDocumentCountOptions } from './operations/estimated_document_count';
206206
export type { EvalOptions } from './operations/eval';
207-
export type { FindOptions, Sort, SortDirection } from './operations/find';
207+
export type { FindOptions } from './operations/find';
208+
export type { Sort, SortDirection } from './sort';
208209
export type { FindAndModifyOptions } from './operations/find_and_modify';
209210
export type {
210211
IndexSpecification,

src/operations/find.ts

+3-16
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,14 @@
11
import { Aspect, defineAspects, Hint } from './operation';
22
import { ReadPreference } from '../read_preference';
3-
import {
4-
maxWireVersion,
5-
MongoDBNamespace,
6-
Callback,
7-
formattedOrderClause,
8-
normalizeHintField
9-
} from '../utils';
3+
import { maxWireVersion, MongoDBNamespace, Callback, normalizeHintField } from '../utils';
104
import { MongoError } from '../error';
115
import type { Document } from '../bson';
126
import type { Server } from '../sdam/server';
137
import type { Collection } from '../collection';
148
import type { CollationOptions } from '../cmap/wire_protocol/write_command';
159
import type { QueryOptions } from '../cmap/wire_protocol/query';
1610
import { CommandOperation, CommandOperationOptions } from './command';
17-
18-
/** @public */
19-
export type SortDirection = 1 | -1 | 'asc' | 'desc' | { $meta: string };
20-
/** @public */
21-
export type Sort =
22-
| { [key: string]: SortDirection }
23-
| [string, SortDirection][]
24-
| [string, SortDirection];
11+
import { Sort, formatSort } from '../sort';
2512

2613
/** @public */
2714
export interface FindOptions extends QueryOptions, CommandOperationOptions {
@@ -138,7 +125,7 @@ export class FindOperation extends CommandOperation<FindOptions, Document> {
138125
const findCommand: Document = Object.assign({}, this.cmd);
139126

140127
if (options.sort) {
141-
findCommand.sort = formattedOrderClause(options.sort);
128+
findCommand.sort = formatSort(options.sort);
142129
}
143130

144131
if (options.projection) {

src/operations/find_and_modify.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
applyRetryableWrites,
55
decorateWithCollation,
66
applyWriteConcern,
7-
formattedOrderClause,
87
hasAtomicOperators,
98
Callback
109
} from '../utils';
@@ -14,7 +13,7 @@ import { defineAspects, Aspect } from './operation';
1413
import type { Document } from '../bson';
1514
import type { Server } from '../sdam/server';
1615
import type { Collection } from '../collection';
17-
import type { Sort } from './find';
16+
import { Sort, formatSort } from '../sort';
1817

1918
/** @public */
2019
export interface FindAndModifyOptions extends CommandOperationOptions {
@@ -69,7 +68,7 @@ export class FindAndModifyOperation extends CommandOperation<FindAndModifyOption
6968
execute(server: Server, callback: Callback<Document>): void {
7069
const coll = this.collection;
7170
const query = this.query;
72-
const sort = formattedOrderClause(this.sort);
71+
const sort = formatSort(this.sort);
7372
const doc = this.doc;
7473
let options = this.options;
7574

src/operations/map_reduce.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ReadPreference, ReadPreferenceMode } from '../read_preference';
1111
import { CommandOperation, CommandOperationOptions } from './command';
1212
import type { Server } from '../sdam/server';
1313
import type { Collection } from '../collection';
14-
import type { Sort } from './find';
14+
import type { Sort } from '../sort';
1515
import { MongoError } from '../error';
1616
import type { ObjectId } from '../bson';
1717

src/sort.ts

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/** @public */
2+
export type SortDirection =
3+
| 1
4+
| -1
5+
| 'asc'
6+
| 'desc'
7+
| 'ascending'
8+
| 'descending'
9+
| { $meta: string };
10+
11+
/** @public */
12+
export type Sort =
13+
| string
14+
| string[]
15+
| { [key: string]: SortDirection }
16+
| [string, SortDirection][]
17+
| [string, SortDirection];
18+
19+
/** Below stricter types were created for sort that correspond with type that the cmd takes */
20+
21+
/** @internal */
22+
type SortDirectionForCmd = 1 | -1 | { $meta: string };
23+
24+
/** @internal */
25+
type SortForCmd = { [key: string]: SortDirectionForCmd };
26+
27+
/** @internal */
28+
function prepareDirection(direction: any = 1): SortDirectionForCmd {
29+
const value = ('' + direction).toLowerCase();
30+
if (isMeta(direction)) return direction;
31+
switch (value) {
32+
case 'ascending':
33+
case 'asc':
34+
case '1':
35+
return 1;
36+
case 'descending':
37+
case 'desc':
38+
case '-1':
39+
return -1;
40+
default:
41+
throw new Error(`Invalid sort direction: ${JSON.stringify(direction)}`);
42+
}
43+
}
44+
45+
/** @internal */
46+
function isMeta(t: SortDirection): t is { $meta: string } {
47+
return typeof t === 'object' && t !== null && '$meta' in t && typeof t.$meta === 'string';
48+
}
49+
50+
/** @internal */
51+
function isPair(t: Sort): t is [string, SortDirection] {
52+
if (Array.isArray(t) && t.length === 2) {
53+
try {
54+
prepareDirection(t[1]);
55+
return true;
56+
} catch (e) {
57+
return false;
58+
}
59+
}
60+
return false;
61+
}
62+
63+
/** @internal */
64+
function pairToObject(v: [string, SortDirection]): SortForCmd {
65+
return { [v[0]]: prepareDirection(v[1]) };
66+
}
67+
68+
/** @internal */
69+
function isDeep(t: Sort): t is [string, SortDirection][] {
70+
return Array.isArray(t) && Array.isArray(t[0]);
71+
}
72+
73+
/** @internal */
74+
function deepToObject(t: [string, SortDirection][]): SortForCmd {
75+
const sortObject: SortForCmd = {};
76+
for (const [name, value] of t) {
77+
sortObject[name] = prepareDirection(value);
78+
}
79+
return sortObject;
80+
}
81+
82+
/** @internal */
83+
function stringsToObject(t: string[]): SortForCmd {
84+
const sortObject: SortForCmd = {};
85+
for (const key of t) {
86+
sortObject[key] = 1;
87+
}
88+
return sortObject;
89+
}
90+
91+
/** @internal */
92+
function objectToObject(t: { [key: string]: SortDirection }): SortForCmd {
93+
const sortObject: SortForCmd = {};
94+
for (const key in t) {
95+
sortObject[key] = prepareDirection(t[key]);
96+
}
97+
return sortObject;
98+
}
99+
100+
/** converts a Sort type into a type that is valid for the server (SortForCmd) */
101+
export function formatSort(
102+
sort: Sort | undefined,
103+
direction?: SortDirection
104+
): SortForCmd | undefined {
105+
if (sort == null) return undefined;
106+
if (Array.isArray(sort) && !sort.length) return undefined;
107+
if (typeof sort === 'object' && !Object.keys(sort).length) return undefined;
108+
if (typeof sort === 'string') return { [sort]: prepareDirection(direction) };
109+
if (isPair(sort)) return pairToObject(sort);
110+
if (isDeep(sort)) return deepToObject(sort);
111+
if (Array.isArray(sort)) return stringsToObject(sort);
112+
if (typeof sort === 'object') return objectToObject(sort);
113+
throw new Error(`Invalid sort format: ${JSON.stringify(sort)}`);
114+
}

0 commit comments

Comments
 (0)