クエリ
クエリのさわりを確認していきます。
さらに突っ込んだ内容は、Prismaの公式ドキュメントを確認してください。
前提
前チャプターの最後の手順(初期データ投入)を実施している前提(ユーザ2件、記事101件)で進める
% npx prisma db seed --preview-feature
事前準備
prisma/index.ts
ファイルを作成し、以下の内容を記載する
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
// TODO ここに処理を記載する
}
main()
.catch((e) => {
throw e
})
.finally(async () => {
await prisma.$disconnect()
})
本チャプターでは、上記のファイルのTODO箇所に処理を記載し、Prismaでのクエリ発行機能を確認していく
単純な検索(1件検索)
ユーザを1件取得する
const alice = await prisma.user.findUnique({
where: { email: 'alice@example.com' }
})
console.log(alice)
- クエリ発行
% npx ts-node prisma/index.ts
{ id: 1, email: 'alice@example.com', name: 'Alice', tel: null }
発行されたSQLは以下のとおり
2021-05-16 11:09:52.395 UTC [3070] LOG: execute s0: SELECT "postgres"."User"."id", "postgres"."User"."email", "postgres"."User"."name", "postgres"."User"."tel" FROM "postgres"."User" WHERE "postgres"."User"."email" = $1 LIMIT $2 OFFSET $3
2021-05-16 11:09:52.395 UTC [3070] DETAIL: parameters: $1 = 'alice@example.com', $2 = '1', $3 = '0'
プリペアドステートメント(Prepared statements
)で処理されていることが確認できる。
単純な検索(リスト検索)
ユーザを複数取得する
const users = await prisma.user.findMany()
console.log(users)
- クエリ発行
% npx ts-node prisma/index.ts
[
{ id: 1, email: 'alice@example.com', name: 'Alice', tel: null },
{ id: 2, email: 'bob@example.com', name: 'Bob', tel: null }
]
発行されたSQLは以下のとおり
2021-05-16 11:25:29.514 UTC [3103] LOG: execute s0: SELECT "postgres"."User"."id", "postgres"."User"."email", "postgres"."User"."name", "postgres"."User"."tel" FROM "postgres"."User" WHERE 1=1 OFFSET $1
2021-05-16 11:25:29.514 UTC [3103] DETAIL: parameters: $1 = '0'
Join検索(ユーザ詳細)
Bobに紐づいた投稿を取得する
const bob = await prisma.user.findUnique({
where: {email: 'bob@example.com'},
include: { posts: true }
})
console.log(bob)
- クエリ発行
% npx ts-node prisma/index.ts
{
id: 2,
email: 'bob@example.com',
name: 'Bob',
tel: null,
posts: [
{
id: 101,
title: 'Check out Prisma with Next.js',
content: null,
published: false,
authorId: 2
}
]
}
発行されたSQLは以下のとおり
joinではなく、クエリを2回発行していることが確認できる(ユーザを特定したのち、IDで再検索)
2021-05-16 11:31:32.453 UTC [3117] LOG: execute s0: SELECT "postgres"."User"."id", "postgres"."User"."email", "postgres"."User"."name", "postgres"."User"."tel" FROM "postgres"."User" WHERE "postgres"."User"."email" = $1 LIMIT $2 OFFSET $3
2021-05-16 11:31:32.453 UTC [3117] DETAIL: parameters: $1 = 'bob@example.com', $2 = '1', $3 = '0'
2021-05-16 11:31:32.457 UTC [3117] LOG: execute s1: SELECT "postgres"."Post"."id", "postgres"."Post"."title", "postgres"."Post"."content", "postgres"."Post"."published", "postgres"."Post"."authorId" FROM "postgres"."Post" WHERE "postgres"."Post"."authorId" IN ($1) OFFSET $2
2021-05-16 11:31:32.457 UTC [3117] DETAIL: parameters: $1 = '2', $2 = '0'
Join検索(投稿一覧)
投稿一覧(ページング)を取得する
タイトルをキーワードで絞り込み、取得件数を絞り込む
const posts = await prisma.post.findMany({
where: {
title: {
startsWith: 'title'
}
},
take: 5,
orderBy: {
id: 'asc'
},
include: { author : true }
})
console.log(posts)
- クエリ発行
% npx ts-node prisma/index.ts
[
{
id: 1,
title: 'title 1',
content: null,
published: false,
authorId: 1,
author: { id: 1, email: 'alice@example.com', name: 'Alice', tel: null }
},
{
id: 2,
title: 'title 2',
content: null,
published: false,
authorId: 1,
author: { id: 1, email: 'alice@example.com', name: 'Alice', tel: null }
},
{
id: 3,
title: 'title 3',
content: null,
published: false,
authorId: 1,
author: { id: 1, email: 'alice@example.com', name: 'Alice', tel: null }
},
{
id: 4,
title: 'title 4',
content: null,
published: false,
authorId: 1,
author: { id: 1, email: 'alice@example.com', name: 'Alice', tel: null }
},
{
id: 5,
title: 'title 5',
content: null,
published: false,
authorId: 1,
author: { id: 1, email: 'alice@example.com', name: 'Alice', tel: null }
}
]
発行されたSQLは以下のとおり
N+1問題を回避。 クエリを2回発行し、結果をマージして返している
2021-05-16 21:42:31.702 UTC [4365] LOG: execute s0: SELECT "postgres"."Post"."id", "postgres"."Post"."title", "postgres"."Post"."content", "postgres"."Post"."published", "postgres"."Post"."authorId" FROM "postgres"."Post" WHERE "postgres"."Post"."title" LIKE $1 ORDER BY "postgres"."Post"."id" ASC LIMIT $2 OFFSET $3
2021-05-16 21:42:31.702 UTC [4365] DETAIL: parameters: $1 = 'title%', $2 = '5', $3 = '0'
2021-05-16 21:42:31.706 UTC [4365] LOG: execute s1: SELECT "postgres"."User"."id", "postgres"."User"."email", "postgres"."User"."name", "postgres"."User"."tel" FROM "postgres"."User" WHERE "postgres"."User"."id" IN ($1) OFFSET $2
2021-05-16 21:42:31.706 UTC [4365] DETAIL: parameters: $1 = '1', $2 = '0'
Native Query [パラメータ埋め込み]
Prismaでは、SQLベタ書きも利用できる。
import { User } from '@prisma/client' // 追加
const prisma = new PrismaClient()
async function main() {
const email = 'alice@example.com'
const alice = await prisma.$queryRaw<User>(`SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = '${email}';`)
console.log(alice)
}
- クエリ発行
% npx ts-node prisma/index.ts
[ { id: 1, email: 'alice@example.com', name: 'Alice', tel: null } ]
発行されたSQLは以下のとおり
2021-05-16 11:48:12.345 UTC [3158] LOG: execute s0: SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = 'alice@example.com';
SQLインジェクション確認(Native Query [パラメータ埋め込み])
SQLインジェクションの可能性がSQLログから確認できたため、以下を試してみる
const email = "'' OR 1=1 ORDER BY 1 DESC"
const alice = await prisma.$queryRaw(`SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = ${email};`)
console.log(alice)
- クエリ発行
% npx ts-node prisma/index.ts
[
{ id: 2, email: 'bob@example.com', name: 'Bob', tel: null },
{ id: 1, email: 'alice@example.com', name: 'Alice', tel: null }
]
発行されたSQLは以下のとおり
2021-05-16 11:54:18.659 UTC [3173] LOG: execute s0: SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = '' OR 1=1 ORDER BY 1 DESC;
この方式を検索フォームなどのユーザ入力を受け付ける箇所で利用するのは、危険なことが確認できた。
Native Query [パラメータ指定]
同様のクエリーをパラメータ指定で試してみる。
const alice = await prisma.$queryRaw(`SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = $1`, 'alice@example.com')
console.log(alice)
- クエリ発行
% npx ts-node prisma/index.ts
[ { id: 1, email: 'alice@example.com', name: 'Alice', tel: null } ]
発行されたSQLは以下のとおり
2021-05-16 12:02:11.912 UTC [3196] LOG: execute s0: SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = $1
2021-05-16 12:02:11.912 UTC [3196] DETAIL: parameters: $1 = 'alice@example.com'
プリペアドステートメント(Prepared statements
)になってくれた。
SQLインジェクション確認(Native Query [パラメータ指定] )
同様のパラメータを指定して、動作を確認してみる。
const alice = await prisma.$queryRaw(`SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = $1`, "'' OR 1=1 ORDER BY 1 DESC")
console.log(alice)
- クエリ発行
% npx ts-node prisma/index.ts
[]
発行されたSQLは以下のとおり
2021-05-16 12:04:31.690 UTC [3202] LOG: execute s0: SELECT * FROM "postgres"."User" WHERE "postgres"."User"."email" = $1
2021-05-16 12:04:31.690 UTC [3202] DETAIL: parameters: $1 = ''''' OR 1=1 ORDER BY 1 DESC'
パラメータがエスケープされ、正しく解釈された。
結果、Native Query
は、パラメータ指定の方式なら、画面側でも用いることはできそう。