fix(drizzle): enforce uniqueness on index names (#8754)

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

Previously, trying to define a config like this:
```ts
{
  type: 'text',
  name: 'someText',
  index: true,
},
{
  type: 'array',
  name: 'some',
  index: true,
  fields: [
    {
      type: 'text',
      name: 'text',
      index: true,
    },
  ],
}
```

Lead to the error:
```
Warning  We've found duplicated index name across public schema. Please rename your index in either the demonstration table or the table with the duplicated index name
```

Now, if we encounter duplicates, we increment the name like this:
`collection_some_text_idx`
`collection_some_text_1_idx`

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
Sasha
2024-10-17 05:05:27 +03:00
committed by GitHub
parent 872b205acc
commit 90bca15f52
14 changed files with 102 additions and 31 deletions

View File

@@ -136,6 +136,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
findGlobalVersions,
findOne,
findVersions,
indexes: new Set<string>(),
init,
insert,
migrate,

View File

@@ -133,6 +133,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
findGlobalVersions,
findOne,
findVersions,
indexes: new Set<string>(),
init,
insert,
migrate,

View File

@@ -1,3 +1,4 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Relation } from 'drizzle-orm'
import type {
AnySQLiteColumn,
@@ -9,7 +10,7 @@ import type {
} from 'drizzle-orm/sqlite-core'
import type { Field, SanitizedJoins } from 'payload'
import { createTableName } from '@payloadcms/drizzle'
import { buildIndexName, createTableName } from '@payloadcms/drizzle'
import { relations, sql } from 'drizzle-orm'
import {
foreignKey,
@@ -416,21 +417,25 @@ export const buildTable = ({
foreignColumns: [adapter.tables[formattedRelationTo].id],
}).onDelete('cascade')
const indexName = [colName]
const indexColumns = [colName]
const unique = !disableUnique && uniqueRelationships.has(relationTo)
if (unique) {
indexName.push('path')
indexColumns.push('path')
}
if (hasLocalizedRelationshipField) {
indexName.push('locale')
indexColumns.push('locale')
}
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
name: indexName,
columnName: `${formattedRelationTo}_id`,
tableName: relationshipsTableName,
const indexName = buildIndexName({
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
adapter: adapter as unknown as DrizzleAdapter,
})
relationExtraConfig[indexName] = createIndex({
name: indexColumns,
indexName,
unique,
})
})

View File

@@ -3,13 +3,12 @@ import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
type CreateIndexArgs = {
columnName: string
indexName: string
name: string | string[]
tableName: string
unique?: boolean
}
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: AnySQLiteColumn }) => {
let columns
if (Array.isArray(name)) {
@@ -21,8 +20,8 @@ export const createIndex = ({ name, columnName, tableName, unique }: CreateIndex
columns = [table[name]]
}
if (unique) {
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
}
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return index(indexName).on(columns[0], ...columns.slice(1))
}
}

View File

@@ -1,8 +1,10 @@
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, SQLiteColumnBuilder } from 'drizzle-orm/sqlite-core'
import type { Field, SanitizedJoins, TabAsField } from 'payload'
import {
buildIndexName,
createTableName,
hasLocalesTable,
validateExistingBlockIsIdentical,
@@ -164,10 +166,15 @@ export const traverseFields = ({
}
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
}
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
const indexName = buildIndexName({
name: `${newTableName}_${columnName}`,
adapter: adapter as unknown as DrizzleAdapter,
})
targetIndexes[indexName] = createIndex({
name: field.localized ? [fieldName, '_locale'] : fieldName,
columnName,
tableName: newTableName,
indexName,
unique,
})
}

View File

@@ -90,6 +90,7 @@ export function vercelPostgresAdapter(args: Args = {}): DatabaseAdapterObj<Verce
fieldConstraints: {},
getMigrationTemplate,
idType: postgresIDType,
indexes: new Set<string>(),
initializing,
localesSuffix: args.localesSuffix || '_locales',
logger: args.logger,

View File

@@ -32,6 +32,7 @@ export { updateGlobal } from './updateGlobal.js'
export { updateGlobalVersion } from './updateGlobalVersion.js'
export { updateVersion } from './updateVersion.js'
export { upsertRow } from './upsertRow/index.js'
export { buildIndexName } from './utilities/buildIndexName.js'
export { executeSchemaHooks } from './utilities/executeSchemaHooks.js'
export { extendDrizzleTable } from './utilities/extendDrizzleTable.js'
export { hasLocalesTable } from './utilities/hasLocalesTable.js'

