Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/cigogne.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import cigogne/migration
import gleam/bool
import gleam/io
import gleam/list
import gleam/option
import gleam/result
import gleam/string
import gleam/time/timestamp
Expand Down Expand Up @@ -141,7 +142,11 @@ pub fn create_engine(
use #(db_data, applied) <- result.try(init_db_and_get_applied(config))
use files <- result.try(read_migrations(config))
use applied <- result.try(
migration.match_migrations(applied, files)
migration.match_migrations(
applied,
files,
config.migrations.no_hash_check |> option.unwrap(False),
)
|> result.map_error(MigrationError),
)
let non_applied = migration.find_non_applied(files, applied)
Expand Down
10 changes: 9 additions & 1 deletion src/cigogne/config.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ pub type MigrationsConfig {
application_name: String,
migration_folder: option.Option(String),
dependencies: List(#(String, String)),
no_hash_check: option.Option(Bool),
)
}

pub const default_migrations_config = MigrationsConfig(
"cigogne",
option.None,
[],
option.None,
)

const default_migrations_folder = "migrations"
Expand Down Expand Up @@ -182,7 +184,12 @@ fn parse_migrations_section(
})
|> result.unwrap(default_migrations_config.dependencies)

MigrationsConfig(application_name:, migration_folder:, dependencies:)
MigrationsConfig(
application_name:,
migration_folder:,
dependencies:,
no_hash_check: option.None,
)
}

/// Merge two configurations, with `to_merge` having precedence over `config`.
Expand Down Expand Up @@ -234,6 +241,7 @@ pub fn merge_migrations_config(
migration_folder: to_merge.migration_folder
|> option.or(config.migration_folder),
dependencies: list.append(to_merge.dependencies, config.dependencies),
no_hash_check: to_merge.no_hash_check |> option.or(config.no_hash_check),
)
}

Expand Down
20 changes: 13 additions & 7 deletions src/cigogne/internal/cli.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,18 @@ fn migrations_config_decoder(
"Folder to store migrations in the priv folder (default: migrations)",
cli_lib.string,
)

