Skip to content

Commit 3e8191c

Browse files
fix(drizzle): dedupe reused localized block tables (#17430)
Fixes duplicate `_N` block tables when identical `blockReferences` are reused under localized block paths. Fixes #17424. 1. `traverseFields` validated existing block tables with the immediate `field.localized` flag, which dropped localization inherited from ancestor blocks and arrays. 2. `validateExistingBlockIsIdentical` never received `parentIsLocalized` from this call site, so nested block fields were flattened with incomplete localization context. The fix passes the accumulated localization context into `validateExistingBlockIsIdentical` and reuses the same localized-table calculation that schema construction already uses when deciding whether an existing block table matches. Adds a regression spec that builds two localized collections reusing the same nested `headline` block reference and asserts Payload does not create suffixed duplicate tables like `pages_blocks_headline_2` or `posts_blocks_headline_2`. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1216743516361216
1 parent fc2572e commit 3e8191c

3 files changed

Lines changed: 118 additions & 11 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { Block, Config, SanitizedConfig } from 'payload'
2+
import { sanitizeConfig } from 'payload'
3+
import { describe, expect, it } from 'vitest'
4+
5+
import { setColumnID } from '../postgres/schema/setColumnID.js'
6+
import type { DrizzleAdapter } from '../types.js'
7+
import { buildRawSchema } from './buildRawSchema.js'
8+
9+
const headlineBlock: Block = {
10+
slug: 'headline',
11+
fields: [
12+
{
13+
name: 'title',
14+
type: 'text',
15+
},
16+
],
17+
}
18+
19+
const createContainerBlock = (slug: string): Block => ({
20+
slug,
21+
fields: [
22+
{
23+
name: 'content',
24+
type: 'blocks',
25+
blockReferences: ['headline'],
26+
blocks: [],
27+
},
28+
],
29+
})
30+
31+
const createAdapter = (config: SanitizedConfig): DrizzleAdapter =>
32+
({
33+
blocksAsJSON: false,
34+
fieldConstraints: {},
35+
idType: 'serial',
36+
localesSuffix: '_locales',
37+
payload: {
38+
blocks: Object.fromEntries(config.blocks.map((block) => [block.slug, block])),
39+
collections: Object.fromEntries(
40+
config.collections.map((collection) => [
41+
collection.slug,
42+
{
43+
config: collection,
44+
customIDType: undefined,
45+
},
46+
]),
47+
),
48+
config,
49+
},
50+
rawRelations: {},
51+
rawTables: {},
52+
tableNameMap: new Map(),
53+
versionsSuffix: '_versions',
54+
}) as unknown as DrizzleAdapter
55+
56+
describe('buildRawSchema', () => {
57+
it('should not create duplicate suffixed block tables for identical reused blocks under localized ancestors', async () => {
58+
const layoutBlocks = [createContainerBlock('container'), createContainerBlock('container50')]
59+
60+
const config = await sanitizeConfig({
61+
blocks: [headlineBlock],
62+
collections: [
63+
{
64+
slug: 'pages',
65+
fields: [
66+
{
67+
name: 'layout',
68+
type: 'blocks',
69+
localized: true,
70+
blocks: layoutBlocks,
71+
},
72+
],
73+
timestamps: false,
74+
},
75+
{
76+
slug: 'posts',
77+
fields: [
78+
{
79+
name: 'layout',
80+
type: 'blocks',
81+
localized: true,
82+
blocks: layoutBlocks,
83+
},
84+
],
85+
timestamps: false,
86+
},
87+
],
88+
localization: {
89+
defaultLocale: 'en',
90+
locales: ['en', 'de'],
91+
},
92+
} as Config)
93+
94+
const adapter = createAdapter(config)
95+
96+
buildRawSchema({
97+
adapter,
98+
setColumnID,
99+
})
100+
101+
expect(adapter.rawTables.pages_blocks_headline).toBeDefined()
102+
expect(adapter.rawTables.pages_blocks_headline.columns._locale).toBeDefined()
103+
expect(adapter.rawTables.pages_blocks_headline_2).toBeUndefined()
104+
expect(adapter.rawTables.pages_blocks_headline_2_locales).toBeUndefined()
105+
expect(adapter.rawTables.posts_blocks_headline).toBeDefined()
106+
expect(adapter.rawTables.posts_blocks_headline.columns._locale).toBeDefined()
107+
expect(adapter.rawTables.posts_blocks_headline_2).toBeUndefined()
108+
expect(adapter.rawTables.posts_blocks_headline_2_locales).toBeUndefined()
109+
})
110+
})

packages/drizzle/src/schema/traverseFields.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -395,15 +395,18 @@ export const traverseFields = ({
395395
throwValidationError,
396396
versionsCustomName: versions,
397397
})
398-
398+
const isLocalizedBlockTable =
399+
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
400+
withinLocalizedArrayOrBlock ||
401+
forceLocalized
399402
if (typeof blocksTableNameMap[blockTableName] === 'undefined') {
400403
blocksTableNameMap[blockTableName] = 1
401404
} else if (
402405
!adapter.rawTables[blockTableName] ||
403406
!validateExistingBlockIsIdentical({
404407
block,
405-
localized: field.localized,
406-
rootTableName,
408+
localized: isLocalizedBlockTable,
409+
parentIsLocalized,
407410
table: adapter.rawTables[blockTableName],
408411
tableLocales: adapter.rawTables[`${blockTableName}${adapter.localesSuffix}`],
409412
})
@@ -465,12 +468,7 @@ export const traverseFields = ({
465468
},
466469
}
467470

468-
const isLocalized =
469-
Boolean(isFieldLocalized && adapter.payload.config.localization) ||
470-
withinLocalizedArrayOrBlock ||
471-
forceLocalized
472-
473-
if (isLocalized) {
471+
if (isLocalizedBlockTable) {
474472
baseColumns._locale = {
475473
name: '_locale',
476474
type: 'enum',
@@ -510,7 +508,7 @@ export const traverseFields = ({
510508
setColumnID,
511509
tableName: blockTableName,
512510
versions,
513-
withinLocalizedArrayOrBlock: isLocalized,
511+
withinLocalizedArrayOrBlock: isLocalizedBlockTable,
514512
})
515513

516514
if (subHasLocalizedManyNumberField) {

packages/drizzle/src/utilities/validateExistingBlockIsIdentical.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ type Args = {
1616
* @todo make required in v4.0. Usually you'd wanna pass this in
1717
*/
1818
parentIsLocalized?: boolean
19-
rootTableName: string
2019
table: RawTable
2120
tableLocales?: RawTable
2221
}

0 commit comments

Comments
 (0)