Skip to main content

Flyway Database Migrations

Complete documentation of Flyway migrations V15-V31, including staging infrastructure, UUID to BIGINT migration, and catalog table seeding.

Overview

Migrations are located in src/main/resources/db/migration/ and executed automatically on application startup via Flyway.

Migration Timeline

1

V15-V21: Catalog Tables

Foundation catalog tables (units, device types, actuator states, sectors)
2

V22-V25: UUID to BIGINT

Major refactoring for 40% performance improvement
3

V26-V30: Settings Refinement

Settings table updates and catalog enhancements
4

V31: Initial Data Seeding

Populate catalog tables with production data

V11: Staging Infrastructure (581 lines)

Purpose: Production-grade bulk import system for million-record datasets Date: November 16, 2025

Usage Workflow

-- Generate batch ID
SELECT gen_random_uuid() AS batch_id;
-- Returns: '550e8400-e29b-41d4-a716-446655440000'

-- Insert raw data from CSV/API
INSERT INTO staging.sensor_readings_raw (
    batch_id, time, sensor_id, greenhouse_id, tenant_id, 
    value, unit, source_system
)
VALUES
    ('550e8400-...', '2025-11-16 10:00:00', 'TEMP_01', 
     'valid-uuid', 'valid-tenant-uuid', 25.5, '°C', 'csv_import'),
    -- ... 1 million more records

V15: Password Reset Fields

Date: 2025-12-29 Purpose: Add password reset token functionality to users table.
ALTER TABLE metadata.users
    ADD COLUMN IF NOT EXISTS reset_password_token VARCHAR(255),
    ADD COLUMN IF NOT EXISTS reset_password_token_expiry TIMESTAMPTZ;

COMMENT ON COLUMN metadata.users.reset_password_token 
    IS 'Token for password reset flow';
COMMENT ON COLUMN metadata.users.reset_password_token_expiry 
    IS 'Expiration time for the password reset token';

V16-V21: Catalog Tables

Date: 2025-12-29Purpose: Normalized catalog for measurement units.
CREATE TABLE metadata.units (
    id SMALLSERIAL PRIMARY KEY,
    symbol VARCHAR(10) NOT NULL,
    name VARCHAR(50) NOT NULL,
    description TEXT,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    CONSTRAINT uq_units_symbol UNIQUE (symbol)
);

INSERT INTO metadata.units (symbol, name, description) VALUES
    ('°C', 'Grados Celsius', 'Temperatura en grados Celsius'),
    ('°F', 'Grados Fahrenheit', 'Temperatura en grados Fahrenheit'),
    ('%', 'Porcentaje', 'Porcentaje (humedad, apertura, velocidad)'),
    ('hPa', 'Hectopascales', 'Presion atmosferica'),
    ('ppm', 'Partes por millon', 'Concentracion de CO2 u otros gases'),
    ('lux', 'Lux', 'Intensidad luminica'),
    -- ... 15 more units
Units Included:
  • Temperature: °C, °F
  • Percentage: %
  • Pressure: hPa, bar
  • Gas Concentration: ppm
  • Light: lux, W/m²
  • Speed: m/s, km/h, RPM
  • Flow: L/h, L/min, m³/h
  • Water: mm (precipitation)
  • Power: W, kW
  • Chemistry: pH, mS/cm, dS/m
  • Angle: ° (degrees)
