fix(drizzle): indexes / unique with relationships (#8432)

Fixes https://github.com/payloadcms/payload/issues/8413 and
https://github.com/payloadcms/payload/issues/6460

- Builds indexes for relationships by default in the SQL schema
- Fixes `unique: true` handling with Postgres / SQLite for every type of
relationships (non-polymorphic. hasMany, polymorphic, polymorphic
hasMany) _note_: disables unique for nested to arrays / blocks
relationships in the `_rels` table.
- adds tests

2.0 PR tha ports only indexes creation
https://github.com/payloadcms/payload/pull/8446, because `unique: true`
could be a breaking change if someone has incosistent unique data in the
database.
This commit is contained in:
Sasha
2024-10-11 20:13:54 +03:00
committed by GitHub
parent 256949e331
commit 1ffb6c3e13
7 changed files with 394 additions and 15 deletions

View File

@@ -24,6 +24,7 @@ import toSnakeCase from 'to-snake-case'
import type { GenericColumns, GenericTable, IDType, SQLiteAdapter } from '../types.js' import type { GenericColumns, GenericTable, IDType, SQLiteAdapter } from '../types.js'
import { createIndex } from './createIndex.js'
import { getIDColumn } from './getIDColumn.js' import { getIDColumn } from './getIDColumn.js'
import { setColumnID } from './setColumnID.js' import { setColumnID } from './setColumnID.js'
import { traverseFields } from './traverseFields.js' import { traverseFields } from './traverseFields.js'
@@ -56,6 +57,7 @@ type Args = {
buildNumbers?: boolean buildNumbers?: boolean
buildRelationships?: boolean buildRelationships?: boolean
disableNotNull: boolean disableNotNull: boolean
disableRelsTableUnique?: boolean
disableUnique: boolean disableUnique: boolean
fields: Field[] fields: Field[]
joins?: SanitizedJoins joins?: SanitizedJoins
@@ -64,6 +66,7 @@ type Args = {
rootRelationsToBuild?: RelationMap rootRelationsToBuild?: RelationMap
rootTableIDColType?: IDType rootTableIDColType?: IDType
rootTableName?: string rootTableName?: string
rootUniqueRelationships?: Set<string>
tableName: string tableName: string
timestamps?: boolean timestamps?: boolean
versions: boolean versions: boolean
@@ -88,6 +91,7 @@ export const buildTable = ({
baseColumns = {}, baseColumns = {},
baseExtraConfig = {}, baseExtraConfig = {},
disableNotNull, disableNotNull,
disableRelsTableUnique,
disableUnique = false, disableUnique = false,
fields, fields,
joins, joins,
@@ -96,6 +100,7 @@ export const buildTable = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName: incomingRootTableName, rootTableName: incomingRootTableName,
rootUniqueRelationships,
tableName, tableName,
timestamps, timestamps,
versions, versions,
@@ -114,6 +119,7 @@ export const buildTable = ({
// Relationships to the base collection // Relationships to the base collection
const relationships: Set<string> = rootRelationships || new Set() const relationships: Set<string> = rootRelationships || new Set()
const uniqueRelationships: Set<string> = rootUniqueRelationships || new Set()
let relationshipsTable: GenericTable | SQLiteTableWithColumns<any> let relationshipsTable: GenericTable | SQLiteTableWithColumns<any>
@@ -133,6 +139,7 @@ export const buildTable = ({
adapter, adapter,
columns, columns,
disableNotNull, disableNotNull,
disableRelsTableUnique,
disableUnique, disableUnique,
fields, fields,
indexes, indexes,
@@ -147,6 +154,7 @@ export const buildTable = ({
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild, rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
rootTableIDColType: rootTableIDColType || idColType, rootTableIDColType: rootTableIDColType || idColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -393,7 +401,9 @@ export const buildTable = ({
colType = 'text' colType = 'text'
} }
relationshipColumns[`${relationTo}ID`] = getIDColumn({ const colName = `${relationTo}ID`
relationshipColumns[colName] = getIDColumn({
name: `${formattedRelationTo}_id`, name: `${formattedRelationTo}_id`,
type: colType, type: colType,
primaryKey: false, primaryKey: false,
@@ -402,9 +412,27 @@ export const buildTable = ({
relationExtraConfig[`${relationTo}IdFk`] = (cols) => relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
foreignKey({ foreignKey({
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`, name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
columns: [cols[`${relationTo}ID`]], columns: [cols[colName]],
foreignColumns: [adapter.tables[formattedRelationTo].id], foreignColumns: [adapter.tables[formattedRelationTo].id],
}).onDelete('cascade') }).onDelete('cascade')
const indexName = [colName]
const unique = !disableUnique && uniqueRelationships.has(relationTo)
if (unique) {
indexName.push('path')
}
if (hasLocalizedRelationshipField) {
indexName.push('locale')
}
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
name: indexName,
columnName: `${formattedRelationTo}_id`,
tableName: relationshipsTableName,
unique,
})
}) })
relationshipsTable = sqliteTable(relationshipsTableName, relationshipColumns, (cols) => { relationshipsTable = sqliteTable(relationshipsTableName, relationshipColumns, (cols) => {

View File

@@ -36,6 +36,7 @@ type Args = {
columnPrefix?: string columnPrefix?: string
columns: Record<string, SQLiteColumnBuilder> columns: Record<string, SQLiteColumnBuilder>
disableNotNull: boolean disableNotNull: boolean
disableRelsTableUnique?: boolean
disableUnique?: boolean disableUnique?: boolean
fieldPrefix?: string fieldPrefix?: string
fields: (Field | TabAsField)[] fields: (Field | TabAsField)[]
@@ -52,6 +53,7 @@ type Args = {
rootRelationsToBuild?: RelationMap rootRelationsToBuild?: RelationMap
rootTableIDColType: IDType rootTableIDColType: IDType
rootTableName: string rootTableName: string
uniqueRelationships: Set<string>
versions: boolean versions: boolean
/** /**
* Tracks whether or not this table is built * Tracks whether or not this table is built
@@ -74,6 +76,7 @@ export const traverseFields = ({
columnPrefix, columnPrefix,
columns, columns,
disableNotNull, disableNotNull,
disableRelsTableUnique,
disableUnique = false, disableUnique = false,
fieldPrefix, fieldPrefix,
fields, fields,
@@ -90,6 +93,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}: Args): Result => { }: Args): Result => {
@@ -147,9 +151,10 @@ export const traverseFields = ({
} }
if ( if (
(field.unique || field.index) && (field.unique || field.index || ['relationship', 'upload'].includes(field.type)) &&
!['array', 'blocks', 'group', 'point', 'relationship', 'upload'].includes(field.type) && !['array', 'blocks', 'group', 'point'].includes(field.type) &&
!('hasMany' in field && field.hasMany === true) !('hasMany' in field && field.hasMany === true) &&
!('relationTo' in field && Array.isArray(field.relationTo))
) { ) {
const unique = disableUnique !== true && field.unique const unique = disableUnique !== true && field.unique
if (unique) { if (unique) {
@@ -401,12 +406,14 @@ export const traverseFields = ({
baseColumns, baseColumns,
baseExtraConfig, baseExtraConfig,
disableNotNull: disableNotNullFromHere, disableNotNull: disableNotNullFromHere,
disableRelsTableUnique: true,
disableUnique, disableUnique,
fields: disableUnique ? idToUUID(field.fields) : field.fields, fields: disableUnique ? idToUUID(field.fields) : field.fields,
rootRelationships: relationships, rootRelationships: relationships,
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
rootUniqueRelationships: uniqueRelationships,
tableName: arrayTableName, tableName: arrayTableName,
versions, versions,
withinLocalizedArrayOrBlock: isLocalized, withinLocalizedArrayOrBlock: isLocalized,
@@ -540,12 +547,14 @@ export const traverseFields = ({
baseColumns, baseColumns,
baseExtraConfig, baseExtraConfig,
disableNotNull: disableNotNullFromHere, disableNotNull: disableNotNullFromHere,
disableRelsTableUnique: true,
disableUnique, disableUnique,
fields: disableUnique ? idToUUID(block.fields) : block.fields, fields: disableUnique ? idToUUID(block.fields) : block.fields,
rootRelationships: relationships, rootRelationships: relationships,
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
rootUniqueRelationships: uniqueRelationships,
tableName: blockTableName, tableName: blockTableName,
versions, versions,
withinLocalizedArrayOrBlock: isLocalized, withinLocalizedArrayOrBlock: isLocalized,
@@ -664,6 +673,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -719,6 +729,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized, withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized,
}) })
@@ -775,6 +786,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -831,6 +843,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -859,9 +872,17 @@ export const traverseFields = ({
case 'relationship': case 'relationship':
case 'upload': case 'upload':
if (Array.isArray(field.relationTo)) { if (Array.isArray(field.relationTo)) {
field.relationTo.forEach((relation) => relationships.add(relation)) field.relationTo.forEach((relation) => {
relationships.add(relation)
if (field.unique && !disableUnique && !disableRelsTableUnique) {
uniqueRelationships.add(relation)
}
})
} else if (field.hasMany) { } else if (field.hasMany) {
relationships.add(field.relationTo) relationships.add(field.relationTo)
if (field.unique && !disableUnique && !disableRelsTableUnique) {
uniqueRelationships.add(field.relationTo)
}
} else { } else {
// simple relationships get a column on the targetTable with a foreign key to the relationTo table // simple relationships get a column on the targetTable with a foreign key to the relationTo table
const relationshipConfig = adapter.payload.collections[field.relationTo].config const relationshipConfig = adapter.payload.collections[field.relationTo].config

View File

@@ -30,6 +30,7 @@ import type {
} from '../types.js' } from '../types.js'
import { createTableName } from '../../createTableName.js' import { createTableName } from '../../createTableName.js'
import { createIndex } from './createIndex.js'
import { parentIDColumnMap } from './parentIDColumnMap.js' import { parentIDColumnMap } from './parentIDColumnMap.js'
import { setColumnID } from './setColumnID.js' import { setColumnID } from './setColumnID.js'
import { traverseFields } from './traverseFields.js' import { traverseFields } from './traverseFields.js'
@@ -45,6 +46,7 @@ type Args = {
buildNumbers?: boolean buildNumbers?: boolean
buildRelationships?: boolean buildRelationships?: boolean
disableNotNull: boolean disableNotNull: boolean
disableRelsTableUnique?: boolean
disableUnique: boolean disableUnique: boolean
fields: Field[] fields: Field[]
joins?: SanitizedJoins joins?: SanitizedJoins
@@ -52,6 +54,7 @@ type Args = {
rootRelationsToBuild?: RelationMap rootRelationsToBuild?: RelationMap
rootTableIDColType?: string rootTableIDColType?: string
rootTableName?: string rootTableName?: string
rootUniqueRelationships?: Set<string>
tableName: string tableName: string
timestamps?: boolean timestamps?: boolean
versions: boolean versions: boolean
@@ -76,6 +79,7 @@ export const buildTable = ({
baseColumns = {}, baseColumns = {},
baseExtraConfig = {}, baseExtraConfig = {},
disableNotNull, disableNotNull,
disableRelsTableUnique = false,
disableUnique = false, disableUnique = false,
fields, fields,
joins, joins,
@@ -83,6 +87,7 @@ export const buildTable = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName: incomingRootTableName, rootTableName: incomingRootTableName,
rootUniqueRelationships,
tableName, tableName,
timestamps, timestamps,
versions, versions,
@@ -102,6 +107,9 @@ export const buildTable = ({
// Relationships to the base collection // Relationships to the base collection
const relationships: Set<string> = rootRelationships || new Set() const relationships: Set<string> = rootRelationships || new Set()
// Unique relationships to the base collection
const uniqueRelationships: Set<string> = rootUniqueRelationships || new Set()
let relationshipsTable: GenericTable | PgTableWithColumns<any> let relationshipsTable: GenericTable | PgTableWithColumns<any>
// Drizzle relations // Drizzle relations
@@ -120,6 +128,7 @@ export const buildTable = ({
adapter, adapter,
columns, columns,
disableNotNull, disableNotNull,
disableRelsTableUnique,
disableUnique, disableUnique,
fields, fields,
indexes, indexes,
@@ -133,6 +142,7 @@ export const buildTable = ({
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild, rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
rootTableIDColType: rootTableIDColType || idColType, rootTableIDColType: rootTableIDColType || idColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -368,16 +378,34 @@ export const buildTable = ({
colType = 'varchar' colType = 'varchar'
} }
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType]( const colName = `${relationTo}ID`
`${formattedRelationTo}_id`,
) relationshipColumns[colName] = parentIDColumnMap[colType](`${formattedRelationTo}_id`)
relationExtraConfig[`${relationTo}IdFk`] = (cols) => relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
foreignKey({ foreignKey({
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`, name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
columns: [cols[`${relationTo}ID`]], columns: [cols[colName]],
foreignColumns: [adapter.tables[formattedRelationTo].id], foreignColumns: [adapter.tables[formattedRelationTo].id],
}).onDelete('cascade') }).onDelete('cascade')
const indexName = [colName]
const unique = !disableUnique && uniqueRelationships.has(relationTo)
if (unique) {
indexName.push('path')
}
if (hasLocalizedRelationshipField) {
indexName.push('locale')
}
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
name: indexName,
columnName: `${formattedRelationTo}_id`,
tableName: relationshipsTableName,
unique,
})
}) })
relationshipsTable = adapter.pgSchema.table( relationshipsTable = adapter.pgSchema.table(

View File

@@ -43,6 +43,7 @@ type Args = {
columnPrefix?: string columnPrefix?: string
columns: Record<string, PgColumnBuilder> columns: Record<string, PgColumnBuilder>
disableNotNull: boolean disableNotNull: boolean
disableRelsTableUnique?: boolean
disableUnique?: boolean disableUnique?: boolean
fieldPrefix?: string fieldPrefix?: string
fields: (Field | TabAsField)[] fields: (Field | TabAsField)[]
@@ -58,6 +59,7 @@ type Args = {
rootRelationsToBuild?: RelationMap rootRelationsToBuild?: RelationMap
rootTableIDColType: string rootTableIDColType: string
rootTableName: string rootTableName: string
uniqueRelationships: Set<string>
versions: boolean versions: boolean
/** /**
* Tracks whether or not this table is built * Tracks whether or not this table is built
@@ -80,6 +82,7 @@ export const traverseFields = ({
columnPrefix, columnPrefix,
columns, columns,
disableNotNull, disableNotNull,
disableRelsTableUnique,
disableUnique = false, disableUnique = false,
fieldPrefix, fieldPrefix,
fields, fields,
@@ -95,6 +98,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}: Args): Result => { }: Args): Result => {
@@ -152,9 +156,10 @@ export const traverseFields = ({
} }
if ( if (
(field.unique || field.index) && (field.unique || field.index || ['relationship', 'upload'].includes(field.type)) &&
!['array', 'blocks', 'group', 'point', 'relationship', 'upload'].includes(field.type) && !['array', 'blocks', 'group', 'point'].includes(field.type) &&
!('hasMany' in field && field.hasMany === true) !('hasMany' in field && field.hasMany === true) &&
!('relationTo' in field && Array.isArray(field.relationTo))
) { ) {
const unique = disableUnique !== true && field.unique const unique = disableUnique !== true && field.unique
if (unique) { if (unique) {
@@ -412,12 +417,14 @@ export const traverseFields = ({
baseColumns, baseColumns,
baseExtraConfig, baseExtraConfig,
disableNotNull: disableNotNullFromHere, disableNotNull: disableNotNullFromHere,
disableRelsTableUnique: true,
disableUnique, disableUnique,
fields: disableUnique ? idToUUID(field.fields) : field.fields, fields: disableUnique ? idToUUID(field.fields) : field.fields,
rootRelationships: relationships, rootRelationships: relationships,
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
rootUniqueRelationships: uniqueRelationships,
tableName: arrayTableName, tableName: arrayTableName,
versions, versions,
withinLocalizedArrayOrBlock: isLocalized, withinLocalizedArrayOrBlock: isLocalized,
@@ -547,12 +554,14 @@ export const traverseFields = ({
baseColumns, baseColumns,
baseExtraConfig, baseExtraConfig,
disableNotNull: disableNotNullFromHere, disableNotNull: disableNotNullFromHere,
disableRelsTableUnique: true,
disableUnique, disableUnique,
fields: disableUnique ? idToUUID(block.fields) : block.fields, fields: disableUnique ? idToUUID(block.fields) : block.fields,
rootRelationships: relationships, rootRelationships: relationships,
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
rootUniqueRelationships: uniqueRelationships,
tableName: blockTableName, tableName: blockTableName,
versions, versions,
withinLocalizedArrayOrBlock: isLocalized, withinLocalizedArrayOrBlock: isLocalized,
@@ -670,6 +679,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -724,6 +734,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized, withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized,
}) })
@@ -779,6 +790,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -834,6 +846,7 @@ export const traverseFields = ({
rootRelationsToBuild, rootRelationsToBuild,
rootTableIDColType, rootTableIDColType,
rootTableName, rootTableName,
uniqueRelationships,
versions, versions,
withinLocalizedArrayOrBlock, withinLocalizedArrayOrBlock,
}) })
@@ -862,9 +875,17 @@ export const traverseFields = ({
case 'relationship': case 'relationship':
case 'upload': case 'upload':
if (Array.isArray(field.relationTo)) { if (Array.isArray(field.relationTo)) {
field.relationTo.forEach((relation) => relationships.add(relation)) field.relationTo.forEach((relation) => {
relationships.add(relation)
if (field.unique && !disableUnique && !disableRelsTableUnique) {
uniqueRelationships.add(relation)
}
})
} else if (field.hasMany) { } else if (field.hasMany) {
relationships.add(field.relationTo) relationships.add(field.relationTo)
if (field.unique && !disableUnique && !disableRelsTableUnique) {
uniqueRelationships.add(field.relationTo)
}
} else { } else {
// simple relationships get a column on the targetTable with a foreign key to the relationTo table // simple relationships get a column on the targetTable with a foreign key to the relationTo table
const relationshipConfig = adapter.payload.collections[field.relationTo].config const relationshipConfig = adapter.payload.collections[field.relationTo].config

View File

@@ -16,6 +16,52 @@ const IndexedFields: CollectionConfig = {
type: 'text', type: 'text',
unique: true, unique: true,
}, },
{
name: 'uniqueRelationship',
type: 'relationship',
relationTo: 'text-fields',
unique: true,
},
{
name: 'uniqueHasManyRelationship',
type: 'relationship',
relationTo: 'text-fields',
unique: true,
hasMany: true,
},
{
name: 'uniqueHasManyRelationship_2',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true,
unique: true,
},
{
name: 'uniquePolymorphicRelationship',
type: 'relationship',
relationTo: ['text-fields'],
unique: true,
},
{
name: 'uniquePolymorphicRelationship_2',
type: 'relationship',
relationTo: ['text-fields'],
unique: true,
},
{
name: 'uniqueHasManyPolymorphicRelationship',
type: 'relationship',
relationTo: ['text-fields'],
unique: true,
hasMany: true,
},
{
name: 'uniqueHasManyPolymorphicRelationship_2',
type: 'relationship',
relationTo: ['text-fields'],
unique: true,
hasMany: true,
},
{ {
name: 'uniqueRequiredText', name: 'uniqueRequiredText',
type: 'text', type: 'text',

View File

@@ -1,9 +1,9 @@
import type { MongooseAdapter } from '@payloadcms/db-mongodb' import type { MongooseAdapter } from '@payloadcms/db-mongodb'
import type { IndexDirection, IndexOptions } from 'mongoose' import type { IndexDirection, IndexOptions } from 'mongoose'
import type { PaginatedDocs, Payload } from 'payload'
import { reload } from '@payloadcms/next/utilities' import { reload } from '@payloadcms/next/utilities'
import path from 'path' import path from 'path'
import { type PaginatedDocs, type Payload, ValidationError } from 'payload'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import type { NextRESTClient } from '../helpers/NextRESTClient.js' import type { NextRESTClient } from '../helpers/NextRESTClient.js'
@@ -1035,6 +1035,218 @@ describe('Fields', () => {
}).toBeDefined() }).toBeDefined()
}) })
it('should throw validation error saving on unique relationship fields hasMany: false non polymorphic', async () => {
const textDoc = await payload.create({ collection: 'text-fields', data: { text: 'asd' } })
await payload
.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '1',
text: '2',
uniqueRequiredText: '3',
uniqueRelationship: textDoc.id,
},
})
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
.then((doc) =>
payload.update({
locale: 'es',
collection: 'indexed-fields',
data: { localizedUniqueRequiredText: '20' },
id: doc.id,
}),
)
await expect(
payload.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '4',
text: '5',
uniqueRequiredText: '10',
uniqueRelationship: textDoc.id,
},
}),
).rejects.toBeTruthy()
})
it('should throw validation error saving on unique relationship fields hasMany: true', async () => {
const textDoc = await payload.create({ collection: 'text-fields', data: { text: 'asd' } })
await payload
.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '1',
text: '2',
uniqueRequiredText: '3',
uniqueHasManyRelationship: [textDoc.id],
},
})
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
.then((doc) =>
payload.update({
locale: 'es',
collection: 'indexed-fields',
data: { localizedUniqueRequiredText: '40' },
id: doc.id,
}),
)
// Should allow the same relationship on a diferrent field!
await payload
.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '31',
text: '24',
uniqueRequiredText: '55',
uniqueHasManyRelationship_2: [textDoc.id],
},
})
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
.then((doc) =>
payload.update({
locale: 'es',
collection: 'indexed-fields',
data: { localizedUniqueRequiredText: '30' },
id: doc.id,
}),
)
await expect(
payload.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '4',
text: '5',
uniqueRequiredText: '10',
uniqueHasManyRelationship: [textDoc.id],
},
}),
).rejects.toBeTruthy()
})
it('should throw validation error saving on unique relationship fields polymorphic', async () => {
const textDoc = await payload.create({ collection: 'text-fields', data: { text: 'asd' } })
await payload
.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '1',
text: '2',
uniqueRequiredText: '3',
uniquePolymorphicRelationship: { relationTo: 'text-fields', value: textDoc.id },
},
})
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
.then((doc) =>
payload.update({
locale: 'es',
collection: 'indexed-fields',
data: { localizedUniqueRequiredText: '20' },
id: doc.id,
}),
)
// Should allow the same relationship on a diferrent field!
await payload
.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '31',
text: '24',
uniqueRequiredText: '55',
uniquePolymorphicRelationship_2: { relationTo: 'text-fields', value: textDoc.id },
},
})
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
.then((doc) =>
payload.update({
locale: 'es',
collection: 'indexed-fields',
data: { localizedUniqueRequiredText: '100' },
id: doc.id,
}),
)
await expect(
payload.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '4',
text: '5',
uniqueRequiredText: '10',
uniquePolymorphicRelationship: { relationTo: 'text-fields', value: textDoc.id },
},
}),
).rejects.toBeTruthy()
})
it('should throw validation error saving on unique relationship fields polymorphic hasMany: true', async () => {
const textDoc = await payload.create({ collection: 'text-fields', data: { text: 'asd' } })
await payload
.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '1',
text: '2',
uniqueRequiredText: '3',
uniqueHasManyPolymorphicRelationship: [
{ relationTo: 'text-fields', value: textDoc.id },
],
},
})
.then((doc) =>
payload.update({
locale: 'es',
collection: 'indexed-fields',
data: { localizedUniqueRequiredText: '100' },
id: doc.id,
}),
)
// Should allow the same relationship on a diferrent field!
await payload
.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '31',
text: '24',
uniqueRequiredText: '55',
uniqueHasManyPolymorphicRelationship_2: [
{ relationTo: 'text-fields', value: textDoc.id },
],
},
})
// Skip mongodb uniuqe error because it threats localizedUniqueRequriedText.es as undefined
.then((doc) =>
payload.update({
locale: 'es',
collection: 'indexed-fields',
data: { localizedUniqueRequiredText: '300' },
id: doc.id,
}),
)
await expect(
payload.create({
collection: 'indexed-fields',
data: {
localizedUniqueRequiredText: '4',
text: '5',
uniqueRequiredText: '10',
uniqueHasManyPolymorphicRelationship: [
{ relationTo: 'text-fields', value: textDoc.id },
],
},
}),
).rejects.toBeTruthy()
})
it('should not throw validation error saving multiple null values for unique fields', async () => { it('should not throw validation error saving multiple null values for unique fields', async () => {
const data = { const data = {
localizedUniqueRequiredText: 'en1', localizedUniqueRequiredText: 'en1',

View File

@@ -1020,6 +1020,29 @@ export interface IndexedField {
id: string; id: string;
text: string; text: string;
uniqueText?: string | null; uniqueText?: string | null;
uniqueRelationship?: (string | null) | TextField;
uniqueHasManyRelationship?: (string | TextField)[] | null;
uniqueHasManyRelationship_2?: (string | TextField)[] | null;
uniquePolymorphicRelationship?: {
relationTo: 'text-fields';
value: string | TextField;
} | null;
uniquePolymorphicRelationship_2?: {
relationTo: 'text-fields';
value: string | TextField;
} | null;
uniqueHasManyPolymorphicRelationship?:
| {
relationTo: 'text-fields';
value: string | TextField;
}[]
| null;
uniqueHasManyPolymorphicRelationship_2?:
| {
relationTo: 'text-fields';
value: string | TextField;
}[]
| null;
uniqueRequiredText: string; uniqueRequiredText: string;
localizedUniqueRequiredText: string; localizedUniqueRequiredText: string;
/** /**