UPDATE ที่ย้ายแถวข้ามผู้เช่าได้ 42501 แล้วเติม WITH CHECK ที่เปิดให้สุด ก็ยังได้ 42501
บทที่ 2 จบลงที่การอ่าน ผู้เช่าที่ถูกตั้งไว้เห็นสามแถวของตัวเอง และไม่เห็นของคนอื่น
บทนี้เปลี่ยนคำสั่งจากการอ่านเป็นการเขียน แล้วถามคำถามเดียว อะไรกันไม่ให้ผู้เช่าคนหนึ่ง ย้ายแถวไปเป็นของอีกคน
คำตอบที่คนส่วนใหญ่คาดคือ WITH CHECK เพราะเป็นส่วนของ policy ที่มีชื่อว่าตรวจ
คำตอบที่วัดได้ไม่ใช่ตัวนั้น และการเขียนตัวนั้นแบบหลวม เป็นการเปิดทางให้แถวลง
ไม่ใช่การปิด
คอร์สนี้อยู่ในซีรีส์คลินิกเดียวกับ คอร์สคัดกรองผู้เรียก และ คอร์ส CORS กับ CSRF แต่หกบทแรกไม่มีแอปมาเกี่ยวข้อง มีแต่ตาราง policy และ role
ตัวเลขทุกตัวในบทนี้คุณรันเองได้ สั่ง scripts/clinic-rls/up.sh ซึ่งยก container
ชื่อ kaen-rls-pg ขึ้นที่ port 55438 แล้วสั่ง python3 scripts/clinic-rls/spike.py
เก็บกวาดด้วย docker rm -f kaen-rls-pg
ตารางใบใหม่ และ policy ใบเดียวที่มีแต่ USING
หัวข้อที่มีชื่อว่า “ตารางใบใหม่ และ policy ใบเดียวที่มีแต่ USING”ทรงตั้งต้นเหมือนบทที่ 1 ทุกบรรทัด ต่างกันที่ชื่อตาราง และบทนี้ไม่สั่ง FORCE เลยสักครั้ง
create table t_update ( id bigserial primary key, tenant_id text not null, patient text not null, archived boolean not null default false);insert into t_update (tenant_id, patient, archived) values ('clinic-a','Ann',false),('clinic-a','Ben',false),('clinic-a','Cid',true), ('clinic-b','Dan',false),('clinic-b','Eve',false),('clinic-b','Fay',true);grant select, insert, update, delete on t_update to kaen_rls_app, kaen_rls_bypass;grant usage, select on sequence t_update_id_seq to kaen_rls_app, kaen_rls_bypass;alter table t_update enable row level security;create policy p on t_update for all using (tenant_id = current_setting('app.tenant', true));บรรทัดสุดท้ายคือ CREATE POLICYCREATE POLICYคำสั่งที่ผูกนิพจน์หนึ่งเข้ากับตารางหนึ่ง · ตาราง t_app ของ probe มี policy ผูกอยู่ 1 ใบ (appPolicyCount) และเขียนถูกทุกตัวอักษร แต่จำนวน policy ที่ผูกอยู่ไม่ใช่หลักฐานว่ามันเคยถูกเรียกใช้ ที่มีแต่ USINGUSING (…)นิพจน์ที่ policy ใช้คัดว่าแถวใดมองเห็นหรือแตะได้ · เมื่อ policy ไม่มี WITH CHECK นิพจน์นี้ทำหน้าที่ตรวจแถวหลังการเขียนให้เองด้วย · UPDATE ที่ย้ายแถวข้ามผู้เช่าบนตาราง t_update ใต้ USING ล้วน ได้ 42501 และยอดของสองผู้เช่าค้างที่ 3 กับ 3 ใบเดียว ไม่มีส่วนอื่นต่อท้าย
for all ทำให้ใบนี้ครอบทั้ง SELECT INSERT UPDATE และ DELETE
ผู้สั่งคำสั่งเขียนทุกก้อนในบทนี้คือ kaen_rls_app ซึ่งไม่ใช่เจ้าของตารางและไม่ใช่ superuser
นิพจน์จึงถูกเรียกใช้จริง โดยไม่ต้องสั่ง FORCE ตามที่
บทที่ 1 วัดไว้
ส่วนยอดของแต่ละผู้เช่า เจ้าของตารางเป็นคนนับ เจ้าของอยู่นอก policy ของตัวเอง
เพราะตารางใบนี้มีแค่ ENABLE ยอดทุกตัวที่ชื่อคีย์ลงท้ายด้วย RowsA หรือ RowsB
จึงเป็นยอดจริงทั้งตาราง ส่วนคีย์ที่ไม่ได้ลงท้ายแบบนั้น ผู้สั่งเป็นคนอื่น
รูปของค่าที่ probe เก็บ
หัวข้อที่มีชื่อว่า “รูปของค่าที่ probe เก็บ”คำสั่งที่คาดว่าจะพัง ถูกเรียกผ่านฟังก์ชัน public.kaen_try ซึ่งคืนค่าเป็นสตริงสองส่วน
select 'KAEN|updMoveUsingOnly|'||public.kaen_try( 'update t_update set tenant_id=''clinic-b'' where patient=''Ann''');- สำเร็จ ได้
00000เว้นวรรค แล้วตามด้วยจำนวนแถวที่คำสั่งกระทำจริง - ล้ม ได้ SQLSTATESQLSTATEรหัสห้าหลักที่ PostgreSQL ให้กับผลของคำสั่งหนึ่ง · คอร์สนี้ยกเฉพาะรหัส ไม่ยกข้อความผิดพลาด เพราะข้อความเป็นร้อยแก้วที่เปลี่ยนข้าม major ส่วนรหัสไม่เปลี่ยน · 00000 แปลว่าคำสั่งสำเร็จ ซึ่งเกิดคู่กับจำนวนแถวที่ถูกกระทำเท่าใดก็ได้ ของข้อผิดพลาด เว้นวรรค แล้วตามด้วย
-1
-1 ไม่ใช่จำนวนแถว มันคือค่าที่ฟังก์ชันใส่ไว้เพื่อบอกว่าคำสั่งไม่เคยนับแถวเลย
คอร์สนี้ยกเฉพาะรหัส ไม่ยกข้อความผิดพลาด เพราะข้อความเปลี่ยนข้าม major ส่วนรหัสไม่เปลี่ยน
UPDATE ที่ย้ายแถวข้ามผู้เช่า และ UPDATE ที่ไม่ย้าย
หัวข้อที่มีชื่อว่า “UPDATE ที่ย้ายแถวข้ามผู้เช่า และ UPDATE ที่ไม่ย้าย”คำสั่งสองก้อนใน session เดียว ต่างกันที่ก้อนหนึ่งแตะ tenant_id อีกก้อนไม่แตะ
set app.tenant = 'clinic-a';select 'KAEN|updMoveUsingOnly|'||public.kaen_try( 'update t_update set tenant_id=''clinic-b'' where patient=''Ann''');select 'KAEN|updSameTenant|'||public.kaen_try( 'update t_update set patient=''Ann2'' where patient=''Ann''');select 'KAEN|updUsingOnlyRowsA|'||count(*) from t_update where tenant_id='clinic-a';select 'KAEN|updUsingOnlyRowsB|'||count(*) from t_update where tenant_id='clinic-b';| คีย์ | สิ่งที่คำสั่งทำ | ค่าที่วัดได้ |
|---|---|---|
updMoveUsingOnly | ย้ายแถว Ann ไปเป็นของ clinic-b | 42501 -1 |
updSameTenant | เปลี่ยนชื่อ Ann เป็น Ann2 ผู้เช่าเดิม | 00000 1 |
updUsingOnlyRowsA | ยอดของ clinic-a หลังจบทั้งสองคำสั่ง | 3 |
updUsingOnlyRowsB | ยอดของ clinic-b หลังจบทั้งสองคำสั่ง | 3 |
42501 insufficient_privilegeSQLSTATE 42501 (insufficient_privilege)รหัสที่ RLS ใช้ปฏิเสธการเขียนแบบส่งเสียงออกมา · วัดได้ในห้าทรง คือ UPDATE บนตาราง t_update ที่ย้ายแถวข้ามผู้เช่า ทั้งใต้ USING ล้วนและใต้ WITH CHECK (true) · INSERT ข้ามผู้เช่าบนตาราง t_insert ใต้ USING ล้วน · ทรงเดียวกันที่ผ่านแล้วแต่เติม RETURNING เข้าไป · และ INSERT บนตาราง t_selonly ใต้ policy ที่ระบุ FOR SELECT อย่างเดียว · ตรงข้ามกับ UPDATE และ DELETE บนตารางใบหลังนั้น ซึ่งคืน 00000 และ 0 แถวโดยไม่ส่งเสียง คือรหัสที่ RLS ใช้ปฏิเสธการเขียนแบบส่งเสียงออกมา สองแถวล่างยืนยันว่าเสียงนั้นตรงกับของจริง ไม่มีแถวไหนย้ายฝั่ง
แถวที่สองสำคัญพอกัน คำสั่งที่แตะแถวเดียวกันเป๊ะ แต่ไม่แตะ tenant_id ผ่านและกระทบหนึ่งแถว
สิ่งที่ถูกปฏิเสธจึงไม่ใช่ “การเขียนลงแถวนี้” แต่เป็นค่าใหม่ที่แถวนี้จะกลายเป็น
p ไม่มีส่วนไหนที่เขียนไว้ให้ตรวจแถวหลังการเขียนเลย แต่การปฏิเสธเกิดขึ้นจริง
หัวข้อถัดไปทดสอบข้อนี้ด้วยการเติมส่วนนั้นเข้าไป
เติม WITH CHECK ที่เปิดให้สุด แล้วผลไม่ขยับ
หัวข้อที่มีชื่อว่า “เติม WITH CHECK ที่เปิดให้สุด แล้วผลไม่ขยับ”นิพจน์ที่เติมเข้าไปคือ true ซึ่งอนุญาตทุกแถวที่เป็นไปได้ และตัวเลขทั้งสามตัวเท่าเดิม
alter policy p on t_update using (tenant_id = current_setting('app.tenant', true)) with check (true);set app.tenant = 'clinic-a';select 'KAEN|updMoveWithCheckTrue|'||public.kaen_try( 'update t_update set tenant_id=''clinic-b'' where patient=''Ann2''');| คีย์ | ค่าที่วัดได้ |
|---|---|
updMoveWithCheckTrue | 42501 -1 |
updWithCheckRowsA | 3 |
updWithCheckRowsB | 3 |
WITH CHECKWITH CHECK (…)นิพจน์ที่ตรวจแถวหลังการเขียน · เติม WITH CHECK (true) ให้ policy ของ INSERT บนตาราง t_insert แล้วแถวข้ามผู้เช่าลงจริง ได้ 00000 หนึ่งแถว และแถวของผู้เช่าปลายทางขึ้นเป็น 4 · การเขียนมันแบบหลวมจึงเป็นการเปิดทาง ไม่ใช่การปิด คือนิพจน์ที่ตรวจแถวหลังการเขียน และ true คือรูปที่หลวมที่สุดที่เขียนได้
มันผ่านให้ทุกแถวโดยไม่ดูค่าอะไรเลย ผลที่ได้คือรหัสเดิมและยอดเดิม
ตัวที่ปฏิเสธ จึงไม่ใช่ WITH CHECK เพราะถ้าใช่ การเปิดมันจนสุดต้องทำให้คำสั่งผ่าน
สิ่งที่บทนี้เพิ่งวัดคือมันไม่ผ่าน ทั้งที่นิพจน์ตัวนั้นเป็น true
ตัวที่ตัดสิน คือ policy ของ SELECT
หัวข้อที่มีชื่อว่า “ตัวที่ตัดสิน คือ policy ของ SELECT”เปลี่ยนนิพจน์ฝั่ง SELECT เป็น true แล้วคำสั่งเดิมผ่าน โดย USING และ WITH CHECK ฝั่งเขียนไม่เปลี่ยน
drop policy p on t_update;create policy p_sel on t_update for select using (true);create policy p_upd on t_update for update using (tenant_id = current_setting('app.tenant', true)) with check (true);set app.tenant = 'clinic-a';select 'KAEN|updMoveSelectOpen|'||public.kaen_try( 'update t_update set tenant_id=''clinic-b'' where patient=''Ann2''');| คีย์ | ค่าที่วัดได้ |
|---|---|
updMoveSelectOpen | 00000 1 |
updSelectOpenRowsA | 2 |
updSelectOpenRowsB | 4 |
ยอดขยับจริง clinic-a เหลือ 2 และ clinic-b เป็น 4 แถวหนึ่งแถวย้ายฝั่งไปแล้ว
เทียบสองทรงทีละส่วน นิพจน์ที่ครอบ UPDATE เหมือนกันทั้งคู่ คือ USING เป็นการเทียบผู้เช่า
และ WITH CHECK เป็น true ส่วนที่เปลี่ยนมีส่วนเดียว คือนิพจน์ที่ครอบ SELECT
ซึ่งเปลี่ยนจากการเทียบผู้เช่า เป็น true
เอกสารของ PostgreSQL 18 มีตารางชื่อ Policies Applied by Command Type
แถวของ UPDATE ระบุว่านิพจน์ USING ของ policy ฝั่ง SELECT ถูกใช้สองหน้าที่
คือคัดแถวเดิม และตรวจแถวใหม่ด้วย
เอกสารหน้าเดียวกันอธิบายเงื่อนไขของหน้าที่ที่สอง คำสั่ง UPDATE ที่ต้องอ่านค่าจากตาราง
เช่นมี where ต้องมีสิทธิ์ SELECT และ policy ฝั่งนั้นจึงถูกนำมาใช้เพิ่มจาก policy ฝั่ง UPDATE
update … where patient='Ann2' มี where จึงต้องอ่านค่าจากตาราง
ในทรงที่หนึ่งและที่สอง policy ใบเดียวเป็น for all มันจึงเป็น policy ฝั่ง SELECT ไปด้วย
และนิพจน์ของมันคือการเทียบผู้เช่า
แถวใหม่ที่จะกลายเป็นของ clinic-b ไม่ผ่านนิพจน์นั้น ทั้งที่ WITH CHECK เป็น true
เพราะการตรวจสองรอบนี้เป็นการรวมด้วย AND ไม่ใช่การเลือกอันใดอันหนึ่ง
ข้อนี้เป็นข้ออ้างเรื่องกลไก จึงอ้างเอกสาร ส่วนตัวเลขสามตัวข้างบนอ้าง probe
ฝั่ง INSERT กลับด้าน
หัวข้อที่มีชื่อว่า “ฝั่ง INSERT กลับด้าน”คำสั่งเดียวกับข้างบนคนละชนิด และ WITH CHECK (true) ที่ไม่ช่วยอะไรเลยกับ UPDATE เปิดทางให้ INSERT
ทรงนี้ใช้ตาราง t_insert ซึ่งสร้างด้วย DDL ก้อนเดียวกับ t_update ทุกบรรทัด
และผูก policy ใบเดียวรูปเดียวกัน คือ for all ที่มีแต่ USING
set app.tenant = 'clinic-a';select 'KAEN|insUsingOnlyOther|'||public.kaen_try( 'insert into t_insert (tenant_id, patient) values (''clinic-b'',''Mallory'')');select 'KAEN|insUsingOnlyOwn|'||public.kaen_try( 'insert into t_insert (tenant_id, patient) values (''clinic-a'',''Gil'')');| คีย์ | แถวที่พยายามเขียน | ค่าที่วัดได้ |
|---|---|---|
insUsingOnlyOther | ของ clinic-b | 42501 -1 |
insUsingOnlyOwn | ของ clinic-a | 00000 1 |
insUsingOnlyRowsA | ยอดของ clinic-a หลังจบ | 4 |
insUsingOnlyRowsB | ยอดของ clinic-b หลังจบ | 3 |
INSERT ไม่มีแถวเดิมให้คัดสักแถว มันมีแต่แถวใหม่ การปฏิเสธของแถวแรกจึงมาจาก
นิพจน์ที่ถูกนำไปใช้ตรวจแถวใหม่ ทั้งที่ policy ใบนี้ไม่มี WITH CHECK เขียนไว้เลย
เอกสารของ PostgreSQL 18 อธิบายข้อนี้ไว้กับ policy ที่ระบุ ALL ว่านิพจน์ USING
ถูกนำไปใช้ทั้งฝั่งที่คัดแถวและฝั่งที่เขียน เมื่อ policy ใบนั้นมีแต่ USING อย่างเดียว
alter policy p on t_insert using (tenant_id = current_setting('app.tenant', true)) with check (true);| คีย์ | ค่าที่วัดได้ |
|---|---|
insWithCheckOther | 00000 1 |
insWithCheckRowsA | 4 |
insWithCheckRowsB | 4 |
insWithCheckOther คือคำสั่งรูปเดียวกับที่เพิ่งได้ 42501 ต่างกันที่ชื่อผู้ป่วยที่ใส่ลงไป
ซึ่งไม่มีนิพจน์ไหนใน policy อ่านค่านั้นเลย ตอนนี้มันผ่าน และกระทบหนึ่งแถว
ยอดของ clinic-b ขึ้นจาก 3 เป็น 4 แถวข้ามผู้เช่าลงในตารางจริง
นี่คือข้อที่บทนี้มีไว้พิสูจน์ นิพจน์ที่หลวมใน WITH CHECK ไม่ได้เพิ่มการตรวจ
มันถอนการตรวจที่ USING ทำให้อยู่แล้วออกไป แล้วเอา true ไปวางแทน
เติม RETURNING ต่อท้าย แล้วรหัสกลับไปเป็น 42501
หัวข้อที่มีชื่อว่า “เติม RETURNING ต่อท้าย แล้วรหัสกลับไปเป็น 42501”คำสั่งรูปเดียวกันสองก้อนใน session เดียว ก้อนที่มีสองคำต่อท้าย ถูกปฏิเสธ
set app.tenant = 'clinic-a';select 'KAEN|insWithCheckOther|'||public.kaen_try( 'insert into t_insert (tenant_id, patient) values (''clinic-b'',''Mal2'')');select 'KAEN|insWithCheckOtherReturning|'||public.kaen_try( 'insert into t_insert (tenant_id, patient) values (''clinic-b'',''Mal3'') returning id');select 'KAEN|insWithCheckVisibleToApp|'||count(*) from t_insert where true;| คีย์ | ค่าที่วัดได้ |
|---|---|
insWithCheckOther | 00000 1 |
insWithCheckOtherReturning | 42501 -1 |
insWithCheckVisibleToApp | 4 |
สองคำสั่งนี้ต่างกันสองอย่าง คือชื่อผู้ป่วยที่ใส่ลงไป ซึ่งไม่มีนิพจน์ไหนใน policy อ่าน
และ returning id ที่ต่อท้ายก้อนที่สอง ค่าที่ต่างกันคือรหัสที่ได้กลับมา
RETURNINGRETURNINGส่วนท้ายของคำสั่งเขียน ที่สั่งให้คืนแถวซึ่งเพิ่งถูกเขียน · การคืนนั้นถูกคัดด้วย policy ของ SELECT · INSERT บนตาราง t_insert ทรงเดิมที่ผ่านด้วย 00000 หนึ่งแถว กลับเป็น 42501 เมื่อเติมเพียง RETURNING เข้าไป สั่งให้คำสั่งคืนแถวที่เพิ่งเขียนกลับมา และการคืนนั้นคือการอ่าน
เอกสารของ PostgreSQL 18 ระบุว่าแถวที่จะถูกคืนต้องผ่าน policy ฝั่ง SELECT ก่อน
และแถวที่ไม่ผ่านจะไม่ถูกข้ามไปโดยไม่ส่งเสียง
insWithCheckVisibleToApp ต้องอ่านคู่กับยอดของเจ้าของตารางถึงจะเห็นของจริง
ตอนนั้นตารางมี 8 แถว คือ clinic-a 4 แถว และ clinic-b 4 แถว
ตามที่ insWithCheckRowsA กับ insWithCheckRowsB วัดไว้ แต่ผู้สั่งนับได้ 4
สี่แถวนั้นเป็นของ clinic-a ทั้งหมด แถวที่ผู้สั่งเพิ่งเขียนลงไปเอง ไม่ได้อยู่ในนั้น
ผู้เขียนมองไม่เห็นแถวที่ตัวเองเพิ่งเขียน และรู้ว่าเขียนสำเร็จจาก 00000 1 เท่านั้น
สี่ทางที่จบลงที่ 42501 และสามทางที่ไม่จบตรงนั้น
หัวข้อที่มีชื่อว่า “สี่ทางที่จบลงที่ 42501 และสามทางที่ไม่จบตรงนั้น”แผนภาพเดียวรวบทุกทรงในบท พร้อมคีย์ที่ probe ใช้เก็บค่าของแต่ละทาง
flowchart TB
U["UPDATE · ย้ายแถวของ clinic-a ไปเป็นของ clinic-b"] --> U1{"policy ใบเดียว for all"}
U1 -->|"มีแต่ USING · updMoveUsingOnly"| UX["42501 · ยอดค้างที่ 3 กับ 3"]
U1 -->|"เติม WITH CHECK true · updMoveWithCheckTrue"| UY["42501 · ยอดค้างที่ 3 กับ 3"]
U --> U2{"แยก policy ของ SELECT ออกมา แล้วเปิดเป็น true"}
U2 -->|"updMoveSelectOpen"| UZ["00000 หนึ่งแถว · ยอดเปลี่ยนเป็น 2 กับ 4"]
V["UPDATE · ไม่แตะ tenant_id · updSameTenant"] --> VZ["00000 หนึ่งแถว"]
I["INSERT · แถวของ clinic-b"] --> I1{"policy ใบเดียว for all"}
I1 -->|"มีแต่ USING · insUsingOnlyOther"| IX["42501"]
I1 -->|"เติม WITH CHECK true · insWithCheckOther"| IY["00000 หนึ่งแถว · ยอดของ clinic-b เป็น 4"]
IY --> I2{"เติม returning id ต่อท้ายคำสั่งเดิม"}
I2 -->|"insWithCheckOtherReturning"| IR["42501"]
คำบรรยายภาพ: สี่ทางในภาพจบที่ 42501 และสามทางจบที่ 00000 พร้อมหนึ่งแถว
ทางของ UPDATE ที่เปิด WITH CHECK จนสุดแล้วยังถูกปฏิเสธ กับทางของ INSERT
ที่เติม returning id แล้วถูกปฏิเสธ เป็นสองทางที่นิพจน์ฝั่งเขียนอนุญาตไว้แล้วทั้งคู่
ที่วัดได้ กับที่เอกสารกำหนด
หัวข้อที่มีชื่อว่า “ที่วัดได้ กับที่เอกสารกำหนด”ตัวเลขทั้งบทมาจาก SQL ที่รันจริง ส่วนคำอธิบายว่านิพจน์ตัวไหนถูกใช้ตอนไหน มาจากเอกสาร
spike.py รันชุดข้ออ้างทั้งชุดสามรอบต่อการเรียกหนึ่งครั้ง แล้วเทียบผลทีละคีย์
รอบที่รันเมื่อ 2026-09-06 ได้ 52 ข้ออ้างตรงทั้งหมด 98 ค่าที่วัดได้
และ 0 ค่าที่ขยับระหว่างรอบ
สิ่งที่บทนี้ ไม่ได้ พิสูจน์คือลำดับข้างใน บทนี้ไม่ได้แสดงว่าตัวตัดสินตรวจนิพจน์ใบไหนก่อน ข้ออ้างแบบนั้นอ้างเอกสารของ PostgreSQL 18 และเอกสารเป็นคนละหลักฐานกับ probe
ผลทั้งหมดในบทนี้วัดบน PostgreSQL 18.6 บน aarch64-unknown-linux-musl
ใน container ของคอร์สนี้เอง major อื่นเป็นคนละการวัด และบทนี้ไม่ได้อ้างถึงมัน
หน่วยของหลักฐานในบทนี้มีสามหน่วย คือรหัส SQLSTATE จำนวนแถวที่คำสั่งกระทำจริง และจำนวนแถวที่นับได้ในตาราง ทั้งคอร์สไม่มีหน่วยเวลาสักตัว
คำถามที่บทนี้ทิ้งไว้
หัวข้อที่มีชื่อว่า “คำถามที่บทนี้ทิ้งไว้”บทนี้เห็นการปฏิเสธที่ส่งเสียงทุกครั้ง และเสียงนั้นคือสิ่งที่ทำให้มันตรวจสอบได้
ทั้งบทมีคำสั่งที่ถูกปฏิเสธสี่ครั้ง และทั้งสี่ครั้งได้ 42501 กลับมา
คนที่เขียนแอปจึงจับได้ทันทีว่าคำสั่งนั้นไม่ผ่าน แล้วตัดสินใจต่อได้
แต่ 42501 ไม่ใช่รูปเดียวของการปฏิเสธ policy ใบหนึ่งปฏิเสธคำสั่งได้โดยไม่ยกรหัสอะไรเลย
คำสั่งจบด้วย 00000 เหมือนคำสั่งที่สำเร็จ ต่างกันแค่จำนวนแถวที่มันกระทำ
บทที่ 4 วัดเสียงนั้น แล้วยืนยันด้วยจำนวนแถวหลังจบ ว่าคำสั่งที่เงียบสนิท ไม่ได้แปลว่าคำสั่งที่ไม่มีอะไรให้ทำ
-
PostgreSQL 18 —
CREATE POLICY(เข้าถึง 2026-09-06) — ตาราง Policies Applied by Command Type ที่ระบุว่านิพจน์ของ policy ฝั่งSELECTถูกใช้ทั้งคัดแถวเดิมและตรวจแถวใหม่ของUPDATE· กฎที่ policy แบบALLซึ่งมีแต่USINGเอานิพจน์นั้นไปใช้ฝั่งเขียนด้วย · และกฎที่แถวซึ่งRETURNINGจะคืน ต้องผ่าน policy ฝั่งSELECTก่อน -
PostgreSQL 18 — Row Security Policies (เข้าถึง 2026-09-06) — ที่มาของกฎว่าเจ้าของตารางอยู่นอก policy ของตัวเอง เมื่อตารางมีแค่
ENABLEซึ่งเป็นเหตุผลที่ยอดในบทนี้นับโดยเจ้าของตาราง -
ตัวเลขทุกตัวในบทนี้มาจาก
scripts/clinic-rls/spike.pyซึ่งสร้างฐานข้อมูลกับ role ขึ้นใหม่ทุกรอบ แล้วรื้อทิ้งเมื่อจบ · ต้องสั่งscripts/clinic-rls/up.shก่อน ซึ่งยก container ของคอร์สนี้เองขึ้นที่ port 55438
เช็กความเข้าใจ — บทที่ 3
ข้อ 1 / 3ตารางมี policy ใบเดียว `for all using (tenant_id = current_setting('app.tenant', true))` · app role ตั้ง `app.tenant` เป็น `clinic-a` แล้วสั่ง UPDATE ย้ายแถวของตัวเองไปเป็นของ `clinic-b` ได้อะไร