fix(drizzle)!: localized fields uniqueness per locale (#8230)
Previously, this worked with MongoDB but failed with Postgres / SQLite
when the `slug` field has both `localized: true` and `unique: true`.
```ts
await payload.create({
collection: "posts",
locale: "en",
data: {
slug: "my-post"
}
})
await payload.create({
collection: "posts",
locale: "de",
data: {
slug: "my-post"
}
})
```
Now, we build unique constraints and indexes in combination with the
_locale column. This should also improve query performance for fields
with both index: true and localized: true.
### Migration steps (Postgres/SQLite only)
This change updates the database schema and requires a migration (if you
have any localized fields). To apply it, run the following commands:
```sh
pnpm payload migration:create locale_unique_indexes
pnpm payload migrate
```
Note that if you use `db.push: true` which is a default, you don't have
to run `pnpm payload migrate` in the development mode, only in the
production, as Payload automatically pushes the schema to your DB with
it.
This commit is contained in:
@@ -153,7 +153,7 @@ export const traverseFields = ({
|
|||||||
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
||||||
}
|
}
|
||||||
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
||||||
name: fieldName,
|
name: field.localized ? [fieldName, '_locale'] : fieldName,
|
||||||
columnName,
|
columnName,
|
||||||
tableName: newTableName,
|
tableName: newTableName,
|
||||||
unique,
|
unique,
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export const traverseFields = ({
|
|||||||
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
|
||||||
}
|
}
|
||||||
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
|
||||||
name: fieldName,
|
name: field.localized ? [fieldName, '_locale'] : fieldName,
|
||||||
columnName,
|
columnName,
|
||||||
tableName: newTableName,
|
tableName: newTableName,
|
||||||
unique,
|
unique,
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ export default buildConfigWithDefaults({
|
|||||||
],
|
],
|
||||||
type: 'group',
|
type: 'group',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'unique',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ArrayCollection,
|
ArrayCollection,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Payload, Where } from 'payload'
|
|
||||||
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { type Payload, type Where } from 'payload'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||||
@@ -1954,6 +1953,44 @@ describe('Localization', () => {
|
|||||||
expect(retrieved.array.es[0].text).toEqual(['hola', 'adios'])
|
expect(retrieved.array.es[0].text).toEqual(['hola', 'adios'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('localized with unique', () => {
|
||||||
|
it('localized with unique should work for each locale', async () => {
|
||||||
|
await payload.create({
|
||||||
|
collection: 'localized-posts',
|
||||||
|
locale: 'ar',
|
||||||
|
data: {
|
||||||
|
unique: 'text',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: 'localized-posts',
|
||||||
|
locale: 'en',
|
||||||
|
data: {
|
||||||
|
unique: 'text',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: 'localized-posts',
|
||||||
|
locale: 'es',
|
||||||
|
data: {
|
||||||
|
unique: 'text',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
payload.create({
|
||||||
|
collection: 'localized-posts',
|
||||||
|
locale: 'en',
|
||||||
|
data: {
|
||||||
|
unique: 'text',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).rejects.toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function createLocalizedPost(data: {
|
async function createLocalizedPost(data: {
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export interface LocalizedPost {
|
|||||||
group?: {
|
group?: {
|
||||||
children?: string | null;
|
children?: string | null;
|
||||||
};
|
};
|
||||||
|
unique?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user