Skip to content

Commit

Permalink
refactor: extract reusable parts of the client engine into a new pack…
Browse files Browse the repository at this point in the history
…age (#26330)

`@prisma/client-engine-runtime` being built by `@prisma/client`'s build
script meant that we still needed to include the client package and all
of its transitive dependencies into the workspace in
`libs/driver-adapters` in the `prisma-engines` repo, which was causing
some issues.

This commit properly extracts the reusable parts of the client engine
into a package for real. This is also important to make Accelerate work
with the query compiler.

Additionally, `@prisma/debug` is changed to be ESM compatible. This is
because `@prisma/client-engine-runtime` needs to be ESM compatible and
depends on `@prisma/debug`.
  • Loading branch information
aqrln authored Feb 14, 2025
1 parent 16be4b0 commit 839f215
Show file tree
Hide file tree
Showing 27 changed files with 351 additions and 251 deletions.
5 changes: 4 additions & 1 deletion packages/client-engine-runtime/helpers/build.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
// this package is actually built by `@prisma/client`
import { build } from '../../../helpers/compile/build'
import { adapterConfig } from '../../../helpers/compile/configs'

void build(adapterConfig)
3 changes: 3 additions & 0 deletions packages/client-engine-runtime/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
preset: '../../helpers/test/presets/default.js',
}
28 changes: 25 additions & 3 deletions packages/client-engine-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,42 @@
"name": "@prisma/client-engine-runtime",
"version": "0.0.0",
"description": "This package is intended for Prisma's internal use",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
}
},
"repository": {
"type": "git",
"url": "https://github.com/prisma/prisma.git",
"directory": "packages/accelerate-contract"
},
"license": "Apache-2.0",
"dependencies": {},
"dependencies": {
"@prisma/debug": "workspace:*",
"@prisma/driver-adapter-utils": "workspace:*"
},
"devDependencies": {
"@types/jest": "29.5.14",
"@types/node": "18.19.31",
"jest": "29.7.0",
"jest-junit": "16.0.0"
},
"scripts": {
"dev": "DEV=true tsx helpers/build.ts",
"build": "tsx helpers/build.ts",
"prepublishOnly": "pnpm run build"
"prepublishOnly": "pnpm run build",
"test": "jest"
},
"files": [
"dist"
Expand Down
6 changes: 6 additions & 0 deletions packages/client-engine-runtime/src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type QueryEvent = {
timestamp: Date
query: string
params: unknown[]
duration: number
}
10 changes: 10 additions & 0 deletions packages/client-engine-runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type { QueryEvent } from './events'
export { QueryInterpreter, type QueryInterpreterOptions } from './interpreter/QueryInterpreter'
export * from './QueryPlan'
export {
IsolationLevel,
type TransactionInfo,
type Options as TransactionOptions,
} from './transactionManager/Transaction'
export { TransactionManager } from './transactionManager/TransactionManager'
export { TransactionManagerError } from './transactionManager/TransactionManagerErrors'
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Query, Queryable } from '@prisma/driver-adapter-utils'

import { QueryEvent } from '../../common/types/Events'
import { QueryEvent } from '../events'
import { JoinExpression, QueryPlanNode } from '../QueryPlan'
import { renderQuery } from './renderQuery'
import { PrismaObject, ScopeBindings, Value } from './scope'
import { serialize } from './serializer'
import { serialize } from './serialize'

export type QueryInterpreterOptions = {
queryable: Queryable
Expand Down Expand Up @@ -158,16 +158,7 @@ export class QueryInterpreter {
timestamp,
duration: endInstant - startInstant,
query: query.sql,
// TODO: we should probably change the interface to contain a proper array in the next major version.
params: JSON.stringify(query.args),
// TODO: this field only exists for historical reasons as we grandfathered it from the time
// when we emitted `tracing` events to stdout in the engine unchanged, and then described
// them in the public API as TS types. Thus this field used to contain the name of the Rust
// module in which an event originated. When using library engine, which uses a different
// mechanism with a JavaScript callback for logs, it's normally just an empty string instead.
// This field is definitely not useful and should be removed from the public types (but it's
// technically a breaking change, even if a tiny and inconsequential one).
target: 'QueryInterpreter',
params: query.args,
})

return result
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ColumnTypeEnum } from '@prisma/driver-adapter-utils'

import { serialize } from '../../../../runtime/core/engines/client/interpreter/serializer'
import { serialize } from './serialize'

