Skip to content

Conversation

@davideme
Copy link
Owner

No description provided.

Add automated database migration support using Flyway to the Kotlin/Ktor
implementation, bringing it to feature parity with the Java implementation.

Changes:
- Add Flyway dependencies (flyway-core and flyway-database-postgresql)
- Create FlywayConfig for migration management on application startup
- Add three versioned migrations:
  * V1__Initial_schema.sql: Creates base lamps table
  * V2__Add_soft_deletes.sql: Adds deleted_at column
  * V3__Add_updated_at_trigger.sql: Adds PostgreSQL trigger for updated_at
- Integrate migration runner into DatabaseFactory.init()
- Add MIGRATIONS.md documentation

The migrations are applied automatically on application startup before
initializing the database connection pool. Failed migrations prevent
application startup to ensure database consistency.
Add automated database migration support using golang-migrate to the Go
implementation, bringing it to feature parity with other language implementations.

Changes:
- Add golang-migrate dependencies (migrate/v4 and postgres driver)
- Create RunMigrations function in api/migrate.go with embedded migrations
- Add six migration files (3 up, 3 down):
  * 000001_initial_schema: Creates base lamps table
  * 000002_add_soft_deletes: Adds deleted_at column
  * 000003_add_updated_at_trigger: Adds PostgreSQL trigger for updated_at
- Integrate migration runner into main.go startup sequence
- Add MIGRATIONS.md documentation

Migrations are embedded into the binary using Go's embed package, allowing
single-binary deployment. They are applied automatically on application
startup before initializing the database connection pool. Failed migrations
prevent connection pool creation to ensure database consistency.

The implementation supports both forward (up) and backward (down) migrations,
with migration history tracked in the schema_migrations table.
Copilot AI review requested due to automatic review settings January 17, 2026 12:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds automated database migration support to both Go and Kotlin implementations of the lamp control API. The Go implementation uses golang-migrate v4.19.1 with embedded migration files, while the Kotlin implementation uses Flyway 10.21.0. Both implementations include three versioned migrations: initial schema, soft deletes, and automated updated_at trigger.

