ADR-005: iDempiere Migration Script Architecture

Status

Accepted

Context

Understanding iDempiere's migration script mechanism is essential for:

  1. Proper CLI tool design that complements (not conflicts with) iDempiere workflows
  2. Correct DDL generation that follows iDempiere conventions
  3. Clear documentation of when to use CLI vs iDempiere native tools

This ADR documents how iDempiere generates and applies migration scripts, serving as architectural reference for the CLI.

iDempiere Migration Script Architecture

Overview

iDempiere uses a two-phase migration system:

┌─────────────────────────────────────────────────────────────────┐
│                    PHASE 1: GENERATION                          │
│  Developer makes AD changes → SQL intercepted → Script file     │
└─────────────────────────────────────────────────────────────────┘
                                ↓
┌─────────────────────────────────────────────────────────────────┐
│                    PHASE 2: APPLICATION                         │
│  RSync2DB.sh → Find pending scripts → Execute → Track in DB     │
└─────────────────────────────────────────────────────────────────┘

Phase 1: Migration Script Generation

1.1 Enabling Generation

Migration script logging is controlled by a session preference (not persisted):

Preferences Window → "Log Migration Script" checkbox
                   → "Migration Script Comment" (optional)

Key Point: This preference resets each session - developers must re-enable it when starting new development work.

1.2 SQL Interception Mechanism

When "Log Migration Script" is enabled, iDempiere intercepts SQL statements at the database layer:

┌──────────────────┐     ┌─────────────────────┐     ┌────────────────┐
│  AD Change       │ ──→ │  SQL Generation     │ ──→ │  DB Execution  │
│  (UI/Process)    │     │  (MColumn/MTable)   │     │                │
└──────────────────┘     └─────────────────────┘     └────────────────┘
                                   │
                                   ↓ (if logging enabled)
                         ┌─────────────────────┐
                         │  Migration Script   │
                         │  File Writer        │
                         └─────────────────────┘
                                   │
                                   ↓
                         ┌─────────────────────┐
                         │  migration/iXX.Xz/  │
                         │  postgresql/        │
                         │  YYYYMMDD_JIRA.sql  │
                         └─────────────────────┘

1.3 SQL Generation Classes

The actual SQL is generated by model classes:

Class Method Purpose
MColumn getSQLAdd() ALTER TABLE ADD COLUMN
MColumn getSQLModify() ALTER TABLE MODIFY COLUMN
MColumn getSQLDDL() Column definition (type + constraints)
MColumn getForeignKeyConstraintSql() FK constraint DDL
MTable getSQLCreate() CREATE TABLE with all columns

Source Files:

1.4 Script File Format

Generated migration scripts follow this structure:

-- Migration script registration (called by application)
SELECT register_migration_script('202312151430_IDEMPIERE-6000.sql') FROM dual;

-- Timestamp: Dec 15, 2023 2:30:45 PM CET
-- IDEMPIERE-6000 Add custom table for inventory tracking

INSERT INTO AD_Table (AD_Table_ID, AD_Client_ID, AD_Org_ID, ...)
VALUES (200350, 0, 0, ...);

-- Timestamp: Dec 15, 2023 2:30:46 PM CET
ALTER TABLE XX_InventoryTrack ADD COLUMN XX_Quantity NUMERIC(10,4);

-- ... more statements with timestamps

Key Elements:

  1. register_migration_script() header - registers script in tracking table
  2. Timestamp comments before each statement
  3. JIRA ticket reference in filename and comments
  4. Centralized IDs (not MAX+1)

1.5 File Naming Convention

YYYYMMDDHHMM_IDEMPIERE-XXXX_description.sql
│           │              │
│           │              └── Optional description
│           └── JIRA ticket number (ID allocation)
└── Timestamp (ensures ordering)

Examples:

202312151430_IDEMPIERE-6000.sql
202312151445_IDEMPIERE-6000_add_columns.sql
202401081200_IDEMPIERE-6100_fix_constraint.sql

1.6 Excluded Operations

Certain operations are NOT logged to migration scripts:

1.7 Centralized ID Management

For core iDempiere contributions, IDs must be allocated via JIRA:

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Create JIRA    │ ──→ │  Request ID     │ ──→ │  Use Allocated  │
│  Ticket         │     │  Range          │     │  IDs in Script  │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Why Centralized IDs?

Reference: Centralized ID Management


Phase 2: Migration Script Application

2.1 Discovery Process

Scripts are discovered by scanning the migration folder structure:

