Claude Code for Natural/ADABAS (2026)

Why Claude Code for Natural/ADABAS Migration

Software AG’s Natural programming language and ADABAS database power government agencies, insurance companies, and European enterprises. Natural’s 4GL syntax with FIND, READ, and HISTOGRAM statements against ADABAS’s inverted-list architecture creates an ecosystem that has no modern equivalent. ADABAS files with periodic groups, multiple-value fields, and super/sub/phonetic descriptors represent a data model that does not map cleanly to SQL. The thousands of Natural maps (screen layouts) and Natural Construct-generated programs compound the migration complexity.

Claude Code understands Natural’s statement-based syntax, ADABAS data definition modules (DDMs), and the hierarchical record structure unique to ADABAS. It can generate equivalent SQL schemas that preserve the multi-value and periodic group semantics, and convert Natural programs to Java or Python service layers.

The Workflow

Step 1: Export and Catalog Natural Objects

# Export Natural source using SYSOBJH or Natural Engineer
mkdir -p ~/natural-migration/{source,output,ddms,maps}
# Catalog by object type
echo "Programs: $(find ~/natural-migration/source -name '*.NSP' | wc -l)"
echo "Subprograms: $(find ~/natural-migration/source -name '*.NSN' | wc -l)"
echo "Maps: $(find ~/natural-migration/source -name '*.NSM' | wc -l)"
echo "DDMs: $(find ~/natural-migration/ddms -name '*.NSD' | wc -l)"
echo "Local DAs: $(find ~/natural-migration/source -name '*.NSL' | wc -l)"
echo "Global DAs: $(find ~/natural-migration/source -name '*.NSG' | wc -l)"
echo "Copycode: $(find ~/natural-migration/source -name '*.NSC' | wc -l)"

Step 2: Convert ADABAS DDMs to SQL Schemas

ADABAS DDM with periodic groups and MU fields:

DB: 055  FILE: 012  - EMPLOYEES                 DDM: EMPLOYEES
TYPE  LEVEL  NAME           FORMAT  SUPPRESSION  DESCRIPTOR
--    --     ----           ------  -----------  ----------
      1      EMPLOYEE-REC
      2      PERSONNEL-ID    A8                   DE,UQ
      2      FIRST-NAME      A20                  DE
      2      LAST-NAME       A30                  DE
      2      DEPARTMENT      A6                   DE
      2      SALARY          P9.2
      2      HIRE-DATE       D
      2      SKILLS          A30/1:10             MU    /* Multiple-value field */
      2      ASSIGNMENTS                          PE    /* Periodic group */
        3    PROJECT-CODE    A10                  DE
        3    START-DATE      D
        3    END-DATE        D
        3    ROLE            A20
        3    HOURS-WORKED    P7.2

Claude Code generates normalized PostgreSQL:

-- Main entity (ADABAS file level 1-2 fields)
CREATE TABLE employees (
    id              SERIAL PRIMARY KEY,
    personnel_id    VARCHAR(8) NOT NULL UNIQUE,
    first_name      VARCHAR(20) NOT NULL,
    last_name       VARCHAR(30) NOT NULL,
    department      VARCHAR(6),
    salary          NUMERIC(9,2),
    hire_date       DATE
);
-- Multiple-value field (MU) -> separate table
-- ADABAS: SKILLS A30/1:10 (up to 10 occurrences)
CREATE TABLE employee_skills (
    id              SERIAL PRIMARY KEY,
    employee_id     INTEGER NOT NULL REFERENCES employees(id),
    occurrence      SMALLINT NOT NULL,  -- preserves MU index (1-10)
    skill           VARCHAR(30) NOT NULL,
    UNIQUE (employee_id, occurrence)
);
-- Periodic group (PE) -> separate table with occurrence tracking
-- ADABAS: ASSIGNMENTS PE (unbounded occurrences)
CREATE TABLE employee_assignments (
    id              SERIAL PRIMARY KEY,
    employee_id     INTEGER NOT NULL REFERENCES employees(id),
    occurrence      SMALLINT NOT NULL,  -- preserves PE ISN sequence
    project_code    VARCHAR(10) NOT NULL,
    start_date      DATE,
    end_date        DATE,
    role            VARCHAR(20),
    hours_worked    NUMERIC(7,2),
    UNIQUE (employee_id, occurrence)
);
CREATE INDEX idx_emp_dept ON employees(department);
CREATE INDEX idx_emp_name ON employees(last_name, first_name);
CREATE INDEX idx_assign_project ON employee_assignments(project_code);

Step 3: Convert Natural Programs to Python

Original Natural program:

** EMPSRCH - Employee search by department with skill filter
DEFINE DATA
LOCAL USING EMPLOYEES    /* DDM reference */
LOCAL
  1 #DEPT      (A6)
  1 #SKILL     (A30)
  1 #COUNT     (P5)
  1 #TOTAL-SAL (P11.2)
END-DEFINE
INPUT USING MAP 'SRCHMAP'    /* Screen map input */
RESET #COUNT #TOTAL-SAL
FIND EMPLOYEES WITH DEPARTMENT = #DEPT
                 SORTED BY LAST-NAME
  IF #SKILL NE ' '
    FIND(1) EMPLOYEES WITH PERSONNEL-ID = PERSONNEL-ID
                       AND SKILLS = #SKILL
      IF *NUMBER = 0 ESCAPE TOP
    END-FIND
  END-IF
  ADD 1 TO #COUNT
  ADD SALARY TO #TOTAL-SAL
  DISPLAY PERSONNEL-ID FIRST-NAME LAST-NAME SALARY
END-FIND
IF #COUNT GT 0
  WRITE / 'Total employees:' #COUNT
  WRITE   'Average salary: ' #TOTAL-SAL / #COUNT
