fix(db-postgres): sort by localized fields (#8839)

### What?
Fixes https://github.com/payloadcms/payload/issues/5152 issue related to
sorting by a localized field with SQLite / Postgres database adapters.

### Why?
It was an incorrect behaviour.


### How?
Modifies the `getTableColumnFromPath` file to have correct join
conditions. Previously if you had this structure in the _locales table
_locale title parent
en          A    1
es          B     1
we sorted by everything that's here, but we need to sort only by the
passed locale.

Additionally fixes a typescript error in `dev.ts` that I added here
https://github.com/payloadcms/payload/pull/8834

Also, removes the condition with `joins.length` in `countDistinct`. It
was there as for this issue
https://github.com/payloadcms/payload/issues/4889 because sorting by a
localized property caused duplication. This can simnifically improve
performance for `.find` with nested querying/sorting on large data sets,
because `count(*)` is faster than `count(DISTINCT id)`
This commit is contained in:
Sasha
2024-10-24 21:47:58 +03:00
committed by GitHub
parent 2e11068f49
commit 9069bd3fac
5 changed files with 192 additions and 93 deletions

View File

@@ -3,7 +3,7 @@ import { type Payload, type Where } from 'payload'
import { fileURLToPath } from 'url'
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import type { LocalizedPost, WithLocalizedRelationship } from './payload-types.js'
import type { LocalizedPost, LocalizedSort, WithLocalizedRelationship } from './payload-types.js'
import { idToString } from '../helpers/idToString.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
@@ -369,6 +369,7 @@ describe('Localization', () => {
describe('Localized Sort Count', () => {
const expectedTotalDocs = 5
const posts: LocalizedSort[] = []
beforeAll(async () => {
for (let i = 1; i <= expectedTotalDocs; i++) {
const post = await payload.create({
@@ -380,6 +381,8 @@ describe('Localization', () => {
locale: englishLocale,
})
posts.push(post)
await payload.update({
id: post.id,
collection: localizedSortSlug,
@@ -419,6 +422,118 @@ describe('Localization', () => {
expect(sortByTitleQuery.totalDocs).toEqual(expectedTotalDocs)
expect(sortByDateQuery.totalDocs).toEqual(expectedTotalDocs)
})
it('should return correct order when sorted by localized fields', async () => {
const { docs: docsAsc } = await payload.find({ collection: localizedSortSlug, sort: 'title' })
docsAsc.forEach((doc, i) => {
expect(posts[i].id).toBe(doc.id)
})
const { docs: docsDesc } = await payload.find({
collection: localizedSortSlug,
sort: '-title',
})
docsDesc.forEach((doc, i) => {
expect(posts.at(posts.length - i - 1).id).toBe(doc.id)
})
// Test with words
const randomWords = [
'sunset',
'whisper',
'lighthouse',
'harmony',
'crystal',
'thunder',
'meadow',
'voyage',
'echo',
'quicksand',
]
const randomWordsSpanish = [
'atardecer',
'susurro',
'faro',
'armonía',
'cristal',
'trueno',
'pradera',
'viaje',
'eco',
'arenas movedizas',
]
expect(randomWords).toHaveLength(randomWordsSpanish.length)
const randomWordsPosts: (number | string)[] = []
for (let i = 0; i < randomWords.length; i++) {
const en = randomWords[i]
const post = await payload.create({ collection: 'localized-sort', data: { title: en } })
const es = randomWordsSpanish[i]
await payload.update({
collection: 'localized-sort',
data: { title: es },
id: post.id,
locale: 'es',
})
randomWordsPosts.push(post.id)
}
const ascSortedWordsEn = randomWords.toSorted((a, b) => a.localeCompare(b))
const descSortedWordsEn = randomWords.toSorted((a, b) => b.localeCompare(a))
const q = { id: { in: randomWordsPosts } }
const { docs: randomWordsEnAsc } = await payload.find({
collection: localizedSortSlug,
sort: 'title',
where: q,
})
randomWordsEnAsc.forEach((doc, i) => {
expect(ascSortedWordsEn[i]).toBe(doc.title)
})
const { docs: randomWordsEnDesc } = await payload.find({
collection: localizedSortSlug,
sort: '-title',
where: q,
})
randomWordsEnDesc.forEach((doc, i) => {
expect(descSortedWordsEn[i]).toBe(doc.title)
})
// Test sorting for Spanish locale
const ascSortedWordsEs = randomWordsSpanish.toSorted((a, b) => a.localeCompare(b))
const descSortedWordsEs = randomWordsSpanish.toSorted((a, b) => b.localeCompare(a))
// Fetch sorted words in Spanish (ascending)
const { docs: randomWordsEsAsc } = await payload.find({
collection: localizedSortSlug,
sort: 'title',
where: q,
locale: 'es',
})
randomWordsEsAsc.forEach((doc, i) => {
expect(ascSortedWordsEs[i]).toBe(doc.title)
})
// Fetch sorted words in Spanish (descending)
const { docs: randomWordsEsDesc } = await payload.find({
collection: localizedSortSlug,
sort: '-title',
where: q,
locale: 'es',
})
randomWordsEsDesc.forEach((doc, i) => {
expect(descSortedWordsEs[i]).toBe(doc.title)
})
})
})
describe('Localized Relationship', () => {