Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions packages/drizzle/src/schema/traverseFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
} from '../types.js'

import { createTableName } from '../createTableName.js'
import { buildForeignKeyName } from '../utilities/buildForeignKeyName.js'
import { buildIndexName } from '../utilities/buildIndexName.js'
import { getArrayRelationName } from '../utilities/getArrayRelationName.js'
import { hasLocalesTable } from '../utilities/hasLocalesTable.js'
Expand Down Expand Up @@ -201,18 +202,18 @@ export const traverseFields = ({

const baseIndexes: Record<string, RawIndex> = {
_orderIdx: {
name: `${arrayTableName}_order_idx`,
name: buildIndexName({ name: `${arrayTableName}_order`, adapter }),
on: ['_order'],
},
_parentIDIdx: {
name: `${arrayTableName}_parent_id_idx`,
name: buildIndexName({ name: `${arrayTableName}_parent_id`, adapter }),
on: '_parentID',
},
}

const baseForeignKeys: Record<string, RawForeignKey> = {
_parentIDFk: {
name: `${arrayTableName}_parent_id_fk`,
name: buildForeignKeyName({ name: `${arrayTableName}_parent_id`, adapter }),
columns: ['_parentID'],
foreignColumns: [
{
Expand All @@ -238,7 +239,7 @@ export const traverseFields = ({
}

baseIndexes._localeIdx = {
name: `${arrayTableName}_locale_idx`,
name: buildIndexName({ name: `${arrayTableName}_locale`, adapter }),
on: '_locale',
}
}
Expand Down Expand Up @@ -437,22 +438,22 @@ export const traverseFields = ({

const baseIndexes: Record<string, RawIndex> = {
_orderIdx: {
name: `${blockTableName}_order_idx`,
name: buildIndexName({ name: `${blockTableName}_order`, adapter }),
on: '_order',
},
_parentIDIdx: {
name: `${blockTableName}_parent_id_idx`,
name: buildIndexName({ name: `${blockTableName}_parent_id`, adapter }),
on: ['_parentID'],
},
_pathIdx: {
name: `${blockTableName}_path_idx`,
name: buildIndexName({ name: `${blockTableName}_path`, adapter }),
on: '_path',
},
}

const baseForeignKeys: Record<string, RawForeignKey> = {
_parentIdFk: {
name: `${blockTableName}_parent_id_fk`,
name: buildForeignKeyName({ name: `${blockTableName}_parent_id`, adapter }),
columns: ['_parentID'],
foreignColumns: [
{
Expand All @@ -478,7 +479,7 @@ export const traverseFields = ({
}

baseIndexes._localeIdx = {
name: `${blockTableName}_locale_idx`,
name: buildIndexName({ name: `${blockTableName}_locale`, adapter }),
on: '_locale',
}
}
Expand Down Expand Up @@ -824,18 +825,18 @@ export const traverseFields = ({

const baseIndexes: Record<string, RawIndex> = {
orderIdx: {
name: `${selectTableName}_order_idx`,
name: buildIndexName({ name: `${selectTableName}_order`, adapter }),
on: 'order',
},
parentIdx: {
name: `${selectTableName}_parent_idx`,
name: buildIndexName({ name: `${selectTableName}_parent`, adapter }),
on: 'parent',
},
}

const baseForeignKeys: Record<string, RawForeignKey> = {
parentFk: {
name: `${selectTableName}_parent_fk`,
name: buildForeignKeyName({ name: `${selectTableName}_parent`, adapter }),
columns: ['parent'],
foreignColumns: [
{
Expand All @@ -861,14 +862,14 @@ export const traverseFields = ({
}

baseIndexes.localeIdx = {
name: `${selectTableName}_locale_idx`,
name: buildIndexName({ name: `${selectTableName}_locale`, adapter }),
on: 'locale',
}
}

if (field.index) {
baseIndexes.value = {
name: `${selectTableName}_value_idx`,
name: buildIndexName({ name: `${selectTableName}_value`, adapter }),
on: 'value',
}
}
Expand Down
87 changes: 87 additions & 0 deletions packages/drizzle/src/utilities/buildForeignKeyName.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { DrizzleAdapter } from '../types.js'

import { buildForeignKeyName } from './buildForeignKeyName.js'

describe('buildForeignKeyName', () => {
let mockAdapter: Pick<DrizzleAdapter, 'foreignKeys'>

beforeEach(() => {
mockAdapter = {
foreignKeys: new Set(),
}
})

it('should create foreign key name with _fk suffix', () => {
const result = buildForeignKeyName({
name: 'users_post_id',
adapter: mockAdapter as DrizzleAdapter,
})

expect(result).toBe('users_post_id_fk')
expect(mockAdapter.foreignKeys.has('users_post_id_fk')).toBe(true)
})

it('should truncate long foreign key names to 60 characters', () => {
const longName = 'users_v_version_ingredient_sections_section_ingredients_parent'
const result = buildForeignKeyName({
name: longName,
adapter: mockAdapter as DrizzleAdapter,
})

// Should be truncated to 60 chars total (57 chars + _fk)
expect(result.length).toBeLessThanOrEqual(60)
expect(result.endsWith('_fk')).toBe(true)
expect(mockAdapter.foreignKeys.has(result)).toBe(true)
})

it('should handle duplicate names by appending numbers', () => {
const name = 'duplicate_foreign_key'

const first = buildForeignKeyName({
name,
adapter: mockAdapter as DrizzleAdapter,
})

const second = buildForeignKeyName({
name,
adapter: mockAdapter as DrizzleAdapter,
})

expect(first).toBe('duplicate_foreign_key_fk')
expect(second).toBe('duplicate_foreign_key_1_fk')
expect(mockAdapter.foreignKeys.has(first)).toBe(true)
expect(mockAdapter.foreignKeys.has(second)).toBe(true)
})

it('should truncate very long names even with numbering', () => {
const veryLongName =
'extremely_long_table_name_with_nested_arrays_and_localization_and_versioning_parent_id'

const first = buildForeignKeyName({
name: veryLongName,
adapter: mockAdapter as DrizzleAdapter,
})

const second = buildForeignKeyName({
name: veryLongName,
adapter: mockAdapter as DrizzleAdapter,
})

expect(first.length).toBeLessThanOrEqual(60)
expect(second.length).toBeLessThanOrEqual(60)
expect(first).not.toBe(second)
})

it('should ensure PostgreSQL 63-character limit is respected', () => {
// PostgreSQL has a 63-character limit for identifiers
// We truncate to 60 to leave room for suffixes and numbering
const name = 'a'.repeat(100) // Very long name

const result = buildForeignKeyName({
name,
adapter: mockAdapter as DrizzleAdapter,
})

expect(result.length).toBeLessThanOrEqual(63)
})
})
86 changes: 86 additions & 0 deletions packages/drizzle/src/utilities/buildIndexName.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { DrizzleAdapter } from '../types.js'

import { buildIndexName } from './buildIndexName.js'

describe('buildIndexName', () => {
let mockAdapter: Pick<DrizzleAdapter, 'indexes' | 'rawTables'>

beforeEach(() => {
mockAdapter = {
indexes: new Set(),
rawTables: {},
}
})

it('should create index name with _idx suffix by default', () => {
const result = buildIndexName({
name: 'users_email',
adapter: mockAdapter as DrizzleAdapter,
})

expect(result).toBe('users_email_idx')
expect(mockAdapter.indexes.has('users_email_idx')).toBe(true)
})

it('should truncate long index names to 60 characters', () => {
const longName = 'users_v_version_ingredient_sections_section_ingredients_locale'
const result = buildIndexName({
name: longName,
adapter: mockAdapter as DrizzleAdapter,
})

// Should be truncated to 60 chars total (56 chars + _idx)
expect(result.length).toBeLessThanOrEqual(60)
expect(result.endsWith('_idx')).toBe(true)
expect(mockAdapter.indexes.has(result)).toBe(true)
})

it('should handle duplicate names by appending numbers', () => {
const name = 'duplicate_index'

const first = buildIndexName({
name,
adapter: mockAdapter as DrizzleAdapter,
})

const second = buildIndexName({
name,
adapter: mockAdapter as DrizzleAdapter,
})

expect(first).toBe('duplicate_index_idx')
expect(second).toBe('duplicate_index_1_idx')
expect(mockAdapter.indexes.has(first)).toBe(true)
expect(mockAdapter.indexes.has(second)).toBe(true)
})

it('should work without suffix when appendSuffix is false', () => {
const result = buildIndexName({
name: 'users_unique',
adapter: mockAdapter as DrizzleAdapter,
appendSuffix: false,
})

expect(result).toBe('users_unique')
expect(mockAdapter.indexes.has('users_unique')).toBe(true)
})

it('should truncate very long names even with numbering', () => {
const veryLongName =
'extremely_long_table_name_with_nested_arrays_and_localization_and_versioning'

const first = buildIndexName({
name: veryLongName,
adapter: mockAdapter as DrizzleAdapter,
})

const second = buildIndexName({
name: veryLongName,
adapter: mockAdapter as DrizzleAdapter,
})

expect(first.length).toBeLessThanOrEqual(60)
expect(second.length).toBeLessThanOrEqual(60)
expect(first).not.toBe(second)
})
})
28 changes: 14 additions & 14 deletions test/_community/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export interface Config {
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
defaultIDType: number;
};
globals: {
menu: Menu;
Expand Down Expand Up @@ -126,7 +126,7 @@ export interface UserAuthOperations {
* via the `definition` "posts".
*/
export interface Post {
id: string;
id: number;
title?: string | null;
content?: {
root: {
Expand All @@ -151,7 +151,7 @@ export interface Post {
* via the `definition` "media".
*/
export interface Media {
id: string;
id: number;
updatedAt: string;
createdAt: string;
url?: string | null;
Expand Down Expand Up @@ -195,7 +195,7 @@ export interface Media {
* via the `definition` "payload-kv".
*/
export interface PayloadKv {
id: string;
id: number;
key: string;
data:
| {
Expand All @@ -212,7 +212,7 @@ export interface PayloadKv {
* via the `definition` "users".
*/
export interface User {
id: string;
id: number;
updatedAt: string;
createdAt: string;
email: string;
Expand All @@ -236,24 +236,24 @@ export interface User {
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
id: number;
document?:
| ({
relationTo: 'posts';
value: string | Post;
value: number | Post;
} | null)
| ({
relationTo: 'media';
value: string | Media;
value: number | Media;
} | null)
| ({
relationTo: 'users';
value: string | User;
value: number | User;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
value: number | User;
};
updatedAt: string;
createdAt: string;
Expand All @@ -263,10 +263,10 @@ export interface PayloadLockedDocument {
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
id: number;
user: {
relationTo: 'users';
value: string | User;
value: number | User;
};
key?: string | null;
value?:
Expand All @@ -286,7 +286,7 @@ export interface PayloadPreference {
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
id: number;
name?: string | null;
batch?: number | null;
updatedAt: string;
Expand Down Expand Up @@ -420,7 +420,7 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
* via the `definition` "menu".
*/
export interface Menu {
id: string;
id: number;
globalText?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
Expand Down
Loading