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:
@@ -136,6 +136,7 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
indexes: new Set<string>(),
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
|
||||
@@ -133,6 +133,7 @@ export function sqliteAdapter(args: Args): DatabaseAdapterObj<SQLiteAdapter> {
|
||||
findGlobalVersions,
|
||||
findOne,
|
||||
findVersions,
|
||||
indexes: new Set<string>(),
|
||||
init,
|
||||
insert,
|
||||
migrate,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
24
packages/drizzle/src/utilities/buildIndexName.ts
Normal file
24
packages/drizzle/src/utilities/buildIndexName.ts
Normal 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,
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user