メインコンテンツにスキップ

`query`: カスタムPrisma Clientクエリを作成

info

Prisma Client extensionsはバージョン4.16.0以降で一般利用可能です。バージョン4.7.0でプレビュー導入されました。4.16.0より前のバージョンを実行している場合は、clientExtensionsプレビュー機能フラグを有効にしてください。

query Prisma Client extensionsコンポーネントタイプを使用すると、クエリライフサイクルにフックして、受信クエリまたはその結果を変更できます。

Prisma Client extensionsのqueryコンポーネントを使用して、独立したクライアントを作成できます。これは、ミドルウェアの代替手段となります。特定のフィルターまたはユーザーに1つのクライアントをバインドし、別のフィルターまたはユーザーに別のクライアントをバインドできます。たとえば、行レベルセキュリティ(RLS)拡張機能でユーザー分離を実現するためにこれを行うことがあります。さらに、ミドルウェアとは異なり、query拡張コンポーネントはエンドツーエンドの型安全性を実現します。query拡張機能とミドルウェアの比較について詳細はこちら

Prisma Clientクエリ操作を拡張する

$extends クライアントレベルメソッドを使用して、拡張クライアントを作成します。拡張クライアントは、1つ以上の拡張機能でラップされた標準のPrisma Clientのバリアントです。

query拡張コンポーネントを使用してクエリを変更します。カスタムクエリは、次の場所で変更できます。

カスタムクエリを作成するには、次の構造を使用します。

const prisma = new PrismaClient().$extends({
name?: 'name',
query?: {
user: { ... } // in this case, we add a query to the `user` model
},
});

プロパティは次のとおりです。

  • name:(オプション)エラーログに表示される拡張機能の名前を指定します。
  • query:カスタムクエリを定義します。

特定のモデル内の特定の操作を変更する

queryオブジェクトには、findUnique()findFirstfindManycountcreateなどのPrisma Client操作の名前にマップする関数を含めることができます。次の例では、18歳より年上のユーザーのみを検索するカスタマイズされたクエリを使用するようにuser.findManyを変更します。

const prisma = new PrismaClient().$extends({
query: {
user: {
async findMany({ model, operation, args, query }) {
// take incoming `where` and set `age`
args.where = { ...args.where, age: { gt: 18 } }

return query(args)
},
},
},
})

await prisma.user.findMany() // returns users whose age is greater than 18

上記の例では、prisma.user.findManyの呼び出しはquery.user.findManyをトリガーします。各コールバックは、クエリを記述するタイプセーフな{ model, operation, args, query }オブジェクトを受け取ります。このオブジェクトには、次のプロパティがあります。

  • model:拡張するクエリを含むモデルの名前。

    上記の例では、model"User"型の文字列です。

  • operation:拡張および実行される操作の名前。

    上記の例では、operation"findMany"型の文字列です。

  • args:拡張される特定のクエリ入力情報。

    これは、クエリが実行される前に変更できるタイプセーフなオブジェクトです。args内の任意のプロパティを変更できます。例外:includeまたはselectは変更できません。これは、予期される出力タイプが変更され、型安全性が損なわれるためです。

  • query:クエリの結果のPromise。

    • このPromiseの値はタイプセーフであるため、awaitを使用してからこのPromiseの結果を変更できます。TypeScriptは、オブジェクトに対する安全でない変更をキャッチします。

スキーマのすべてのモデル内の特定の操作を変更する

スキーマのすべてのモデルのクエリを拡張するには、特定のモデル名の代わりに$allModelsを使用します。例:

const prisma = new PrismaClient().$extends({
query: {
$allModels: {
async findMany({ model, operation, args, query }) {
// set `take` and fill with the rest of `args`
args = { ...args, take: 100 }

return query(args)
},
},
},
})

特定のモデル内のすべての操作を変更する

$allOperationsを使用して、特定のモデル内のすべての操作を拡張します。

たとえば、次のコードは、userモデルのすべての操作にカスタムクエリを適用します。

const prisma = new PrismaClient().$extends({
query: {
user: {
$allOperations({ model, operation, args, query }) {
/* your custom logic here */
return query(args)
},
},
},
})

すべてのPrisma Client操作を変更する

$allOperationsメソッドを使用して、Prisma Clientに存在するすべてのクエリメソッドを変更します。$allOperationsは、モデル操作とraw queryの両方で使用できます。

すべてのメソッドを次のように変更できます。

const prisma = new PrismaClient().$extends({
query: {
$allOperations({ model, operation, args, query }) {
/* your custom logic for modifying all Prisma Client operations here */
return query(args)
},
},
})

raw queryが呼び出された場合、コールバックに渡されるmodel引数はundefinedになります。