Changes:

  • Added golang-migrate dependency and migration infrastructure to Go implementation with embedded SQL files
  • Added Flyway dependency and migration configuration to Kotlin implementation
  • Created identical SQL migration files (V1-V3) for both implementations covering initial schema, soft deletes, and updated_at triggers
  • Added comprehensive MIGRATIONS.md documentation for both implementations explaining usage, troubleshooting, and rollback procedures

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/go/go.mod Added golang-migrate v4.19.1 dependency and updated transitive dependencies
src/go/go.sum Updated checksums for new and updated dependencies
src/go/api/migrate.go Implemented RunMigrations function using golang-migrate with embedded filesystem
src/go/cmd/lamp-control-api/main.go Integrated migration execution before connection pool creation with fallback logic
src/go/api/migrations/*.sql Created six migration files (up and down) for three schema versions
src/go/MIGRATIONS.md Added comprehensive documentation for golang-migrate usage and troubleshooting
src/kotlin/build.gradle.kts Added Flyway 10.21.0 dependencies for core and PostgreSQL
src/kotlin/src/main/kotlin/com/lampcontrol/database/FlywayConfig.kt Implemented Flyway configuration object with runMigrations function
src/kotlin/src/main/kotlin/com/lampcontrol/database/DatabaseFactory.kt Integrated Flyway migration execution before database initialization
src/kotlin/src/main/resources/db/migration/*.sql Created three Flyway migration files following V{version}__{description}.sql naming
src/kotlin/MIGRATIONS.md Added comprehensive documentation for Flyway usage and comparison with other implementations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- **Python**: Uses Alembic (Python-specific migration tool)
- **TypeScript**: Uses Prisma Migrate (schema-first approach)
- **C#**: Uses Entity Framework Migrations (code-first approach)
- **Go**: Manual migrations (shared schema file)
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentation is now outdated. Since this PR adds golang-migrate support to the Go implementation, the reference should be updated to reflect that Go now also uses automated migrations with golang-migrate, similar to the other implementations. The current text "Manual migrations (shared schema file)" no longer accurately describes the Go implementation.

Suggested change
- **Go**: Manual migrations (shared schema file)
- **Go**: Uses golang-migrate for automated SQL-based migrations

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,57 @@
package com.lampcontrol.database

import com.zaxxer.hikari.HikariDataSource
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HikariDataSource import is unused in this file. Flyway is configured directly with the connection string, user, and password parameters, and doesn't require a HikariDataSource instance. This import should be removed.

Suggested change
import com.zaxxer.hikari.HikariDataSource

Copilot uses AI. Check for mistakes.
Comment on lines 69 to 77
// Run database migrations before creating connection pool
connectionString := dbConfig.ConnectionString()
// golang-migrate requires postgres:// prefix instead of postgresql://
// and needs sslmode parameter
if len(connectionString) >= 10 && connectionString[:10] == "host=" {
// If using component-based connection string, convert to URL format for migrate
connectionString = fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connection string conversion logic has a potential issue. When DATABASE_URL is set, it's returned as-is by ConnectionString(), which might use the "postgresql://" prefix. However, golang-migrate requires "postgres://" prefix. The current logic only handles conversion from component-based format ("host=...") but doesn't handle the case where DATABASE_URL uses "postgresql://" instead of "postgres://". Consider also checking for and converting "postgresql://" prefix to "postgres://" to ensure compatibility with golang-migrate.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +58
func RunMigrations(connectionString string) error {
log.Println("Starting database migrations...")

// Create source driver from embedded filesystem
sourceDriver, err := iofs.New(migrationsFS, "migrations")
if err != nil {
return fmt.Errorf("failed to create migration source driver: %w", err)
}

// Create migrate instance
m, err := migrate.NewWithSourceInstance("iofs", sourceDriver, connectionString)
if err != nil {
return fmt.Errorf("failed to create migrate instance: %w", err)
}
defer func() {
if srcErr, dbErr := m.Close(); srcErr != nil || dbErr != nil {
log.Printf("Warning: error closing migrate instance: source=%v, database=%v", srcErr, dbErr)
}
}()

// Run migrations
if err := m.Up(); err != nil {
if err == migrate.ErrNoChange {
log.Println("Database schema is up to date (no migrations to apply)")
return nil
}
return fmt.Errorf("failed to run migrations: %w", err)
}

// Get current version
version, dirty, err := m.Version()
if err != nil && err != migrate.ErrNilVersion {
log.Printf("Warning: could not determine migration version: %v", err)
} else if dirty {
log.Printf("Warning: database is in dirty state at version %d", version)
} else {
log.Printf("Successfully applied migrations. Current schema version: %d", version)
}

return nil
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new migration functionality in migrate.go lacks test coverage. While the Go codebase maintains comprehensive test coverage (80%+ minimum per coding guidelines), the RunMigrations function has no associated tests. Consider adding tests that verify: (1) handling of ErrNoChange when schema is up to date, (2) proper error handling for invalid connection strings, (3) proper resource cleanup via defer. Note that these could be unit tests using test fixtures or mocked migration instances, not necessarily requiring a live database.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 20 to 56
fun runMigrations(config: DatabaseConfig): Boolean {
return try {
logger.info("Starting database migrations with Flyway")

val flyway = Flyway.configure()
.dataSource(
config.connectionString(),
config.user,
config.password
)
.locations("classpath:db/migration")
.baselineOnMigrate(true)
.baselineVersion("0")
.validateOnMigrate(true)
.load()

val migrationsExecuted = flyway.migrate()

if (migrationsExecuted.migrationsExecuted > 0) {
logger.info(
"Successfully executed {} migration(s). Current schema version: {}",
migrationsExecuted.migrationsExecuted,
migrationsExecuted.targetSchemaVersion
)
} else {
logger.info(
"Database schema is up to date at version: {}",
flyway.info().current()?.version ?: "baseline"
)
}

true
} catch (e: Exception) {
logger.error("Failed to run database migrations", e)
false
}
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FlywayConfig.runMigrations function lacks test coverage. While the Kotlin codebase has comprehensive tests for database configuration, the migration functionality itself is not tested. Consider adding tests to verify: (1) successful migration execution, (2) proper error handling and logging when migrations fail, (3) behavior when schema is already up to date. These could be unit tests using an embedded H2 database or mocked Flyway instances.

Copilot generated this review using guidance from repository custom instructions.
davideme and others added 2 commits January 17, 2026 13:19
Add support for three distinct operation modes across all 6 language
implementations: serve (default, run migrations then start server),
migrate (run migrations only), and serve-only (start server without migrations).

This enables better control over database migrations in production environments,
supporting patterns like init containers, separate migration jobs, and
zero-downtime deployments.

Changes by implementation:

**Go:**
- Add --mode flag with serve/migrate/serve-only options
- Refactor main.go with runMigrationsOnly() and initializeRepository()
- Migrations run conditionally based on mode

**Kotlin:**
- Add --mode argument parsing in Application.kt
- Add runMigrationsOnly() for migration-only mode
- Use System property to skip migrations in serve-only mode
- Update DatabaseFactory to check skip.migrations property

**Java (Spring Boot):**
- Create ApplicationMode class for mode handling
- Add mode parsing and configuration in main method
- Control spring.flyway.enabled based on mode
- Separate runMigrationsOnly() method for migrate mode

**Python (FastAPI):**
- Create new cli.py module for CLI handling
- Add argparse-based mode selection
- Implement run_migrations_only() using Alembic
- Conditionally run migrations in start_server()

**TypeScript (Fastify):**
- Create new cli.ts entry point
- Add --mode argument parsing
- Use execSync to run Prisma migrations
- Separate functions for migrate-only and server modes

**C# (.NET):**
- Update Program.cs with mode argument parsing
- Add RunMigrationsOnly() helper method
- Run EF Core migrations conditionally based on mode
- Use Database.Migrate() for migration execution

**Documentation:**
- Add comprehensive OPERATION_MODES.md documentation
- Include usage examples for all 6 implementations
- Document production deployment patterns (init containers, jobs, CI/CD)
- Add troubleshooting guide and environment variables reference

This brings all implementations to feature parity for production deployment
scenarios where migrations need to be managed separately from application startup.
"""

import argparse
import sys
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[black and ruff] reported by reviewdog 🐶

Suggested change
import sys

import argparse
import sys
import logging
from pathlib import Path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[black and ruff] reported by reviewdog 🐶

Suggested change
from pathlib import Path
import sys
from pathlib import Path

from pathlib import Path

import uvicorn
from alembic import command
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[black and ruff] reported by reviewdog 🐶

Suggested change
from alembic import command
from alembic import command

import uvicorn
from alembic import command
from alembic.config import Config

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[black and ruff] reported by reviewdog 🐶

Suggested change

@davideme davideme closed this Jan 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants