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

ScyllaDB / Cassandra Data Types (Beta) ​

SQL TypeGo TypeTypeScript TypePython TypeDescription
textstringstringstrUTF-8 text
intint32numberint32-bit integer
bigintint64numberint64-bit integer
booleanboolbooleanboolTrue/false
uuidstringstringstrUUID
timeuuidstringstringstrTime-based UUID
timestamptime.TimeDatedatetimeDate and time
datetime.TimeDatedateDate only
floatfloat32numberfloat32-bit float
doublefloat64numberfloat64-bit float
map<K,V>map[K]VRecord<K,V>dictTyped map
list<T>[]TT[]listTyped list
set<T>[]TT[]setTyped set
frozen<T>custom structcustom typecustom classFrozen immutable type

ClickHouse Data Types (Beta) ​

SQL TypeGo TypeTypeScript TypePython TypeDescription
StringstringstringstrVariable-length string
Int32int32numberint32-bit integer
UInt32uint32numberint32-bit unsigned integer
Int64int64numberint64-bit integer
UInt64uint64numberint64-bit unsigned integer
Float32float32numberfloat32-bit float
Float64float64numberfloat64-bit float
BoolboolbooleanboolBoolean
Datetime.TimeDatedateDate only
DateTimetime.TimeDatedatetimeDate and time
UUIDstringstringstrUUID
Decimal(P,S)stringnumberDecimalFixed-point decimal
JSONstringanydictJSON object

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.