たとえば、$allOperationsメソッドを使用して、次のようにクエリをログに記録できます。

const prisma = new PrismaClient().$extends({
query: {
async $allOperations({ operation, model, args, query }) {
const start = performance.now()
const result = await query(args)
const end = performance.now()
const time = end - start
console.log(
util.inspect(
{ model, operation, args, time },
{ showHidden: false, depth: null, colors: true }
)
)
return result
},
},
})

スキーマのすべてのモデル内のすべての操作を変更する

$allModelsおよび$allOperationsを使用して、スキーマのすべてのモデル内のすべての操作を拡張します。

スキーマのすべてのモデルのすべての操作にカスタムクエリを適用するには:

const prisma = new PrismaClient().$extends({
query: {
$allModels: {
$allOperations({ model, operation, args, query }) {
/* your custom logic for modifying all operations on all models here */
return query(args)
},
},
},
})

トップレベルraw query操作を変更する

特定のトップレベルraw query操作にカスタム動作を適用するには、モデル名の代わりにトップレベルraw query関数の名前を使用します。

const prisma = new PrismaClient().$extends({
query: {
$queryRaw({ args, query, operation }) {
// handle $queryRaw operation
return query(args)
},
$executeRaw({ args, query, operation }) {
// handle $executeRaw operation
return query(args)
},
$queryRawUnsafe({ args, query, operation }) {
// handle $queryRawUnsafe operation
return query(args)
},
$executeRawUnsafe({ args, query, operation }) {
// handle $executeRawUnsafe operation
return query(args)
},
},
})

クエリの結果を変更する

awaitを使用してから、query Promiseの結果を変更できます。

const prisma = new PrismaClient().$extends({
query: {
user: {
async findFirst({ model, operation, args, query }) {
const user = await query(args)

if (user.password !== undefined) {
user.password = '******'
}

return user
},
},
},
})
info

上記の例を含めたのは、これが可能であることを示すためです。ただし、パフォーマンス上の理由から、既存のフィールドをオーバーライドするには、resultコンポーネントタイプを使用することをお勧めします。resultコンポーネントタイプは、アクセス時にのみ計算するため、通常、この状況でより優れたパフォーマンスを発揮します。queryコンポーネントタイプは、クエリ実行後に計算します。

クエリをバッチトランザクションにラップする

拡張クエリをバッチトランザクションにラップできます。たとえば、これを使用して行レベルセキュリティ(RLS)を実施できます。

次の例では、findFirstを拡張して、バッチトランザクションで実行されるようにします。

const transactionExtension = Prisma.defineExtension((prisma) => 
prisma.$extends({
query: {
user: {
// Get the input `args` and a callback to `query`
async findFirst({ args, query, operation }) {
const [result] = await prisma.$transaction([query(args)]) // wrap the query in a batch transaction, and destructure the result to return an array
return result // return the first result found in the array
},
},
},
})
)
const prisma = new PrismaClient().$extends(transactionExtension)

Query extensionsとミドルウェアの比較

query extensionsまたはミドルウェアを使用して、クエリライフサイクルにフックし、受信クエリまたはその結果を変更できます。Client extensionsとミドルウェアは、次の点で異なります。

  • ミドルウェアは常に同じクライアントにグローバルに適用されます。Client extensionsは、意図的に組み合わせない限り分離されています。Client extensionsの詳細はこちら
    • たとえば、行レベルセキュリティ(RLS)シナリオでは、各ユーザーを完全に分離されたクライアントに保持できます。ミドルウェアを使用すると、すべてのユーザーが同じクライアントでアクティブになります。
  • アプリケーションの実行中に、extensionsを使用すると、1つ以上の拡張クライアントまたは標準のPrisma Clientから選択できます。ミドルウェアを使用すると、使用するクライアントを選択できません。グローバルクライアントが1つしかないためです。
  • Extensionsはエンドツーエンドの型安全性と推論の恩恵を受けますが、ミドルウェアは受けません。

ミドルウェアを使用できるすべてのシナリオでPrisma Client extensionsを使用できます。

query拡張コンポーネントとミドルウェアを使用する場合

プロジェクトでquery拡張コンポーネントとミドルウェアを使用する場合、次のルールと優先順位が適用されます。

  • アプリケーションコードでは、メインのPrisma Clientインスタンスですべてのミドルウェアを宣言する必要があります。拡張クライアントで宣言することはできません。
  • queryコンポーネントを備えたミドルウェアとextensionsが実行される状況では、Prisma Clientは、queryコンポーネントを備えたextensionsを実行する前にミドルウェアを実行します。Prisma Clientは、$useまたは$extendsでインスタンス化した順序で個々のミドルウェアとextensionsを実行します。