14. 少し複雑なクエリ
鍵付きユーザーの「いいね」について名前を
「匿名」にして出している
WITH comments AS (
SELECT comments.id, content, users.name AS user_name, comments.created_at
FROM comments JOIN users ON comments.user_id = users.id
), likes AS (
SELECT COALESCE(users.name, '匿名') AS user_name, likes.created_at, comment_id
FROM likes LEFT OUTER JOIN users ON likes.user_id = users.id
)
SELECT comments.id, content, comments.user_name
, JSON_AGG(likes ORDER BY likes.created_at DESC) AS liked_by
FROM comments JOIN likes ON likes.comment_id = comments.id
GROUP BY 1, 2, 3
16. CTE(Common Table Expressions)
users をシャドウイングしてサブセットのテー
ブルに置き換えている様子
クエリーを発行しているユーザー自身と鍵付きではな
いユーザーのレコードのみ
id, name にしかアクセス出来ない
WITH users AS (
SELECT id, name FROM users WHERE id = ? OR privacy = false
)
SELECT * FROM users
17. CTE(Common Table Expressions)
comments をシャドウイングしてサブセットの
テーブルに置き換えている様子
自身のと鍵付きではないコメントのレコードのみ
id, name, content, updated_at にしかアクセス出来ない
WITH comments AS (
SELECT id, name, content, updated_at FROM comments
WHERE user_id = ? OR privacy = false
)
SELECT * FROM comments
23. スキーマ修飾
WITH users AS (
SELECT * FROM users WHERE false
)
SELECT * FROM users
これなら結果が0件になるが
24. スキーマ修飾
WITH users AS (
SELECT * FROM users WHERE false
)
SELECT * FROM public.users
元のテーブルの全カラムが全件見えてしまう
スキーマ名で修飾すると、大元のテーブルに
アクセス出来てしまうので、スキーマ修飾も
殺さないといけない
31. replica 属性のコネクション
SQLQL の処理をするときだけ replica 属性の
コネクションを使えば、Mutations っぽい
SQL は Rails が弾いてくれて便利っぽい
ActiveRecord::Base.connected_to(database: :readonly) do
User.first.update(name: 'hoge')
end
#=> ActiveRecord::ReadOnlyError
#=> (Write query attempted while in readonly mode...
32. と思うじゃん?
CTE の WITH 句は Rails 的にはホワイトリス
トに入ってないっぽい……
ActiveRecord::Base.connected_to(database: :readonly) do
ActiveRecord::Base.connection.execute(
"WITH t AS (SELECT 1 AS n) SELECT * FROM t"
)
end
#=> ActiveRecord::ReadOnlyError
#=> (Write query attempted while in readonly mode...
35. やったー
readonly コネクションで WITH が使えるよう
になったぞ
res = ActiveRecord::Base.connected_to(database: :readonly) do
ActiveRecord::Base.connection.execute(
"WITH t AS (SELECT 1 AS n) SELECT * FROM t"
)
end
res.to_a #=> [{"n"=>1}]
37. WITH は副作用を起こせる
この機能、めちゃくちゃ便利なんですけど、
今は邪魔ですね
res = ActiveRecord::Base.connected_to(database: :readonly) do
ActiveRecord::Base.connection.execute(
"WITH t AS (UPDATE users SET name = 'hoge' RETURNING *) SELECT * FROM t"
)
end
res.to_a #=> [{"id"=>1,"name"=>"hoge"},{"id"=>2,"name"=>"hoge"}]
38. DB ユーザーの権限
しゃーない
ちゃんと、DB レベルで readonly なユーザー
を作るしかない
create user readonlyuser with password 'readonlyuser' NOCREATEDB NOCREATEROLE;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "readonlyuser";
40. DB は最後の砦
これで、副作用のあるクエリは DB が弾いて
くれるようになった
ActiveRecord::Base.connected_to(database: :readonly) do
ActiveRecord::Base.connection.execute(
"WITH t AS (UPDATE users SET book_name = 'hoge' RETURNING *) SELECT * FROM t"
)
end
#=> ActiveRecord::StatementInvalid
#=> (PG::InsufficientPrivilege: ERROR: permission denied for table users
51. Relation to Object
JSON_AGG 関数や ROW_TO_JSON 関数を
使えば、SQL でババっと JSON を生成できる
WITH users(id, "name") AS (VALUES (1, 'taro'), (2, 'jiro'))
SELECT JSON_AGG(users) AS users FROM users;
-- users
-- ---------------------------
-- [{"id":1,"name":"taro"},{"id":2,"name":"jiro"}]
-- (1 row)
52. Relation to Object
欲しい JSON の形でスっと出せる
WITH users(id, "name") AS (VALUES (1, 'taro'))
, comments(id, content, user_id) AS (
VALUES(1, 'aaaaaaa', 1), (2, 'bbbbbb', 1))
, t AS (
SELECT users.id, users.name, JSON_AGG(comments) as comments
FROM users JOIN comments ON users.id = comments.user_id GROUP BY 1, 2)
SELECT JSON_AGG(t) AS users FROM t;
-- users
---------------------------------------------------------------------------------
-- [{"id":1,"name":"taro","comments":[{"id":1,"content":"aaaaaaa","user_id":1}, +
-- {"id":2,"content":"bbbbbb","user_id":1}]}]
-- (1 row)