Contents
Use CLS and Principal Scopes
pg_liquid includes extension-level read filtering for graph and compound data.
The Three Entry Points
liquid.query(program): unrestricted by default unlesspg_liquid.policy_principalis setliquid.query_as(principal, program): trusted wrapper for principal-bound reads or writesliquid.read_as(principal, program): least-privilege read wrapper; rejects top-level assertions
Step 1. Define What Can Be Read
select *
from liquid.query($$
DefCompound("UserSignal", "user", "0", "liquid/node").
DefCompound("UserSignal", "value", "0", "liquid/string").
Edge("UserSignal", "liquid/mutable", "false").
CompoundReadByRole@(compound_type="UserSignal", role="user").
UserSignal@(user="user:alice", value="prefers_async_updates").
UserSignal@(user="user:bob", value="prefers_dashboards").
UserSignal@(cid=cid, user=user_id, value=value)?
$$) as t(cid text, user_id text, value text);
Step 2. Read as a Principal
select user_id, value
from liquid.read_as('user:alice', $$
UserSignal@(cid=cid, user=user_id, value=value)?
$$) as t(cid text, user_id text, value text);
Only compounds whose guarded role matches user:alice are visible.
Step 3. Add Principal Inheritance
select *
from liquid.query($$
Edge("session:alice-app", "liquid/acts_for", "user:alice").
$$) as t(dummy text);
Now a bound session principal can inherit the user’s effective scope.
Step 4. Prefer read_as(...) for Application Reads
Use liquid.read_as(...) when:
- clients should not assert data
- you want one-call principal binding
- you want least-privilege access through SQL
Step 5. Keep PostgreSQL ACLs in Place
pg_liquid policy rules do not replace ordinary PostgreSQL permissions.
Recommended production baseline:
revoke all on schema liquid from public;
revoke all on all tables in schema liquid from public;
revoke all on all functions in schema liquid from public;
grant usage on schema liquid to app_user;
grant execute on function liquid.read_as(text, text) to app_user;