Date: 2025-12-30Purpose: Catalog of actuator operational states with colors.
CREATE TABLE metadata.actuator_states (
    id SMALLSERIAL PRIMARY KEY,
    name VARCHAR(30) NOT NULL UNIQUE,
    description TEXT,
    is_operational BOOLEAN NOT NULL DEFAULT FALSE,
    display_order SMALLINT,
    color VARCHAR(7),  -- Hex color code
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

INSERT INTO metadata.actuator_states (name, description, is_operational, color) VALUES
    ('OFF', 'Apagado', false, '#808080'),
    ('ON', 'Encendido', true, '#00FF00'),
    ('AUTO', 'Modo automatico', true, '#0066FF'),
    ('MANUAL', 'Control manual', true, '#FFA500'),
    ('ERROR', 'Error operacional', false, '#FF0000'),
    ('MAINTENANCE', 'En mantenimiento', false, '#FFFF00'),
    ('STANDBY', 'En espera', false, '#ADD8E6'),
    ('CALIBRATING', 'Calibrando', false, '#9932CC'),
    ('OFFLINE', 'Sin conexion', false, '#000000'),
    ('UNKNOWN', 'Estado desconocido', false, '#C0C0C0');
Date: 2025-12-30Purpose: Catalog of sensor and actuator types with validation ranges.
CREATE TABLE metadata.device_types (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    description TEXT,
    default_unit_id SMALLINT REFERENCES units(id),
    data_type VARCHAR(20),
    min_expected_value NUMERIC(10,2),
    max_expected_value NUMERIC(10,2),
    control_type VARCHAR(20),  -- BINARY, CONTINUOUS, NULL (sensors)
    category_id SMALLINT NOT NULL REFERENCES device_categories(id),
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

INSERT INTO metadata.device_types (name, default_unit_id, data_type, min_expected_value, max_expected_value, category_id) VALUES
    -- Sensors
    ('TEMPERATURE', 1, 'DECIMAL', -50.00, 100.00, 1),
    ('HUMIDITY', 3, 'DECIMAL', 0.00, 100.00, 1),
    ('SOIL MOISTURE', 3, 'DECIMAL', 0.00, 100.00, 1),
    ('LIGHT INTENSITY', 6, 'DECIMAL', 0.00, 200000.00, 1),
    ('CO2 LEVEL', 5, 'DECIMAL', 0.00, 5000.00, 1),
    -- ... more sensors
    
    -- Actuators
    ('VENTILATOR', 3, 'DECIMAL', NULL, NULL, 2),
    ('HEATER', 11, 'DECIMAL', NULL, NULL, 2),
    ('IRRIGATOR', 13, 'DECIMAL', NULL, NULL, 2);
    -- ... more actuators
34 Device Types Seeded:
  • 13 Sensors: TEMPERATURE, HUMIDITY, SOIL MOISTURE, LIGHT INTENSITY, CO2 LEVEL, ATMOSPHERIC PRESSURE, WIND SPEED, WIND DIRECTION, RAINFALL, SOLAR RADIATION, PH, EC, UV INDEX
  • 21 Actuators: VENTILATOR, FAN, HEATER, COOLER, IRRIGATOR, LIGHTING, CURTAIN, WINDOW, VALVE, PUMP, EXTRACTOR, MISTING, DEHUMIDIFIER, CO2 INJECTOR, etc.
Date: 2025-12-29Purpose: Greenhouse subdivisions for crop management.
CREATE TABLE metadata.sectors (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    greenhouse_id UUID NOT NULL REFERENCES greenhouses(id) ON DELETE CASCADE,
    
    sector_code VARCHAR(20) NOT NULL,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    
    area_m2 NUMERIC(10,2),
    location_data JSONB,
    
    sector_type VARCHAR(30) CHECK (
        sector_type IN ('PRODUCTION', 'NURSERY', 'STORAGE', 
                        'IRRIGATION_ZONE', 'CLIMATE_ZONE', 'OTHER')
    ),
    
    crop_type VARCHAR(100),
    crop_stage VARCHAR(30) CHECK (
        crop_stage IN ('SEEDLING', 'VEGETATIVE', 'FLOWERING', 
                       'FRUITING', 'HARVEST', 'DORMANT')
    ),
    
    target_temperature_min NUMERIC(5,2),
    target_temperature_max NUMERIC(5,2),
    target_humidity_min NUMERIC(5,2),
    target_humidity_max NUMERIC(5,2),
    
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    CONSTRAINT uq_sector_code_per_greenhouse UNIQUE (greenhouse_id, sector_code)
);

V22-V25: UUID to BIGINT Migration

Major Performance Refactoring - This migration took ~4 hours to develop and execute safely.

V22: Migrate UUID to BIGINT (780 lines)

Date: 2026-01-08 Purpose: Migrate all UUID primary keys to BIGINT for 40% performance improvement. Impact:
  • Index size reduced by 50%
  • JOIN queries 40% faster
  • Auto-increment IDs instead of UUID generation overhead
Tables Migrated:
  1. tenants (root)
  2. users
  3. greenhouses
  4. sectors
  5. devices
  6. alerts
  7. settings
  8. command_history

V23: Cleanup UUID Migration (21,369 lines)

Date: 2026-01-09 Purpose: Remove temporary mapping tables after verification.
DROP TABLE IF EXISTS metadata.uuid_mapping_tenants;
DROP TABLE IF EXISTS metadata.uuid_mapping_users;
DROP TABLE IF EXISTS metadata.uuid_mapping_greenhouses;
-- ... drop all 8 mapping tables

V24: Add TSID and Codes (19,724 lines)

Date: 2026-01-10 Purpose: Add Time-Sorted ID (TSID) support and unique codes.
ALTER TABLE metadata.devices ADD COLUMN tsid BIGINT;
ALTER TABLE metadata.devices ADD COLUMN device_code VARCHAR(50) UNIQUE;

V25: Migrate Existing IDs to TSID (15,977 lines)

Date: 2026-01-11 Purpose: Backfill TSID for existing records.
UPDATE metadata.devices
SET tsid = id,  -- Use BIGINT id as TSID
    device_code = 'DEVICE_' || LPAD(id::TEXT, 8, '0')
WHERE tsid IS NULL;

V26-V30: Settings and Catalog Refinements

Date: 2026-01-12Purpose: Link settings to actuator states instead of periods.
ALTER TABLE metadata.settings DROP COLUMN period_id;
ALTER TABLE metadata.settings ADD COLUMN actuator_state_id SMALLINT 
    REFERENCES actuator_states(id);
Date: 2026-01-15Purpose: Catalog for data type validation.
CREATE TABLE metadata.data_types (
    id SMALLSERIAL PRIMARY KEY,
    name VARCHAR(30) NOT NULL UNIQUE,
    description TEXT,
    validation_regex VARCHAR(255),
    example_value VARCHAR(100),
    display_order SMALLINT,
    is_active BOOLEAN DEFAULT TRUE
);

INSERT INTO metadata.data_types (name, validation_regex, example_value) VALUES
    ('INTEGER', '^-?\d+$', '25'),
    ('LONG', '^-?\d+$', '9223372036854775807'),
    ('DOUBLE', '^-?\d+(\.\d+)?$', '25.5'),
    ('BOOLEAN', '^(true|false|TRUE|FALSE|1|0)$', 'true'),
    ('STRING', '.*', 'Invernadero Norte'),
    ('DATE', '^\d{4}-\d{2}-\d{2}$', '2026-01-15'),
    ('TIME', '^\d{2}:\d{2}(:\d{2})?$', '14:30:00'),
    ('DATETIME', '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}', '2026-01-15T14:30:00'),
    ('JSON', '^\{.*\}$|^\[.*\]$', '{"key": "value"}');
Date: 2026-01-18Purpose: Alerts and settings now reference sectors instead of greenhouses.
ALTER TABLE metadata.alerts DROP COLUMN greenhouse_id;
ALTER TABLE metadata.alerts ADD COLUMN sector_id BIGINT 
    REFERENCES sectors(id) ON DELETE CASCADE;

ALTER TABLE metadata.settings DROP COLUMN greenhouse_id;
ALTER TABLE metadata.settings ADD COLUMN sector_id BIGINT 
    REFERENCES sectors(id) ON DELETE CASCADE;
Date: 2026-01-20Purpose: Add description fields to multiple tables.
ALTER TABLE metadata.devices ADD COLUMN description TEXT;
ALTER TABLE metadata.alerts ALTER COLUMN message DROP NOT NULL;
Date: 2026-01-22Purpose: Rename variety column to name in sectors table.
ALTER TABLE metadata.sectors RENAME COLUMN variety TO name;

V31: Seed All Initial Data (148 lines)

Date: 2026-02-23 Purpose: Populate catalog tables with production data.

Migration Best Practices

Running Migrations

Flyway automatically executes migrations on application startup. Manual execution is rarely needed.
# Migrations run automatically on startup
./gradlew bootRun

# Or in Docker
docker-compose up -d api

# Check migration status
./gradlew flywayInfo

# Validate checksums
./gradlew flywayValidate

Migration Safety

Never modify executed migrations! Changing a migration file after execution will cause checksum mismatch and prevent startup.

✅ Safe Operations

  • Add new columns (nullable)
  • Create new tables
  • Add indexes
  • Insert seed data with ON CONFLICT DO NOTHING

❌ Dangerous Operations

  • Drop columns with data
  • Change column types (may fail)
  • Drop tables (data loss)
  • Modify existing seed data

Rollback Strategy

1

Database Backup

Always backup before running migrations in production
pg_dump -U admin -d postgres > backup_$(date +%Y%m%d).sql
2

Test in Staging

Run migrations in staging environment first
3

Monitor Logs

Watch Flyway output for errors
kubectl logs -f deployment/invernaderos-api-prod | grep Flyway
4

Rollback if Needed

Restore from backup if migration fails
psql -U admin -d postgres < backup_20260223.sql

Database Architecture

Dual database strategy overview

Caching Strategy

Redis implementation details

API Reference

REST endpoints for data access

Build docs developers (and LLMs) love