# SQLite Rules

Guidelines for designing schemas specific to SQLite.

---

## Data Types

SQLite has **dynamic typing**, with only 5 storage classes:

| Storage Class | Use case | Notes |
|---------------|----------|-------|
| `INTEGER` | PK, FK, counts, booleans | |
| `REAL` | Floats, decimals | |
| `TEXT` | Strings, dates, JSON, enums | |
| `BLOB` | Binary data | |
| `NULL` | Null values | |

### Type affinity
SQLite does not strictly enforce types. Type declaration is just a "hint":
```sql
-- All of these will be stored
CREATE TABLE test (price REAL);
INSERT INTO test VALUES (100);      -- Stored as INTEGER
INSERT INTO test VALUES (99.99);    -- Stored as REAL
INSERT INTO test VALUES ('free');   -- Stored as TEXT (!)
```

### Date/Time
SQLite **does not have** DATE/TIME type. Store as TEXT with ISO format:
```sql
created_at TEXT DEFAULT (datetime('now'))
-- Format: YYYY-MM-DD HH:MM:SS
```

---

## Metadata Tables (REQUIRED)

SQLite **does not support comments** on tables and columns. Need to create metadata tables:

### Create metadata tables

```sql
CREATE TABLE IF NOT EXISTS metadata_tables (
    table_name TEXT PRIMARY KEY,
    description TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS metadata_columns (
    table_name TEXT NOT NULL,
    column_name TEXT NOT NULL,
    description TEXT NOT NULL,
    data_type TEXT,
    is_nullable INTEGER DEFAULT 1,  -- 0=NOT NULL, 1=NULL
    default_value TEXT,
    created_at TEXT DEFAULT (datetime('now')),
    PRIMARY KEY (table_name, column_name),
    FOREIGN KEY (table_name) REFERENCES metadata_tables(table_name) ON DELETE CASCADE
);

CREATE INDEX idx_metadata_columns_table ON metadata_columns(table_name);
```

### Insert metadata after creating table

```sql
-- Table metadata
INSERT INTO metadata_tables (table_name, description)
VALUES ('orders', 'Table storing customer order information');

-- Column metadata
INSERT INTO metadata_columns (table_name, column_name, description, data_type, is_nullable)
VALUES
    ('orders', 'id', 'Primary key, auto increment', 'INTEGER', 0),
    ('orders', 'order_number', 'Unique order code', 'TEXT', 0),
    ('orders', 'user_id', 'FK to users.id', 'INTEGER', 0),
    ('orders', 'status', 'pending|confirmed|shipped|cancelled', 'TEXT', 0),
    ('orders', 'total_amount', 'Total value', 'REAL', 0);
```

### Query metadata

```sql
-- View schema with descriptions
SELECT
    t.table_name,
    t.description AS table_description,
    c.column_name,
    c.description AS column_description,
    c.data_type
FROM metadata_tables t
LEFT JOIN metadata_columns c ON t.table_name = c.table_name
ORDER BY t.table_name, c.column_name;
```

---

## Auto Increment

```sql
-- Option 1: INTEGER PRIMARY KEY (recommended)
-- SQLite auto-increments INTEGER PRIMARY KEY automatically
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT
);

-- Option 2: AUTOINCREMENT keyword
-- Ensures deleted IDs are not reused (slower)
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT
);
```

---

## Foreign Keys

**IMPORTANT**: FK is disabled by default in SQLite!

```sql
-- Enable FK for each connection
PRAGMA foreign_keys = ON;

-- Create table with FK
CREATE TABLE order_items (
    id INTEGER PRIMARY KEY,
    order_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE RESTRICT
);

-- Create index for FK
CREATE INDEX idx_order_items_order ON order_items(order_id);
CREATE INDEX idx_order_items_product ON order_items(product_id);
```

---

## ENUM substitute

SQLite does not have ENUM. Use CHECK constraint:

```sql
CREATE TABLE orders (
    status TEXT NOT NULL DEFAULT 'pending'
        CHECK (status IN ('pending', 'confirmed', 'shipped', 'cancelled'))
);
```

---

## Index Types

SQLite only supports B-Tree indexes:

```sql
-- Standard index
CREATE INDEX idx_orders_status ON orders(status);

-- Composite index
CREATE INDEX idx_orders_user_status ON orders(user_id, status);

-- Unique index
CREATE UNIQUE INDEX idx_orders_number ON orders(order_number);

-- Partial index (SQLite 3.8.0+)
CREATE INDEX idx_orders_active ON orders(user_id)
    WHERE deleted_at IS NULL;
```

---

## Limitations

| Feature | Status | Workaround |
|---------|--------|------------|
| `ALTER COLUMN` | ❌ Not supported | Recreate table |
| `DROP COLUMN` | ✅ SQLite 3.35+ | Recreate table (older) |
| Comments | ❌ Not supported | Metadata tables |
| ENUM | ❌ Not supported | CHECK constraint |
| Stored procedures | ❌ Not supported | App logic |
| Concurrent writes | ⚠️ Limited | Single writer |

---

## Example DDL

```sql
-- Enable foreign keys
PRAGMA foreign_keys = ON;

-- Create table
CREATE TABLE orders (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_number TEXT NOT NULL,
    user_id INTEGER NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending'
        CHECK (status IN ('pending', 'confirmed', 'shipped', 'cancelled')),
    subtotal REAL NOT NULL DEFAULT 0,
    discount_amount REAL NOT NULL DEFAULT 0,
    total_amount REAL NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    deleted_at TEXT,

    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT
);

-- Indexes
CREATE UNIQUE INDEX idx_orders_number ON orders(order_number);
CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_updated ON orders(updated_at);

-- Metadata
INSERT INTO metadata_tables (table_name, description)
VALUES ('orders', 'Table storing order information');

INSERT INTO metadata_columns (table_name, column_name, description, data_type, is_nullable)
VALUES
    ('orders', 'id', 'Primary key', 'INTEGER', 0),
    ('orders', 'order_number', 'Unique order code', 'TEXT', 0),
    ('orders', 'user_id', 'FK to users.id', 'INTEGER', 0),
    ('orders', 'status', 'pending|confirmed|shipped|cancelled', 'TEXT', 0),
    ('orders', 'total_amount', 'Total payment', 'REAL', 0);
```

---

## Checklist

- [ ] `PRAGMA foreign_keys = ON` at start of each connection
- [ ] Metadata tables created
- [ ] INSERT metadata after each CREATE TABLE
- [ ] Use `TEXT` for dates (ISO format: YYYY-MM-DD HH:MM:SS)
- [ ] CHECK constraint instead of ENUM
- [ ] Index for FK columns
- [ ] Update metadata when ALTER/DROP table
