From 70b0bb76e6035689ecf8c3e8cc155bd70654da31 Mon Sep 17 00:00:00 2001 From: Pavlo Golub Date: Wed, 22 May 2024 14:18:04 +0200 Subject: [PATCH] [+] bump to Go v1.22 and use extracted `pgx-migrator` (#653) --- go.mod | 22 +- go.sum | 46 ++-- internal/migrator/migrator.go | 218 ----------------- internal/migrator/migrator_test.go | 347 ---------------------------- internal/pgengine/migration.go | 2 +- internal/pgengine/migration_test.go | 2 +- 6 files changed, 34 insertions(+), 603 deletions(-) delete mode 100644 internal/migrator/migrator.go delete mode 100644 internal/migrator/migrator_test.go diff --git a/go.mod b/go.mod index 8e241e92..4fa4dde9 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,19 @@ module github.com/cybertec-postgresql/pg_timetable -go 1.21 +go 1.22.0 require ( github.com/cavaliercoder/grab v2.0.0+incompatible - github.com/jackc/pgx/v5 v5.5.4 + github.com/cybertec-postgresql/pgx-migrator v1.0.0 + github.com/jackc/pgx/v5 v5.5.5 github.com/jessevdk/go-flags v1.5.0 github.com/ory/mail/v3 v3.0.1-0.20210418065910-7f033ddea8dc - github.com/pashagolub/pgxmock/v3 v3.3.0 + github.com/pashagolub/pgxmock/v3 v3.4.0 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 github.com/sethvargo/go-retry v0.2.4 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -25,7 +26,7 @@ require ( github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -35,13 +36,12 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 29c7dd49..b4af2b80 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/cavaliercoder/grab v2.0.0+incompatible h1:wZHbBQx56+Yxjx2TCGDcenhh3cJn7cCLMfkEPmySTSE= github.com/cavaliercoder/grab v2.0.0+incompatible/go.mod h1:tTBkfNqSBfuMmMBFaO2phgyhdYhiZQ/+iXCZDzcDsMI= +github.com/cybertec-postgresql/pgx-migrator v1.0.0 h1:RCRMWhOqA5V4TaLYNEEqRAO6ou+N0IHvmNjOSSS2cfM= +github.com/cybertec-postgresql/pgx-migrator v1.0.0/go.mod h1:Wop9kQYAmVibyprt1R2/5idAeqgyUmN9TN5y0kp8KlQ= 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= @@ -14,14 +16,10 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s= -github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8= -github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= 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/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= @@ -36,12 +34,12 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/ory/mail/v3 v3.0.1-0.20210418065910-7f033ddea8dc h1:BU12v9x5hvONtYU2R2LnlkxmWSsjzco046NzJLcWMHg= github.com/ory/mail/v3 v3.0.1-0.20210418065910-7f033ddea8dc/go.mod h1:vAPEMm1zIQKGmM9hcZTSlOU/CDVCXHGOw6SFxPlSoHw= -github.com/pashagolub/pgxmock/v3 v3.3.0 h1:vMDQiBs74JEIYT/DeWNtUDrcfKCsgMmKd+ecQs1WsV4= -github.com/pashagolub/pgxmock/v3 v3.3.0/go.mod h1:ywwoE43oyD7aqpA3Jh5tvZ8h00P7RRiygA23aXmNpWU= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pashagolub/pgxmock/v3 v3.4.0 h1:87VMr2q7m2+6VzXo4Tsp9kMklGlj6mMN19Hp/bp2Rwo= +github.com/pashagolub/pgxmock/v3 v3.4.0/go.mod h1:FvCl7xqPbLLI3XohihJ1NzXnikjM3q/NWSixg4t9hrU= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -72,43 +70,41 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV 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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -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/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -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/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/migrator/migrator.go b/internal/migrator/migrator.go deleted file mode 100644 index 95bc2160..00000000 --- a/internal/migrator/migrator.go +++ /dev/null @@ -1,218 +0,0 @@ -package migrator - -import ( - "context" - "errors" - "fmt" - - pgx "github.com/jackc/pgx/v5" - pgconn "github.com/jackc/pgx/v5/pgconn" -) - -// PgxIface is interface for database connection or transaction -type PgxIface interface { - Begin(ctx context.Context) (pgx.Tx, error) - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row - Query(ctx context.Context, query string, args ...interface{}) (pgx.Rows, error) - Ping(ctx context.Context) error -} - -const defaultTableName = "migrations" - -// Migrator is the migrator implementation -type Migrator struct { - TableName string - migrations []interface{} - onNotice func(string) -} - -// Option sets options such migrations or table name. -type Option func(*Migrator) - -// TableName creates an option to allow overriding the default table name -func TableName(tableName string) Option { - return func(m *Migrator) { - m.TableName = tableName - } -} - -// SetNotice overrides the default standard output function -func SetNotice(noticeFunc func(string)) Option { - return func(m *Migrator) { - m.onNotice = noticeFunc - } -} - -// Migrations creates an option with provided migrations -func Migrations(migrations ...interface{}) Option { - return func(m *Migrator) { - m.migrations = migrations - } -} - -// New creates a new migrator instance -func New(opts ...Option) (*Migrator, error) { - m := &Migrator{ - TableName: defaultTableName, - onNotice: func(msg string) { - fmt.Println(msg) - }, - } - for _, opt := range opts { - opt(m) - } - - if len(m.migrations) == 0 { - return nil, errors.New("Migrations must be provided") - } - - for _, m := range m.migrations { - switch m.(type) { - case *Migration: - case *MigrationNoTx: - default: - return nil, errors.New("Invalid migration type") - } - } - - return m, nil -} - -// Migrate applies all available migrations -func (m *Migrator) Migrate(ctx context.Context, db PgxIface) error { - // create migrations table if doesn't exist - _, err := db.Exec(ctx, fmt.Sprintf(` - CREATE TABLE IF NOT EXISTS %s ( - id INT8 NOT NULL, - version TEXT NOT NULL, - PRIMARY KEY (id) - ); - `, m.TableName)) - if err != nil { - return err - } - - pm, count, err := m.Pending(ctx, db) - if err != nil { - return err - } - - // plan migrations - for idx, migration := range pm { - insertVersion := fmt.Sprintf("INSERT INTO %s (id, version) VALUES (%d, '%s')", m.TableName, idx+count, migration.(fmt.Stringer).String()) - switch mm := migration.(type) { - case *Migration: - if err := migrate(ctx, db, insertVersion, mm, m.onNotice); err != nil { - return fmt.Errorf("Error while running migrations: %w", err) - } - case *MigrationNoTx: - if err := migrateNoTx(ctx, db, insertVersion, mm, m.onNotice); err != nil { - return fmt.Errorf("Error while running migrations: %w", err) - } - } - } - - return nil -} - -// Pending returns all pending (not yet applied) migrations and count of migration applied -func (m *Migrator) Pending(ctx context.Context, db PgxIface) ([]interface{}, int, error) { - count, err := countApplied(ctx, db, m.TableName) - if err != nil { - return nil, 0, err - } - if count > len(m.migrations) { - count = len(m.migrations) - } - return m.migrations[count:len(m.migrations)], count, nil -} - -// NeedUpgrade returns True if database need to be updated with migrations -func (m *Migrator) NeedUpgrade(ctx context.Context, db PgxIface) (bool, error) { - exists, err := tableExists(ctx, db, m.TableName) - if !exists { - return true, err - } - mm, _, err := m.Pending(ctx, db) - return len(mm) > 0, err -} - -func countApplied(ctx context.Context, db PgxIface, tableName string) (int, error) { - // count applied migrations - var count int - err := db.QueryRow(ctx, fmt.Sprintf("SELECT count(*) FROM %s", tableName)).Scan(&count) - if err != nil { - return 0, err - } - return count, nil -} - -func tableExists(ctx context.Context, db PgxIface, tableName string) (bool, error) { - var exists bool - err := db.QueryRow(ctx, "SELECT to_regclass($1) IS NOT NULL", tableName).Scan(&exists) - if err != nil { - return false, err - } - return exists, nil -} - -// Migration represents a single migration -type Migration struct { - Name string - Func func(context.Context, pgx.Tx) error -} - -// String returns a string representation of the migration -func (m *Migration) String() string { - return m.Name -} - -// MigrationNoTx represents a single not transactional migration -type MigrationNoTx struct { - Name string - Func func(context.Context, PgxIface) error -} - -func (m *MigrationNoTx) String() string { - return m.Name -} - -func migrate(ctx context.Context, db PgxIface, insertVersion string, migration *Migration, notice func(string)) error { - tx, err := db.Begin(ctx) - if err != nil { - return err - } - defer func() { - if err != nil { - if errRb := tx.Rollback(ctx); errRb != nil { - err = fmt.Errorf("Error rolling back: %s\n%s", errRb, err) - } - return - } - err = tx.Commit(ctx) - }() - notice(fmt.Sprintf("Applying migration named '%s'...", migration.Name)) - if err = migration.Func(ctx, tx); err != nil { - return fmt.Errorf("Error executing golang migration: %w", err) - } - if _, err = tx.Exec(ctx, insertVersion); err != nil { - return fmt.Errorf("Error updating migration versions: %w", err) - } - notice(fmt.Sprintf("Applied migration named '%s'", migration.Name)) - - return err -} - -func migrateNoTx(ctx context.Context, db PgxIface, insertVersion string, migration *MigrationNoTx, notice func(string)) error { - notice(fmt.Sprintf("Applying no tx migration named '%s'...", migration.Name)) - if err := migration.Func(ctx, db); err != nil { - return fmt.Errorf("Error executing golang migration: %w", err) - } - if _, err := db.Exec(ctx, insertVersion); err != nil { - return fmt.Errorf("Error updating migration versions: %w", err) - } - notice(fmt.Sprintf("Applied no tx migration named '%s'...", migration.Name)) - - return nil -} diff --git a/internal/migrator/migrator_test.go b/internal/migrator/migrator_test.go deleted file mode 100644 index 7bd5c810..00000000 --- a/internal/migrator/migrator_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package migrator_test - -import ( - "context" - "errors" - "fmt" - "math" - "strings" - "testing" - - "github.com/cybertec-postgresql/pg_timetable/internal/config" - "github.com/cybertec-postgresql/pg_timetable/internal/log" - "github.com/cybertec-postgresql/pg_timetable/internal/migrator" - "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" - pgx "github.com/jackc/pgx/v5" - "github.com/pashagolub/pgxmock/v3" - "github.com/stretchr/testify/assert" -) - -var cmdOpts = config.NewCmdOptions("-c", "migrator_unit_test", "--password=somestrong") - -var migrations = []interface{}{ - &migrator.Migration{ - Name: "Using tx, encapsulate two queries", - Func: func(ctx context.Context, tx pgx.Tx) error { - if _, err := tx.Exec(ctx, "CREATE TABLE foo (id INT PRIMARY KEY)"); err != nil { - return err - } - if _, err := tx.Exec(ctx, "INSERT INTO foo (id) VALUES (1)"); err != nil { - return err - } - return nil - }, - }, - &migrator.MigrationNoTx{ - Name: "Using db, execute one query", - Func: func(ctx context.Context, db migrator.PgxIface) error { - if _, err := db.Exec(ctx, "INSERT INTO foo (id) VALUES (2)"); err != nil { - return err - } - return nil - }, - }, -} - -func migrateTest() error { - migrator, err := migrator.New(migrator.Migrations(migrations...)) - if err != nil { - return err - } - - // Migrate up - ctx := context.Background() - pge, err := pgengine.New(ctx, *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"})) - if err != nil { - return err - } - defer pge.Finalize() - _, _ = pge.ConfigDb.Exec(ctx, "DROP TABLE IF EXISTS foo, bar, baz, migration") - db, err := pge.ConfigDb.Acquire(ctx) - if err != nil { - return err - } - defer db.Release() - return migrator.Migrate(ctx, db.Conn()) -} - -func mustMigrator(migrator *migrator.Migrator, err error) *migrator.Migrator { - if err != nil { - panic(err) - } - return migrator -} - -func TestPostgres(t *testing.T) { - if err := migrateTest(); err != nil { - t.Fatal(err) - } -} - -func TestBadMigrations(t *testing.T) { - pge, err := pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"})) - if err != nil { - t.Fatal(err) - } - defer pge.Finalize() - - ctx := context.Background() - db, err := pge.ConfigDb.Acquire(ctx) - if err != nil { - t.Fatal(err) - } - defer db.Release() - - var migrators = []struct { - name string - input *migrator.Migrator - want error - }{ - { - name: "bad tx migration", - input: mustMigrator(migrator.New(migrator.Migrations(&migrator.Migration{ - Name: "bad tx migration", - Func: func(ctx context.Context, tx pgx.Tx) error { - if _, err := tx.Exec(ctx, "FAIL FAST"); err != nil { - return err - } - return nil - }, - }))), - }, - { - name: "bad db migration", - input: mustMigrator(migrator.New(migrator.Migrations(&migrator.MigrationNoTx{ - Name: "bad db migration", - Func: func(ctx context.Context, db migrator.PgxIface) error { - if _, err := db.Exec(ctx, "FAIL FAST"); err != nil { - return err - } - return nil - }, - }))), - }, - } - - for _, tt := range migrators { - _, err := db.Exec(ctx, fmt.Sprintf("DROP TABLE IF EXISTS %s", tt.input.TableName)) - if err != nil { - t.Fatal(err) - } - t.Run(tt.name, func(t *testing.T) { - err := tt.input.Migrate(context.Background(), db.Conn()) - if err != nil && !strings.Contains(err.Error(), "syntax error") { - t.Fatal(err) - } - }) - } -} - -func TestPending(t *testing.T) { - pge, err := pgengine.New(context.Background(), *cmdOpts, log.Init(config.LoggingOpts{LogLevel: "error"})) - if err != nil { - t.Fatal(err) - } - defer pge.Finalize() - ctx := context.Background() - db, err := pge.ConfigDb.Acquire(ctx) - if err != nil { - t.Fatal(err) - } - defer db.Release() - migrator := mustMigrator(migrator.New(migrator.Migrations( - &migrator.Migration{ - Name: "Using tx, create baz table", - Func: func(ctx context.Context, tx pgx.Tx) error { - if _, err := tx.Exec(ctx, "CREATE TABLE baz (id INT PRIMARY KEY)"); err != nil { - return err - } - return nil - }, - }, - ))) - pending, _, err := migrator.Pending(context.Background(), db.Conn()) - if err != nil { - t.Fatal(err) - } - if len(pending) != 1 { - t.Fatalf("pending migrations should be 1, got %d", len(pending)) - } -} - -func TestMigratorConstructor(t *testing.T) { - _, err := migrator.New() //migrator.Migrations(migrations...) - assert.Error(t, err, "Should throw error when migrations are empty") - - _, err = migrator.New(migrator.Migrations(struct{ Foo string }{Foo: "bar"})) - assert.Error(t, err, "Should throw error for unknown migration type") -} - -func TestTableExists(t *testing.T) { - mock, err := pgxmock.NewPool() - assert.NoError(t, err) - defer mock.Close() - - m, err := migrator.New(migrator.Migrations(migrations...)) - assert.NoError(t, err) - assert.NotNil(t, m) - - sqlresults := []struct { - testname string - tableexists bool - appliedcount int - needupgrade bool - tableerr error - counterr error - }{ - { - testname: "table exists and no migrations applied", - tableexists: true, - appliedcount: 0, - needupgrade: true, - tableerr: nil, - counterr: nil, - }, - { - testname: "table exists and a lot of migrations applied", - tableexists: true, - appliedcount: math.MaxInt32, - needupgrade: false, - tableerr: nil, - counterr: nil, - }, - { - testname: "error occurred during count query", - tableexists: true, - appliedcount: 0, - needupgrade: false, - tableerr: nil, - counterr: errors.New("internal error"), - }, - { - testname: "error occurred during table exists query", - tableexists: false, - appliedcount: 0, - needupgrade: true, - tableerr: errors.New("internal error"), - counterr: nil, - }, - } - var expectederr error - for _, res := range sqlresults { - if q := mock.ExpectQuery("SELECT to_regclass").WithArgs(pgxmock.AnyArg()); res.tableerr != nil { - q.WillReturnError(res.tableerr) - expectederr = res.tableerr - } else { - q.WillReturnRows(pgxmock.NewRows([]string{"to_regclass"}).AddRow(res.tableexists)) - } - if q := mock.ExpectQuery("SELECT count"); res.counterr != nil { - q.WillReturnError(res.counterr) - expectederr = res.counterr - } else { - q.WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(res.appliedcount)) - } - need, err := m.NeedUpgrade(context.Background(), mock) - assert.Equal(t, expectederr, err, "NeedUpgrade test failed: ", res.testname) - assert.Equal(t, res.needupgrade, need, "NeedUpgrade incorrect return: ", res.testname) - } -} - -func TestMigrateExists(t *testing.T) { - mock, err := pgxmock.NewPool() - assert.NoError(t, err) - defer mock.Close() - - m, err := migrator.New(migrator.Migrations(migrations...)) - assert.NoError(t, err) - assert.NotNil(t, m) - - expectederr := errors.New("internal error") - - mock.ExpectExec("CREATE TABLE").WillReturnResult(pgxmock.NewResult("DDL", 0)) - mock.ExpectQuery("SELECT count").WillReturnError(expectederr) - - err = m.Migrate(context.Background(), mock) - assert.Equal(t, expectederr, err, "Migrate test failed: ", err) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("there were unfulfilled expectations: %s", err) - } -} - -func TestMigrateNoTxError(t *testing.T) { - mock, err := pgxmock.NewPool() - assert.NoError(t, err) - defer mock.Close() - - m, err := migrator.New(migrator.Migrations(&migrator.MigrationNoTx{Func: func(context.Context, migrator.PgxIface) error { return nil }})) - assert.NoError(t, err) - assert.NotNil(t, m) - - expectederr := errors.New("internal error") - - mock.ExpectExec("CREATE TABLE").WillReturnResult(pgxmock.NewResult("DDL", 0)) - mock.ExpectQuery("SELECT count").WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec("INSERT").WillReturnError(expectederr) - - err = m.Migrate(context.Background(), mock) - for errors.Unwrap(err) != nil { - err = errors.Unwrap(err) - } - assert.Equal(t, expectederr, err, "MigrateNoTxError test failed: ", err) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("there were unfulfilled expectations: %s", err) - } -} - -func TestMigrateTxError(t *testing.T) { - mock, err := pgxmock.NewPool() - assert.NoError(t, err) - defer mock.Close() - - m, err := migrator.New(migrator.Migrations(&migrator.Migration{Func: func(context.Context, pgx.Tx) error { return nil }})) - assert.NoError(t, err) - assert.NotNil(t, m) - - expectederr := errors.New("create table error") - mock.ExpectExec("CREATE TABLE").WillReturnError(expectederr) - err = m.Migrate(context.Background(), mock) - assert.Equal(t, expectederr, err, "MigrateTxError test failed: ", err) - - expectederr = errors.New("internal tx error") - mock.ExpectExec("CREATE TABLE").WillReturnResult(pgxmock.NewResult("DDL", 0)) - mock.ExpectQuery("SELECT count").WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectBegin().WillReturnError(expectederr) - err = m.Migrate(context.Background(), mock) - for errors.Unwrap(err) != nil { - err = errors.Unwrap(err) - } - assert.Equal(t, expectederr, err, "MigrateTxError test failed: ", err) - - expectederr = errors.New("internal tx error") - mock.ExpectExec("CREATE TABLE").WillReturnResult(pgxmock.NewResult("DDL", 0)) - mock.ExpectQuery("SELECT count").WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectBegin() - mock.ExpectExec("INSERT").WillReturnError(expectederr) - err = m.Migrate(context.Background(), mock) - for errors.Unwrap(err) != nil { - err = errors.Unwrap(err) - } - assert.Equal(t, expectederr, err, "MigrateTxError test failed: ", err) - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("there were unfulfilled expectations: %s", err) - } -} - -func TestMigratorOptions(t *testing.T) { - O := migrator.TableName("foo") - m := &migrator.Migrator{} - O(m) - assert.Equal(t, "foo", m.TableName) - - f := func(string) {} - O = migrator.SetNotice(f) - O(m) -} diff --git a/internal/pgengine/migration.go b/internal/pgengine/migration.go index b1852d61..bb382b85 100644 --- a/internal/pgengine/migration.go +++ b/internal/pgengine/migration.go @@ -5,7 +5,7 @@ import ( "embed" "github.com/cybertec-postgresql/pg_timetable/internal/log" - "github.com/cybertec-postgresql/pg_timetable/internal/migrator" + migrator "github.com/cybertec-postgresql/pgx-migrator" pgx "github.com/jackc/pgx/v5" ) diff --git a/internal/pgengine/migration_test.go b/internal/pgengine/migration_test.go index ddb8a5a3..dab01126 100644 --- a/internal/pgengine/migration_test.go +++ b/internal/pgengine/migration_test.go @@ -5,8 +5,8 @@ import ( _ "embed" "testing" - "github.com/cybertec-postgresql/pg_timetable/internal/migrator" "github.com/cybertec-postgresql/pg_timetable/internal/pgengine" + migrator "github.com/cybertec-postgresql/pgx-migrator" "github.com/stretchr/testify/assert" )