Merge branch 'main' of github.com:payloadcms/payload into fix/empty-in-array

This commit is contained in:
James
2023-10-15 17:29:51 -04:00
7 changed files with 190 additions and 31 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/db-postgres", "name": "@payloadcms/db-postgres",
"version": "0.1.6", "version": "0.1.7",
"description": "The officially supported Postgres database adapter for Payload", "description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload", "repository": "https://github.com/payloadcms/payload",
"license": "MIT", "license": "MIT",

View File

@@ -34,12 +34,14 @@ type Args = {
aliasTable?: GenericTable aliasTable?: GenericTable
collectionPath: string collectionPath: string
columnPrefix?: string columnPrefix?: string
constraintPath?: string
constraints?: Constraint[] constraints?: Constraint[]
fields: (Field | TabAsField)[] fields: (Field | TabAsField)[]
joinAliases: BuildQueryJoinAliases joinAliases: BuildQueryJoinAliases
joins: BuildQueryJoins joins: BuildQueryJoins
locale?: string locale?: string
pathSegments: string[] pathSegments: string[]
rootTableName?: string
selectFields: Record<string, GenericColumn> selectFields: Record<string, GenericColumn>
tableName: string tableName: string
} }
@@ -53,17 +55,22 @@ export const getTableColumnFromPath = ({
aliasTable, aliasTable,
collectionPath, collectionPath,
columnPrefix = '', columnPrefix = '',
constraintPath: incomingConstraintPath,
constraints = [], constraints = [],
fields, fields,
joinAliases, joinAliases,
joins, joins,
locale: incomingLocale, locale: incomingLocale,
pathSegments: incomingSegments, pathSegments: incomingSegments,
rootTableName: incomingRootTableName,
selectFields, selectFields,
tableName, tableName,
}: Args): TableColumn => { }: Args): TableColumn => {
const fieldPath = incomingSegments[0] const fieldPath = incomingSegments[0]
let locale = incomingLocale let locale = incomingLocale
const rootTableName = incomingRootTableName || tableName
let constraintPath = incomingConstraintPath || ''
const field = flattenTopLevelFields(fields as Field[]).find( const field = flattenTopLevelFields(fields as Field[]).find(
(fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath, (fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath,
) as Field | TabAsField ) as Field | TabAsField
@@ -105,6 +112,7 @@ export const getTableColumnFromPath = ({
aliasTable, aliasTable,
collectionPath, collectionPath,
columnPrefix, columnPrefix,
constraintPath,
constraints, constraints,
fields: field.tabs.map((tab) => ({ fields: field.tabs.map((tab) => ({
...tab, ...tab,
@@ -114,6 +122,7 @@ export const getTableColumnFromPath = ({
joins, joins,
locale, locale,
pathSegments: pathSegments.slice(1), pathSegments: pathSegments.slice(1),
rootTableName,
selectFields, selectFields,
tableName: newTableName, tableName: newTableName,
}) })
@@ -125,12 +134,14 @@ export const getTableColumnFromPath = ({
aliasTable, aliasTable,
collectionPath, collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`, columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints, constraints,
fields: field.fields, fields: field.fields,
joinAliases, joinAliases,
joins, joins,
locale, locale,
pathSegments: pathSegments.slice(1), pathSegments: pathSegments.slice(1),
rootTableName,
selectFields, selectFields,
tableName: newTableName, tableName: newTableName,
}) })
@@ -140,12 +151,14 @@ export const getTableColumnFromPath = ({
aliasTable, aliasTable,
collectionPath, collectionPath,
columnPrefix, columnPrefix,
constraintPath,
constraints, constraints,
fields: field.fields, fields: field.fields,
joinAliases, joinAliases,
joins, joins,
locale, locale,
pathSegments: pathSegments.slice(1), pathSegments: pathSegments.slice(1),
rootTableName,
selectFields, selectFields,
tableName: newTableName, tableName: newTableName,
}) })
@@ -172,12 +185,14 @@ export const getTableColumnFromPath = ({
aliasTable, aliasTable,
collectionPath, collectionPath,
columnPrefix: `${columnPrefix}${field.name}_`, columnPrefix: `${columnPrefix}${field.name}_`,
constraintPath,
constraints, constraints,
fields: field.fields, fields: field.fields,
joinAliases, joinAliases,
joins, joins,
locale, locale,
pathSegments: pathSegments.slice(1), pathSegments: pathSegments.slice(1),
rootTableName,
selectFields, selectFields,
tableName: newTableName, tableName: newTableName,
}) })
@@ -185,6 +200,7 @@ export const getTableColumnFromPath = ({
case 'array': { case 'array': {
newTableName = `${tableName}_${toSnakeCase(field.name)}` newTableName = `${tableName}_${toSnakeCase(field.name)}`
constraintPath = `${constraintPath}${field.name}.%.`
if (locale && field.localized && adapter.payload.config.localization) { if (locale && field.localized && adapter.payload.config.localization) {
joins[newTableName] = and( joins[newTableName] = and(
eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID), eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID),
@@ -206,12 +222,14 @@ export const getTableColumnFromPath = ({
return getTableColumnFromPath({ return getTableColumnFromPath({
adapter, adapter,
collectionPath, collectionPath,
constraintPath,
constraints, constraints,
fields: field.fields, fields: field.fields,
joinAliases, joinAliases,
joins, joins,
locale, locale,
pathSegments: pathSegments.slice(1), pathSegments: pathSegments.slice(1),
rootTableName,
selectFields, selectFields,
tableName: newTableName, tableName: newTableName,
}) })
@@ -229,12 +247,14 @@ export const getTableColumnFromPath = ({
result = getTableColumnFromPath({ result = getTableColumnFromPath({
adapter, adapter,
collectionPath, collectionPath,
constraintPath: '',
constraints: blockConstraints, constraints: blockConstraints,
fields: block.fields, fields: block.fields,
joinAliases, joinAliases,
joins, joins,
locale, locale,
pathSegments: pathSegments.slice(1), pathSegments: pathSegments.slice(1),
rootTableName,
selectFields: blockSelectFields, selectFields: blockSelectFields,
tableName: newTableName, tableName: newTableName,
}) })
@@ -283,9 +303,8 @@ export const getTableColumnFromPath = ({
case 'relationship': case 'relationship':
case 'upload': { case 'upload': {
let relationshipFields let relationshipFields
const relationTableName = `${tableName}_rels` const relationTableName = `${rootTableName}_rels`
const newCollectionPath = pathSegments.slice(1).join('.') const newCollectionPath = pathSegments.slice(1).join('.')
const aliasRelationshipTableName = uuid() const aliasRelationshipTableName = uuid()
const aliasRelationshipTable = alias( const aliasRelationshipTable = alias(
adapter.tables[relationTableName], adapter.tables[relationTableName],
@@ -295,7 +314,7 @@ export const getTableColumnFromPath = ({
// Join in the relationships table // Join in the relationships table
joinAliases.push({ joinAliases.push({
condition: eq( condition: eq(
(aliasTable || adapter.tables[tableName]).id, (aliasTable || adapter.tables[rootTableName]).id,
aliasRelationshipTable.parent, aliasRelationshipTable.parent,
), ),
table: aliasRelationshipTable, table: aliasRelationshipTable,
@@ -306,7 +325,7 @@ export const getTableColumnFromPath = ({
constraints.push({ constraints.push({
columnName: 'path', columnName: 'path',
table: aliasRelationshipTable, table: aliasRelationshipTable,
value: field.name, value: `${constraintPath}${field.name}`,
}) })
let newAliasTable let newAliasTable
@@ -368,6 +387,7 @@ export const getTableColumnFromPath = ({
joins, joins,
locale, locale,
pathSegments: pathSegments.slice(1), pathSegments: pathSegments.slice(1),
rootTableName: newTableName,
selectFields, selectFields,
tableName: newTableName, tableName: newTableName,
}) })

View File

@@ -100,7 +100,11 @@ export async function parseParams({
const val = where[relationOrPath][operator] const val = where[relationOrPath][operator]
queryConstraints.forEach(({ columnName: col, table: constraintTable, value }) => { queryConstraints.forEach(({ columnName: col, table: constraintTable, value }) => {
constraints.push(operatorMap.equals(constraintTable[col], value)) if (typeof value === 'string' && value.indexOf('%') > -1) {
constraints.push(operatorMap.like(constraintTable[col], value))
} else {
constraints.push(operatorMap.equals(constraintTable[col], value))
}
}) })
if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) { if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) {

View File

@@ -3,63 +3,81 @@ import type { CollectionConfig } from '../../../../packages/payload/src/collecti
export const relationshipFieldsSlug = 'relationship-fields' export const relationshipFieldsSlug = 'relationship-fields'
const RelationshipFields: CollectionConfig = { const RelationshipFields: CollectionConfig = {
slug: relationshipFieldsSlug,
fields: [ fields: [
{
name: 'text',
type: 'text',
},
{ {
name: 'relationship', name: 'relationship',
type: 'relationship',
relationTo: ['text-fields', 'array-fields'], relationTo: ['text-fields', 'array-fields'],
required: true, required: true,
type: 'relationship',
}, },
{ {
name: 'relationToSelf', name: 'relationToSelf',
type: 'relationship',
relationTo: relationshipFieldsSlug, relationTo: relationshipFieldsSlug,
type: 'relationship',
}, },
{ {
name: 'relationToSelfSelectOnly', name: 'relationToSelfSelectOnly',
type: 'relationship',
relationTo: relationshipFieldsSlug,
admin: { admin: {
allowCreate: false, allowCreate: false,
}, },
relationTo: relationshipFieldsSlug,
type: 'relationship',
}, },
{ {
name: 'relationWithDynamicDefault', name: 'relationWithDynamicDefault',
type: 'relationship', defaultValue: ({ user }) => user?.id,
relationTo: 'users', relationTo: 'users',
defaultValue: ({ user }) => user.id, type: 'relationship',
}, },
{ {
name: 'relationHasManyWithDynamicDefault', name: 'relationHasManyWithDynamicDefault',
type: 'relationship', defaultValue: ({ user }) =>
user
? {
relationTo: 'users',
value: user.id,
}
: undefined,
relationTo: ['users'], relationTo: ['users'],
defaultValue: ({ user }) => ({ type: 'relationship',
relationTo: 'users',
value: user.id,
}),
}, },
{ {
name: 'relationshipWithMin', name: 'relationshipWithMin',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true, hasMany: true,
minRows: 2, minRows: 2,
relationTo: 'text-fields',
type: 'relationship',
}, },
{ {
name: 'relationshipWithMax', name: 'relationshipWithMax',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true, hasMany: true,
maxRows: 2, maxRows: 2,
relationTo: 'text-fields',
type: 'relationship',
}, },
{ {
name: 'relationshipHasMany', name: 'relationshipHasMany',
type: 'relationship',
relationTo: 'text-fields',
hasMany: true, hasMany: true,
relationTo: 'text-fields',
type: 'relationship',
},
{
name: 'array',
fields: [
{
name: 'relationship',
relationTo: 'text-fields',
type: 'relationship',
},
],
type: 'array',
}, },
], ],
slug: relationshipFieldsSlug,
} }
export default RelationshipFields export default RelationshipFields

View File

@@ -44,18 +44,18 @@ export default buildConfigWithDefaults({
collections: [ collections: [
LexicalFields, LexicalFields,
{ {
slug: 'users',
auth: true,
admin: { admin: {
useAsTitle: 'email', useAsTitle: 'email',
}, },
auth: true,
fields: [ fields: [
{ {
name: 'canViewConditionalField', name: 'canViewConditionalField',
type: 'checkbox',
defaultValue: true, defaultValue: true,
type: 'checkbox',
}, },
], ],
slug: 'users',
}, },
ArrayFields, ArrayFields,
BlockFields, BlockFields,
@@ -81,8 +81,8 @@ export default buildConfigWithDefaults({
], ],
localization: { localization: {
defaultLocale: 'en', defaultLocale: 'en',
locales: ['en', 'es'],
fallback: true, fallback: true,
locales: ['en', 'es'],
}, },
onInit: async (payload) => { onInit: async (payload) => {
await payload.create({ await payload.create({

View File

@@ -1093,7 +1093,7 @@ describe('fields', () => {
.locator('#field-relationship .relationship-add-new__relation-button--text-fields') .locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click() .click()
const textField = page.locator('#field-text') const textField = page.locator('.drawer__content #field-text')
const textValue = 'hello' const textValue = 'hello'
await textField.fill(textValue) await textField.fill(textValue)
@@ -1217,7 +1217,7 @@ describe('fields', () => {
.locator('#field-relationship .relationship-add-new__relation-button--text-fields') .locator('#field-relationship .relationship-add-new__relation-button--text-fields')
.click() .click()
await page.locator('#field-text').fill('something') await page.locator('.drawer__content #field-text').fill('something')
await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click() await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click()
await expect(page.locator('.Toastify')).toContainText('successfully') await expect(page.locator('.Toastify')).toContainText('successfully')
@@ -1290,7 +1290,7 @@ describe('fields', () => {
await page.getByRole('button', { name: 'Edit Seeded text document' }).click() await page.getByRole('button', { name: 'Edit Seeded text document' }).click()
// Fill 'text' field of 'Seeded text document' // Fill 'text' field of 'Seeded text document'
await page.locator('#field-text').fill('some updated text value') await page.locator('.drawer__content #field-text').fill('some updated text value')
// Save drawer (not parent page) with hotkey // Save drawer (not parent page) with hotkey
await saveDocHotkeyAndAssert(page) await saveDocHotkeyAndAssert(page)

View File

@@ -21,6 +21,7 @@ import {
} from './collections/Group' } from './collections/Group'
import { defaultNumber, numberDoc } from './collections/Number' import { defaultNumber, numberDoc } from './collections/Number'
import { pointDoc } from './collections/Point' import { pointDoc } from './collections/Point'
import { relationshipFieldsSlug } from './collections/Relationship'
import { tabsDoc } from './collections/Tabs' import { tabsDoc } from './collections/Tabs'
import { import {
localizedTextValue, localizedTextValue,
@@ -73,6 +74,122 @@ describe('Fields', () => {
}) })
}) })
describe('relationship', () => {
let textDoc
let otherTextDoc
let selfReferencing
let parent
let child
let grandChild
let relationshipInArray
const textDocText = 'text document'
const otherTextDocText = 'alt text'
const relationshipText = 'relationship text'
beforeAll(async () => {
textDoc = await payload.create({
collection: 'text-fields',
data: {
text: textDocText,
},
})
otherTextDoc = await payload.create({
collection: 'text-fields',
data: {
text: otherTextDocText,
},
})
const relationship = { relationTo: 'text-fields', value: textDoc.id }
parent = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationship,
text: relationshipText,
},
})
child = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationToSelf: parent.id,
relationship,
text: relationshipText,
},
})
grandChild = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationToSelf: child.id,
relationship,
text: relationshipText,
},
})
selfReferencing = await payload.create({
collection: relationshipFieldsSlug,
data: {
relationship,
text: relationshipText,
},
})
relationshipInArray = await payload.create({
collection: relationshipFieldsSlug,
data: {
array: [
{
relationship: otherTextDoc.id,
},
],
relationship,
},
})
})
it('should query parent self-reference', async () => {
const childResult = await payload.find({
collection: relationshipFieldsSlug,
where: {
relationToSelf: { equals: parent.id },
},
})
const grandChildResult = await payload.find({
collection: relationshipFieldsSlug,
where: {
relationToSelf: { equals: child.id },
},
})
const anyChildren = await payload.find({
collection: relationshipFieldsSlug,
})
const allChildren = await payload.find({
collection: relationshipFieldsSlug,
where: {
'relationToSelf.text': { equals: relationshipText },
},
})
expect(childResult.docs[0].id).toStrictEqual(child.id)
expect(grandChildResult.docs[0].id).toStrictEqual(grandChild.id)
expect(allChildren.docs).toHaveLength(2)
})
it('should query relationship inside array', async () => {
const result = await payload.find({
collection: relationshipFieldsSlug,
where: {
'array.relationship.text': { equals: otherTextDocText },
},
})
expect(result.docs).toHaveLength(1)
expect(result.docs[0]).toMatchObject(relationshipInArray)
})
})
describe('timestamps', () => { describe('timestamps', () => {
const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10) const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10)
let doc let doc