zenstackhq / zenstack

Fullstack TypeScript toolkit enhances Prisma ORM with flexible Authorization layer for RBAC/ABAC/PBAC/ReBAC, offering auto-generated type-safe APIs and frontend hooks.
https://zenstack.dev
MIT License
1.83k stars 78 forks source link

Invalid result when using take (LIMIT) #1519

Open israelins85 opened 2 weeks ago

israelins85 commented 2 weeks ago

Description and expected behavior Using take on enhanced prisma returns different result than prisma for the same expected result.

Exemple code:

` export async function getPrismaEnhanced(req: NextApiRequest, res: NextApiResponse) { const userId = await userIdFromRequest(req); let user: auth.Usuario | null = null;

if (userId != null) {
    user = { id: userId };
} else {
    sendError(res, StatusCodes.LOCKED, "LOCKED");
    return;
}

const dispositivoId =
    req.headers["x-dispositivo-id"] ?? req.headers["x-device-id"];
if (dispositivoId != null) {
    prisma.$executeRawUnsafe(`SET var.device_id = '${dispositivoId}'`);
}

const teste = await prisma.aluno.findMany({
    take: 10,
    where: {
        usuarioId: userId,
    },
});

console.log("teste", teste);

const prismaEnhanced = enhance(
    prisma,
    { user: user ?? undefined },
    {
        logPrismaQuery: true,
    },
);

const teste2 = await prismaEnhanced.aluno.findMany({
    take: 10,
});

console.log("teste2", teste2);

return prismaEnhanced;

}`

Logging the statements at prisma level on pure prisma: SELECT "public"."aluno"."id", "public"."aluno"."usuarioid", "public"."aluno"."dthrcriacao", "public"."aluno"."dthratualizacao", "public"."aluno"."nome", "public"."aluno"."sobrenome", "public"."aluno"."celular", "public"."aluno"."email", "public"."aluno"."observacoes", "public"."aluno"."saldodeaulas", "public"."aluno"."dtnascimento", "public"."aluno"."peso", "public"."aluno"."altura", "public"."aluno"."removido" FROM "public"."aluno" WHERE "public"."aluno"."usuarioid" = $1 ORDER BY "public"."aluno"."id" ASC LIMIT $2 offset $3

And on enhanced:

SELECT "public"."aluno"."id", "public"."aluno"."usuarioid", "public"."aluno"."dthrcriacao", "public"."aluno"."dthratualizacao", "public"."aluno"."nome", "public"."aluno"."sobrenome", "public"."aluno"."celular", "public"."aluno"."email", "public"."aluno"."observacoes", "public"."aluno"."saldodeaulas", "public"."aluno"."dtnascimento", "public"."aluno"."peso", "public"."aluno"."altura", "public"."aluno"."removido" FROM "public"."aluno" WHERE 1 = 1 ORDER BY "public"."aluno"."id" ASC LIMIT $1 offset $2

and some records are skipped: prisma:info [policy] dropping aluno entity due to entity checker prisma:info [policy] dropping aluno entity due to entity checker prisma:info [policy] dropping aluno entity due to entity checker prisma:info [policy] dropping aluno entity due to entity checker prisma:info [policy] dropping aluno entity due to entity checker prisma:info [policy] dropping aluno entity due to entity checker prisma:info [policy] dropping aluno entity due to entity checker prisma:info [policy] dropping aluno entity due to entity checker

the records from other user...

An workaround is using on enhanced the same where

Environment (please complete the following information):

Additional context I guess need be highlighted the fact that the where need be still used;

ymc9 commented 2 weeks ago

Hi @israelins85 , thanks for reporting this. Could you share a ZModel snippet that can be used to reproduce the problem?

israelins85 commented 2 weeks ago
abstract model AbsModelPadrao {
  id              String   @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid

  dtHrCriacao     DateTime @default(now()) @db.Timestamptz
  dtHrAtualizacao DateTime @default(now()) @updatedAt() @db.Timestamptz
}

model Usuario extends AbsModelPadrao {
  nome              String
  sobrenome         String?
  login             String                @unique
  senha             String                @password @omit
  codIdioma         String                @default("en_US")
  dtFimTeste        DateTime              @db.Date @default(dbgenerated("(CURRENT_DATE + '30 days'::INTERVAL)")) @deny("update", true)

  pin               String?               @omit
  status            UsuarioStatus         @default(NaoAtivado)

  // users can change its own data
  @@allow("all", auth() != null && auth().id == id)

  @@auth()

  alunos            Aluno[]
}

abstract model AbsModelDadosDoUsuario {
  id              String   @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
  usuarioId       String   @db.Uuid @default(auth().id)

  dtHrCriacao     DateTime @default(now()) @db.Timestamptz
  dtHrAtualizacao DateTime @default(now()) @updatedAt() @db.Timestamptz

  // relations
  usuario         Usuario  @relation(fields: [usuarioId], references: [id], onDelete: Cascade)

  // allow acess to myself
  @@allow("all", auth() != null && auth().id == usuarioId)

  @@index([usuarioId])
}

model Aluno extends AbsModelDadosDoUsuario {
  nome          String
  sobrenome     String?
  celular       String?
  email         String?
  observacoes   String?
  saldoDeAulas  Int                @default(0)
  dtNascimento  DateTime?          @db.Date
  peso          Float?
  altura        Float?
  removido      Boolean            @default(false)

  @@prisma.passthrough("@@index([nome(ops: raw(\"gin_trgm_ops\"))], type: Gin, name: \"Aluno_nome_idx\")")
}
ymc9 commented 1 week ago

Hi @israelins85 , I had a try but couldn't reproduce this issue with v2.2.3. I've shared a project here: https://github.com/ymc9/issue-1519

Could you check what differences it may have with your project? Thanks!