Skip to content

Schema Definition

Flash ORM uses SQL files to define your database schema. This approach gives you full control over your database structure while providing powerful code generation capabilities.

Table of Contents

Basic Schema

Single Schema File

sql
-- db/schema/schema.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    published BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Multiple Schema Files

Flash ORM supports splitting your schema across multiple files for better organization:

db/schema/
├── users.sql
├── posts.sql
├── comments.sql
└── categories.sql
sql
-- db/schema/users.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
sql
-- db/schema/posts.sql
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    published BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Data Types

PostgreSQL Data Types

SQL TypeGo TypeTypeScript TypePython TypeDescription
SERIALint64numberintAuto-incrementing integer
BIGSERIALint64numberintLarge auto-incrementing integer
INTEGERint32numberint32-bit integer
BIGINTint64numberint64-bit integer
VARCHAR(n)stringstringstrVariable-length string
TEXTstringstringstrUnlimited text
BOOLEANboolbooleanboolTrue/false
TIMESTAMPtime.TimeDatedatetimeDate and time
DATEtime.TimeDatedateDate only
TIMEtime.TimestringtimeTime only
JSONB[]byteanydictJSON data
UUIDstringstringstrUUID string
BYTEA[]byteBufferbytesBinary data

MySQL Data Types

SQL TypeGo TypeTypeScript TypePython TypeDescription
AUTO_INCREMENTint64numberintAuto-incrementing integer
INTint32numberint32-bit integer
BIGINTint64numberint64-bit integer
VARCHAR(n)stringstringstrVariable-length string
TEXTstringstringstrText data
TINYINT(1)boolbooleanboolBoolean
DATETIMEtime.TimeDatedatetimeDate and time
DATEtime.TimeDatedateDate only
JSON[]byteanydictJSON data
BLOB[]byteBufferbytesBinary data

SQLite Data Types

SQL TypeGo TypeTypeScript TypePython TypeDescription
INTEGERint64numberintInteger (auto-incrementing with PRIMARY KEY)
TEXTstringstringstrText data
REALfloat64numberfloatFloating point
BLOB[]byteBufferbytesBinary data

Constraints

Primary Keys

sql
-- Single column primary key
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL
);

-- Composite primary key
CREATE TABLE user_permissions (
    user_id INTEGER NOT NULL,
    permission_id INTEGER NOT NULL,
    granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, permission_id)
);

Foreign Keys

sql
-- Basic foreign key
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    title VARCHAR(255) NOT NULL
);

-- Foreign key with actions
CREATE TABLE comments (
    id SERIAL PRIMARY KEY,
    post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE ON UPDATE CASCADE,
    user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
    content TEXT NOT NULL
);

Unique Constraints

sql
-- Single column unique
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL
);

-- Multiple column unique
CREATE TABLE user_follows (
    follower_id INTEGER NOT NULL,
    following_id INTEGER NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (follower_id, following_id)
);

Check Constraints

sql
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10,2) CHECK (price > 0),
    stock_quantity INTEGER CHECK (stock_quantity >= 0),
    discount_percent DECIMAL(5,2) CHECK (discount_percent BETWEEN 0 AND 100)
);

Not Null Constraints

sql
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id),
    total_amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(50) NOT NULL DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Indexes

Basic Indexes

sql
-- Single column index
CREATE INDEX idx_users_email ON users(email);

-- Multiple column index
CREATE INDEX idx_posts_user_created ON posts(user_id, created_at DESC);

-- Unique index
CREATE UNIQUE INDEX idx_users_username ON users(username);

Partial Indexes

sql
-- Index only active users
CREATE INDEX idx_active_users ON users(email) WHERE is_active = true;

-- Index recent posts
CREATE INDEX idx_recent_posts ON posts(created_at DESC) WHERE created_at > NOW() - INTERVAL '30 days';

Index Types

sql
-- B-tree index (default)
CREATE INDEX idx_users_name ON users(name);

-- Hash index (PostgreSQL only)
CREATE INDEX idx_users_email_hash ON users USING HASH (email);

-- GIN index for arrays (PostgreSQL)
CREATE INDEX idx_posts_tags ON posts USING GIN (tags);

-- GIN index for JSONB (PostgreSQL)
CREATE INDEX idx_user_prefs ON user_preferences USING GIN (preferences);

Enums

PostgreSQL Enums

sql
-- Create enum type
CREATE TYPE user_role AS ENUM ('admin', 'moderator', 'user');
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered', 'cancelled');

-- Use in tables
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    role user_role DEFAULT 'user'
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    status order_status DEFAULT 'pending',
    total_amount DECIMAL(10,2) NOT NULL
);

MySQL Enums

sql
-- MySQL enum (stored as string)
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    role ENUM('admin', 'moderator', 'user') DEFAULT 'user'
);

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT REFERENCES users(id),
    status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
    total_amount DECIMAL(10,2) NOT NULL
);