$IDEMPIERE_HOME/migration/
├── i10.0z/
│   ├── postgresql/
│   │   ├── 202301011200_IDEMPIERE-5500.sql
│   │   └── 202301021300_IDEMPIERE-5501.sql
│   └── oracle/
│       └── ...
├── i11.0z/
│   └── postgresql/
│       └── ...
└── processes_post_migration/
    └── ...

2.1.1 Local Migration Folder (Partners/Plugins)

iDempiere supports a local migration folder for partner customizations:

$IDEMPIERE_HOME/migration-local/
├── i1.0z/                      # Partner plugin version
│   ├── postgresql/
│   │   ├── 202412011200_PLUGIN-001_AddCustomTable.sql
│   │   └── 202412021300_PLUGIN-002_AddProcess.sql
│   └── oracle/
│       └── ...
└── processes_post_migration/
    └── ...

Key Differences:

Folder Purpose ID Management
migration/ iDempiere core scripts Centralized IDs (JIRA)
migration-local/ Partner/plugin scripts Local IDs (MAX+1)

CLI Default: The CLI generates scripts to migration/ by default, but partners should use migration-local/ to avoid conflicts with core scripts:

# Generate to local migration folder
idempiere-cli migration-script generate table XX_MyTable -i PLUGIN-001 -o migration-local

2.2 Application Methods

Method 1: RUN_SyncDBDev.sh (Development)

cd $IDEMPIERE_REPOSITORY
bash RUN_SyncDBDev.sh

Method 2: RSync2DB.sh (Production)

cd $IDEMPIERE_HOME/utils
./RSync2DB.sh

Method 3: Database Migration Window (UI)

2.3 Execution Order

Scripts are applied alphabetically by filename, which is why timestamp-based naming is critical:

1. 202301011200_IDEMPIERE-5500.sql  (first - oldest timestamp)
2. 202301021300_IDEMPIERE-5501.sql  (second)
3. 202301031400_IDEMPIERE-5502.sql  (third - newest timestamp)

2.4 Tracking Table: ad_migrationscript

Applied scripts are tracked in the ad_migrationscript table:

CREATE TABLE ad_migrationscript (
    ad_migrationscript_id  NUMERIC(10,0) NOT NULL,
    ad_client_id           NUMERIC(10,0) NOT NULL,
    ad_org_id              NUMERIC(10,0) NOT NULL,
    isactive               CHAR(1) DEFAULT 'Y',
    created                TIMESTAMP NOT NULL,
    createdby              NUMERIC(10,0) NOT NULL,
    updated                TIMESTAMP NOT NULL,
    updatedby              NUMERIC(10,0) NOT NULL,
    name                   VARCHAR(60) NOT NULL,      -- Script filename
    filename               VARCHAR(500),               -- Full path
    developername          VARCHAR(60),                -- Author
    projectname            VARCHAR(60),                -- Project/JIRA
    description            VARCHAR(2000),
    reference              VARCHAR(2000),              -- JIRA URL
    url                    VARCHAR(2000),
    releaseno              VARCHAR(10),                -- iDempiere version
    status                 VARCHAR(2),                 -- IP=In Progress, CO=Complete, ER=Error
    script                 BYTEA,                      -- Script content
    applyscript            CHAR(1) DEFAULT 'N',
    ad_migrationscript_uu  VARCHAR(36),
    CONSTRAINT ad_migrationscript_pkey PRIMARY KEY (ad_migrationscript_id)
);

Status Values:

2.5 Checking Applied Scripts

-- List all applied migration scripts
SELECT name, status, created
FROM ad_migrationscript
ORDER BY name;

-- Find pending scripts (compare with filesystem)
SELECT name FROM ad_migrationscript WHERE status = 'CO' ORDER BY 1;

2.6 Error Handling