cli_lib.options(
config.MigrationsConfig(
application_name:,
migration_folder:,
dependencies: [],
),
use no_hash_check <- cli_lib.flag(
"no-hash-check",
[],
"",
"/!\\ Warning: not recommended /!\\ Disable hash checks on engine creation",
cli_lib.bool,
)

cli_lib.options(config.MigrationsConfig(
application_name:,
migration_folder:,
dependencies: [],
no_hash_check: option.Some(no_hash_check),
))
}
2 changes: 1 addition & 1 deletion src/cigogne/internal/cli_lib.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ pub fn run(command: Command(a), args: List(String)) -> Result(a, Nil) {
|> tuple_to_result()
|> result.map_error(fn(err) {
print_errors(err)
print_help(command)
print_help_action(action)
})
Error(_) -> {
print_help(command)
Expand Down
3 changes: 2 additions & 1 deletion src/cigogne/migration.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ pub fn find_non_applied(
pub fn match_migrations(
elements: List(Migration),
matches: List(Migration),
no_hash_check: Bool,
) -> Result(List(Migration), MigrationError) {
let #(zeroes, elements) = list.split_while(elements, is_zero_migration)
let matches = utils.find_matches(elements, matches, compare)
Expand All @@ -168,7 +169,7 @@ pub fn match_migrations(
use #(migration, match_res) <- list.map(matches)
case match_res {
Ok(match) -> {
case migration.sha256 == match.sha256 {
case no_hash_check || migration.sha256 == match.sha256 {
True -> Ok(match)
False -> Error(FileHashChanged(migration |> to_fullname()))
}
Expand Down
43 changes: 28 additions & 15 deletions src/cigogne/to_v3.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -36,32 +36,40 @@ fn is_v3_applied(
})
}

fn get_applied_migrations(db_data: database.DatabaseData) {
let query = "SELECT createdAt, name FROM _migrations ORDER BY appliedAt ASC;"

pog.query(query)
|> pog.returning({
use timestamp <- decode.field(0, pog.timestamp_decoder())
use name <- decode.field(1, decode.string)
decode.success(#(timestamp, name))
})
|> pog.execute(db_data.connection)
|> result.map_error(database.PogQueryError)
|> result.map(fn(returned) {
returned.rows
|> list.map(fn(data) { migration.Migration("", data.0, data.1, [], [], "") })
})
}

fn to_v3(
db_data: database.DatabaseData,
app_name: String,
config: config.Config,
) -> Result(Nil, cigogne.CigogneError) {
use is_applied <- result.try(is_v3_applied(db_data))
io.println("Is v3 already applied ? " <> is_applied |> bool.to_string)
use <- bool.guard(is_applied, Ok(Nil))

use _ <- result.try(create_hash_column(db_data.connection))

use migration_files <- result.try(cigogne.read_migrations(
config.Config(
..config.default_config,
migrations: config.MigrationsConfig(
app_name,
option.Some("migrations"),
[],
),
),
))
use migration_files <- result.try(cigogne.read_migrations(config))
use applied_migrations <- result.try(
database.get_applied_migrations(db_data)
get_applied_migrations(db_data)
|> result.map_error(cigogne.DatabaseError),
)
use applied_migrations <- result.try(
migration.match_migrations(applied_migrations, migration_files)
migration.match_migrations(applied_migrations, migration_files, True)
|> result.map_error(cigogne.MigrationError),
)

Expand Down Expand Up @@ -126,12 +134,17 @@ pub fn main() {
option.Some("public"),
option.Some("migrations"),
),
config.MigrationsConfig(app_name, option.Some("migrations"), []),
config.MigrationsConfig(
app_name,
option.Some("migrations"),
[],
option.Some(True),
),
)
let assert Ok(db_data) = database.init(config)
io.println("Connected to database")

case to_v3(db_data, app_name) {
case to_v3(db_data, config) {
Ok(_) -> io.println("Good to go !")
Error(err) -> cigogne.print_error(err)
}
Expand Down
1 change: 1 addition & 0 deletions test/cigogne_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub fn read_migrations_test() {
application_name: "cigogne",
migration_folder: option.Some("test/migrations"),
dependencies: [],
no_hash_check: option.None,
),
)

Expand Down
5 changes: 5 additions & 0 deletions test/cigogne_test/config_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub fn merge_config_test() {
application_name: "cigogne",
migration_folder: option.None,
dependencies: [],
no_hash_check: option.Some(False),
),
)
let config_from_file =
Expand All @@ -51,6 +52,7 @@ pub fn merge_config_test() {
#("dep1", "20250915152222-AddUsers"),
#("dep2", "20250915152323-AddSomeConstraint"),
],
no_hash_check: option.None,
),
)

Expand All @@ -76,6 +78,7 @@ pub fn merge_config_test() {
#("dep1", "20250915152222-AddUsers"),
#("dep2", "20250915152323-AddSomeConstraint"),
],
no_hash_check: option.Some(False),
),
)
}
Expand All @@ -101,6 +104,7 @@ pub fn print_config_test() {
#("dep1", "20250915152222-AddUsers"),
#("dep2", "20250915152323-AddSomeConstraint"),
],
no_hash_check: option.Some(False),
),
)

Expand Down Expand Up @@ -142,6 +146,7 @@ pub fn get_migrations_folder_test() {
application_name: "cigogne",
migration_folder: option.Some("custom_migrations"),
dependencies: [],
no_hash_check: option.None,
)

assert config.get_migrations_folder(test_config) == "migrations"
Expand Down
9 changes: 8 additions & 1 deletion test/cigogne_test/internal/cli_test.gleam
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import cigogne/config
import cigogne/internal/cli
import gleam/option

pub fn new_requires_name_flag_test() {
let args = ["new", "--name", "Test"]
let args_no_name = ["new"]

assert cli.get_action("cigogne", args)
== Ok(cli.NewMigration(config.default_config.migrations, "Test"))
== Ok(cli.NewMigration(
config.MigrationsConfig(
..config.default_config.migrations,
no_hash_check: option.Some(False),
),
"Test",
))
assert cli.get_action("cigogne", args_no_name) == Error(Nil)
}
42 changes: 36 additions & 6 deletions test/cigogne_test/internal/database_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ pub fn init_with_envvar_test() {
option.Some(schema),
option.Some(migration_table),
),
config.MigrationsConfig("cigogne", option.Some("test/migrations"), []),
config.MigrationsConfig(
"cigogne",
option.Some("test/migrations"),
[],
option.None,
),
)
let init_res = database.init(config)