Relationships

One-to-Many

sql
-- One user has many posts
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title VARCHAR(255) NOT NULL,
    content TEXT
);

Many-to-Many

sql
-- Users can have many roles, roles can belong to many users
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE roles (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL
);

CREATE TABLE user_roles (
    user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    role_id INTEGER NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
    assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, role_id)
);

Self-Referencing

sql
-- Employee hierarchy
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    manager_id INTEGER REFERENCES employees(id),
    department VARCHAR(50)
);

Advanced Features

Arrays (PostgreSQL)

sql
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    tags TEXT[],  -- Array of strings
    categories INTEGER[],  -- Array of integers
    metadata JSONB
);

JSON/JSONB (PostgreSQL)

sql
CREATE TABLE user_preferences (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    preferences JSONB,  -- Flexible JSON storage
    settings JSON,      -- Regular JSON
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- GIN index for JSON queries
CREATE INDEX idx_user_prefs ON user_preferences USING GIN (preferences);

Generated Columns (PostgreSQL)

sql
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    tax_rate DECIMAL(5,4) DEFAULT 0.08,
    -- Generated column
    price_with_tax DECIMAL(10,2) GENERATED ALWAYS AS (price * (1 + tax_rate)) STORED
);

Triggers

sql
-- Update timestamp trigger
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_users_updated_at
    BEFORE UPDATE ON users
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

Views

sql
-- Create a view
CREATE VIEW user_posts AS
SELECT
    u.id as user_id,
    u.name as user_name,
    u.email,
    p.id as post_id,
    p.title,
    p.content,
    p.published,
    p.created_at as post_created_at
FROM users u
LEFT JOIN posts p ON u.id = p.user_id;

-- Use the view in queries
-- name: GetUserWithPosts :many
SELECT * FROM user_posts WHERE user_id = $1;

Best Practices

Naming Conventions

sql
-- Tables: lowercase, plural, snake_case
CREATE TABLE user_profiles (
    -- Primary keys: id
    id SERIAL PRIMARY KEY,

    -- Foreign keys: {table}_id
    user_id INTEGER REFERENCES users(id),

    -- Columns: snake_case
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    phone_number VARCHAR(20),
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Indexes: idx_{table}_{columns}
CREATE INDEX idx_user_profiles_user_id ON user_profiles(user_id);
CREATE INDEX idx_user_profiles_name ON user_profiles(first_name, last_name);

Schema Organization

db/schema/
├── 01_users.sql           # Core entities first
├── 02_authentication.sql  # Authentication related
├── 03_content.sql         # Content management
├── 04_interactions.sql    # User interactions
├── 05_analytics.sql       # Analytics and reporting
└── 06_migrations.sql      # Migration helpers

Performance Considerations

sql
-- Use appropriate data types
CREATE TABLE logs (
    id BIGSERIAL PRIMARY KEY,
    level VARCHAR(10) NOT NULL,  -- Use constrained VARCHAR instead of TEXT
    message TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Add indexes for frequently queried columns
CREATE INDEX idx_logs_level_created ON logs(level, created_at DESC);
CREATE INDEX idx_logs_created_at ON logs(created_at DESC);

-- Use partial indexes for common filters
CREATE INDEX idx_active_users ON users(created_at) WHERE is_active = true;

Data Integrity

sql
-- Use constraints to enforce business rules
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id),
    total_amount DECIMAL(10,2) NOT NULL CHECK (total_amount > 0),
    status VARCHAR(20) NOT NULL DEFAULT 'pending'
        CHECK (status IN ('pending', 'processing', 'shipped', 'delivered', 'cancelled')),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Prevent negative inventory
CREATE TABLE inventory (
    product_id INTEGER PRIMARY KEY REFERENCES products(id),
    quantity INTEGER NOT NULL DEFAULT 0 CHECK (quantity >= 0),
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Documentation

sql
-- Add comments to document your schema
COMMENT ON TABLE users IS 'Registered users of the application';
COMMENT ON COLUMN users.email IS 'Unique email address for authentication';
COMMENT ON COLUMN users.is_active IS 'Soft delete flag - false means deactivated';

-- Document constraints
ALTER TABLE users ADD CONSTRAINT chk_email_format
    CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
COMMENT ON CONSTRAINT chk_email_format ON users IS 'Ensures email follows valid format';

Migration Safety

sql
-- Always provide rollback strategies
-- Up migration
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- Down migration (in separate file)
ALTER TABLE users DROP COLUMN phone;

-- Use transactions for complex changes
BEGIN;
ALTER TABLE orders ADD COLUMN shipping_address TEXT;
UPDATE orders SET shipping_address = user_addresses.address
FROM user_addresses WHERE orders.user_id = user_addresses.user_id;
COMMIT;

Remember: Your schema files are the source of truth for your database structure. Keep them well-organized, properly constrained, and thoroughly tested before applying migrations to production environments.

Released under the MIT License.