fix(db-postgres): long array field table aliases cause error even when dbName is used (#11995)

Fixes https://github.com/payloadcms/payload/issues/11975

Previously, this configuration was causing errors in postgres due to
long names, even though `dbName` is used:
```
{
  slug: 'aliases',
  fields: [
    {
      name: 'thisIsALongFieldNameThatWillCauseAPostgresErrorEvenThoughWeSetAShorterDBName',
      dbName: 'shortname',
      type: 'array',
      fields: [
        {
          name: 'nested_field_1',
          type: 'array',
          dbName: 'short_nested_1',
          fields: [],
        },
        {
          name: 'nested_field_2',
          type: 'text',
        },
      ],
    },
  ],
},
```

This is because we were generating Drizzle relation name (for arrays)
always based on the field path and internally, drizzle uses this name
for aliasing. Now, if `dbName` is present, we use `_{dbName}` instead
for the relation name.
This commit is contained in:
Sasha
2025-04-07 23:12:43 +03:00
committed by GitHub
parent b270901fa6
commit 09782be0e0
10 changed files with 144 additions and 3 deletions

View File

@@ -72,6 +72,7 @@ export const deleteOne: DeleteOne = async function deleteOne(
data: docToDelete,
fields: collection.flattenedFields,
joinQuery: false,
tableName,
})
await this.deleteWhere({

View File

@@ -158,6 +158,7 @@ export const findMany = async function find({
data,
fields,
joinQuery,
tableName,
})
})

View File

