Skip to content

Commit

Permalink
feat: add rename table and change column type
Browse files Browse the repository at this point in the history
  • Loading branch information
alexisvisco committed May 17, 2024
1 parent bba5080 commit db2b932
Show file tree
Hide file tree
Showing 20 changed files with 533 additions and 98 deletions.
25 changes: 25 additions & 0 deletions docs/docs/04-api/01-migrating-in-go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Migrating in go

Usually you will need to run migration in your Go application, to do so you can use the `amigo` package.

```go
package main

import (
"database/sql"
migrations "package/where/your/migrations/are"
"github.com/alexisvisco/amigo/pkg/amigo"
_ "github.com/lib/pq"
)


func main() {
db, _ := sql.Open("postgres", "postgres://user:password@localhost:5432/dbname?sslmode=disable")
ok, err := amigo.RunPostgresMigrations(&amigo.RunMigrationOptions{
Connection: db,
Migrations: migrations.Migrations,
})
}
```

You can specify all the options the cli can take in the `RunMigrationOptions` struct (steps, version, dryrun ...)
2 changes: 2 additions & 0 deletions docs/docs/04-api/01-postgres/01-constructive.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ They are the operations that create, alter, or drop tables, columns, indexes, co

- [AddColumn(tableName schema.TableName, columnName string, columnType schema.ColumnType, opts ...schema.ColumnOptions)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.AddColumn)

