question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

MongoDB: Upsert fails on update with relation when `notablescan: 1`

See original GitHub issue

Bug description

Using MongoDB, the following error occurs when running an upsert and the document already exists:

PrismaClientUnknownRequestError:
Invalid `prisma.user.upsert()` invocation:


  Error occurred during query execution:
ConnectorError(ConnectorError { user_facing_error: None, kind: RawDatabaseError { code: "unknown", message: "Command failed (NoQueryExecutionPlans): error processing query: ns=ninja-diary.projectsTree: $expr {$and: [{$in: [\"$ownerId\", {$const: [\"qGdWa0VNzHfW8GRaDxz5nfjPDRt1\"]}]}, {$or: [{$ne: [{$ifNull: [\"$ownerId\", {$const: null}]}, {$const: null}]}, {$eq: [\"$ownerId\", {$const: null}]}]}]}Sort: {}\nProj: { _id: true, ownerId: true }\nCollation: { locale: \"simple\" }\n planner returned error :: caused by :: No indexed plans available, and running with 'notablescan')" } })
    at Object.request (/usr/app/node_modules/@prisma/client/runtime/index.js:45582:15)
    at async PrismaClient._request (/usr/app/node_modules/@prisma/client/runtime/index.js:46405:18)
    at async setSyncTimeForUser (/usr/app/src/features/sync/utils.ts:29:18)
    at async /usr/app/src/features/sync/push.ts:19:5
    at async Proxy._transactionWithCallback (/usr/app/node_modules/@prisma/client/runtime/index.js:46366:18)
    at async push (/usr/app/src/features/sync/push.ts:15:3)
    at async /usr/app/src/app.ts:442:7 {
  clientVersion: '3.12.0'
}

How to reproduce

model User {
  id       String    @id @map("_id") @db.String
  name String
  lastSync DateTime  @updatedAt
  projects Project[]

  @@map("users")
}

model Project {
  id        String   @id @map("_id") @db.String
  name      String
  owner     User     @relation(fields: [ownerId], references: [id])
  ownerId   String   @db.String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  isDeleted Boolean  @default(false)
  
  @@index([ownerId])
  @@index([createdAt])
  @@index([updatedAt])
  @@index([isDeleted])
  @@map("projects")
}

Run this once:

  await prisma.user.upsert({
    where: { id: userId },
    create: { id: userId, name: "foo" },
    update: { name: "foo2" },
  });

This will create the document, which works. Then run it a second time, which will execute the “update” part since the document exists. It now fails with the error above.

If I remove the relation from the schema (and only have a “dum” ownerId field) the opertation works again.

Expected behavior

It should not fail

Prisma information

Prisma v3.12.0

mongoDB 4.4

model User {
  id       String    @id @map("_id") @db.String
  name String
  lastSync DateTime  @updatedAt
  projects Project[]

  @@map("users")
}

model Project {
  id        String   @id @map("_id") @db.String
  name      String
  owner     User     @relation(fields: [ownerId], references: [id])
  ownerId   String   @db.String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  isDeleted Boolean  @default(false)
  
  @@index([ownerId])
  @@index([createdAt])
  @@index([updatedAt])
  @@index([isDeleted])
  @@map("projects")
}

Run this once:

  await prisma.user.upsert({
    where: { id: userId },
    create: { id: userId, name: "foo" },
    update: { name: "foo2" },
  });

Environment & setup

  • OS: debian
  • Database: MongoDB Atlas v4.4
  • Node.js version: 16.13

Prisma Version

prisma                  : 3.12.0
@prisma/client          : 3.12.0
Current platform        : debian-openssl-1.1.x
Query Engine (Node-API) : libquery-engine 22b822189f46ef0dc5c5b503368d1bee01213980 (at node_modules/@prisma/engines/libquery_engine-debian-openssl-1.1.x.so.node)
Migration Engine        : migration-engine-cli 22b822189f46ef0dc5c5b503368d1bee01213980 (at node_modules/@prisma/engines/migration-engine-debian-openssl-1.1.x)
Introspection Engine    : introspection-core 22b822189f46ef0dc5c5b503368d1bee01213980 (at node_modules/@prisma/engines/introspection-engine-debian-openssl-1.1.x)
Format Binary           : prisma-fmt 22b822189f46ef0dc5c5b503368d1bee01213980 (at node_modules/@prisma/engines/prisma-fmt-debian-openssl-1.1.x)
Default Engines Hash    : 22b822189f46ef0dc5c5b503368d1bee01213980
Studio                  : 0.459.0
Preview Features        : interactiveTransactions, extendedIndexes

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:24 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
janpiocommented, May 9, 2022

Simplified schema for further reproduction:

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

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

model User {
  id       String    @id @map("_id") @db.String
  name String
  projects Project[]
}

model Project {
  id        String   @id @map("_id") @db.String
  owner     User     @relation(fields: [ownerId], references: [id])
  ownerId   String   @db.String
}

and script with query logging:

const { PrismaClient } = require('@prisma/client')

const prisma = new PrismaClient({ log: ["query"]})

// A `main` function so that we can use async/await
async function main() {
    let userId = "3"
    await prisma.user.upsert({
        where: { id: userId },
        create: { id: userId, name: "foo" },
        update: { name: "foo2" },
      });
}

main()
  .catch((e) => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })
1reaction
paulrostorpcommented, May 4, 2022

I did some more investigating and the issue seems broader, as a lot of operations seem to cause a collection scan instead of using an index.

Specifically, I have found that using the in operator always causes an aggregation command that fails to use an index. For example

prisma.project.findMany({
  where: {
    id: { in: [uuid()] },
  },
});

causes a collection scan. I have confirmed this by running explain on the aggregate command prisma ends up running (using query logging).

I added a couple million objects to the collection, and running the findMany command takes over 300ms locally vs <5ms when using mongoDB driver

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mongodb update with upsert fails - Stack Overflow
It is actually a case of oversized document. db.collection_name.stats() confirmed it.
Read more >
Does MongoDB still update or overwrite a document if the ...
I'm working with Mongoose and the phenomena happens for both update and updateMany. It seems, if the update object just happens to be...
Read more >
Error returned after an update, but the update was successful
The document in the db does have a lot of other fields, but I'm only updating one of them. Thanks. isabelatkinson (Isabel Atkinson)...
Read more >
db.collection.update() — MongoDB Manual
When you execute an update() with upsert: true and the query matches no ... update. document or pipeline. The modifications to apply. Can...
Read more >
Use case for upsert with examples - M001: MongoDB Basics
I think just like anytime you have insert new doc of update existing one if already exists kind of situation, you can use...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found