zenstack icon indicating copy to clipboard operation
zenstack copied to clipboard

.count and .findMany generated where clause not matching

Open PodkopovP opened this issue 6 months ago • 0 comments

Description and expected behavior I'm attempting to implement a generic paginated function which returns the total, next cursor, and data. I'm finding that, because of my RLS settings for this model, the data returned by .findMany does not match the count returned from .count, using exactly the same provided 'where'.

I would expect the where clause generated by Zenstack to be the same for both - especially considering manually inputing the generated .findMany clause into an un-enhanced prisma client .count works as intended.

Screenshots I've logged the different where clauses that are generated by Zenstack by combining my client provided where clause along with the auto-generated RLS where clause. To be claer, the findMany where clause is the one that is functioning as expected.

findMany:

{"status":{"in":["friend","pending"]},"OR":[{"requesterId":"pcHL3YTMhy6wNgEzIruoY"},{"recipientId":"pcHL3YTMhy6wNgEzIruoY"}],"AND":[{"OR":[{"AND":[{"AND":[{"NOT":{"deletedAt":{"not":{"equals":null}}}},{"NOT":{"OR":[{"requester":{"deletedAt":{"not":{"equals":null}}}},{"AND":[{"requester":{"banned":{"not":{"equals":false}}}},{"requester":{"banned":{"not":{"equals":null}}}}]}]}},{"NOT":{"OR":[{"recipient":{"deletedAt":{"not":{"equals":null}}}},{"AND":[{"recipient":{"banned":{"not":{"equals":false}}}},{"recipient":{"banned":{"not":{"equals":null}}}}]}]}}]},{"OR":[{"status":{"equals":"friend"}},{"OR":[{"requester":{"is":{"id":"pcHL3YTMhy6wNgEzIruoY"}}},{"recipient":{"is":{"id":"pcHL3YTMhy6wNgEzIruoY"}}}]}]}]}]},{"AND":[{"requester":{"OR":[{"AND":[{"AND":[{"NOT":{"deletedAt":{"not":{"equals":null}}}},{"NOT":{"sentBlocks":{"some":{"recipient":{"is":{"id":"pcHL3YTMhy6wNgEzIruoY"}}}}}},{"NOT":{"AND":[{"banned":{"not":{"equals":false}}},{"banned":{"not":{"equals":null}}}]}}]}]}]}},{"recipient":{"OR":[{"AND":[{"AND":[{"NOT":{"deletedAt":{"not":{"equals":null}}}},{"NOT":{"sentBlocks":{"some":{"recipient":{"is":{"id":"pcHL3YTMhy6wNgEzIruoY"}}}}}},{"NOT":{"AND":[{"banned":{"not":{"equals":false}}},{"banned":{"not":{"equals":null}}}]}}]}]}]}}]}]}

count:

{"AND":[{"status":{"in":["friend","pending"]},"OR":[{"requesterId":"pcHL3YTMhy6wNgEzIruoY"},{"recipientId":"pcHL3YTMhy6wNgEzIruoY"}]},{"OR":[{"AND":[{"OR":[{"AND":[{"AND":[{"NOT":{"deletedAt":{"not":{"equals":null}}}},{"NOT":{"OR":[{"requester":{"deletedAt":{"not":{"equals":null}}}},{"AND":[{"requester":{"banned":{"not":{"equals":false}}}},{"requester":{"banned":{"not":{"equals":null}}}}]}]}},{"NOT":{"OR":[{"recipient":{"deletedAt":{"not":{"equals":null}}}},{"AND":[{"recipient":{"banned":{"not":{"equals":false}}}},{"recipient":{"banned":{"not":{"equals":null}}}}]}]}}]},{"OR":[{"status":{"equals":"friend"}},{"OR":[{"requester":{"is":{"id":"pcHL3YTMhy6wNgEzIruoY"}}},{"recipient":{"is":{"id":"pcHL3YTMhy6wNgEzIruoY"}}}]}]}]}]}]}]}]}

Models:

abstract model BaseWithUsersRelationship extends Base {
    requesterId String @map("requester_id")
    recipientId String @map("recipient_id")
    requester   User   @relation("RelationshipsSent", fields: [requesterId], references: [id], onDelete: Cascade)
    recipient   User   @relation("RelationshipsReceived", fields: [recipientId], references: [id], onDelete: Cascade)

    // deny all access if either user is deleted or banned
    @@deny('all', requester.deletedAt != null || (requester.banned != false && requester.banned != null))
    @@deny('all', recipient.deletedAt != null || (recipient.banned != false && recipient.banned != null))

    // deny all access if either user is blocked
    @@deny('all', requester.sentBlocks?[recipient == this.recipient] || recipient.sentBlocks?[recipient == this.requester])

    @@unique([requesterId, recipientId], map: "relationship_unique_idx")

    @@index([requesterId, recipientId], map: "relationship_requester_reverse_idx")
    @@index([recipientId, requesterId], map: "relationship_recipient_reverse_idx")
}

model UserRelationship extends BaseWithUsersRelationship {
    status UserRelationshipStatus

    // only auth users can create
    @@allow('create', auth() != null)
    // allow all users to see it, if they are friends
    @@allow('read', status == friend)
    // only the requester/recipient can read/update
    @@allow('read,update', (auth() == requester || auth() == recipient))

    @@map("user_relationship")
}

model User extends Base {
    name                  String
    email                 String             @deny('read', auth().id != this.id) @deny('read', auth() != this)
    emailVerified         Boolean            @map("email_verified") @deny('read', auth() != this)
    role                  String?            @deny('read', true)
    banned                Boolean?           @deny("all", true)
    banReason             String?            @map("ban_reason") @deny('read', true)
    banExpires            DateTime?          @map("ban_expires") @deny('read', true)
    apiKeys               Apikey[]
    chatMemberships       ChatMember[]
    chatMessages          ChatMessage[]
    sentBlocks            UserBlock[]        @relation("BlocksSent")
    receivedBlocks        UserBlock[]        @relation("BlocksReceived")
    sentRelationships     UserRelationship[] @relation("RelationshipsSent")
    receivedRelationships UserRelationship[] @relation("RelationshipsReceived")
    authSessions          AuthSession[]
    authAccounts          AuthAccount[]

    // users visible to everyone
    @@allow('read', true)
    // only authenticated user can update this
    @@allow('all', auth() == this)

    // deny read access to users blocked by this user
    @@deny('read', sentBlocks?[auth() == recipient])
    // deny all access if banned
    @@deny('all', banned != false && banned != null)

    @@unique([email])

    @@map("user")

    @@schema("auth")
}

Environment (please complete the following information):

  • ZenStack version: 2.15.1
  • Prisma version: 6.10.0
  • Database type: Postgresql

Additional context By manually putting in the findMany where clause into a non-enhanced prisma client .count on this model, it does return the same total that I'm expecting.

PodkopovP avatar Jun 24 '25 09:06 PodkopovP