Skip to content

Commit

Permalink
Merge pull request #56 from farreldarian/pgd-37-foreign-key-is-not-co…
Browse files Browse the repository at this point in the history
…rrectly-generated-for-implicit-mn

fix: hardcoding M:N implicit relation pk
  • Loading branch information
fdarian authored May 29, 2024
2 parents 9dabe96 + adc7351 commit 5482f8f
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function getManyToManyRelation(
ctx: GenerateTableRelationsInput
) {
const opposingModel = findOpposingRelationModel(field, ctx.datamodel)
const joinTable = createImplicitJoinTable(field.relationName, [
const joinTable = createImplicitJoinTable(ctx, field.relationName, [
ctx.modelModule.model,
opposingModel,
])
Expand Down Expand Up @@ -189,6 +189,7 @@ function createRelationOpts(input: {
* @param models Two models having a many-to-many relationship
*/
function createImplicitJoinTable(
ctx: GenerateTableRelationsInput,
baseName: string,
models: [DMMF.Model, DMMF.Model]
) {
Expand Down Expand Up @@ -227,7 +228,7 @@ function createImplicitJoinTable(
type: pair[0],
// relationName: `${baseName}_A`,
relationFromFields: ['A'],
relationToFields: ['id'],
relationToFields: [findModelPrimaryKey(ctx.datamodel, pair[0]).name],
isGenerated: false,
isUpdatedAt: false,
},
Expand Down Expand Up @@ -256,7 +257,7 @@ function createImplicitJoinTable(
type: pair[1],
// relationName: `${baseName}_B`,
relationFromFields: ['B'],
relationToFields: ['id'],
relationToFields: [findModelPrimaryKey(ctx.datamodel, pair[1]).name],
isGenerated: false,
isUpdatedAt: false,
},
Expand All @@ -270,6 +271,15 @@ function createImplicitJoinTable(
return { varName, baseName, model, pair }
}

function findModelPrimaryKey(datamodel: DMMF.Datamodel, modelName: string) {
const model = datamodel.models.find((model) => model.name === modelName)
if (model == null) throw new Error(`Model ${modelName} not found`)
const pkField = model.fields.find((field) => field.isId)
if (pkField == null)
throw new Error(`Primary key not found in model ${modelName}`)
return pkField
}

function findOpposingRelationModel(
field: PrismaRelationField,
datamodel: DMMF.Datamodel
Expand Down
14 changes: 14 additions & 0 deletions packages/usage/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,17 @@ model Disambiguating_Sale {
}

// #endregion

// #region https://github.com/farreldarian/prisma-generator-drizzle/issues/55

model Disambiguating_Country {
id String @id @default(cuid())
currencies Disambiguating_Currency[]
}

model Disambiguating_Currency {
code String @id @unique
countries Disambiguating_Country[]
}

// #endregion
71 changes: 69 additions & 2 deletions packages/usage/tests/shared/testDisambiguatingRelationship.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { createId } from '@paralleldrive/cuid2'
import { inArray } from 'drizzle-orm'
import { throwIfnull } from 'tests/utils/query'
import { matchesId, throwIfnull } from 'tests/utils/query'
import type { TestContext } from 'tests/utils/types'

export function testDisambiguatingRelationship({ db, schema }: TestContext) {
export function testDisambiguatingRelationship(ctx: TestContext) {
const { db, schema } = ctx
const taxReceiver = { id: createId() }
const merchant = { id: createId() }
const customer = { id: createId() }
Expand Down Expand Up @@ -182,5 +183,71 @@ export function testDisambiguatingRelationship({ db, schema }: TestContext) {
tax: taxTransfer,
})
})

// https://github.com/farreldarian/prisma-generator-drizzle/issues/55
test('implicit with pk other than `id`', async () => {
const country = await prepareCountry(ctx)
const currency = await prepareCurrency(ctx)
await prepareRelation(ctx, { country, currency })

const result = await fetchCountryWithCurrencies(ctx, country.id)

expect(result).toStrictEqual({ currencies: [{ code: currency.code }] })

// --

async function prepareCurrency(ctx: TestContext) {
const currency = { code: createId() }
await ctx.db
.insert(ctx.schema.disambiguatingCurrencies)
.values(currency)
return currency
}

async function prepareCountry(ctx: TestContext) {
const country = { id: createId() }
await ctx.db.insert(ctx.schema.disambiguatingCountries).values(country)
return country
}

async function prepareRelation(
ctx: TestContext,
args: { country: { id: string }; currency: { code: string } }
) {
await ctx.db
.insert(ctx.schema.disambiguatingCountriesToDisambiguatingCurrencies)
.values({
A: args.country.id,
B: args.currency.code,
})
}

async function fetchCountryWithCurrencies(
ctx: TestContext,
countryId: string
) {
return ctx.db.query.disambiguatingCountries
.findFirst({
where: matchesId(countryId),
with: {
currencies: {
with: {
disambiguatingCurrency: {
columns: {
code: true,
},
},
},
},
},
})
.then(throwIfnull)
.then((result) => ({
currencies: result.currencies.map(
(currency) => currency.disambiguatingCurrency
),
}))
}
})
})
}
16 changes: 16 additions & 0 deletions packages/usage/tests/utils/query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
import type { BinaryOperator, Column, GetColumnData } from 'drizzle-orm'

export function throwIfnull<T>(data: T | undefined) {
if (data == null) throw new Error('data is null')
return data
}

export function shouldReturnOne<T>(results: Array<T>): T {
const result = results.at(0)
if (result == null)
throw new Error(`Expected one result from ${results}, got none`)
return result
}

export function matchesId<TColumn extends Column>(
id: GetColumnData<TColumn, 'raw'>
) {
return (table: { id: TColumn }, { eq }: { eq: BinaryOperator }) =>
eq(table.id, id)
}

0 comments on commit 5482f8f

Please sign in to comment.