Skip to content

Commit c902f14

Browse files
authored
fix(db-mongodb): add ability to disable fallback sort and no longer adds a fallback for unique fields (#12961)
You can now disable fallback sort in the mongodb adapter by passing `disableFallbackSort: true` in the options. We also no longer add fallback sort to sorts on unique fields by default now. This came out of a discussion in this issue #12690 and the linked PR #12888 Closes #12690
1 parent c66e5ca commit c902f14

File tree

4 files changed

+145
-2
lines changed

4 files changed

+145
-2
lines changed

docs/database/mongodb.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export default buildConfig({
4141
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
4242
| `allowAdditionalKeys` | By default, Payload strips all additional keys from MongoDB data that don't exist in the Payload schema. If you have some data that you want to include to the result but it doesn't exist in Payload, you can set this to `true`. Be careful as Payload access control _won't_ work for this data. |
4343
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
44+
| `disableFallbackSort` | Set to `true` to disable the adapter adding a fallback sort when sorting by non-unique fields, this can affect performance in some cases but it ensures a consistent order of results. |
4445

4546
## Access to Mongoose models
4647

packages/db-mongodb/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ export interface Args {
118118
*/
119119
useFacet?: boolean
120120
} & ConnectOptions
121+
/**
122+
* We add a secondary sort based on `createdAt` to ensure that results are always returned in the same order when sorting by a non-unique field.
123+
* This is because MongoDB does not guarantee the order of results, however in very large datasets this could affect performance.
124+
*
125+
* Set to `true` to disable this behaviour.
126+
*/
127+
disableFallbackSort?: boolean
121128
/** Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false */
122129
disableIndexHints?: boolean
123130
/**
@@ -131,6 +138,7 @@ export interface Args {
131138
*/
132139
mongoMemoryServer?: MongoMemoryReplSet
133140
prodMigrations?: Migration[]
141+
134142
transactionOptions?: false | TransactionOptions
135143

136144
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
@@ -198,6 +206,7 @@ export function mongooseAdapter({
198206
autoPluralization = true,
199207
collectionsSchemaOptions = {},
200208
connectOptions,
209+
disableFallbackSort = false,
201210
disableIndexHints = false,
202211
ensureIndexes = false,
203212
migrationDir: migrationDirArg,
@@ -251,6 +260,7 @@ export function mongooseAdapter({
251260
deleteOne,
252261
deleteVersions,
253262
destroy,
263+
disableFallbackSort,
254264
find,
255265
findGlobal,
256266
findGlobalVersions,
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import type { Config, SanitizedConfig } from 'payload'
2+
3+
import { sanitizeConfig } from 'payload'
4+
5+
import { buildSortParam } from './buildSortParam.js'
6+
import { MongooseAdapter } from '../index.js'
7+
8+
let config: SanitizedConfig
9+
10+
describe('builds sort params', () => {
11+
beforeAll(async () => {
12+
config = await sanitizeConfig({
13+
localization: {
14+
defaultLocale: 'en',
15+
fallback: true,
16+
locales: ['en', 'es'],
17+
},
18+
} as Config)
19+
})
20+
it('adds a fallback on non-unique field', () => {
21+
const result = buildSortParam({
22+
config,
23+
parentIsLocalized: false,
24+
fields: [
25+
{
26+
name: 'title',
27+
type: 'text',
28+
},
29+
{
30+
name: 'order',
31+
type: 'number',
32+
},
33+
],
34+
locale: 'en',
35+
sort: 'order',
36+
timestamps: true,
37+
adapter: {
38+
disableFallbackSort: false,
39+
} as MongooseAdapter,
40+
})
41+
42+
expect(result).toStrictEqual({ order: 'asc', createdAt: 'desc' })
43+
})
44+
45+
it('adds a fallback when sort isnt provided', () => {
46+
const result = buildSortParam({
47+
config,
48+
parentIsLocalized: false,
49+
fields: [
50+
{
51+
name: 'title',
52+
type: 'text',
53+
},
54+
{
55+
name: 'order',
56+
type: 'number',
57+
},
58+
],
59+
locale: 'en',
60+
sort: undefined,
61+
timestamps: true,
62+
adapter: {
63+
disableFallbackSort: false,
64+
} as MongooseAdapter,
65+
})
66+
67+
expect(result).toStrictEqual({ createdAt: 'desc' })
68+
})
69+
70+
it('does not add a fallback on non-unique field when disableFallbackSort is true', () => {
71+
const result = buildSortParam({
72+
config,
73+
parentIsLocalized: false,
74+
fields: [
75+
{
76+
name: 'title',
77+
type: 'text',
78+
},
79+
{
80+
name: 'order',
81+
type: 'number',
82+
},
83+
],
84+
locale: 'en',
85+
sort: 'order',
86+
timestamps: true,
87+
adapter: {
88+
disableFallbackSort: true,
89+
} as MongooseAdapter,
90+
})
91+
92+
expect(result).toStrictEqual({ order: 'asc' })
93+
})
94+
95+
// This test should be true even when disableFallbackSort is false
96+
it('does not add a fallback on unique field', () => {
97+
const result = buildSortParam({
98+
config,
99+
parentIsLocalized: false,
100+
fields: [
101+
{
102+
name: 'title',
103+
type: 'text',
104+
},
105+
{
106+
name: 'order',
107+
type: 'number',
108+
unique: true, // Marking this field as unique
109+
},
110+
],
111+
locale: 'en',
112+
sort: 'order',
113+
timestamps: true,
114+
adapter: {
115+
disableFallbackSort: false,
116+
} as MongooseAdapter,
117+
})
118+
119+
expect(result).toStrictEqual({ order: 'asc' })
120+
})
121+
})

packages/db-mongodb/src/queries/buildSortParam.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type Args = {
1919
fields: FlattenedField[]
2020
locale?: string
2121
parentIsLocalized?: boolean
22-
sort: Sort
22+
sort?: Sort
2323
sortAggregation?: PipelineStage[]
2424
timestamps: boolean
2525
versions?: boolean
@@ -150,6 +150,12 @@ export const buildSortParam = ({
150150
sort = [sort]
151151
}
152152

153+
// We use this flag to determine if the sort is unique or not to decide whether to add a fallback sort.
154+
const isUniqueSort = sort.some((item) => {
155+
const field = getFieldByPath({ fields, path: item })
156+
return field?.field?.unique
157+
})
158+
153159
// In the case of Mongo, when sorting by a field that is not unique, the results are not guaranteed to be in the same order each time.
154160
// So we add a fallback sort to ensure that the results are always in the same order.
155161
let fallbackSort = '-id'
@@ -158,7 +164,12 @@ export const buildSortParam = ({
158164
fallbackSort = '-createdAt'
159165
}
160166

161-
if (!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))) {
167+
const includeFallbackSort =
168+
!adapter.disableFallbackSort &&
169+
!isUniqueSort &&
170+
!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))
171+
172+
if (includeFallbackSort) {
162173
sort.push(fallbackSort)
163174
}
164175

0 commit comments

Comments
 (0)