- [AddTimestamps(tableName schema.TableName](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.AddTimestamps)

- [AddColumnComment(tableName schema.TableName, columnName string, comment *string, opts ...schema.ColumnCommentOptions)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.AddColumnComment)

- [AddCheckConstraint(tableName schema.TableName, constraintName string, expression string, opts ...schema.CheckConstraintOptions)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.AddCheckConstraint)
Expand Down
9 changes: 9 additions & 0 deletions docs/docs/04-api/01-postgres/04-transformative.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Transformative operations

They are the operations that change the data in the database.

- [RenameColumn(tableName schema.TableName, oldColumnName, newColumnName string)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.RenameColumn)

- [RenameTable(tableName schema.TableName, newTableName string)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.RenameTable)

- [ChangeColumnType(tableName schema.TableName, columnName string, columnType schema.ColumnType, opts ...schema.ChangeColumnTypeOptions)](https://pkg.go.dev/github.com/alexisvisco/amigo/pkg/schema/pg#Schema.ChangeColumnType)
File renamed without changes.
51 changes: 51 additions & 0 deletions docs/docs/04-api/02-return-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Return error

As you can see in a migration file the functions Up, Down or Change cannot return an error.
If you want to raise an error you can use the `RaiseError` function from the context.

```go
package migrations

import (
"github.com/alexisvisco/amigo/pkg/schema/pg"
"...../repositories/userrepo"
"time"
)

type Migration20240517135429Droptable struct{}

func (m Migration20240517135429Droptable) Change(s *pg.Schema) {
s.CreateTable("test", func(def *pg.PostgresTableDef) {
def.String("name")
def.JSON("data")
})

_, err := userrepo.New(s.DB).GetUser(5)
if err != nil {
s.Context.RaiseError(fmt.Errorf("error: %w", err))
}
}

func (m Migration20240517135429Droptable) Name() string {
return "droptable"
}

func (m Migration20240517135429Droptable) Date() time.Time {
t, _ := time.Parse(time.RFC3339, "2024-05-17T15:54:29+02:00")
return t
}

```

In this example, if the `GetUser` function returns an error, the migration will fail and the error will be displayed in the logs.


The only way to not crash the migration when a RaiseError is called is to use the `--continue-on-error` flag.

And the only way to crash when this flag is used is to use a `schema.ForceStopError` error.

```go
s.Context.RaiseError(schema.NewForceStopError(errors.New("force stop")))
```

This will crash the migration EVEN if the `--continue-on-error` flag is used.
12 changes: 10 additions & 2 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ A Migration In Golang (AMIGO) is a library that allows you to write migrations i
It provides you with all the benefits of Go, including type safety, simplicity, and strong tooling support.
AMIGO is designed to be easy to use and integrate into existing projects.

General documentation: [https://amigo.alexisvis.co](https://amigo.alexisvis.co)
## Why another migration library?

First thing, I don't have anything against SQL migrations file library. I appreciate them but somethimes with SQL files you are limited to do complex migrations that imply your models and business logic.

I just like the way activerecord (rails) migration system and I think it's powerful to combine migration and code.

Some libraries offer Go files migrations but they do not offer APIs to interact with the database schema.

This library offer to you a new way to create migrations in Go with a powerful API to interact with the database schema.

## Features

Expand All @@ -38,7 +46,7 @@ mit migrate # apply the migration

## Example of migration

```templ
```go
package migrations

import (
Expand Down
16 changes: 14 additions & 2 deletions pkg/schema/migrator_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ type MigrationEvents struct {
tablesCreated []TableOptions
versionCreated []string

columnsRenamed []RenameColumnOptions
columnComments []ColumnCommentOptions
columnsRenamed []RenameColumnOptions
tablesRenamed []RenameTableOptions
changeColumnTypes []ChangeColumnTypeOptions
columnComments []ColumnCommentOptions

extensionsDropped []DropExtensionOptions
tablesDropped []DropTableOptions
Expand Down Expand Up @@ -171,3 +173,13 @@ func (m *MigratorContext) AddVersionDeleted(version string) {
m.MigrationEvents.versionDeleted = append(m.MigrationEvents.versionDeleted, version)
logger.Info(events.VersionDeletedEvent{Version: version})
}

func (m *MigratorContext) AddTableRenamed(options RenameTableOptions) {
m.MigrationEvents.tablesRenamed = append(m.MigrationEvents.tablesRenamed, options)
logger.Info(options)
}

func (m *MigratorContext) AddChangeColumnType(options ChangeColumnTypeOptions) {
m.MigrationEvents.changeColumnTypes = append(m.MigrationEvents.changeColumnTypes, options)
logger.Info(options)
}
143 changes: 141 additions & 2 deletions pkg/schema/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,18 @@ func (e DropExtensionOptions) String() string {
return fmt.Sprintf("-- drop_extension(%s)", e.ExtensionName)
}

type ColumnData interface {
SetLimit(int)
GetLimit() int
GetPrecision() int
SetNotNull(bool)
SetPrimaryKey(bool)
SetPrecision(int)
GetType() ColumnType
GetScale() int
IsArray() bool
}

type ColumnOptions struct {
Table TableName
ColumnName string
Expand Down Expand Up @@ -375,14 +387,50 @@ type ColumnOptions struct {
Constraints []ConstraintOption
}

func (c ColumnOptions) EventName() string {
func (c *ColumnOptions) EventName() string {
return "ColumnEvent"
}

func (c ColumnOptions) String() string {
func (c *ColumnOptions) String() string {
return fmt.Sprintf("-- add_column(table: %s, column: %s, type: %s)", c.Table, c.ColumnName, c.ColumnType)
}

func (c *ColumnOptions) SetLimit(limit int) {
c.Limit = limit
}

func (c *ColumnOptions) SetNotNull(notNull bool) {
c.NotNull = notNull
}

func (c *ColumnOptions) SetPrimaryKey(primaryKey bool) {
c.PrimaryKey = primaryKey
}

func (c *ColumnOptions) SetPrecision(precision int) {
c.Precision = precision
}

func (c *ColumnOptions) GetPrecision() int {
return c.Precision
}

func (c *ColumnOptions) GetLimit() int {
return c.Limit
}

func (c *ColumnOptions) GetType() ColumnType {
return c.ColumnType
}

func (c *ColumnOptions) GetScale() int {
return c.Scale
}

func (c *ColumnOptions) IsArray() bool {
return c.Array
}

type DropColumnOptions struct {
Table TableName
ColumnName string
Expand Down Expand Up @@ -441,6 +489,97 @@ func (r RenameColumnOptions) String() string {
return fmt.Sprintf("-- rename_column(%s, old: %s, new: %s)", r.Table, r.OldColumnName, r.NewColumnName)
}

type RenameTableOptions struct {
OldTable TableName
NewTable TableName
}

func (r RenameTableOptions) EventName() string {
return "RenameTableEvent"
}

func (r RenameTableOptions) String() string {
return fmt.Sprintf("-- rename_table(old: %s, new: %s)", r.OldTable, r.NewTable)
}

type ChangeColumnTypeOptions struct {
Table TableName
ColumnName string

// ColumnType is the type of the column.
// Could be a custom one but it's recommended to use the predefined ones for portability.
ColumnType ColumnType

// Limit is a maximum column length. This is the number of characters for a ColumnTypeString column and number of
// bytes for ColumnTypeText, ColumnTypeBinary, ColumnTypeBlob, and ColumnTypeInteger Columns.
Limit int

// Scale is the scale of the column.
// Mainly supported for Specifies the scale for the ColumnTypeDecimal, ColumnTypeNumeric
// The scale is the number of digits to the right of the decimal point in a number.
Scale int

// Precision is the precision of the column.
// Mainly supported for the ColumnTypeDecimal, ColumnTypeNumeric, ColumnTypeDatetime, and ColumnTypeTime
// The precision is the number of significant digits in a number.
Precision int

// Array specifies if the column is an array.
Array bool

// Using is the USING clause for the change column type.
// Postgres only.
Using string

// Reversible will allow the migrator to reverse the operation by creating the column.
// Specify the old column type.
Reversible *ChangeColumnTypeOptions
}

func (c *ChangeColumnTypeOptions) EventName() string {
return "ChangeColumnTypeEvent"
}

func (c *ChangeColumnTypeOptions) String() string {
return fmt.Sprintf("-- change_column_type(table: %s, column: %s, type: %s)", c.Table, c.ColumnName, c.ColumnType)
}

func (c *ChangeColumnTypeOptions) SetLimit(limit int) {
c.Limit = limit
}

func (c *ChangeColumnTypeOptions) SetNotNull(_ bool) {
// Do nothing
}

func (c *ChangeColumnTypeOptions) SetPrecision(precision int) {
c.Precision = precision
}

func (c *ChangeColumnTypeOptions) GetPrecision() int {
return c.Precision
}

func (c *ChangeColumnTypeOptions) SetPrimaryKey(_ bool) {
// Do nothing
}

func (c *ChangeColumnTypeOptions) GetLimit() int {
return c.Limit
}

func (c *ChangeColumnTypeOptions) GetType() ColumnType {
return c.ColumnType
}

func (c *ChangeColumnTypeOptions) GetScale() int {
return c.Scale
}

func (c *ChangeColumnTypeOptions) IsArray() bool {
return c.Array
}

type PrimaryKeyConstraintOptions struct {
Table TableName

Expand Down
18 changes: 9 additions & 9 deletions pkg/schema/pg/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,28 @@ view_exists?, views
*/

type Schema struct {
db schema.DB
DB schema.DB
Context *schema.MigratorContext
*schema.ReversibleMigrationExecutor
}

func NewPostgres(ctx *schema.MigratorContext, db schema.DB) *Schema {
return &Schema{db: db, Context: ctx, ReversibleMigrationExecutor: schema.NewReversibleMigrationExecutor(ctx)}
return &Schema{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{
db: p.db,
DB: p.DB,
Context: &ctx,
ReversibleMigrationExecutor: schema.NewReversibleMigrationExecutor(&ctx),
}
}

func (p *Schema) Exec(query string, args ...interface{}) {
_, err := p.db.ExecContext(p.Context.Context, query, args...)
_, err := p.DB.ExecContext(p.Context.Context, query, args...)
if err != nil {
p.Context.RaiseError(fmt.Errorf("error while executing query: %w", err))
return
Expand Down Expand Up @@ -99,7 +99,7 @@ func (p *Schema) AddExtension(name string, option ...schema.ExtensionOptions) {
"schema": utils.StrFuncPredicate(options.Schema != "", fmt.Sprintf("SCHEMA %s", options.Schema)),
}

_, err := p.db.ExecContext(p.Context.Context, replacer.Replace(sql))
_, err := p.DB.ExecContext(p.Context.Context, replacer.Replace(sql))
if err != nil {
p.Context.RaiseError(fmt.Errorf("error while adding extension: %w", err))
return
Expand Down Expand Up @@ -154,7 +154,7 @@ func (p *Schema) DropExtension(name string, opt ...schema.DropExtensionOptions)
"name": utils.StrFunc(p.toExtension(options.ExtensionName)),
}

_, err := p.db.ExecContext(p.Context.Context, replacer.Replace(sql))
_, err := p.DB.ExecContext(p.Context.Context, replacer.Replace(sql))
if err != nil {
p.Context.RaiseError(fmt.Errorf("error while dropping extension: %w", err))
return
Expand All @@ -172,7 +172,7 @@ func (p *Schema) AddVersion(version string) {
"version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()),
}

_, err := p.db.ExecContext(p.Context.Context, replacer.Replace(sql), version)
_, err := p.DB.ExecContext(p.Context.Context, replacer.Replace(sql), version)
if err != nil {
p.Context.RaiseError(fmt.Errorf("error while adding version: %w", err))
return
Expand All @@ -190,7 +190,7 @@ func (p *Schema) RemoveVersion(version string) {
"version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()),
}

_, err := p.db.ExecContext(p.Context.Context, replacer.Replace(sql), version)
_, err := p.DB.ExecContext(p.Context.Context, replacer.Replace(sql), version)
if err != nil {
p.Context.RaiseError(fmt.Errorf("error while removing version: %w", err))
return
Expand All @@ -207,7 +207,7 @@ func (p *Schema) FindAppliedVersions() []string {
"version_table": utils.StrFunc(p.Context.MigratorOptions.SchemaVersionTable.String()),
}

rows, err := p.db.QueryContext(p.Context.Context, replacer.Replace(sql))
rows, err := p.DB.QueryContext(p.Context.Context, replacer.Replace(sql))
if err != nil {
p.Context.RaiseError(fmt.Errorf("error while fetching applied versions: %w", err))
return nil
Expand Down
Loading

0 comments on commit db2b932

Please sign in to comment.