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
https://github.com/payloadcms/payload/issues/12690
and the linked PR https://github.com/payloadcms/payload/pull/12888

Closes https://github.com/payloadcms/payload/issues/12690
This commit is contained in:
Paul
2025-06-27 06:45:30 -07:00
committed by GitHub
parent c66e5ca823
commit c902f14cb3
4 changed files with 145 additions and 2 deletions

View File

@@ -118,6 +118,13 @@ export interface Args {
*/
useFacet?: boolean
} & ConnectOptions
/**
* 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.
* This is because MongoDB does not guarantee the order of results, however in very large datasets this could affect performance.
*
* Set to `true` to disable this behaviour.
*/
disableFallbackSort?: boolean
/** 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 */
disableIndexHints?: boolean
/**
@@ -131,6 +138,7 @@ export interface Args {
*/
mongoMemoryServer?: MongoMemoryReplSet
prodMigrations?: Migration[]
transactionOptions?: false | TransactionOptions
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
@@ -198,6 +206,7 @@ export function mongooseAdapter({
autoPluralization = true,
collectionsSchemaOptions = {},
connectOptions,
disableFallbackSort = false,
disableIndexHints = false,
ensureIndexes = false,
migrationDir: migrationDirArg,
@@ -251,6 +260,7 @@ export function mongooseAdapter({
deleteOne,
deleteVersions,
destroy,
disableFallbackSort,
find,
findGlobal,
findGlobalVersions,

View File

@@ -0,0 +1,121 @@
import type { Config, SanitizedConfig } from 'payload'
import { sanitizeConfig } from 'payload'
import { buildSortParam } from './buildSortParam.js'
import { MongooseAdapter } from '../index.js'
let config: SanitizedConfig
describe('builds sort params', () => {
beforeAll(async () => {
config = await sanitizeConfig({
localization: {
defaultLocale: 'en',
fallback: true,
locales: ['en', 'es'],
},
} as Config)
})
it('adds a fallback on non-unique field', () => {
const result = buildSortParam({
config,
parentIsLocalized: false,
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'order',
type: 'number',
},
],
locale: 'en',
sort: 'order',
timestamps: true,
adapter: {
disableFallbackSort: false,
} as MongooseAdapter,
})
expect(result).toStrictEqual({ order: 'asc', createdAt: 'desc' })
})
it('adds a fallback when sort isnt provided', () => {
const result = buildSortParam({
config,
parentIsLocalized: false,
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'order',
type: 'number',
},
],
locale: 'en',
sort: undefined,
timestamps: true,
adapter: {
disableFallbackSort: false,
} as MongooseAdapter,
})
expect(result).toStrictEqual({ createdAt: 'desc' })
})
it('does not add a fallback on non-unique field when disableFallbackSort is true', () => {
const result = buildSortParam({
config,
parentIsLocalized: false,
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'order',
type: 'number',
},
],
locale: 'en',
sort: 'order',
timestamps: true,
adapter: {
disableFallbackSort: true,
} as MongooseAdapter,
})
expect(result).toStrictEqual({ order: 'asc' })
})
// This test should be true even when disableFallbackSort is false
it('does not add a fallback on unique field', () => {
const result = buildSortParam({
config,
parentIsLocalized: false,
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'order',
type: 'number',
unique: true, // Marking this field as unique
},
],
locale: 'en',
sort: 'order',
timestamps: true,
adapter: {
disableFallbackSort: false,
} as MongooseAdapter,
})
expect(result).toStrictEqual({ order: 'asc' })
})
})

View File

@@ -19,7 +19,7 @@ type Args = {
fields: FlattenedField[]
locale?: string
parentIsLocalized?: boolean
sort: Sort
sort?: Sort
sortAggregation?: PipelineStage[]
timestamps: boolean
versions?: boolean
@@ -150,6 +150,12 @@ export const buildSortParam = ({
sort = [sort]
}
// We use this flag to determine if the sort is unique or not to decide whether to add a fallback sort.
const isUniqueSort = sort.some((item) => {
const field = getFieldByPath({ fields, path: item })
return field?.field?.unique
})
// 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.
// So we add a fallback sort to ensure that the results are always in the same order.
let fallbackSort = '-id'
@@ -158,7 +164,12 @@ export const buildSortParam = ({
fallbackSort = '-createdAt'
}
if (!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))) {
const includeFallbackSort =
!adapter.disableFallbackSort &&
!isUniqueSort &&
!(sort.includes(fallbackSort) || sort.includes(fallbackSort.replace('-', '')))
if (includeFallbackSort) {
sort.push(fallbackSort)
}