@@ -196,7 +196,8 @@ export const traverseFields = ({
}
}
currentArgs.with[`${path}${field.name}`] = withArray
const relationName = field.dbName ? `_${arrayTableName}` : `${path}${field.name}`
currentArgs.with[relationName] = withArray
traverseFields({
_locales: withArray.with._locales,

View File

@@ -2,6 +2,7 @@ import type { CompoundIndex, FlattenedField } from 'payload'
import { InvalidConfiguration } from 'payload'
import {
array,
fieldAffectsData,
fieldIsVirtual,
fieldShouldBeLocalized,
@@ -287,7 +288,9 @@ export const traverseFields = ({
}
}
relationsToBuild.set(fieldName, {
const relationName = field.dbName ? `_${arrayTableName}` : fieldName
relationsToBuild.set(relationName, {
type: 'many',
// arrays have their own localized table, independent of the base table.
localized: false,
@@ -304,7 +307,7 @@ export const traverseFields = ({
},
],
references: ['id'],
relationName: fieldName,
relationName,
to: parentTableName,
},
}

View File

@@ -15,6 +15,7 @@ type TransformArgs = {
joinQuery?: JoinQuery
locale?: string
parentIsLocalized?: boolean
tableName: string
}
// This is the entry point to transform Drizzle output data
@@ -26,6 +27,7 @@ export const transform = <T extends Record<string, unknown> | TypeWithID>({
fields,
joinQuery,
parentIsLocalized,
tableName,
}: TransformArgs): T => {
let relationships: Record<string, Record<string, unknown>[]> = {}
let texts: Record<string, Record<string, unknown>[]> = {}
@@ -53,6 +55,7 @@ export const transform = <T extends Record<string, unknown> | TypeWithID>({
adapter,
blocks,
config,
currentTableName: tableName,
dataRef: {
id: data.id,
},
@@ -65,7 +68,9 @@ export const transform = <T extends Record<string, unknown> | TypeWithID>({
path: '',
relationships,
table: data,
tablePath: '',
texts,
topLevelTableName: tableName,
})
deletions.forEach((deletion) => deletion())

View File

@@ -1,6 +1,7 @@
import type { FlattenedBlock, FlattenedField, JoinQuery, SanitizedConfig } from 'payload'
import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
import toSnakeCase from 'to-snake-case'
import type { DrizzleAdapter } from '../../types.js'
import type { BlocksMap } from '../../utilities/createBlocksMap.js'
@@ -22,6 +23,7 @@ type TraverseFieldsArgs = {
* The full Payload config
*/
config: SanitizedConfig
currentTableName: string
/**
* The data reference to be mutated within this recursive function
*/
@@ -59,10 +61,12 @@ type TraverseFieldsArgs = {
* Data structure representing the nearest table from db
*/
table: Record<string, unknown>
tablePath: string
/**
* All hasMany text fields, as returned by Drizzle, keyed on an object by field path
*/
texts: Record<string, Record<string, unknown>[]>
topLevelTableName: string
/**
* Set to a locale if this group of fields is within a localized array or block.
*/
@@ -75,6 +79,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
adapter,
blocks,
config,
currentTableName,
dataRef,
deletions,
fieldPrefix,
@@ -85,7 +90,9 @@ export const traverseFields = <T extends Record<string, unknown>>({
path,
relationships,
table,
tablePath,
texts,
topLevelTableName,
withinArrayOrBlockLocale,
}: TraverseFieldsArgs): T => {
const sanitizedPath = path ? `${path}.` : path
@@ -110,6 +117,14 @@ export const traverseFields = <T extends Record<string, unknown>>({
const isLocalized = fieldShouldBeLocalized({ field, parentIsLocalized })
if (field.type === 'array') {
const arrayTableName = adapter.tableNameMap.get(
`${currentTableName}_${tablePath}${toSnakeCase(field.name)}`,
)
if (field.dbName) {
fieldData = table[`_${arrayTableName}`]
}
if (Array.isArray(fieldData)) {
if (isLocalized) {
result[field.name] = fieldData.reduce((arrayResult, row) => {
@@ -129,6 +144,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
adapter,
blocks,
config,
currentTableName: arrayTableName,
dataRef: data,
deletions,
fieldPrefix: '',
@@ -138,7 +154,9 @@ export const traverseFields = <T extends Record<string, unknown>>({
path: `${sanitizedPath}${field.name}.${row._order - 1}`,
relationships,
table: row,
tablePath: '',
texts,
topLevelTableName,
withinArrayOrBlockLocale: locale,
})
@@ -175,6 +193,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
adapter,
blocks,
config,
currentTableName: arrayTableName,
dataRef: row,
deletions,
fieldPrefix: '',
@@ -184,7 +203,9 @@ export const traverseFields = <T extends Record<string, unknown>>({
path: `${sanitizedPath}${field.name}.${i}`,
relationships,
table: row,
tablePath: '',
texts,
topLevelTableName,
withinArrayOrBlockLocale,
}),
)
@@ -228,11 +249,16 @@ export const traverseFields = <T extends Record<string, unknown>>({
(block) => typeof block !== 'string' && block.slug === row.blockType,
) as FlattenedBlock | undefined)
const tableName = adapter.tableNameMap.get(
`${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`,
)
if (block) {
const blockResult = traverseFields<T>({
adapter,
blocks,
config,
currentTableName: tableName,
dataRef: row,
deletions,
fieldPrefix: '',
@@ -242,7 +268,9 @@ export const traverseFields = <T extends Record<string, unknown>>({
path: `${blockFieldPath}.${row._order - 1}`,
relationships,
table: row,
tablePath: '',
texts,
topLevelTableName,
withinArrayOrBlockLocale: locale,
})
@@ -300,11 +328,16 @@ export const traverseFields = <T extends Record<string, unknown>>({
delete row._index
}
const tableName = adapter.tableNameMap.get(
`${topLevelTableName}_blocks_${toSnakeCase(block.slug)}`,
)
acc.push(
traverseFields<T>({
adapter,
blocks,
config,
currentTableName: tableName,
dataRef: row,
deletions,
fieldPrefix: '',
@@ -314,7 +347,9 @@ export const traverseFields = <T extends Record<string, unknown>>({
path: `${blockFieldPath}.${i}`,
relationships,
table: row,
tablePath: '',
texts,
topLevelTableName,
withinArrayOrBlockLocale,
}),
)
@@ -614,6 +649,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
adapter,
blocks,
config,
currentTableName,
dataRef: groupData as Record<string, unknown>,
deletions,
fieldPrefix: groupFieldPrefix,
@@ -624,7 +660,9 @@ export const traverseFields = <T extends Record<string, unknown>>({
path: `${sanitizedPath}${field.name}`,
relationships,
table,
tablePath: `${tablePath}${toSnakeCase(field.name)}_`,
texts,
topLevelTableName,
withinArrayOrBlockLocale: locale || withinArrayOrBlockLocale,
})

View File

@@ -467,6 +467,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
data: doc,
fields,
joinQuery: false,
tableName,
})
return result

View File

@@ -604,6 +604,29 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'aliases',
fields: [
{
name: 'thisIsALongFieldNameThatCanCauseAPostgresErrorEvenThoughWeSetAShorterDBName',
dbName: 'shortname',
type: 'array',
fields: [
{
name: 'nestedArray',
type: 'array',
dbName: 'short_nested_1',
fields: [
{
type: 'text',
name: 'text',
},
],
},
],
},
],
},
],
globals: [
{

View File

@@ -21,6 +21,7 @@ import {
killTransaction,
QueryError,
} from 'payload'
import { assert } from 'ts-essentials'
import { fileURLToPath } from 'url'
import type { Global2 } from './payload-types.js'
@@ -783,6 +784,28 @@ describe('database', () => {
expect(doc.blocks[0].text).toStrictEqual('hello')
expect(doc.blocks[0].localizedText).toStrictEqual('goodbye')
})
it('arrays should work with both long field names and dbName', async () => {
const { id } = await payload.create({
collection: 'aliases',
data: {
thisIsALongFieldNameThatCanCauseAPostgresErrorEvenThoughWeSetAShorterDBName: [
{
nestedArray: [{ text: 'some-text' }],
},
],
},
})
const res = await payload.findByID({ collection: 'aliases', id })
expect(
res.thisIsALongFieldNameThatCanCauseAPostgresErrorEvenThoughWeSetAShorterDBName,
).toHaveLength(1)
const item =
res.thisIsALongFieldNameThatCanCauseAPostgresErrorEvenThoughWeSetAShorterDBName?.[0]
assert(item)
expect(item.nestedArray).toHaveLength(1)
expect(item.nestedArray?.[0]?.text).toBe('some-text')
})
})
describe('transactions', () => {

View File

@@ -80,6 +80,7 @@ export interface Config {
'fake-custom-ids': FakeCustomId;
'relationships-migration': RelationshipsMigration;
'compound-indexes': CompoundIndex;
aliases: Alias;
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
@@ -100,6 +101,7 @@ export interface Config {
'fake-custom-ids': FakeCustomIdsSelect<false> | FakeCustomIdsSelect<true>;
'relationships-migration': RelationshipsMigrationSelect<false> | RelationshipsMigrationSelect<true>;
'compound-indexes': CompoundIndexesSelect<false> | CompoundIndexesSelect<true>;
aliases: AliasesSelect<false> | AliasesSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
@@ -419,6 +421,26 @@ export interface CompoundIndex {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "aliases".
*/
export interface Alias {
id: string;
thisIsALongFieldNameThatCanCauseAPostgresErrorEvenThoughWeSetAShorterDBName?:
| {
nestedArray?:
| {
text?: string | null;
id?: string | null;
}[]
| null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
@@ -495,6 +517,10 @@ export interface PayloadLockedDocument {
relationTo: 'compound-indexes';
value: string | CompoundIndex;
} | null)
| ({
relationTo: 'aliases';
value: string | Alias;
} | null)
| ({
relationTo: 'users';
value: string | User;
@@ -795,6 +821,25 @@ export interface CompoundIndexesSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "aliases_select".
*/
export interface AliasesSelect<T extends boolean = true> {
thisIsALongFieldNameThatCanCauseAPostgresErrorEvenThoughWeSetAShorterDBName?:
| T
| {
nestedArray?:
| T
| {
text?: T;
id?: T;
};
id?: T;
};
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".