From 677e48365253b936cad9d8519f8bbb93eb1f0ea7 Mon Sep 17 00:00:00 2001 From: Alexis Viscogliosi Date: Thu, 30 May 2024 08:57:03 +0200 Subject: [PATCH] feat: support unknown DSN --- .gitignore | 4 +- cmd/init.go | 9 +- cmd/root.go | 5 + docker-compose.yml | 13 ++ ...chema.md => 04-working-with-old-schema.md} | 0 .../05-working-with-not-supported-db.md | 5 + .../04-api/01-postgres/01-constructive.md | 2 +- .../docs/04-api/01-postgres/02-destructive.md | 2 +- .../docs/04-api/01-postgres/03-informative.md | 2 +- example/mysql/.amigo/config.yml | 10 ++ example/mysql/.amigo/main.go | 23 ++++ example/mysql/.gitignore | 1 + example/mysql/go.mod | 18 +++ example/mysql/go.sum | 54 ++++++++ ...40529125357_create_table_schema_version.go | 30 +++++ example/mysql/migrations/migrations.go | 11 ++ ...40524090427_create_table_schema_version.go | 26 ---- ...40530063939_create_table_schema_version.go | 28 +++++ ...go => 20240530063940_create_user_table.go} | 8 +- example/pg/migrations/migrations.go | 4 +- go.work | 1 + pkg/amigo/amigo.go | 44 ++++--- pkg/amigo/execute_main.go | 2 +- pkg/amigo/run_migration.go | 29 +++-- pkg/amigoctx/ctx.go | 10 +- pkg/schema/base/base.go | 117 ++++++++++++++++++ pkg/schema/migrator.go | 46 +++++-- pkg/schema/pg/postgres.go | 76 +----------- pkg/schema/pg/postgres_test.go | 25 ++++ pkg/schema/schema.go | 2 +- pkg/templates/init_create_table.go.tmpl | 4 +- pkg/templates/init_create_table_base.go.tmpl | 5 + pkg/templates/main.go.tmpl | 2 +- ...ation_change.go.tmpl => migration.go.tmpl} | 26 ++-- pkg/templates/migration_classic.go.tmpl | 38 ------ pkg/templates/types.go | 12 +- pkg/templates/util.go | 55 +++++--- pkg/types/types.go | 20 +-- pkg/utils/error.go | 31 +++++ 39 files changed, 546 insertions(+), 254 deletions(-) rename docs/docs/02-quick-start/{03-working-with-old-schema.md => 04-working-with-old-schema.md} (100%) create mode 100644 docs/docs/02-quick-start/05-working-with-not-supported-db.md create mode 100644 example/mysql/.amigo/config.yml create mode 100644 example/mysql/.amigo/main.go create mode 100644 example/mysql/.gitignore create mode 100644 example/mysql/go.mod create mode 100644 example/mysql/go.sum create mode 100644 example/mysql/migrations/20240529125357_create_table_schema_version.go create mode 100644 example/mysql/migrations/migrations.go delete mode 100644 example/pg/migrations/20240524090427_create_table_schema_version.go create mode 100644 example/pg/migrations/20240530063939_create_table_schema_version.go rename example/pg/migrations/{20240524090434_create_user_table.go => 20240530063940_create_user_table.go} (61%) create mode 100644 pkg/schema/base/base.go create mode 100644 pkg/templates/init_create_table_base.go.tmpl rename pkg/templates/{migration_change.go.tmpl => migration.go.tmpl} (55%) delete mode 100644 pkg/templates/migration_classic.go.tmpl create mode 100644 pkg/utils/error.go diff --git a/.gitignore b/.gitignore index d2f5903..32df4c0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ .idea migrations .amigo -!example/pg/.amigo -!example/pg/migrations +!example/*/.amigo +!example/*/migrations diff --git a/cmd/init.go b/cmd/init.go index e8eddba..29f9e1d 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -45,18 +45,21 @@ var initCmd = &cobra.Command{ return fmt.Errorf("unable to open migrations.go file: %w", err) } - template, err := templates.GetInitCreateTableTemplate(templates.CreateTableData{Name: cmdCtx.SchemaVersionTable}) + inUp, err := templates.GetInitCreateTableTemplate(templates.CreateTableData{Name: cmdCtx.SchemaVersionTable}, + am.Driver == types.DriverUnknown) if err != nil { return err } err = am.GenerateMigrationFile(&amigo.GenerateMigrationFileParams{ Name: "create_table_schema_version", - Up: template, + Up: inUp, + Down: "// nothing to do to keep the schema version table", Type: types.MigrationFileTypeClassic, Now: now, Writer: file, - UseSchemaImport: true, + UseSchemaImport: am.Driver != types.DriverUnknown, + UseFmtImport: am.Driver == types.DriverUnknown, }) if err != nil { return err diff --git a/cmd/root.go b/cmd/root.go index 27f74a3..ce5e4b2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -27,8 +27,13 @@ var rootCmd = &cobra.Command{ First you need to create a main folder with amigo init: will create a folder named .amigo with a context file inside to not have to pass the dsn every time. + + Postgres: $ amigo context --dsn "postgres://user:password@host:port/dbname?sslmode=disable" + Unknown Driver (Mysql in this case): + $ amigo context --dsn "user:password@tcp(host:port)/dbname" + $ amigo init note: will create: diff --git a/docker-compose.yml b/docker-compose.yml index d68992d..1554775 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,19 @@ services: - 6666:5432 volumes: - pg_data:/var/lib/postgresql/data + mysql_mig: + image: mysql:8 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: mysql + MYSQL_USER: mysql + MYSQL_PASSWORD: mysql + ports: + - 6668:3306 + volumes: + - mysql_data:/var/lib/mysql volumes: pg_data: name: pg_data + mysql_data: + name: mysql_data diff --git a/docs/docs/02-quick-start/03-working-with-old-schema.md b/docs/docs/02-quick-start/04-working-with-old-schema.md similarity index 100% rename from docs/docs/02-quick-start/03-working-with-old-schema.md rename to docs/docs/02-quick-start/04-working-with-old-schema.md diff --git a/docs/docs/02-quick-start/05-working-with-not-supported-db.md b/docs/docs/02-quick-start/05-working-with-not-supported-db.md new file mode 100644 index 0000000..cd9b2e4 --- /dev/null +++ b/docs/docs/02-quick-start/05-working-with-not-supported-db.md @@ -0,0 +1,5 @@ +# Working with not supported database + +If you want to use a database that is not supported by amigo, you can. + +If the DSN is not recognized you will be using the base interface which is `base.Schema` (it only implement methods to manipulate the versions table) but you have access to the `*sql.DB`. \ No newline at end of file diff --git a/docs/docs/04-api/01-postgres/01-constructive.md b/docs/docs/04-api/01-postgres/01-constructive.md index c98db1b..7e862c5 100644 --- a/docs/docs/04-api/01-postgres/01-constructive.md +++ b/docs/docs/04-api/01-postgres/01-constructive.md @@ -22,7 +22,7 @@ They are the operations that create, alter, or drop tables, columns, indexes, co - [AddPrimaryKeyConstraint(tableName schema.TableName, columns []string, opts ...schema.PrimaryKeyConstraintOptions)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.AddPrimaryKeyConstraint) -- [AddVersion(version string)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.AddVersion) +- [AddVersion(version string)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/base#Schema.AddVersion) - [CreateEnum(name string, values []string, opts ...schema.CreateEnumOptions)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.CreateEnum) diff --git a/docs/docs/04-api/01-postgres/02-destructive.md b/docs/docs/04-api/01-postgres/02-destructive.md index 94bf527..13b15f2 100644 --- a/docs/docs/04-api/01-postgres/02-destructive.md +++ b/docs/docs/04-api/01-postgres/02-destructive.md @@ -17,7 +17,7 @@ They are the operations that drop tables, columns, indexes, constraints, and so - [DropTable(tableName schema.TableName, opts ...schema.DropTableOptions)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.DropTable) -- [RemoveVersion(version string)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.RemoveVersion) +- [RemoveVersion(version string)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/base#Schema.RemoveVersion) - [RenameColumn(tableName schema.TableName, oldColumnName, newColumnName string)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.RenameColumn) diff --git a/docs/docs/04-api/01-postgres/03-informative.md b/docs/docs/04-api/01-postgres/03-informative.md index 99262a5..36c1055 100644 --- a/docs/docs/04-api/01-postgres/03-informative.md +++ b/docs/docs/04-api/01-postgres/03-informative.md @@ -13,7 +13,7 @@ They are the operations that give you information about the database schema. - [PrimaryKeyExist(tableName schema.TableName) bool](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.PrimaryKeyExist) -- [FindAppliedVersions() []string](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.FindAppliedVersions) +- [FindAppliedVersions() []string](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/base#Schema.FindAppliedVersions) - [FindEnumUsage(name string, schemaName *string) []schema.EnumUsage](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.FindEnumUsage) diff --git a/example/mysql/.amigo/config.yml b/example/mysql/.amigo/config.yml new file mode 100644 index 0000000..60407f7 --- /dev/null +++ b/example/mysql/.amigo/config.yml @@ -0,0 +1,10 @@ +amigo-folder: .amigo +debug: false +dsn: root:root@tcp(localhost:6668)/mysql +folder: migrations +json: false +package: migrations +schema-version-table: mig_schema_versions +shell-path: /bin/bash +sql: false +sql-syntax-highlighting: true diff --git a/example/mysql/.amigo/main.go b/example/mysql/.amigo/main.go new file mode 100644 index 0000000..01f0f50 --- /dev/null +++ b/example/mysql/.amigo/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "database/sql" + "github.com/alexisvisco/amigo/pkg/entrypoint" + "github.com/alexisvisco/amigo/pkg/utils/events" + "github.com/alexisvisco/amigo/pkg/utils/logger" + _ "github.com/go-sql-driver/mysql" + migrations "mysql/migrations" + "os" +) + +func main() { + opts, arg := entrypoint.AmigoContextFromFlags() + + db, err := sql.Open("mysql", opts.DSN) + if err != nil { + logger.Error(events.MessageEvent{Message: err.Error()}) + os.Exit(1) + } + + entrypoint.Main(db, arg, migrations.Migrations, opts) +} diff --git a/example/mysql/.gitignore b/example/mysql/.gitignore new file mode 100644 index 0000000..003a133 --- /dev/null +++ b/example/mysql/.gitignore @@ -0,0 +1 @@ +.amigo/main diff --git a/example/mysql/go.mod b/example/mysql/go.mod new file mode 100644 index 0000000..f0c9f2d --- /dev/null +++ b/example/mysql/go.mod @@ -0,0 +1,18 @@ +module mysql + +go 1.22 + +replace github.com/alexisvisco/amigo => ../../ + +require ( + github.com/alexisvisco/amigo v0.0.0-00010101000000-000000000000 + github.com/go-sql-driver/mysql v1.8.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect + github.com/gobuffalo/flect v1.0.2 // indirect + github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551 // indirect +) diff --git a/example/mysql/go.sum b/example/mysql/go.sum new file mode 100644 index 0000000..f5168e2 --- /dev/null +++ b/example/mysql/go.sum @@ -0,0 +1,54 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551 h1:+EXKKt7RC4HyE/iE8zSeFL+7YBL8Z7vpBaEE3c7lCnk= +github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551/go.mod h1:ztTX0ctjRZ1wn9OXrzhonvNmv43yjFUXJYJR95JQAJE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/example/mysql/migrations/20240529125357_create_table_schema_version.go b/example/mysql/migrations/20240529125357_create_table_schema_version.go new file mode 100644 index 0000000..a9107e7 --- /dev/null +++ b/example/mysql/migrations/20240529125357_create_table_schema_version.go @@ -0,0 +1,30 @@ +package migrations + +import ( + "fmt" + "github.com/alexisvisco/amigo/pkg/schema/base" + "time" +) + +type Migration20240529125357CreateTableSchemaVersion struct{} + +func (m Migration20240529125357CreateTableSchemaVersion) Up(s *base.Schema) { + query := `CREATE TABLE IF NOT EXISTS mig_schema_versions ( id VARCHAR(255) NOT NULL PRIMARY KEY )` + _, err := s.TX.ExecContext(s.Context.Context, query) + if err != nil { + s.Context.RaiseError(fmt.Errorf("unable to create table schema version: %w", err)) + } +} + +func (m Migration20240529125357CreateTableSchemaVersion) Down(s *base.Schema) { + // nothing to do to keep the schema version table +} + +func (m Migration20240529125357CreateTableSchemaVersion) Name() string { + return "create_table_schema_version" +} + +func (m Migration20240529125357CreateTableSchemaVersion) Date() time.Time { + t, _ := time.Parse(time.RFC3339, "2024-05-29T14:53:57+02:00") + return t +} diff --git a/example/mysql/migrations/migrations.go b/example/mysql/migrations/migrations.go new file mode 100644 index 0000000..a9c39a3 --- /dev/null +++ b/example/mysql/migrations/migrations.go @@ -0,0 +1,11 @@ +// Package migrations +// /!\ File is auto-generated DO NOT EDIT. +package migrations + +import ( + "github.com/alexisvisco/amigo/pkg/schema" +) + +var Migrations = []schema.Migration{ + &Migration20240529125357CreateTableSchemaVersion{}, +} diff --git a/example/pg/migrations/20240524090427_create_table_schema_version.go b/example/pg/migrations/20240524090427_create_table_schema_version.go deleted file mode 100644 index 323a7ba..0000000 --- a/example/pg/migrations/20240524090427_create_table_schema_version.go +++ /dev/null @@ -1,26 +0,0 @@ -package migrations - -import ( - "github.com/alexisvisco/amigo/pkg/schema" - "github.com/alexisvisco/amigo/pkg/schema/pg" - "time" -) - -type Migration20240524090427CreateTableSchemaVersion struct{} - -func (m Migration20240524090427CreateTableSchemaVersion) Up(s *pg.Schema) { - s.CreateTable("public.mig_schema_versions", func(s *pg.PostgresTableDef) { - s.String("id") - }, schema.TableOptions{IfNotExists: true}) -} - -func (m Migration20240524090427CreateTableSchemaVersion) Down(s *pg.Schema) {} - -func (m Migration20240524090427CreateTableSchemaVersion) Name() string { - return "create_table_schema_version" -} - -func (m Migration20240524090427CreateTableSchemaVersion) Date() time.Time { - t, _ := time.Parse(time.RFC3339, "2024-05-24T11:04:27+02:00") - return t -} diff --git a/example/pg/migrations/20240530063939_create_table_schema_version.go b/example/pg/migrations/20240530063939_create_table_schema_version.go new file mode 100644 index 0000000..a8efaef --- /dev/null +++ b/example/pg/migrations/20240530063939_create_table_schema_version.go @@ -0,0 +1,28 @@ +package migrations + +import ( + "github.com/alexisvisco/amigo/pkg/schema" + "github.com/alexisvisco/amigo/pkg/schema/pg" + "time" +) + +type Migration20240530063939CreateTableSchemaVersion struct{} + +func (m Migration20240530063939CreateTableSchemaVersion) Up(s *pg.Schema) { + s.CreateTable("public.mig_schema_versions", func(s *pg.PostgresTableDef) { + s.String("id") + }, schema.TableOptions{IfNotExists: true}) +} + +func (m Migration20240530063939CreateTableSchemaVersion) Down(s *pg.Schema) { + // nothing to do to keep the schema version table +} + +func (m Migration20240530063939CreateTableSchemaVersion) Name() string { + return "create_table_schema_version" +} + +func (m Migration20240530063939CreateTableSchemaVersion) Date() time.Time { + t, _ := time.Parse(time.RFC3339, "2024-05-30T08:39:39+02:00") + return t +} diff --git a/example/pg/migrations/20240524090434_create_user_table.go b/example/pg/migrations/20240530063940_create_user_table.go similarity index 61% rename from example/pg/migrations/20240524090434_create_user_table.go rename to example/pg/migrations/20240530063940_create_user_table.go index 97c29e9..1f94ee3 100644 --- a/example/pg/migrations/20240524090434_create_user_table.go +++ b/example/pg/migrations/20240530063940_create_user_table.go @@ -5,9 +5,9 @@ import ( "time" ) -type Migration20240524090434CreateUserTable struct{} +type Migration20240530063940CreateUserTable struct{} -func (m Migration20240524090434CreateUserTable) Change(s *pg.Schema) { +func (m Migration20240530063940CreateUserTable) Change(s *pg.Schema) { s.CreateTable("users", func(def *pg.PostgresTableDef) { def.Column("id", "bigserial") def.String("name") @@ -17,11 +17,11 @@ func (m Migration20240524090434CreateUserTable) Change(s *pg.Schema) { }) } -func (m Migration20240524090434CreateUserTable) Name() string { +func (m Migration20240530063940CreateUserTable) Name() string { return "create_user_table" } -func (m Migration20240524090434CreateUserTable) Date() time.Time { +func (m Migration20240530063940CreateUserTable) Date() time.Time { t, _ := time.Parse(time.RFC3339, "2024-05-24T11:04:34+02:00") return t } diff --git a/example/pg/migrations/migrations.go b/example/pg/migrations/migrations.go index 2925f4f..d34ba77 100644 --- a/example/pg/migrations/migrations.go +++ b/example/pg/migrations/migrations.go @@ -7,6 +7,6 @@ import ( ) var Migrations = []schema.Migration{ - &Migration20240524090427CreateTableSchemaVersion{}, - &Migration20240524090434CreateUserTable{}, + &Migration20240530063939CreateTableSchemaVersion{}, + &Migration20240530063940CreateUserTable{}, } diff --git a/go.work b/go.work index e0e3ce4..b019544 100644 --- a/go.work +++ b/go.work @@ -2,5 +2,6 @@ go 1.22 use ( example/pg + example/mysql . ) diff --git a/pkg/amigo/amigo.go b/pkg/amigo/amigo.go index cea22e2..d87b673 100644 --- a/pkg/amigo/amigo.go +++ b/pkg/amigo/amigo.go @@ -2,7 +2,6 @@ package amigo import ( "database/sql" - "errors" "fmt" "github.com/alexisvisco/amigo/pkg/amigoctx" "github.com/alexisvisco/amigo/pkg/schema" @@ -23,14 +22,14 @@ import ( type Amigo struct { ctx *amigoctx.Context - driver types.Driver + Driver types.Driver } // NewAmigo create a new amigo instance func NewAmigo(ctx *amigoctx.Context) Amigo { return Amigo{ ctx: ctx, - driver: getDriver(ctx.DSN), + Driver: getDriver(ctx.DSN), } } @@ -99,8 +98,8 @@ func (a Amigo) GenerateMainFile(writer io.Writer) error { template, err := templates.GetMainTemplate(templates.MainData{ PackagePath: packagePath, - DriverPath: a.driver.PackagePath(), - DriverName: a.driver.String(), + DriverPath: a.Driver.PackagePath(), + DriverName: a.Driver.String(), }) if err != nil { @@ -119,9 +118,11 @@ type GenerateMigrationFileParams struct { Name string Up string Down string + Change string Type types.MigrationFileType Now time.Time UseSchemaImport bool + UseFmtImport bool Writer io.Writer } @@ -129,16 +130,26 @@ type GenerateMigrationFileParams struct { func (a Amigo) GenerateMigrationFile(params *GenerateMigrationFileParams) error { structName := utils.MigrationStructName(params.Now, params.Name) - fileContent, err := templates.GetMigrationChangeTemplate(params.Type, templates.MigrationData{ + orDefault := func(s string) string { + if s == "" { + return "// TODO: implement the migration" + } + return s + } + + fileContent, err := templates.GetMigrationTemplate(templates.MigrationData{ Package: a.ctx.PackagePath, - PackageDriverName: a.driver.PackageName(), - PackageDriverPath: a.driver.PackageSchemaPath(), StructName: structName, Name: flect.Underscore(params.Name), - InUp: params.Up, - InDown: params.Down, + Type: params.Type, + InChange: orDefault(params.Change), + InUp: orDefault(params.Up), + InDown: orDefault(params.Down), CreatedAt: params.Now.Format(time.RFC3339), + PackageDriverName: a.Driver.PackageName(), + PackageDriverPath: a.Driver.PackageSchemaPath(), UseSchemaImport: params.UseSchemaImport, + UseFmtImport: params.UseFmtImport, }) if err != nil { @@ -234,16 +245,3 @@ func (a Amigo) SkipMigrationFile(db *sql.DB) error { return nil } - -var ( - ErrDriverNotFound = errors.New("driver not found") -) - -func getDriver(dsn string) types.Driver { - switch { - case strings.HasPrefix(dsn, "postgres"): - return types.DriverPostgres - } - - return types.DriverUnknown -} diff --git a/pkg/amigo/execute_main.go b/pkg/amigo/execute_main.go index 2185d8f..6c728b1 100644 --- a/pkg/amigo/execute_main.go +++ b/pkg/amigo/execute_main.go @@ -50,7 +50,7 @@ func (a Amigo) ExecuteMain(arg MainArg) error { args = []string{ "./" + mainBinaryPath, - "-dsn", a.ctx.DSN, + "-dsn", fmt.Sprintf(`"%s"`, a.ctx.DSN), "-schema-version-table", a.ctx.SchemaVersionTable, } diff --git a/pkg/amigo/run_migration.go b/pkg/amigo/run_migration.go index 82f63f1..4f7e077 100644 --- a/pkg/amigo/run_migration.go +++ b/pkg/amigo/run_migration.go @@ -6,6 +6,7 @@ import ( "errors" "github.com/alexisvisco/amigo/pkg/amigoctx" "github.com/alexisvisco/amigo/pkg/schema" + "github.com/alexisvisco/amigo/pkg/schema/base" "github.com/alexisvisco/amigo/pkg/schema/pg" "github.com/alexisvisco/amigo/pkg/types" "github.com/alexisvisco/amigo/pkg/utils" @@ -13,6 +14,7 @@ import ( sqldblogger "github.com/simukti/sqldb-logger" "io" "log/slog" + "strings" "time" ) @@ -88,6 +90,15 @@ func (a Amigo) validateRunMigration(conn *sql.DB, direction *types.MigrationDire return nil } +func getDriver(dsn string) types.Driver { + switch { + case strings.HasPrefix(dsn, "postgres"): + return types.DriverPostgres + } + + return types.DriverUnknown +} + func (a Amigo) getMigrationApplier( ctx context.Context, conn *sql.DB, @@ -99,15 +110,17 @@ func (a Amigo) getMigrationApplier( conn = sqldblogger.OpenDriver(a.ctx.DSN, conn.Driver(), recorder) } - switch a.driver { + opts := &schema.MigratorOption{ + DryRun: a.ctx.Migration.DryRun, + ContinueOnError: a.ctx.Migration.ContinueOnError, + SchemaVersionTable: schema.TableName(a.ctx.SchemaVersionTable), + DBLogger: recorder, + } + + switch a.Driver { case types.DriverPostgres: - return schema.NewMigrator(ctx, conn, pg.NewPostgres, &schema.MigratorOption{ - DryRun: a.ctx.Migration.DryRun, - ContinueOnError: a.ctx.Migration.ContinueOnError, - SchemaVersionTable: schema.TableName(a.ctx.SchemaVersionTable), - DBLogger: recorder, - }), nil + return schema.NewMigrator(ctx, conn, pg.NewPostgres, opts), nil } - return nil, errors.New("driver not supported") + return schema.NewMigrator(ctx, conn, base.NewBase, opts), nil } diff --git a/pkg/amigoctx/ctx.go b/pkg/amigoctx/ctx.go index 655b707..0999a3e 100644 --- a/pkg/amigoctx/ctx.go +++ b/pkg/amigoctx/ctx.go @@ -82,15 +82,7 @@ func (r *Root) ValidateDSN() error { return ErrDSNEmpty } - allowedDrivers := []string{"postgres"} - - for _, driver := range allowedDrivers { - if strings.Contains(r.DSN, driver) { - return nil - } - } - - return fmt.Errorf("unsupported driver, allowed drivers are: %s", strings.Join(allowedDrivers, ", ")) + return nil } type Migration struct { diff --git a/pkg/schema/base/base.go b/pkg/schema/base/base.go new file mode 100644 index 0000000..2d530bc --- /dev/null +++ b/pkg/schema/base/base.go @@ -0,0 +1,117 @@ +package base + +import ( + "fmt" + "github.com/alexisvisco/amigo/pkg/schema" + "github.com/alexisvisco/amigo/pkg/types" + "github.com/alexisvisco/amigo/pkg/utils" +) + +// Schema is the base schema. It is used to support unknown database types and provide a default implementation. +type Schema struct { + // TX is the transaction to execute the queries. + TX schema.DB + + // DB is a database connection but not in a transaction. + DB schema.DB + + Context *schema.MigratorContext + + // ReversibleMigrationExecutor is a helper to execute reversible migrations in change method. + *schema.ReversibleMigrationExecutor +} + +// NewBase creates a new base schema. +func NewBase(ctx *schema.MigratorContext, tx schema.DB, db schema.DB) *Schema { + return &Schema{ + TX: tx, + DB: db, + Context: ctx, + ReversibleMigrationExecutor: schema.NewReversibleMigrationExecutor(ctx), + } +} + +// rollbackMode will allow to execute migration without getting a infinite loop by checking the migration direction. +func (p *Schema) rollbackMode() *Schema { + ctx := *p.Context + ctx.MigrationDirection = types.MigrationDirectionNotReversible + return &Schema{ + TX: p.TX, + DB: p.DB, + Context: &ctx, + ReversibleMigrationExecutor: schema.NewReversibleMigrationExecutor(&ctx), + } +} + +// AddVersion adds a new version to the schema_migrations table. +// This function is not reversible. +func (p *Schema) AddVersion(version string) { + sql := `INSERT INTO {version_table} (id) VALUES ({version})` + + replacer := utils.Replacer{ + "version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()), + "version": utils.StrFunc(fmt.Sprintf("'%s'", version)), + } + + _, err := p.TX.ExecContext(p.Context.Context, replacer.Replace(sql)) + if err != nil { + p.Context.RaiseError(fmt.Errorf("error while adding version: %w", err)) + return + } + + p.Context.AddVersionCreated(version) +} + +// RemoveVersion removes a version from the schema_migrations table. +// This function is not reversible. +func (p *Schema) RemoveVersion(version string) { + sql := `DELETE FROM {version_table} WHERE id = {version}` + + replacer := utils.Replacer{ + "version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()), + "version": utils.StrFunc(fmt.Sprintf("'%s'", version)), + } + + _, err := p.TX.ExecContext(p.Context.Context, replacer.Replace(sql)) + if err != nil { + p.Context.RaiseError(fmt.Errorf("error while removing version: %w", err)) + return + } + + p.Context.AddVersionDeleted(version) +} + +// FindAppliedVersions returns all the applied versions in the schema_migrations table. +func (p *Schema) FindAppliedVersions() []string { + sql := `SELECT id FROM {version_table} ORDER BY id ASC` + + replacer := utils.Replacer{ + "version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()), + } + + rows, err := p.TX.QueryContext(p.Context.Context, replacer.Replace(sql)) + if err != nil { + p.Context.RaiseError(fmt.Errorf("error while fetching applied versions: %w", err)) + return nil + } + + defer rows.Close() + + var versions []string + + for rows.Next() { + var version string + if err := rows.Scan(&version); err != nil { + p.Context.RaiseError(fmt.Errorf("error while scanning version: %w", err)) + return nil + } + versions = append(versions, version) + } + + if err := rows.Err(); err != nil { + p.Context.RaiseError(fmt.Errorf("error after iterating rows: %w", err)) + return nil + } + + return versions +} diff --git a/pkg/schema/migrator.go b/pkg/schema/migrator.go index d0eba7b..2cf8543 100644 --- a/pkg/schema/migrator.go +++ b/pkg/schema/migrator.go @@ -8,6 +8,7 @@ import ( "github.com/alexisvisco/amigo/pkg/utils/dblog" "github.com/alexisvisco/amigo/pkg/utils/events" "github.com/alexisvisco/amigo/pkg/utils/logger" + "regexp" "slices" "time" ) @@ -80,17 +81,13 @@ func NewMigrator[T Schema]( func (m *Migrator[T]) Apply(direction types.MigrationDirection, version *string, steps *int, migrations []Migration) bool { db := m.schemaFactory(m.ctx, m.db, m.db) - migrationsToExecute := make([]Migration, 0, len(migrations)) - if !db.TableExist(m.Options().SchemaVersionTable) { - // the first migration is always the creation of the schema version table - migrationsToExecute = append(migrationsToExecute, migrations[0]) - } else { - migrationsToExecute = m.findMigrationsToExecute(db, - direction, - migrations, - version, - steps) - } + migrationsToExecute := m.findMigrationsToExecute( + db, + direction, + migrations, + version, + steps, + ) if len(migrationsToExecute) == 0 { logger.Info(events.MessageEvent{Message: "Found 0 migrations to apply"}) @@ -148,7 +145,13 @@ func (m *Migrator[T]) findMigrationsToExecute( version *string, steps *int, // only used for rollback ) []Migration { - appliedVersions := s.FindAppliedVersions() + appliedVersions, err := utils.PanicToError1(s.FindAppliedVersions) + if isTableDoesNotExists(err) { + appliedVersions = []string{} + } else if err != nil { + m.ctx.RaiseError(err) + } + var versionsToApply []Migration var migrationsTimeFormat []string var versionToMigration = make(map[string]Migration) @@ -284,3 +287,22 @@ func (m *Migrator[T]) ToggleDBLog(b bool) { m.Options().DBLogger.ToggleLogger(b) } } + +func isTableDoesNotExists(err error) bool { + if err == nil { + return false + } + + re := []*regexp.Regexp{ + regexp.MustCompile(`Error 1146 \(42S02\): Table '.*' doesn't exist`), + regexp.MustCompile(`ERROR: relation ".*" does not exist \(SQLSTATE 42P01\)`), + } + + for _, r := range re { + if r.MatchString(err.Error()) { + return true + } + } + + return false +} diff --git a/pkg/schema/pg/postgres.go b/pkg/schema/pg/postgres.go index fadb461..d9682fb 100644 --- a/pkg/schema/pg/postgres.go +++ b/pkg/schema/pg/postgres.go @@ -3,6 +3,7 @@ package pg import ( "fmt" "github.com/alexisvisco/amigo/pkg/schema" + "github.com/alexisvisco/amigo/pkg/schema/base" "github.com/alexisvisco/amigo/pkg/types" "github.com/alexisvisco/amigo/pkg/utils" ) @@ -16,6 +17,8 @@ type Schema struct { Context *schema.MigratorContext + *base.Schema + // ReversibleMigrationExecutor is a helper to execute reversible migrations in change method. *schema.ReversibleMigrationExecutor } @@ -25,6 +28,7 @@ func NewPostgres(ctx *schema.MigratorContext, tx schema.DB, db schema.DB) *Schem TX: tx, DB: db, Context: ctx, + Schema: base.NewBase(ctx, tx, db), ReversibleMigrationExecutor: schema.NewReversibleMigrationExecutor(ctx), } } @@ -37,6 +41,7 @@ func (p *Schema) rollbackMode() *Schema { TX: p.TX, DB: p.DB, Context: &ctx, + Schema: base.NewBase(&ctx, p.TX, p.DB), ReversibleMigrationExecutor: schema.NewReversibleMigrationExecutor(&ctx), } } @@ -142,77 +147,6 @@ func (p *Schema) DropExtension(name string, opt ...schema.DropExtensionOptions) p.Context.AddExtensionDropped(options) } -// AddVersion adds a new version to the schema_migrations table. -// This function is not reversible. -func (p *Schema) AddVersion(version string) { - sql := `INSERT INTO {version_table} (id) VALUES ($1)` - - replacer := utils.Replacer{ - "version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()), - } - - _, err := p.TX.ExecContext(p.Context.Context, replacer.Replace(sql), version) - if err != nil { - p.Context.RaiseError(fmt.Errorf("error while adding version: %w", err)) - return - } - - p.Context.AddVersionCreated(version) -} - -// RemoveVersion removes a version from the schema_migrations table. -// This function is not reversible. -func (p *Schema) RemoveVersion(version string) { - sql := `DELETE FROM {version_table} WHERE id = $1` - - replacer := utils.Replacer{ - "version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()), - } - - _, err := p.TX.ExecContext(p.Context.Context, replacer.Replace(sql), version) - if err != nil { - p.Context.RaiseError(fmt.Errorf("error while removing version: %w", err)) - return - } - - p.Context.AddVersionDeleted(version) -} - -// FindAppliedVersions returns all the applied versions in the schema_migrations table. -func (p *Schema) FindAppliedVersions() []string { - sql := `SELECT id FROM {version_table} ORDER BY id ASC` - - replacer := utils.Replacer{ - "version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()), - } - - rows, err := p.TX.QueryContext(p.Context.Context, replacer.Replace(sql)) - if err != nil { - p.Context.RaiseError(fmt.Errorf("error while fetching applied versions: %w", err)) - return nil - } - - defer rows.Close() - - var versions []string - - for rows.Next() { - var version string - if err := rows.Scan(&version); err != nil { - p.Context.RaiseError(fmt.Errorf("error while scanning version: %w", err)) - return nil - } - versions = append(versions, version) - } - - if err := rows.Err(); err != nil { - p.Context.RaiseError(fmt.Errorf("error after iterating rows: %w", err)) - return nil - } - - return versions -} - func (p *Schema) toExtension(extension string) string { switch extension { case "uuid": diff --git a/pkg/schema/pg/postgres_test.go b/pkg/schema/pg/postgres_test.go index fbe5256..b8c5799 100644 --- a/pkg/schema/pg/postgres_test.go +++ b/pkg/schema/pg/postgres_test.go @@ -37,6 +37,12 @@ var ( postgresDB) ) +func versionTable(schemaName string, s *Schema) { + s.CreateTable(schema.Table("mig_schema_version", schemaName), func(s *PostgresTableDef) { + s.String("id") + }, schema.TableOptions{IfNotExists: true}) +} + func connect(t *testing.T) (*sql.DB, dblog.DatabaseLogger) { db, err := sql.Open("pgx", conn) @@ -116,6 +122,25 @@ func TestPostgres_AddExtension(t *testing.T) { }) } +func TestPostgres_Versions(t *testing.T) { + p, _, _ := baseTest(t, "select 1", "tst_pg_add_version") + + versionTable("tst_pg_add_version", p) + + p.Context.MigratorOptions.SchemaVersionTable = schema.Table("mig_schema_version", "tst_pg_add_version") + + p.AddVersion("v1") + versions := p.FindAppliedVersions() + + require.Len(t, versions, 1) + require.Equal(t, "v1", versions[0]) + + p.RemoveVersion("v1") + + versions = p.FindAppliedVersions() + require.Len(t, versions, 0) +} + func assertConstraintExist(t *testing.T, p *Schema, tableName schema.TableName, constraintName string) { require.True(t, p.ConstraintExist(tableName, constraintName)) } diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 2fcf00c..9d8522b 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -1,7 +1,7 @@ package schema +// Schema is the interface that need to be implemented to support migrations. type Schema interface { - TableExist(tableName TableName) bool AddVersion(version string) RemoveVersion(version string) FindAppliedVersions() []string diff --git a/pkg/templates/init_create_table.go.tmpl b/pkg/templates/init_create_table.go.tmpl index 842ded7..cb60a29 100644 --- a/pkg/templates/init_create_table.go.tmpl +++ b/pkg/templates/init_create_table.go.tmpl @@ -1,3 +1,3 @@ s.CreateTable("{{ .Name }}", func(s *pg.PostgresTableDef) { - s.String("id") - }, schema.TableOptions{ IfNotExists: true }) + s.String("id") +}, schema.TableOptions{ IfNotExists: true }) \ No newline at end of file diff --git a/pkg/templates/init_create_table_base.go.tmpl b/pkg/templates/init_create_table_base.go.tmpl new file mode 100644 index 0000000..e64ef62 --- /dev/null +++ b/pkg/templates/init_create_table_base.go.tmpl @@ -0,0 +1,5 @@ +query := `CREATE TABLE IF NOT EXISTS {{ .Name }} ( "id" VARCHAR(255) NOT NULL PRIMARY KEY )` +_, err := s.TX.ExecContext(s.Context.Context, query) +if err != nil { + s.Context.RaiseError(fmt.Errorf("unable to create table schema version: %w", err)) +} \ No newline at end of file diff --git a/pkg/templates/main.go.tmpl b/pkg/templates/main.go.tmpl index 0b70328..1a3c44b 100644 --- a/pkg/templates/main.go.tmpl +++ b/pkg/templates/main.go.tmpl @@ -14,7 +14,7 @@ import ( func main() { opts, arg := entrypoint.AmigoContextFromFlags() - db, err := sql.Open("postgres", opts.DSN) + db, err := sql.Open("{{ .DriverName }}", opts.DSN) if err != nil { logger.Error(events.MessageEvent{Message: err.Error()}) os.Exit(1) diff --git a/pkg/templates/migration_change.go.tmpl b/pkg/templates/migration.go.tmpl similarity index 55% rename from pkg/templates/migration_change.go.tmpl rename to pkg/templates/migration.go.tmpl index eb920d2..58a1d34 100644 --- a/pkg/templates/migration_change.go.tmpl +++ b/pkg/templates/migration.go.tmpl @@ -2,23 +2,26 @@ package {{ .Package }} import ( - {{- if .UseSchemaImport }}"github.com/alexisvisco/amigo/pkg/schema"{{ end }} - "{{ .PackageDriverPath }}" - "time" +{{- range .Imports }} + "{{ . }}" +{{- end }} ) type {{ .StructName }} struct {} - -{{- if not (eq .InUp "") }} - +{{ if eq .Type "change" }} func (m {{ .StructName }}) Change(s *{{ .PackageDriverName }}.Schema) { - {{ .InUp -}} + {{ indent 1 .InChange }} +} +{{ end -}} +{{ if eq .Type "classic" }} +func (m {{ .StructName }}) Up(s *{{ .PackageDriverName }}.Schema) { + {{ indent 1 .InUp }} } -{{- else }} - -func (m {{ .StructName }}) Change(s *{{ .PackageDriverName }}.Schema) {} -{{- end }} +func (m {{ .StructName }}) Down(s *{{ .PackageDriverName }}.Schema) { + {{ indent 1 .InDown }} +} +{{ end }} func (m {{ .StructName }}) Name() string { return "{{ .Name }}" } @@ -27,4 +30,3 @@ func (m {{ .StructName }}) Date() time.Time { t, _ := time.Parse(time.RFC3339, "{{ .CreatedAt }}") return t } - diff --git a/pkg/templates/migration_classic.go.tmpl b/pkg/templates/migration_classic.go.tmpl deleted file mode 100644 index 2baddb3..0000000 --- a/pkg/templates/migration_classic.go.tmpl +++ /dev/null @@ -1,38 +0,0 @@ -package migrations - -import ( - {{ if .UseSchemaImport }}"github.com/alexisvisco/amigo/pkg/schema"{{- end }} - "{{ .PackageDriverPath }}" - "time" -) - -type {{ .StructName }} struct {} - -{{- if not (eq .InUp "") }} - -func (m {{ .StructName }}) Up(s *{{ .PackageDriverName }}.Schema) { - {{ .InUp -}} -} -{{- else }} - -func (m {{ .StructName }}) Up(s *{{ .PackageDriverName }}.Schema) {} -{{- end }} - -{{- if not (eq .InDown "") }} - -func (m {{ .StructName }}) Down(s *{{ .PackageDriverName }}.Schema) { - {{ .InDown -}} -} -{{- else }} - -func (m {{ .StructName }}) Down(s *{{ .PackageDriverName }}.Schema) {} -{{- end }} - -func (m {{ .StructName }}) Name() string { - return "{{ .Name }}" -} - -func (m {{ .StructName }}) Date() time.Time { - t, _ := time.Parse(time.RFC3339, "{{ .CreatedAt }}") - return t -} diff --git a/pkg/templates/types.go b/pkg/templates/types.go index 025ee71..caf1c5c 100644 --- a/pkg/templates/types.go +++ b/pkg/templates/types.go @@ -1,5 +1,7 @@ package templates +import "github.com/alexisvisco/amigo/pkg/types" + type ( MigrationsData struct { Package string @@ -11,8 +13,13 @@ type ( StructName string Name string - InUp string - InDown string + Type types.MigrationFileType + + Imports []string + + InChange string + InUp string + InDown string CreatedAt string // RFC3339 @@ -20,6 +27,7 @@ type ( PackageDriverPath string UseSchemaImport bool + UseFmtImport bool } CreateTableData struct { diff --git a/pkg/templates/util.go b/pkg/templates/util.go index 923a971..20aef38 100644 --- a/pkg/templates/util.go +++ b/pkg/templates/util.go @@ -3,22 +3,23 @@ package templates import ( "bytes" _ "embed" - "github.com/alexisvisco/amigo/pkg/types" + "sort" + "strings" "text/template" ) //go:embed migrations.go.tmpl var migrationsList string -//go:embed migration_change.go.tmpl -var migrationChange string - -//go:embed migration_classic.go.tmpl -var migrationClassic string +//go:embed migration.go.tmpl +var migration string //go:embed init_create_table.go.tmpl var initCreateTable string +//go:embed init_create_table_base.go.tmpl +var initCreateTableBase string + //go:embed main.go.tmpl var main string @@ -36,18 +37,22 @@ func GetMigrationsTemplate(t MigrationsData) (string, error) { return tpl.String(), nil } -func GetMigrationChangeTemplate(direction types.MigrationFileType, t MigrationData) (string, error) { - var tpl string - switch direction { - case types.MigrationFileTypeClassic: - tpl = migrationClassic - case types.MigrationFileTypeChange: - tpl = migrationChange - default: - return "", nil +func GetMigrationTemplate(t MigrationData) (string, error) { + + t.Imports = append(t.Imports, "time") + t.Imports = append(t.Imports, "github.com/alexisvisco/amigo/pkg/schema/"+t.PackageDriverName) + + if t.UseSchemaImport { + t.Imports = append(t.Imports, "github.com/alexisvisco/amigo/pkg/schema") + } + + if t.UseFmtImport { + t.Imports = append(t.Imports, "fmt") } - parse, err := template.New("migration").Parse(tpl) + sort.Strings(t.Imports) + + parse, err := template.New("migration").Funcs(funcMap).Parse(migration) if err != nil { return "", err } @@ -60,8 +65,13 @@ func GetMigrationChangeTemplate(direction types.MigrationFileType, t MigrationDa return buf.String(), nil } -func GetInitCreateTableTemplate(t CreateTableData) (string, error) { - parse, err := template.New("initCreateTable").Parse(initCreateTable) +func GetInitCreateTableTemplate(t CreateTableData, base bool) (string, error) { + + tmpl := initCreateTable + if base { + tmpl = initCreateTableBase + } + parse, err := template.New("initCreateTable").Parse(tmpl) if err != nil { return "", err } @@ -75,7 +85,7 @@ func GetInitCreateTableTemplate(t CreateTableData) (string, error) { } func GetMainTemplate(t MainData) (string, error) { - parse, err := template.New("main").Parse(main) + parse, err := template.New("main").Funcs(funcMap).Parse(main) if err != nil { return "", err } @@ -87,3 +97,10 @@ func GetMainTemplate(t MainData) (string, error) { return tpl.String(), nil } + +var funcMap = template.FuncMap{ + // indent the string with n tabs + "indent": func(n int, s string) string { + return strings.ReplaceAll(s, "\n", "\n"+strings.Repeat("\t", n)) + }, +} diff --git a/pkg/types/types.go b/pkg/types/types.go index 5d07517..924caf7 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -51,18 +51,18 @@ func (d Driver) PackageSchemaPath() string { switch d { case DriverPostgres: return "github.com/alexisvisco/amigo/pkg/schema/pg" + default: + return "github.com/alexisvisco/amigo/pkg/schema/base" } - - return "" } func (d Driver) PackagePath() string { switch d { case DriverPostgres: return "github.com/jackc/pgx/v5/stdlib" + default: + return "your_driver_here" } - - return "" } func (d Driver) PackageName() string { @@ -74,20 +74,10 @@ func (d Driver) String() string { case DriverPostgres: return "pgx" default: - return "" + return "your_driver_here" } } -func (d Driver) IsValid() bool { - for _, v := range DriverValues { - if v == d { - return true - } - } - - return false -} - type MigrationFileType string const ( diff --git a/pkg/utils/error.go b/pkg/utils/error.go new file mode 100644 index 0000000..f2d193a --- /dev/null +++ b/pkg/utils/error.go @@ -0,0 +1,31 @@ +package utils + +import "fmt" + +func PanicToError(fn func()) (err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok { + err = e + } else { + err = fmt.Errorf("%v", r) + } + } + }() + fn() + return +} + +func PanicToError1[Out any](fn func() Out) (out Out, err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok { + err = e + } else { + err = fmt.Errorf("%v", r) + } + } + }() + out = fn() + return +}