Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prisma client - computed fields v2 #14793

Closed
mishase opened this issue Aug 13, 2022 · 1 comment
Closed

Prisma client - computed fields v2 #14793

mishase opened this issue Aug 13, 2022 · 1 comment
Assignees
Labels
kind/feature A request for a new feature. status/has-client-extension This feature request is available as a Client Extension. team/client Issue for team Client. topic: clientExtensions topic: computed fields topic: extend-client Extending the Prisma Client

Comments

@mishase
Copy link

mishase commented Aug 13, 2022

Problem

I often select user's firstName and lastName to just concat it or password to tell if user has it and then cut off from the rest of response

Suggested solution

Computed fields

For example i'll use this user model:

model User {
  id        String  @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
  firstName String  @db.VarChar(64)
  lastName  String  @db.VarChar(64)
  email     String  @unique @db.VarChar(256)
  password  String? @db.Char(128)
}

I want to have ability to extend prisma client, for example:

const client = new PrismaClient({
  extensions: {
    user: {
      name: {
        select: {
          firstName: true,
          lastName: true,
        },
        callback(user) {
          return `${user.firstName} ${user.lastName}`;
        },
      },
      hasPassword: {
        select: {
          password: true,
        },
        callback(user) {
          return !!user.password;
        },
      },
    },
  },
});

Then query this fields:

const user = await client.user.findUnique({
  select: {
    id: true,
    firstName: true,
    name: true,
    hasPassword: true,
  },
  where: {
    id,
  },
});

I expect the user object to have this fields:

Defined: id, firstName, name, hasPassword
Undefined (selected internally but not passed to result): lastName, password

Alternatives

This is the same aggregation as we saw before but it is not flexible and requires me to cut off some fields every time:

const { lastName, password, ...rest } = await client.user.findUnique({
  select: {
    id: true,
    firstName: true,
    lastName: true,
    password: true,
  },
  where: {
    id,
  },
});

return { name: `${user.firstName} ${lastName}`, hasPassword: !!password, ...rest };

Additional context

Example implementation:

removeFalse(object) function that removes all object fields with false value, example:

Before:

{
  firstName: true,
  lastName: false,
}

After:

{
  firstName: true,
}

present(result, query) function that truncates query result to match select statement, example:

Select statement (query argument):

{
  firstName: true,
  lastName: true,
}

Before:

{
  id: "00000000-0000-0000-0000-000000000000",
  firstName: "John",
  lastName: "Doe",
}

After (selects firstName and lastName):

{
  firstName: "John",
  lastName: "Doe",
}

Now we will use three layers of query:

presentation - defines what user sees
subquery - adds fields to database query
database - selects all required fields from db

Assuming presentation query to be:

{
  id: true,
  firstName: true,
  name: true,
  hasPassword: true,
}

We see two subqueries in presentation query:

{
  firstName: true,
  lastName: true,
}
{
  password: true,
}

Let's build database query, execute it and save result as dbResult:

Warning: we should cut off all false select statements to make object joins correctly
{
  ...presentationQuery,
  ...removeFalse(subquery1.select),
  ...removeFalse(subquery2.select),
}

Now call the subqueries callbacks like:

const name = subquery1.callback(present(dbResult, subquery1.select));
const hasPassword = subquery2.callback(present(dbResult, subquery2.select));

Concat subquery results with dbResult and save it as result:

{
  ...dbResult,
  name,
  hasPassword,
}

And return presented result:

present(result, presentation.select)

We finally got:

{
  id: "00000000-0000-0000-0000-000000000000",
  firstName: "John",
  name: "John Doe",
  hasPassword: true,
}

Also it would be good to have support for nesting subqueries and async subquery callback

@do4gr do4gr added team/client Issue for team Client. kind/feature A request for a new feature. labels Aug 15, 2022
@janpio janpio added the topic: extend-client Extending the Prisma Client label Nov 12, 2022
@janpio janpio self-assigned this May 21, 2024
@janpio
Copy link
Member

janpio commented May 21, 2024

I think this feature request ist mostly covered by our Client Extensions: https://www.prisma.io/docs/orm/prisma-client/queries/computed-fields

(This issue was taken into account during the design phase already, as discussed here: #15074 (comment))

@janpio janpio closed this as completed May 21, 2024
@janpio janpio added topic: clientExtensions status/has-client-extension This feature request is available as a Client Extension. labels May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature A request for a new feature. status/has-client-extension This feature request is available as a Client Extension. team/client Issue for team Client. topic: clientExtensions topic: computed fields topic: extend-client Extending the Prisma Client
Projects
None yet
Development

No branches or pull requests

3 participants