สัญญาว่าด้วยการมองเห็น — ทุกข้อที่พูดได้แล้ว และแถวที่ไม่มีบทไหนพิสูจน์
เจ็ดบทที่ผ่านมาวัดค่าออกมาเป็นชุด บทนี้ไม่สร้างข้ออ้างใหม่ มันประกอบของเดิม
· สามค่าที่โผล่เฉพาะบทนี้คือ dbLeftAfterDrop rolesLeftAfterDrop และ
STRICTAFTERRESET.strictCount ซึ่งเป็นค่าที่ probe วัด ไม่ใช่ค่าที่บทนี้คิดขึ้น
มันเรียงสิ่งที่คุณพูดได้แล้วไว้ข้างหลักฐานของข้อนั้น แล้วบอกว่าใครเป็นคนพิสูจน์
row-level securityrow-level security (RLS)กลไกของ PostgreSQL ที่ให้นิพจน์ตัดสินเป็นรายแถวว่าคำสั่งหนึ่งเห็นหรือแตะแถวใดได้บ้าง · เอกสารของ PostgreSQL 18 แยกมันเป็นธงสองใบใน catalog คือ relrowsecurity กับ relforcerowsecurity และการเปิดใบแรกไม่ได้ทำให้ใบที่สองเปิดตาม · สิ่งที่ probe วัดคือผลของการแยกนั้นบนตาราง t_owner ไม่ใช่ค่าของธงบนตารางที่ยกไว้แค่ใบแรก ตอบคำถามเดียว คือผู้อ่านคนนี้เห็นแถวไหนได้บ้าง คอร์สนี้ตอบคำถามนั้นด้วยจำนวนแถวที่นับได้ ไม่ได้ตอบด้วยชื่อของกลไก
และคำตอบชุดนี้มีจุดบอดหนึ่งจุด สองทรงในคอร์สนี้เปลี่ยนของข้างในไปแล้ว โดยจำนวนแถวไม่ขยับสักแถว หัวข้อสุดท้ายของบทเป็นเรื่องนั้น
คอร์สนี้อยู่ในซีรีส์คลินิกเดียวกับ คอร์สคัดกรองผู้เรียก และ คอร์ส CORS กับ CSRF และบทนี้ไม่ได้สร้างค่าใหม่ ทุกช่องในตารางมาจากที่เดิมกับที่เจ็ดบทก่อนหน้าใช้
เวที A สั่ง scripts/clinic-rls/up.sh ซึ่งยก container ชื่อ kaen-rls-pg
ขึ้นที่ port 55438 แล้วสั่ง python3 scripts/clinic-rls/spike.py เก็บกวาดด้วย
docker rm -f kaen-rls-pg ส่วนเวที B ถูกตรึงไว้ที่ src/data/perf/clinic-rls.ts
ตารางสัญญา · ข้อที่พูดได้ กับทรงที่พิสูจน์มัน
หัวข้อที่มีชื่อว่า “ตารางสัญญา · ข้อที่พูดได้ กับทรงที่พิสูจน์มัน”ทุกแถวถือคู่ของทรงกับคีย์ และช่องขวาสุดบอกว่าบทไหนเป็นคนวัด
ตารางใบนี้อ่านจากซ้ายไปขวา ช่องซ้ายคือประโยคที่คุณพูดกับเพื่อนร่วมงานได้ ช่องกลางคือของที่ทำให้ประโยคนั้นไม่ใช่ความเห็น และช่องขวาคือที่ที่หลักฐานถูกวัด
แถวที่ช่องขวาเขียนว่า ไม่มีบทไหนพิสูจน์ อยู่ในตารางใบเดียวกันโดยตั้งใจ สัญญาที่เก็บแต่ข้อที่ทำได้ อ่านเหมือนสัญญาที่ครอบทุกอย่าง
| ข้อที่พูดได้ | ทรง · คีย์ · ค่าที่วัดได้ | บทที่พิสูจน์ |
|---|---|---|
| เปิด RLS ไม่ใช่บังคับ RLS | t_owner · ownerEnableNoGuc 6 เทียบกับ ownerForceNoGuc 0 | บทที่ 1 |
| คำสั่งที่ทำให้เลขขยับ ไม่ได้แตะ SQL ของ policy | t_owner · ownerForceGucA 3 · ownerForceGucB 3 · ownerNoForceGucB 6 | บทที่ 1 |
| ธงครบสองใบยังไม่พอ ถ้าผู้อ่านเป็น superuser | t_super · superRlsEnabled true · superRlsForced true · superOwnerForceNoGuc 6 · superOwnerForceGucA 6 | บทที่ 1 |
BYPASSRLS ผูกกับ role ไม่ได้ผูกกับตาราง | t_app · bypassNoGuc 6 · bypassGucA 6 ขณะที่ appPolicyCount 1 | บทที่ 1 |
| คีย์ของผู้เช่าเป็นค่าตั้งของ session | t_app · appGucA 3 · appGucB 3 · appNoGuc 0 | บทที่ 2 |
| ผู้เช่าที่ไม่มีอยู่จริงไม่ใช่ error มันเป็นศูนย์แถว | t_app · appGucUnknown 0 | บทที่ 2 |
SET LOCAL จบที่ commit · SET ธรรมดาไม่จบ | t_guc · gucInsideSetLocal 3 แล้ว gucAfterCommitSetLocal 0 เทียบกับ gucInsideSet 3 แล้ว gucAfterCommitSet 3 | บทที่ 2 |
| rollback คืนค่าก่อนหน้า ไม่ได้คืนค่าว่าง | t_guc · gucAfterRollbackSet 3 · gucValueAfterRollback clinic-a | บทที่ 2 |
| ”ไม่เคยตั้ง” กับ “เคยตั้งแล้วล้าง” เป็นคนละสถานะ | ทรง current_setting ที่ไม่ใส่อาร์กิวเมนต์ที่สอง · csNoArgNeverSet 42704 เทียบกับ csNoArgAfterReset 00000 · csTrueAfterResetLen 0 · csCountAfterReset 0 | บทที่ 2 |
USING ทำหน้าที่เป็น check ให้เองเมื่อไม่มี WITH CHECK | t_insert · insUsingOnlyOther 42501 แล้วเติม with check (true) ได้ insWithCheckOther 00000 กระทบ 1 แถว · insWithCheckRowsB 4 | บทที่ 3 |
| ตัวที่ตัดสิน UPDATE ข้ามผู้เช่า คือ policy ของ SELECT | t_update · updMoveUsingOnly 42501 · updMoveWithCheckTrue 42501 · updMoveSelectOpen 00000 กระทบ 1 แถว · updSelectOpenRowsA 2 · updSelectOpenRowsB 4 | บทที่ 3 |
| ผู้เขียนมองไม่เห็นแถวที่ตัวเองเพิ่งเขียน | t_insert · insWithCheckOtherReturning 42501 · insWithCheckVisibleToApp 4 | บทที่ 3 |
| การปฏิเสธมีสองเสียง และเสียงหนึ่งไม่ดัง | t_selonly · selOnlyUpdate 00000 กระทบ 0 แถว · selOnlyDelete 00000 กระทบ 0 แถว · selOnlyInsert 42501 · selOnlyRowsAfter 6 · selOnlyBangs 0 | บทที่ 4 |
| เปิด RLS โดยไม่มี policy สักใบ คือปฏิเสธ ไม่ใช่อนุญาต | t_combine · combineZeroPolicies 0 | บทที่ 4 |
| permissive รวมกันด้วย OR · restrictive รวมด้วย AND | t_combine · combineTwoPermissive 6 · combinePermissiveRestrictive 0 · combineTenantAndNotArchived 2 · combineRestrictiveAlone 0 | บทที่ 4 |
| ทางเข้าที่ policy มองไม่เห็น มีมากกว่าหนึ่งทาง | t_view ใต้ no force · noForceBase 3 แต่ noForceViewDefault 6 และ noForceSecDefiner 6 ขณะที่ noForceViewInvoker 3 และ noForceSecInvoker 3 | บทที่ 5 |
FORCE ปิดสองทางที่เปิดอยู่ ส่วนอีกสองทางไม่เคยเปิด เมื่อ policy ผูกกับค่าตั้งของ session | t_view ใต้ force · forceBase forceViewDefault forceViewInvoker forceSecDefiner forceSecInvoker เป็น 3 ทั้งห้าตัว · forceNoGucViewDefault 0 · forceNoGucSecDefiner 0 | บทที่ 5 |
| ตัวที่ปิดได้ ขึ้นกับสิ่งที่ policy ผูกไว้ ไม่ได้ขึ้นกับชื่อของกลไก | t_view · policy ที่อ่าน current_user ให้ currentUserBase 3 · currentUserViewDefault 3 · currentUserViewInvoker 3 · currentUserSecInvoker 3 · currentUserSecDefiner 0 | บทที่ 5 |
| แถวที่เห็นเปลี่ยนเจ้าของได้ โดยยอดรวมไม่ขยับ | t_hijack · hijackBaseTotal 3 กับ hijackBaseRowsB 0 แล้วเป็น hijackOwnTotal 3 กับ hijackOwnRowsA 0 และ hijackOwnRowsB 3 | บทที่ 6 |
ตรึง search_path ลงบนฟังก์ชันแล้ว เลขกลับที่เดิม | t_hijack · hijackFnHasSearchPath false ก่อนตรึง · หลังตรึงได้ hijackPinnedTotal 3 กับ hijackPinnedRowsB 0 | บทที่ 6 |
ทาง pg_temp ปิดอยู่ สำหรับการค้นชื่อฟังก์ชัน | t_hijack · hijackTempQualified clinic-b แต่ hijackTempUnqualified clinic-a · hijackTempTotal 3 · hijackTempRowsB 0 | บทที่ 6 |
| connection ที่ถูกคืนเข้า pool พกค่าตั้งติดไปด้วย | ทรง plain-then-no-tenant · PLAINTHENNOTENANT.b ได้ 200 กับ 3 แถว ซึ่งเป็นแถวของผู้เช่าคนก่อน | บทที่ 7 |
| สองทรงที่ “แก้แล้ว” ปิดฝั่งอ่านได้จริง | LOCALTHENNOTENANT.b.count 0 · RESETTHENNOTENANT.b.count 0 · FRESHNOTENANT.b.count 0 | บทที่ 7 |
| ยาแก้ย้ายที่เขียน มันไม่ได้หยุดการเขียน | WRITEPLAIN ได้ total 8 · visibleToA 5 · visibleToB 3 · leakWrittenUnder <clinic-a> เทียบกับ WRITELOCAL และ WRITERESET ที่ได้ total 8 · visibleToA 4 · visibleToB 3 · leakWrittenUnder <> | บทที่ 7 |
| การอ่านที่เข้มขึ้น แยกสองสถานะของค่าตั้งไม่ออก | STRICTFRESH.strictStatus 500 กับ caughtSqlstate 42704 เทียบกับ STRICTAFTERRESET.strictStatus 200 กับ strictCount 0 | บทที่ 7 |
| ราคาของ policy predicate | มีนาฬิกาอยู่ข้างใน จึงตกนอกหน่วยที่คอร์สนี้ยอมรับ · ไม่ได้วัด | ไม่มีบทไหนพิสูจน์ |
LEAKPROOF และการรั่วผ่านข้อความผิดพลาดของฟังก์ชันใน WHERE | เป็นข้อจำกัดที่รู้จักกันดีของ RLS แต่ probe ของคอร์สนี้ไม่ได้วัดมันสักทรง | ไม่มีบทไหนพิสูจน์ |
| RLS บนตารางแบบ partition และการสืบทอด | ไม่ได้วัด | ไม่มีบทไหนพิสูจน์ |
การค้นชื่อ ตาราง ผ่าน pg_temp | probe วัดเฉพาะการค้นชื่อฟังก์ชัน และกฎของสองอย่างนี้ไม่เหมือนกัน | ไม่มีบทไหนพิสูจน์ |
pg_hba.conf · TLS · การจัดการ role ทั้งระบบ | เป็นคนละคำถามกับคำถามของคอร์สนี้ | ไม่มีบทไหนพิสูจน์ |
| RLS ของ SQL Server และของ Oracle | ทุกข้ออ้างในคอร์สนี้ผูกกับ PostgreSQL 18.6 | ไม่มีบทไหนพิสูจน์ |
| การเข้ารหัสข้อมูลในแถว | เป็นเรื่องของคอร์ส security ทั่วไป | ไม่มีบทไหนพิสูจน์ |
เจ็ดแถวล่างสุดไม่ใช่รายการของที่ยังไม่ได้ทำ มันเป็นรายการของประโยคที่คุณพูดไม่ได้ ถึงจะอ่านคอร์สนี้จบแล้วก็ตาม การเอาแถวใดแถวหนึ่งไปอ้างเท่ากับอ้างของที่ไม่มีใครวัด
ข้อเดียวที่ทำให้ทั้งตารางมีความหมาย
หัวข้อที่มีชื่อว่า “ข้อเดียวที่ทำให้ทั้งตารางมีความหมาย”ถ้า role ที่วัดเป็น superuser ทุกช่องในตารางจะเป็นหกแถว แล้วอ่านเหมือนการค้นพบ
บทที่ 1 วัดไว้แล้วว่า superuser ข้าม policy โดยไม่มีเงื่อนไข superOwnerForceNoGuc
เป็น 6 ทั้งที่ธงใน catalog เป็น true ครบสองใบ ข้อนั้นทำให้ probe ที่ต่อฐานข้อมูล
ด้วย role ผิดใบ กลายเป็น probe ที่ไม่ได้วัดอะไรเลย
เวที A จึงนับ role ที่ตัวเองสร้างซึ่งเป็น superuser ไว้เป็นค่าที่วัดได้ตัวหนึ่ง
คือ rolesAreSuperuser เป็น 0 และมันตรวจข้อนี้ก่อนที่ทรงไหนจะได้วัดสักทรง
อีกสองค่าคุมสิ่งที่ค้างข้ามรอบ คือ dbLeftAfterDrop เป็น 0
และ rolesLeftAfterDrop เป็น 0 สถานะที่ค้างอยู่ทำให้รอบถัดไปวัดคนละเรื่อง
แล้วยังพิมพ์ตัวเลขออกมาหน้าตาเหมือนเดิม
คำถามหกข้อ ตามลำดับที่มันตัดสินจริง
หัวข้อที่มีชื่อว่า “คำถามหกข้อ ตามลำดับที่มันตัดสินจริง”แผนผังนี้ไม่ได้เพิ่มข้ออ้าง มันเรียงข้อที่ตารางข้างบนถืออยู่แล้ว ตามลำดับที่คำสั่งหนึ่งเจอมัน
flowchart TB
Q1{"ผู้อ่านเป็น superuser หรือถือธง BYPASSRLS หรือไม่"}
Q1 -->|"ใช่"| X1["นิพจน์ไม่ถูกเรียกใช้ · บทที่ 1 · superOwnerForceNoGuc 6 และ bypassNoGuc 6"]
Q1 -->|"ไม่ใช่"| Q2{"ผู้อ่านเป็นเจ้าของตาราง และตารางยังไม่ถูกสั่ง FORCE หรือไม่"}
Q2 -->|"ใช่"| X2["เจ้าของอ่านได้ทั้งตาราง · บทที่ 1 · ownerEnableNoGuc 6"]
Q2 -->|"ไม่ใช่"| Q3{"คำสั่งเดินผ่าน view หรือฟังก์ชันหรือไม่"}
Q3 -->|"ผ่าน view ธรรมดา หรือ SECURITY DEFINER ใต้ NO FORCE"| X3["ทางอ้อมไม่เท่าทางตรง · บทที่ 5 · noForceBase 3 เทียบ noForceViewDefault 6"]
Q3 -->|"เดินตรงเข้าตาราง"| Q4{"คำสั่งนี้เขียนหรืออ่าน"}
Q4 -->|"เขียนข้ามผู้เช่า"| X4["policy ของ SELECT เป็นตัวตัดสิน · บทที่ 3 · updMoveUsingOnly 42501"]
Q4 -->|"อ่าน"| Q5{"app.tenant ถือค่าอะไรอยู่ตอนนี้"}
Q5 -->|"ไม่เคยตั้ง หรือถูกล้างไปแล้ว"| X5["0 แถว · บทที่ 2 · appNoGuc 0 และ csCountAfterReset 0"]
Q5 -->|"ผู้เช่าของผู้เรียกเอง"| Q6{"นิพจน์ของ policy เรียกฟังก์ชันที่ไม่ได้ตรึง search_path หรือไม่"}
Q5 -->|"ค่าที่คำขอก่อนหน้าทิ้งไว้บน connection เดิม"| X6["แถวของผู้เช่าคนก่อน · บทที่ 7 · PLAINTHENNOTENANT b 3 แถว"]
Q6 -->|"ไม่ได้ตรึง"| X7["ยอดรวมเท่าเดิม แต่เจ้าของแถวสลับ · บทที่ 6 · hijackOwnTotal 3 กับ hijackOwnRowsB 3"]
Q6 -->|"ตรึงแล้ว"| X8["3 แถวของผู้เช่าตัวเอง · บทที่ 6 · hijackPinnedTotal 3 กับ hijackPinnedRowsB 0"]
คำบรรยายภาพ: ลำดับที่คำสั่งหนึ่งเดินผ่าน จากชนิดของผู้อ่าน ไปที่ธงของตาราง ไปที่ทางเข้า แล้วจึงถึงค่าตั้งของ session ปลายทางทุกใบมีบทเจ้าของกำกับ และสองใบที่ยอดรวมไม่ได้บอกความจริงทั้งหมด คือใบของบทที่ 7 กับใบของบทที่ 6
จุดที่อ่านผิดกันบ่อยที่สุดอยู่ที่คำถามข้อแรก คนที่ตรวจตารางของตัวเองว่าเปิดครบแล้ว ยังตอบคำถามข้อนั้นไม่ได้ เพราะคำตอบอยู่ที่ role ของผู้อ่าน ไม่ได้อยู่ที่ตาราง
จุดบอดของหน่วยที่คอร์สนี้ใช้ทั้งคอร์ส
หัวข้อที่มีชื่อว่า “จุดบอดของหน่วยที่คอร์สนี้ใช้ทั้งคอร์ส”สองทรงที่เหลืออยู่ในตาราง เปลี่ยนของข้างในไปแล้ว โดยจำนวนแถวไม่ขยับ
หน่วยของคอร์สนี้คือจำนวนแถว และมันถูกเลือกเพราะคำขอที่รั่วสำเร็จทุกครั้ง จึงไม่มีรหัสข้อผิดพลาดให้ดู แต่หน่วยนี้ไม่ได้เห็นทุกอย่าง
ทรงแรกคือทรงของบทที่ 6 hijackBaseTotal เป็น 3 และหลังผู้โจมตีวาง schema
ของตัวเองไว้หน้า search_path แล้ว hijackOwnTotal ก็ยังเป็น 3
หลังตรึงค่านั้นลงบนฟังก์ชัน hijackPinnedTotal ก็เป็น 3 อีกครั้ง
สามสถานะให้เลขเดียวกัน สิ่งที่ขยับอยู่ในช่องที่ยอดรวมไม่ได้มอง คือ hijackBaseRowsB
เป็น 0 แล้วกลายเป็น hijackOwnRowsB เป็น 3 และ hijackOwnRowsA เป็น 0
ทรงที่สองคือทรงของบทที่ 7 WRITEPLAIN ซึ่งเป็นทรงที่รั่ว มี total เป็น 8
และ WRITELOCAL กับ WRITERESET ซึ่งเป็นสองทรงที่ “แก้แล้ว” ก็มี total เป็น 8
เท่ากันทั้งสามทรง
ช่องที่แยกทรงที่แก้แล้วออกจากทรงที่รั่ว คือ leakWrittenUnder ซึ่งเป็น <clinic-a>
ในทรงแรก และเป็น <> ในสองทรงหลัง ส่วนรหัส HTTP กับจำนวนแถวที่ลงจริงเท่ากันหมด
WRITELOCAL มี visibleToA เป็น 4 และ visibleToB เป็น 3
ขณะที่ total เป็น 8 สิ่งที่ผู้เช่าสองคนเห็นรวมกัน จึงน้อยกว่าจำนวนแถวในตาราง
แถวที่หายไปไม่ได้ถูกกันไว้จากใครคนใดคนหนึ่ง มันไม่มีผู้เช่าคนไหนเห็นเลย
และไม่มีรหัสข้อผิดพลาดใบไหนบอกเรื่องนี้ ทั้ง WRITEPLAIN และ WRITELOCAL
มี aStatus เป็น 200 และ aInserted เป็น 1 เหมือนกัน
ด่านที่เฝ้าเฉพาะ total ผ่านทั้งสองทรงไปอย่างสะอาด และด่านที่เฝ้าเฉพาะรหัส HTTP
ก็เช่นกัน ข้อที่ทำให้เห็นความต่างคือการนับแยกเป็นรายผู้เช่า
ที่วัดได้ กับที่เอกสารกำหนด
หัวข้อที่มีชื่อว่า “ที่วัดได้ กับที่เอกสารกำหนด”ตารางข้างบนถือของสองชนิด และการสลับสองชนิดนี้เป็นความผิดพลาดคนละแบบ
ช่องกลางของทุกแถวเป็นค่าที่ SQL หรือคำขอ HTTP ตัวจริงคืนกลับมา ส่วนคำอธิบายว่า
ธงใบไหนทำอะไร หรือทำไม pg_temp ไม่ถูกค้นสำหรับชื่อฟังก์ชัน มาจากเอกสาร
ของ PostgreSQL 18 และเอกสารเป็นคนละหลักฐานกับ probe
เวที A รันชุดข้ออ้างทั้งชุดสามรอบต่อการเรียกหนึ่งครั้ง แล้วเทียบผลทีละคีย์ รอบที่รันเมื่อ 2026-09-06 ได้ 52 ข้ออ้างตรงทั้งหมด 98 ค่าที่วัดได้ และ 0 ค่าที่ขยับระหว่างรอบ
เวที B ตรึงค่าของตัวเองลง src/data/perf/clinic-rls.ts ซึ่งเป็นของที่สร้างขึ้น
ไม่ใช่ของที่พิมพ์มือ ทรงของ pool ทั้งสามใบถูกตรึงไว้ที่ maximumPoolSize 1
และ minimumIdle 1 ตามที่ POOL บันทึกไว้
ผลทั้งหมดในบทนี้วัดบน PostgreSQL 18.6 บน aarch64-unknown-linux-musl
ใน container ของคอร์สนี้เอง ส่วนเวที B ยก Spring Boot 4.1.1
กับ Kotlin 2.4.10 ขึ้นจริง major อื่นเป็นคนละการวัด
หน่วยของหลักฐานในคอร์สนี้มีจำนวนแถว จำนวนแถวที่คำสั่งกระทำ จำนวน policy
รหัส SQLSTATE ธงบูลีนใน catalog รหัส HTTP ที่บทที่ 7 วัดและบทนี้ยกมา ความยาวและค่าของสตริงที่
current_setting คืนมา จำนวน connection ที่ pool ถืออยู่พร้อมจำนวน backend
ที่คำขอวิ่งผ่าน และจำนวนโทเค็นที่ต่างกันระหว่างสามบรรทัดที่ประกาศ pool
รวมเก้าหน่วย ตามที่หน้า landing ของคอร์สประกาศไว้
ทั้งคอร์สไม่มีหน่วยเวลาสักตัว
คอร์สจบตรงนี้ แล้วเดินต่อที่ไหน
หัวข้อที่มีชื่อว่า “คอร์สจบตรงนี้ แล้วเดินต่อที่ไหน”สัญญาใบนี้ใช้ได้กับตารางของคุณ ก็ต่อเมื่อคุณตอบคำถามหกข้อในแผนผังด้วยของจริง
ข้อแรกที่ควรทำกับระบบของตัวเอง ไม่ใช่การเขียน policy ใบใหม่ แต่เป็นการตอบว่า
role ที่แอปใช้ต่อฐานข้อมูล เป็น superuser หรือถือธง BYPASSRLS อยู่หรือไม่
สองข้อนั้นทำให้ policy ทุกใบที่คุณเขียนไม่ถูกเรียกใช้
ข้อที่สองคือทางเข้า ตารางเดียวกันมี view และฟังก์ชันชี้เข้าหามันกี่ทาง
และแต่ละทางถูกวัดแล้วหรือยัง บทที่ 5 วัดสี่ทาง และตัวเลขของสี่ทางนั้นไม่เท่ากัน
จนกว่าจะสั่ง FORCE
ข้อที่สามคืออายุของค่าตั้ง ถ้าแอปของคุณใช้ connection pool คำถามไม่ใช่ว่า ตั้งค่าถูกหรือไม่ แต่เป็นว่าค่านั้นถูกล้างเมื่อไร และคำขอใบถัดไปเห็นอะไร
- บทที่ 1 — ธงของตาราง ชนิดของ role และธงบน role
- บทที่ 2 — อายุของค่าตั้ง และสองสถานะที่จำนวนแถวแยกไม่ออก
- บทที่ 3 —
USINGWITH CHECKและRETURNINGบนฝั่งเขียน - บทที่ 4 — การปฏิเสธที่ไม่ส่งเสียง และการรวม policy หลายใบ
- บทที่ 5 — view และฟังก์ชันที่ policy มองไม่เห็น
- บทที่ 6 — การโจมตีที่ไม่ขยับยอดรวม
- บทที่ 7 — connection ที่พกค่าตั้งข้ามคำขอ
-
PostgreSQL 18 — Row Security Policies (เข้าถึง 2026-09-06) — ที่มาของกฎว่าเจ้าของตารางถูกยกเว้นจาก policy ตามค่าปริยาย และ superuser ข้าม policy โดยไม่มีเงื่อนไข
-
PostgreSQL 18 —
CREATE POLICY(เข้าถึง 2026-09-06) — กฎการรวม policy หลายใบ คือ permissive รวมด้วย OR และ restrictive รวมด้วย AND -
PostgreSQL 18 —
CREATE ROLE(เข้าถึง 2026-09-06) — ที่มาของกฎว่าธงBYPASSRLSผูกอยู่กับ role ไม่ได้ผูกกับตาราง -
PostgreSQL 18 —
SET(เข้าถึง 2026-09-06) — อายุของค่าตั้งใน session และความต่างของSET LOCAL -
ค่าของเวที A ทุกตัวมาจาก
scripts/clinic-rls/spike.pyต้องสั่งscripts/clinic-rls/up.shก่อน ซึ่งยก container ของคอร์สนี้เองขึ้นที่ port 55438 · ค่าของเวที B ทุกตัวมาจากsrc/data/perf/clinic-rls.tsซึ่งscripts/clinic-rls/probe.pyสร้างขึ้นและตรึงไว้ใน repo
เช็กความเข้าใจ — บทที่ 8
ข้อ 1 / 3ด่านที่เฝ้าเฉพาะยอดรวมของแถว ผ่านสองทรงในคอร์สนี้ไปโดยไม่แดง ทรงไหนบ้าง