If a script fails:

  1. Status set to ER in ad_migrationscript
  2. Error logged to /tmp/SyncDB_out_[##]/ folder
  3. Developer must:
    • Review error log
    • Fix the script or database state
    • Re-run RSync2DB.sh

CLI Design: Two Separate Flows

Flow 1: 2Pack (PackOut/PackIn)

For exporting/importing Application Dictionary elements as XML:

┌─────────────────────────────────────────────────────────────────┐
│  FLOW 1: 2Pack                                                  │
│                                                                 │
│  CLI generate → PackOut.xml → validate → PackIn → AD applied   │
│                                                                 │
│  Commands:                                                      │
│    - idempiere-cli 2pack generate                              │
│    - idempiere-cli 2pack validate                              │
│    - idempiere-cli 2pack packin                                │
└─────────────────────────────────────────────────────────────────┘

Uses: REST API or direct SQL for reading, XML output

Flow 2: Migration Scripts

For generating SQL migration scripts for plugin tables:

┌─────────────────────────────────────────────────────────────────┐
│  FLOW 2: Migration Scripts (SEPARATE from 2Pack)               │
│                                                                 │
│  CLI reads DB → generates SQL → register_migration_script()    │
│              → apply to DB → post-migration                    │
│                                                                 │
│  Commands:                                                      │
│    - idempiere-cli migration-script init                        │
│    - idempiere-cli migration-script generate table XX_MyTable   │
│    - idempiere-cli migration-script apply                       │
│    - idempiere-cli migration-script apply-post                  │
└─────────────────────────────────────────────────────────────────┘

Uses: Direct SQL connection to PostgreSQL (NOT REST API)

CLI Migration Script Service Architecture

The MigrationScriptService uses direct SQL connection:

┌─────────────────────────────────────────────────────────────────┐
│  MigrationScriptService                                         │
│                                                                 │
│  DataSource (JDBC) → PostgreSQL:5433                           │
│       │                                                         │
│       ├── getTableByNameFromDB()  → SELECT FROM ad_table       │
│       ├── getColumnsFromDB()      → SELECT FROM ad_column      │
│       └── getColumnByNameFromDB() → SELECT FROM ad_column      │
│                                                                 │
│  Output: migration/iX.Xz/postgresql/YYYYMMDDHHMM_ISSUE.sql     │
└─────────────────────────────────────────────────────────────────┘

Configuration:

# iDempiere Database connection (IDEMPIERE_DB_* env vars)
export IDEMPIERE_DB_HOST=localhost
export IDEMPIERE_DB_PORT=5433
export IDEMPIERE_DB_NAME=idempiere
export IDEMPIERE_DB_USER=adempiere
export IDEMPIERE_DB_PASSWORD=adempiere

Key Differences: 2Pack vs Migration Scripts

Aspect 2Pack Migration Scripts
Format XML (PackOut.xml) SQL files
Connection REST API or SQL Direct SQL only
Registration PackIn process register_migration_script()
Use case AD element export/import DDL + DML for DB changes
iDempiere core Uses PIPO2 handlers Uses register_migration_script()

What CLI Should NOT Do

  1. Generate migration scripts for core iDempiere - These require:

    • Centralized ID allocation via JIRA
    • Review by core team
    • Use iDempiere's built-in "Log Migration Script" preference
  2. Mix 2Pack and Migration flows - They serve different purposes:

    • 2Pack = AD elements as portable XML
    • Migration = SQL scripts for database changes

When to Use Each Flow

Use Case Recommended Flow
Export table + window + process 2Pack
Add new plugin table to DB Migration Scripts
Share plugin between instances 2Pack
Version control DB changes Migration Scripts
Core iDempiere contribution iDempiere native (not CLI)

References

Official Documentation

Source Code

Decision

  1. Document iDempiere's migration script architecture for reference
  2. Implement migration script generation in CLI for plugin development
  3. Support all AD element types: table, column, window, process, reference, menu, message, sysconfig
  4. Use direct SQL connection (DataSource) for reading AD_* tables
  5. Reserve centralized ID allocation for core iDempiere contributions only (via JIRA)

CLI vs Core Contributions

Use Case Approach
Plugin development CLI generates migration scripts (local IDs, MAX+1)
Core iDempiere Use iDempiere native "Log Migration Script" + Centralized ID via JIRA

Supported Migration Types

Type Tables Command Example
table AD_Table, AD_Column + DDL migration-script generate table XX_MyTable -i PLUGIN-001
column AD_Column + ALTER TABLE migration-script generate column XX_MyTable.MyColumn -i PLUGIN-002
window AD_Window, AD_Tab, AD_Field migration-script generate window "My Window" -i PLUGIN-003
process AD_Process, AD_Process_Para migration-script generate process "My Process" -i PLUGIN-004
reference AD_Reference, AD_Ref_List migration-script generate reference "My List" -i PLUGIN-005
menu AD_Menu, AD_TreeNodeMM migration-script generate menu "My Menu Item" -i PLUGIN-006
message AD_Message migration-script generate message "MyMessageKey" -i PLUGIN-007
sysconfig AD_SysConfig migration-script generate sysconfig "MySysConfigKey" -i PLUGIN-008

Consequences

Positive

Negative

Neutral

Path: /docs/developers/architecture/idempiere-hub/005-idempiere-migration-script-architecture