Skip to content

Add support for SQL migration files (Hybrid TypeScript/SQL handler) #58

@vlavrynovych

Description

@vlavrynovych

Overview

Add support for SQL migration files alongside existing TypeScript migrations, similar to Flyway. This enables users to choose the best tool for each migration:

  • SQL files for simple DDL operations (CREATE TABLE, ALTER TABLE, etc.)
  • TypeScript for complex data migrations and business logic

Proposed Solution

Implement a hybrid handler that detects file type and executes appropriately:

// Example migration structure
migrations/
├── V202501230001_create_tables.sql      # SQL for DDL
├── V202501230002_seed_data.ts           # TypeScript for complex logic
├── V202501230003_alter_columns.sql      # SQL for schema changes
└── V202501230004_migrate_data.ts        # TypeScript for data transformations

Features

Basic SQL Support

  • Execute .sql files directly
  • Parse and execute multiple statements (split by semicolons)
  • Support standard SQL DDL/DML operations

Up/Down Migrations (Optional)

  • Support -- @UP and -- @DOWN sections in SQL files
  • Store down migrations for rollback capability
  • Parse SQL file sections

Example:

-- V202501230001_create_users.sql

-- @UP
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL
);

-- @DOWN
DROP TABLE users;

Hybrid Handler

  • Automatically detect file type (.sql vs .ts)
  • Execute SQL files directly
  • Load and execute TypeScript migrations
  • Consistent error handling for both types

Implementation

export class HybridMigrationHandler implements IMigrationScriptHandler {
  async handle(script: MigrationScript): Promise<string> {
    if (script.filepath.endsWith('.sql')) {
      return this.handleSQL(script);
    } else if (script.filepath.endsWith('.ts')) {
      return this.handleTypeScript(script);
    }
    throw new Error(`Unsupported file type: ${script.filepath}`);
  }

  private async handleSQL(script: MigrationScript): Promise<string> {
    const sql = fs.readFileSync(script.filepath, 'utf8');
    
    // Parse and execute SQL statements
    const statements = this.parseSQL(sql);
    for (const statement of statements) {
      await this.db.query(statement);
    }
    
    return `Executed ${statements.length} SQL statements`;
  }

  private async handleTypeScript(script: MigrationScript): Promise<string> {
    await script.init(); // Load TS migration
    return await script.script.up(this.db);
  }
}

Configuration

Update Config to support SQL files:

export class Config {
  filePattern: RegExp = /^V(\d+)_(.+)\.(ts|sql)$/; // Support both .ts and .sql
  sqlMigrationSeparator?: string = '_';
  supportDownMigrations?: boolean = false;
}

Benefits

  • Flyway familiarity: Developers coming from Flyway can use same patterns
  • Flexibility: Choose the right tool (SQL vs TypeScript) for each migration
  • Simplicity: Simple schema changes don't need TypeScript boilerplate
  • Power: Complex data migrations still use TypeScript's full capabilities
  • Database-agnostic: Works with any database (PostgreSQL, MySQL, MongoDB via SQL-like DSL)

Acceptance Criteria

  • Handler detects and executes .sql files
  • Handler supports existing .ts migrations
  • SQL statements are parsed correctly (handle semicolons, comments)
  • Tests for SQL migration execution
  • Tests for hybrid (mixed SQL/TS) migrations
  • Documentation with examples
  • Support for UP/DOWN migrations (optional enhancement)
  • Error handling for invalid SQL
  • 100% test coverage maintained

Related Issues

  • Make backup/restore optional to support down() migrations #50 - Make backup/restore optional to support down() migrations (UP/DOWN migrations in SQL files complement down() methods in TypeScript)
  • Complements database-specific implementations (PostgreSQL, MySQL, etc.)
  • Works with existing backup/restore functionality
  • Compatible with planned CLI commands

Notes

  • This should be a separate optional handler, not modify existing TypeScript handler
  • Users can create their own SQL handlers if needed
  • Consider creating example implementations in documentation
  • SQL file UP/DOWN sections work well with optional backup/restore (Make backup/restore optional to support down() migrations #50)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions