Skip to content
Open
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ The required SQL statements will be printed to the console for use with other mi
tools like [`dbmate`](https://github.com/amacneil/dbmate). Alternatively, you can
apply the migrations directly using the `--apply` flag.

Support for [Cigogne](https://github.com/Billuc/cigogne) is in the works.
When using [Cigogne](https://github.com/Billuc/cigogne), you can include the migrations
into your project by running `gleam run -m cigogne include --name m25`.

## Development

Expand Down
1 change: 1 addition & 0 deletions gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ simplifile = ">= 2.3.0 and < 3.0.0"
gtempo = ">= 7.2.2 and < 8.0.0"
glint = ">= 1.2.1 and < 2.0.0"
argv = ">= 1.0.2 and < 2.0.0"
cigogne = ">= 5.0.4 and < 6.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
Expand Down
3 changes: 3 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" },
{ name = "cigogne", version = "5.0.4", build_tools = ["gleam"], requirements = ["argv", "envoy", "gleam_crypto", "gleam_erlang", "gleam_otp", "gleam_stdlib", "gleam_time", "pog", "simplifile", "splitter", "tom"], otp_app = "cigogne", source = "hex", outer_checksum = "2138362840E0773213C5811DD6FB43B59E44F0DA3DF9FF2882223C789207C201" },
{ name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" },
{ name = "eval", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "eval", source = "hex", outer_checksum = "264DAF4B49DF807F303CA4A4E4EBC012070429E40BE384C58FE094C4958F9BDA" },
{ name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" },
Expand Down Expand Up @@ -34,6 +35,7 @@ packages = [
{ name = "pog", version = "4.1.0", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_otp", "gleam_stdlib", "gleam_time", "pgo"], otp_app = "pog", source = "hex", outer_checksum = "E4AFBA39A5FAA2E77291836C9683ADE882E65A06AB28CA7D61AE7A3AD61EBBD5" },
{ name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" },
{ name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" },
{ name = "splitter", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "3DFD6B6C49E61EDAF6F7B27A42054A17CFF6CA2135FF553D0CB61C234D281DD0" },
{ name = "squirrel", version = "4.2.0", build_tools = ["gleam"], requirements = ["argv", "envoy", "eval", "filepath", "glam", "gleam_community_ansi", "gleam_crypto", "gleam_json", "gleam_regexp", "gleam_stdlib", "gleam_time", "glexer", "justin", "mug", "non_empty_list", "pog", "simplifile", "term_size", "tom", "tote", "youid"], otp_app = "squirrel", source = "hex", outer_checksum = "4FE8428187DF4A9A7E5F918D6AD68856ECC0D773F8F782EF95E03753F65CCB3D" },
{ name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" },
{ name = "tom", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "74D0C5A3761F7A7D06994755D4D5AD854122EF8E9F9F76A3E7547606D8C77091" },
Expand All @@ -43,6 +45,7 @@ packages = [

[requirements]
argv = { version = ">= 1.0.2 and < 2.0.0" }
cigogne = { version = ">= 5.0.4 and < 6.0.0" }
filepath = { version = ">= 1.1.2 and < 2.0.0" }
gleam_erlang = { version = ">= 1.0.0 and < 2.0.0" }
gleam_json = { version = ">= 3.0.1 and < 4.0.0" }
Expand Down
2 changes: 2 additions & 0 deletions priv/cigogne.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[migrations]
migration_folder = "migrations"
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
--- migration:up
create schema if not exists m25;

create table if not exists m25.job (
Expand Down Expand Up @@ -49,3 +50,8 @@ create table if not exists m25.version (
version timestamptz not null primary key,
created_at timestamptz not null default now()
);

--- migration:down
drop schema m25;

--- migration:end
94 changes: 33 additions & 61 deletions src/m25/internal/cli.gleam
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import argv
import filepath
import cigogne
import cigogne/config
import gleam/bool
import gleam/dynamic/decode
import gleam/erlang/application
import gleam/erlang/process
import gleam/io
import gleam/list
Expand All @@ -15,10 +15,7 @@ import gleam/time/calendar
import gleam/time/timestamp
import glint
import pog
import simplifile
import snag
import tempo
import tempo/datetime

fn add_error_context(error: String, context: String) {
context <> ": " <> error
Expand Down Expand Up @@ -77,74 +74,49 @@ fn get_current_version(
}
}

fn get_migrations() -> Result(List(String), String) {
use priv <- result.try(
application.priv_directory("m25")
|> result.replace_error("Couldn't get priv directory"),
)

let assert Ok(directory) =
[priv, "migrations"]
|> list.reduce(filepath.join)

simplifile.get_files(directory)
|> result.map_error(fn(err) {
err
|> string.inspect
|> add_error_context("Unable to get files in priv directory")
})
}

fn version_from_filename(filename: String) -> timestamp.Timestamp {
let assert Ok(filename) = filename |> filepath.split |> list.last
let assert Ok(version_string) = filename |> string.split("_") |> list.first
// gtempo requires an offset to be specified, so add one manually
let assert Ok(version) =
datetime.parse(version_string <> "Z", tempo.Custom("YYYYMMDDHHmmssZ"))
datetime.to_timestamp(version)
}

fn get_migrations_to_apply(
current_version: option.Option(timestamp.Timestamp),
) -> Result(option.Option(String), String) {
use files <- result.try(
get_migrations()
|> result.map_error(add_error_context(
_,
"Failed to get migrations for m25 tables",
)),
use config <- result.try(
config.get("m25")
|> result.map_error(fn(err) {
err
|> string.inspect
|> add_error_context("Failed to get migrations configuration")
}),
)
Comment on lines +80 to +87

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When I run this:

gleam run -m m25 migrate --addr=postgres://postgres:postgres@localhost:5432/postgres

I get the following:

Getting migrations for m25 tables... Command failed with an error: Failed to get migrations to apply for m25 tables: Failed to create migrations engine: DatabaseError(EnvVarUnset("DATABASE_URL")

Requiring DATABASE_URL is a breaking change I'd prefer to avoid. I don't mind supporting it, but it has to be alongside the --addr flag, with the flag taking precedence. I think you could do this in the connection_string_flag function where the flag's default is:

result.unwrap(
  envoy.get("DATABSE_URL"),
  "postgresql://postgres:postgres@localhost:5432/postgres",
)

You'd need to always pass the configured DB URL into Cignogne then.


use engine <- result.try(
cigogne.create_engine(config)
|> result.map_error(fn(err) {
err
|> string.inspect
|> add_error_context("Failed to create migrations engine")
}),
)

let files_to_apply = case current_version {
option.None -> files
let migrations = cigogne.get_all_migrations(engine)

let migrations_to_apply = case current_version {
option.None -> migrations
option.Some(current_version) -> {
files
|> list.filter(fn(file) {
timestamp.compare(version_from_filename(file), current_version)
== order.Gt
migrations
|> list.filter(fn(migration) {
timestamp.compare(migration.timestamp, current_version) == order.Gt
})
}
}

// Check if there are files to apply
use <- bool.guard(
case files_to_apply {
[] -> True
_ -> False
},
Ok(option.None),
)
use migration_sql <- result.try(
files_to_apply
|> list.try_map(simplifile.read)
|> result.map(string.join(_, "\n"))
|> result.map_error(fn(err) {
add_error_context(string.inspect(err), "Failed to read migrations")
}),
)
use <- bool.guard(list.is_empty(migrations_to_apply), Ok(option.None))

let migration_sql =
migrations_to_apply
|> list.flat_map(fn(m) { m.queries_up })
|> string.join("\n")

let assert Ok(latest_migration) = list.last(files_to_apply)
let new_version = version_from_filename(latest_migration)
let assert Ok(latest_migration) = list.last(migrations_to_apply)
let new_version = latest_migration.timestamp

let new_version_sql = "insert into m25.version (version)
values (timestamptz '" <> {
Expand Down