test('should serialize empty rows', () => {
const result = serialize({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ResultSet } from '@prisma/driver-adapter-utils'
import type { ResultSet } from '@prisma/driver-adapter-utils'

export function serialize(resultSet: ResultSet): Record<string, unknown>[] {
return resultSet.rows.map((row) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const enum IsolationLevel {
ReadUncommitted = 'ReadUncommitted',
ReadCommitted = 'ReadCommitted',
RepeatableRead = 'RepeatableRead',
Snapshot = 'Snapshot',
Serializable = 'Serializable',
}

export type Options = {
maxWait?: number
timeout?: number
isolationLevel?: IsolationLevel
}

export type TransactionInfo = {
id: string
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import type {
} from '@prisma/driver-adapter-utils'
import { ok } from '@prisma/driver-adapter-utils'

import { PrismaClientKnownRequestError } from '../../../errors/PrismaClientKnownRequestError'
import type * as Tx from '../../common/types/Transaction'
import { IsolationLevel } from '../../common/types/Transaction'
import { IsolationLevel, Options } from './Transaction'
import { TransactionManager } from './TransactionManager'
import {
InvalidTransactionIsolationLevelError,
Expand Down Expand Up @@ -83,7 +81,7 @@ class MockDriverAdapter implements DriverAdapter {
}
}

async function startTransaction(transactionManager: TransactionManager, options: Partial<Tx.Options> = {}) {
async function startTransaction(transactionManager: TransactionManager, options: Partial<Options> = {}) {
const [{ id }] = await Promise.all([
transactionManager.startTransaction({
timeout: TRANSACTION_EXECUTION_TIMEOUT,
Expand All @@ -97,7 +95,7 @@ async function startTransaction(transactionManager: TransactionManager, options:

test('transaction executes normally', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

const id = await startTransaction(transactionManager)

Expand All @@ -115,7 +113,7 @@ test('transaction executes normally', async () => {

test('transaction is rolled back', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

const id = await startTransaction(transactionManager)

Expand All @@ -133,7 +131,7 @@ test('transaction is rolled back', async () => {

test('transactions are rolled back when shutting down', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

const id1 = await startTransaction(transactionManager)
const id2 = await startTransaction(transactionManager)
Expand All @@ -156,7 +154,7 @@ test('transactions are rolled back when shutting down', async () => {

test('when driver adapter requires phantom queries does not execute transaction statements', async () => {
const driverAdapter = new MockDriverAdapter({ usePhantomQuery: true })
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

const id = await startTransaction(transactionManager)

Expand All @@ -172,7 +170,7 @@ test('when driver adapter requires phantom queries does not execute transaction

test('with explicit isolation level', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

const id = await startTransaction(transactionManager, { isolationLevel: IsolationLevel.Serializable })

Expand All @@ -191,7 +189,7 @@ test('with explicit isolation level', async () => {

test('for MySQL with explicit isolation level requires isolation level set before BEGIN', async () => {
const driverAdapter = new MockDriverAdapter({ provider: 'mysql' })
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

const id = await startTransaction(transactionManager, { isolationLevel: IsolationLevel.Serializable })

Expand All @@ -203,7 +201,7 @@ test('for MySQL with explicit isolation level requires isolation level set befor

test('for SQLite with unsupported isolation level', async () => {
const driverAdapter = new MockDriverAdapter({ provider: 'sqlite' })
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

await expect(
startTransaction(transactionManager, { isolationLevel: IsolationLevel.RepeatableRead }),
Expand All @@ -212,7 +210,7 @@ test('for SQLite with unsupported isolation level', async () => {

test('with isolation level only supported in MS SQL Server, "snapshot"', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

await expect(
startTransaction(transactionManager, { isolationLevel: IsolationLevel.Snapshot }),
Expand All @@ -221,7 +219,7 @@ test('with isolation level only supported in MS SQL Server, "snapshot"', async (

test('transaction times out during starting', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

await expect(startTransaction(transactionManager, { maxWait: START_TRANSACTION_TIME / 2 })).rejects.toBeInstanceOf(
TransactionStartTimoutError,
Expand All @@ -230,7 +228,7 @@ test('transaction times out during starting', async () => {

test('transaction times out during execution', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

const id = await startTransaction(transactionManager)

Expand All @@ -242,7 +240,7 @@ test('transaction times out during execution', async () => {

test('trying to commit or rollback invalid transaction id fails with TransactionNotFoundError', async () => {
const driverAdapter = new MockDriverAdapter()
const transactionManager = new TransactionManager({ driverAdapter, clientVersion: '1.0.0' })
const transactionManager = new TransactionManager({ driverAdapter })

await expect(transactionManager.commitTransaction('invalid-tx-id')).rejects.toBeInstanceOf(TransactionNotFoundError)
await expect(transactionManager.rollbackTransaction('invalid-tx-id')).rejects.toBeInstanceOf(TransactionNotFoundError)
Expand All @@ -252,12 +250,10 @@ test('trying to commit or rollback invalid transaction id fails with Transaction
expect(driverAdapter.rollbackMock).not.toHaveBeenCalled()
})

test('TransactionManagerErrors are PrismaClientKnownRequestError', () => {
const error = new TransactionManagerError('test message', { clientVersion: '1.0.0', meta: { foo: 'bar' } })
test('TransactionManagerErrors have common structure', () => {
const error = new TransactionManagerError('test message', { foo: 'bar' })

expect(error).toBeInstanceOf(PrismaClientKnownRequestError)
expect(error.code).toEqual('P2028')
expect(error.message).toEqual('Transaction API error: test message')
expect(error.clientVersion).toEqual('1.0.0')
expect(error.meta).toEqual({ foo: 'bar' })
})
Loading

0 comments on commit 839f215

Please sign in to comment.