Skip to content

Commit

Permalink
fix(client): middleware args consistency (#15897)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Starns <danielstarns@hotmail.com>
  • Loading branch information
2 people authored and jkomyno committed Dec 21, 2022
1 parent 5dad723 commit 70a8456
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 67 deletions.
7 changes: 2 additions & 5 deletions .github/renovate.json
@@ -1,10 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", ":disableRateLimiting", ":noUnscheduledUpdates"],
"schedule": [
"before 7am every weekday",
"every weekend"
],
"schedule": ["before 7am every weekday", "every weekend"],
"semanticCommits": "enabled",
"reviewers": ["@Jolg42", "@millsp", "@aqrln", "@SevInf", "@danstarns", "@jkomyno"],
"rebaseWhen": "conflicted",
Expand Down Expand Up @@ -122,7 +119,7 @@
{
"groupName": "sqlite-async",
"packageNames": ["sqlite-async"],
"schedule": ["before 7am on Wednesday"],
"schedule": ["before 7am on Wednesday"]
},
{
"groupName": "@swc/core for internals",
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/update-engines-version.yml
Expand Up @@ -77,8 +77,8 @@ jobs:
const hash = context.payload.inputs.version.split('.').pop()
core.setOutput('hash', hash)
#
# Regular engines PR, from prisma-engines main branch
#
# Regular engines PR, from prisma-engines main branch
# Will be automerged if tests are passing
#
- name: Create Pull Request
Expand Down Expand Up @@ -150,7 +150,7 @@ jobs:
if: github.event.inputs.npmDistTag == 'integration'
run: |
echo "Pull Request URL - ${{ steps.cpr-integration.outputs.pull-request-url }}"
#
# Patch Engine PR, from a prisma-engines patch branch (e.g. "4.6.x")
# This will create a draft PR as we don't want to merge it automatically
Expand Down Expand Up @@ -190,7 +190,7 @@ jobs:
if: github.event.inputs.npmDistTag == 'patch'
run: |
echo "Pull Request URL - ${{ steps.cpr-patch.outputs.pull-request-url }}"
- name: 'Set current job url in SLACK_FOOTER env var'
if: ${{ failure() }}
run: echo "SLACK_FOOTER=<$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID|Click here to go to the job logs>" >> $GITHUB_ENV
Expand Down
36 changes: 17 additions & 19 deletions packages/client/src/runtime/core/model/aggregates/aggregate.ts
@@ -1,4 +1,3 @@
import type { Client } from '../../../getPrismaClient'
import type { ModelAction } from '../applyModel'
import type { UserArgs } from '../UserArgs'
import { aggregateMap } from './utils/aggregateMap'
Expand All @@ -7,12 +6,12 @@ import { aggregateMap } from './utils/aggregateMap'
* Transforms the `userArgs` for the `.aggregate` shorthand. It is an API sugar
* for not having to do things like: `{select: {_avg: {select: {age: true}}}}`.
* The goal here is to desugar it into something that is understood by the QE.
* @param userArgs to transform
* @param args to transform
* @returns
*/
export function desugarUserArgs(userArgs: UserArgs) {
const _userArgs = desugarCountInUserArgs(userArgs)
const userArgsEntries = Object.entries(_userArgs)
export function desugarUserArgs(args: UserArgs = {}) {
const _args = desugarCountInUserArgs(args)
const userArgsEntries = Object.entries(_args)

return userArgsEntries.reduce(
(aggregateArgs, [key, value]) => {
Expand All @@ -31,26 +30,26 @@ export function desugarUserArgs(userArgs: UserArgs) {

/**
* Desugar `userArgs` when it contains `{_count: true}`.
* @param userArgs the user input
* @param args the user input
* @returns
*/
function desugarCountInUserArgs(userArgs: UserArgs) {
if (typeof userArgs['_count'] === 'boolean') {
return { ...userArgs, _count: { _all: userArgs['_count'] } }
function desugarCountInUserArgs(args: UserArgs = {}) {
if (typeof args['_count'] === 'boolean') {
return { ...args, _count: { _all: args['_count'] } }
}

return userArgs
return args
}

/**
* Creates an unpacker that adds sugar to the basic result of the QE. An
* unpacker helps to transform a result before returning it to the user.
* @param userArgs the user input
* @param args the user input
* @returns
*/
export function createUnpacker(userArgs: UserArgs) {
export function createUnpacker(args: UserArgs = {}) {
return (data: object) => {
if (typeof userArgs['_count'] === 'boolean') {
if (typeof args['_count'] === 'boolean') {
data['_count'] = data['_count']['_all']
}

Expand All @@ -61,17 +60,16 @@ export function createUnpacker(userArgs: UserArgs) {
/**
* Executes the `.aggregate` action on a model.
* @see {desugarUserArgs}
* @param client to provide dmmf information
* @param userArgs the user input to desugar
* @param args the user input to desugar
* @param modelAction a callback action that triggers request execution
* @returns
*/
export function aggregate(client: Client, userArgs: UserArgs | undefined, modelAction: ModelAction) {
const aggregateArgs = desugarUserArgs(userArgs ?? {})
const aggregateUnpacker = createUnpacker(userArgs ?? {})
export function aggregate(args: UserArgs | undefined, modelAction: ModelAction) {
const aggregateUnpacker = createUnpacker(args)

return modelAction({
action: 'aggregate',
unpacker: aggregateUnpacker,
})(aggregateArgs)
argsMapper: desugarUserArgs,
})(args)
}
53 changes: 37 additions & 16 deletions packages/client/src/runtime/core/model/aggregates/count.ts
@@ -1,27 +1,48 @@
import type { Client } from '../../../getPrismaClient'
import type { ModelAction } from '../applyModel'
import type { UserArgs } from '../UserArgs'
import { aggregate } from './aggregate'
import { createUnpacker as createUnpackerAggregate, desugarUserArgs as desugarUserArgsAggregate } from './aggregate'

/**
* Executes the `.count` action on a model via {@link aggregate}.
* @param client to provide dmmf information
* @param userArgs the user input to desugar
* @param modelAction a callback action that triggers request execution
* Transforms the `userArgs` for the `.count` shorthand. It is an API sugar. It
* reuses the logic from the `.aggregate` shorthand to add additional handling.
* The goal here is to desugar it into something that is understood by the QE.
* @param args to transform
* @returns
*/
export function count(client: Client, userArgs: UserArgs | undefined, modelAction: ModelAction) {
const { select, ..._userArgs } = userArgs ?? {} // exclude select
function desugarUserArgs(args: UserArgs = {}) {
const { select, ..._args } = args // exclude select

// count is an aggregate, we reuse that but hijack its unpacker
if (typeof select === 'object') {
// we transpose the original select field into the _count field
return aggregate(client, { ..._userArgs, _count: select }, (p) =>
modelAction({ ...p, action: 'count', unpacker: (data) => p.unpacker?.(data)['_count'] }),
) // for count selects, return the relevant part of the result
return desugarUserArgsAggregate({ ..._args, _count: select })
} else {
return desugarUserArgsAggregate({ ..._args, _count: { _all: true } })
}
}

/**
* Creates an unpacker that adds sugar to the basic result of the QE. An
* unpacker helps to transform a result before returning it to the user.
* @param args the user input
* @returns
*/
export function createUnpacker(args: UserArgs = {}) {
if (typeof args['select'] === 'object') {
return (data: object) => createUnpackerAggregate(args)(data)['_count']
} else {
return aggregate(client, { ..._userArgs, _count: { _all: true } }, (p) =>
modelAction({ ...p, action: 'count', unpacker: (data) => p.unpacker?.(data)['_count']['_all'] }),
) // for simple counts, just return the result that is a number
return (data: object) => createUnpackerAggregate(args)(data)['_count']['_all']
}
}

/**
* Executes the `.count` action on a model via {@link aggregate}.
* @param args the user input to desugar
* @param modelAction a callback action that triggers request execution
* @returns
*/
export function count(args: UserArgs | undefined, modelAction: ModelAction) {
return modelAction({
action: 'count',
unpacker: createUnpacker(args),
argsMapper: desugarUserArgs,
})(args)
}
34 changes: 15 additions & 19 deletions packages/client/src/runtime/core/model/aggregates/groupBy.ts
@@ -1,4 +1,3 @@
import type { Client } from '../../../getPrismaClient'
import type { ModelAction } from '../applyModel'
import type { UserArgs } from '../UserArgs'
import { desugarUserArgs as desugarUserArgsAggregate } from './aggregate'
Expand All @@ -8,33 +7,33 @@ import { desugarUserArgs as desugarUserArgsAggregate } from './aggregate'
* It reuses the logic from the `.aggregate` shorthand and adds additional
* handling for the `by` clause. The goal here is to desugar it into something
* that is understood by the QE.
* @param userArgs to transform
* @param args to transform
* @returns
*/
function desugarUserArgs(userArgs: UserArgs) {
const _userArgs = desugarUserArgsAggregate(userArgs)
function desugarUserArgs(args: UserArgs = {}) {
const _args = desugarUserArgsAggregate(args)

// we desugar the array into { [key]: boolean }
if (Array.isArray(userArgs['by'])) {
for (const key of userArgs['by']) {
if (Array.isArray(_args['by'])) {
for (const key of _args['by']) {
if (typeof key === 'string') {
_userArgs['select'][key] = true
_args['select'][key] = true
}
}
}

return _userArgs
return _args
}

/**
* Creates an unpacker that adds sugar to the basic result of the QE. An
* unpacker helps to transform a result before returning it to the user.
* @param userArgs the user input
* @param args the user input
* @returns
*/
export function createUnpacker(userArgs: UserArgs) {
export function createUnpacker(args: UserArgs = {}) {
return (data: object[]) => {
if (typeof userArgs['_count'] === 'boolean') {
if (typeof args?.['_count'] === 'boolean') {
data.forEach((row) => {
row['_count'] = row['_count']['_all']
})
Expand All @@ -46,17 +45,14 @@ export function createUnpacker(userArgs: UserArgs) {

/**
* Executes the `.groupBy` action on a model by reusing {@link aggregate}.
* @param client to provide dmmf information
* @param userArgs the user input to desugar
* @param args the user input to desugar
* @param modelAction a callback action that triggers request execution
* @returns
*/
export function groupBy(client: Client, userArgs: UserArgs | undefined, modelAction: ModelAction) {
const groupByArgs = desugarUserArgs(userArgs ?? {})
const groupByUnpacker = createUnpacker(userArgs ?? {})

export function groupBy(args: UserArgs | undefined, modelAction: ModelAction) {
return modelAction({
action: 'groupBy',
unpacker: groupByUnpacker,
})(groupByArgs)
unpacker: createUnpacker(args),
argsMapper: desugarUserArgs,
})(args)
}
7 changes: 3 additions & 4 deletions packages/client/src/runtime/core/model/applyAggregates.ts
Expand Up @@ -11,16 +11,15 @@ import type { UserArgs } from './UserArgs'
* short, we manipulate the user input which is designed to have DX to transform
* it into something that the engines understand. Similarly, we take the engine
* output for that input and produce something that is easier to work with.
* @param client to provide dmmf information
* @param action that tells which aggregate action to execute
* @param modelAction a callback action that triggers request execution
* @returns
*/
export function applyAggregates(client: Client, action: Action, modelAction: ModelAction) {
// we effectively take over the aggregate api to perform data changes
if (action === 'aggregate') return (userArgs?: UserArgs) => aggregate(client, userArgs, modelAction)
if (action === 'count') return (userArgs?: UserArgs) => count(client, userArgs, modelAction)
if (action === 'groupBy') return (userArgs?: UserArgs) => groupBy(client, userArgs, modelAction)
if (action === 'aggregate') return (userArgs?: UserArgs) => aggregate(userArgs, modelAction)
if (action === 'count') return (userArgs?: UserArgs) => count(userArgs, modelAction)
if (action === 'groupBy') return (userArgs?: UserArgs) => groupBy(userArgs, modelAction)

return undefined
}
7 changes: 7 additions & 0 deletions packages/client/src/runtime/getPrismaClient.ts
Expand Up @@ -30,6 +30,7 @@ import { PrismaClientValidationError } from '.'
import { $extends, Args as Extension } from './core/extensions/$extends'
import { MetricsClient } from './core/metrics/MetricsClient'
import { applyModelsAndClientExtensions } from './core/model/applyModelsAndClientExtensions'
import { UserArgs } from './core/model/UserArgs'
import { createPrismaPromise } from './core/request/createPrismaPromise'
import type {
InteractiveTransactionOptions,
Expand Down Expand Up @@ -181,6 +182,8 @@ export type InternalRequestParams = {
unpacker?: Unpacker // TODO what is this
lock?: PromiseLike<void>
otelParentCtx?: Context
/** Used to "desugar" a user input into an "expanded" one */
argsMapper?: (args?: UserArgs) => UserArgs
} & Omit<QueryMiddlewareParams, 'runInTransaction'>

// only used by the .use() hooks
Expand Down Expand Up @@ -1106,6 +1109,7 @@ new PrismaClient({
action,
model,
headers,
argsMapper,
transaction,
lock,
unpacker,
Expand All @@ -1115,6 +1119,9 @@ new PrismaClient({
this._dmmf = await this._getDmmf({ clientMethod, callsite })
}

// execute argument transformation before execution
args = argsMapper ? argsMapper(args) : args

let rootField: string | undefined
const operation = actionOperationMap[action]

Expand Down
@@ -0,0 +1,9 @@
import { defineMatrix } from '../../_utils/defineMatrix'

export default defineMatrix(() => [
[
{
provider: 'sqlite',
},
],
])
@@ -0,0 +1,19 @@
import { idForProvider } from '../../../_utils/idForProvider'
import testMatrix from '../_matrix'

export default testMatrix.setupSchema(({ provider }) => {
return /* Prisma */ `
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "${provider}"
url = env("DATABASE_URI_${provider}")
}
model Resource {
id ${idForProvider(provider)}
}
`
})

0 comments on commit 70a8456

Please sign in to comment.