Skip to content

Commit

Permalink
js-adapters: Better result type (#4274)
Browse files Browse the repository at this point in the history
Adds convenience functions for creating result types: `err` and `ok`,
similar to Rust ones.

Adds `map` and `flatMap` methods on `Result` type for easier way to work
with a `Result` on a happy path.

Split from #4213
  • Loading branch information
Serhii Tatarintsev authored Sep 22, 2023
1 parent 818b9fc commit c78977b
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 57 deletions.
16 changes: 8 additions & 8 deletions query-engine/driver-adapters/js/adapter-neon/src/neon.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type neon from '@neondatabase/serverless'
import { Debug } from '@prisma/driver-adapter-utils'
import { Debug, ok, err } from '@prisma/driver-adapter-utils'
import type {
DriverAdapter,
ResultSet,
Expand Down Expand Up @@ -36,7 +36,7 @@ abstract class NeonQueryable implements Queryable {
rows,
}

return { ok: true, value: resultSet }
return ok(resultSet)
}

async executeRaw(query: Query): Promise<Result<number>> {
Expand All @@ -46,7 +46,7 @@ abstract class NeonQueryable implements Queryable {
const { rowCount: rowsAffected } = await this.performIO(query)

// Note: `rowsAffected` can sometimes be null (e.g., when executing `"BEGIN"`)
return { ok: true, value: rowsAffected ?? 0 }
return ok(rowsAffected ?? 0)
}

abstract performIO(query: Query): Promise<PerformIOResult>
Expand Down Expand Up @@ -82,14 +82,14 @@ class NeonTransaction extends NeonWsQueryable<neon.PoolClient> implements Transa
debug(`[js::commit]`)

this.client.release()
return Promise.resolve({ ok: true, value: undefined })
return Promise.resolve(ok(undefined))
}

async rollback(): Promise<Result<void>> {
debug(`[js::rollback]`)

this.client.release()
return Promise.resolve({ ok: true, value: undefined })
return Promise.resolve(ok(undefined))
}
}

Expand All @@ -109,15 +109,15 @@ export class PrismaNeon extends NeonWsQueryable<neon.Pool> implements DriverAdap
debug(`${tag} options: %O`, options)

const connection = await this.client.connect()
return { ok: true, value: new NeonTransaction(connection, options) }
return ok(new NeonTransaction(connection, options))
}

async close() {
if (this.isRunning) {
await this.client.end()
this.isRunning = false
}
return { ok: true as const, value: undefined }
return ok(undefined)
}
}

Expand All @@ -139,6 +139,6 @@ export class PrismaNeonHTTP extends NeonQueryable implements DriverAdapter {
}

async close() {
return { ok: true as const, value: undefined }
return ok(undefined)
}
}
37 changes: 21 additions & 16 deletions query-engine/driver-adapters/js/adapter-pg/src/pg.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import type pg from 'pg'
import { Debug } from '@prisma/driver-adapter-utils'
import type { DriverAdapter, Query, Queryable, Result, ResultSet, Transaction, TransactionOptions } from '@prisma/driver-adapter-utils'
import { Debug, ok } from '@prisma/driver-adapter-utils'
import type {
DriverAdapter,
Query,
Queryable,
Result,
ResultSet,
Transaction,
TransactionOptions,
} from '@prisma/driver-adapter-utils'
import { fieldToColumnType } from './conversion'

const debug = Debug('prisma:driver-adapter:pg')

type StdClient = pg.Pool
type TransactionClient = pg.PoolClient