Expand All @@ -60,7 +65,12 @@ pub fn init_with_url_test() {
option.Some(schema),
option.Some(migration_table),
),
config.MigrationsConfig("cigogne", option.Some("test/migrations"), []),
config.MigrationsConfig(
"cigogne",
option.Some("test/migrations"),
[],
option.None,
),
)
let init_res = database.init(config)

Expand All @@ -84,7 +94,12 @@ pub fn init_with_detailed_config_test() {
option.Some(schema),
option.Some(migration_table),
),
config.MigrationsConfig("cigogne", option.Some("test/migrations"), []),
config.MigrationsConfig(
"cigogne",
option.Some("test/migrations"),
[],
option.None,
),
)
let init_res = database.init(config)

Expand All @@ -106,7 +121,12 @@ pub fn init_with_connection_test() {
option.Some(schema),
option.Some(migration_table),
),
config.MigrationsConfig("cigogne", option.Some("test/migrations"), []),
config.MigrationsConfig(
"cigogne",
option.Some("test/migrations"),
[],
option.None,
),
)
let init_res = database.init(config)
let assert Ok(init_res) = init_res
Expand All @@ -123,7 +143,12 @@ pub fn migration_table_exists_after_zero_test() {
option.Some(schema),
option.Some(migration_table),
),
config.MigrationsConfig("cigogne", option.Some("test/migrations"), []),
config.MigrationsConfig(
"cigogne",
option.Some("test/migrations"),
[],
option.None,
),
)
let assert Ok(init_res) = database.init(config)

Expand Down Expand Up @@ -158,7 +183,12 @@ pub fn apply_get_rollback_migrations_test() {
option.Some(schema),
option.Some(migration_table),
),
config.MigrationsConfig("cigogne", option.Some("test/migrations"), []),
config.MigrationsConfig(
"cigogne",
option.Some("test/migrations"),
[],
option.None,
),
)
let assert Ok(db) = database.init(config)

Expand Down
30 changes: 27 additions & 3 deletions test/cigogne_test/migration_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ pub fn match_migrations_test() {
let migs_from_db = [db_mig1, db_mig3]

let assert Ok(matches) =
migration.match_migrations(migs_from_db, migs_from_files)
migration.match_migrations(migs_from_db, migs_from_files, False)

assert matches == [mig1, mig3]
}
Expand All @@ -190,7 +190,7 @@ pub fn match_migrations_no_match_test() {
let migs_from_db = [db_mig1, db_mig_other]

let assert Error(err) =
migration.match_migrations(migs_from_db, migs_from_files)
migration.match_migrations(migs_from_db, migs_from_files, False)

assert err
== migration.CompoundError([
Expand Down Expand Up @@ -224,14 +224,38 @@ pub fn match_migrations_hash_changed_test() {
let migs_from_db = [db_mig1, db_mig3]

let assert Error(err) =
migration.match_migrations(migs_from_db, migs_from_files)
migration.match_migrations(migs_from_db, migs_from_files, False)

assert err
== migration.CompoundError([
migration.FileHashChanged(db_mig3 |> migration.to_fullname()),
])
}

pub fn match_migrations_no_hash_check_test() {
let assert Ok(ts1) = timestamp.parse_rfc3339("2024-12-17T21:00:00Z")
let assert Ok(ts2) = timestamp.parse_rfc3339("2024-12-17T21:01:00Z")
let assert Ok(ts3) = timestamp.parse_rfc3339("2024-12-17T21:02:00Z")

let mig1 =
migration.Migration("path1", ts1, "Mig1", ["up1"], ["down1"], "hash1")
let mig2 =
migration.Migration("path2", ts2, "Mig2", ["up2"], ["down2"], "hash2")
let mig3 =
migration.Migration("path3", ts3, "Mig3", ["up3"], ["down3"], "hash3")

let db_mig1 = migration.Migration("", ts1, "Mig1", [], [], "hash456")
let db_mig3 = migration.Migration("", ts3, "Mig3", [], [], "hash789")

let migs_from_files = [mig1, mig2, mig3]
let migs_from_db = [db_mig1, db_mig3]

let assert Ok(matches) =
migration.match_migrations(migs_from_db, migs_from_files, True)

assert matches == [mig1, mig3]
}

pub fn is_zero_migration_test() {
assert migration.Migration("", utils.epoch(), "", [], [], "")
|> migration.is_zero_migration()
Expand Down