# Canonical Code Examples

This document contains the definitive versions of common code examples used throughout FraiseQL documentation. Instead of duplicating these examples, other documentation pages link here.

---

## Database Schema Examples

### User Table

**Complete example with Trinity identifiers:**

```sql
CREATE TABLE tb_user (
    -- Trinity identifiers (see: https://docs.fraiseql.com/core/concepts-glossary.md#trinity-identifiers)
    pk_user INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,  -- Internal (fast JOINs)
    id UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,         -- Public API
    identifier TEXT UNIQUE,                                     -- Human-readable slug

    -- User data
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    bio TEXT,
    avatar_url TEXT,

    -- Timestamps
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    deleted_at TIMESTAMPTZ
);

-- Indexes
CREATE INDEX idx_user_email ON tb_user(email) WHERE deleted_at IS NULL;
CREATE INDEX idx_user_identifier ON tb_user(identifier) WHERE deleted_at IS NULL;
CREATE INDEX idx_user_created ON tb_user(created_at DESC) WHERE deleted_at IS NULL;
```

**Referenced from**:
- [Core Concepts Glossary](../core/concepts-glossary.md#trinity-identifiers)
- [Trinity Pattern Guide](../guides/trinity-pattern-guide.md)
- [Blog API Tutorial](../tutorials/blog-api.md)

---

### Post Table

**Blog post example with foreign keys:**

```sql
CREATE TABLE tb_post (
    -- Trinity identifiers
    pk_post INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    id UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
    identifier TEXT UNIQUE,  -- URL slug like "my-first-post"

    -- Foreign keys (use pk_* for performance)
    user_id INT NOT NULL REFERENCES tb_user(pk_user),

    -- Content
    title TEXT NOT NULL,
    content TEXT,
    status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),

    -- Timestamps
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    published_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ
);

-- Indexes
CREATE INDEX idx_post_user ON tb_post(user_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_post_status ON tb_post(status, created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX idx_post_identifier ON tb_post(identifier) WHERE deleted_at IS NULL;
```

**Referenced from**:
- [Blog API Tutorial](../tutorials/blog-api.md)
- [Database Patterns](../advanced/database-patterns.md)

---

## PostgreSQL Function Examples

### Create User Function

**Complete example with validation and error handling:**

```sql
CREATE OR REPLACE FUNCTION fn_create_user(
    p_name TEXT,
    p_email TEXT,
    p_identifier TEXT DEFAULT NULL
) RETURNS JSONB AS $$
DECLARE
    v_user_id UUID;
BEGIN
    -- Validation: Email format
    IF p_email !~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$' THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'Invalid email format',
            'code', 'INVALID_EMAIL'
        );
    END IF;

    -- Validation: Name length
    IF LENGTH(p_name) < 2 THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'Name must be at least 2 characters',
            'code', 'INVALID_NAME'
        );
    END IF;

    -- Insert user
    INSERT INTO tb_user (name, email, identifier)
    VALUES (p_name, p_email, p_identifier)
    RETURNING id INTO v_user_id;

    -- Audit log (optional)
    INSERT INTO audit_log (action, details)
    VALUES ('user_created', jsonb_build_object('user_id', v_user_id));

    -- Return success with user data
    RETURN jsonb_build_object(
        'success', true,
        'user', (SELECT data FROM v_user WHERE id = v_user_id),
        'message', 'User created successfully'
    );
EXCEPTION
    WHEN unique_violation THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'Email or identifier already exists',
            'code', 'DUPLICATE_USER'
        );
    WHEN OTHERS THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', SQLERRM,
            'code', 'DATABASE_ERROR'
        );
END;
$$ LANGUAGE plpgsql;
```

**Referenced from**:
- [Mutation SQL Requirements](../guides/mutation-sql-requirements.md)
- [Getting Started Quickstart](../getting-started/quickstart.md)
- [Blog API Tutorial](../tutorials/blog-api.md)

---

### Update User Function

**Example with optimistic locking:**

```sql
CREATE OR REPLACE FUNCTION fn_update_user(
    p_id UUID,
    p_name TEXT DEFAULT NULL,
    p_bio TEXT DEFAULT NULL,
    p_avatar_url TEXT DEFAULT NULL
) RETURNS JSONB AS $$
DECLARE
    v_updated_rows INT;
BEGIN
    -- Update only provided fields
    UPDATE tb_user
    SET
        name = COALESCE(p_name, name),
        bio = COALESCE(p_bio, bio),
        avatar_url = COALESCE(p_avatar_url, avatar_url),
        updated_at = NOW()
    WHERE id = p_id
      AND deleted_at IS NULL;

    GET DIAGNOSTICS v_updated_rows = ROW_COUNT;

    IF v_updated_rows = 0 THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'User not found',
            'code', 'NOT_FOUND'
        );
    END IF;

    RETURN jsonb_build_object(
        'success', true,
        'user', (SELECT data FROM v_user WHERE id = p_id),
        'message', 'User updated successfully'
    );
END;
$$ LANGUAGE plpgsql;
```

---

### Delete User Function (Soft Delete)

**Example with cascade considerations:**

```sql
CREATE OR REPLACE FUNCTION fn_delete_user(
    p_id UUID
) RETURNS JSONB AS $$
DECLARE
    v_deleted_rows INT;
BEGIN
    -- Soft delete
    UPDATE tb_user
    SET deleted_at = NOW()
    WHERE id = p_id
      AND deleted_at IS NULL;

    GET DIAGNOSTICS v_deleted_rows = ROW_COUNT;

    IF v_deleted_rows = 0 THEN
        RETURN jsonb_build_object(
            'success', false,
            'error', 'User not found or already deleted',
            'code', 'NOT_FOUND'
        );
    END IF;

    RETURN jsonb_build_object(
        'success', true,
        'message', 'User deleted successfully'
    );
END;
$$ LANGUAGE plpgsql;
```

---

## GraphQL Type Examples

### User Type

**Complete example with Trinity identifiers:**

```python
import fraiseql
from fraiseql.types import ID
from datetime import datetime

@fraiseql.type(sql_source="v_user")
class User:
    """User account with Trinity identifiers."""
    # Trinity identifiers (only public ones exposed)
    id: ID          # Public API identifier
    identifier: str   # Human-readable slug
    # pk_user is NOT exposed (internal only)

    # User data
    name: str
    email: str
    bio: str | None
    avatar_url: str | None

    # Timestamps
    created_at: datetime
    updated_at: datetime
```

**Referenced from**:
- [Core Concepts Glossary](../core/concepts-glossary.md)
- [Types & Schema](../core/types-and-schema.md)
- [Getting Started](../getting-started/quickstart.md)

---

### Post Type with Relations

**Example with nested data:**

```python
import fraiseql
from fraiseql.types import ID
from datetime import datetime

@fraiseql.type(sql_source="v_post")
class Post:
    """Blog post with author relation."""
    # Trinity identifiers
    id: ID
    identifier: str

    # Content
    title: str
    content: str | None
    status: str

    # Relations (embedded in JSONB or via dataloader)
    author: User

    # Timestamps
    created_at: datetime
    updated_at: datetime
    published_at: datetime | None
```

---

## GraphQL Query Examples

### Basic Query

**Fetch all users:**

```graphql
query {
  users {
    id
    name
    email
  }
}
```

---

### Query with WHERE Filters

**Filter by status:**

```graphql
query {
  posts(where: {
    status: { eq: "published" }
    created_at: { gte: "2024-01-01" }
  }) {
    id
    title
    author {
      name
    }
  }
}
```

---

### Query with Pagination

**Relay-style cursor pagination:**

```graphql
query {
  posts(first: 10, after: "cursor123") {
    nodes {
      id
      title
    }
    pageInfo {
      hasNextPage
      endCursor
    }
    totalCount
  }
}
```

---

### Mutation Example

**Create user mutation:**

```graphql
mutation {
  createUser(input: {
    name: "Alice"
    email: "alice@example.com"
    identifier: "alice"
  }) {
    success {
      user {
        id
        name
        email
      }
      message
    }
    failure {
      message
      code
    }
  }
}
```

---

## View Examples

### Basic JSONB View

**Simple view without relations:**

```sql
CREATE VIEW v_user AS
SELECT
    id,  -- Direct column for WHERE filtering
    jsonb_build_object(
        'id', id,
        'identifier', identifier,
        'name', name,
        'email', email,
        'bio', bio,
        'avatar_url', avatar_url,
        'created_at', created_at,
        'updated_at', updated_at
    ) as data
FROM tb_user
WHERE deleted_at IS NULL;
```

**Referenced from**:
- [Core Concepts Glossary](../core/concepts-glossary.md#jsonb-view-pattern)
- [Database API](../core/database-api.md)

---

### JSONB View with Relations

**View with embedded nested data:**

```sql
CREATE VIEW v_post_with_author AS
SELECT
    p.id,
    jsonb_build_object(
        'id', p.id,
        'identifier', p.identifier,
        'title', p.title,
        'content', p.content,
        'status', p.status,
        'author', (
            SELECT jsonb_build_object(
                'id', u.id,
                'name', u.name,
                'email', u.email
            )
            FROM tb_user u
            WHERE u.pk_user = p.user_id
        ),
        'created_at', p.created_at,
        'published_at', p.published_at
    ) as data
FROM tb_post p
WHERE p.deleted_at IS NULL;
```

---

### Projection Table (tv_*)

**High-performance cached table:**

```sql
-- Projection table (regular table with cached JSONB)
CREATE TABLE tv_user (
    id UUID PRIMARY KEY,
    data JSONB NOT NULL,
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Sync function (must be called explicitly)
CREATE FUNCTION fn_sync_tv_user(p_id UUID) RETURNS VOID AS $$
BEGIN
    INSERT INTO tv_user (id, data)
    SELECT id, data FROM v_user WHERE id = p_id
    ON CONFLICT (id) DO UPDATE SET
        data = EXCLUDED.data,
        updated_at = NOW();
END;
$$ LANGUAGE plpgsql;

-- Usage in mutation (explicit sync required)
CREATE FUNCTION fn_create_user(...) RETURNS JSONB AS $$
DECLARE v_user_id UUID;
BEGIN
    INSERT INTO tb_user (...) VALUES (...) RETURNING id INTO v_user_id;

    -- ✅ Explicitly sync to projection table
    PERFORM fn_sync_tv_user(v_user_id);

    RETURN (SELECT data FROM tv_user WHERE id = v_user_id);
END;
$$ LANGUAGE plpgsql;
```

**Referenced from**:
- [Core Concepts Glossary](../core/concepts-glossary.md#projection-tables-tv_)
- [Database Patterns](../advanced/database-patterns.md)

---

## Usage Guidelines

**When referencing these examples:**

✅ **DO:**
- Link to this page instead of copying
- Use relative links: `[example](../examples/canonical-examples.md#user-table)`
- Mention which section you're referring to

❌ **DON'T:**
- Copy/paste entire examples into your documentation
- Modify examples inline (create variations in your own doc instead)
- Create new "canonical" examples elsewhere

**If you need a variation:**
1. Link to the canonical example first
2. Show only the difference:
   ```markdown
   Based on the [canonical User table](../examples/canonical-examples.md#user-table),
   add a `role` column:

   ```sql
   ALTER TABLE tb_user ADD COLUMN role TEXT DEFAULT 'user';
   ```
   ```

---

## Maintaining This File

**Guidelines for contributors:**

- Keep examples complete and runnable
- Include comments explaining non-obvious parts
- Show best practices (error handling, validation, etc.)
- Update "Referenced from" sections when adding new links
- Test examples before committing
- Follow [FraiseQL terminology guide](../reference/terminology.md)

**When to add a new example:**
- When you find the same pattern in 3+ documentation files
- When creating a new tutorial that needs a standard example
- When a common question needs a definitive answer

**When NOT to add:**
- One-off examples specific to a single guide
- Variations of existing examples (link to original + show diff)
- Deprecated patterns

---

**Last Updated**: 2025-12-30
**Maintained By**: FraiseQL documentation team
