`query`: カスタムPrisma Clientクエリを作成する
Prisma Client 拡張機能は、バージョン 4.16.0 以降で一般提供されています。これらはバージョン 4.7.0 でプレビューとして導入されました。バージョン 4.16.0 より前のバージョンで実行している場合は、`clientExtensions` プレビュー機能フラグを有効にしてください。
`query` Prisma Client 拡張機能コンポーネントタイプを使用して、クエリのライフサイクルにフックし、受信クエリまたはその結果を変更できます。
Prisma Client 拡張機能の `query` コンポーネントを使用すると、独立したクライアントを作成できます。これは、ミドルウェアの代替手段となります。1つのクライアントを特定のフィルターやユーザーにバインドし、別のクライアントを別のフィルターやユーザーにバインドすることができます。たとえば、行レベルセキュリティ (RLS) 拡張機能でユーザー分離を実現するためにこれを行うことができます。さらに、ミドルウェアとは異なり、`query` 拡張コンポーネントはエンドツーエンドの型安全性を提供します。`query` 拡張機能とミドルウェアの違いについて詳しくはこちら。
Prisma Clientクエリ操作を拡張する
`$extends` クライアントレベルメソッドを使用して、拡張クライアントを作成します。拡張クライアントは、1つ以上の拡張機能によってラップされた標準Prisma Clientのバリアントです。
`query` 拡張コンポーネントを使用してクエリを変更します。カスタムクエリは次のように変更できます。
- 特定のモデルにおける特定の操作
- スキーマのすべてのモデルにおける特定の操作
- すべてのPrisma Client操作
- 特定のモデルにおけるすべての操作
- スキーマのすべてのモデルにおけるすべての操作
- 特定のトップレベルの生クエリ操作
カスタムクエリを作成するには、以下の構造を使用します。
const prisma = new PrismaClient().$extends({
name?: 'name',
query?: {
user: { ... } // in this case, we add a query to the `user` model
},
});
プロパティは次のとおりです。
- `name`: (任意) エラーログに表示される拡張機能の名前を指定します。
- `query`: カスタムクエリを定義します。
特定のモデルにおける特定の操作を変更する
`query` オブジェクトには、`findUnique()`、`findFirst`、`findMany`、`count`、`create` などのPrisma Client 操作の名前とマッピングされる関数を含めることができます。次の例では、`user.findMany` を、18歳以上のユーザーのみを検索するカスタマイズされたクエリを使用するように変更します。
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`: クエリの結果に対するプロミス。
- `await` を使用してこのプロミスの結果を変更できます。その値は型安全だからです。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` は、モデル操作と生クエリの両方で使用できます。
すべてのメソッドは次のように変更できます。
const prisma = new PrismaClient().$extends({
query: {
$allOperations({ model, operation, args, query }) {
/* your custom logic for modifying all Prisma Client operations here */
return query(args)
},
},
})
生クエリが呼び出された場合、コールバックに渡される `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)
},
},
},
})
トップレベルの生クエリ操作を変更する
特定のトップレベルの生クエリ操作にカスタム動作を適用するには、モデル名の代わりにトップレベルの生クエリ関数の名前を使用します。
- リレーショナルデータベース
- MongoDB
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)
},
},
})
const prisma = new PrismaClient().$extends({
query: {
$runCommandRaw({ args, query, operation }) {
// handle $runCommandRaw operation
return query(args)
},
},
})
クエリの結果を変更する
`await` を使用して、`query` プロミスの結果を変更できます。
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
},
},
},
})
上記の例は、これが可能であることを示すために含めています。ただし、パフォーマンス上の理由から、既存のフィールドをオーバーライドするには、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)
クエリ拡張機能とミドルウェアの比較
クエリ拡張機能またはミドルウェアを使用して、クエリのライフサイクルにフックし、受信クエリまたはその結果を変更できます。クライアント拡張機能とミドルウェアは次の点で異なります。
- ミドルウェアは常に同じクライアントにグローバルに適用されます。クライアント拡張機能は、意図的に結合しない限り分離されます。クライアント拡張機能について詳しくはこちら。
- たとえば、行レベルセキュリティ (RLS) のシナリオでは、各ユーザーを完全に独立したクライアントに保持できます。ミドルウェアの場合、すべてのユーザーが同じクライアントでアクティブになります。
- アプリケーションの実行中、拡張機能を使用すると、1つ以上の拡張クライアント、または標準のPrisma Clientから選択できます。ミドルウェアの場合、グローバルクライアントが1つしかないため、どのクライアントを使用するかを選択できません。
- 拡張機能はエンドツーエンドの型安全性と型推論の恩恵を受けますが、ミドルウェアはそうではありません。
ミドルウェアが使用できるすべてのシナリオで、Prisma Client 拡張機能を使用できます。
`query` 拡張コンポーネントとミドルウェアを使用する場合
プロジェクトで `query` 拡張コンポーネントとミドルウェアを使用する場合、以下のルールと優先順位が適用されます。
- アプリケーションコードでは、すべてのミドルウェアをメインのPrisma Clientインスタンスで宣言する必要があります。拡張クライアントでは宣言できません。
- ミドルウェアと `query` コンポーネントを持つ拡張機能が実行される状況では、Prisma Client は `query` コンポーネントを持つ拡張機能を実行する前にミドルウェアを実行します。Prisma Client は、個々のミドルウェアと拡張機能を `$use` または `$extends` でインスタンス化した順序で実行します。