Skip to content

Policies

PostgreSQL Row Level Security (RLS) policies control which rows users can access in tables.

PostgreSQL Specification

CREATE POLICY Syntax

CREATE POLICY name ON table_name
    [ AS { PERMISSIVE | RESTRICTIVE } ]
    [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
    [ TO { role_name | PUBLIC | CURRENT_ROLE | CURRENT_USER | SESSION_USER } [, ...] ]
    [ USING ( using_expression ) ]
    [ WITH CHECK ( check_expression ) ]

Reference: PostgreSQL 17 CREATE POLICY

pgdelta Support

✅ Currently Supported

  • CREATE POLICY with all options
  • DROP POLICY
  • ALTER POLICY
  • All policy types (PERMISSIVE, RESTRICTIVE)
  • All policy commands (SELECT, INSERT, UPDATE, DELETE, ALL)
CREATE POLICY "users_own_data" ON "public"."users"
    FOR ALL
    TO authenticated
    USING (auth.uid() = id);

❌ Not Yet Supported

  • Complex policy dependency optimization
  • Policy inheritance patterns

🚫 Intentionally Not Supported

  • Role-based security (environment-specific)

Usage Examples

Basic Row-Level Security

target_sql = """
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    is_active BOOLEAN DEFAULT true
);

-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- Policy for users to see their own data
CREATE POLICY users_own_data ON users
    FOR ALL
    TO authenticated
    USING (auth.uid() = id);
"""

Restrictive Policy

target_sql = """
CREATE TABLE sensitive_data (
    id SERIAL PRIMARY KEY,
    user_id INTEGER,
    data TEXT
);

ALTER TABLE sensitive_data ENABLE ROW LEVEL SECURITY;

-- Only allow access to active users
CREATE POLICY active_users_only ON sensitive_data
    AS RESTRICTIVE
    FOR ALL
    TO authenticated
    USING (EXISTS (SELECT 1 FROM users WHERE users.id = user_id AND is_active = true));
"""

Command-Specific Policies

target_sql = """
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    author_id INTEGER,
    title TEXT,
    content TEXT,
    published BOOLEAN DEFAULT false
);

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Users can read published posts or their own posts
CREATE POLICY posts_select ON posts
    FOR SELECT
    TO authenticated
    USING (published = true OR auth.uid() = author_id);

-- Users can only update their own posts
CREATE POLICY posts_update ON posts
    FOR UPDATE
    TO authenticated
    USING (auth.uid() = author_id);
"""

Implementation Details

Policy Models

@dataclass(frozen=True)
class CreatePolicy:
    stable_id: str              # Format: "p:schema.table.policy_name"
    namespace: str              # Schema name
    table_name: str             # Table name
    policy_name: str            # Policy name
    policy_definition: str      # Complete policy definition

@dataclass(frozen=True)
class AlterPolicy:
    stable_id: str              # Format: "p:schema.table.policy_name"
    namespace: str              # Schema name
    table_name: str             # Table name
    policy_name: str            # Policy name
    policy_definition: str      # New policy definition

SQL Generation

def generate_create_policy_sql(change: CreatePolicy) -> str:
    """Generate CREATE POLICY SQL."""
    return change.policy_definition + ";"

def generate_alter_policy_sql(change: AlterPolicy) -> str:
    """Generate ALTER POLICY SQL."""
    return change.policy_definition + ";"

Policy Types

Permissive Policies (Default)

-- Allow access when condition is true
CREATE POLICY permissive_policy ON table_name
    AS PERMISSIVE
    FOR ALL
    USING (condition);

Restrictive Policies

-- Restrict access unless condition is true
CREATE POLICY restrictive_policy ON table_name
    AS RESTRICTIVE
    FOR ALL
    USING (condition);

Future Enhancements

Planned Features (v0.2.0)

  • Policy inheritance tracking
  • Complex policy dependency resolution
  • Policy performance analysis

Best Practices

Policy Naming

# Good policy names
good_names = [
    "users_own_data",         # Clear ownership
    "published_posts_read",   # Specific action
    "admin_full_access",      # Role-based
    "active_users_only",      # Status-based
]

# Naming patterns
patterns = {
    "ownership": "entity_own_data",
    "action_based": "entity_action_condition",
    "role_based": "role_access_level",
    "status_based": "condition_only"
}

Security Considerations

# RLS security guidelines
security_guidelines = {
    "enable_rls": "Always enable RLS on sensitive tables",
    "default_deny": "Start with restrictive policies",
    "test_policies": "Test with different user contexts",
    "performance": "Keep policy conditions efficient"
}

# Common patterns
common_patterns = {
    "user_isolation": "Users can only access their own data",
    "role_based": "Different access levels for different roles",
    "status_based": "Access based on record status",
    "time_based": "Access based on time conditions"
}