Skip to content

Commit

Permalink
feat(client): wip - aggregate
Browse files Browse the repository at this point in the history
  • Loading branch information
timsuchanek committed Jul 3, 2020
1 parent afc33d2 commit 3d5e41a
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 114 deletions.
115 changes: 27 additions & 88 deletions src/packages/client/fixtures/blog/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,97 +5,36 @@ const prisma = new PrismaClient({
})

async function main() {
;(prisma as any).use('all', async ({ params, fetch }) => {
console.log(params.clientMethod, `before`, params)
const data = await fetch(params)
console.log(params.clientMethod, data)
data.push({
name: 'fake user',
})
console.log(params.clientMethod, `after`)
return data
})
const users = await prisma.transaction([
prisma.user.findMany({
include: {
posts: {
include: {
author: true,
},
orderBy: {
title: 'asc',
},
},
},
}),
prisma.user.findMany({
include: {
posts: {
include: {
author: true,
},
orderBy: {
title: 'asc',
},
},
},
}),
])

prisma.post.create({
data: {
author: {
connectOrCreate: {
where: {
email: 'a@a.de',
},
create: {
email: 'a@a.de',
},
},
},
published: true,
title: 'Title',
const result = await prisma.user.aggregate({
avg: {
age: true,
blub: true,
},
sum: {
age: true,
},
min: {
age: true,
},
max: {
age: true,
},
count: true,
})
console.log(result)
prisma.disconnect()
}

console.log(users)

// const result = await prisma.user.updateMany({
// data: {
// id: null,
// },
// })

// console.log(result)

// // prisma.disconnect()

// const user = await prisma.post.findOne({
// include: {
// author: true,
// },
// where: {
// id: '',
// },
// })

// const x = await prisma.post.update({
// where: {
// id: '',
// },
// data: {
// id: '',
// published: true,
// title: null,
// },
// })

// prisma.post.findMany({
// where: {
// title: null,
// },
// })
async function seed() {
for (let i = 0; i < 10; i++) {
await prisma.user.create({
data: {
email: `a${i}@asd.de`,
age: 29,
name: 'Bob',
},
})
}
}

main().catch((e) => {
Expand Down
3 changes: 2 additions & 1 deletion src/packages/client/fixtures/blog/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ datasource db {
generator client {
provider = "prisma-client-js"
binaryTargets = ["native"]
experimentalFeatures = ["transactionApi", "connectOrCreate"]
experimentalFeatures = ["connectOrCreate", "aggregations"]
}

model User {
id String @default(cuid()) @id
email String @unique
name String?
age Int?
posts Post[]
Like Like[]
}
Expand Down
7 changes: 6 additions & 1 deletion src/packages/client/src/generation/TSClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -992,12 +992,17 @@ ${actionName}<T extends ${getModelArgName(name, actionName)}>(
tab,
)}
/**
*
* Count
*/
count(args?: Omit<${getModelArgName(
name,
DMMF.ModelAction.findMany,
)}, 'select' | 'include'>): Promise<number>
/**
* Aggregate
*/
aggregate(args: any): Promise<any>
}
/**
Expand Down
60 changes: 54 additions & 6 deletions src/packages/client/src/runtime/getPrismaClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ export interface GetPrismaClientOptions {
engineVersion?: string
}

const aggregateKeys = {
avg: true,
count: true,
sum: true,
min: true,
max: true,
}

// TODO: We **may** be able to get real types. However, we have both a bootstrapping
// problem here, that we want to return a type that's not yet defined
// and we're typecasting this anyway later
Expand Down Expand Up @@ -247,6 +255,11 @@ export function getPrismaClient(config: GetPrismaClientOptions): any {
cwd = config.dirname
}

const experimentalFeatures = config.generator?.experimentalFeatures ?? []
if (!experimentalFeatures.includes('aggregations')) {
experimentalFeatures.push('aggregations')
}

this.engineConfig = {
cwd,
debug: useDebug,
Expand All @@ -269,9 +282,7 @@ export function getPrismaClient(config: GetPrismaClientOptions): any {
env: envFile,
flags: [],
clientVersion: config.clientVersion,
enableExperimental: mapExperimentalFeatures(
config.generator?.experimentalFeatures,
),
enableExperimental: mapExperimentalFeatures(experimentalFeatures),
}

const sanitizedEngineConfig = omit(this.engineConfig, [
Expand Down Expand Up @@ -628,7 +639,12 @@ export function getPrismaClient(config: GetPrismaClientOptions): any {
const query = String(document)
debug(`Prisma Client call:`)
debug(
`prisma.${clientMethod}(${printJsonWithErrors(args, [], [], [])})`,
`prisma.${clientMethod}(${printJsonWithErrors({
ast: args,
keyPaths: [],
valuePaths: [],
missingItems: [],
})})`,
)
debug(`Generated request:`)
debug(query + '\n')
Expand Down Expand Up @@ -784,16 +800,48 @@ export function getPrismaClient(config: GetPrismaClientOptions): any {
delegate.count = (args) =>
clients[mapping.model]({
operation: 'query',
actionName: 'count',
actionName: 'count', // actionName is just cosmetics 💅🏽
rootField: mapping.aggregate,
args: args
? {
select: { count: args },
...args,
select: { count: true },
}
: undefined,
dataPath: ['count'],
})

delegate.aggregate = (args) => {
/**
* avg, count, sum, min, max need to go into select
* For speed reasons we can go with "for in "
*/
const select = Object.entries(args).reduce((acc, [key, value]) => {
if (aggregateKeys[key]) {
if (!acc.select) {
acc.select = {}
}
// `count` doesn't have a subselection
if (key === 'count') {
acc.select[key] = value
} else {
acc.select[key] = { select: value }
}
} else {
acc[key] = value
}
return acc
}, {} as any)

return clients[mapping.model]({
operation: 'query',
actionName: 'aggregate', // actionName is just cosmetics 💅🏽
rootField: mapping.aggregate,
args: select,
dataPath: [],
})
}

this[lowerCaseModel] = delegate
}
}
Expand Down
78 changes: 71 additions & 7 deletions src/packages/client/src/runtime/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import { deepExtend } from './utils/deep-extend'
import { deepGet } from './utils/deep-set'
import { filterObject } from './utils/filterObject'
import { omit } from './utils/omit'
import { MissingItem, printJsonWithErrors } from './utils/printJsonErrors'
import {
MissingItem,
printJsonWithErrors,
PrintJsonWithErrorsArgs,
} from './utils/printJsonErrors'
import { printStack } from './utils/printStack'
import stringifyObject from './utils/stringifyObject'
import { visit } from './visit'
Expand Down Expand Up @@ -133,6 +137,9 @@ ${indent(this.children.map(String).join('\n'), tab)}

const fieldType = fieldError.error.field.outputType
.type as DMMF.OutputType

console.log(fieldError)

fieldType.fields
.filter((field) =>
fieldError.error.type === 'emptyInclude'
Expand Down Expand Up @@ -235,13 +242,21 @@ ${fieldErrors
showColors: errorFormat && errorFormat === 'pretty',
})

let printJsonArgs: PrintJsonWithErrorsArgs = {
ast: isTopLevelQuery ? { [topLevelQueryName]: select } : select,
keyPaths,
valuePaths,
missingItems,
}

// as for aggregate we simplify the api to not include `select`
// we need to map this here so the errors make sense
if (originalMethod?.endsWith('aggregate')) {
printJsonArgs = transformAggregatePrintJsonArgs(printJsonArgs)
}

const errorStr = `${stack}${indent(
printJsonWithErrors(
isTopLevelQuery ? { [topLevelQueryName]: select } : select,
keyPaths,
valuePaths,
missingItems,
),
printJsonWithErrors(printJsonArgs),
indentValue,
).slice(indentValue)}${chalk.dim(afterLines)}
Expand Down Expand Up @@ -1825,3 +1840,52 @@ export function getField(document: Document, path: string[]): Field {

return pointer!
}

function removeSelectFromPath(path: string): string {
return path
.split('.')
.filter((p) => p !== 'select')
.join('.')
}

function removeSelectFromObject(obj: object): object {
const type = Object.prototype.toString.call(obj)
if (type === '[object Object]') {
const copy = {}
for (const key in obj) {
if (key === 'select') {
for (const subKey in obj['select']) {
copy[subKey] = removeSelectFromObject(obj['select'][subKey])
}
} else {
copy[key] = removeSelectFromObject(obj[key])
}
}
return copy
}

return obj
}

function transformAggregatePrintJsonArgs({
ast,
keyPaths,
missingItems,
valuePaths,
}: PrintJsonWithErrorsArgs): PrintJsonWithErrorsArgs {
const newKeyPaths = keyPaths.map(removeSelectFromPath)
const newValuePaths = valuePaths.map(removeSelectFromPath)
const newMissingItems = missingItems.map((item) => ({
path: removeSelectFromPath(item.path),
isRequired: item.isRequired,
type: item.type,
}))

const newAst = removeSelectFromObject(ast)
return {
ast: newAst,
keyPaths: newKeyPaths,
missingItems: newMissingItems,
valuePaths: newValuePaths,
}
}
20 changes: 13 additions & 7 deletions src/packages/client/src/runtime/utils/printJsonErrors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import chalk from 'chalk'
import stripAnsi from 'strip-ansi'
import { dedent } from './dedent'
import { deepSet } from './deep-set'
import stringifyObject from './stringifyObject'

Expand All @@ -12,12 +11,19 @@ export interface MissingItem {

const DIM_TOKEN = '@@__DIM_POINTER__@@'

export function printJsonWithErrors(
ast: object,
keyPaths: string[],
valuePaths: string[],
missingItems: MissingItem[] = [],
) {
export type PrintJsonWithErrorsArgs = {
ast: object
keyPaths: string[]
valuePaths: string[]
missingItems: MissingItem[]
}

export function printJsonWithErrors({
ast,
keyPaths,
valuePaths,
missingItems,
}: PrintJsonWithErrorsArgs) {
let obj = ast
for (const { path, type } of missingItems) {
obj = deepSet(obj, path, type)
Expand Down

0 comments on commit 3d5e41a

Please sign in to comment.