Skip to content

Commit 1ffb6c3

Browse files
authored
fix(drizzle): indexes / unique with relationships (#8432)
Fixes #8413 and #6460 - Builds indexes for relationships by default in the SQL schema - Fixes `unique: true` handling with Postgres / SQLite for every type of relationships (non-polymorphic. hasMany, polymorphic, polymorphic hasMany) _note_: disables unique for nested to arrays / blocks relationships in the `_rels` table. - adds tests 2.0 PR tha ports only indexes creation #8446, because `unique: true` could be a breaking change if someone has incosistent unique data in the database.
1 parent 256949e commit 1ffb6c3

File tree

7 files changed

+394
-15
lines changed

7 files changed

+394
-15
lines changed

packages/db-sqlite/src/schema/build.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import toSnakeCase from 'to-snake-case'
2424

2525
import type { GenericColumns, GenericTable, IDType, SQLiteAdapter } from '../types.js'
2626

27+
import { createIndex } from './createIndex.js'
2728
import { getIDColumn } from './getIDColumn.js'
2829
import { setColumnID } from './setColumnID.js'
2930
import { traverseFields } from './traverseFields.js'
@@ -56,6 +57,7 @@ type Args = {
5657
buildNumbers?: boolean
5758
buildRelationships?: boolean
5859
disableNotNull: boolean
60+
disableRelsTableUnique?: boolean
5961
disableUnique: boolean
6062
fields: Field[]
6163
joins?: SanitizedJoins
@@ -64,6 +66,7 @@ type Args = {
6466
rootRelationsToBuild?: RelationMap
6567
rootTableIDColType?: IDType
6668
rootTableName?: string
69+
rootUniqueRelationships?: Set<string>
6770
tableName: string
6871
timestamps?: boolean
6972
versions: boolean
@@ -88,6 +91,7 @@ export const buildTable = ({
8891
baseColumns = {},
8992
baseExtraConfig = {},
9093
disableNotNull,
94+
disableRelsTableUnique,
9195
disableUnique = false,
9296
fields,
9397
joins,
@@ -96,6 +100,7 @@ export const buildTable = ({
96100
rootRelationsToBuild,
97101
rootTableIDColType,
98102
rootTableName: incomingRootTableName,
103+
rootUniqueRelationships,
99104
tableName,
100105
timestamps,
101106
versions,
@@ -114,6 +119,7 @@ export const buildTable = ({
114119

115120
// Relationships to the base collection
116121
const relationships: Set<string> = rootRelationships || new Set()
122+
const uniqueRelationships: Set<string> = rootUniqueRelationships || new Set()
117123

118124
let relationshipsTable: GenericTable | SQLiteTableWithColumns<any>
119125

@@ -133,6 +139,7 @@ export const buildTable = ({
133139
adapter,
134140
columns,
135141
disableNotNull,
142+
disableRelsTableUnique,
136143
disableUnique,
137144
fields,
138145
indexes,
@@ -147,6 +154,7 @@ export const buildTable = ({
147154
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
148155
rootTableIDColType: rootTableIDColType || idColType,
149156
rootTableName,
157+
uniqueRelationships,
150158
versions,
151159
withinLocalizedArrayOrBlock,
152160
})
@@ -393,7 +401,9 @@ export const buildTable = ({
393401
colType = 'text'
394402
}
395403

396-
relationshipColumns[`${relationTo}ID`] = getIDColumn({
404+
const colName = `${relationTo}ID`
405+
406+
relationshipColumns[colName] = getIDColumn({
397407
name: `${formattedRelationTo}_id`,
398408
type: colType,
399409
primaryKey: false,
@@ -402,9 +412,27 @@ export const buildTable = ({
402412
relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
403413
foreignKey({
404414
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
405-
columns: [cols[`${relationTo}ID`]],
415+
columns: [cols[colName]],
406416
foreignColumns: [adapter.tables[formattedRelationTo].id],
407417
}).onDelete('cascade')
418+
419+
const indexName = [colName]
420+
421+
const unique = !disableUnique && uniqueRelationships.has(relationTo)
422+
423+
if (unique) {
424+
indexName.push('path')
425+
}
426+
if (hasLocalizedRelationshipField) {
427+
indexName.push('locale')
428+
}
429+
430+
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
431+
name: indexName,
432+
columnName: `${formattedRelationTo}_id`,
433+
tableName: relationshipsTableName,
434+
unique,
435+
})
408436
})
409437

410438
relationshipsTable = sqliteTable(relationshipsTableName, relationshipColumns, (cols) => {

packages/db-sqlite/src/schema/traverseFields.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type Args = {
3636
columnPrefix?: string
3737
columns: Record<string, SQLiteColumnBuilder>
3838
disableNotNull: boolean
39+
disableRelsTableUnique?: boolean
3940
disableUnique?: boolean
4041
fieldPrefix?: string
4142
fields: (Field | TabAsField)[]
@@ -52,6 +53,7 @@ type Args = {
5253
rootRelationsToBuild?: RelationMap
5354
rootTableIDColType: IDType
5455
rootTableName: string
56+
uniqueRelationships: Set<string>
5557
versions: boolean
5658
/**
5759
* Tracks whether or not this table is built
@@ -74,6 +76,7 @@ export const traverseFields = ({
7476
columnPrefix,
7577
columns,
7678
disableNotNull,
79+
disableRelsTableUnique,
7780
disableUnique = false,
7881
fieldPrefix,
7982
fields,
@@ -90,6 +93,7 @@ export const traverseFields = ({
9093
rootRelationsToBuild,
9194
rootTableIDColType,
9295
rootTableName,
96+
uniqueRelationships,
9397
versions,
9498
withinLocalizedArrayOrBlock,
9599
}: Args): Result => {
@@ -147,9 +151,10 @@ export const traverseFields = ({
147151
}
148152

149153
if (
150-
(field.unique || field.index) &&
151-
!['array', 'blocks', 'group', 'point', 'relationship', 'upload'].includes(field.type) &&
152-
!('hasMany' in field && field.hasMany === true)
154+
(field.unique || field.index || ['relationship', 'upload'].includes(field.type)) &&
155+
!['array', 'blocks', 'group', 'point'].includes(field.type) &&
156+
!('hasMany' in field && field.hasMany === true) &&
157+
!('relationTo' in field && Array.isArray(field.relationTo))
153158
) {
154159
const unique = disableUnique !== true && field.unique
155160
if (unique) {
@@ -401,12 +406,14 @@ export const traverseFields = ({
401406
baseColumns,
402407
baseExtraConfig,
403408
disableNotNull: disableNotNullFromHere,
409+
disableRelsTableUnique: true,
404410
disableUnique,
405411
fields: disableUnique ? idToUUID(field.fields) : field.fields,
406412
rootRelationships: relationships,
407413
rootRelationsToBuild,
408414
rootTableIDColType,
409415
rootTableName,
416+
rootUniqueRelationships: uniqueRelationships,
410417
tableName: arrayTableName,
411418
versions,
412419
withinLocalizedArrayOrBlock: isLocalized,
@@ -540,12 +547,14 @@ export const traverseFields = ({
540547
baseColumns,
541548
baseExtraConfig,
542549
disableNotNull: disableNotNullFromHere,
550+
disableRelsTableUnique: true,
543551
disableUnique,
544552
fields: disableUnique ? idToUUID(block.fields) : block.fields,
545553
rootRelationships: relationships,
546554
rootRelationsToBuild,
547555
rootTableIDColType,
548556
rootTableName,
557+
rootUniqueRelationships: uniqueRelationships,
549558
tableName: blockTableName,
550559
versions,
551560
withinLocalizedArrayOrBlock: isLocalized,
@@ -664,6 +673,7 @@ export const traverseFields = ({
664673
rootRelationsToBuild,
665674
rootTableIDColType,
666675
rootTableName,
676+
uniqueRelationships,
667677
versions,
668678
withinLocalizedArrayOrBlock,
669679
})
@@ -719,6 +729,7 @@ export const traverseFields = ({
719729
rootRelationsToBuild,
720730
rootTableIDColType,
721731
rootTableName,
732+
uniqueRelationships,
722733
versions,
723734
withinLocalizedArrayOrBlock: withinLocalizedArrayOrBlock || field.localized,
724735
})
@@ -775,6 +786,7 @@ export const traverseFields = ({
775786
rootRelationsToBuild,
776787
rootTableIDColType,
777788
rootTableName,
789+
uniqueRelationships,
778790
versions,
779791
withinLocalizedArrayOrBlock,
780792
})
@@ -831,6 +843,7 @@ export const traverseFields = ({
831843
rootRelationsToBuild,
832844
rootTableIDColType,
833845
rootTableName,
846+
uniqueRelationships,
834847
versions,
835848
withinLocalizedArrayOrBlock,
836849
})
@@ -859,9 +872,17 @@ export const traverseFields = ({
859872
case 'relationship':
860873
case 'upload':
861874
if (Array.isArray(field.relationTo)) {
862-
field.relationTo.forEach((relation) => relationships.add(relation))
875+
field.relationTo.forEach((relation) => {
876+
relationships.add(relation)
877+
if (field.unique && !disableUnique && !disableRelsTableUnique) {
878+
uniqueRelationships.add(relation)
879+
}
880+
})
863881
} else if (field.hasMany) {
864882
relationships.add(field.relationTo)
883+
if (field.unique && !disableUnique && !disableRelsTableUnique) {
884+
uniqueRelationships.add(field.relationTo)
885+
}
865886
} else {
866887
// simple relationships get a column on the targetTable with a foreign key to the relationTo table
867888
const relationshipConfig = adapter.payload.collections[field.relationTo].config

packages/drizzle/src/postgres/schema/build.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type {
3030
} from '../types.js'
3131

3232
import { createTableName } from '../../createTableName.js'
33+
import { createIndex } from './createIndex.js'
3334
import { parentIDColumnMap } from './parentIDColumnMap.js'
3435
import { setColumnID } from './setColumnID.js'
3536
import { traverseFields } from './traverseFields.js'
@@ -45,13 +46,15 @@ type Args = {
4546
buildNumbers?: boolean
4647
buildRelationships?: boolean
4748
disableNotNull: boolean
49+
disableRelsTableUnique?: boolean
4850
disableUnique: boolean
4951
fields: Field[]
5052
joins?: SanitizedJoins
5153
rootRelationships?: Set<string>
5254
rootRelationsToBuild?: RelationMap
5355
rootTableIDColType?: string
5456
rootTableName?: string
57+
rootUniqueRelationships?: Set<string>
5558
tableName: string
5659
timestamps?: boolean
5760
versions: boolean
@@ -76,13 +79,15 @@ export const buildTable = ({
7679
baseColumns = {},
7780
baseExtraConfig = {},
7881
disableNotNull,
82+
disableRelsTableUnique = false,
7983
disableUnique = false,
8084
fields,
8185
joins,
8286
rootRelationships,
8387
rootRelationsToBuild,
8488
rootTableIDColType,
8589
rootTableName: incomingRootTableName,
90+
rootUniqueRelationships,
8691
tableName,
8792
timestamps,
8893
versions,
@@ -102,6 +107,9 @@ export const buildTable = ({
102107
// Relationships to the base collection
103108
const relationships: Set<string> = rootRelationships || new Set()
104109

110+
// Unique relationships to the base collection
111+
const uniqueRelationships: Set<string> = rootUniqueRelationships || new Set()
112+
105113
let relationshipsTable: GenericTable | PgTableWithColumns<any>
106114

107115
// Drizzle relations
@@ -120,6 +128,7 @@ export const buildTable = ({
120128
adapter,
121129
columns,
122130
disableNotNull,
131+
disableRelsTableUnique,
123132
disableUnique,
124133
fields,
125134
indexes,
@@ -133,6 +142,7 @@ export const buildTable = ({
133142
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
134143
rootTableIDColType: rootTableIDColType || idColType,
135144
rootTableName,
145+
uniqueRelationships,
136146
versions,
137147
withinLocalizedArrayOrBlock,
138148
})
@@ -368,16 +378,34 @@ export const buildTable = ({
368378
colType = 'varchar'
369379
}
370380

371-
relationshipColumns[`${relationTo}ID`] = parentIDColumnMap[colType](
372-
`${formattedRelationTo}_id`,
373-
)
381+
const colName = `${relationTo}ID`
382+
383+
relationshipColumns[colName] = parentIDColumnMap[colType](`${formattedRelationTo}_id`)
374384

375385
relationExtraConfig[`${relationTo}IdFk`] = (cols) =>
376386
foreignKey({
377387
name: `${relationshipsTableName}_${toSnakeCase(relationTo)}_fk`,
378-
columns: [cols[`${relationTo}ID`]],
388+
columns: [cols[colName]],
379389
foreignColumns: [adapter.tables[formattedRelationTo].id],
380390
}).onDelete('cascade')
391+
392+
const indexName = [colName]
393+
394+
const unique = !disableUnique && uniqueRelationships.has(relationTo)
395+
396+
if (unique) {
397+
indexName.push('path')
398+
}
399+
if (hasLocalizedRelationshipField) {
400+
indexName.push('locale')
401+
}
402+
403+
relationExtraConfig[`${relationTo}IdIdx`] = createIndex({
404+
name: indexName,
405+
columnName: `${formattedRelationTo}_id`,
406+
tableName: relationshipsTableName,
407+
unique,
408+
})
381409
})
382410

383411
relationshipsTable = adapter.pgSchema.table(

0 commit comments

Comments
 (0)