ELSE
  WRITE 'No employees found'
END-IF
END

Claude Code converts:

# services/employee_search.py
from dataclasses import dataclass
from decimal import Decimal
from sqlalchemy import select, func, and_
from sqlalchemy.ext.asyncio import AsyncSession
from models import Employee, EmployeeSkill
@dataclass
class SearchResult:
    employees: list
    count: int
    avg_salary: Decimal | None
async def search_employees(
    session: AsyncSession,
    department: str,
    skill: str | None = None
) -> SearchResult:
    """Employee search by department with optional skill filter.
    Converted from Natural program EMPSRCH."""
    # Base query: FIND EMPLOYEES WITH DEPARTMENT = #DEPT
    query = select(Employee).where(
        Employee.department == department
    ).order_by(Employee.last_name)  # SORTED BY LAST-NAME

    # Skill filter: nested FIND with SKILLS = #SKILL
    if skill and skill.strip():
        skill_subq = (
            select(EmployeeSkill.employee_id)
            .where(EmployeeSkill.skill == skill)
        )
        query = query.where(Employee.id.in_(skill_subq))
    result = await session.execute(query)
    employees = result.scalars().all()
    count = len(employees)
    total_salary = sum(e.salary for e in employees if e.salary)
    avg_salary = (total_salary / count) if count > 0 else None
    return SearchResult(
        employees=[{
            'personnel_id': e.personnel_id,
            'first_name': e.first_name,
            'last_name': e.last_name,
            'salary': e.salary
        } for e in employees],
        count=count,
        avg_salary=avg_salary
    )

Step 4: Verify

# Run migration parity tests
cd ~/natural-migration/output
python3 -m pytest tests/test_ddm_migration.py -v
python3 -m pytest tests/test_program_parity.py -v
# Verify record counts match
python3 scripts/verify_counts.py --adabas-report counts.txt --pg-db migrated_db
# Check periodic group occurrence preservation
psql -d migrated_db -c "
  SELECT e.personnel_id, COUNT(a.id) as assignment_count
  FROM employees e
  JOIN employee_assignments a ON e.id = a.employee_id
  GROUP BY e.personnel_id
  ORDER BY assignment_count DESC
  LIMIT 10;"

CLAUDE.md for Natural/ADABAS Migration

# Natural/ADABAS to Python/PostgreSQL Migration Standards
## Domain Rules
- ADABAS files map to PostgreSQL tables
- Multiple-value fields (MU) map to one-to-many child tables with occurrence column
- Periodic groups (PE) map to one-to-many child tables with occurrence column
- FIND statement maps to SELECT with WHERE
- READ PHYSICAL maps to sequential scan (avoid in production)
- HISTOGRAM maps to SELECT DISTINCT or GROUP BY
- Natural maps (NSM) map to API endpoints or web forms
- Descriptors (DE) map to PostgreSQL indexes
- Super-descriptors map to composite indexes
- Sub-descriptors map to partial indexes or expression indexes
## File Patterns
- Source: *.NSP (programs), *.NSN (subprograms), *.NSM (maps)
- Source: *.NSD (DDMs), *.NSL (local DA), *.NSG (global DA)
- Target: Python (FastAPI + SQLAlchemy + PostgreSQL)
- Natural programs: src/services/
- DDMs: src/models/ (SQLAlchemy models)
- Maps: src/routes/ (API endpoints)
## Common Commands
- python3 -m alembic revision --autogenerate -m "from_ddm"
- python3 -m alembic upgrade head
- python3 -m pytest tests/ -v
- uvicorn main:app --reload

Common Pitfalls in Natural/ADABAS Migration

  • Periodic group ordering: ADABAS periodic groups maintain insertion order by ISN. Claude Code preserves this with an explicit occurrence column and enforces ordering in all queries to match the original ADABAS behavior.

  • ADABAS FIND with COUPLED files: Natural FIND with coupled files creates implicit joins across ADABAS files. Claude Code translates these into explicit SQL JOINs with proper foreign key relationships.

  • Natural ESCAPE logic: Natural’s ESCAPE TOP, ESCAPE BOTTOM, and ESCAPE ROUTINE have different scope behaviors than break/continue/return. Claude Code maps each escape level to the correct Python control flow construct.

Build yours → Create a custom CLAUDE.md with our Generator Tool.

Estimate tokens → Calculate your usage with our Token Estimator.

Try it: Estimate your monthly spend with our Cost Calculator.

Frequently Asked Questions

Do I need a paid Anthropic plan to use this?

Claude Code works with any Anthropic API plan, including the free tier. However, the free tier has lower rate limits (requests per minute and tokens per minute) that may slow down multi-step workflows. For professional use, the Build or Scale plan provides higher limits and priority access during peak hours.

How does this affect token usage and cost?

The token cost depends on the size of your prompts and Claude’s responses. Typical development tasks consume 10K-50K tokens per interaction. Using a CLAUDE.md file and skills reduces exploration tokens by 50-80%, which directly lowers costs. Monitor your usage at console.anthropic.com/settings/billing.

Can I customize this for my specific project?

Yes. All Claude Code behavior can be customized through CLAUDE.md (project rules), .claude/settings.json (permissions), and .claude/skills/ (domain knowledge). The most impactful customization is adding your project’s specific patterns, conventions, and common commands to CLAUDE.md so Claude Code follows your standards from the start.

What happens when Claude Code makes a mistake?

Claude Code creates files and edits through standard filesystem operations, so all changes are visible in git diff. If a change is wrong, revert it with git checkout -- <file> for a single file or git stash for all changes. Claude Code does not make irreversible changes unless you explicitly allow destructive commands in settings.json.