View File

@@ -30,6 +30,7 @@ import type {
} from '../types.js'
import { createTableName } from '../../createTableName.js'
import { buildIndexName } from '../../utilities/buildIndexName.js'
import { createIndex } from './createIndex.js'
import { parentIDColumnMap } from './parentIDColumnMap.js'
import { setColumnID } from './setColumnID.js'
@@ -389,21 +390,25 @@ export const buildTable = ({
foreignColumns: [adapter.tables[formattedRelationTo].id],
}).onDelete('cascade')
const indexName = [colName]
const indexColumns = [colName]
const unique = !disableUnique && uniqueRelationships.has(relationTo)
if (unique) {
indexName.push('path')
indexColumns.push('path')
}
if (hasLocalizedRelationshipField) {
indexName.push('locale')
indexColumns.push('locale')
}
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
name: indexName,
columnName: `${formattedRelationTo}_id`,
tableName: relationshipsTableName,
const indexName = buildIndexName({
name: `${relationshipsTableName}_${formattedRelationTo}_id`,
adapter,
})
relationExtraConfig[indexName] = createIndex({
name: indexColumns,
indexName,
unique,
})
})

View File

@@ -3,13 +3,12 @@ import { index, uniqueIndex } from 'drizzle-orm/pg-core'
import type { GenericColumn } from '../types.js'
type CreateIndexArgs = {
columnName: string
indexName: string
name: string | string[]
tableName: string
unique?: boolean
}
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
export const createIndex = ({ name, indexName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: GenericColumn }) => {
let columns
if (Array.isArray(name)) {
@@ -21,8 +20,8 @@ export const createIndex = ({ name, columnName, tableName, unique }: CreateIndex
columns = [table[name]]
}
if (unique) {
return uniqueIndex(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return uniqueIndex(indexName).on(columns[0], ...columns.slice(1))
}
return index(`${tableName}_${columnName}_idx`).on(columns[0], ...columns.slice(1))
return index(indexName).on(columns[0], ...columns.slice(1))
}
}

View File

@@ -30,6 +30,7 @@ import type {
} from '../types.js'
import { createTableName } from '../../createTableName.js'
import { buildIndexName } from '../../utilities/buildIndexName.js'
import { hasLocalesTable } from '../../utilities/hasLocalesTable.js'
import { validateExistingBlockIsIdentical } from '../../utilities/validateExistingBlockIsIdentical.js'
import { buildTable } from './build.js'
@@ -169,10 +170,12 @@ export const traverseFields = ({
}
adapter.fieldConstraints[rootTableName][`${columnName}_idx`] = constraintValue
}
targetIndexes[`${newTableName}_${field.name}Idx`] = createIndex({
const indexName = buildIndexName({ name: `${newTableName}_${columnName}`, adapter })
targetIndexes[indexName] = createIndex({
name: field.localized ? [fieldName, '_locale'] : fieldName,
columnName,
tableName: newTableName,
indexName,
unique,
})
}

View File

@@ -174,6 +174,7 @@ export interface DrizzleAdapter extends BaseDatabaseAdapter {
fieldConstraints: Record<string, Record<string, string>>
getMigrationTemplate: (args: MigrationTemplateArgs) => string
idType: 'serial' | 'uuid'
indexes: Set<string>
initializing: Promise<void>
insert: Insert
localesSuffix?: string

View File

@@ -0,0 +1,24 @@
import type { DrizzleAdapter } from '../types.js'
export const buildIndexName = ({
name,
adapter,
number = 0,
}: {
adapter: DrizzleAdapter
name: string
number?: number
}): string => {
const indexName = `${name}${number ? `_${number}` : ''}_idx`
if (!adapter.indexes.has(indexName)) {
adapter.indexes.add(indexName)
return indexName
}
return buildIndexName({
name,
adapter,
number: number + 1,
})
}

View File

@@ -120,6 +120,23 @@ const IndexedFields: CollectionConfig = {
],
label: 'Collapsible',
},
{
type: 'text',
name: 'someText',
index: true,
},
{
type: 'array',
name: 'some',
index: true,
fields: [
{
type: 'text',
name: 'text',
index: true,
},
],
},
],
versions: true,
}

View File

@@ -1062,6 +1062,13 @@ export interface IndexedField {
};
collapsibleLocalizedUnique?: string | null;
collapsibleTextUnique?: string | null;
someText?: string | null;
some?:
| {
text?: string | null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}