class PgQueryable<ClientT extends StdClient | TransactionClient>
implements Queryable {
class PgQueryable<ClientT extends StdClient | TransactionClient> implements Queryable {
readonly flavour = 'postgres'

constructor(protected readonly client: ClientT) {
}
constructor(protected readonly client: ClientT) {}

/**
* Execute a query given as SQL, interpolating the given parameters.
Expand All @@ -25,14 +31,14 @@ class PgQueryable<ClientT extends StdClient | TransactionClient>
const { fields, rows } = await this.performIO(query)

const columns = fields.map((field) => field.name)
const columnTypes = fields.map((field) => fieldToColumnType(field.dataTypeID));
const columnTypes = fields.map((field) => fieldToColumnType(field.dataTypeID))
const resultSet: ResultSet = {
columnNames: columns,
columnTypes,
rows,
}

return { ok: true, value: resultSet }
return ok(resultSet)
}

/**
Expand All @@ -45,9 +51,9 @@ class PgQueryable<ClientT extends StdClient | TransactionClient>
debug(`${tag} %O`, query)

const { rowCount: rowsAffected } = await this.performIO(query)

// Note: `rowsAffected` can sometimes be null (e.g., when executing `"BEGIN"`)
return { ok: true, value: rowsAffected ?? 0 }
return ok(rowsAffected ?? 0)
}

/**
Expand All @@ -69,8 +75,7 @@ class PgQueryable<ClientT extends StdClient | TransactionClient>
}
}

class PgTransaction extends PgQueryable<TransactionClient>
implements Transaction {
class PgTransaction extends PgQueryable<TransactionClient> implements Transaction {
constructor(client: pg.PoolClient, readonly options: TransactionOptions) {
super(client)
}
Expand All @@ -79,14 +84,14 @@ class PgTransaction extends PgQueryable<TransactionClient>
debug(`[js::commit]`)

this.client.release()
return Promise.resolve({ ok: true, value: undefined })
return ok(undefined)
}

async rollback(): Promise<Result<void>> {
debug(`[js::rollback]`)

this.client.release()
return Promise.resolve({ ok: true, value: undefined })
return ok(undefined)
}
}

Expand All @@ -104,10 +109,10 @@ export class PrismaPg extends PgQueryable<StdClient> implements DriverAdapter {
debug(`${tag} options: %O`, options)

const connection = await this.client.connect()
return { ok: true, value: new PgTransaction(connection, options) }
return ok(new PgTransaction(connection, options))
}

async close() {
return { ok: true as const, value: undefined }
return ok(undefined)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type planetScale from '@planetscale/database'
import { Debug } from '@prisma/driver-adapter-utils'
import { Debug, ok } from '@prisma/driver-adapter-utils'
import type {
DriverAdapter,
ResultSet,
Expand Down Expand Up @@ -46,7 +46,7 @@ class PlanetScaleQueryable<ClientT extends planetScale.Connection | planetScale.
lastInsertId,
}

return { ok: true, value: resultSet }
return ok(resultSet)
}

/**
Expand All @@ -59,7 +59,7 @@ class PlanetScaleQueryable<ClientT extends planetScale.Connection | planetScale.
debug(`${tag} %O`, query)

const { rowsAffected } = await this.performIO(query)
return { ok: true, value: rowsAffected }
return ok(rowsAffected)
}

/**
Expand Down Expand Up @@ -97,14 +97,14 @@ class PlanetScaleTransaction extends PlanetScaleQueryable<planetScale.Transactio
debug(`[js::commit]`)

this.txDeferred.resolve()
return Promise.resolve({ ok: true, value: await this.txResultPromise })
return Promise.resolve(ok(await this.txResultPromise))
}

async rollback(): Promise<Result<void>> {
debug(`[js::rollback]`)

this.txDeferred.reject(new RollbackError())
return Promise.resolve({ ok: true, value: await this.txResultPromise })
return Promise.resolve(ok(await this.txResultPromise))
}
}

Expand All @@ -127,7 +127,7 @@ export class PrismaPlanetScale extends PlanetScaleQueryable<planetScale.Connecti
const [txDeferred, deferredPromise] = createDeferred<void>()
const txWrapper = new PlanetScaleTransaction(tx, options, txDeferred, txResultPromise)

resolve({ ok: true, value: txWrapper })
resolve(ok(txWrapper))
return deferredPromise
})
.catch((error) => {
Expand All @@ -143,6 +143,6 @@ export class PrismaPlanetScale extends PlanetScaleQueryable<planetScale.Connecti
}

async close() {
return { ok: true as const, value: undefined }
return ok(undefined)
}
}
17 changes: 4 additions & 13 deletions query-engine/driver-adapters/js/driver-adapter-utils/src/binder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import type {
ErrorCapturingDriverAdapter,
DriverAdapter,
Transaction,
ErrorRegistry,
ErrorRecord,
Result,
} from './types'
import { Result, err, ok } from './result'
import type { ErrorCapturingDriverAdapter, DriverAdapter, Transaction, ErrorRegistry, ErrorRecord } from './types'

class ErrorRegistryInternal implements ErrorRegistry {
private registeredErrors: ErrorRecord[] = []
Expand Down Expand Up @@ -37,10 +31,7 @@ export const bindAdapter = (adapter: DriverAdapter): ErrorCapturingDriverAdapter
flavour: adapter.flavour,
startTransaction: async (...args) => {
const result = await startTransaction(...args)
if (result.ok) {
return { ok: true, value: bindTransaction(errorRegistry, result.value) }
}
return result
return result.map((tx) => bindTransaction(errorRegistry, tx))
},
close: wrapAsync(errorRegistry, adapter.close.bind(adapter)),
}
Expand Down Expand Up @@ -68,7 +59,7 @@ function wrapAsync<A extends unknown[], R>(
return await fn(...args)
} catch (error) {
const id = registry.registerNewError(error)
return { ok: false, error: { kind: 'GenericJsError', id } }
return err({ kind: 'GenericJsError', id })
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { bindAdapter } from './binder'
export { ColumnTypeEnum } from './const'
export { Debug } from './debug'
export { ok, err, type Result } from './result'
export type * from './types'
41 changes: 41 additions & 0 deletions query-engine/driver-adapters/js/driver-adapter-utils/src/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Error } from './types'
export type Result<T> = {
// common methods
map<U>(fn: (value: T) => U): Result<U>
flatMap<U>(fn: (value: T) => Result<U>): Result<U>
} & (
| {
readonly ok: true
readonly value: T
}
| {
readonly ok: false
readonly error: Error
}
)

export function ok<T>(value: T): Result<T> {
return {
ok: true,
value,
map(fn) {
return ok(fn(value))
},
flatMap(fn) {
return fn(value)
},
}
}

export function err<T>(error: Error): Result<T> {
return {
ok: false,
error,
map() {
return err(error)
},
flatMap() {
return err(error)
},
}
}
19 changes: 6 additions & 13 deletions query-engine/driver-adapters/js/driver-adapter-utils/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ColumnTypeEnum } from './const'
import { Result } from './result'

export type ColumnType = typeof ColumnTypeEnum[keyof typeof ColumnTypeEnum]
export type ColumnType = (typeof ColumnTypeEnum)[keyof typeof ColumnTypeEnum]

export interface ResultSet {
/**
Expand Down Expand Up @@ -33,33 +34,25 @@ export type Query = {
}

export type Error = {
kind: 'GenericJsError',
kind: 'GenericJsError'
id: number
}

export type Result<T> = {
ok: true,
value: T
} | {
ok: false,
error: Error
}

export interface Queryable {
export interface Queryable {
readonly flavour: 'mysql' | 'postgres' | 'sqlite'

/**
* Execute a query given as SQL, interpolating the given parameters,
* and returning the type-aware result set of the query.
*
*
* This is the preferred way of executing `SELECT` queries.
*/
queryRaw(params: Query): Promise<Result<ResultSet>>

/**
* Execute a query given as SQL, interpolating the given parameters,
* and returning the number of affected rows.
*
*
* This is the preferred way of executing `INSERT`, `UPDATE`, `DELETE` queries,
* as well as transactional queries.
*/
Expand Down

0 comments on commit c78977b

Please sign in to comment.