`query`: カスタムPrisma Clientクエリを作成
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
拡張コンポーネントを使用してクエリを変更します。カスタムクエリは、次の場所で変更できます。
- 特定のモデル内の特定の操作
- スキーマのすべてのモデル内の特定の操作
- すべてのPrisma Client操作
- 特定のモデル内のすべての操作
- スキーマのすべてのモデル内のすべての操作
- 特定のトップレベルraw 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()
、findFirst
、findMany
、count
、create
などの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は、オブジェクトに対する安全でない変更をキャッチします。
- このPromiseの値はタイプセーフであるため、
スキーマのすべてのモデル内の特定の操作を変更する
スキーマのすべてのモデルのクエリを拡張するには、特定のモデル名の代わりに$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関数の名前を使用します。
- リレーショナルデータベース
- 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
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
},
},
},
})
上記の例を含めたのは、これが可能であることを示すためです。ただし、パフォーマンス上の理由から、既存のフィールドをオーバーライドするには、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を実行します。