Search code examples
sqliteprismanext.js13

Is my SQLite Prisma schema set up correctly for many-to-many relations? I'm getting an error when using the Prisma Studio to setup a dummy user


This is my first time working with a backend, in this case i'm using the Prisma ORM with a SQLite db within Next.js. as an overview of how i intend my data work:

  • Users have access to Boards which can have many Users, and Users can have many Boards.

  • Boards contain Items (in this case movies pulled in through the movieDB api.)

  • Items have a listing of Ranks (from S to D). Ranks are organized by Users to keep track of who Ranked which Item so that i can display everyones ranking of an Item as well as average out the ranking to display a seperate board averaged ranking of all the items.

I don't entirely understand the Prisma doc in regards to implicit vs explicit relations and could use a bit of guidance here.

Here is my schema:

generator client {
    provider = "prisma-client-js"
}

datasource db {
    provider = "sqlite"
    url      = env("DATABASE_URL")
}

model User {
    id          String   @id @default(uuid())
    email       String   @unique
    name        String?
    boards      Board[]
    ownedBoards Board[]  @relation("BoardOwner")
    createdAt   DateTime @default(now())
    updatedAt   DateTime @updatedAt
    Rank        Rank[]
}

model Board {
    id        String  @id @default(uuid())
    boardName String
    owner     User    @relation("BoardOwner", fields: [ownerId], references: [id])
    ownerId   String
    users     User[]
    userId    String
    items     Items[]
}

model Items {
    id           Int     @id @unique
    name         String
    description  String
    poster       String
    backdrop     String?
    rank         Rank[]
    release_date String?
    Board        Board?  @relation(fields: [boardId], references: [id], onDelete: Cascade)
    boardId      String?
}

model Rank {
    User    User   @relation(fields: [userId], references: [id])
    userId  String
    rank    String
    Items   Items? @relation(fields: [itemsId], references: [id], onDelete: Cascade)
    itemsId Int    @id
}

And here the error I get in Prisma Studio when trying to add a rank for a dummy user:

Type: undefined
Message: 
Invalid `f=e.match(mgt)?.[1]??"",g=e.match(hgt)?.[1]??null,v=e.match(ggt)?.[1]??null,{getPrismaClient:E,PrismaClientKnownRequestError:x,PrismaClientRustPanicError:S,PrismaClientInitializationError:C,PrismaClientValidationError:A}=require(`${c.prismaClient}/runtime/${u}`),O=e,I=(0,Rk.createHash)("sha256").update()` invocation in
C:\Users\John\Developement\movie.night\movie.night\node_modules\prisma\build\index.js:1825:10334

  1822       }
  1823     }
  1824   }
