feat: compound indexes (#11512)
### What?
This PR adds ability to define indexes on several fields for collections
(compound indexes).
Example:
```ts
{
indexes: [{ unique: true, fields: ['title', 'group.name'] }]
}
```
### Why?
This can be used to either speed up querying/sorting by 2 or more fields
at the same time or to ensure uniqueness between several fields.
### How?
Implements this logic in database adapters. Additionally, adds a utility
`getFieldByPath`.
This commit is contained in:
@@ -4,10 +4,9 @@ const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
import type { TextField } from 'payload'
|
||||
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { randomUUID } from 'crypto'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { seed } from './seed.js'
|
||||
import {
|
||||
customIDsSlug,
|
||||
@@ -501,7 +500,7 @@ export default buildConfigWithDefaults({
|
||||
beforeChange: [
|
||||
({ value, operation }) => {
|
||||
if (operation === 'create') {
|
||||
return uuid()
|
||||
return randomUUID()
|
||||
}
|
||||
return value
|
||||
},
|
||||
@@ -564,6 +563,43 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
versions: true,
|
||||
},
|
||||
{
|
||||
slug: 'compound-indexes',
|
||||
fields: [
|
||||
{
|
||||
name: 'one',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'two',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'three',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'four',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
indexes: [
|
||||
{
|
||||
fields: ['one', 'two'],
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
fields: ['three', 'group.four'],
|
||||
unique: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
globals: [
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
migrateRelationshipsV2_V3,
|
||||
migrateVersionsV1_V2,
|
||||
} from '@payloadcms/db-mongodb/migration-utils'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { type Table } from 'drizzle-orm'
|
||||
import * as drizzlePg from 'drizzle-orm/pg-core'
|
||||
import * as drizzleSqlite from 'drizzle-orm/sqlite-core'
|
||||
@@ -305,6 +306,68 @@ describe('database', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Compound Indexes', () => {
|
||||
beforeEach(async () => {
|
||||
await payload.delete({ collection: 'compound-indexes', where: {} })
|
||||
})
|
||||
|
||||
it('top level: should throw a unique error', async () => {
|
||||
await payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: { three: randomUUID(), one: '1', two: '2' },
|
||||
})
|
||||
|
||||
// does not fail
|
||||
await payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: { three: randomUUID(), one: '1', two: '3' },
|
||||
})
|
||||
// does not fail
|
||||
await payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: { three: randomUUID(), one: '-1', two: '2' },
|
||||
})
|
||||
|
||||
// fails
|
||||
await expect(
|
||||
payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: { three: randomUUID(), one: '1', two: '2' },
|
||||
}),
|
||||
).rejects.toBeTruthy()
|
||||
})
|
||||
|
||||
it('combine group and top level: should throw a unique error', async () => {
|
||||
await payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: {
|
||||
one: randomUUID(),
|
||||
three: '3',
|
||||
group: { four: '4' },
|
||||
},
|
||||
})
|
||||
|
||||
// does not fail
|
||||
await payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: { one: randomUUID(), three: '3', group: { four: '5' } },
|
||||
})
|
||||
// does not fail
|
||||
await payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: { one: randomUUID(), three: '4', group: { four: '4' } },
|
||||
})
|
||||
|
||||
// fails
|
||||
await expect(
|
||||
payload.create({
|
||||
collection: 'compound-indexes',
|
||||
data: { one: randomUUID(), three: '3', group: { four: '4' } },
|
||||
}),
|
||||
).rejects.toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('migrations', () => {
|
||||
let ranFreshTest = false
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ export interface Config {
|
||||
'custom-ids': CustomId;
|
||||
'fake-custom-ids': FakeCustomId;
|
||||
'relationships-migration': RelationshipsMigration;
|
||||
'compound-indexes': CompoundIndex;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
@@ -97,6 +98,7 @@ export interface Config {
|
||||
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||
'fake-custom-ids': FakeCustomIdsSelect<false> | FakeCustomIdsSelect<true>;
|
||||
'relationships-migration': RelationshipsMigrationSelect<false> | RelationshipsMigrationSelect<true>;
|
||||
'compound-indexes': CompoundIndexesSelect<false> | CompoundIndexesSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
@@ -400,6 +402,21 @@ export interface RelationshipsMigration {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "compound-indexes".
|
||||
*/
|
||||
export interface CompoundIndex {
|
||||
id: string;
|
||||
one?: string | null;
|
||||
two?: string | null;
|
||||
three?: string | null;
|
||||
group?: {
|
||||
four?: string | null;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
@@ -472,6 +489,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'relationships-migration';
|
||||
value: string | RelationshipsMigration;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'compound-indexes';
|
||||
value: string | CompoundIndex;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -755,6 +776,22 @@ export interface RelationshipsMigrationSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "compound-indexes_select".
|
||||
*/
|
||||
export interface CompoundIndexesSelect<T extends boolean = true> {
|
||||
one?: T;
|
||||
two?: T;
|
||||
three?: T;
|
||||
group?:
|
||||
| T
|
||||
| {
|
||||
four?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
|
||||
Reference in New Issue
Block a user