→ 1825 `}});return Oe.resourceList(a.workspaces)}};var G8e=require("@prisma/engines");var w2e=require("buffer");function _2e(e,r,n,i){Object.defineProperty(e,r,{get:n,set:i,enumerable:!0,configurable:!0})}var E2e={};_2e(E2e,"serializeRPCMessage",()=>KR);_2e(E2e,"deserializeRPCMessage",()=>YR);var WR="PrismaBigInt::",zR="PrismaBytes::";function KR(e){return JSON.stringify(e,(r,n)=>typeof n=="bigint"?WR+n:n?.type==="Buffer"&&Array.isArray(n?.data)?zR+w2e.Buffer.from(n.data).toString("base64"):n)}function YR(e){return JSON.parse(e,(r,n)=>typeof n=="string"&&n.startsWith(WR)?BigInt(n.substr(WR.length)):typeof n=="string"&&n.startsWith(zR)?n.substr(zR.length):n)}var N8e=$(O2e()),d9=$(w8e()),F8e=$(require("http")),$8e=$(S8e()),L8e=require("zlib");var Ts=require("path");var Rk=require("crypto"),O8e=$(Ck());function Ak(e,r,n,i){Object.defineProperty(e,r,{get:n,set:i,enumerable:!0,configurable:!0})}var R8e=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{},Tk={},l9={},Yu=R8e.parcelRequire1308;Yu==null&&(Yu=function(e){if(e in Tk)return Tk[e].exports;if(e in l9){var r=l9[e];delete l9[e];var n={id:e,exports:{}};return Tk[e]=n,r.call(n.exports,n,n.exports),n.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i},Yu.register=function(r,n){l9[r]=n},R8e.parcelRequire1308=Yu);Yu.register("9lTzd",function(module,exports){Ak(module.exports,"guessEnginePaths",()=>guessEnginePaths),Ak(module.exports,"guessPrismaClientPath",()=>guessPrismaClientPath);var $5COlq=Yu("5COlq");async function guessEnginePaths({forceBinary,forceLibrary,resolveOverrides}){let queryEngineName,queryEngineType;if(forceLibrary?(queryEngineName=await $5COlq.prismaEngineName("query-engine","library"),queryEngineType="library"):forceBinary?(queryEngineName=await $5COlq.prismaEngineName("query-engine","binary"),queryEngineType="binary"):(queryEngineName=void 0,queryEngineType=void 0),!queryEngineName||!queryEngineType)return{queryEngine:void 0};let queryEnginePath;if(resolveOverrides[".prisma/client"])queryEnginePath=(0,Ts.resolve)(resolveOverrides[".prisma/client"],`../${queryEngineName}`);else if(resolveOverrides["@prisma/engines"])queryEnginePath=(0,Ts.resolve)(resolveOverrides["@prisma/engines"],`../../${queryEngineName}`);else{let atPrismaEnginesPath;try{atPrismaEnginesPath=eval("require.resolve('@prisma/engines')")}catch(e){throw new Error("Unable to resolve Prisma engine paths. This is a bug.")}queryEnginePath=(0,Ts.resolve)(atPrismaEnginesPath`../../${queryEngineName}`)}return{queryEngine:{type:queryEngineType,path:queryEnginePath}}}function guessPrismaClientPath({resolveOverrides}){let prismaClientPath=resolveOverrides["@prisma/client"]||eval("require.resolve('@prisma/client')");return(0,Ts.resolve)(prismaClientPath,"../")}});Yu.register("5COlq",function(e,r){Ak(e.exports,"prismaEngineName",()=>n);async function n(i,a){let o=await Er(),u=o==="windows"?".exe":"";if(a==="library")return ka(o,"fs");if(a==="binary")return`${i}-${o}${u}`;throw new Error(`Unknown engine type: ${a}`)}});function fgt(e){return{models:Pk(e.models),enums:Pk(e.enums),types:Pk(e.types)}}function Pk(e){let r={};for(let{name:n,...i}of e)r[n]=i;return r}var M2=(0,O8e.debug)("prisma:studio-pcw"),mgt=/^\s*datasource\s+([^\s]+)\s*{/m,hgt=/url *= *env\("(.*)"\)/,ggt=/url *= *"(.*)"/;async function vgt({schema:e,schemaPath:r,dmmf:n,datasourceProvider:i,previewFeatures:a,datasources:o,engineType:u,paths:c,directUrl:p,versions:l}){let f=e.match(mgt)?.[1]??"",g=e.match(hgt)?.[1]??null,v=e.match(ggt)?.[1]??null,{getPrismaClient:E,PrismaClientKnownRequestError:x,PrismaClientRustPanicError:S,PrismaClientInitializationError:C,PrismaClientValidationError:A}=require(`${c.prismaClient}/runtime/${u}`),O=e,I=(0,Rk.createHash)("sha256").update(
Unique constraint failed on the fields: (`itemsId`)

Code: P2002

Code P2002 corresponds to "Unique constraint failed on the {constraint}", not sure what that means.

Edit to include prisma functions, however this error happened in Prisma Studio, Prisma's UI based tool. This was the first time using the tool, so these are what i used to build up everything prior to the Dummy User attempt:

"use server"
import prisma from "@/db"

export async function getBoards() {
    return await prisma.board.findMany({
        include: {
            items: {
                include: {
                    rank: true,
                },
            },
            users: true,
            owner: true,
        },
    })
}
export async function getSpecificBoard(boardId) {
    return await prisma.board.findUnique({
        where: { id: boardId },
        include: {
            items: {
                include: {
                    rank: true,
                },
            },
            users: true,
            owner: true,
        },
    })
}
export async function getItems() {
    return await prisma.items.findMany({
        include: {
            rank: true,
        },
    })
}
export async function getUsers() {
    return await prisma.user.findMany({
        include: {
            boards: true,
            ownedBoards: true,
        },
    })
}
export async function getUser(userId) {
    return await prisma.user.findFirst({
        where: { id: userId },
        include: {
            boards: true,
            ownedBoards: true,
            Rank: true,
        },
    })
}
export async function addItem(board, user, item) {
    const boardId = board.id

    await prisma.board.update({
        where: { id: boardId },
        data: {
            items: {
                create: {
                    id: item.id,
                    name: item.title,
                    description: item.overview,
                    poster: item.poster_path,
                    backdrop: item.backdrop_path,
                    release_date: item.release_date,
                    rank: {
                        create: {
                            rank: "",
                            User: {
                                connect: {
                                    id: user.id,
                                },
                            },
                        },
                    },
                },
            },
        },

        include: {
            items: true,
            users: true,
        },
    })
}

export async function deleteItem(board, item) {
    await prisma.board.update({
        where: { id: board.id },
        data: {
            items: {
                deleteMany: {
                    id: item.id,
                },
                disconnect: {
                    id: item.id,
                },
            },
        },
        include: {
            items: {
                include: {
                    Board: true,
                },
            },
        },
    })
}

export async function updateRank(item, user, score) {
    // console.log("update start")
    await prisma.board.update({
        where: { id: item.boardId },
        data: {
            items: {
                update: {
                    where: { id: item.id },
                    data: {
                        rank: {
                            upsert: {
                                create: {
                                    rank: score,
                                    User: {
                                        connect: {
                                            email: "[email protected]",
                                        },
                                    },
                                },
                                update: {
                                    rank: score,
                                    User: {
                                        connect: {
                                            email: "[email protected]",
                                        },
                                    },
                                },
                                where: { userId: user.id, itemsId: item.id },
                            },
                        },
                    },
                },
            },
        },
        include: {
            items: true,
            users: true,
        },
    })
}

export async function updateBoardName(board, newName) {
    await prisma.board.update({
        where: { id: board.id },
        data: { boardName: newName },
    })
}


Solution

  • The problem

    It took a while to comprehend what you are trying to achieve, but now I'm fairly confident I can answer your question. Let me try to write how I've understood the scenario:

    You're using Prisma Studio to manually add a new "Rank" record to a DB with some seeded/dummy data. When clicking on "Save", the insert fails with code P2002 which refers to "Unique constraint failed on the constraint" and itemsId mentioned. Judging from schema, this is clearly Rank.itemsId.

    Rank.itemsId is a foreign key referencing Items.id. So, when entering new row data, the value should be the id of an existing item. What is odd in your schema is this:

    model Rank {
      /// ...
      Items   Items? @relation(fields: [itemsId], references: [id], onDelete: Cascade)
      itemsId Int    @id
    }
    

    As explained in the documentation:

    @id Defines a single-field ID on the model.

    and:

    [@id] Cannot be defined on a relation field

    The solution

    Remove @id attribute from Rank.itemsId.

    I don't know why creating the database schema even succeeds when this shouldn't be a supported scenario.