From 3052aa1fa0f7ddd19dd2ac153d145fe621a65b2d Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 5 Jul 2020 16:48:20 +0700 Subject: [PATCH 01/44] wip dsl --- schema/column.go | 35 +++++++++++++++++ schema/index.go | 7 ++++ schema/migration.go | 81 ++++++++++++++++++++++++++++++++++++++++ schema/migration_test.go | 39 +++++++++++++++++++ schema/option.go | 4 ++ schema/schema.go | 19 ++++++++++ schema/table.go | 77 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 262 insertions(+) create mode 100644 schema/column.go create mode 100644 schema/index.go create mode 100644 schema/migration.go create mode 100644 schema/migration_test.go create mode 100644 schema/option.go create mode 100644 schema/schema.go create mode 100644 schema/table.go diff --git a/schema/column.go b/schema/column.go new file mode 100644 index 00000000..ae02f5e3 --- /dev/null +++ b/schema/column.go @@ -0,0 +1,35 @@ +package schema + +// ColumnType definition. +type ColumnType string + +const ( + // Boolean ColumnType. + Boolean ColumnType = "boolean" + // Integer ColumnType. + Integer ColumnType = "integer" + // BigInt ColumnType. + BigInt ColumnType = "bigint" + // Float ColumnType. + Float ColumnType = "float" + // Decimal ColumnType. + Decimal ColumnType = "decimal" + // String ColumnType. + String ColumnType = "string" + // Text ColumnType. + Text ColumnType = "text" + // Date ColumnType. + Date ColumnType = "date" + // DateTime ColumnType. + DateTime ColumnType = "datetime" + // Time ColumnType. + Time ColumnType = "time" + // Timestamp ColumnType. + Timestamp ColumnType = "timestamp" +) + +// Column definition. +type Column struct { + Name string + Type ColumnType +} diff --git a/schema/index.go b/schema/index.go new file mode 100644 index 00000000..9e5881a0 --- /dev/null +++ b/schema/index.go @@ -0,0 +1,7 @@ +package schema + +// Index definition. +type Index struct { + Columns []string + Name string +} diff --git a/schema/migration.go b/schema/migration.go new file mode 100644 index 00000000..aa58e386 --- /dev/null +++ b/schema/migration.go @@ -0,0 +1,81 @@ +package schema + +import ( + "github.com/Fs02/rel" +) + +// Migrater private interface. +type Migrater interface { + migrate() +} + +// Migrate builder. +type Migrate []Migrater + +// CreateTable with name and its definition. +func (m *Migrate) CreateTable(name string, fn func(t *Table), options ...Option) { + table := Table{Name: name} // TODO: use actual migrater + fn(&table) + *m = append(*m, table) +} + +// RenameTable by name. +func (m *Migrate) RenameTable(name string, newname string) { +} + +// DropTable by name. +func (m *Migrate) DropTable(name string) { +} + +// AddColumn with name and type. +func (m *Migrate) AddColumn(table string, name string, typ ColumnType, options ...Option) { +} + +// RenameColumn by name. +func (m *Migrate) RenameColumn(table string, name string, newname string) { +} + +// DropColumn by name. +func (m *Migrate) DropColumn(table string, name string) { +} + +// AddIndex for columns. +func (m *Migrate) AddIndex(table string, column []string, options ...Option) { +} + +// RenameIndex by name. +func (m *Migrate) RenameIndex(table string, name string, newname string) { +} + +// DropIndex by name. +func (m *Migrate) DropIndex(table string, name string) { +} + +// Exec queries using repo. +// Useful for data migration. +func (m *Migrate) Exec(func(repo rel.Repository) error) { +} + +// Migration definition. +type Migration struct { + Version int + Ups Migrate + Downs Migrate +} + +// Up migration. +func (m *Migration) Up(fn func(migrate *Migrate)) { + fn(&m.Ups) +} + +// Down migration. +func (m *Migration) Down(fn func(migrate *Migrate)) { + fn(&m.Downs) +} + +// NewMigration for schema. +func NewMigration(version int) Migration { + return Migration{ + Version: version, + } +} diff --git a/schema/migration_test.go b/schema/migration_test.go new file mode 100644 index 00000000..fe03c76c --- /dev/null +++ b/schema/migration_test.go @@ -0,0 +1,39 @@ +package schema + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMigration(t *testing.T) { + var ( + migration = NewMigration(20200705164100) + ) + + migration.Up(func(m *Migrate) { + m.CreateTable("products", func(t *Table) { + t.Integer("id") + t.String("name") + t.Text("description") + }) + }) + + migration.Down(func(m *Migrate) { + m.DropTable("products") + }) + + assert.Equal(t, Migration{ + Version: 20200705164100, + Ups: Migrate{ + Table{ + Name: "products", + Columns: []Column{ + {Name: "id", Type: Integer}, + {Name: "name", Type: String}, + {Name: "description", Type: Text}, + }, + }, + }, + }, migration) +} diff --git a/schema/option.go b/schema/option.go new file mode 100644 index 00000000..3c106906 --- /dev/null +++ b/schema/option.go @@ -0,0 +1,4 @@ +package schema + +// Option for schema. +type Option interface{} diff --git a/schema/schema.go b/schema/schema.go new file mode 100644 index 00000000..c22aec24 --- /dev/null +++ b/schema/schema.go @@ -0,0 +1,19 @@ +package schema + +// Schema definition. +type Schema struct { + Version int + Tables []Table +} + +// Table with name and its definition. +func (s Schema) Table(name string, fn func(t *Table), options ...Option) { + table := Table{Name: name} + fn(&table) + s.Tables = append(s.Tables, table) +} + +// New Schema. +func New(version int) Schema { + return Schema{Version: version} +} diff --git a/schema/table.go b/schema/table.go new file mode 100644 index 00000000..b836fd06 --- /dev/null +++ b/schema/table.go @@ -0,0 +1,77 @@ +package schema + +// Table definition. +type Table struct { + Name string + Columns []Column + Indices []Index +} + +// Column defines a column with name and type. +func (t *Table) Column(name string, typ ColumnType, options ...Option) { + column := Column{Name: name, Type: typ} + t.Columns = append(t.Columns, column) +} + +// Index defines an index for columns. +func (t *Table) Index(columns []string, options ...Option) { + index := Index{Columns: columns} + t.Indices = append(t.Indices, index) +} + +// Boolean defines a column with name and Boolean type. +func (t *Table) Boolean(name string, options ...Option) { + t.Column(name, Boolean, options...) +} + +// Integer defines a column with name and Integer type. +func (t *Table) Integer(name string, options ...Option) { + t.Column(name, Integer, options...) +} + +// BigInt defines a column with name and BigInt type. +func (t *Table) BigInt(name string, options ...Option) { + t.Column(name, BigInt, options...) +} + +// Float defines a column with name and Float type. +func (t *Table) Float(name string, options ...Option) { + t.Column(name, Float, options...) +} + +// Decimal defines a column with name and Decimal type. +func (t *Table) Decimal(name string, options ...Option) { + t.Column(name, Decimal, options...) +} + +// String defines a column with name and String type. +func (t *Table) String(name string, options ...Option) { + t.Column(name, String, options...) +} + +// Text defines a column with name and Text type. +func (t *Table) Text(name string, options ...Option) { + t.Column(name, Text, options...) +} + +// Date defines a column with name and Date type. +func (t *Table) Date(name string, options ...Option) { + t.Column(name, Date, options...) +} + +// DateTime defines a column with name and DateTime type. +func (t *Table) DateTime(name string, options ...Option) { + t.Column(name, DateTime, options...) +} + +// Time defines a column with name and Time type. +func (t *Table) Time(name string, options ...Option) { + t.Column(name, Time, options...) +} + +// Timestamp defines a column with name and Timestamp type. +func (t *Table) Timestamp(name string, options ...Option) { + t.Column(name, Timestamp, options...) +} + +func (t Table) migrate() {} From 3d01a42f763d964eaa89780306135d1df298a5bb Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 5 Jul 2020 19:08:07 +0700 Subject: [PATCH 02/44] add more functionality to dsl --- schema/column.go | 51 ++++++++++++++++++++++++-- schema/migrates.go | 77 ++++++++++++++++++++++++++++++++++++++++ schema/migration.go | 64 +++------------------------------ schema/migration_test.go | 68 ++++++++++++++++++++++++++++++++--- schema/table.go | 45 +++++++++++++++++++++-- 5 files changed, 235 insertions(+), 70 deletions(-) create mode 100644 schema/migrates.go diff --git a/schema/column.go b/schema/column.go index ae02f5e3..1ff8983e 100644 --- a/schema/column.go +++ b/schema/column.go @@ -1,5 +1,19 @@ package schema +// ColumnOp definition. +type ColumnOp uint8 + +const ( + // AddColumn operation. + AddColumn ColumnOp = iota + // AlterColumn operation. + AlterColumn + // RenameColumn operation. + RenameColumn + // DropColumn operation. + DropColumn +) + // ColumnType definition. type ColumnType string @@ -30,6 +44,39 @@ const ( // Column definition. type Column struct { - Name string - Type ColumnType + Op ColumnOp + Name string + Type ColumnType + NewName string +} + +func addColumn(name string, typ ColumnType, options ...Option) Column { + return Column{ + Op: AddColumn, + Name: name, + Type: typ, + } +} + +func alterColumn(name string, typ ColumnType, options ...Option) Column { + return Column{ + Op: AlterColumn, + Name: name, + Type: typ, + } +} + +func renameColumn(name string, newName string, options ...Option) Column { + return Column{ + Op: RenameColumn, + Name: name, + NewName: newName, + } +} + +func dropColumn(name string, options ...Option) Column { + return Column{ + Op: DropColumn, + Name: name, + } } diff --git a/schema/migrates.go b/schema/migrates.go new file mode 100644 index 00000000..eb9a7d28 --- /dev/null +++ b/schema/migrates.go @@ -0,0 +1,77 @@ +package schema + +import "github.com/Fs02/rel" + +// Migrator private interface. +type Migrator interface { + migrate() +} + +// Migrates builder. +type Migrates []Migrator + +func (m *Migrates) add(migrator Migrator) { + *m = append(*m, migrator) +} + +// CreateTable with name and its definition. +func (m *Migrates) CreateTable(name string, fn func(t *Table), options ...Option) { + table := Table{Name: name} + fn(&table) + m.add(CreateTable(table)) +} + +// AlterTable with name and its definition. +func (m *Migrates) AlterTable(name string, fn func(t *AlterTable), options ...Option) { + table := AlterTable{Table: Table{Name: name}} + fn(&table) + m.add(table) +} + +// RenameTable by name. +func (m *Migrates) RenameTable(name string, newName string) { + m.add(RenameTable{Name: name, NewName: newName}) +} + +// DropTable by name. +func (m *Migrates) DropTable(name string) { + m.add(DropTable{Name: name}) +} + +// AddColumn with name and type. +func (m *Migrates) AddColumn(table string, name string, typ ColumnType, options ...Option) { + at := AlterTable{Table: Table{Name: name}} + at.Column(name, typ, options...) + m.add(at) +} + +// RenameColumn by name. +func (m *Migrates) RenameColumn(table string, name string, newName string, options ...Option) { + at := AlterTable{Table: Table{Name: name}} + at.RenameColumn(name, newName, options...) + m.add(at) +} + +// DropColumn by name. +func (m *Migrates) DropColumn(table string, name string, options ...Option) { + at := AlterTable{Table: Table{Name: name}} + at.DropColumn(name, options...) + m.add(at) +} + +// AddIndex for columns. +func (m *Migrates) AddIndex(table string, column []string, options ...Option) { +} + +// RenameIndex by name. +func (m *Migrates) RenameIndex(table string, name string, newName string) { +} + +// DropIndex by name. +func (m *Migrates) DropIndex(table string, name string) { +} + +// Exec queries using repo. +// Useful for data migration. +func (m *Migrates) Exec(func(repo rel.Repository) error) { +} diff --git a/schema/migration.go b/schema/migration.go index aa58e386..07cd8ca0 100644 --- a/schema/migration.go +++ b/schema/migration.go @@ -1,75 +1,19 @@ package schema -import ( - "github.com/Fs02/rel" -) - -// Migrater private interface. -type Migrater interface { - migrate() -} - -// Migrate builder. -type Migrate []Migrater - -// CreateTable with name and its definition. -func (m *Migrate) CreateTable(name string, fn func(t *Table), options ...Option) { - table := Table{Name: name} // TODO: use actual migrater - fn(&table) - *m = append(*m, table) -} - -// RenameTable by name. -func (m *Migrate) RenameTable(name string, newname string) { -} - -// DropTable by name. -func (m *Migrate) DropTable(name string) { -} - -// AddColumn with name and type. -func (m *Migrate) AddColumn(table string, name string, typ ColumnType, options ...Option) { -} - -// RenameColumn by name. -func (m *Migrate) RenameColumn(table string, name string, newname string) { -} - -// DropColumn by name. -func (m *Migrate) DropColumn(table string, name string) { -} - -// AddIndex for columns. -func (m *Migrate) AddIndex(table string, column []string, options ...Option) { -} - -// RenameIndex by name. -func (m *Migrate) RenameIndex(table string, name string, newname string) { -} - -// DropIndex by name. -func (m *Migrate) DropIndex(table string, name string) { -} - -// Exec queries using repo. -// Useful for data migration. -func (m *Migrate) Exec(func(repo rel.Repository) error) { -} - // Migration definition. type Migration struct { Version int - Ups Migrate - Downs Migrate + Ups Migrates + Downs Migrates } // Up migration. -func (m *Migration) Up(fn func(migrate *Migrate)) { +func (m *Migration) Up(fn func(migrate *Migrates)) { fn(&m.Ups) } // Down migration. -func (m *Migration) Down(fn func(migrate *Migrate)) { +func (m *Migration) Down(fn func(migrate *Migrates)) { fn(&m.Downs) } diff --git a/schema/migration_test.go b/schema/migration_test.go index fe03c76c..bdd68ecb 100644 --- a/schema/migration_test.go +++ b/schema/migration_test.go @@ -6,27 +6,48 @@ import ( "github.com/stretchr/testify/assert" ) -func TestMigration(t *testing.T) { +func TestMigration_tables(t *testing.T) { var ( migration = NewMigration(20200705164100) ) - migration.Up(func(m *Migrate) { + migration.Up(func(m *Migrates) { m.CreateTable("products", func(t *Table) { t.Integer("id") t.String("name") t.Text("description") }) + + m.AlterTable("users", func(t *AlterTable) { + t.Boolean("verified") + t.RenameColumn("name", "fullname") + }) + + m.RenameTable("trxs", "transactions") + + m.DropTable("logs") }) - migration.Down(func(m *Migrate) { + migration.Down(func(m *Migrates) { + m.CreateTable("logs", func(t *Table) { + t.Integer("id") + t.String("value") + }) + + m.RenameTable("transactions", "trxs") + + m.AlterTable("users", func(t *AlterTable) { + t.DropColumn("verified") + t.RenameColumn("fullname", "name") + }) + m.DropTable("products") }) assert.Equal(t, Migration{ Version: 20200705164100, - Ups: Migrate{ - Table{ + Ups: Migrates{ + CreateTable{ Name: "products", Columns: []Column{ {Name: "id", Type: Integer}, @@ -34,6 +55,43 @@ func TestMigration(t *testing.T) { {Name: "description", Type: Text}, }, }, + AlterTable{ + Table: Table{ + Name: "users", + Columns: []Column{ + {Name: "verified", Type: Boolean}, + {Name: "name", NewName: "fullname", Op: RenameColumn}, + }, + }, + }, + RenameTable{ + Name: "trxs", + NewName: "transactions", + }, + DropTable{Name: "logs"}, + }, + Downs: Migrates{ + CreateTable{ + Name: "logs", + Columns: []Column{ + {Name: "id", Type: Integer}, + {Name: "value", Type: String}, + }, + }, + RenameTable{ + Name: "transactions", + NewName: "trxs", + }, + AlterTable{ + Table: Table{ + Name: "users", + Columns: []Column{ + {Name: "verified", Op: DropColumn}, + {Name: "fullname", NewName: "name", Op: RenameColumn}, + }, + }, + }, + DropTable{Name: "products"}, }, }, migration) } diff --git a/schema/table.go b/schema/table.go index b836fd06..392db84b 100644 --- a/schema/table.go +++ b/schema/table.go @@ -9,8 +9,7 @@ type Table struct { // Column defines a column with name and type. func (t *Table) Column(name string, typ ColumnType, options ...Option) { - column := Column{Name: name, Type: typ} - t.Columns = append(t.Columns, column) + t.Columns = append(t.Columns, addColumn(name, typ, options...)) } // Index defines an index for columns. @@ -74,4 +73,44 @@ func (t *Table) Timestamp(name string, options ...Option) { t.Column(name, Timestamp, options...) } -func (t Table) migrate() {} +// CreateTable Migrator. +type CreateTable Table + +func (ct CreateTable) migrate() {} + +// AlterTable Migrator. +type AlterTable struct { + Table +} + +// RenameColumn to a new name. +func (at *AlterTable) RenameColumn(name string, newName string, options ...Option) { + at.Columns = append(at.Columns, renameColumn(name, newName, options...)) +} + +// AlterColumn from this table. +func (at *AlterTable) AlterColumn(name string, typ ColumnType, options ...Option) { + at.Columns = append(at.Columns, alterColumn(name, typ, options...)) +} + +// DropColumn from this table. +func (at *AlterTable) DropColumn(name string, options ...Option) { + at.Columns = append(at.Columns, dropColumn(name, options...)) +} + +func (at AlterTable) migrate() {} + +// RenameTable Migrator. +type RenameTable struct { + Name string + NewName string +} + +func (rt RenameTable) migrate() {} + +// DropTable Migrator. +type DropTable struct { + Name string +} + +func (dt DropTable) migrate() {} From 7823c28f7ad461e576879c1bfc0bd5ea8217282f Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Tue, 7 Jul 2020 00:04:56 +0700 Subject: [PATCH 03/44] simplify struct and add tests for column --- schema/column.go | 46 ++++++++------ schema/migrates.go | 39 +++++++----- schema/migration_test.go | 132 +++++++++++++++++++++++++++++++++------ schema/table.go | 66 ++++++++++++++------ 4 files changed, 209 insertions(+), 74 deletions(-) diff --git a/schema/column.go b/schema/column.go index 1ff8983e..c1ac0453 100644 --- a/schema/column.go +++ b/schema/column.go @@ -4,14 +4,14 @@ package schema type ColumnOp uint8 const ( - // AddColumn operation. - AddColumn ColumnOp = iota - // AlterColumn operation. - AlterColumn - // RenameColumn operation. - RenameColumn - // DropColumn operation. - DropColumn + // AddColumnOp operation. + AddColumnOp ColumnOp = iota + // AlterColumnOp operation. + AlterColumnOp + // RenameColumnOp operation. + RenameColumnOp + // DropColumnOp operation. + DropColumnOp ) // ColumnType definition. @@ -44,39 +44,45 @@ const ( // Column definition. type Column struct { - Op ColumnOp - Name string - Type ColumnType - NewName string + Op ColumnOp + Name string + Type ColumnType + NewName string + Limit int + Default interface{} + Null bool + Precision int + Scale int + Comment string } -func addColumn(name string, typ ColumnType, options ...Option) Column { +func addColumn(name string, typ ColumnType, options []Option) Column { return Column{ - Op: AddColumn, + Op: AddColumnOp, Name: name, Type: typ, } } -func alterColumn(name string, typ ColumnType, options ...Option) Column { +func alterColumn(name string, typ ColumnType, options []Option) Column { return Column{ - Op: AlterColumn, + Op: AlterColumnOp, Name: name, Type: typ, } } -func renameColumn(name string, newName string, options ...Option) Column { +func renameColumn(name string, newName string, options []Option) Column { return Column{ - Op: RenameColumn, + Op: RenameColumnOp, Name: name, NewName: newName, } } -func dropColumn(name string, options ...Option) Column { +func dropColumn(name string, options []Option) Column { return Column{ - Op: DropColumn, + Op: DropColumnOp, Name: name, } } diff --git a/schema/migrates.go b/schema/migrates.go index eb9a7d28..9f3b3916 100644 --- a/schema/migrates.go +++ b/schema/migrates.go @@ -1,6 +1,8 @@ package schema -import "github.com/Fs02/rel" +import ( + "github.com/Fs02/rel" +) // Migrator private interface. type Migrator interface { @@ -16,47 +18,54 @@ func (m *Migrates) add(migrator Migrator) { // CreateTable with name and its definition. func (m *Migrates) CreateTable(name string, fn func(t *Table), options ...Option) { - table := Table{Name: name} + table := createTable(name, options) fn(&table) - m.add(CreateTable(table)) + m.add(table) } // AlterTable with name and its definition. func (m *Migrates) AlterTable(name string, fn func(t *AlterTable), options ...Option) { - table := AlterTable{Table: Table{Name: name}} + table := alterTable(name, options) fn(&table) - m.add(table) + m.add(table.Table) } // RenameTable by name. -func (m *Migrates) RenameTable(name string, newName string) { - m.add(RenameTable{Name: name, NewName: newName}) +func (m *Migrates) RenameTable(name string, newName string, options ...Option) { + m.add(renameTable(name, newName, options)) } // DropTable by name. -func (m *Migrates) DropTable(name string) { - m.add(DropTable{Name: name}) +func (m *Migrates) DropTable(name string, options ...Option) { + m.add(dropTable(name, options)) } // AddColumn with name and type. func (m *Migrates) AddColumn(table string, name string, typ ColumnType, options ...Option) { - at := AlterTable{Table: Table{Name: name}} + at := alterTable(table, options) at.Column(name, typ, options...) - m.add(at) + m.add(at.Table) +} + +// AlterColumn by name. +func (m *Migrates) AlterColumn(table string, name string, typ ColumnType, options ...Option) { + at := alterTable(table, options) + at.AlterColumn(name, typ, options...) + m.add(at.Table) } // RenameColumn by name. func (m *Migrates) RenameColumn(table string, name string, newName string, options ...Option) { - at := AlterTable{Table: Table{Name: name}} + at := alterTable(table, options) at.RenameColumn(name, newName, options...) - m.add(at) + m.add(at.Table) } // DropColumn by name. func (m *Migrates) DropColumn(table string, name string, options ...Option) { - at := AlterTable{Table: Table{Name: name}} + at := alterTable(table, options) at.DropColumn(name, options...) - m.add(at) + m.add(at.Table) } // AddIndex for columns. diff --git a/schema/migration_test.go b/schema/migration_test.go index bdd68ecb..f530d13f 100644 --- a/schema/migration_test.go +++ b/schema/migration_test.go @@ -47,7 +47,8 @@ func TestMigration_tables(t *testing.T) { assert.Equal(t, Migration{ Version: 20200705164100, Ups: Migrates{ - CreateTable{ + Table{ + Op: CreateTableOp, Name: "products", Columns: []Column{ {Name: "id", Type: Integer}, @@ -55,43 +56,134 @@ func TestMigration_tables(t *testing.T) { {Name: "description", Type: Text}, }, }, - AlterTable{ - Table: Table{ - Name: "users", - Columns: []Column{ - {Name: "verified", Type: Boolean}, - {Name: "name", NewName: "fullname", Op: RenameColumn}, - }, + Table{ + Op: AlterTableOp, + Name: "users", + Columns: []Column{ + {Name: "verified", Type: Boolean, Op: AddColumnOp}, + {Name: "name", NewName: "fullname", Op: RenameColumnOp}, }, }, - RenameTable{ + Table{ + Op: RenameTableOp, Name: "trxs", NewName: "transactions", }, - DropTable{Name: "logs"}, + Table{ + Op: DropTableOp, + Name: "logs", + }, }, Downs: Migrates{ - CreateTable{ + Table{ + Op: CreateTableOp, Name: "logs", Columns: []Column{ {Name: "id", Type: Integer}, {Name: "value", Type: String}, }, }, - RenameTable{ + Table{ + Op: RenameTableOp, Name: "transactions", NewName: "trxs", }, - AlterTable{ - Table: Table{ - Name: "users", - Columns: []Column{ - {Name: "verified", Op: DropColumn}, - {Name: "fullname", NewName: "name", Op: RenameColumn}, - }, + Table{ + Op: AlterTableOp, + Name: "users", + Columns: []Column{ + {Name: "verified", Op: DropColumnOp}, + {Name: "fullname", NewName: "name", Op: RenameColumnOp}, + }, + }, + Table{ + Op: DropTableOp, + Name: "products", + }, + }, + }, migration) +} + +func TestMigration_columns(t *testing.T) { + var ( + migration = NewMigration(20200805165500) + ) + + migration.Up(func(m *Migrates) { + m.AddColumn("products", "description", String) + m.AlterColumn("products", "sale", Boolean) + m.RenameColumn("users", "name", "fullname") + m.DropColumn("users", "verified") + }) + + migration.Down(func(m *Migrates) { + m.AddColumn("users", "verified", Boolean) + m.RenameColumn("users", "fullname", "name") + m.AlterColumn("products", "sale", Integer) + m.DropColumn("products", "description") + }) + + assert.Equal(t, Migration{ + Version: 20200805165500, + Ups: Migrates{ + Table{ + Op: AlterTableOp, + Name: "products", + Columns: []Column{ + {Name: "description", Type: String, Op: AddColumnOp}, + }, + }, + Table{ + Op: AlterTableOp, + Name: "products", + Columns: []Column{ + {Name: "sale", Type: Boolean, Op: AlterColumnOp}, + }, + }, + Table{ + Op: AlterTableOp, + Name: "users", + Columns: []Column{ + {Name: "name", NewName: "fullname", Op: RenameColumnOp}, + }, + }, + Table{ + Op: AlterTableOp, + Name: "users", + Columns: []Column{ + {Name: "verified", Op: DropColumnOp}, + }, + }, + }, + Downs: Migrates{ + Table{ + Op: AlterTableOp, + Name: "users", + Columns: []Column{ + {Name: "verified", Type: Boolean, Op: AddColumnOp}, + }, + }, + Table{ + Op: AlterTableOp, + Name: "users", + Columns: []Column{ + {Name: "fullname", NewName: "name", Op: RenameColumnOp}, + }, + }, + Table{ + Op: AlterTableOp, + Name: "products", + Columns: []Column{ + {Name: "sale", Type: Integer, Op: AlterColumnOp}, + }, + }, + Table{ + Op: AlterTableOp, + Name: "products", + Columns: []Column{ + {Name: "description", Op: DropColumnOp}, }, }, - DropTable{Name: "products"}, }, }, migration) } diff --git a/schema/table.go b/schema/table.go index 392db84b..9eff7df0 100644 --- a/schema/table.go +++ b/schema/table.go @@ -1,15 +1,32 @@ package schema +// TableOp definition. +type TableOp uint8 + +const ( + // CreateTableOp operation. + CreateTableOp TableOp = iota + // AlterTableOp operation. + AlterTableOp + // RenameTableOp operation. + RenameTableOp + // DropTableOp operation. + DropTableOp +) + // Table definition. type Table struct { + Op TableOp Name string + NewName string Columns []Column Indices []Index + Options string } // Column defines a column with name and type. func (t *Table) Column(name string, typ ColumnType, options ...Option) { - t.Columns = append(t.Columns, addColumn(name, typ, options...)) + t.Columns = append(t.Columns, addColumn(name, typ, options)) } // Index defines an index for columns. @@ -73,10 +90,7 @@ func (t *Table) Timestamp(name string, options ...Option) { t.Column(name, Timestamp, options...) } -// CreateTable Migrator. -type CreateTable Table - -func (ct CreateTable) migrate() {} +func (t Table) migrate() {} // AlterTable Migrator. type AlterTable struct { @@ -85,32 +99,46 @@ type AlterTable struct { // RenameColumn to a new name. func (at *AlterTable) RenameColumn(name string, newName string, options ...Option) { - at.Columns = append(at.Columns, renameColumn(name, newName, options...)) + at.Columns = append(at.Columns, renameColumn(name, newName, options)) } // AlterColumn from this table. func (at *AlterTable) AlterColumn(name string, typ ColumnType, options ...Option) { - at.Columns = append(at.Columns, alterColumn(name, typ, options...)) + at.Columns = append(at.Columns, alterColumn(name, typ, options)) } // DropColumn from this table. func (at *AlterTable) DropColumn(name string, options ...Option) { - at.Columns = append(at.Columns, dropColumn(name, options...)) + at.Columns = append(at.Columns, dropColumn(name, options)) } -func (at AlterTable) migrate() {} - -// RenameTable Migrator. -type RenameTable struct { - Name string - NewName string +func createTable(name string, options []Option) Table { + return Table{ + Op: CreateTableOp, + Name: name, + } } -func (rt RenameTable) migrate() {} +func alterTable(name string, options []Option) AlterTable { + return AlterTable{ + Table: Table{ + Op: AlterTableOp, + Name: name, + }, + } +} -// DropTable Migrator. -type DropTable struct { - Name string +func renameTable(name string, newName string, options []Option) Table { + return Table{ + Op: RenameTableOp, + Name: name, + NewName: newName, + } } -func (dt DropTable) migrate() {} +func dropTable(name string, options []Option) Table { + return Table{ + Op: DropTableOp, + Name: name, + } +} From 37cde2bb700ccf7a0c7510c63f85533d08618910 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Wed, 8 Jul 2020 00:39:22 +0700 Subject: [PATCH 04/44] options --- schema/column.go | 85 ++++++++++++++++++++++++++++++++++++++------ schema/migrates.go | 26 +++++++------- schema/option.go | 4 --- schema/schema.go | 12 ------- schema/table.go | 87 +++++++++++++++++++++++++++++++--------------- 5 files changed, 147 insertions(+), 67 deletions(-) delete mode 100644 schema/option.go diff --git a/schema/column.go b/schema/column.go index c1ac0453..e2fb9d0b 100644 --- a/schema/column.go +++ b/schema/column.go @@ -32,6 +32,8 @@ const ( String ColumnType = "string" // Text ColumnType. Text ColumnType = "text" + // Binary ColumnType. + Binary ColumnType = "binary" // Date ColumnType. Date ColumnType = "date" // DateTime ColumnType. @@ -48,41 +50,104 @@ type Column struct { Name string Type ColumnType NewName string + Nil bool Limit int - Default interface{} - Null bool Precision int Scale int Comment string + Default interface{} } -func addColumn(name string, typ ColumnType, options []Option) Column { - return Column{ +func addColumn(name string, typ ColumnType, options []ColumnOption) Column { + column := Column{ Op: AddColumnOp, Name: name, Type: typ, } + + applyColumnOptions(&column, options) + return column } -func alterColumn(name string, typ ColumnType, options []Option) Column { - return Column{ +func alterColumn(name string, typ ColumnType, options []ColumnOption) Column { + column := Column{ Op: AlterColumnOp, Name: name, Type: typ, } + + applyColumnOptions(&column, options) + return column } -func renameColumn(name string, newName string, options []Option) Column { - return Column{ +func renameColumn(name string, newName string, options []ColumnOption) Column { + column := Column{ Op: RenameColumnOp, Name: name, NewName: newName, } + + applyColumnOptions(&column, options) + return column } -func dropColumn(name string, options []Option) Column { - return Column{ +func dropColumn(name string, options []ColumnOption) Column { + column := Column{ Op: DropColumnOp, Name: name, } + + applyColumnOptions(&column, options) + return column +} + +// ColumnOption functor. +type ColumnOption func(column *Column) + +// Nil allows or disallows nil values in the column. +func Nil(allow bool) ColumnOption { + return func(column *Column) { + column.Nil = allow + } +} + +// Limit sets the maximum size of the string/text/binary/integer columns. +func Limit(limit int) ColumnOption { + return func(column *Column) { + column.Limit = limit + } +} + +// Precision defines the precision for the decimal fields, representing the total number of digits in the number. +func Precision(precision int) ColumnOption { + return func(column *Column) { + column.Precision = precision + } +} + +// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. +func Scale(scale int) ColumnOption { + return func(column *Column) { + column.Scale = scale + } +} + +// Comment adds a comment for the column. +func Comment(comment string) ColumnOption { + return func(column *Column) { + column.Comment = comment + } +} + +// Default allows to set a default value on the column.). +func Default(def interface{}) ColumnOption { + return func(column *Column) { + column.Default = def + } +} + +func applyColumnOptions(table *Column, options []ColumnOption) { + for i := range options { + options[i](table) + } } diff --git a/schema/migrates.go b/schema/migrates.go index 9f3b3916..360c4de1 100644 --- a/schema/migrates.go +++ b/schema/migrates.go @@ -17,59 +17,59 @@ func (m *Migrates) add(migrator Migrator) { } // CreateTable with name and its definition. -func (m *Migrates) CreateTable(name string, fn func(t *Table), options ...Option) { +func (m *Migrates) CreateTable(name string, fn func(t *Table), options ...TableOption) { table := createTable(name, options) fn(&table) m.add(table) } // AlterTable with name and its definition. -func (m *Migrates) AlterTable(name string, fn func(t *AlterTable), options ...Option) { +func (m *Migrates) AlterTable(name string, fn func(t *AlterTable), options ...TableOption) { table := alterTable(name, options) fn(&table) m.add(table.Table) } // RenameTable by name. -func (m *Migrates) RenameTable(name string, newName string, options ...Option) { +func (m *Migrates) RenameTable(name string, newName string, options ...TableOption) { m.add(renameTable(name, newName, options)) } // DropTable by name. -func (m *Migrates) DropTable(name string, options ...Option) { +func (m *Migrates) DropTable(name string, options ...TableOption) { m.add(dropTable(name, options)) } // AddColumn with name and type. -func (m *Migrates) AddColumn(table string, name string, typ ColumnType, options ...Option) { - at := alterTable(table, options) +func (m *Migrates) AddColumn(table string, name string, typ ColumnType, options ...ColumnOption) { + at := alterTable(table, nil) at.Column(name, typ, options...) m.add(at.Table) } // AlterColumn by name. -func (m *Migrates) AlterColumn(table string, name string, typ ColumnType, options ...Option) { - at := alterTable(table, options) +func (m *Migrates) AlterColumn(table string, name string, typ ColumnType, options ...ColumnOption) { + at := alterTable(table, nil) at.AlterColumn(name, typ, options...) m.add(at.Table) } // RenameColumn by name. -func (m *Migrates) RenameColumn(table string, name string, newName string, options ...Option) { - at := alterTable(table, options) +func (m *Migrates) RenameColumn(table string, name string, newName string, options ...ColumnOption) { + at := alterTable(table, nil) at.RenameColumn(name, newName, options...) m.add(at.Table) } // DropColumn by name. -func (m *Migrates) DropColumn(table string, name string, options ...Option) { - at := alterTable(table, options) +func (m *Migrates) DropColumn(table string, name string, options ...ColumnOption) { + at := alterTable(table, nil) at.DropColumn(name, options...) m.add(at.Table) } // AddIndex for columns. -func (m *Migrates) AddIndex(table string, column []string, options ...Option) { +func (m *Migrates) AddIndex(table string, column []string) { } // RenameIndex by name. diff --git a/schema/option.go b/schema/option.go deleted file mode 100644 index 3c106906..00000000 --- a/schema/option.go +++ /dev/null @@ -1,4 +0,0 @@ -package schema - -// Option for schema. -type Option interface{} diff --git a/schema/schema.go b/schema/schema.go index c22aec24..5c470463 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -5,15 +5,3 @@ type Schema struct { Version int Tables []Table } - -// Table with name and its definition. -func (s Schema) Table(name string, fn func(t *Table), options ...Option) { - table := Table{Name: name} - fn(&table) - s.Tables = append(s.Tables, table) -} - -// New Schema. -func New(version int) Schema { - return Schema{Version: version} -} diff --git a/schema/table.go b/schema/table.go index 9eff7df0..4026c2c4 100644 --- a/schema/table.go +++ b/schema/table.go @@ -25,68 +25,73 @@ type Table struct { } // Column defines a column with name and type. -func (t *Table) Column(name string, typ ColumnType, options ...Option) { +func (t *Table) Column(name string, typ ColumnType, options ...ColumnOption) { t.Columns = append(t.Columns, addColumn(name, typ, options)) } // Index defines an index for columns. -func (t *Table) Index(columns []string, options ...Option) { +func (t *Table) Index(columns []string, options ...ColumnOption) { index := Index{Columns: columns} t.Indices = append(t.Indices, index) } // Boolean defines a column with name and Boolean type. -func (t *Table) Boolean(name string, options ...Option) { +func (t *Table) Boolean(name string, options ...ColumnOption) { t.Column(name, Boolean, options...) } // Integer defines a column with name and Integer type. -func (t *Table) Integer(name string, options ...Option) { +func (t *Table) Integer(name string, options ...ColumnOption) { t.Column(name, Integer, options...) } // BigInt defines a column with name and BigInt type. -func (t *Table) BigInt(name string, options ...Option) { +func (t *Table) BigInt(name string, options ...ColumnOption) { t.Column(name, BigInt, options...) } // Float defines a column with name and Float type. -func (t *Table) Float(name string, options ...Option) { +func (t *Table) Float(name string, options ...ColumnOption) { t.Column(name, Float, options...) } // Decimal defines a column with name and Decimal type. -func (t *Table) Decimal(name string, options ...Option) { +func (t *Table) Decimal(name string, options ...ColumnOption) { t.Column(name, Decimal, options...) } // String defines a column with name and String type. -func (t *Table) String(name string, options ...Option) { +func (t *Table) String(name string, options ...ColumnOption) { t.Column(name, String, options...) } // Text defines a column with name and Text type. -func (t *Table) Text(name string, options ...Option) { +func (t *Table) Text(name string, options ...ColumnOption) { t.Column(name, Text, options...) } +// Binary defines a column with name and Binary type. +func (t *Table) Binary(name string, options ...ColumnOption) { + t.Column(name, Binary, options...) +} + // Date defines a column with name and Date type. -func (t *Table) Date(name string, options ...Option) { +func (t *Table) Date(name string, options ...ColumnOption) { t.Column(name, Date, options...) } // DateTime defines a column with name and DateTime type. -func (t *Table) DateTime(name string, options ...Option) { +func (t *Table) DateTime(name string, options ...ColumnOption) { t.Column(name, DateTime, options...) } // Time defines a column with name and Time type. -func (t *Table) Time(name string, options ...Option) { +func (t *Table) Time(name string, options ...ColumnOption) { t.Column(name, Time, options...) } // Timestamp defines a column with name and Timestamp type. -func (t *Table) Timestamp(name string, options ...Option) { +func (t *Table) Timestamp(name string, options ...ColumnOption) { t.Column(name, Timestamp, options...) } @@ -98,47 +103,73 @@ type AlterTable struct { } // RenameColumn to a new name. -func (at *AlterTable) RenameColumn(name string, newName string, options ...Option) { +func (at *AlterTable) RenameColumn(name string, newName string, options ...ColumnOption) { at.Columns = append(at.Columns, renameColumn(name, newName, options)) } // AlterColumn from this table. -func (at *AlterTable) AlterColumn(name string, typ ColumnType, options ...Option) { +func (at *AlterTable) AlterColumn(name string, typ ColumnType, options ...ColumnOption) { at.Columns = append(at.Columns, alterColumn(name, typ, options)) } // DropColumn from this table. -func (at *AlterTable) DropColumn(name string, options ...Option) { +func (at *AlterTable) DropColumn(name string, options ...ColumnOption) { at.Columns = append(at.Columns, dropColumn(name, options)) } -func createTable(name string, options []Option) Table { - return Table{ +func createTable(name string, options []TableOption) Table { + table := Table{ Op: CreateTableOp, Name: name, } + + applyTableOptions(&table, options) + return table } -func alterTable(name string, options []Option) AlterTable { - return AlterTable{ - Table: Table{ - Op: AlterTableOp, - Name: name, - }, +func alterTable(name string, options []TableOption) AlterTable { + table := Table{ + Op: AlterTableOp, + Name: name, } + + applyTableOptions(&table, options) + return AlterTable{Table: table} } -func renameTable(name string, newName string, options []Option) Table { - return Table{ +func renameTable(name string, newName string, options []TableOption) Table { + table := Table{ Op: RenameTableOp, Name: name, NewName: newName, } + + applyTableOptions(&table, options) + return table } -func dropTable(name string, options []Option) Table { - return Table{ +func dropTable(name string, options []TableOption) Table { + table := Table{ Op: DropTableOp, Name: name, } + + applyTableOptions(&table, options) + return table +} + +// TableOption functor. +type TableOption func(table *Table) + +// Options allow additional SQL fragment to be used when creating a table. +func Options(options string) TableOption { + return func(table *Table) { + table.Options = options + } +} + +func applyTableOptions(table *Table, options []TableOption) { + for i := range options { + options[i](table) + } } From 23505ba8b3cb521695fc7918b71c2d33f8bb7ded Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 12 Jul 2020 00:16:34 +0700 Subject: [PATCH 05/44] add index --- schema/column.go | 81 +++-------------------- schema/index.go | 79 ++++++++++++++++++++++- schema/migrates.go | 15 ++++- schema/migration_test.go | 96 ++++++++++++++------------- schema/op.go | 15 +++++ schema/options.go | 136 +++++++++++++++++++++++++++++++++++++++ schema/table.go | 94 +++++++++++++-------------- 7 files changed, 344 insertions(+), 172 deletions(-) create mode 100644 schema/op.go create mode 100644 schema/options.go diff --git a/schema/column.go b/schema/column.go index e2fb9d0b..78f203c5 100644 --- a/schema/column.go +++ b/schema/column.go @@ -1,19 +1,5 @@ package schema -// ColumnOp definition. -type ColumnOp uint8 - -const ( - // AddColumnOp operation. - AddColumnOp ColumnOp = iota - // AlterColumnOp operation. - AlterColumnOp - // RenameColumnOp operation. - RenameColumnOp - // DropColumnOp operation. - DropColumnOp -) - // ColumnType definition. type ColumnType string @@ -46,21 +32,23 @@ const ( // Column definition. type Column struct { - Op ColumnOp + Op Op Name string Type ColumnType NewName string - Nil bool + Required bool + Unsigned bool Limit int Precision int Scale int - Comment string Default interface{} + Comment string + Options string } func addColumn(name string, typ ColumnType, options []ColumnOption) Column { column := Column{ - Op: AddColumnOp, + Op: Add, Name: name, Type: typ, } @@ -71,7 +59,7 @@ func addColumn(name string, typ ColumnType, options []ColumnOption) Column { func alterColumn(name string, typ ColumnType, options []ColumnOption) Column { column := Column{ - Op: AlterColumnOp, + Op: Alter, Name: name, Type: typ, } @@ -82,7 +70,7 @@ func alterColumn(name string, typ ColumnType, options []ColumnOption) Column { func renameColumn(name string, newName string, options []ColumnOption) Column { column := Column{ - Op: RenameColumnOp, + Op: Rename, Name: name, NewName: newName, } @@ -93,61 +81,10 @@ func renameColumn(name string, newName string, options []ColumnOption) Column { func dropColumn(name string, options []ColumnOption) Column { column := Column{ - Op: DropColumnOp, + Op: Drop, Name: name, } applyColumnOptions(&column, options) return column } - -// ColumnOption functor. -type ColumnOption func(column *Column) - -// Nil allows or disallows nil values in the column. -func Nil(allow bool) ColumnOption { - return func(column *Column) { - column.Nil = allow - } -} - -// Limit sets the maximum size of the string/text/binary/integer columns. -func Limit(limit int) ColumnOption { - return func(column *Column) { - column.Limit = limit - } -} - -// Precision defines the precision for the decimal fields, representing the total number of digits in the number. -func Precision(precision int) ColumnOption { - return func(column *Column) { - column.Precision = precision - } -} - -// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. -func Scale(scale int) ColumnOption { - return func(column *Column) { - column.Scale = scale - } -} - -// Comment adds a comment for the column. -func Comment(comment string) ColumnOption { - return func(column *Column) { - column.Comment = comment - } -} - -// Default allows to set a default value on the column.). -func Default(def interface{}) ColumnOption { - return func(column *Column) { - column.Default = def - } -} - -func applyColumnOptions(table *Column, options []ColumnOption) { - for i := range options { - options[i](table) - } -} diff --git a/schema/index.go b/schema/index.go index 9e5881a0..f7216fee 100644 --- a/schema/index.go +++ b/schema/index.go @@ -1,7 +1,82 @@ package schema +// IndexType definition. +type IndexType string + +const ( + // Simple IndexType. + Simple IndexType = "index" + // UniqueIndex IndexType. + UniqueIndex IndexType = "unique" + // PrimaryKey IndexType. + PrimaryKey IndexType = "primary key" + // ForeignKey IndexType. + ForeignKey IndexType = "foreign key" +) + // Index definition. type Index struct { - Columns []string - Name string + Op Op + Name string + Type IndexType + Columns []string + Reference Reference + NewName string + Comment string + Options string +} + +// Reference definition. +type Reference struct { + Table string + Column string + OnDelete string + OnUpdate string +} + +func addIndex(columns []string, typ IndexType, options []IndexOption) Index { + index := Index{ + Op: Add, + Columns: columns, + Type: typ, + } + + applyIndexOptions(&index, options) + return index +} + +func addForeignKey(column string, refTable string, refColumn string, options []IndexOption) Index { + index := Index{ + Op: Add, + Columns: []string{column}, + Reference: Reference{ + Table: refTable, + Column: refColumn, + }, + Type: ForeignKey, + } + + applyIndexOptions(&index, options) + return index +} + +func renameIndex(name string, newName string, options []IndexOption) Index { + index := Index{ + Op: Rename, + Name: name, + NewName: newName, + } + + applyIndexOptions(&index, options) + return index +} + +func dropIndex(name string, options []IndexOption) Index { + index := Index{ + Op: Drop, + Name: name, + } + + applyIndexOptions(&index, options) + return index } diff --git a/schema/migrates.go b/schema/migrates.go index 360c4de1..21c38bc0 100644 --- a/schema/migrates.go +++ b/schema/migrates.go @@ -69,15 +69,24 @@ func (m *Migrates) DropColumn(table string, name string, options ...ColumnOption } // AddIndex for columns. -func (m *Migrates) AddIndex(table string, column []string) { +func (m *Migrates) AddIndex(table string, column []string, typ IndexType, options ...IndexOption) { + at := alterTable(table, nil) + at.Index(column, typ, options...) + m.add(at.Table) } // RenameIndex by name. -func (m *Migrates) RenameIndex(table string, name string, newName string) { +func (m *Migrates) RenameIndex(table string, name string, newName string, options ...IndexOption) { + at := alterTable(table, nil) + at.RenameIndex(name, newName, options...) + m.add(at.Table) } // DropIndex by name. -func (m *Migrates) DropIndex(table string, name string) { +func (m *Migrates) DropIndex(table string, name string, options ...IndexOption) { + at := alterTable(table, nil) + at.DropIndex(name, options...) + m.add(at.Table) } // Exec queries using repo. diff --git a/schema/migration_test.go b/schema/migration_test.go index f530d13f..fc03eca5 100644 --- a/schema/migration_test.go +++ b/schema/migration_test.go @@ -16,6 +16,8 @@ func TestMigration_tables(t *testing.T) { t.Integer("id") t.String("name") t.Text("description") + + t.PrimaryKey("id") }) m.AlterTable("users", func(t *AlterTable) { @@ -32,6 +34,8 @@ func TestMigration_tables(t *testing.T) { m.CreateTable("logs", func(t *Table) { t.Integer("id") t.String("value") + + t.PrimaryKey("id") }) m.RenameTable("transactions", "trxs") @@ -48,56 +52,58 @@ func TestMigration_tables(t *testing.T) { Version: 20200705164100, Ups: Migrates{ Table{ - Op: CreateTableOp, + Op: Add, Name: "products", - Columns: []Column{ - {Name: "id", Type: Integer}, - {Name: "name", Type: String}, - {Name: "description", Type: Text}, + Definitions: []interface{}{ + Column{Name: "id", Type: Integer}, + Column{Name: "name", Type: String}, + Column{Name: "description", Type: Text}, + Index{Columns: []string{"id"}, Type: PrimaryKey}, }, }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "users", - Columns: []Column{ - {Name: "verified", Type: Boolean, Op: AddColumnOp}, - {Name: "name", NewName: "fullname", Op: RenameColumnOp}, + Definitions: []interface{}{ + Column{Name: "verified", Type: Boolean, Op: Add}, + Column{Name: "name", NewName: "fullname", Op: Rename}, }, }, Table{ - Op: RenameTableOp, + Op: Rename, Name: "trxs", NewName: "transactions", }, Table{ - Op: DropTableOp, + Op: Drop, Name: "logs", }, }, Downs: Migrates{ Table{ - Op: CreateTableOp, + Op: Add, Name: "logs", - Columns: []Column{ - {Name: "id", Type: Integer}, - {Name: "value", Type: String}, + Definitions: []interface{}{ + Column{Name: "id", Type: Integer}, + Column{Name: "value", Type: String}, + Index{Columns: []string{"id"}, Type: PrimaryKey}, }, }, Table{ - Op: RenameTableOp, + Op: Rename, Name: "transactions", NewName: "trxs", }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "users", - Columns: []Column{ - {Name: "verified", Op: DropColumnOp}, - {Name: "fullname", NewName: "name", Op: RenameColumnOp}, + Definitions: []interface{}{ + Column{Name: "verified", Op: Drop}, + Column{Name: "fullname", NewName: "name", Op: Rename}, }, }, Table{ - Op: DropTableOp, + Op: Drop, Name: "products", }, }, @@ -127,61 +133,61 @@ func TestMigration_columns(t *testing.T) { Version: 20200805165500, Ups: Migrates{ Table{ - Op: AlterTableOp, + Op: Alter, Name: "products", - Columns: []Column{ - {Name: "description", Type: String, Op: AddColumnOp}, + Definitions: []interface{}{ + Column{Name: "description", Type: String, Op: Add}, }, }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "products", - Columns: []Column{ - {Name: "sale", Type: Boolean, Op: AlterColumnOp}, + Definitions: []interface{}{ + Column{Name: "sale", Type: Boolean, Op: Alter}, }, }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "users", - Columns: []Column{ - {Name: "name", NewName: "fullname", Op: RenameColumnOp}, + Definitions: []interface{}{ + Column{Name: "name", NewName: "fullname", Op: Rename}, }, }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "users", - Columns: []Column{ - {Name: "verified", Op: DropColumnOp}, + Definitions: []interface{}{ + Column{Name: "verified", Op: Drop}, }, }, }, Downs: Migrates{ Table{ - Op: AlterTableOp, + Op: Alter, Name: "users", - Columns: []Column{ - {Name: "verified", Type: Boolean, Op: AddColumnOp}, + Definitions: []interface{}{ + Column{Name: "verified", Type: Boolean, Op: Add}, }, }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "users", - Columns: []Column{ - {Name: "fullname", NewName: "name", Op: RenameColumnOp}, + Definitions: []interface{}{ + Column{Name: "fullname", NewName: "name", Op: Rename}, }, }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "products", - Columns: []Column{ - {Name: "sale", Type: Integer, Op: AlterColumnOp}, + Definitions: []interface{}{ + Column{Name: "sale", Type: Integer, Op: Alter}, }, }, Table{ - Op: AlterTableOp, + Op: Alter, Name: "products", - Columns: []Column{ - {Name: "description", Op: DropColumnOp}, + Definitions: []interface{}{ + Column{Name: "description", Op: Drop}, }, }, }, diff --git a/schema/op.go b/schema/op.go new file mode 100644 index 00000000..f2ec313e --- /dev/null +++ b/schema/op.go @@ -0,0 +1,15 @@ +package schema + +// Op type. +type Op uint8 + +const ( + // Add operation. + Add Op = iota + // Alter operation. + Alter + // Rename operation. + Rename + // Drop operation. + Drop +) diff --git a/schema/options.go b/schema/options.go new file mode 100644 index 00000000..ed96bb39 --- /dev/null +++ b/schema/options.go @@ -0,0 +1,136 @@ +package schema + +// TableOption interface. +// Available options are: Comment, Options. +type TableOption interface { + applyTable(table *Table) +} + +func applyTableOptions(table *Table, options []TableOption) { + for i := range options { + options[i].applyTable(table) + } +} + +// ColumnOption interface. +// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. +type ColumnOption interface { + applyColumn(column *Column) +} + +func applyColumnOptions(column *Column, options []ColumnOption) { + for i := range options { + options[i].applyColumn(column) + } +} + +// IndexOption interface. +// Available options are: Comment, Options. +type IndexOption interface { + applyIndex(index *Index) +} + +func applyIndexOptions(index *Index, options []IndexOption) { + for i := range options { + options[i].applyIndex(index) + } +} + +// Comment options for table, column and index. +type Comment string + +func (c Comment) applyTable(table *Table) { + table.Comment = string(c) +} + +func (c Comment) applyColumn(column *Column) { + column.Comment = string(c) +} + +func (c Comment) applyIndex(index *Index) { + index.Comment = string(c) +} + +// Options options for table, column and index. +type Options string + +func (o Options) applyTable(table *Table) { + table.Options = string(o) +} + +func (o Options) applyColumn(column *Column) { + column.Options = string(o) +} + +func (o Options) applyIndex(index *Index) { + index.Options = string(o) +} + +// Required disallows nil values in the column. +type Required bool + +func (r Required) applyColumn(column *Column) { + column.Required = bool(r) +} + +// Unsigned sets integer column to be unsigned. +type Unsigned bool + +func (u Unsigned) applyColumn(column *Column) { + column.Unsigned = bool(u) +} + +// Limit sets the maximum size of the string/text/binary/integer columns. +type Limit int + +func (l Limit) applyColumn(column *Column) { + column.Limit = int(l) +} + +// Precision defines the precision for the decimal fields, representing the total number of digits in the number. +type Precision int + +func (p Precision) applyColumn(column *Column) { + column.Precision = int(p) +} + +// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. +type Scale int + +func (s Scale) applyColumn(column *Column) { + column.Scale = int(s) +} + +type defaultValue struct { + value interface{} +} + +func (d defaultValue) applyColumn(column *Column) { + column.Default = d +} + +// Default allows to set a default value on the column.). +func Default(def interface{}) ColumnOption { + return defaultValue{value: def} +} + +// Name option for defining custom index name. +type Name string + +func (n Name) applyIndex(index *Index) { + index.Name = string(n) +} + +// OnDelete option for foreign key index. +type OnDelete string + +func (od OnDelete) applyIndex(index *Index) { + index.Reference.OnDelete = string(od) +} + +// OnUpdate option for foreign key index. +type OnUpdate string + +func (ou OnUpdate) applyIndex(index *Index) { + index.Reference.OnUpdate = string(ou) +} diff --git a/schema/table.go b/schema/table.go index 4026c2c4..566985db 100644 --- a/schema/table.go +++ b/schema/table.go @@ -1,38 +1,18 @@ package schema -// TableOp definition. -type TableOp uint8 - -const ( - // CreateTableOp operation. - CreateTableOp TableOp = iota - // AlterTableOp operation. - AlterTableOp - // RenameTableOp operation. - RenameTableOp - // DropTableOp operation. - DropTableOp -) - // Table definition. type Table struct { - Op TableOp - Name string - NewName string - Columns []Column - Indices []Index - Options string + Op Op + Name string + NewName string + Definitions []interface{} + Comment string + Options string } // Column defines a column with name and type. func (t *Table) Column(name string, typ ColumnType, options ...ColumnOption) { - t.Columns = append(t.Columns, addColumn(name, typ, options)) -} - -// Index defines an index for columns. -func (t *Table) Index(columns []string, options ...ColumnOption) { - index := Index{Columns: columns} - t.Indices = append(t.Indices, index) + t.Definitions = append(t.Definitions, addColumn(name, typ, options)) } // Boolean defines a column with name and Boolean type. @@ -95,6 +75,26 @@ func (t *Table) Timestamp(name string, options ...ColumnOption) { t.Column(name, Timestamp, options...) } +// Index defines an index for columns. +func (t *Table) Index(columns []string, typ IndexType, options ...IndexOption) { + t.Definitions = append(t.Definitions, addIndex(columns, typ, options)) +} + +// Unique defines an unique index for columns. +func (t *Table) Unique(columns []string, options ...IndexOption) { + t.Index(columns, UniqueIndex, options...) +} + +// PrimaryKey defines an primary key for table. +func (t *Table) PrimaryKey(column string, options ...IndexOption) { + t.Index([]string{column}, PrimaryKey, options...) +} + +// ForeignKey defines foreign key index. +func (t *Table) ForeignKey(column string, refTable string, refColumn string, options ...IndexOption) { + t.Definitions = append(t.Definitions, addForeignKey(column, refTable, refColumn, options)) +} + func (t Table) migrate() {} // AlterTable Migrator. @@ -104,22 +104,32 @@ type AlterTable struct { // RenameColumn to a new name. func (at *AlterTable) RenameColumn(name string, newName string, options ...ColumnOption) { - at.Columns = append(at.Columns, renameColumn(name, newName, options)) + at.Definitions = append(at.Definitions, renameColumn(name, newName, options)) } // AlterColumn from this table. func (at *AlterTable) AlterColumn(name string, typ ColumnType, options ...ColumnOption) { - at.Columns = append(at.Columns, alterColumn(name, typ, options)) + at.Definitions = append(at.Definitions, alterColumn(name, typ, options)) } // DropColumn from this table. func (at *AlterTable) DropColumn(name string, options ...ColumnOption) { - at.Columns = append(at.Columns, dropColumn(name, options)) + at.Definitions = append(at.Definitions, dropColumn(name, options)) +} + +// RenameIndex to a new name. +func (at *AlterTable) RenameIndex(name string, newName string, options ...IndexOption) { + at.Definitions = append(at.Definitions, renameIndex(name, newName, options)) +} + +// DropIndex from this table. +func (at *AlterTable) DropIndex(name string, options ...IndexOption) { + at.Definitions = append(at.Definitions, dropIndex(name, options)) } func createTable(name string, options []TableOption) Table { table := Table{ - Op: CreateTableOp, + Op: Add, Name: name, } @@ -129,7 +139,7 @@ func createTable(name string, options []TableOption) Table { func alterTable(name string, options []TableOption) AlterTable { table := Table{ - Op: AlterTableOp, + Op: Alter, Name: name, } @@ -139,7 +149,7 @@ func alterTable(name string, options []TableOption) AlterTable { func renameTable(name string, newName string, options []TableOption) Table { table := Table{ - Op: RenameTableOp, + Op: Rename, Name: name, NewName: newName, } @@ -150,26 +160,10 @@ func renameTable(name string, newName string, options []TableOption) Table { func dropTable(name string, options []TableOption) Table { table := Table{ - Op: DropTableOp, + Op: Drop, Name: name, } applyTableOptions(&table, options) return table } - -// TableOption functor. -type TableOption func(table *Table) - -// Options allow additional SQL fragment to be used when creating a table. -func Options(options string) TableOption { - return func(table *Table) { - table.Options = options - } -} - -func applyTableOptions(table *Table, options []TableOption) { - for i := range options { - options[i](table) - } -} From 7e45fd2c0b544522c5e901f24155aa1309e67e13 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 12 Jul 2020 01:07:32 +0700 Subject: [PATCH 06/44] add tests --- schema/column_test.go | 125 +++++++++++++++++++++ schema/index.go | 33 ++---- schema/index_test.go | 84 +++++++++++++++ schema/migrates.go | 8 +- schema/migration_test.go | 32 ++++++ schema/options.go | 6 +- schema/table_test.go | 228 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 485 insertions(+), 31 deletions(-) create mode 100644 schema/column_test.go create mode 100644 schema/index_test.go create mode 100644 schema/table_test.go diff --git a/schema/column_test.go b/schema/column_test.go new file mode 100644 index 00000000..1770dafd --- /dev/null +++ b/schema/column_test.go @@ -0,0 +1,125 @@ +package schema + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddColumn(t *testing.T) { + var ( + options = []ColumnOption{ + Required(true), + Unsigned(true), + Limit(1000), + Precision(5), + Scale(2), + Default(0), + Comment("comment"), + Options("options"), + } + column = addColumn("add", Decimal, options) + ) + + assert.Equal(t, Column{ + Name: "add", + Type: Decimal, + Required: true, + Unsigned: true, + Limit: 1000, + Precision: 5, + Scale: 2, + Default: 0, + Comment: "comment", + Options: "options", + }, column) +} + +func TestAlterColumn(t *testing.T) { + var ( + options = []ColumnOption{ + Required(true), + Unsigned(true), + Limit(1000), + Precision(5), + Scale(2), + Default(0), + Comment("comment"), + Options("options"), + } + column = alterColumn("alter", Decimal, options) + ) + + assert.Equal(t, Column{ + Op: Alter, + Name: "alter", + Type: Decimal, + Required: true, + Unsigned: true, + Limit: 1000, + Precision: 5, + Scale: 2, + Default: 0, + Comment: "comment", + Options: "options", + }, column) +} + +func TestRenameColumn(t *testing.T) { + var ( + options = []ColumnOption{ + Required(true), + Unsigned(true), + Limit(1000), + Precision(5), + Scale(2), + Default(0), + Comment("comment"), + Options("options"), + } + column = renameColumn("add", "rename", options) + ) + + assert.Equal(t, Column{ + Op: Rename, + Name: "add", + NewName: "rename", + Required: true, + Unsigned: true, + Limit: 1000, + Precision: 5, + Scale: 2, + Default: 0, + Comment: "comment", + Options: "options", + }, column) +} + +func TestDropColumn(t *testing.T) { + var ( + options = []ColumnOption{ + Required(true), + Unsigned(true), + Limit(1000), + Precision(5), + Scale(2), + Default(0), + Comment("comment"), + Options("options"), + } + column = dropColumn("drop", options) + ) + + assert.Equal(t, Column{ + Op: Drop, + Name: "drop", + Required: true, + Unsigned: true, + Limit: 1000, + Precision: 5, + Scale: 2, + Default: 0, + Comment: "comment", + Options: "options", + }, column) +} diff --git a/schema/index.go b/schema/index.go index f7216fee..d372fe45 100644 --- a/schema/index.go +++ b/schema/index.go @@ -4,8 +4,8 @@ package schema type IndexType string const ( - // Simple IndexType. - Simple IndexType = "index" + // SimpleIndex IndexType. + SimpleIndex IndexType = "index" // UniqueIndex IndexType. UniqueIndex IndexType = "unique" // PrimaryKey IndexType. @@ -16,22 +16,15 @@ const ( // Index definition. type Index struct { - Op Op - Name string - Type IndexType - Columns []string - Reference Reference - NewName string - Comment string - Options string -} - -// Reference definition. -type Reference struct { - Table string - Column string + Op Op + Name string + Type IndexType + Columns []string // when fk: [column, fk table, fk column] + NewName string OnDelete string OnUpdate string + Comment string + Options string } func addIndex(columns []string, typ IndexType, options []IndexOption) Index { @@ -48,12 +41,8 @@ func addIndex(columns []string, typ IndexType, options []IndexOption) Index { func addForeignKey(column string, refTable string, refColumn string, options []IndexOption) Index { index := Index{ Op: Add, - Columns: []string{column}, - Reference: Reference{ - Table: refTable, - Column: refColumn, - }, - Type: ForeignKey, + Columns: []string{column, refTable, refColumn}, + Type: ForeignKey, } applyIndexOptions(&index, options) diff --git a/schema/index_test.go b/schema/index_test.go new file mode 100644 index 00000000..35ca8438 --- /dev/null +++ b/schema/index_test.go @@ -0,0 +1,84 @@ +package schema + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddIndex(t *testing.T) { + var ( + options = []IndexOption{ + Name("simple"), + Comment("comment"), + Options("options"), + } + index = addIndex([]string{"add"}, SimpleIndex, options) + ) + + assert.Equal(t, Index{ + Type: SimpleIndex, + Name: "simple", + Columns: []string{"add"}, + Comment: "comment", + Options: "options", + }, index) +} + +func TestAddForeignKey(t *testing.T) { + var ( + options = []IndexOption{ + OnDelete("cascade"), + OnUpdate("cascade"), + Name("fk"), + Comment("comment"), + Options("options"), + } + index = addForeignKey("table_id", "table", "id", options) + ) + + assert.Equal(t, Index{ + Type: ForeignKey, + Name: "fk", + Columns: []string{"table_id", "table", "id"}, + OnDelete: "cascade", + OnUpdate: "cascade", + Comment: "comment", + Options: "options", + }, index) +} + +func TestRenameIndex(t *testing.T) { + var ( + options = []IndexOption{ + Comment("comment"), + Options("options"), + } + index = renameIndex("add", "rename", options) + ) + + assert.Equal(t, Index{ + Op: Rename, + Name: "add", + NewName: "rename", + Comment: "comment", + Options: "options", + }, index) +} + +func TestDropIndex(t *testing.T) { + var ( + options = []IndexOption{ + Comment("comment"), + Options("options"), + } + index = dropIndex("drop", options) + ) + + assert.Equal(t, Index{ + Op: Drop, + Name: "drop", + Comment: "comment", + Options: "options", + }, index) +} diff --git a/schema/migrates.go b/schema/migrates.go index 21c38bc0..16f930d5 100644 --- a/schema/migrates.go +++ b/schema/migrates.go @@ -1,9 +1,5 @@ package schema -import ( - "github.com/Fs02/rel" -) - // Migrator private interface. type Migrator interface { migrate() @@ -91,5 +87,5 @@ func (m *Migrates) DropIndex(table string, name string, options ...IndexOption) // Exec queries using repo. // Useful for data migration. -func (m *Migrates) Exec(func(repo rel.Repository) error) { -} +// func (m *Migrates) Exec(func(repo rel.Repository) error) { +// } diff --git a/schema/migration_test.go b/schema/migration_test.go index fc03eca5..edc1de00 100644 --- a/schema/migration_test.go +++ b/schema/migration_test.go @@ -120,6 +120,8 @@ func TestMigration_columns(t *testing.T) { m.AlterColumn("products", "sale", Boolean) m.RenameColumn("users", "name", "fullname") m.DropColumn("users", "verified") + m.AddIndex("products", []string{"sale"}, SimpleIndex) + m.RenameIndex("products", "store_id", "fk_store_id") }) migration.Down(func(m *Migrates) { @@ -127,6 +129,8 @@ func TestMigration_columns(t *testing.T) { m.RenameColumn("users", "fullname", "name") m.AlterColumn("products", "sale", Integer) m.DropColumn("products", "description") + m.DropIndex("products", "sale") + m.RenameIndex("products", "fk_store_id", "store_id") }) assert.Equal(t, Migration{ @@ -160,6 +164,20 @@ func TestMigration_columns(t *testing.T) { Column{Name: "verified", Op: Drop}, }, }, + Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: Add}, + }, + }, + Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Index{Name: "store_id", NewName: "fk_store_id", Op: Rename}, + }, + }, }, Downs: Migrates{ Table{ @@ -190,6 +208,20 @@ func TestMigration_columns(t *testing.T) { Column{Name: "description", Op: Drop}, }, }, + Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Index{Name: "sale", Op: Drop}, + }, + }, + Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Index{Name: "fk_store_id", NewName: "store_id", Op: Rename}, + }, + }, }, }, migration) } diff --git a/schema/options.go b/schema/options.go index ed96bb39..38dec433 100644 --- a/schema/options.go +++ b/schema/options.go @@ -106,7 +106,7 @@ type defaultValue struct { } func (d defaultValue) applyColumn(column *Column) { - column.Default = d + column.Default = d.value } // Default allows to set a default value on the column.). @@ -125,12 +125,12 @@ func (n Name) applyIndex(index *Index) { type OnDelete string func (od OnDelete) applyIndex(index *Index) { - index.Reference.OnDelete = string(od) + index.OnDelete = string(od) } // OnUpdate option for foreign key index. type OnUpdate string func (ou OnUpdate) applyIndex(index *Index) { - index.Reference.OnUpdate = string(ou) + index.OnUpdate = string(ou) } diff --git a/schema/table_test.go b/schema/table_test.go new file mode 100644 index 00000000..25b1e750 --- /dev/null +++ b/schema/table_test.go @@ -0,0 +1,228 @@ +package schema + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTable(t *testing.T) { + var table Table + + t.Run("Column", func(t *testing.T) { + table.Column("column", String, Comment("column")) + assert.Equal(t, Column{ + Name: "column", + Type: String, + Comment: "column", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Boolean", func(t *testing.T) { + table.Boolean("boolean", Comment("boolean")) + assert.Equal(t, Column{ + Name: "boolean", + Type: Boolean, + Comment: "boolean", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Integer", func(t *testing.T) { + table.Integer("integer", Comment("integer")) + assert.Equal(t, Column{ + Name: "integer", + Type: Integer, + Comment: "integer", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("BigInt", func(t *testing.T) { + table.BigInt("bigint", Comment("bigint")) + assert.Equal(t, Column{ + Name: "bigint", + Type: BigInt, + Comment: "bigint", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Float", func(t *testing.T) { + table.Float("float", Comment("float")) + assert.Equal(t, Column{ + Name: "float", + Type: Float, + Comment: "float", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Decimal", func(t *testing.T) { + table.Decimal("decimal", Comment("decimal")) + assert.Equal(t, Column{ + Name: "decimal", + Type: Decimal, + Comment: "decimal", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("String", func(t *testing.T) { + table.String("string", Comment("string")) + assert.Equal(t, Column{ + Name: "string", + Type: String, + Comment: "string", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Text", func(t *testing.T) { + table.Text("text", Comment("text")) + assert.Equal(t, Column{ + Name: "text", + Type: Text, + Comment: "text", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Binary", func(t *testing.T) { + table.Binary("binary", Comment("binary")) + assert.Equal(t, Column{ + Name: "binary", + Type: Binary, + Comment: "binary", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Date", func(t *testing.T) { + table.Date("date", Comment("date")) + assert.Equal(t, Column{ + Name: "date", + Type: Date, + Comment: "date", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("DateTime", func(t *testing.T) { + table.DateTime("datetime", Comment("datetime")) + assert.Equal(t, Column{ + Name: "datetime", + Type: DateTime, + Comment: "datetime", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Time", func(t *testing.T) { + table.Time("time", Comment("time")) + assert.Equal(t, Column{ + Name: "time", + Type: Time, + Comment: "time", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Timestamp", func(t *testing.T) { + table.Timestamp("timestamp", Comment("timestamp")) + assert.Equal(t, Column{ + Name: "timestamp", + Type: Timestamp, + Comment: "timestamp", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Index", func(t *testing.T) { + table.Index([]string{"id"}, PrimaryKey, Comment("primary key")) + assert.Equal(t, Index{ + Columns: []string{"id"}, + Type: PrimaryKey, + Comment: "primary key", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Unique", func(t *testing.T) { + table.Unique([]string{"username"}, Comment("unique")) + assert.Equal(t, Index{ + Columns: []string{"username"}, + Type: UniqueIndex, + Comment: "unique", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("PrimaryKey", func(t *testing.T) { + table.PrimaryKey("id", Comment("primary key")) + assert.Equal(t, Index{ + Columns: []string{"id"}, + Type: PrimaryKey, + Comment: "primary key", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("ForeignKey", func(t *testing.T) { + table.ForeignKey("user_id", "users", "id", Comment("foreign key")) + assert.Equal(t, Index{ + Columns: []string{"user_id", "users", "id"}, + Type: ForeignKey, + Comment: "foreign key", + }, table.Definitions[len(table.Definitions)-1]) + }) +} + +func TestAlterTable(t *testing.T) { + var table AlterTable + + t.Run("RenameColumn", func(t *testing.T) { + table.RenameColumn("column", "new_column") + assert.Equal(t, Column{ + Op: Rename, + Name: "column", + NewName: "new_column", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("AlterColumn", func(t *testing.T) { + table.AlterColumn("column", Boolean, Comment("column")) + assert.Equal(t, Column{ + Op: Alter, + Name: "column", + Type: Boolean, + Comment: "column", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("DropColumn", func(t *testing.T) { + table.DropColumn("column") + assert.Equal(t, Column{ + Op: Drop, + Name: "column", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("RenameIndex", func(t *testing.T) { + table.RenameIndex("index", "new_index") + assert.Equal(t, Index{ + Op: Rename, + Name: "index", + NewName: "new_index", + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("DropIndex", func(t *testing.T) { + table.DropIndex("index") + assert.Equal(t, Index{ + Op: Drop, + Name: "index", + }, table.Definitions[len(table.Definitions)-1]) + }) +} + +func TestCreateTable(t *testing.T) { + var ( + options = []TableOption{ + Comment("comment"), + Options("options"), + } + table = createTable("table", options) + ) + + assert.Equal(t, Table{ + Name: "table", + Comment: "comment", + Options: "options", + }, table) +} From 5d0ec7d81bb969439b811d0a73e90e2d6bc61827 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 18 Jul 2020 23:49:40 +0700 Subject: [PATCH 07/44] wip query builder --- adapter/sql/builder.go | 252 ++++++++++++++++++++++++++++++++++++ adapter/sql/builder_test.go | 83 ++++++++++++ schema/column.go | 28 ++-- schema/index.go | 40 ++++-- schema/index_test.go | 18 ++- schema/migration_test.go | 24 ++-- schema/options.go | 4 +- schema/table.go | 12 +- schema/table_test.go | 22 ++-- 9 files changed, 419 insertions(+), 64 deletions(-) diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 8bb6043d..3de400e0 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -1,11 +1,13 @@ package sql import ( + "encoding/json" "strconv" "strings" "sync" "github.com/Fs02/rel" + "github.com/Fs02/rel/schema" ) // UnescapeCharacter disable field escaping when it starts with this character. @@ -20,6 +22,256 @@ type Builder struct { count int } +// Table generates query for table creation and modification. +func (b *Builder) Table(table schema.Table) string { + var buffer Buffer + + switch table.Op { + case schema.Add: + b.createTable(&buffer, table) + case schema.Alter: + b.alterTable(&buffer, table) + case schema.Rename: + buffer.WriteString("RENAME TABLE ") + buffer.WriteString(b.escape(table.Name)) + buffer.WriteString(" TO ") + buffer.WriteString(b.escape(table.NewName)) + buffer.WriteByte(';') + case schema.Drop: + buffer.WriteString("DROP TABLE ") + buffer.WriteString(b.escape(table.Name)) + buffer.WriteByte(';') + } + + return buffer.String() +} + +func (b *Builder) createTable(buffer *Buffer, table schema.Table) { + buffer.WriteString("CREATE TABLE ") + buffer.WriteString(b.escape(table.Name)) + buffer.WriteString(" (") + + for i, def := range table.Definitions { + if i > 0 { + buffer.WriteString(", ") + } + switch v := def.(type) { + case schema.Column: + b.column(buffer, v) + case schema.Index: + b.index(buffer, v) + } + } + + buffer.WriteByte(')') + b.comment(buffer, table.Comment) + b.options(buffer, table.Options) + buffer.WriteByte(';') +} + +func (b *Builder) alterTable(buffer *Buffer, table schema.Table) { + buffer.WriteString("ALTER TABLE ") + buffer.WriteString(b.escape(table.Name)) + buffer.WriteByte(' ') + + for i, def := range table.Definitions { + if i > 0 { + buffer.WriteString(", ") + } + + switch v := def.(type) { + case schema.Column: + switch v.Op { + case schema.Add: + buffer.WriteString("ADD ") + b.column(buffer, v) + case schema.Alter: // TODO: use modify keyword? + buffer.WriteString("MODIFY ") + b.column(buffer, v) + case schema.Rename: + buffer.WriteString("RENAME COLUMN ") + buffer.WriteString(b.escape(v.Name)) + buffer.WriteString(" TO ") + buffer.WriteString(b.escape(v.NewName)) + case schema.Drop: + buffer.WriteString("DROP COLUMN ") + buffer.WriteString(b.escape(v.Name)) + } + case schema.Index: + switch v.Op { + case schema.Add: + buffer.WriteString("ADD ") + b.index(buffer, v) + case schema.Rename: + buffer.WriteString("RENAME INDEX ") + buffer.WriteString(b.escape(v.Name)) + buffer.WriteString(" TO ") + buffer.WriteString(b.escape(v.NewName)) + case schema.Drop: + buffer.WriteString("DROP INDEX ") + buffer.WriteString(b.escape(v.Name)) + } + } + } + + b.options(buffer, table.Options) + buffer.WriteByte(';') +} + +func (b *Builder) column(buffer *Buffer, column schema.Column) { + var ( + m, n int + typ string + ) + + // TODO: type mapping + + switch column.Type { + case schema.Bool: + typ = "BOOL" + case schema.Int: + typ = "INT" + m = column.Limit + case schema.BigInt: + typ = "BIGINT" + m = column.Limit + case schema.Float: + typ = "FLOAT" + m = column.Precision + case schema.Decimal: + typ = "DECIMAL" + m = column.Precision + n = column.Scale + case schema.String: + typ = "VARCHAR" + m = column.Limit + if m == 0 { + m = 255 + } + case schema.Text: + typ = "TEXT" + m = column.Limit + case schema.Binary: + typ = "BINARY" + m = column.Limit + case schema.Date: + typ = "DATE" + case schema.DateTime: + typ = "DATETIME" + case schema.Time: + typ = "TIME" + case schema.Timestamp: + // TODO: mysql automatically add on update options. + typ = "TIMESTAMP" + default: + typ = string(column.Type) + } + + buffer.WriteString(b.escape(column.Name)) + buffer.WriteByte(' ') + buffer.WriteString(typ) + + if m != 0 { + buffer.WriteByte('(') + buffer.WriteString(strconv.Itoa(m)) + + if n != 0 { + buffer.WriteByte(',') + buffer.WriteString(strconv.Itoa(n)) + } + + buffer.WriteByte(')') + } + + if column.Unsigned { + buffer.WriteString(" UNSIGNED") + } + + if column.Required { + buffer.WriteString(" NOT NULL") + } + + if column.Default != nil { + buffer.WriteString(" DEFAULT ") + // TODO: improve + bytes, _ := json.Marshal(column.Default) + buffer.Write(bytes) + } + + b.comment(buffer, column.Comment) + b.options(buffer, column.Options) +} + +func (b *Builder) index(buffer *Buffer, index schema.Index) { + var ( + typ = string(index.Type) + ) + + // TODO: type mapping + + buffer.WriteString(typ) + + if index.Name != "" { + buffer.WriteByte(' ') + buffer.WriteString(b.escape(index.Name)) + } + + buffer.WriteString(" (") + for i, col := range index.Columns { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(b.escape(col)) + } + buffer.WriteString(")") + + if index.Type == schema.ForeignKey { + buffer.WriteString(" REFERENCES ") + buffer.WriteString(b.escape(index.Reference.Table)) + + buffer.WriteString(" (") + for i, col := range index.Reference.Columns { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(b.escape(col)) + } + buffer.WriteString(")") + + if onDelete := index.Reference.OnDelete; onDelete != "" { + buffer.WriteString(" ON DELETE ") + buffer.WriteString(onDelete) + } + + if onUpdate := index.Reference.OnUpdate; onUpdate != "" { + buffer.WriteString(" ON UPDATE ") + buffer.WriteString(onUpdate) + } + } + + b.comment(buffer, index.Comment) + b.options(buffer, index.Options) +} + +func (b *Builder) comment(buffer *Buffer, comment string) { + if comment == "" { + return + } + + buffer.WriteString(" COMMENT '") + buffer.WriteString(comment) + buffer.WriteByte('\'') +} + +func (b *Builder) options(buffer *Buffer, options string) { + if options == "" { + return + } + + buffer.WriteByte(' ') + buffer.WriteString(options) +} + // Find generates query for select. func (b *Builder) Find(query rel.Query) (string, []interface{}) { if query.SQLQuery.Statement != "" { diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 4ab0bb45..8d9a5da3 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/Fs02/rel" + "github.com/Fs02/rel/schema" "github.com/Fs02/rel/sort" "github.com/Fs02/rel/where" "github.com/stretchr/testify/assert" @@ -32,6 +33,88 @@ func BenchmarkBuilder_Find(b *testing.B) { } } +func TestBuilder_Table(t *testing.T) { + var ( + config = &Config{ + Placeholder: "?", + EscapeChar: "`", + } + ) + + tests := []struct { + result string + table schema.Table + }{ + { + result: "CREATE TABLE `products` (`id` INT, `name` VARCHAR(255), `description` TEXT, PRIMARY KEY (`id`));", + table: schema.Table{ + Op: schema.Add, + Name: "products", + Definitions: []interface{}{ + schema.Column{Name: "id", Type: schema.Int}, + schema.Column{Name: "name", Type: schema.String}, + schema.Column{Name: "description", Type: schema.Text}, + schema.Index{Columns: []string{"id"}, Type: schema.PrimaryKey}, + }, + }, + }, + { + result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `binary` BINARY(255), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE INDEX `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) COMMENT 'TEST' Engine=InnoDB;", + table: schema.Table{ + Op: schema.Add, + Name: "columns", + Definitions: []interface{}{ + schema.Column{Name: "bool", Type: schema.Bool, Required: true, Default: false}, + schema.Column{Name: "int", Type: schema.Int, Limit: 11, Unsigned: true}, + schema.Column{Name: "bigint", Type: schema.BigInt, Limit: 20, Unsigned: true}, + schema.Column{Name: "float", Type: schema.Float, Precision: 24, Unsigned: true}, + schema.Column{Name: "decimal", Type: schema.Decimal, Precision: 6, Scale: 2, Unsigned: true}, + schema.Column{Name: "string", Type: schema.String, Limit: 144}, + schema.Column{Name: "text", Type: schema.Text, Limit: 1000}, + schema.Column{Name: "binary", Type: schema.Binary, Limit: 255}, + schema.Column{Name: "date", Type: schema.Date}, + schema.Column{Name: "datetime", Type: schema.DateTime}, + schema.Column{Name: "time", Type: schema.Time}, + schema.Column{Name: "timestamp", Type: schema.Timestamp}, + schema.Column{Name: "blob", Type: "blob"}, + schema.Index{Columns: []string{"int"}, Type: schema.PrimaryKey}, + schema.Index{Columns: []string{"date"}, Type: schema.UniqueIndex, Name: "date_unique"}, + schema.Index{Columns: []string{"datetime"}, Type: schema.SimpleIndex}, + schema.Index{Columns: []string{"int", "string"}, Type: schema.ForeignKey, Reference: schema.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, + }, + Options: "Engine=InnoDB", + Comment: "TEST", + }, + }, + { + result: "RENAME TABLE `columns` TO `definitions`;", + table: schema.Table{ + Op: schema.Rename, + Name: "columns", + NewName: "definitions", + }, + }, + { + result: "DROP TABLE `columns`;", + table: schema.Table{ + Op: schema.Drop, + Name: "columns", + }, + }, + } + + for _, test := range tests { + t.Run(test.result, func(t *testing.T) { + var ( + builder = NewBuilder(config) + result = builder.Table(test.table) + ) + + assert.Equal(t, test.result, result) + }) + } +} + func TestBuilder_Find(t *testing.T) { var ( config = &Config{ diff --git a/schema/column.go b/schema/column.go index 78f203c5..0929753b 100644 --- a/schema/column.go +++ b/schema/column.go @@ -4,30 +4,30 @@ package schema type ColumnType string const ( - // Boolean ColumnType. - Boolean ColumnType = "boolean" - // Integer ColumnType. - Integer ColumnType = "integer" + // Bool ColumnType. + Bool ColumnType = "BOOL" + // Int ColumnType. + Int ColumnType = "INT" // BigInt ColumnType. - BigInt ColumnType = "bigint" + BigInt ColumnType = "BIGINT" // Float ColumnType. - Float ColumnType = "float" + Float ColumnType = "FLOAT" // Decimal ColumnType. - Decimal ColumnType = "decimal" + Decimal ColumnType = "DECIMAL" // String ColumnType. - String ColumnType = "string" + String ColumnType = "STRING" // Text ColumnType. - Text ColumnType = "text" + Text ColumnType = "TEXT" // Binary ColumnType. - Binary ColumnType = "binary" + Binary ColumnType = "BINARY" // Date ColumnType. - Date ColumnType = "date" + Date ColumnType = "DATE" // DateTime ColumnType. - DateTime ColumnType = "datetime" + DateTime ColumnType = "DATETIME" // Time ColumnType. - Time ColumnType = "time" + Time ColumnType = "TIME" // Timestamp ColumnType. - Timestamp ColumnType = "timestamp" + Timestamp ColumnType = "TIMESTAMP" ) // Column definition. diff --git a/schema/index.go b/schema/index.go index d372fe45..6e16e057 100644 --- a/schema/index.go +++ b/schema/index.go @@ -5,26 +5,33 @@ type IndexType string const ( // SimpleIndex IndexType. - SimpleIndex IndexType = "index" + SimpleIndex IndexType = "INDEX" // UniqueIndex IndexType. - UniqueIndex IndexType = "unique" + UniqueIndex IndexType = "UNIQUE INDEX" // PrimaryKey IndexType. - PrimaryKey IndexType = "primary key" + PrimaryKey IndexType = "PRIMARY KEY" // ForeignKey IndexType. - ForeignKey IndexType = "foreign key" + ForeignKey IndexType = "FOREIGN KEY" ) -// Index definition. -type Index struct { - Op Op - Name string - Type IndexType - Columns []string // when fk: [column, fk table, fk column] - NewName string +// ForeignKeyReference definition. +type ForeignKeyReference struct { + Table string + Columns []string OnDelete string OnUpdate string - Comment string - Options string +} + +// Index definition. +type Index struct { + Op Op + Name string + Type IndexType + Columns []string + NewName string + Reference ForeignKeyReference + Comment string + Options string } func addIndex(columns []string, typ IndexType, options []IndexOption) Index { @@ -38,11 +45,16 @@ func addIndex(columns []string, typ IndexType, options []IndexOption) Index { return index } +// TODO: support multi columns func addForeignKey(column string, refTable string, refColumn string, options []IndexOption) Index { index := Index{ Op: Add, - Columns: []string{column, refTable, refColumn}, Type: ForeignKey, + Columns: []string{column}, + Reference: ForeignKeyReference{ + Table: refTable, + Columns: []string{refColumn}, + }, } applyIndexOptions(&index, options) diff --git a/schema/index_test.go b/schema/index_test.go index 35ca8438..fdc9445f 100644 --- a/schema/index_test.go +++ b/schema/index_test.go @@ -38,13 +38,17 @@ func TestAddForeignKey(t *testing.T) { ) assert.Equal(t, Index{ - Type: ForeignKey, - Name: "fk", - Columns: []string{"table_id", "table", "id"}, - OnDelete: "cascade", - OnUpdate: "cascade", - Comment: "comment", - Options: "options", + Type: ForeignKey, + Name: "fk", + Columns: []string{"table_id"}, + Reference: ForeignKeyReference{ + Table: "table", + Columns: []string{"id"}, + OnDelete: "cascade", + OnUpdate: "cascade", + }, + Comment: "comment", + Options: "options", }, index) } diff --git a/schema/migration_test.go b/schema/migration_test.go index edc1de00..a2baab17 100644 --- a/schema/migration_test.go +++ b/schema/migration_test.go @@ -13,7 +13,7 @@ func TestMigration_tables(t *testing.T) { migration.Up(func(m *Migrates) { m.CreateTable("products", func(t *Table) { - t.Integer("id") + t.Int("id") t.String("name") t.Text("description") @@ -21,7 +21,7 @@ func TestMigration_tables(t *testing.T) { }) m.AlterTable("users", func(t *AlterTable) { - t.Boolean("verified") + t.Bool("verified") t.RenameColumn("name", "fullname") }) @@ -32,7 +32,7 @@ func TestMigration_tables(t *testing.T) { migration.Down(func(m *Migrates) { m.CreateTable("logs", func(t *Table) { - t.Integer("id") + t.Int("id") t.String("value") t.PrimaryKey("id") @@ -55,7 +55,7 @@ func TestMigration_tables(t *testing.T) { Op: Add, Name: "products", Definitions: []interface{}{ - Column{Name: "id", Type: Integer}, + Column{Name: "id", Type: Int}, Column{Name: "name", Type: String}, Column{Name: "description", Type: Text}, Index{Columns: []string{"id"}, Type: PrimaryKey}, @@ -65,7 +65,7 @@ func TestMigration_tables(t *testing.T) { Op: Alter, Name: "users", Definitions: []interface{}{ - Column{Name: "verified", Type: Boolean, Op: Add}, + Column{Name: "verified", Type: Bool, Op: Add}, Column{Name: "name", NewName: "fullname", Op: Rename}, }, }, @@ -84,7 +84,7 @@ func TestMigration_tables(t *testing.T) { Op: Add, Name: "logs", Definitions: []interface{}{ - Column{Name: "id", Type: Integer}, + Column{Name: "id", Type: Int}, Column{Name: "value", Type: String}, Index{Columns: []string{"id"}, Type: PrimaryKey}, }, @@ -117,7 +117,7 @@ func TestMigration_columns(t *testing.T) { migration.Up(func(m *Migrates) { m.AddColumn("products", "description", String) - m.AlterColumn("products", "sale", Boolean) + m.AlterColumn("products", "sale", Bool) m.RenameColumn("users", "name", "fullname") m.DropColumn("users", "verified") m.AddIndex("products", []string{"sale"}, SimpleIndex) @@ -125,9 +125,9 @@ func TestMigration_columns(t *testing.T) { }) migration.Down(func(m *Migrates) { - m.AddColumn("users", "verified", Boolean) + m.AddColumn("users", "verified", Bool) m.RenameColumn("users", "fullname", "name") - m.AlterColumn("products", "sale", Integer) + m.AlterColumn("products", "sale", Int) m.DropColumn("products", "description") m.DropIndex("products", "sale") m.RenameIndex("products", "fk_store_id", "store_id") @@ -147,7 +147,7 @@ func TestMigration_columns(t *testing.T) { Op: Alter, Name: "products", Definitions: []interface{}{ - Column{Name: "sale", Type: Boolean, Op: Alter}, + Column{Name: "sale", Type: Bool, Op: Alter}, }, }, Table{ @@ -184,7 +184,7 @@ func TestMigration_columns(t *testing.T) { Op: Alter, Name: "users", Definitions: []interface{}{ - Column{Name: "verified", Type: Boolean, Op: Add}, + Column{Name: "verified", Type: Bool, Op: Add}, }, }, Table{ @@ -198,7 +198,7 @@ func TestMigration_columns(t *testing.T) { Op: Alter, Name: "products", Definitions: []interface{}{ - Column{Name: "sale", Type: Integer, Op: Alter}, + Column{Name: "sale", Type: Int, Op: Alter}, }, }, Table{ diff --git a/schema/options.go b/schema/options.go index 38dec433..5b7b73db 100644 --- a/schema/options.go +++ b/schema/options.go @@ -125,12 +125,12 @@ func (n Name) applyIndex(index *Index) { type OnDelete string func (od OnDelete) applyIndex(index *Index) { - index.OnDelete = string(od) + index.Reference.OnDelete = string(od) } // OnUpdate option for foreign key index. type OnUpdate string func (ou OnUpdate) applyIndex(index *Index) { - index.OnUpdate = string(ou) + index.Reference.OnUpdate = string(ou) } diff --git a/schema/table.go b/schema/table.go index 566985db..ed5e7f87 100644 --- a/schema/table.go +++ b/schema/table.go @@ -15,14 +15,14 @@ func (t *Table) Column(name string, typ ColumnType, options ...ColumnOption) { t.Definitions = append(t.Definitions, addColumn(name, typ, options)) } -// Boolean defines a column with name and Boolean type. -func (t *Table) Boolean(name string, options ...ColumnOption) { - t.Column(name, Boolean, options...) +// Bool defines a column with name and Bool type. +func (t *Table) Bool(name string, options ...ColumnOption) { + t.Column(name, Bool, options...) } -// Integer defines a column with name and Integer type. -func (t *Table) Integer(name string, options ...ColumnOption) { - t.Column(name, Integer, options...) +// Int defines a column with name and Int type. +func (t *Table) Int(name string, options ...ColumnOption) { + t.Column(name, Int, options...) } // BigInt defines a column with name and BigInt type. diff --git a/schema/table_test.go b/schema/table_test.go index 25b1e750..7fcb13a3 100644 --- a/schema/table_test.go +++ b/schema/table_test.go @@ -18,20 +18,20 @@ func TestTable(t *testing.T) { }, table.Definitions[len(table.Definitions)-1]) }) - t.Run("Boolean", func(t *testing.T) { - table.Boolean("boolean", Comment("boolean")) + t.Run("Bool", func(t *testing.T) { + table.Bool("boolean", Comment("boolean")) assert.Equal(t, Column{ Name: "boolean", - Type: Boolean, + Type: Bool, Comment: "boolean", }, table.Definitions[len(table.Definitions)-1]) }) - t.Run("Integer", func(t *testing.T) { - table.Integer("integer", Comment("integer")) + t.Run("Int", func(t *testing.T) { + table.Int("integer", Comment("integer")) assert.Equal(t, Column{ Name: "integer", - Type: Integer, + Type: Int, Comment: "integer", }, table.Definitions[len(table.Definitions)-1]) }) @@ -156,8 +156,12 @@ func TestTable(t *testing.T) { t.Run("ForeignKey", func(t *testing.T) { table.ForeignKey("user_id", "users", "id", Comment("foreign key")) assert.Equal(t, Index{ - Columns: []string{"user_id", "users", "id"}, + Columns: []string{"user_id"}, Type: ForeignKey, + Reference: ForeignKeyReference{ + Table: "users", + Columns: []string{"id"}, + }, Comment: "foreign key", }, table.Definitions[len(table.Definitions)-1]) }) @@ -176,11 +180,11 @@ func TestAlterTable(t *testing.T) { }) t.Run("AlterColumn", func(t *testing.T) { - table.AlterColumn("column", Boolean, Comment("column")) + table.AlterColumn("column", Bool, Comment("column")) assert.Equal(t, Column{ Op: Alter, Name: "column", - Type: Boolean, + Type: Bool, Comment: "column", }, table.Definitions[len(table.Definitions)-1]) }) From 77f94962dd9910366571a9462eb531419f257c7c Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 19 Jul 2020 14:29:35 +0700 Subject: [PATCH 08/44] more tests --- adapter/sql/builder.go | 5 +++-- adapter/sql/builder_test.go | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 3de400e0..a0f1d882 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -83,12 +83,13 @@ func (b *Builder) alterTable(buffer *Buffer, table schema.Table) { case schema.Column: switch v.Op { case schema.Add: - buffer.WriteString("ADD ") + buffer.WriteString("ADD COLUMN ") b.column(buffer, v) case schema.Alter: // TODO: use modify keyword? - buffer.WriteString("MODIFY ") + buffer.WriteString("MODIFY COLUMN ") b.column(buffer, v) case schema.Rename: + // Add Change buffer.WriteString("RENAME COLUMN ") buffer.WriteString(b.escape(v.Name)) buffer.WriteString(" TO ") diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 8d9a5da3..939a7eaf 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -86,6 +86,49 @@ func TestBuilder_Table(t *testing.T) { Comment: "TEST", }, }, + { + result: "ALTER TABLE `columns` ADD COLUMN `verified` BOOL, RENAME COLUMN `string` TO `name`, MODIFY COLUMN `bool` INT, DROP COLUMN `blob`;", + table: schema.Table{ + Op: schema.Alter, + Name: "columns", + Definitions: []interface{}{ + schema.Column{Name: "verified", Type: schema.Bool, Op: schema.Add}, + schema.Column{Name: "string", NewName: "name", Op: schema.Rename}, + schema.Column{Name: "bool", Type: schema.Int, Op: schema.Alter}, + schema.Column{Name: "blob", Op: schema.Drop}, + }, + }, + }, + { + result: "ALTER TABLE `columns` ADD INDEX `verified_int` (`verified`, `int`);", + table: schema.Table{ + Op: schema.Alter, + Name: "columns", + Definitions: []interface{}{ + schema.Index{Name: "verified_int", Columns: []string{"verified", "int"}, Type: schema.SimpleIndex, Op: schema.Add}, + }, + }, + }, + { + result: "ALTER TABLE `columns` RENAME INDEX `verified_int` TO `verified_int_index`;", + table: schema.Table{ + Op: schema.Alter, + Name: "columns", + Definitions: []interface{}{ + schema.Index{Name: "verified_int", NewName: "verified_int_index", Op: schema.Rename}, + }, + }, + }, + { + result: "ALTER TABLE `columns` DROP INDEX `verified_int_index`;", + table: schema.Table{ + Op: schema.Alter, + Name: "columns", + Definitions: []interface{}{ + schema.Index{Name: "verified_int_index", Op: schema.Drop}, + }, + }, + }, { result: "RENAME TABLE `columns` TO `definitions`;", table: schema.Table{ From 1b3f76ed5bfc6bfccee81e029bfbddf8791bdd86 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 19 Jul 2020 14:46:10 +0700 Subject: [PATCH 09/44] reafactor map column type --- adapter/sql/builder.go | 78 +++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index a0f1d882..5ca27e08 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -121,11 +121,49 @@ func (b *Builder) alterTable(buffer *Buffer, table schema.Table) { func (b *Builder) column(buffer *Buffer, column schema.Column) { var ( - m, n int - typ string + typ, m, n = b.mapColumnType(column) ) - // TODO: type mapping + buffer.WriteString(b.escape(column.Name)) + buffer.WriteByte(' ') + buffer.WriteString(typ) + + if m != 0 { + buffer.WriteByte('(') + buffer.WriteString(strconv.Itoa(m)) + + if n != 0 { + buffer.WriteByte(',') + buffer.WriteString(strconv.Itoa(n)) + } + + buffer.WriteByte(')') + } + + if column.Unsigned { + buffer.WriteString(" UNSIGNED") + } + + if column.Required { + buffer.WriteString(" NOT NULL") + } + + if column.Default != nil { + buffer.WriteString(" DEFAULT ") + // TODO: improve + bytes, _ := json.Marshal(column.Default) + buffer.Write(bytes) + } + + b.comment(buffer, column.Comment) + b.options(buffer, column.Options) +} + +func (b *Builder) mapColumnType(column schema.Column) (string, int, int) { + var ( + typ string + m, n int + ) switch column.Type { case schema.Bool: @@ -168,39 +206,7 @@ func (b *Builder) column(buffer *Buffer, column schema.Column) { typ = string(column.Type) } - buffer.WriteString(b.escape(column.Name)) - buffer.WriteByte(' ') - buffer.WriteString(typ) - - if m != 0 { - buffer.WriteByte('(') - buffer.WriteString(strconv.Itoa(m)) - - if n != 0 { - buffer.WriteByte(',') - buffer.WriteString(strconv.Itoa(n)) - } - - buffer.WriteByte(')') - } - - if column.Unsigned { - buffer.WriteString(" UNSIGNED") - } - - if column.Required { - buffer.WriteString(" NOT NULL") - } - - if column.Default != nil { - buffer.WriteString(" DEFAULT ") - // TODO: improve - bytes, _ := json.Marshal(column.Default) - buffer.Write(bytes) - } - - b.comment(buffer, column.Comment) - b.options(buffer, column.Options) + return typ, m, n } func (b *Builder) index(buffer *Buffer, index schema.Index) { From 931d48ebe16d990acf3258332a5e53992b90739f Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Wed, 29 Jul 2020 22:41:23 +0700 Subject: [PATCH 10/44] wip migration manager and use functor to define migration --- schema/adapter.go | 13 +++ schema/migrates.go | 91 ---------------- schema/migration.go | 161 ++++++++++++++++++++++++--- schema/migration_test.go | 227 --------------------------------------- schema/schema.go | 92 +++++++++++++++- schema/schema_test.go | 169 +++++++++++++++++++++++++++++ 6 files changed, 418 insertions(+), 335 deletions(-) create mode 100644 schema/adapter.go delete mode 100644 schema/migrates.go delete mode 100644 schema/migration_test.go create mode 100644 schema/schema_test.go diff --git a/schema/adapter.go b/schema/adapter.go new file mode 100644 index 00000000..155240c5 --- /dev/null +++ b/schema/adapter.go @@ -0,0 +1,13 @@ +package schema + +import ( + "context" + + "github.com/Fs02/rel" +) + +// Adapter interface +type Adapter interface { + rel.Adapter + Apply(ctx context.Context, table Table) error +} diff --git a/schema/migrates.go b/schema/migrates.go deleted file mode 100644 index 16f930d5..00000000 --- a/schema/migrates.go +++ /dev/null @@ -1,91 +0,0 @@ -package schema - -// Migrator private interface. -type Migrator interface { - migrate() -} - -// Migrates builder. -type Migrates []Migrator - -func (m *Migrates) add(migrator Migrator) { - *m = append(*m, migrator) -} - -// CreateTable with name and its definition. -func (m *Migrates) CreateTable(name string, fn func(t *Table), options ...TableOption) { - table := createTable(name, options) - fn(&table) - m.add(table) -} - -// AlterTable with name and its definition. -func (m *Migrates) AlterTable(name string, fn func(t *AlterTable), options ...TableOption) { - table := alterTable(name, options) - fn(&table) - m.add(table.Table) -} - -// RenameTable by name. -func (m *Migrates) RenameTable(name string, newName string, options ...TableOption) { - m.add(renameTable(name, newName, options)) -} - -// DropTable by name. -func (m *Migrates) DropTable(name string, options ...TableOption) { - m.add(dropTable(name, options)) -} - -// AddColumn with name and type. -func (m *Migrates) AddColumn(table string, name string, typ ColumnType, options ...ColumnOption) { - at := alterTable(table, nil) - at.Column(name, typ, options...) - m.add(at.Table) -} - -// AlterColumn by name. -func (m *Migrates) AlterColumn(table string, name string, typ ColumnType, options ...ColumnOption) { - at := alterTable(table, nil) - at.AlterColumn(name, typ, options...) - m.add(at.Table) -} - -// RenameColumn by name. -func (m *Migrates) RenameColumn(table string, name string, newName string, options ...ColumnOption) { - at := alterTable(table, nil) - at.RenameColumn(name, newName, options...) - m.add(at.Table) -} - -// DropColumn by name. -func (m *Migrates) DropColumn(table string, name string, options ...ColumnOption) { - at := alterTable(table, nil) - at.DropColumn(name, options...) - m.add(at.Table) -} - -// AddIndex for columns. -func (m *Migrates) AddIndex(table string, column []string, typ IndexType, options ...IndexOption) { - at := alterTable(table, nil) - at.Index(column, typ, options...) - m.add(at.Table) -} - -// RenameIndex by name. -func (m *Migrates) RenameIndex(table string, name string, newName string, options ...IndexOption) { - at := alterTable(table, nil) - at.RenameIndex(name, newName, options...) - m.add(at.Table) -} - -// DropIndex by name. -func (m *Migrates) DropIndex(table string, name string, options ...IndexOption) { - at := alterTable(table, nil) - at.DropIndex(name, options...) - m.add(at.Table) -} - -// Exec queries using repo. -// Useful for data migration. -// func (m *Migrates) Exec(func(repo rel.Repository) error) { -// } diff --git a/schema/migration.go b/schema/migration.go index 07cd8ca0..eea123f2 100644 --- a/schema/migration.go +++ b/schema/migration.go @@ -1,25 +1,158 @@ package schema -// Migration definition. -type Migration struct { +import ( + "context" + "sort" + + "github.com/Fs02/rel" +) + +const schemaVersionTable = "rel_schema_versions" + +type schemaVersion struct { + ID int + Version int +} + +func (schemaVersion) Table() string { + return schemaVersionTable +} + +// MigrationStep definition. +type MigrationStep struct { Version int - Ups Migrates - Downs Migrates + Up Schema + Down Schema + Applied bool +} + +// MigrationSteps definition. +type MigrationSteps []MigrationStep + +func (ms MigrationSteps) Len() int { + return len(ms) } -// Up migration. -func (m *Migration) Up(fn func(migrate *Migrates)) { - fn(&m.Ups) +func (ms MigrationSteps) Less(i, j int) bool { + return ms[i].Version < ms[j].Version } -// Down migration. -func (m *Migration) Down(fn func(migrate *Migrates)) { - fn(&m.Downs) +func (ms MigrationSteps) Swap(i, j int) { + ms[i], ms[j] = ms[j], ms[i] +} + +// Migration manager definition. +type Migration struct { + Adapter Adapter + Repository rel.Repository + Steps MigrationSteps +} + +// AddVersion adds a migration with explicit version. +func (m *Migration) AddVersion(version int, up func(schema *Schema), down func(schema *Schema)) { + var upSchema, downSchema Schema + + up(&upSchema) + down(&downSchema) + + m.Steps = append(m.Steps, MigrationStep{Version: version, Up: upSchema, Down: downSchema}) +} + +func (m Migration) buildVersionTableDefinition() Table { + var versionTable = createTable(schemaVersionTable, nil) // TODO: create if not exists + + versionTable.Int("id") + versionTable.Int("version") + versionTable.DateTime("created_at") + versionTable.DateTime("updated_at") + + versionTable.PrimaryKey("id") + versionTable.Unique([]string{"version"}) + + return versionTable +} + +func (m *Migration) sync(ctx context.Context) { + var ( + versions []schemaVersion + vi int + ) + + check(m.Adapter.Apply(ctx, m.buildVersionTableDefinition())) + m.Repository.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) + sort.Sort(m.Steps) + + for i := range m.Steps { + if len(versions) <= vi { + break + } + + if m.Steps[i].Version == versions[vi].Version { + m.Steps[i].Applied = true + vi++ + } + } + + if len(versions) <= vi { + panic("rel: inconsistent schema state") + } +} + +// Up perform migration to the latest schema version. +func (m *Migration) Up(ctx context.Context) { + m.sync(ctx) + + for _, step := range m.Steps { + if step.Applied { + continue + } + + m.Repository.Transaction(ctx, func(ctx context.Context) error { + m.Repository.MustInsert(ctx, &schemaVersion{Version: step.Version}) + + adapter := m.Repository.Adapter(ctx).(Adapter) + for _, migrator := range step.Up.Pending { + switch v := migrator.(type) { + case Table: + check(adapter.Apply(ctx, v)) + } + } + + return nil + }) + } +} + +// Down perform rollback migration 1 step. +func (m *Migration) Down(ctx context.Context) { + m.sync(ctx) + + for i := range m.Steps { + step := m.Steps[len(m.Steps)-i-1] + if !step.Applied { + continue + } + + m.Repository.Transaction(ctx, func(ctx context.Context) error { + m.Repository.MustInsert(ctx, &schemaVersion{Version: step.Version}) + + adapter := m.Repository.Adapter(ctx).(Adapter) + for _, migrator := range step.Down.Pending { + switch v := migrator.(type) { + case Table: + check(adapter.Apply(ctx, v)) + } + } + + return nil + }) + + return + } } -// NewMigration for schema. -func NewMigration(version int) Migration { - return Migration{ - Version: version, +func check(err error) { + if err != nil { + panic(err) } } diff --git a/schema/migration_test.go b/schema/migration_test.go deleted file mode 100644 index a2baab17..00000000 --- a/schema/migration_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package schema - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMigration_tables(t *testing.T) { - var ( - migration = NewMigration(20200705164100) - ) - - migration.Up(func(m *Migrates) { - m.CreateTable("products", func(t *Table) { - t.Int("id") - t.String("name") - t.Text("description") - - t.PrimaryKey("id") - }) - - m.AlterTable("users", func(t *AlterTable) { - t.Bool("verified") - t.RenameColumn("name", "fullname") - }) - - m.RenameTable("trxs", "transactions") - - m.DropTable("logs") - }) - - migration.Down(func(m *Migrates) { - m.CreateTable("logs", func(t *Table) { - t.Int("id") - t.String("value") - - t.PrimaryKey("id") - }) - - m.RenameTable("transactions", "trxs") - - m.AlterTable("users", func(t *AlterTable) { - t.DropColumn("verified") - t.RenameColumn("fullname", "name") - }) - - m.DropTable("products") - }) - - assert.Equal(t, Migration{ - Version: 20200705164100, - Ups: Migrates{ - Table{ - Op: Add, - Name: "products", - Definitions: []interface{}{ - Column{Name: "id", Type: Int}, - Column{Name: "name", Type: String}, - Column{Name: "description", Type: Text}, - Index{Columns: []string{"id"}, Type: PrimaryKey}, - }, - }, - Table{ - Op: Alter, - Name: "users", - Definitions: []interface{}{ - Column{Name: "verified", Type: Bool, Op: Add}, - Column{Name: "name", NewName: "fullname", Op: Rename}, - }, - }, - Table{ - Op: Rename, - Name: "trxs", - NewName: "transactions", - }, - Table{ - Op: Drop, - Name: "logs", - }, - }, - Downs: Migrates{ - Table{ - Op: Add, - Name: "logs", - Definitions: []interface{}{ - Column{Name: "id", Type: Int}, - Column{Name: "value", Type: String}, - Index{Columns: []string{"id"}, Type: PrimaryKey}, - }, - }, - Table{ - Op: Rename, - Name: "transactions", - NewName: "trxs", - }, - Table{ - Op: Alter, - Name: "users", - Definitions: []interface{}{ - Column{Name: "verified", Op: Drop}, - Column{Name: "fullname", NewName: "name", Op: Rename}, - }, - }, - Table{ - Op: Drop, - Name: "products", - }, - }, - }, migration) -} - -func TestMigration_columns(t *testing.T) { - var ( - migration = NewMigration(20200805165500) - ) - - migration.Up(func(m *Migrates) { - m.AddColumn("products", "description", String) - m.AlterColumn("products", "sale", Bool) - m.RenameColumn("users", "name", "fullname") - m.DropColumn("users", "verified") - m.AddIndex("products", []string{"sale"}, SimpleIndex) - m.RenameIndex("products", "store_id", "fk_store_id") - }) - - migration.Down(func(m *Migrates) { - m.AddColumn("users", "verified", Bool) - m.RenameColumn("users", "fullname", "name") - m.AlterColumn("products", "sale", Int) - m.DropColumn("products", "description") - m.DropIndex("products", "sale") - m.RenameIndex("products", "fk_store_id", "store_id") - }) - - assert.Equal(t, Migration{ - Version: 20200805165500, - Ups: Migrates{ - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Column{Name: "description", Type: String, Op: Add}, - }, - }, - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Column{Name: "sale", Type: Bool, Op: Alter}, - }, - }, - Table{ - Op: Alter, - Name: "users", - Definitions: []interface{}{ - Column{Name: "name", NewName: "fullname", Op: Rename}, - }, - }, - Table{ - Op: Alter, - Name: "users", - Definitions: []interface{}{ - Column{Name: "verified", Op: Drop}, - }, - }, - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: Add}, - }, - }, - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Index{Name: "store_id", NewName: "fk_store_id", Op: Rename}, - }, - }, - }, - Downs: Migrates{ - Table{ - Op: Alter, - Name: "users", - Definitions: []interface{}{ - Column{Name: "verified", Type: Bool, Op: Add}, - }, - }, - Table{ - Op: Alter, - Name: "users", - Definitions: []interface{}{ - Column{Name: "fullname", NewName: "name", Op: Rename}, - }, - }, - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Column{Name: "sale", Type: Int, Op: Alter}, - }, - }, - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Column{Name: "description", Op: Drop}, - }, - }, - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Index{Name: "sale", Op: Drop}, - }, - }, - Table{ - Op: Alter, - Name: "products", - Definitions: []interface{}{ - Index{Name: "fk_store_id", NewName: "store_id", Op: Rename}, - }, - }, - }, - }, migration) -} diff --git a/schema/schema.go b/schema/schema.go index 5c470463..20df35a7 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -1,7 +1,93 @@ package schema -// Schema definition. +// Migrator private interface. +type Migrator interface { + migrate() +} + +// Schema builder. type Schema struct { - Version int - Tables []Table + Pending []Migrator +} + +func (s *Schema) add(migrator Migrator) { + s.Pending = append(s.Pending, migrator) +} + +// CreateTable with name and its definition. +func (s *Schema) CreateTable(name string, fn func(t *Table), options ...TableOption) { + table := createTable(name, options) + fn(&table) + s.add(table) +} + +// AlterTable with name and its definition. +func (s *Schema) AlterTable(name string, fn func(t *AlterTable), options ...TableOption) { + table := alterTable(name, options) + fn(&table) + s.add(table.Table) +} + +// RenameTable by name. +func (s *Schema) RenameTable(name string, newName string, options ...TableOption) { + s.add(renameTable(name, newName, options)) +} + +// DropTable by name. +func (s *Schema) DropTable(name string, options ...TableOption) { + s.add(dropTable(name, options)) +} + +// AddColumn with name and type. +func (s *Schema) AddColumn(table string, name string, typ ColumnType, options ...ColumnOption) { + at := alterTable(table, nil) + at.Column(name, typ, options...) + s.add(at.Table) } + +// AlterColumn by name. +func (s *Schema) AlterColumn(table string, name string, typ ColumnType, options ...ColumnOption) { + at := alterTable(table, nil) + at.AlterColumn(name, typ, options...) + s.add(at.Table) +} + +// RenameColumn by name. +func (s *Schema) RenameColumn(table string, name string, newName string, options ...ColumnOption) { + at := alterTable(table, nil) + at.RenameColumn(name, newName, options...) + s.add(at.Table) +} + +// DropColumn by name. +func (s *Schema) DropColumn(table string, name string, options ...ColumnOption) { + at := alterTable(table, nil) + at.DropColumn(name, options...) + s.add(at.Table) +} + +// AddIndex for columns. +func (s *Schema) AddIndex(table string, column []string, typ IndexType, options ...IndexOption) { + at := alterTable(table, nil) + at.Index(column, typ, options...) + s.add(at.Table) +} + +// RenameIndex by name. +func (s *Schema) RenameIndex(table string, name string, newName string, options ...IndexOption) { + at := alterTable(table, nil) + at.RenameIndex(name, newName, options...) + s.add(at.Table) +} + +// DropIndex by name. +func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { + at := alterTable(table, nil) + at.DropIndex(name, options...) + s.add(at.Table) +} + +// Exec queries using repo. +// Useful for data migration. +// func (s *Schema) Exec(func(repo rel.Repository) error) { +// } diff --git a/schema/schema_test.go b/schema/schema_test.go new file mode 100644 index 00000000..3aa3b247 --- /dev/null +++ b/schema/schema_test.go @@ -0,0 +1,169 @@ +package schema + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSchema_CreateTable(t *testing.T) { + var schema Schema + + schema.CreateTable("products", func(t *Table) { + t.Int("id") + t.String("name") + t.Text("description") + + t.PrimaryKey("id") + }) + + assert.Equal(t, Table{ + Op: Add, + Name: "products", + Definitions: []interface{}{ + Column{Name: "id", Type: Int}, + Column{Name: "name", Type: String}, + Column{Name: "description", Type: Text}, + Index{Columns: []string{"id"}, Type: PrimaryKey}, + }, + }, schema.Pending[0]) +} + +func TestSchema_AlterTable(t *testing.T) { + var schema Schema + + schema.AlterTable("users", func(t *AlterTable) { + t.Bool("verified") + t.RenameColumn("name", "fullname") + }) + + assert.Equal(t, Table{ + Op: Alter, + Name: "users", + Definitions: []interface{}{ + Column{Name: "verified", Type: Bool, Op: Add}, + Column{Name: "name", NewName: "fullname", Op: Rename}, + }, + }, schema.Pending[0]) +} + +func TestSchema_RenameTable(t *testing.T) { + var schema Schema + + schema.RenameTable("trxs", "transactions") + + assert.Equal(t, Table{ + Op: Rename, + Name: "trxs", + NewName: "transactions", + }, schema.Pending[0]) +} + +func TestSchema_DropTable(t *testing.T) { + var schema Schema + + schema.DropTable("logs") + + assert.Equal(t, Table{ + Op: Drop, + Name: "logs", + }, schema.Pending[0]) +} + +func TestSchema_AddColumn(t *testing.T) { + var schema Schema + + schema.AddColumn("products", "description", String) + + assert.Equal(t, Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Column{Name: "description", Type: String, Op: Add}, + }, + }, schema.Pending[0]) +} + +func TestSchema_AlterColumn(t *testing.T) { + var schema Schema + + schema.AlterColumn("products", "sale", Bool) + + assert.Equal(t, Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Column{Name: "sale", Type: Bool, Op: Alter}, + }, + }, schema.Pending[0]) +} + +func TestSchema_RenameColumn(t *testing.T) { + var schema Schema + + schema.RenameColumn("users", "name", "fullname") + + assert.Equal(t, Table{ + Op: Alter, + Name: "users", + Definitions: []interface{}{ + Column{Name: "name", NewName: "fullname", Op: Rename}, + }, + }, schema.Pending[0]) +} + +func TestSchema_DropColumn(t *testing.T) { + var schema Schema + + schema.DropColumn("users", "verified") + + assert.Equal(t, Table{ + Op: Alter, + Name: "users", + Definitions: []interface{}{ + Column{Name: "verified", Op: Drop}, + }, + }, schema.Pending[0]) +} + +func TestSchema_AddIndex(t *testing.T) { + var schema Schema + + schema.AddIndex("products", []string{"sale"}, SimpleIndex) + + assert.Equal(t, Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: Add}, + }, + }, schema.Pending[0]) +} + +func TestSchema_RenameIndex(t *testing.T) { + var schema Schema + + schema.RenameIndex("products", "store_id", "fk_store_id") + + assert.Equal(t, Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Index{Name: "store_id", NewName: "fk_store_id", Op: Rename}, + }, + }, schema.Pending[0]) +} + +func TestSchema_DropIndex(t *testing.T) { + var schema Schema + + schema.DropIndex("products", "sale") + + assert.Equal(t, Table{ + Op: Alter, + Name: "products", + Definitions: []interface{}{ + Index{Name: "sale", Op: Drop}, + }, + }, schema.Pending[0]) +} From fecc1948b764e392f9eae1bfc8b20f50d0e9b525 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 13:36:54 +0700 Subject: [PATCH 11/44] refactor dsl to root package --- adapter.go | 2 + adapter/sql/adapter.go | 143 ++++++++++++------------ adapter/sql/builder.go | 74 ++++++------ adapter/sql/builder_test.go | 91 ++++++++------- adapter_test.go | 5 + schema/column.go => column.go | 24 +++- schema/column_test.go => column_test.go | 8 +- schema/index.go => index.go | 24 +++- schema/index_test.go => index_test.go | 6 +- {schema => migration}/migration.go | 68 +++++------ migration/migration_test.go | 66 +++++++++++ query.go | 8 +- reltest/nop_adapter.go | 4 + reltest/repository.go | 2 +- schema/schema.go => schema.go | 115 ++++++++++++++++++- schema/adapter.go | 13 --- schema/op.go | 15 --- schema/options.go | 136 ---------------------- schema/schema_test.go => schema_test.go | 42 +++---- schema/table.go => table.go | 25 ++++- schema/table_test.go => table_test.go | 12 +- 21 files changed, 481 insertions(+), 402 deletions(-) rename schema/column.go => column.go (79%) rename schema/column_test.go => column_test.go (95%) rename schema/index.go => index.go (80%) rename schema/index_test.go => index_test.go (96%) rename {schema => migration}/migration.go (62%) create mode 100644 migration/migration_test.go rename schema/schema.go => schema.go (50%) delete mode 100644 schema/adapter.go delete mode 100644 schema/op.go delete mode 100644 schema/options.go rename schema/schema_test.go => schema_test.go (77%) rename schema/table.go => table.go (92%) rename schema/table_test.go => table_test.go (97%) diff --git a/adapter.go b/adapter.go index a5a1c45d..ab7c16f7 100644 --- a/adapter.go +++ b/adapter.go @@ -18,4 +18,6 @@ type Adapter interface { Begin(ctx context.Context) (Adapter, error) Commit(ctx context.Context) error Rollback(ctx context.Context) error + + Apply(ctx context.Context, table Table) error } diff --git a/adapter/sql/adapter.go b/adapter/sql/adapter.go index 59159210..d634f139 100644 --- a/adapter/sql/adapter.go +++ b/adapter/sql/adapter.go @@ -32,42 +32,42 @@ type Adapter struct { var _ rel.Adapter = (*Adapter)(nil) // Close database connection. -func (adapter *Adapter) Close() error { - return adapter.DB.Close() +func (a *Adapter) Close() error { + return a.DB.Close() } // Instrumentation set instrumenter for this adapter. -func (adapter *Adapter) Instrumentation(instrumenter rel.Instrumenter) { - adapter.Instrumenter = instrumenter +func (a *Adapter) Instrumentation(instrumenter rel.Instrumenter) { + a.Instrumenter = instrumenter } // Instrument call instrumenter, if no instrumenter is set, this will be a no op. -func (adapter *Adapter) Instrument(ctx context.Context, op string, message string) func(err error) { - if adapter.Instrumenter != nil { - return adapter.Instrumenter(ctx, op, message) +func (a *Adapter) Instrument(ctx context.Context, op string, message string) func(err error) { + if a.Instrumenter != nil { + return a.Instrumenter(ctx, op, message) } return func(err error) {} } // Ping database. -func (adapter *Adapter) Ping(ctx context.Context) error { - return adapter.DB.PingContext(ctx) +func (a *Adapter) Ping(ctx context.Context) error { + return a.DB.PingContext(ctx) } // Aggregate record using given query. -func (adapter *Adapter) Aggregate(ctx context.Context, query rel.Query, mode string, field string) (int, error) { +func (a *Adapter) Aggregate(ctx context.Context, query rel.Query, mode string, field string) (int, error) { var ( err error out sql.NullInt64 - statement, args = NewBuilder(adapter.Config).Aggregate(query, mode, field) + statement, args = NewBuilder(a.Config).Aggregate(query, mode, field) ) - finish := adapter.Instrument(ctx, "adapter-aggregate", statement) - if adapter.Tx != nil { - err = adapter.Tx.QueryRowContext(ctx, statement, args...).Scan(&out) + finish := a.Instrument(ctx, "adapter-aggregate", statement) + if a.Tx != nil { + err = a.Tx.QueryRowContext(ctx, statement, args...).Scan(&out) } else { - err = adapter.DB.QueryRowContext(ctx, statement, args...).Scan(&out) + err = a.DB.QueryRowContext(ctx, statement, args...).Scan(&out) } finish(err) @@ -75,34 +75,34 @@ func (adapter *Adapter) Aggregate(ctx context.Context, query rel.Query, mode str } // Query performs query operation. -func (adapter *Adapter) Query(ctx context.Context, query rel.Query) (rel.Cursor, error) { +func (a *Adapter) Query(ctx context.Context, query rel.Query) (rel.Cursor, error) { var ( - statement, args = NewBuilder(adapter.Config).Find(query) + statement, args = NewBuilder(a.Config).Find(query) ) - finish := adapter.Instrument(ctx, "adapter-query", statement) - rows, err := adapter.query(ctx, statement, args) + finish := a.Instrument(ctx, "adapter-query", statement) + rows, err := a.query(ctx, statement, args) finish(err) - return &Cursor{rows}, adapter.Config.ErrorFunc(err) + return &Cursor{rows}, a.Config.ErrorFunc(err) } -func (adapter *Adapter) query(ctx context.Context, statement string, args []interface{}) (*sql.Rows, error) { - if adapter.Tx != nil { - return adapter.Tx.QueryContext(ctx, statement, args...) +func (a *Adapter) query(ctx context.Context, statement string, args []interface{}) (*sql.Rows, error) { + if a.Tx != nil { + return a.Tx.QueryContext(ctx, statement, args...) } - return adapter.DB.QueryContext(ctx, statement, args...) + return a.DB.QueryContext(ctx, statement, args...) } // Exec performs exec operation. -func (adapter *Adapter) Exec(ctx context.Context, statement string, args []interface{}) (int64, int64, error) { - finish := adapter.Instrument(ctx, "adapter-exec", statement) - res, err := adapter.exec(ctx, statement, args) +func (a *Adapter) Exec(ctx context.Context, statement string, args []interface{}) (int64, int64, error) { + finish := a.Instrument(ctx, "adapter-exec", statement) + res, err := a.exec(ctx, statement, args) finish(err) if err != nil { - return 0, 0, adapter.Config.ErrorFunc(err) + return 0, 0, a.Config.ErrorFunc(err) } lastID, _ := res.LastInsertId() @@ -111,28 +111,28 @@ func (adapter *Adapter) Exec(ctx context.Context, statement string, args []inter return lastID, rowCount, nil } -func (adapter *Adapter) exec(ctx context.Context, statement string, args []interface{}) (sql.Result, error) { - if adapter.Tx != nil { - return adapter.Tx.ExecContext(ctx, statement, args...) +func (a *Adapter) exec(ctx context.Context, statement string, args []interface{}) (sql.Result, error) { + if a.Tx != nil { + return a.Tx.ExecContext(ctx, statement, args...) } - return adapter.DB.ExecContext(ctx, statement, args...) + return a.DB.ExecContext(ctx, statement, args...) } // Insert inserts a record to database and returns its id. -func (adapter *Adapter) Insert(ctx context.Context, query rel.Query, primaryField string, mutates map[string]rel.Mutate) (interface{}, error) { +func (a *Adapter) Insert(ctx context.Context, query rel.Query, primaryField string, mutates map[string]rel.Mutate) (interface{}, error) { var ( - statement, args = NewBuilder(adapter.Config).Insert(query.Table, mutates) - id, _, err = adapter.Exec(ctx, statement, args) + statement, args = NewBuilder(a.Config).Insert(query.Table, mutates) + id, _, err = a.Exec(ctx, statement, args) ) return id, err } // InsertAll inserts all record to database and returns its ids. -func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, primaryField string, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { - statement, args := NewBuilder(adapter.Config).InsertAll(query.Table, fields, bulkMutates) - id, _, err := adapter.Exec(ctx, statement, args) +func (a *Adapter) InsertAll(ctx context.Context, query rel.Query, primaryField string, fields []string, bulkMutates []map[string]rel.Mutate) ([]interface{}, error) { + statement, args := NewBuilder(a.Config).InsertAll(query.Table, fields, bulkMutates) + id, _, err := a.Exec(ctx, statement, args) if err != nil { return nil, err } @@ -142,8 +142,8 @@ func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, primaryF inc = 1 ) - if adapter.Config.IncrementFunc != nil { - inc = adapter.Config.IncrementFunc(*adapter) + if a.Config.IncrementFunc != nil { + inc = a.Config.IncrementFunc(*a) } if inc < 0 { @@ -169,89 +169,94 @@ func (adapter *Adapter) InsertAll(ctx context.Context, query rel.Query, primaryF } // Update updates a record in database. -func (adapter *Adapter) Update(ctx context.Context, query rel.Query, mutates map[string]rel.Mutate) (int, error) { +func (a *Adapter) Update(ctx context.Context, query rel.Query, mutates map[string]rel.Mutate) (int, error) { var ( - statement, args = NewBuilder(adapter.Config).Update(query.Table, mutates, query.WhereQuery) - _, updatedCount, err = adapter.Exec(ctx, statement, args) + statement, args = NewBuilder(a.Config).Update(query.Table, mutates, query.WhereQuery) + _, updatedCount, err = a.Exec(ctx, statement, args) ) return int(updatedCount), err } // Delete deletes all results that match the query. -func (adapter *Adapter) Delete(ctx context.Context, query rel.Query) (int, error) { +func (a *Adapter) Delete(ctx context.Context, query rel.Query) (int, error) { var ( - statement, args = NewBuilder(adapter.Config).Delete(query.Table, query.WhereQuery) - _, deletedCount, err = adapter.Exec(ctx, statement, args) + statement, args = NewBuilder(a.Config).Delete(query.Table, query.WhereQuery) + _, deletedCount, err = a.Exec(ctx, statement, args) ) return int(deletedCount), err } // Begin begins a new transaction. -func (adapter *Adapter) Begin(ctx context.Context) (rel.Adapter, error) { +func (a *Adapter) Begin(ctx context.Context) (rel.Adapter, error) { var ( tx *sql.Tx savepoint int err error ) - finish := adapter.Instrument(ctx, "adapter-begin", "begin transaction") + finish := a.Instrument(ctx, "adapter-begin", "begin transaction") - if adapter.Tx != nil { - tx = adapter.Tx - savepoint = adapter.savepoint + 1 - _, _, err = adapter.Exec(ctx, "SAVEPOINT s"+strconv.Itoa(savepoint)+";", []interface{}{}) + if a.Tx != nil { + tx = a.Tx + savepoint = a.savepoint + 1 + _, _, err = a.Exec(ctx, "SAVEPOINT s"+strconv.Itoa(savepoint)+";", []interface{}{}) } else { - tx, err = adapter.DB.BeginTx(ctx, nil) + tx, err = a.DB.BeginTx(ctx, nil) } finish(err) return &Adapter{ - Instrumenter: adapter.Instrumenter, - Config: adapter.Config, + Instrumenter: a.Instrumenter, + Config: a.Config, Tx: tx, savepoint: savepoint, }, err } // Commit commits current transaction. -func (adapter *Adapter) Commit(ctx context.Context) error { +func (a *Adapter) Commit(ctx context.Context) error { var err error - finish := adapter.Instrument(ctx, "adapter-commit", "commit transaction") + finish := a.Instrument(ctx, "adapter-commit", "commit transaction") - if adapter.Tx == nil { + if a.Tx == nil { err = errors.New("unable to commit outside transaction") - } else if adapter.savepoint > 0 { - _, _, err = adapter.Exec(ctx, "RELEASE SAVEPOINT s"+strconv.Itoa(adapter.savepoint)+";", []interface{}{}) + } else if a.savepoint > 0 { + _, _, err = a.Exec(ctx, "RELEASE SAVEPOINT s"+strconv.Itoa(a.savepoint)+";", []interface{}{}) } else { - err = adapter.Tx.Commit() + err = a.Tx.Commit() } finish(err) - return adapter.Config.ErrorFunc(err) + return a.Config.ErrorFunc(err) } // Rollback revert current transaction. -func (adapter *Adapter) Rollback(ctx context.Context) error { +func (a *Adapter) Rollback(ctx context.Context) error { var err error - finish := adapter.Instrument(ctx, "adapter-rollback", "rollback transaction") + finish := a.Instrument(ctx, "adapter-rollback", "rollback transaction") - if adapter.Tx == nil { + if a.Tx == nil { err = errors.New("unable to rollback outside transaction") - } else if adapter.savepoint > 0 { - _, _, err = adapter.Exec(ctx, "ROLLBACK TO SAVEPOINT s"+strconv.Itoa(adapter.savepoint)+";", []interface{}{}) + } else if a.savepoint > 0 { + _, _, err = a.Exec(ctx, "ROLLBACK TO SAVEPOINT s"+strconv.Itoa(a.savepoint)+";", []interface{}{}) } else { - err = adapter.Tx.Rollback() + err = a.Tx.Rollback() } finish(err) - return adapter.Config.ErrorFunc(err) + return a.Config.ErrorFunc(err) +} + +// Apply table. +func (a *Adapter) Apply(ctx context.Context, table rel.Table) error { + return nil } // New initialize adapter without db. diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 5ca27e08..b3af4ac6 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -7,7 +7,6 @@ import ( "sync" "github.com/Fs02/rel" - "github.com/Fs02/rel/schema" ) // UnescapeCharacter disable field escaping when it starts with this character. @@ -23,21 +22,21 @@ type Builder struct { } // Table generates query for table creation and modification. -func (b *Builder) Table(table schema.Table) string { +func (b *Builder) Table(table rel.Table) string { var buffer Buffer switch table.Op { - case schema.Add: + case rel.SchemaAdd: b.createTable(&buffer, table) - case schema.Alter: + case rel.SchemaAlter: b.alterTable(&buffer, table) - case schema.Rename: + case rel.SchemaRename: buffer.WriteString("RENAME TABLE ") buffer.WriteString(b.escape(table.Name)) buffer.WriteString(" TO ") buffer.WriteString(b.escape(table.NewName)) buffer.WriteByte(';') - case schema.Drop: + case rel.SchemaDrop: buffer.WriteString("DROP TABLE ") buffer.WriteString(b.escape(table.Name)) buffer.WriteByte(';') @@ -46,8 +45,13 @@ func (b *Builder) Table(table schema.Table) string { return buffer.String() } -func (b *Builder) createTable(buffer *Buffer, table schema.Table) { +func (b *Builder) createTable(buffer *Buffer, table rel.Table) { buffer.WriteString("CREATE TABLE ") + + if table.IfNotExists { + buffer.WriteString("IF NOT EXISTS ") + } + buffer.WriteString(b.escape(table.Name)) buffer.WriteString(" (") @@ -56,9 +60,9 @@ func (b *Builder) createTable(buffer *Buffer, table schema.Table) { buffer.WriteString(", ") } switch v := def.(type) { - case schema.Column: + case rel.Column: b.column(buffer, v) - case schema.Index: + case rel.Index: b.index(buffer, v) } } @@ -69,7 +73,7 @@ func (b *Builder) createTable(buffer *Buffer, table schema.Table) { buffer.WriteByte(';') } -func (b *Builder) alterTable(buffer *Buffer, table schema.Table) { +func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { buffer.WriteString("ALTER TABLE ") buffer.WriteString(b.escape(table.Name)) buffer.WriteByte(' ') @@ -80,35 +84,35 @@ func (b *Builder) alterTable(buffer *Buffer, table schema.Table) { } switch v := def.(type) { - case schema.Column: + case rel.Column: switch v.Op { - case schema.Add: + case rel.SchemaAdd: buffer.WriteString("ADD COLUMN ") b.column(buffer, v) - case schema.Alter: // TODO: use modify keyword? + case rel.SchemaAlter: // TODO: use modify keyword? buffer.WriteString("MODIFY COLUMN ") b.column(buffer, v) - case schema.Rename: + case rel.SchemaRename: // Add Change buffer.WriteString("RENAME COLUMN ") buffer.WriteString(b.escape(v.Name)) buffer.WriteString(" TO ") buffer.WriteString(b.escape(v.NewName)) - case schema.Drop: + case rel.SchemaDrop: buffer.WriteString("DROP COLUMN ") buffer.WriteString(b.escape(v.Name)) } - case schema.Index: + case rel.Index: switch v.Op { - case schema.Add: + case rel.SchemaAdd: buffer.WriteString("ADD ") b.index(buffer, v) - case schema.Rename: + case rel.SchemaRename: buffer.WriteString("RENAME INDEX ") buffer.WriteString(b.escape(v.Name)) buffer.WriteString(" TO ") buffer.WriteString(b.escape(v.NewName)) - case schema.Drop: + case rel.SchemaDrop: buffer.WriteString("DROP INDEX ") buffer.WriteString(b.escape(v.Name)) } @@ -119,7 +123,7 @@ func (b *Builder) alterTable(buffer *Buffer, table schema.Table) { buffer.WriteByte(';') } -func (b *Builder) column(buffer *Buffer, column schema.Column) { +func (b *Builder) column(buffer *Buffer, column rel.Column) { var ( typ, m, n = b.mapColumnType(column) ) @@ -159,47 +163,47 @@ func (b *Builder) column(buffer *Buffer, column schema.Column) { b.options(buffer, column.Options) } -func (b *Builder) mapColumnType(column schema.Column) (string, int, int) { +func (b *Builder) mapColumnType(column rel.Column) (string, int, int) { var ( typ string m, n int ) switch column.Type { - case schema.Bool: + case rel.Bool: typ = "BOOL" - case schema.Int: + case rel.Int: typ = "INT" m = column.Limit - case schema.BigInt: + case rel.BigInt: typ = "BIGINT" m = column.Limit - case schema.Float: + case rel.Float: typ = "FLOAT" m = column.Precision - case schema.Decimal: + case rel.Decimal: typ = "DECIMAL" m = column.Precision n = column.Scale - case schema.String: + case rel.String: typ = "VARCHAR" m = column.Limit if m == 0 { m = 255 } - case schema.Text: + case rel.Text: typ = "TEXT" m = column.Limit - case schema.Binary: + case rel.Binary: typ = "BINARY" m = column.Limit - case schema.Date: + case rel.Date: typ = "DATE" - case schema.DateTime: + case rel.DateTime: typ = "DATETIME" - case schema.Time: + case rel.Time: typ = "TIME" - case schema.Timestamp: + case rel.Timestamp: // TODO: mysql automatically add on update options. typ = "TIMESTAMP" default: @@ -209,7 +213,7 @@ func (b *Builder) mapColumnType(column schema.Column) (string, int, int) { return typ, m, n } -func (b *Builder) index(buffer *Buffer, index schema.Index) { +func (b *Builder) index(buffer *Buffer, index rel.Index) { var ( typ = string(index.Type) ) @@ -232,7 +236,7 @@ func (b *Builder) index(buffer *Buffer, index schema.Index) { } buffer.WriteString(")") - if index.Type == schema.ForeignKey { + if index.Type == rel.ForeignKey { buffer.WriteString(" REFERENCES ") buffer.WriteString(b.escape(index.Reference.Table)) diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 939a7eaf..6791db4f 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/Fs02/rel" - "github.com/Fs02/rel/schema" "github.com/Fs02/rel/sort" "github.com/Fs02/rel/where" "github.com/stretchr/testify/assert" @@ -43,44 +42,44 @@ func TestBuilder_Table(t *testing.T) { tests := []struct { result string - table schema.Table + table rel.Table }{ { result: "CREATE TABLE `products` (`id` INT, `name` VARCHAR(255), `description` TEXT, PRIMARY KEY (`id`));", - table: schema.Table{ - Op: schema.Add, + table: rel.Table{ + Op: rel.SchemaAdd, Name: "products", Definitions: []interface{}{ - schema.Column{Name: "id", Type: schema.Int}, - schema.Column{Name: "name", Type: schema.String}, - schema.Column{Name: "description", Type: schema.Text}, - schema.Index{Columns: []string{"id"}, Type: schema.PrimaryKey}, + rel.Column{Name: "id", Type: rel.Int}, + rel.Column{Name: "name", Type: rel.String}, + rel.Column{Name: "description", Type: rel.Text}, + rel.Index{Columns: []string{"id"}, Type: rel.PrimaryKey}, }, }, }, { result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `binary` BINARY(255), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE INDEX `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) COMMENT 'TEST' Engine=InnoDB;", - table: schema.Table{ - Op: schema.Add, + table: rel.Table{ + Op: rel.SchemaAdd, Name: "columns", Definitions: []interface{}{ - schema.Column{Name: "bool", Type: schema.Bool, Required: true, Default: false}, - schema.Column{Name: "int", Type: schema.Int, Limit: 11, Unsigned: true}, - schema.Column{Name: "bigint", Type: schema.BigInt, Limit: 20, Unsigned: true}, - schema.Column{Name: "float", Type: schema.Float, Precision: 24, Unsigned: true}, - schema.Column{Name: "decimal", Type: schema.Decimal, Precision: 6, Scale: 2, Unsigned: true}, - schema.Column{Name: "string", Type: schema.String, Limit: 144}, - schema.Column{Name: "text", Type: schema.Text, Limit: 1000}, - schema.Column{Name: "binary", Type: schema.Binary, Limit: 255}, - schema.Column{Name: "date", Type: schema.Date}, - schema.Column{Name: "datetime", Type: schema.DateTime}, - schema.Column{Name: "time", Type: schema.Time}, - schema.Column{Name: "timestamp", Type: schema.Timestamp}, - schema.Column{Name: "blob", Type: "blob"}, - schema.Index{Columns: []string{"int"}, Type: schema.PrimaryKey}, - schema.Index{Columns: []string{"date"}, Type: schema.UniqueIndex, Name: "date_unique"}, - schema.Index{Columns: []string{"datetime"}, Type: schema.SimpleIndex}, - schema.Index{Columns: []string{"int", "string"}, Type: schema.ForeignKey, Reference: schema.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, + rel.Column{Name: "bool", Type: rel.Bool, Required: true, Default: false}, + rel.Column{Name: "int", Type: rel.Int, Limit: 11, Unsigned: true}, + rel.Column{Name: "bigint", Type: rel.BigInt, Limit: 20, Unsigned: true}, + rel.Column{Name: "float", Type: rel.Float, Precision: 24, Unsigned: true}, + rel.Column{Name: "decimal", Type: rel.Decimal, Precision: 6, Scale: 2, Unsigned: true}, + rel.Column{Name: "string", Type: rel.String, Limit: 144}, + rel.Column{Name: "text", Type: rel.Text, Limit: 1000}, + rel.Column{Name: "binary", Type: rel.Binary, Limit: 255}, + rel.Column{Name: "date", Type: rel.Date}, + rel.Column{Name: "datetime", Type: rel.DateTime}, + rel.Column{Name: "time", Type: rel.Time}, + rel.Column{Name: "timestamp", Type: rel.Timestamp}, + rel.Column{Name: "blob", Type: "blob"}, + rel.Index{Columns: []string{"int"}, Type: rel.PrimaryKey}, + rel.Index{Columns: []string{"date"}, Type: rel.UniqueIndex, Name: "date_unique"}, + rel.Index{Columns: []string{"datetime"}, Type: rel.SimpleIndex}, + rel.Index{Columns: []string{"int", "string"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, }, Options: "Engine=InnoDB", Comment: "TEST", @@ -88,59 +87,59 @@ func TestBuilder_Table(t *testing.T) { }, { result: "ALTER TABLE `columns` ADD COLUMN `verified` BOOL, RENAME COLUMN `string` TO `name`, MODIFY COLUMN `bool` INT, DROP COLUMN `blob`;", - table: schema.Table{ - Op: schema.Alter, + table: rel.Table{ + Op: rel.SchemaAlter, Name: "columns", Definitions: []interface{}{ - schema.Column{Name: "verified", Type: schema.Bool, Op: schema.Add}, - schema.Column{Name: "string", NewName: "name", Op: schema.Rename}, - schema.Column{Name: "bool", Type: schema.Int, Op: schema.Alter}, - schema.Column{Name: "blob", Op: schema.Drop}, + rel.Column{Name: "verified", Type: rel.Bool, Op: rel.SchemaAdd}, + rel.Column{Name: "string", NewName: "name", Op: rel.SchemaRename}, + rel.Column{Name: "bool", Type: rel.Int, Op: rel.SchemaAlter}, + rel.Column{Name: "blob", Op: rel.SchemaDrop}, }, }, }, { result: "ALTER TABLE `columns` ADD INDEX `verified_int` (`verified`, `int`);", - table: schema.Table{ - Op: schema.Alter, + table: rel.Table{ + Op: rel.SchemaAlter, Name: "columns", Definitions: []interface{}{ - schema.Index{Name: "verified_int", Columns: []string{"verified", "int"}, Type: schema.SimpleIndex, Op: schema.Add}, + rel.Index{Name: "verified_int", Columns: []string{"verified", "int"}, Type: rel.SimpleIndex, Op: rel.SchemaAdd}, }, }, }, { result: "ALTER TABLE `columns` RENAME INDEX `verified_int` TO `verified_int_index`;", - table: schema.Table{ - Op: schema.Alter, + table: rel.Table{ + Op: rel.SchemaAlter, Name: "columns", Definitions: []interface{}{ - schema.Index{Name: "verified_int", NewName: "verified_int_index", Op: schema.Rename}, + rel.Index{Name: "verified_int", NewName: "verified_int_index", Op: rel.SchemaRename}, }, }, }, { result: "ALTER TABLE `columns` DROP INDEX `verified_int_index`;", - table: schema.Table{ - Op: schema.Alter, + table: rel.Table{ + Op: rel.SchemaAlter, Name: "columns", Definitions: []interface{}{ - schema.Index{Name: "verified_int_index", Op: schema.Drop}, + rel.Index{Name: "verified_int_index", Op: rel.SchemaDrop}, }, }, }, { result: "RENAME TABLE `columns` TO `definitions`;", - table: schema.Table{ - Op: schema.Rename, + table: rel.Table{ + Op: rel.SchemaRename, Name: "columns", NewName: "definitions", }, }, { result: "DROP TABLE `columns`;", - table: schema.Table{ - Op: schema.Drop, + table: rel.Table{ + Op: rel.SchemaDrop, Name: "columns", }, }, diff --git a/adapter_test.go b/adapter_test.go index c8a87048..a0e0e03b 100644 --- a/adapter_test.go +++ b/adapter_test.go @@ -76,6 +76,11 @@ func (ta *testAdapter) Rollback(ctx context.Context) error { return args.Error(0) } +func (ta *testAdapter) Apply(ctx context.Context, table Table) error { + args := ta.Called(table) + return args.Error(0) +} + func (ta *testAdapter) Result(result interface{}) *testAdapter { ta.result = result return ta diff --git a/schema/column.go b/column.go similarity index 79% rename from schema/column.go rename to column.go index 0929753b..f12841f8 100644 --- a/schema/column.go +++ b/column.go @@ -1,4 +1,4 @@ -package schema +package rel // ColumnType definition. type ColumnType string @@ -32,7 +32,7 @@ const ( // Column definition. type Column struct { - Op Op + Op SchemaOp Name string Type ColumnType NewName string @@ -48,7 +48,7 @@ type Column struct { func addColumn(name string, typ ColumnType, options []ColumnOption) Column { column := Column{ - Op: Add, + Op: SchemaAdd, Name: name, Type: typ, } @@ -59,7 +59,7 @@ func addColumn(name string, typ ColumnType, options []ColumnOption) Column { func alterColumn(name string, typ ColumnType, options []ColumnOption) Column { column := Column{ - Op: Alter, + Op: SchemaAlter, Name: name, Type: typ, } @@ -70,7 +70,7 @@ func alterColumn(name string, typ ColumnType, options []ColumnOption) Column { func renameColumn(name string, newName string, options []ColumnOption) Column { column := Column{ - Op: Rename, + Op: SchemaRename, Name: name, NewName: newName, } @@ -81,10 +81,22 @@ func renameColumn(name string, newName string, options []ColumnOption) Column { func dropColumn(name string, options []ColumnOption) Column { column := Column{ - Op: Drop, + Op: SchemaDrop, Name: name, } applyColumnOptions(&column, options) return column } + +// ColumnOption interface. +// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. +type ColumnOption interface { + applyColumn(column *Column) +} + +func applyColumnOptions(column *Column, options []ColumnOption) { + for i := range options { + options[i].applyColumn(column) + } +} diff --git a/schema/column_test.go b/column_test.go similarity index 95% rename from schema/column_test.go rename to column_test.go index 1770dafd..c2c964fa 100644 --- a/schema/column_test.go +++ b/column_test.go @@ -1,4 +1,4 @@ -package schema +package rel import ( "testing" @@ -51,7 +51,7 @@ func TestAlterColumn(t *testing.T) { ) assert.Equal(t, Column{ - Op: Alter, + Op: SchemaAlter, Name: "alter", Type: Decimal, Required: true, @@ -81,7 +81,7 @@ func TestRenameColumn(t *testing.T) { ) assert.Equal(t, Column{ - Op: Rename, + Op: SchemaRename, Name: "add", NewName: "rename", Required: true, @@ -111,7 +111,7 @@ func TestDropColumn(t *testing.T) { ) assert.Equal(t, Column{ - Op: Drop, + Op: SchemaDrop, Name: "drop", Required: true, Unsigned: true, diff --git a/schema/index.go b/index.go similarity index 80% rename from schema/index.go rename to index.go index 6e16e057..2a584d2f 100644 --- a/schema/index.go +++ b/index.go @@ -1,4 +1,4 @@ -package schema +package rel // IndexType definition. type IndexType string @@ -24,7 +24,7 @@ type ForeignKeyReference struct { // Index definition. type Index struct { - Op Op + Op SchemaOp Name string Type IndexType Columns []string @@ -36,7 +36,7 @@ type Index struct { func addIndex(columns []string, typ IndexType, options []IndexOption) Index { index := Index{ - Op: Add, + Op: SchemaAdd, Columns: columns, Type: typ, } @@ -48,7 +48,7 @@ func addIndex(columns []string, typ IndexType, options []IndexOption) Index { // TODO: support multi columns func addForeignKey(column string, refTable string, refColumn string, options []IndexOption) Index { index := Index{ - Op: Add, + Op: SchemaAdd, Type: ForeignKey, Columns: []string{column}, Reference: ForeignKeyReference{ @@ -63,7 +63,7 @@ func addForeignKey(column string, refTable string, refColumn string, options []I func renameIndex(name string, newName string, options []IndexOption) Index { index := Index{ - Op: Rename, + Op: SchemaRename, Name: name, NewName: newName, } @@ -74,10 +74,22 @@ func renameIndex(name string, newName string, options []IndexOption) Index { func dropIndex(name string, options []IndexOption) Index { index := Index{ - Op: Drop, + Op: SchemaDrop, Name: name, } applyIndexOptions(&index, options) return index } + +// IndexOption interface. +// Available options are: Comment, Options. +type IndexOption interface { + applyIndex(index *Index) +} + +func applyIndexOptions(index *Index, options []IndexOption) { + for i := range options { + options[i].applyIndex(index) + } +} diff --git a/schema/index_test.go b/index_test.go similarity index 96% rename from schema/index_test.go rename to index_test.go index fdc9445f..a0af73c8 100644 --- a/schema/index_test.go +++ b/index_test.go @@ -1,4 +1,4 @@ -package schema +package rel import ( "testing" @@ -62,7 +62,7 @@ func TestRenameIndex(t *testing.T) { ) assert.Equal(t, Index{ - Op: Rename, + Op: SchemaRename, Name: "add", NewName: "rename", Comment: "comment", @@ -80,7 +80,7 @@ func TestDropIndex(t *testing.T) { ) assert.Equal(t, Index{ - Op: Drop, + Op: SchemaDrop, Name: "drop", Comment: "comment", Options: "options", diff --git a/schema/migration.go b/migration/migration.go similarity index 62% rename from schema/migration.go rename to migration/migration.go index eea123f2..0798536f 100644 --- a/schema/migration.go +++ b/migration/migration.go @@ -1,4 +1,4 @@ -package schema +package migration import ( "context" @@ -18,67 +18,68 @@ func (schemaVersion) Table() string { return schemaVersionTable } -// MigrationStep definition. -type MigrationStep struct { +// Step definition. +type Step struct { Version int - Up Schema - Down Schema + Up rel.Schema + Down rel.Schema Applied bool } -// MigrationSteps definition. -type MigrationSteps []MigrationStep +// Steps definition. +type Steps []Step -func (ms MigrationSteps) Len() int { - return len(ms) +func (s Steps) Len() int { + return len(s) } -func (ms MigrationSteps) Less(i, j int) bool { - return ms[i].Version < ms[j].Version +func (s Steps) Less(i, j int) bool { + return s[i].Version < s[j].Version } -func (ms MigrationSteps) Swap(i, j int) { - ms[i], ms[j] = ms[j], ms[i] +func (s Steps) Swap(i, j int) { + s[i], s[j] = s[j], s[i] } // Migration manager definition. type Migration struct { - Adapter Adapter Repository rel.Repository - Steps MigrationSteps + Steps Steps } // AddVersion adds a migration with explicit version. -func (m *Migration) AddVersion(version int, up func(schema *Schema), down func(schema *Schema)) { - var upSchema, downSchema Schema +func (m *Migration) AddVersion(version int, up func(schema *rel.Schema), down func(schema *rel.Schema)) { + var upSchema, downSchema rel.Schema up(&upSchema) down(&downSchema) - m.Steps = append(m.Steps, MigrationStep{Version: version, Up: upSchema, Down: downSchema}) + m.Steps = append(m.Steps, Step{Version: version, Up: upSchema, Down: downSchema}) } -func (m Migration) buildVersionTableDefinition() Table { - var versionTable = createTable(schemaVersionTable, nil) // TODO: create if not exists +func (m Migration) buildVersionTableDefinition() rel.Table { + var schema rel.Schema + schema.CreateTable(schemaVersionTable, func(t *rel.Table) { + t.Int("id") + t.Int("version") + t.DateTime("created_at") + t.DateTime("updated_at") - versionTable.Int("id") - versionTable.Int("version") - versionTable.DateTime("created_at") - versionTable.DateTime("updated_at") + t.PrimaryKey("id") + t.Unique([]string{"version"}) + }, rel.IfNotExists(true)) - versionTable.PrimaryKey("id") - versionTable.Unique([]string{"version"}) - - return versionTable + return schema.Pending[0].(rel.Table) } func (m *Migration) sync(ctx context.Context) { var ( versions []schemaVersion vi int + adapter = m.Repository.Adapter(ctx).(rel.Adapter) ) - check(m.Adapter.Apply(ctx, m.buildVersionTableDefinition())) + check(adapter.Apply(ctx, m.buildVersionTableDefinition())) m.Repository.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) sort.Sort(m.Steps) @@ -110,10 +111,11 @@ func (m *Migration) Up(ctx context.Context) { m.Repository.Transaction(ctx, func(ctx context.Context) error { m.Repository.MustInsert(ctx, &schemaVersion{Version: step.Version}) - adapter := m.Repository.Adapter(ctx).(Adapter) + adapter := m.Repository.Adapter(ctx).(rel.Adapter) for _, migrator := range step.Up.Pending { + // TODO: exec script switch v := migrator.(type) { - case Table: + case rel.Table: check(adapter.Apply(ctx, v)) } } @@ -136,10 +138,10 @@ func (m *Migration) Down(ctx context.Context) { m.Repository.Transaction(ctx, func(ctx context.Context) error { m.Repository.MustInsert(ctx, &schemaVersion{Version: step.Version}) - adapter := m.Repository.Adapter(ctx).(Adapter) + adapter := m.Repository.Adapter(ctx).(rel.Adapter) for _, migrator := range step.Down.Pending { switch v := migrator.(type) { - case Table: + case rel.Table: check(adapter.Apply(ctx, v)) } } diff --git a/migration/migration_test.go b/migration/migration_test.go new file mode 100644 index 00000000..b819890f --- /dev/null +++ b/migration/migration_test.go @@ -0,0 +1,66 @@ +package migration + +import ( + "testing" + + "github.com/Fs02/rel" + "github.com/Fs02/rel/reltest" + "github.com/stretchr/testify/assert" +) + +func TestSchema_Migration(t *testing.T) { + var ( + // ctx = context.TODO() + repo = reltest.New() + migration = Migration{ + Repository: repo, + } + ) + + t.Run("AddVersion", func(t *testing.T) { + migration.AddVersion(20200829114000, + func(schema *rel.Schema) { + schema.CreateTable("users", func(t *rel.Table) { + t.Int("id") + t.PrimaryKey("id") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("users") + }, + ) + + migration.AddVersion(20200828100000, + func(schema *rel.Schema) { + schema.CreateTable("tags", func(t *rel.Table) { + t.Int("id") + t.PrimaryKey("id") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("tags") + }, + ) + + migration.AddVersion(20200829115100, + func(schema *rel.Schema) { + schema.CreateTable("books", func(t *rel.Table) { + t.Int("id") + t.PrimaryKey("id") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("books") + }, + ) + + assert.Len(t, migration.Steps, 3) + assert.Equal(t, 20200829114000, migration.Steps[0].Version) + assert.Equal(t, 20200828100000, migration.Steps[1].Version) + assert.Equal(t, 20200829115100, migration.Steps[2].Version) + }) + + // t.Run("Up", func(t *testing.T) { + // migration.Up(ctx) + // }) +} diff --git a/query.go b/query.go index a8d8d354..5aaffe8e 100644 --- a/query.go +++ b/query.go @@ -336,7 +336,9 @@ func (o Offset) Build(query *Query) { query.OffsetQuery = o } -// Limit query. +// Limit options. +// When passed as query, it limits returned result from database. +// When passed as column option, it sets the maximum size of the string/text/binary/integer columns. type Limit int // Build query. @@ -344,6 +346,10 @@ func (l Limit) Build(query *Query) { query.LimitQuery = l } +func (l Limit) applyColumn(column *Column) { + column.Limit = int(l) +} + // Lock query. // This query will be ignored if used outside of transaction. type Lock string diff --git a/reltest/nop_adapter.go b/reltest/nop_adapter.go index d7dc555c..9cabab98 100644 --- a/reltest/nop_adapter.go +++ b/reltest/nop_adapter.go @@ -61,6 +61,10 @@ func (na *nopAdapter) Update(ctx context.Context, query rel.Query, mutates map[s return 1, nil } +func (na *nopAdapter) Apply(ctx context.Context, table rel.Table) error { + return nil +} + type nopCursor struct { count int } diff --git a/reltest/repository.go b/reltest/repository.go index 17fb8685..3fe770d5 100644 --- a/reltest/repository.go +++ b/reltest/repository.go @@ -20,7 +20,7 @@ var _ rel.Repository = (*Repository)(nil) // Adapter provides a mock function with given fields: func (r *Repository) Adapter(ctx context.Context) rel.Adapter { - return nil + return r.repo.Adapter(ctx) } // Instrumentation provides a mock function with given fields: instrumenter diff --git a/schema/schema.go b/schema.go similarity index 50% rename from schema/schema.go rename to schema.go index 20df35a7..e8f3dcb9 100644 --- a/schema/schema.go +++ b/schema.go @@ -1,4 +1,18 @@ -package schema +package rel + +// SchemaOp type. +type SchemaOp uint8 + +const ( + // SchemaAdd operation. + SchemaAdd SchemaOp = iota + // SchemaAlter operation. + SchemaAlter + // SchemaRename operation. + SchemaRename + // SchemaDrop operation. + SchemaDrop +) // Migrator private interface. type Migrator interface { @@ -91,3 +105,102 @@ func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { // Useful for data migration. // func (s *Schema) Exec(func(repo rel.Repository) error) { // } + +// Comment options for table, column and index. +type Comment string + +func (c Comment) applyTable(table *Table) { + table.Comment = string(c) +} + +func (c Comment) applyColumn(column *Column) { + column.Comment = string(c) +} + +func (c Comment) applyIndex(index *Index) { + index.Comment = string(c) +} + +// Options options for table, column and index. +type Options string + +func (o Options) applyTable(table *Table) { + table.Options = string(o) +} + +func (o Options) applyColumn(column *Column) { + column.Options = string(o) +} + +func (o Options) applyIndex(index *Index) { + index.Options = string(o) +} + +// Required disallows nil values in the column. +type Required bool + +func (r Required) applyColumn(column *Column) { + column.Required = bool(r) +} + +// Unsigned sets integer column to be unsigned. +type Unsigned bool + +func (u Unsigned) applyColumn(column *Column) { + column.Unsigned = bool(u) +} + +// Precision defines the precision for the decimal fields, representing the total number of digits in the number. +type Precision int + +func (p Precision) applyColumn(column *Column) { + column.Precision = int(p) +} + +// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. +type Scale int + +func (s Scale) applyColumn(column *Column) { + column.Scale = int(s) +} + +type defaultValue struct { + value interface{} +} + +func (d defaultValue) applyColumn(column *Column) { + column.Default = d.value +} + +// Default allows to set a default value on the column.). +func Default(def interface{}) ColumnOption { + return defaultValue{value: def} +} + +// Name option for defining custom index name. +type Name string + +func (n Name) applyIndex(index *Index) { + index.Name = string(n) +} + +// OnDelete option for foreign key index. +type OnDelete string + +func (od OnDelete) applyIndex(index *Index) { + index.Reference.OnDelete = string(od) +} + +// OnUpdate option for foreign key index. +type OnUpdate string + +func (ou OnUpdate) applyIndex(index *Index) { + index.Reference.OnUpdate = string(ou) +} + +// IfNotExists option for table creation. +type IfNotExists bool + +func (ine IfNotExists) applyTable(table *Table) { + table.IfNotExists = bool(ine) +} diff --git a/schema/adapter.go b/schema/adapter.go deleted file mode 100644 index 155240c5..00000000 --- a/schema/adapter.go +++ /dev/null @@ -1,13 +0,0 @@ -package schema - -import ( - "context" - - "github.com/Fs02/rel" -) - -// Adapter interface -type Adapter interface { - rel.Adapter - Apply(ctx context.Context, table Table) error -} diff --git a/schema/op.go b/schema/op.go deleted file mode 100644 index f2ec313e..00000000 --- a/schema/op.go +++ /dev/null @@ -1,15 +0,0 @@ -package schema - -// Op type. -type Op uint8 - -const ( - // Add operation. - Add Op = iota - // Alter operation. - Alter - // Rename operation. - Rename - // Drop operation. - Drop -) diff --git a/schema/options.go b/schema/options.go deleted file mode 100644 index 5b7b73db..00000000 --- a/schema/options.go +++ /dev/null @@ -1,136 +0,0 @@ -package schema - -// TableOption interface. -// Available options are: Comment, Options. -type TableOption interface { - applyTable(table *Table) -} - -func applyTableOptions(table *Table, options []TableOption) { - for i := range options { - options[i].applyTable(table) - } -} - -// ColumnOption interface. -// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. -type ColumnOption interface { - applyColumn(column *Column) -} - -func applyColumnOptions(column *Column, options []ColumnOption) { - for i := range options { - options[i].applyColumn(column) - } -} - -// IndexOption interface. -// Available options are: Comment, Options. -type IndexOption interface { - applyIndex(index *Index) -} - -func applyIndexOptions(index *Index, options []IndexOption) { - for i := range options { - options[i].applyIndex(index) - } -} - -// Comment options for table, column and index. -type Comment string - -func (c Comment) applyTable(table *Table) { - table.Comment = string(c) -} - -func (c Comment) applyColumn(column *Column) { - column.Comment = string(c) -} - -func (c Comment) applyIndex(index *Index) { - index.Comment = string(c) -} - -// Options options for table, column and index. -type Options string - -func (o Options) applyTable(table *Table) { - table.Options = string(o) -} - -func (o Options) applyColumn(column *Column) { - column.Options = string(o) -} - -func (o Options) applyIndex(index *Index) { - index.Options = string(o) -} - -// Required disallows nil values in the column. -type Required bool - -func (r Required) applyColumn(column *Column) { - column.Required = bool(r) -} - -// Unsigned sets integer column to be unsigned. -type Unsigned bool - -func (u Unsigned) applyColumn(column *Column) { - column.Unsigned = bool(u) -} - -// Limit sets the maximum size of the string/text/binary/integer columns. -type Limit int - -func (l Limit) applyColumn(column *Column) { - column.Limit = int(l) -} - -// Precision defines the precision for the decimal fields, representing the total number of digits in the number. -type Precision int - -func (p Precision) applyColumn(column *Column) { - column.Precision = int(p) -} - -// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. -type Scale int - -func (s Scale) applyColumn(column *Column) { - column.Scale = int(s) -} - -type defaultValue struct { - value interface{} -} - -func (d defaultValue) applyColumn(column *Column) { - column.Default = d.value -} - -// Default allows to set a default value on the column.). -func Default(def interface{}) ColumnOption { - return defaultValue{value: def} -} - -// Name option for defining custom index name. -type Name string - -func (n Name) applyIndex(index *Index) { - index.Name = string(n) -} - -// OnDelete option for foreign key index. -type OnDelete string - -func (od OnDelete) applyIndex(index *Index) { - index.Reference.OnDelete = string(od) -} - -// OnUpdate option for foreign key index. -type OnUpdate string - -func (ou OnUpdate) applyIndex(index *Index) { - index.Reference.OnUpdate = string(ou) -} diff --git a/schema/schema_test.go b/schema_test.go similarity index 77% rename from schema/schema_test.go rename to schema_test.go index 3aa3b247..4f382cc4 100644 --- a/schema/schema_test.go +++ b/schema_test.go @@ -1,4 +1,4 @@ -package schema +package rel import ( "testing" @@ -18,7 +18,7 @@ func TestSchema_CreateTable(t *testing.T) { }) assert.Equal(t, Table{ - Op: Add, + Op: SchemaAdd, Name: "products", Definitions: []interface{}{ Column{Name: "id", Type: Int}, @@ -38,11 +38,11 @@ func TestSchema_AlterTable(t *testing.T) { }) assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "users", Definitions: []interface{}{ - Column{Name: "verified", Type: Bool, Op: Add}, - Column{Name: "name", NewName: "fullname", Op: Rename}, + Column{Name: "verified", Type: Bool, Op: SchemaAdd}, + Column{Name: "name", NewName: "fullname", Op: SchemaRename}, }, }, schema.Pending[0]) } @@ -53,7 +53,7 @@ func TestSchema_RenameTable(t *testing.T) { schema.RenameTable("trxs", "transactions") assert.Equal(t, Table{ - Op: Rename, + Op: SchemaRename, Name: "trxs", NewName: "transactions", }, schema.Pending[0]) @@ -65,7 +65,7 @@ func TestSchema_DropTable(t *testing.T) { schema.DropTable("logs") assert.Equal(t, Table{ - Op: Drop, + Op: SchemaDrop, Name: "logs", }, schema.Pending[0]) } @@ -76,10 +76,10 @@ func TestSchema_AddColumn(t *testing.T) { schema.AddColumn("products", "description", String) assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "products", Definitions: []interface{}{ - Column{Name: "description", Type: String, Op: Add}, + Column{Name: "description", Type: String, Op: SchemaAdd}, }, }, schema.Pending[0]) } @@ -90,10 +90,10 @@ func TestSchema_AlterColumn(t *testing.T) { schema.AlterColumn("products", "sale", Bool) assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "products", Definitions: []interface{}{ - Column{Name: "sale", Type: Bool, Op: Alter}, + Column{Name: "sale", Type: Bool, Op: SchemaAlter}, }, }, schema.Pending[0]) } @@ -104,10 +104,10 @@ func TestSchema_RenameColumn(t *testing.T) { schema.RenameColumn("users", "name", "fullname") assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "users", Definitions: []interface{}{ - Column{Name: "name", NewName: "fullname", Op: Rename}, + Column{Name: "name", NewName: "fullname", Op: SchemaRename}, }, }, schema.Pending[0]) } @@ -118,10 +118,10 @@ func TestSchema_DropColumn(t *testing.T) { schema.DropColumn("users", "verified") assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "users", Definitions: []interface{}{ - Column{Name: "verified", Op: Drop}, + Column{Name: "verified", Op: SchemaDrop}, }, }, schema.Pending[0]) } @@ -132,10 +132,10 @@ func TestSchema_AddIndex(t *testing.T) { schema.AddIndex("products", []string{"sale"}, SimpleIndex) assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "products", Definitions: []interface{}{ - Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: Add}, + Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: SchemaAdd}, }, }, schema.Pending[0]) } @@ -146,10 +146,10 @@ func TestSchema_RenameIndex(t *testing.T) { schema.RenameIndex("products", "store_id", "fk_store_id") assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "products", Definitions: []interface{}{ - Index{Name: "store_id", NewName: "fk_store_id", Op: Rename}, + Index{Name: "store_id", NewName: "fk_store_id", Op: SchemaRename}, }, }, schema.Pending[0]) } @@ -160,10 +160,10 @@ func TestSchema_DropIndex(t *testing.T) { schema.DropIndex("products", "sale") assert.Equal(t, Table{ - Op: Alter, + Op: SchemaAlter, Name: "products", Definitions: []interface{}{ - Index{Name: "sale", Op: Drop}, + Index{Name: "sale", Op: SchemaDrop}, }, }, schema.Pending[0]) } diff --git a/schema/table.go b/table.go similarity index 92% rename from schema/table.go rename to table.go index ed5e7f87..f2496c29 100644 --- a/schema/table.go +++ b/table.go @@ -1,11 +1,12 @@ -package schema +package rel // Table definition. type Table struct { - Op Op + Op SchemaOp Name string NewName string Definitions []interface{} + IfNotExists bool Comment string Options string } @@ -129,7 +130,7 @@ func (at *AlterTable) DropIndex(name string, options ...IndexOption) { func createTable(name string, options []TableOption) Table { table := Table{ - Op: Add, + Op: SchemaAdd, Name: name, } @@ -139,7 +140,7 @@ func createTable(name string, options []TableOption) Table { func alterTable(name string, options []TableOption) AlterTable { table := Table{ - Op: Alter, + Op: SchemaAlter, Name: name, } @@ -149,7 +150,7 @@ func alterTable(name string, options []TableOption) AlterTable { func renameTable(name string, newName string, options []TableOption) Table { table := Table{ - Op: Rename, + Op: SchemaRename, Name: name, NewName: newName, } @@ -160,10 +161,22 @@ func renameTable(name string, newName string, options []TableOption) Table { func dropTable(name string, options []TableOption) Table { table := Table{ - Op: Drop, + Op: SchemaDrop, Name: name, } applyTableOptions(&table, options) return table } + +// TableOption interface. +// Available options are: Comment, Options. +type TableOption interface { + applyTable(table *Table) +} + +func applyTableOptions(table *Table, options []TableOption) { + for i := range options { + options[i].applyTable(table) + } +} diff --git a/schema/table_test.go b/table_test.go similarity index 97% rename from schema/table_test.go rename to table_test.go index 7fcb13a3..ab1ba672 100644 --- a/schema/table_test.go +++ b/table_test.go @@ -1,4 +1,4 @@ -package schema +package rel import ( "testing" @@ -173,7 +173,7 @@ func TestAlterTable(t *testing.T) { t.Run("RenameColumn", func(t *testing.T) { table.RenameColumn("column", "new_column") assert.Equal(t, Column{ - Op: Rename, + Op: SchemaRename, Name: "column", NewName: "new_column", }, table.Definitions[len(table.Definitions)-1]) @@ -182,7 +182,7 @@ func TestAlterTable(t *testing.T) { t.Run("AlterColumn", func(t *testing.T) { table.AlterColumn("column", Bool, Comment("column")) assert.Equal(t, Column{ - Op: Alter, + Op: SchemaAlter, Name: "column", Type: Bool, Comment: "column", @@ -192,7 +192,7 @@ func TestAlterTable(t *testing.T) { t.Run("DropColumn", func(t *testing.T) { table.DropColumn("column") assert.Equal(t, Column{ - Op: Drop, + Op: SchemaDrop, Name: "column", }, table.Definitions[len(table.Definitions)-1]) }) @@ -200,7 +200,7 @@ func TestAlterTable(t *testing.T) { t.Run("RenameIndex", func(t *testing.T) { table.RenameIndex("index", "new_index") assert.Equal(t, Index{ - Op: Rename, + Op: SchemaRename, Name: "index", NewName: "new_index", }, table.Definitions[len(table.Definitions)-1]) @@ -209,7 +209,7 @@ func TestAlterTable(t *testing.T) { t.Run("DropIndex", func(t *testing.T) { table.DropIndex("index") assert.Equal(t, Index{ - Op: Drop, + Op: SchemaDrop, Name: "index", }, table.Definitions[len(table.Definitions)-1]) }) From 5491e8813c4bc22a20160429acdea607c92df5f7 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 16:11:06 +0700 Subject: [PATCH 12/44] implement migrator --- migration/migration.go | 160 --------------------------- migration/migration_test.go | 66 ------------ migrator/migrator.go | 163 ++++++++++++++++++++++++++++ migrator/migrator_test.go | 210 ++++++++++++++++++++++++++++++++++++ 4 files changed, 373 insertions(+), 226 deletions(-) delete mode 100644 migration/migration.go delete mode 100644 migration/migration_test.go create mode 100644 migrator/migrator.go create mode 100644 migrator/migrator_test.go diff --git a/migration/migration.go b/migration/migration.go deleted file mode 100644 index 0798536f..00000000 --- a/migration/migration.go +++ /dev/null @@ -1,160 +0,0 @@ -package migration - -import ( - "context" - "sort" - - "github.com/Fs02/rel" -) - -const schemaVersionTable = "rel_schema_versions" - -type schemaVersion struct { - ID int - Version int -} - -func (schemaVersion) Table() string { - return schemaVersionTable -} - -// Step definition. -type Step struct { - Version int - Up rel.Schema - Down rel.Schema - Applied bool -} - -// Steps definition. -type Steps []Step - -func (s Steps) Len() int { - return len(s) -} - -func (s Steps) Less(i, j int) bool { - return s[i].Version < s[j].Version -} - -func (s Steps) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// Migration manager definition. -type Migration struct { - Repository rel.Repository - Steps Steps -} - -// AddVersion adds a migration with explicit version. -func (m *Migration) AddVersion(version int, up func(schema *rel.Schema), down func(schema *rel.Schema)) { - var upSchema, downSchema rel.Schema - - up(&upSchema) - down(&downSchema) - - m.Steps = append(m.Steps, Step{Version: version, Up: upSchema, Down: downSchema}) -} - -func (m Migration) buildVersionTableDefinition() rel.Table { - var schema rel.Schema - schema.CreateTable(schemaVersionTable, func(t *rel.Table) { - t.Int("id") - t.Int("version") - t.DateTime("created_at") - t.DateTime("updated_at") - - t.PrimaryKey("id") - t.Unique([]string{"version"}) - }, rel.IfNotExists(true)) - - return schema.Pending[0].(rel.Table) -} - -func (m *Migration) sync(ctx context.Context) { - var ( - versions []schemaVersion - vi int - adapter = m.Repository.Adapter(ctx).(rel.Adapter) - ) - - check(adapter.Apply(ctx, m.buildVersionTableDefinition())) - m.Repository.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) - sort.Sort(m.Steps) - - for i := range m.Steps { - if len(versions) <= vi { - break - } - - if m.Steps[i].Version == versions[vi].Version { - m.Steps[i].Applied = true - vi++ - } - } - - if len(versions) <= vi { - panic("rel: inconsistent schema state") - } -} - -// Up perform migration to the latest schema version. -func (m *Migration) Up(ctx context.Context) { - m.sync(ctx) - - for _, step := range m.Steps { - if step.Applied { - continue - } - - m.Repository.Transaction(ctx, func(ctx context.Context) error { - m.Repository.MustInsert(ctx, &schemaVersion{Version: step.Version}) - - adapter := m.Repository.Adapter(ctx).(rel.Adapter) - for _, migrator := range step.Up.Pending { - // TODO: exec script - switch v := migrator.(type) { - case rel.Table: - check(adapter.Apply(ctx, v)) - } - } - - return nil - }) - } -} - -// Down perform rollback migration 1 step. -func (m *Migration) Down(ctx context.Context) { - m.sync(ctx) - - for i := range m.Steps { - step := m.Steps[len(m.Steps)-i-1] - if !step.Applied { - continue - } - - m.Repository.Transaction(ctx, func(ctx context.Context) error { - m.Repository.MustInsert(ctx, &schemaVersion{Version: step.Version}) - - adapter := m.Repository.Adapter(ctx).(rel.Adapter) - for _, migrator := range step.Down.Pending { - switch v := migrator.(type) { - case rel.Table: - check(adapter.Apply(ctx, v)) - } - } - - return nil - }) - - return - } -} - -func check(err error) { - if err != nil { - panic(err) - } -} diff --git a/migration/migration_test.go b/migration/migration_test.go deleted file mode 100644 index b819890f..00000000 --- a/migration/migration_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package migration - -import ( - "testing" - - "github.com/Fs02/rel" - "github.com/Fs02/rel/reltest" - "github.com/stretchr/testify/assert" -) - -func TestSchema_Migration(t *testing.T) { - var ( - // ctx = context.TODO() - repo = reltest.New() - migration = Migration{ - Repository: repo, - } - ) - - t.Run("AddVersion", func(t *testing.T) { - migration.AddVersion(20200829114000, - func(schema *rel.Schema) { - schema.CreateTable("users", func(t *rel.Table) { - t.Int("id") - t.PrimaryKey("id") - }) - }, - func(schema *rel.Schema) { - schema.DropTable("users") - }, - ) - - migration.AddVersion(20200828100000, - func(schema *rel.Schema) { - schema.CreateTable("tags", func(t *rel.Table) { - t.Int("id") - t.PrimaryKey("id") - }) - }, - func(schema *rel.Schema) { - schema.DropTable("tags") - }, - ) - - migration.AddVersion(20200829115100, - func(schema *rel.Schema) { - schema.CreateTable("books", func(t *rel.Table) { - t.Int("id") - t.PrimaryKey("id") - }) - }, - func(schema *rel.Schema) { - schema.DropTable("books") - }, - ) - - assert.Len(t, migration.Steps, 3) - assert.Equal(t, 20200829114000, migration.Steps[0].Version) - assert.Equal(t, 20200828100000, migration.Steps[1].Version) - assert.Equal(t, 20200829115100, migration.Steps[2].Version) - }) - - // t.Run("Up", func(t *testing.T) { - // migration.Up(ctx) - // }) -} diff --git a/migrator/migrator.go b/migrator/migrator.go new file mode 100644 index 00000000..9fe57884 --- /dev/null +++ b/migrator/migrator.go @@ -0,0 +1,163 @@ +package migrator + +import ( + "context" + "fmt" + "sort" + "time" + + "github.com/Fs02/rel" +) + +const versionTable = "rel_schema_versions" + +type version struct { + ID int + Version int + CreatedAt time.Time + DeletedAt time.Time + + up rel.Schema + down rel.Schema + applied bool +} + +func (version) Table() string { + return versionTable +} + +type versions []version + +func (v versions) Len() int { + return len(v) +} + +func (v versions) Less(i, j int) bool { + return v[i].Version < v[j].Version +} + +func (v versions) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +// Migrator is a migration manager that handles migration logic. +type Migrator struct { + repo rel.Repository + versions versions +} + +// RegisterVersion registers a migration with an explicit version. +func (m *Migrator) RegisterVersion(v int, up func(schema *rel.Schema), down func(schema *rel.Schema)) { + var upSchema, downSchema rel.Schema + + up(&upSchema) + down(&downSchema) + + m.versions = append(m.versions, version{Version: v, up: upSchema, down: downSchema}) +} + +func (m Migrator) buildVersionTableDefinition() rel.Table { + var schema rel.Schema + schema.CreateTable(versionTable, func(t *rel.Table) { + t.Int("id") + t.Int("version") + t.DateTime("created_at") + t.DateTime("updated_at") + + t.PrimaryKey("id") + t.Unique([]string{"version"}) + }, rel.IfNotExists(true)) + + return schema.Pending[0].(rel.Table) +} + +func (m *Migrator) sync(ctx context.Context) { + var ( + versions versions + vi int + adapter = m.repo.Adapter(ctx).(rel.Adapter) + ) + + check(adapter.Apply(ctx, m.buildVersionTableDefinition())) + m.repo.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) + sort.Sort(m.versions) + + for i := range m.versions { + if vi < len(versions) && m.versions[i].Version == versions[vi].Version { + m.versions[i].ID = versions[vi].ID + m.versions[i].applied = true + vi++ + } else { + m.versions[i].applied = false + } + } + + if vi != len(versions) { + panic(fmt.Sprint("rel: missing local migration: ", versions[vi].Version)) + } +} + +// Migrate to the latest schema version. +func (m *Migrator) Migrate(ctx context.Context) { + m.sync(ctx) + + for _, step := range m.versions { + if step.applied { + continue + } + + m.repo.Transaction(ctx, func(ctx context.Context) error { + m.repo.MustInsert(ctx, &version{Version: step.Version}) + + adapter := m.repo.Adapter(ctx).(rel.Adapter) + for _, migrator := range step.up.Pending { + // TODO: exec script + switch v := migrator.(type) { + case rel.Table: + check(adapter.Apply(ctx, v)) + } + } + + return nil + }) + } +} + +// Rollback migration 1 step. +func (m *Migrator) Rollback(ctx context.Context) { + m.sync(ctx) + + for i := range m.versions { + v := m.versions[len(m.versions)-i-1] + if !v.applied { + continue + } + + m.repo.Transaction(ctx, func(ctx context.Context) error { + m.repo.MustDelete(ctx, &v) + + adapter := m.repo.Adapter(ctx).(rel.Adapter) + for _, migrator := range v.down.Pending { + switch v := migrator.(type) { + case rel.Table: + check(adapter.Apply(ctx, v)) + } + } + + return nil + }) + + return + } +} + +// New migrationr. +func New(repo rel.Repository) Migrator { + return Migrator{repo: repo} +} + +func check(err error) { + if err != nil { + panic(err) + } +} diff --git a/migrator/migrator_test.go b/migrator/migrator_test.go new file mode 100644 index 00000000..064da951 --- /dev/null +++ b/migrator/migrator_test.go @@ -0,0 +1,210 @@ +package migrator + +import ( + "context" + "errors" + "testing" + + "github.com/Fs02/rel" + "github.com/Fs02/rel/reltest" + "github.com/stretchr/testify/assert" +) + +func TestMigrator(t *testing.T) { + var ( + ctx = context.TODO() + repo = reltest.New() + migrator = New(repo) + ) + + t.Run("RegisterVersion", func(t *testing.T) { + migrator.RegisterVersion(20200829084000, + func(schema *rel.Schema) { + schema.CreateTable("users", func(t *rel.Table) { + t.Int("id") + t.PrimaryKey("id") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("users") + }, + ) + + migrator.RegisterVersion(20200828100000, + func(schema *rel.Schema) { + schema.CreateTable("tags", func(t *rel.Table) { + t.Int("id") + t.PrimaryKey("id") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("tags") + }, + ) + + migrator.RegisterVersion(20200829115100, + func(schema *rel.Schema) { + schema.CreateTable("books", func(t *rel.Table) { + t.Int("id") + t.PrimaryKey("id") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("books") + }, + ) + + assert.Len(t, migrator.versions, 3) + assert.Equal(t, 20200829084000, migrator.versions[0].Version) + assert.Equal(t, 20200828100000, migrator.versions[1].Version) + assert.Equal(t, 20200829115100, migrator.versions[2].Version) + }) + + t.Run("Migrate", func(t *testing.T) { + repo.ExpectFindAll(rel.NewSortAsc("version")). + Result(versions{{ID: 1, Version: 20200829115100}}) + + repo.ExpectTransaction(func(repo *reltest.Repository) { + repo.ExpectInsert().For(&version{Version: 20200828100000}) + }) + + repo.ExpectTransaction(func(repo *reltest.Repository) { + repo.ExpectInsert().For(&version{Version: 20200829084000}) + }) + + migrator.Migrate(ctx) + }) + + t.Run("Rollback", func(t *testing.T) { + repo.ExpectFindAll(rel.NewSortAsc("version")). + Result(versions{ + {ID: 1, Version: 20200828100000}, + {ID: 2, Version: 20200829084000}, + }) + + assert.Equal(t, 20200829084000, migrator.versions[1].Version) + + repo.ExpectTransaction(func(repo *reltest.Repository) { + repo.ExpectDelete().For(&migrator.versions[1]) + }) + + migrator.Rollback(ctx) + }) +} + +func TestMigrator_Sync(t *testing.T) { + var ( + ctx = context.TODO() + repo = reltest.New() + nfn = func(schema *rel.Schema) {} + ) + + tests := []struct { + name string + applied versions + synced versions + isPanic bool + }{ + { + name: "all migrated", + applied: versions{ + {ID: 1, Version: 1}, + {ID: 2, Version: 2}, + {ID: 3, Version: 3}, + }, + synced: versions{ + {ID: 1, Version: 1, applied: true}, + {ID: 2, Version: 2, applied: true}, + {ID: 3, Version: 3, applied: true}, + }, + }, + { + name: "not migrated", + applied: versions{}, + synced: versions{ + {ID: 0, Version: 1, applied: false}, + {ID: 0, Version: 2, applied: false}, + {ID: 0, Version: 3, applied: false}, + }, + }, + { + name: "first not migrated", + applied: versions{ + {ID: 2, Version: 2}, + {ID: 3, Version: 3}, + }, + synced: versions{ + {ID: 0, Version: 1, applied: false}, + {ID: 2, Version: 2, applied: true}, + {ID: 3, Version: 3, applied: true}, + }, + }, + { + name: "middle not migrated", + applied: versions{ + {ID: 1, Version: 1}, + {ID: 3, Version: 3}, + }, + synced: versions{ + {ID: 1, Version: 1, applied: true}, + {ID: 0, Version: 2, applied: false}, + {ID: 3, Version: 3, applied: true}, + }, + }, + { + name: "last not migrated", + applied: versions{ + {ID: 1, Version: 1}, + {ID: 2, Version: 2}, + }, + synced: versions{ + {ID: 1, Version: 1, applied: true}, + {ID: 2, Version: 2, applied: true}, + {ID: 0, Version: 3, applied: false}, + }, + }, + { + name: "broken migration", + applied: versions{ + {ID: 1, Version: 1}, + {ID: 2, Version: 2}, + {ID: 3, Version: 3}, + {ID: 4, Version: 4}, + }, + synced: versions{ + {ID: 1, Version: 1, applied: true}, + {ID: 2, Version: 2, applied: true}, + {ID: 3, Version: 3, applied: true}, + }, + isPanic: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + migrator := New(repo) + migrator.RegisterVersion(3, nfn, nfn) + migrator.RegisterVersion(2, nfn, nfn) + migrator.RegisterVersion(1, nfn, nfn) + + repo.ExpectFindAll(rel.NewSortAsc("version")).Result(test.applied) + + if test.isPanic { + assert.Panics(t, func() { + migrator.sync(ctx) + }) + } else { + assert.NotPanics(t, func() { + migrator.sync(ctx) + }) + + assert.Equal(t, test.synced, migrator.versions) + } + }) + } +} +func TestCheck(t *testing.T) { + assert.Panics(t, func() { + check(errors.New("error")) + }) +} From 5141df0a64284ffd245d5a8c160ad082b2d67c6a Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 16:20:30 +0700 Subject: [PATCH 13/44] fix test --- reltest/repository_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reltest/repository_test.go b/reltest/repository_test.go index 77ecbc0c..d95c6b65 100644 --- a/reltest/repository_test.go +++ b/reltest/repository_test.go @@ -45,7 +45,7 @@ func TestRepository_Adapter(t *testing.T) { repo = New() ) - assert.Nil(t, repo.Adapter(ctx)) + assert.NotNil(t, repo.Adapter(ctx)) } func TestRepository_Instrumentation(t *testing.T) { From 526c0582e1e025791e9bb85660760fadd8199b14 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 16:39:58 +0700 Subject: [PATCH 14/44] fix coverage --- reltest/nop_adapter_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 reltest/nop_adapter_test.go diff --git a/reltest/nop_adapter_test.go b/reltest/nop_adapter_test.go new file mode 100644 index 00000000..00237bc3 --- /dev/null +++ b/reltest/nop_adapter_test.go @@ -0,0 +1,18 @@ +package reltest + +import ( + "context" + "testing" + + "github.com/Fs02/rel" + "github.com/stretchr/testify/assert" +) + +func TestNopAdapter_Apply(t *testing.T) { + var ( + ctx = context.TODO() + adapter = &nopAdapter{} + ) + + assert.Nil(t, adapter.Apply(ctx, rel.Table{})) +} From 0d28e3dedca3f156170321ddd9caf18f529be9e3 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 19:01:55 +0700 Subject: [PATCH 15/44] wip adapter integration for sqlite3 --- adapter/specs/migration.go | 94 +++++++++++++++++++++++++++++++++ adapter/sql/adapter.go | 17 +++--- adapter/sql/builder.go | 63 ++++------------------ adapter/sql/config.go | 67 +++++++++++++++++++++++ adapter/sqlite3/sqlite3.go | 18 +++++++ adapter/sqlite3/sqlite3_test.go | 62 +++------------------- index.go | 2 +- migrator/migrator.go | 13 +++-- schema.go | 10 ++-- table.go | 7 ++- 10 files changed, 222 insertions(+), 131 deletions(-) create mode 100644 adapter/specs/migration.go create mode 100644 adapter/sql/config.go diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go new file mode 100644 index 00000000..a148e286 --- /dev/null +++ b/adapter/specs/migration.go @@ -0,0 +1,94 @@ +package specs + +import ( + "context" + + "github.com/Fs02/rel" + "github.com/Fs02/rel/migrator" +) + +// Migrate database for specs execution. +func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { + m := migrator.New(rel.New(adapter)) + + m.RegisterVersion(1, + func(schema *rel.Schema) { + schema.CreateTable("users", func(t *rel.Table) { + t.Int("id") + t.String("slug", rel.Limit(30)) + t.String("name", rel.Limit(30), rel.Default("")) + t.String("gender", rel.Limit(10), rel.Default("")) + t.Int("age", rel.Required(true), rel.Default(0)) + t.String("note", rel.Limit(50)) + t.DateTime("created_at") + t.DateTime("updated_at") + + t.PrimaryKey("id") + t.Unique([]string{"slug"}) + }) + }, + func(schema *rel.Schema) { + schema.DropTable("users") + }, + ) + + m.RegisterVersion(2, + func(schema *rel.Schema) { + schema.CreateTable("addresses", func(t *rel.Table) { + t.Int("id") + t.Int("user_id") + t.String("name", rel.Limit(60), rel.Required(true), rel.Default("")) + t.DateTime("created_at") + t.DateTime("updated_at") + + t.PrimaryKey("id") + t.ForeignKey("user_id", "users", "id") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("addresses") + }, + ) + + m.RegisterVersion(3, + func(schema *rel.Schema) { + schema.CreateTable("extras", func(t *rel.Table) { + t.Int("id") + t.Int("user_id") + t.String("slug", rel.Limit(30)) + t.Int("score", rel.Default(0)) + + t.PrimaryKey("id") + t.ForeignKey("user_id", "users", "id") + t.Unique([]string{"slug"}) + t.Fragment("CONSTRAINT extras_score_check CHECK (score>=0 AND score<=100)") + }) + }, + func(schema *rel.Schema) { + schema.DropTable("extras") + }, + ) + + m.RegisterVersion(4, + func(schema *rel.Schema) { + schema.CreateTable("composites", func(t *rel.Table) { + t.Int("primary1") + t.Int("primary2") + t.String("data") + + t.Index([]string{"primary1", "primary2"}, rel.PrimaryKey) + }) + }, + func(schema *rel.Schema) { + schema.DropTable("composites") + }, + ) + + if rollback { + for i := 0; i < 4; i++ { + m.Rollback(ctx) + } + } else { + m.Migrate(ctx) + } +} diff --git a/adapter/sql/adapter.go b/adapter/sql/adapter.go index d634f139..94505c9a 100644 --- a/adapter/sql/adapter.go +++ b/adapter/sql/adapter.go @@ -10,16 +10,6 @@ import ( "github.com/Fs02/rel" ) -// Config holds configuration for adapter. -type Config struct { - Placeholder string - Ordinal bool - InsertDefaultValues bool - EscapeChar string - ErrorFunc func(error) error - IncrementFunc func(Adapter) int -} - // Adapter definition for database database. type Adapter struct { Instrumenter rel.Instrumenter @@ -256,7 +246,12 @@ func (a *Adapter) Rollback(ctx context.Context) error { // Apply table. func (a *Adapter) Apply(ctx context.Context, table rel.Table) error { - return nil + var ( + statement = NewBuilder(a.Config).Table(table) + _, _, err = a.Exec(ctx, statement, nil) + ) + + return err } // New initialize adapter without db. diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index b3af4ac6..3c1556bf 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -38,6 +38,11 @@ func (b *Builder) Table(table rel.Table) string { buffer.WriteByte(';') case rel.SchemaDrop: buffer.WriteString("DROP TABLE ") + + if table.Optional { + buffer.WriteString("IF EXISTS ") + } + buffer.WriteString(b.escape(table.Name)) buffer.WriteByte(';') } @@ -48,7 +53,7 @@ func (b *Builder) Table(table rel.Table) string { func (b *Builder) createTable(buffer *Buffer, table rel.Table) { buffer.WriteString("CREATE TABLE ") - if table.IfNotExists { + if table.Optional { buffer.WriteString("IF NOT EXISTS ") } @@ -64,6 +69,8 @@ func (b *Builder) createTable(buffer *Buffer, table rel.Table) { b.column(buffer, v) case rel.Index: b.index(buffer, v) + case string: + buffer.WriteString(v) } } @@ -125,7 +132,7 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { func (b *Builder) column(buffer *Buffer, column rel.Column) { var ( - typ, m, n = b.mapColumnType(column) + typ, m, n = b.config.MapColumnTypeFunc(column) ) buffer.WriteString(b.escape(column.Name)) @@ -163,63 +170,11 @@ func (b *Builder) column(buffer *Buffer, column rel.Column) { b.options(buffer, column.Options) } -func (b *Builder) mapColumnType(column rel.Column) (string, int, int) { - var ( - typ string - m, n int - ) - - switch column.Type { - case rel.Bool: - typ = "BOOL" - case rel.Int: - typ = "INT" - m = column.Limit - case rel.BigInt: - typ = "BIGINT" - m = column.Limit - case rel.Float: - typ = "FLOAT" - m = column.Precision - case rel.Decimal: - typ = "DECIMAL" - m = column.Precision - n = column.Scale - case rel.String: - typ = "VARCHAR" - m = column.Limit - if m == 0 { - m = 255 - } - case rel.Text: - typ = "TEXT" - m = column.Limit - case rel.Binary: - typ = "BINARY" - m = column.Limit - case rel.Date: - typ = "DATE" - case rel.DateTime: - typ = "DATETIME" - case rel.Time: - typ = "TIME" - case rel.Timestamp: - // TODO: mysql automatically add on update options. - typ = "TIMESTAMP" - default: - typ = string(column.Type) - } - - return typ, m, n -} - func (b *Builder) index(buffer *Buffer, index rel.Index) { var ( typ = string(index.Type) ) - // TODO: type mapping - buffer.WriteString(typ) if index.Name != "" { diff --git a/adapter/sql/config.go b/adapter/sql/config.go new file mode 100644 index 00000000..a06d9824 --- /dev/null +++ b/adapter/sql/config.go @@ -0,0 +1,67 @@ +package sql + +import ( + "github.com/Fs02/rel" +) + +// Config holds configuration for adapter. +type Config struct { + Placeholder string + Ordinal bool + InsertDefaultValues bool + EscapeChar string + ErrorFunc func(error) error + IncrementFunc func(Adapter) int + MapColumnTypeFunc func(column rel.Column) (string, int, int) +} + +// MapColumnType func. +func MapColumnType(column rel.Column) (string, int, int) { + var ( + typ string + m, n int + ) + + switch column.Type { + case rel.Bool: + typ = "BOOL" + case rel.Int: + typ = "INT" + m = column.Limit + case rel.BigInt: + typ = "BIGINT" + m = column.Limit + case rel.Float: + typ = "FLOAT" + m = column.Precision + case rel.Decimal: + typ = "DECIMAL" + m = column.Precision + n = column.Scale + case rel.String: + typ = "VARCHAR" + m = column.Limit + if m == 0 { + m = 255 + } + case rel.Text: + typ = "TEXT" + m = column.Limit + case rel.Binary: + typ = "BINARY" + m = column.Limit + case rel.Date: + typ = "DATE" + case rel.DateTime: + typ = "DATETIME" + case rel.Time: + typ = "TIME" + case rel.Timestamp: + // TODO: mysql automatically add on update options. + typ = "TIMESTAMP" + default: + typ = string(column.Type) + } + + return typ, m, n +} diff --git a/adapter/sqlite3/sqlite3.go b/adapter/sqlite3/sqlite3.go index e8eed1f5..c1069438 100644 --- a/adapter/sqlite3/sqlite3.go +++ b/adapter/sqlite3/sqlite3.go @@ -37,6 +37,7 @@ func New(database *db.DB) *Adapter { InsertDefaultValues: true, IncrementFunc: incrementFunc, ErrorFunc: errorFunc, + MapColumnTypeFunc: mapColumnTypeFunc, }, DB: database, }, @@ -87,3 +88,20 @@ func errorFunc(err error) error { return err } } + +func mapColumnTypeFunc(column rel.Column) (string, int, int) { + var ( + typ string + m, n int + ) + + switch column.Type { + case rel.Int: + typ = "INTEGER" + m = column.Limit + default: + typ, m, n = sql.MapColumnType(column) + } + + return typ, m, n +} diff --git a/adapter/sqlite3/sqlite3_test.go b/adapter/sqlite3/sqlite3_test.go index 4934f113..41d69701 100644 --- a/adapter/sqlite3/sqlite3_test.go +++ b/adapter/sqlite3/sqlite3_test.go @@ -14,62 +14,6 @@ import ( var ctx = context.TODO() -func init() { - adapter, err := Open(dsn()) - paranoid.Panic(err, "failed to open database connection") - defer adapter.Close() - - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS extras;`, nil) - paranoid.Panic(err, "failed when dropping extras table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS addresses;`, nil) - paranoid.Panic(err, "failed when dropping addresses table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS users;`, nil) - paranoid.Panic(err, "failed when dropping users table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS composites;`, nil) - paranoid.Panic(err, "failed when dropping users table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE users ( - id INTEGER PRIMARY KEY, - slug VARCHAR(30) DEFAULT NULL, - name VARCHAR(30) NOT NULL DEFAULT '', - gender VARCHAR(10) NOT NULL DEFAULT '', - age INTEGER NOT NULL DEFAULT 0, - note varchar(50), - created_at DATETIME, - updated_at DATETIME, - UNIQUE (slug) - );`, nil) - paranoid.Panic(err, "failed when creating users table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE addresses ( - id INTEGER PRIMARY KEY, - user_id INTEGER, - name VARCHAR(60) NOT NULL DEFAULT '', - created_at DATETIME, - updated_at DATETIME, - FOREIGN KEY (user_id) REFERENCES users(id) - );`, nil) - paranoid.Panic(err, "failed when creating addresses table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE extras ( - id INTEGER PRIMARY KEY, - slug VARCHAR(30) DEFAULT NULL UNIQUE, - user_id INTEGER, - score INTEGER DEFAULT 0, - FOREIGN KEY (user_id) REFERENCES users(id), - CONSTRAINT extras_score_check CHECK (score>=0 AND score<=100) - );`, nil) - paranoid.Panic(err, "failed when creating extras table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE composites ( - primary1 INTEGER, - primary2 INTEGER, - data VARCHAR(255) DEFAULT NULL, - PRIMARY KEY (primary1, primary2) - );`, nil) - paranoid.Panic(err, "failed when creating extras table") -} - func dsn() string { if os.Getenv("SQLITE3_DATABASE") != "" { return os.Getenv("SQLITE3_DATABASE") + "?_foreign_keys=1&_loc=Local" @@ -85,6 +29,9 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) + // Prepare tables + specs.Migrate(ctx, adapter, false) + // Query Specs specs.Query(t, repo) specs.QueryJoin(t, repo) @@ -138,6 +85,9 @@ func TestAdapter_specs(t *testing.T) { // - foreign key constraint is not supported because of lack of information in the error message. specs.UniqueConstraint(t, repo) specs.CheckConstraint(t, repo) + + // Cleanup tables + specs.Migrate(ctx, adapter, true) } // func TestAdapter_InsertAll_error(t *testing.T) { diff --git a/index.go b/index.go index 2a584d2f..bd2e1641 100644 --- a/index.go +++ b/index.go @@ -7,7 +7,7 @@ const ( // SimpleIndex IndexType. SimpleIndex IndexType = "INDEX" // UniqueIndex IndexType. - UniqueIndex IndexType = "UNIQUE INDEX" + UniqueIndex IndexType = "UNIQUE" // PrimaryKey IndexType. PrimaryKey IndexType = "PRIMARY KEY" // ForeignKey IndexType. diff --git a/migrator/migrator.go b/migrator/migrator.go index 9fe57884..f379625a 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -15,7 +15,7 @@ type version struct { ID int Version int CreatedAt time.Time - DeletedAt time.Time + UpdatedAt time.Time up rel.Schema down rel.Schema @@ -66,7 +66,7 @@ func (m Migrator) buildVersionTableDefinition() rel.Table { t.PrimaryKey("id") t.Unique([]string{"version"}) - }, rel.IfNotExists(true)) + }, rel.Optional(true)) return schema.Pending[0].(rel.Table) } @@ -106,7 +106,7 @@ func (m *Migrator) Migrate(ctx context.Context) { continue } - m.repo.Transaction(ctx, func(ctx context.Context) error { + err := m.repo.Transaction(ctx, func(ctx context.Context) error { m.repo.MustInsert(ctx, &version{Version: step.Version}) adapter := m.repo.Adapter(ctx).(rel.Adapter) @@ -120,6 +120,8 @@ func (m *Migrator) Migrate(ctx context.Context) { return nil }) + + check(err) } } @@ -133,7 +135,7 @@ func (m *Migrator) Rollback(ctx context.Context) { continue } - m.repo.Transaction(ctx, func(ctx context.Context) error { + err := m.repo.Transaction(ctx, func(ctx context.Context) error { m.repo.MustDelete(ctx, &v) adapter := m.repo.Adapter(ctx).(rel.Adapter) @@ -147,6 +149,9 @@ func (m *Migrator) Rollback(ctx context.Context) { return nil }) + check(err) + + // only rollback one version. return } } diff --git a/schema.go b/schema.go index e8f3dcb9..55da91a3 100644 --- a/schema.go +++ b/schema.go @@ -198,9 +198,11 @@ func (ou OnUpdate) applyIndex(index *Index) { index.Reference.OnUpdate = string(ou) } -// IfNotExists option for table creation. -type IfNotExists bool +// Optional option. +// when used with create table, will create table only if it's not exists. +// when used with drop table, will drop table only if it's exists. +type Optional bool -func (ine IfNotExists) applyTable(table *Table) { - table.IfNotExists = bool(ine) +func (o Optional) applyTable(table *Table) { + table.Optional = bool(o) } diff --git a/table.go b/table.go index f2496c29..5016b4c6 100644 --- a/table.go +++ b/table.go @@ -6,7 +6,7 @@ type Table struct { Name string NewName string Definitions []interface{} - IfNotExists bool + Optional bool Comment string Options string } @@ -96,6 +96,11 @@ func (t *Table) ForeignKey(column string, refTable string, refColumn string, opt t.Definitions = append(t.Definitions, addForeignKey(column, refTable, refColumn, options)) } +// Fragment defines anything using sql fragment. +func (t *Table) Fragment(fragment string) { + t.Definitions = append(t.Definitions, fragment) +} + func (t Table) migrate() {} // AlterTable Migrator. From 4fed8b0ef27631d4285901be2c65ed9bf3c9da17 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 20:42:33 +0700 Subject: [PATCH 16/44] special column type: id, wip mysql integration --- adapter/mysql/mysql.go | 9 +++--- adapter/mysql/mysql_test.go | 58 ++++--------------------------------- adapter/specs/migration.go | 15 ++++------ adapter/sql/config.go | 2 ++ adapter/sqlite3/sqlite3.go | 2 ++ column.go | 2 ++ migrator/migrator.go | 3 +- migrator/migrator_test.go | 9 ++---- schema_test.go | 7 ++--- table.go | 10 +++++-- table_test.go | 2 +- 11 files changed, 38 insertions(+), 81 deletions(-) diff --git a/adapter/mysql/mysql.go b/adapter/mysql/mysql.go index 0a36a752..a7c73c2a 100644 --- a/adapter/mysql/mysql.go +++ b/adapter/mysql/mysql.go @@ -32,10 +32,11 @@ func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ Config: &sql.Config{ - Placeholder: "?", - EscapeChar: "`", - IncrementFunc: incrementFunc, - ErrorFunc: errorFunc, + Placeholder: "?", + EscapeChar: "`", + IncrementFunc: incrementFunc, + ErrorFunc: errorFunc, + MapColumnTypeFunc: sql.MapColumnType, }, DB: database, }, diff --git a/adapter/mysql/mysql_test.go b/adapter/mysql/mysql_test.go index 9a90182d..51e5bd3b 100644 --- a/adapter/mysql/mysql_test.go +++ b/adapter/mysql/mysql_test.go @@ -15,58 +15,6 @@ import ( var ctx = context.TODO() -func init() { - adapter, err := Open(dsn()) - paranoid.Panic(err, "failed to open database connection") - - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS extras;`, nil) - paranoid.Panic(err, "failed dropping extras table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS addresses;`, nil) - paranoid.Panic(err, "failed dropping addresses table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS users;`, nil) - paranoid.Panic(err, "failed dropping users table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS composites;`, nil) - paranoid.Panic(err, "failed dropping composites table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE users ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(30) NOT NULL DEFAULT '', - gender VARCHAR(10) NOT NULL DEFAULT '', - age INT NOT NULL DEFAULT 0, - note varchar(50), - created_at DATETIME, - updated_at DATETIME - );`, nil) - paranoid.Panic(err, "failed creating users table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE addresses ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - user_id INT UNSIGNED, - name VARCHAR(60) NOT NULL DEFAULT '', - created_at DATETIME, - updated_at DATETIME, - FOREIGN KEY (user_id) REFERENCES users(id) - );`, nil) - paranoid.Panic(err, "failed creating addresses table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE extras ( - id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - slug VARCHAR(30) DEFAULT NULL UNIQUE, - user_id INT UNSIGNED, - SCORE INT, - CONSTRAINT extras_user_id_fk FOREIGN KEY (user_id) REFERENCES users(id) - );`, nil) - paranoid.Panic(err, "failed creating extras table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE composites ( - primary1 INT UNSIGNED, - primary2 INT UNSIGNED, - data VARCHAR(255) DEFAULT NULL, - PRIMARY KEY (primary1, primary2) - );`, nil) - paranoid.Panic(err, "failed creating composites table") -} - func dsn() string { if os.Getenv("MYSQL_DATABASE") != "" { return os.Getenv("MYSQL_DATABASE") + "?charset=utf8&parseTime=True&loc=Local" @@ -82,6 +30,9 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) + // Prepare tables + specs.Migrate(ctx, adapter, false) + // Query Specs specs.Query(t, repo) specs.QueryJoin(t, repo) @@ -135,6 +86,9 @@ func TestAdapter_specs(t *testing.T) { // - Check constraint is not supported by mysql specs.UniqueConstraint(t, repo) specs.ForeignKeyConstraint(t, repo) + + // Cleanup tables + specs.Migrate(ctx, adapter, true) } func TestAdapter_Open(t *testing.T) { diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index a148e286..3f6910ca 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -14,7 +14,7 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { m.RegisterVersion(1, func(schema *rel.Schema) { schema.CreateTable("users", func(t *rel.Table) { - t.Int("id") + t.ID("id") t.String("slug", rel.Limit(30)) t.String("name", rel.Limit(30), rel.Default("")) t.String("gender", rel.Limit(10), rel.Default("")) @@ -23,7 +23,6 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { t.DateTime("created_at") t.DateTime("updated_at") - t.PrimaryKey("id") t.Unique([]string{"slug"}) }) }, @@ -35,13 +34,12 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { m.RegisterVersion(2, func(schema *rel.Schema) { schema.CreateTable("addresses", func(t *rel.Table) { - t.Int("id") - t.Int("user_id") + t.ID("id") + t.Int("user_id", rel.Unsigned(true)) t.String("name", rel.Limit(60), rel.Required(true), rel.Default("")) t.DateTime("created_at") t.DateTime("updated_at") - t.PrimaryKey("id") t.ForeignKey("user_id", "users", "id") }) }, @@ -53,12 +51,11 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { m.RegisterVersion(3, func(schema *rel.Schema) { schema.CreateTable("extras", func(t *rel.Table) { - t.Int("id") - t.Int("user_id") + t.ID("id") + t.Int("user_id", rel.Unsigned(true)) t.String("slug", rel.Limit(30)) t.Int("score", rel.Default(0)) - t.PrimaryKey("id") t.ForeignKey("user_id", "users", "id") t.Unique([]string{"slug"}) t.Fragment("CONSTRAINT extras_score_check CHECK (score>=0 AND score<=100)") @@ -76,7 +73,7 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { t.Int("primary2") t.String("data") - t.Index([]string{"primary1", "primary2"}, rel.PrimaryKey) + t.PrimaryKey([]string{"primary1", "primary2"}) }) }, func(schema *rel.Schema) { diff --git a/adapter/sql/config.go b/adapter/sql/config.go index a06d9824..aed6a10e 100644 --- a/adapter/sql/config.go +++ b/adapter/sql/config.go @@ -23,6 +23,8 @@ func MapColumnType(column rel.Column) (string, int, int) { ) switch column.Type { + case rel.ID: + typ = "INT UNSIGNED AUTO_INCREMENT PRIMARY KEY" case rel.Bool: typ = "BOOL" case rel.Int: diff --git a/adapter/sqlite3/sqlite3.go b/adapter/sqlite3/sqlite3.go index c1069438..2198f415 100644 --- a/adapter/sqlite3/sqlite3.go +++ b/adapter/sqlite3/sqlite3.go @@ -96,6 +96,8 @@ func mapColumnTypeFunc(column rel.Column) (string, int, int) { ) switch column.Type { + case rel.ID: + typ = "INTEGER PRIMARY KEY" case rel.Int: typ = "INTEGER" m = column.Limit diff --git a/column.go b/column.go index f12841f8..12ddb9f2 100644 --- a/column.go +++ b/column.go @@ -4,6 +4,8 @@ package rel type ColumnType string const ( + // ID ColumnType. + ID ColumnType = "ID" // Bool ColumnType. Bool ColumnType = "BOOL" // Int ColumnType. diff --git a/migrator/migrator.go b/migrator/migrator.go index f379625a..6f1b84ce 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -59,12 +59,11 @@ func (m *Migrator) RegisterVersion(v int, up func(schema *rel.Schema), down func func (m Migrator) buildVersionTableDefinition() rel.Table { var schema rel.Schema schema.CreateTable(versionTable, func(t *rel.Table) { - t.Int("id") + t.ID("id") t.Int("version") t.DateTime("created_at") t.DateTime("updated_at") - t.PrimaryKey("id") t.Unique([]string{"version"}) }, rel.Optional(true)) diff --git a/migrator/migrator_test.go b/migrator/migrator_test.go index 064da951..1feb7e58 100644 --- a/migrator/migrator_test.go +++ b/migrator/migrator_test.go @@ -21,8 +21,7 @@ func TestMigrator(t *testing.T) { migrator.RegisterVersion(20200829084000, func(schema *rel.Schema) { schema.CreateTable("users", func(t *rel.Table) { - t.Int("id") - t.PrimaryKey("id") + t.ID("id") }) }, func(schema *rel.Schema) { @@ -33,8 +32,7 @@ func TestMigrator(t *testing.T) { migrator.RegisterVersion(20200828100000, func(schema *rel.Schema) { schema.CreateTable("tags", func(t *rel.Table) { - t.Int("id") - t.PrimaryKey("id") + t.ID("id") }) }, func(schema *rel.Schema) { @@ -45,8 +43,7 @@ func TestMigrator(t *testing.T) { migrator.RegisterVersion(20200829115100, func(schema *rel.Schema) { schema.CreateTable("books", func(t *rel.Table) { - t.Int("id") - t.PrimaryKey("id") + t.ID("id") }) }, func(schema *rel.Schema) { diff --git a/schema_test.go b/schema_test.go index 4f382cc4..3a8a7cd5 100644 --- a/schema_test.go +++ b/schema_test.go @@ -10,21 +10,18 @@ func TestSchema_CreateTable(t *testing.T) { var schema Schema schema.CreateTable("products", func(t *Table) { - t.Int("id") + t.ID("id") t.String("name") t.Text("description") - - t.PrimaryKey("id") }) assert.Equal(t, Table{ Op: SchemaAdd, Name: "products", Definitions: []interface{}{ - Column{Name: "id", Type: Int}, + Column{Name: "id", Type: ID}, Column{Name: "name", Type: String}, Column{Name: "description", Type: Text}, - Index{Columns: []string{"id"}, Type: PrimaryKey}, }, }, schema.Pending[0]) } diff --git a/table.go b/table.go index 5016b4c6..41802413 100644 --- a/table.go +++ b/table.go @@ -16,6 +16,12 @@ func (t *Table) Column(name string, typ ColumnType, options ...ColumnOption) { t.Definitions = append(t.Definitions, addColumn(name, typ, options)) } +// ID defines a column with name and ID type. +// the resulting database type will depends on database. +func (t *Table) ID(name string, options ...ColumnOption) { + t.Column(name, ID, options...) +} + // Bool defines a column with name and Bool type. func (t *Table) Bool(name string, options ...ColumnOption) { t.Column(name, Bool, options...) @@ -87,8 +93,8 @@ func (t *Table) Unique(columns []string, options ...IndexOption) { } // PrimaryKey defines an primary key for table. -func (t *Table) PrimaryKey(column string, options ...IndexOption) { - t.Index([]string{column}, PrimaryKey, options...) +func (t *Table) PrimaryKey(columns []string, options ...IndexOption) { + t.Index(columns, PrimaryKey, options...) } // ForeignKey defines foreign key index. diff --git a/table_test.go b/table_test.go index ab1ba672..f2b2c64f 100644 --- a/table_test.go +++ b/table_test.go @@ -145,7 +145,7 @@ func TestTable(t *testing.T) { }) t.Run("PrimaryKey", func(t *testing.T) { - table.PrimaryKey("id", Comment("primary key")) + table.PrimaryKey([]string{"id"}, Comment("primary key")) assert.Equal(t, Index{ Columns: []string{"id"}, Type: PrimaryKey, From 3fb4844151349d40f4a2bd68b42f2463fa816684 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 21:10:57 +0700 Subject: [PATCH 17/44] wip postgres spec --- adapter/mysql/mysql.go | 10 ++--- adapter/postgres/postgres.go | 25 +++++++++++++ adapter/postgres/postgres_test.go | 62 ++++--------------------------- adapter/sql/builder.go | 2 +- adapter/sql/config.go | 6 +-- adapter/sqlite3/sqlite3.go | 6 +-- 6 files changed, 45 insertions(+), 66 deletions(-) diff --git a/adapter/mysql/mysql.go b/adapter/mysql/mysql.go index a7c73c2a..691cd369 100644 --- a/adapter/mysql/mysql.go +++ b/adapter/mysql/mysql.go @@ -32,11 +32,11 @@ func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ Config: &sql.Config{ - Placeholder: "?", - EscapeChar: "`", - IncrementFunc: incrementFunc, - ErrorFunc: errorFunc, - MapColumnTypeFunc: sql.MapColumnType, + Placeholder: "?", + EscapeChar: "`", + IncrementFunc: incrementFunc, + ErrorFunc: errorFunc, + MapColumnFunc: sql.MapColumn, }, DB: database, }, diff --git a/adapter/postgres/postgres.go b/adapter/postgres/postgres.go index 0cca263d..02a2c7d0 100644 --- a/adapter/postgres/postgres.go +++ b/adapter/postgres/postgres.go @@ -37,6 +37,7 @@ func New(database *db.DB) *Adapter { Ordinal: true, InsertDefaultValues: true, ErrorFunc: errorFunc, + MapColumnFunc: mapColumnFunc, }, DB: database, }, @@ -144,3 +145,27 @@ func errorFunc(err error) error { return err } } + +func mapColumnFunc(column *rel.Column) (string, int, int) { + var ( + typ string + m, n int + ) + + // postgres specific + column.Unsigned = false + if column.Default == "" { + column.Default = nil + } + + switch column.Type { + case rel.ID: + typ = "SERIAL NOT NULL PRIMARY KEY" + case rel.DateTime: + typ = "TIMESTAMPTZ" + default: + typ, m, n = sql.MapColumn(column) + } + + return typ, m, n +} diff --git a/adapter/postgres/postgres_test.go b/adapter/postgres/postgres_test.go index aa75d214..02f1a7ce 100644 --- a/adapter/postgres/postgres_test.go +++ b/adapter/postgres/postgres_test.go @@ -16,60 +16,8 @@ import ( var ctx = context.TODO() func init() { - adapter, err := Open(dsn()) - paranoid.Panic(err, "failed to open database connection") - defer adapter.Close() - - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS extras;`, nil) - paranoid.Panic(err, "failed dropping extras table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS addresses;`, nil) - paranoid.Panic(err, "failed dropping addresses table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS users;`, nil) - paranoid.Panic(err, "failed dropping users table") - _, _, err = adapter.Exec(ctx, `DROP TABLE IF EXISTS composites;`, nil) - paranoid.Panic(err, "failed dropping composites table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE users ( - id SERIAL NOT NULL PRIMARY KEY, - slug VARCHAR(30) DEFAULT NULL, - name VARCHAR(30) NOT NULL DEFAULT '', - gender VARCHAR(10) NOT NULL DEFAULT '', - age INT NOT NULL DEFAULT 0, - note varchar(50), - created_at TIMESTAMPTZ, - updated_at TIMESTAMPTZ, - UNIQUE(slug) - );`, nil) - paranoid.Panic(err, "failed creating users table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE addresses ( - id SERIAL NOT NULL PRIMARY KEY, - user_id INTEGER REFERENCES users(id), - name VARCHAR(60) NOT NULL DEFAULT '', - created_at TIMESTAMPTZ, - updated_at TIMESTAMPTZ - );`, nil) - paranoid.Panic(err, "failed creating addresses table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE extras ( - id SERIAL NOT NULL PRIMARY KEY, - slug VARCHAR(30) DEFAULT NULL UNIQUE, - user_id INTEGER REFERENCES users(id), - score INTEGER DEFAULT 0 CHECK (score>=0 AND score<=100) - );`, nil) - paranoid.Panic(err, "failed creating extras table") - - _, _, err = adapter.Exec(ctx, `CREATE TABLE composites ( - primary1 SERIAL NOT NULL, - primary2 SERIAL NOT NULL, - data VARCHAR(255) DEFAULT NULL, - PRIMARY KEY (primary1, primary2) - );`, nil) - paranoid.Panic(err, "failed creating composites table") - // hack to make sure location it has the same location object as returned by pq driver. - time.Local, err = time.LoadLocation("Asia/Jakarta") - paranoid.Panic(err, "failed loading time location") + time.Local, _ = time.LoadLocation("Asia/Jakarta") } func dsn() string { @@ -77,7 +25,7 @@ func dsn() string { return os.Getenv("POSTGRESQL_DATABASE") + "?sslmode=disable&timezone=Asia/Jakarta" } - return "postgres://rel@localhost:9920/rel_test?sslmode=disable&timezone=Asia/Jakarta" + return "postgres://rel@localhost:5432/rel_test?sslmode=disable&timezone=Asia/Jakarta" } func TestAdapter_specs(t *testing.T) { @@ -87,6 +35,9 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) + // Prepare tables + specs.Migrate(ctx, adapter, false) + // Query Specs specs.Query(t, repo) specs.QueryJoin(t, repo) @@ -140,6 +91,9 @@ func TestAdapter_specs(t *testing.T) { specs.UniqueConstraint(t, repo) specs.ForeignKeyConstraint(t, repo) specs.CheckConstraint(t, repo) + + // Cleanup tables + specs.Migrate(ctx, adapter, true) } // func TestAdapter_InsertAll_error(t *testing.T) { diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 3c1556bf..20e27eb9 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -132,7 +132,7 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { func (b *Builder) column(buffer *Buffer, column rel.Column) { var ( - typ, m, n = b.config.MapColumnTypeFunc(column) + typ, m, n = b.config.MapColumnFunc(&column) ) buffer.WriteString(b.escape(column.Name)) diff --git a/adapter/sql/config.go b/adapter/sql/config.go index aed6a10e..6fcb6b7e 100644 --- a/adapter/sql/config.go +++ b/adapter/sql/config.go @@ -12,11 +12,11 @@ type Config struct { EscapeChar string ErrorFunc func(error) error IncrementFunc func(Adapter) int - MapColumnTypeFunc func(column rel.Column) (string, int, int) + MapColumnFunc func(column *rel.Column) (string, int, int) } -// MapColumnType func. -func MapColumnType(column rel.Column) (string, int, int) { +// MapColumn func. +func MapColumn(column *rel.Column) (string, int, int) { var ( typ string m, n int diff --git a/adapter/sqlite3/sqlite3.go b/adapter/sqlite3/sqlite3.go index 2198f415..4c5d0ce8 100644 --- a/adapter/sqlite3/sqlite3.go +++ b/adapter/sqlite3/sqlite3.go @@ -37,7 +37,7 @@ func New(database *db.DB) *Adapter { InsertDefaultValues: true, IncrementFunc: incrementFunc, ErrorFunc: errorFunc, - MapColumnTypeFunc: mapColumnTypeFunc, + MapColumnFunc: mapColumnFunc, }, DB: database, }, @@ -89,7 +89,7 @@ func errorFunc(err error) error { } } -func mapColumnTypeFunc(column rel.Column) (string, int, int) { +func mapColumnFunc(column *rel.Column) (string, int, int) { var ( typ string m, n int @@ -102,7 +102,7 @@ func mapColumnTypeFunc(column rel.Column) (string, int, int) { typ = "INTEGER" m = column.Limit default: - typ, m, n = sql.MapColumnType(column) + typ, m, n = sql.MapColumn(column) } return typ, m, n From 905c87c06939ba753febe4424bc54fc911187257 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sat, 29 Aug 2020 21:15:53 +0700 Subject: [PATCH 18/44] fix test --- adapter/sql/adapter_test.go | 1 + adapter/sql/builder_test.go | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/adapter/sql/adapter_test.go b/adapter/sql/adapter_test.go index facf5b59..9ceb8b0b 100644 --- a/adapter/sql/adapter_test.go +++ b/adapter/sql/adapter_test.go @@ -20,6 +20,7 @@ func open(t *testing.T) *Adapter { InsertDefaultValues: true, ErrorFunc: func(err error) error { return err }, IncrementFunc: func(Adapter) int { return -1 }, + MapColumnFunc: MapColumn, } adapter = New(config) ) diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 6791db4f..f6cbe6e2 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -35,8 +35,9 @@ func BenchmarkBuilder_Find(b *testing.B) { func TestBuilder_Table(t *testing.T) { var ( config = &Config{ - Placeholder: "?", - EscapeChar: "`", + Placeholder: "?", + EscapeChar: "`", + MapColumnFunc: MapColumn, } ) @@ -58,7 +59,7 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `binary` BINARY(255), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE INDEX `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) COMMENT 'TEST' Engine=InnoDB;", + result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `binary` BINARY(255), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) COMMENT 'TEST' Engine=InnoDB;", table: rel.Table{ Op: rel.SchemaAdd, Name: "columns", From 24e51e38c959d7c01fef0f6f8c0f1414ebe58abd Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 13:04:02 +0700 Subject: [PATCH 19/44] wip migrate spec, cancel support for comment and binary --- adapter/mysql/mysql_test.go | 9 +-- adapter/postgres/postgres.go | 7 ++ adapter/postgres/postgres_test.go | 9 +-- adapter/specs/migration.go | 72 +++++++++++++++---- adapter/sql/builder.go | 26 +++---- adapter/sql/builder_test.go | 2 - adapter/sql/config.go | 17 ++++- adapter/sqlite3/sqlite3.go | 11 ++- adapter/sqlite3/sqlite3_test.go | 9 +-- column.go | 3 - column_test.go | 8 --- index.go | 1 - index_test.go | 8 --- migrator/migrator.go | 4 +- migrator/migrator_test.go | 14 ++-- schema.go | 15 ---- table.go | 6 -- table_test.go | 116 ++++++++++++------------------ 18 files changed, 166 insertions(+), 171 deletions(-) diff --git a/adapter/mysql/mysql_test.go b/adapter/mysql/mysql_test.go index 51e5bd3b..1a18d1cf 100644 --- a/adapter/mysql/mysql_test.go +++ b/adapter/mysql/mysql_test.go @@ -31,7 +31,11 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) // Prepare tables - specs.Migrate(ctx, adapter, false) + specs.Migrate(t, repo, false) + defer specs.Migrate(t, repo, true) + + // Migration Specs + specs.MigrateTable(t, repo) // Query Specs specs.Query(t, repo) @@ -86,9 +90,6 @@ func TestAdapter_specs(t *testing.T) { // - Check constraint is not supported by mysql specs.UniqueConstraint(t, repo) specs.ForeignKeyConstraint(t, repo) - - // Cleanup tables - specs.Migrate(ctx, adapter, true) } func TestAdapter_Open(t *testing.T) { diff --git a/adapter/postgres/postgres.go b/adapter/postgres/postgres.go index 02a2c7d0..64cb8a40 100644 --- a/adapter/postgres/postgres.go +++ b/adapter/postgres/postgres.go @@ -15,6 +15,7 @@ package postgres import ( "context" db "database/sql" + "time" "github.com/Fs02/rel" "github.com/Fs02/rel/adapter/sql" @@ -163,6 +164,12 @@ func mapColumnFunc(column *rel.Column) (string, int, int) { typ = "SERIAL NOT NULL PRIMARY KEY" case rel.DateTime: typ = "TIMESTAMPTZ" + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format("2006-01-02 15:04:05") + } + case rel.Int, rel.BigInt, rel.Text: + column.Limit = 0 + typ, m, n = sql.MapColumn(column) default: typ, m, n = sql.MapColumn(column) } diff --git a/adapter/postgres/postgres_test.go b/adapter/postgres/postgres_test.go index 02f1a7ce..a4a36648 100644 --- a/adapter/postgres/postgres_test.go +++ b/adapter/postgres/postgres_test.go @@ -36,7 +36,11 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) // Prepare tables - specs.Migrate(ctx, adapter, false) + specs.Migrate(t, repo, false) + defer specs.Migrate(t, repo, true) + + // Migration Specs + specs.MigrateTable(t, repo) // Query Specs specs.Query(t, repo) @@ -91,9 +95,6 @@ func TestAdapter_specs(t *testing.T) { specs.UniqueConstraint(t, repo) specs.ForeignKeyConstraint(t, repo) specs.CheckConstraint(t, repo) - - // Cleanup tables - specs.Migrate(ctx, adapter, true) } // func TestAdapter_InsertAll_error(t *testing.T) { diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index 3f6910ca..987392f1 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -1,17 +1,26 @@ package specs import ( - "context" + "testing" + "time" "github.com/Fs02/rel" "github.com/Fs02/rel/migrator" ) +var m migrator.Migrator + // Migrate database for specs execution. -func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { - m := migrator.New(rel.New(adapter)) +func Migrate(t *testing.T, repo rel.Repository, rollback bool) { + if rollback { + for i := 0; i < 4; i++ { + m.Rollback(ctx) + } + return + } - m.RegisterVersion(1, + m = migrator.New(repo) + m.Register(1, func(schema *rel.Schema) { schema.CreateTable("users", func(t *rel.Table) { t.ID("id") @@ -31,7 +40,7 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { }, ) - m.RegisterVersion(2, + m.Register(2, func(schema *rel.Schema) { schema.CreateTable("addresses", func(t *rel.Table) { t.ID("id") @@ -48,7 +57,7 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { }, ) - m.RegisterVersion(3, + m.Register(3, func(schema *rel.Schema) { schema.CreateTable("extras", func(t *rel.Table) { t.ID("id") @@ -66,7 +75,7 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { }, ) - m.RegisterVersion(4, + m.Register(4, func(schema *rel.Schema) { schema.CreateTable("composites", func(t *rel.Table) { t.Int("primary1") @@ -81,11 +90,46 @@ func Migrate(ctx context.Context, adapter rel.Adapter, rollback bool) { }, ) - if rollback { - for i := 0; i < 4; i++ { - m.Rollback(ctx) - } - } else { - m.Migrate(ctx) - } + m.Migrate(ctx) +} + +// MigrateTable specs. +func MigrateTable(t *testing.T, repo rel.Repository) { + m.Register(5, + func(schema *rel.Schema) { + schema.CreateTable("dummies", func(t *rel.Table) { + t.ID("id") + t.Bool("bool1") + t.Bool("bool2", rel.Default(true)) + t.Int("int1") + t.Int("int2", rel.Default(8), rel.Unsigned(true), rel.Limit(10)) + t.BigInt("bigint1") + t.BigInt("bigint2", rel.Default(8), rel.Unsigned(true), rel.Limit(200)) + t.Float("float1") + t.Float("float2", rel.Default(10.00), rel.Precision(2)) + t.Decimal("decimal1") + t.Decimal("decimal2", rel.Default(10.00), rel.Precision(6), rel.Scale(2)) + t.String("string1") + t.String("string2", rel.Default("string"), rel.Limit(100)) + t.Text("text") + t.Date("date1") + t.Date("date2", rel.Default(time.Now())) + t.DateTime("datetime1") + t.DateTime("datetime2", rel.Default(time.Now())) + t.Time("time1") + t.Time("time2", rel.Default(time.Now())) + t.Timestamp("timestamp1") + t.Timestamp("timestamp2", rel.Default(time.Now())) + + t.Unique([]string{"int1"}) + t.Unique([]string{"bigint1", "bigint2"}) + }) + }, + func(schema *rel.Schema) { + schema.DropTable("dummies") + }, + ) + + m.Migrate(ctx) + m.Rollback(ctx) } diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 20e27eb9..ae51649a 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -75,7 +75,6 @@ func (b *Builder) createTable(buffer *Buffer, table rel.Table) { } buffer.WriteByte(')') - b.comment(buffer, table.Comment) b.options(buffer, table.Options) buffer.WriteByte(';') } @@ -161,12 +160,18 @@ func (b *Builder) column(buffer *Buffer, column rel.Column) { if column.Default != nil { buffer.WriteString(" DEFAULT ") - // TODO: improve - bytes, _ := json.Marshal(column.Default) - buffer.Write(bytes) + switch v := column.Default.(type) { + case string: + buffer.WriteByte('\'') + buffer.WriteString(v) + buffer.WriteByte('\'') + default: + // TODO: improve + bytes, _ := json.Marshal(column.Default) + buffer.Write(bytes) + } } - b.comment(buffer, column.Comment) b.options(buffer, column.Options) } @@ -215,20 +220,9 @@ func (b *Builder) index(buffer *Buffer, index rel.Index) { } } - b.comment(buffer, index.Comment) b.options(buffer, index.Options) } -func (b *Builder) comment(buffer *Buffer, comment string) { - if comment == "" { - return - } - - buffer.WriteString(" COMMENT '") - buffer.WriteString(comment) - buffer.WriteByte('\'') -} - func (b *Builder) options(buffer *Buffer, options string) { if options == "" { return diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index f6cbe6e2..1a859b8c 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -71,7 +71,6 @@ func TestBuilder_Table(t *testing.T) { rel.Column{Name: "decimal", Type: rel.Decimal, Precision: 6, Scale: 2, Unsigned: true}, rel.Column{Name: "string", Type: rel.String, Limit: 144}, rel.Column{Name: "text", Type: rel.Text, Limit: 1000}, - rel.Column{Name: "binary", Type: rel.Binary, Limit: 255}, rel.Column{Name: "date", Type: rel.Date}, rel.Column{Name: "datetime", Type: rel.DateTime}, rel.Column{Name: "time", Type: rel.Time}, @@ -83,7 +82,6 @@ func TestBuilder_Table(t *testing.T) { rel.Index{Columns: []string{"int", "string"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, }, Options: "Engine=InnoDB", - Comment: "TEST", }, }, { diff --git a/adapter/sql/config.go b/adapter/sql/config.go index 6fcb6b7e..025c2d9e 100644 --- a/adapter/sql/config.go +++ b/adapter/sql/config.go @@ -1,6 +1,8 @@ package sql import ( + "time" + "github.com/Fs02/rel" ) @@ -49,18 +51,27 @@ func MapColumn(column *rel.Column) (string, int, int) { case rel.Text: typ = "TEXT" m = column.Limit - case rel.Binary: - typ = "BINARY" - m = column.Limit case rel.Date: typ = "DATE" + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format("2006-01-02") + } case rel.DateTime: typ = "DATETIME" + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format("2006-01-02 15:04:05") + } case rel.Time: typ = "TIME" + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format("15:04:05") + } case rel.Timestamp: // TODO: mysql automatically add on update options. typ = "TIMESTAMP" + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format("2006-01-02 15:04:05") + } default: typ = string(column.Type) } diff --git a/adapter/sqlite3/sqlite3.go b/adapter/sqlite3/sqlite3.go index 4c5d0ce8..cb571ce4 100644 --- a/adapter/sqlite3/sqlite3.go +++ b/adapter/sqlite3/sqlite3.go @@ -91,10 +91,13 @@ func errorFunc(err error) error { func mapColumnFunc(column *rel.Column) (string, int, int) { var ( - typ string - m, n int + typ string + m, n int + unsigned = column.Unsigned ) + column.Unsigned = false + switch column.Type { case rel.ID: typ = "INTEGER PRIMARY KEY" @@ -105,5 +108,9 @@ func mapColumnFunc(column *rel.Column) (string, int, int) { typ, m, n = sql.MapColumn(column) } + if unsigned { + typ = "UNSIGNED " + typ + } + return typ, m, n } diff --git a/adapter/sqlite3/sqlite3_test.go b/adapter/sqlite3/sqlite3_test.go index 41d69701..cc146d1f 100644 --- a/adapter/sqlite3/sqlite3_test.go +++ b/adapter/sqlite3/sqlite3_test.go @@ -30,7 +30,11 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) // Prepare tables - specs.Migrate(ctx, adapter, false) + specs.Migrate(t, repo, false) + defer specs.Migrate(t, repo, true) + + // Migration Specs + specs.MigrateTable(t, repo) // Query Specs specs.Query(t, repo) @@ -85,9 +89,6 @@ func TestAdapter_specs(t *testing.T) { // - foreign key constraint is not supported because of lack of information in the error message. specs.UniqueConstraint(t, repo) specs.CheckConstraint(t, repo) - - // Cleanup tables - specs.Migrate(ctx, adapter, true) } // func TestAdapter_InsertAll_error(t *testing.T) { diff --git a/column.go b/column.go index 12ddb9f2..29aae406 100644 --- a/column.go +++ b/column.go @@ -20,8 +20,6 @@ const ( String ColumnType = "STRING" // Text ColumnType. Text ColumnType = "TEXT" - // Binary ColumnType. - Binary ColumnType = "BINARY" // Date ColumnType. Date ColumnType = "DATE" // DateTime ColumnType. @@ -44,7 +42,6 @@ type Column struct { Precision int Scale int Default interface{} - Comment string Options string } diff --git a/column_test.go b/column_test.go index c2c964fa..ffdafa0a 100644 --- a/column_test.go +++ b/column_test.go @@ -15,7 +15,6 @@ func TestAddColumn(t *testing.T) { Precision(5), Scale(2), Default(0), - Comment("comment"), Options("options"), } column = addColumn("add", Decimal, options) @@ -30,7 +29,6 @@ func TestAddColumn(t *testing.T) { Precision: 5, Scale: 2, Default: 0, - Comment: "comment", Options: "options", }, column) } @@ -44,7 +42,6 @@ func TestAlterColumn(t *testing.T) { Precision(5), Scale(2), Default(0), - Comment("comment"), Options("options"), } column = alterColumn("alter", Decimal, options) @@ -60,7 +57,6 @@ func TestAlterColumn(t *testing.T) { Precision: 5, Scale: 2, Default: 0, - Comment: "comment", Options: "options", }, column) } @@ -74,7 +70,6 @@ func TestRenameColumn(t *testing.T) { Precision(5), Scale(2), Default(0), - Comment("comment"), Options("options"), } column = renameColumn("add", "rename", options) @@ -90,7 +85,6 @@ func TestRenameColumn(t *testing.T) { Precision: 5, Scale: 2, Default: 0, - Comment: "comment", Options: "options", }, column) } @@ -104,7 +98,6 @@ func TestDropColumn(t *testing.T) { Precision(5), Scale(2), Default(0), - Comment("comment"), Options("options"), } column = dropColumn("drop", options) @@ -119,7 +112,6 @@ func TestDropColumn(t *testing.T) { Precision: 5, Scale: 2, Default: 0, - Comment: "comment", Options: "options", }, column) } diff --git a/index.go b/index.go index bd2e1641..9e43c5cf 100644 --- a/index.go +++ b/index.go @@ -30,7 +30,6 @@ type Index struct { Columns []string NewName string Reference ForeignKeyReference - Comment string Options string } diff --git a/index_test.go b/index_test.go index a0af73c8..3958ca3a 100644 --- a/index_test.go +++ b/index_test.go @@ -10,7 +10,6 @@ func TestAddIndex(t *testing.T) { var ( options = []IndexOption{ Name("simple"), - Comment("comment"), Options("options"), } index = addIndex([]string{"add"}, SimpleIndex, options) @@ -20,7 +19,6 @@ func TestAddIndex(t *testing.T) { Type: SimpleIndex, Name: "simple", Columns: []string{"add"}, - Comment: "comment", Options: "options", }, index) } @@ -31,7 +29,6 @@ func TestAddForeignKey(t *testing.T) { OnDelete("cascade"), OnUpdate("cascade"), Name("fk"), - Comment("comment"), Options("options"), } index = addForeignKey("table_id", "table", "id", options) @@ -47,7 +44,6 @@ func TestAddForeignKey(t *testing.T) { OnDelete: "cascade", OnUpdate: "cascade", }, - Comment: "comment", Options: "options", }, index) } @@ -55,7 +51,6 @@ func TestAddForeignKey(t *testing.T) { func TestRenameIndex(t *testing.T) { var ( options = []IndexOption{ - Comment("comment"), Options("options"), } index = renameIndex("add", "rename", options) @@ -65,7 +60,6 @@ func TestRenameIndex(t *testing.T) { Op: SchemaRename, Name: "add", NewName: "rename", - Comment: "comment", Options: "options", }, index) } @@ -73,7 +67,6 @@ func TestRenameIndex(t *testing.T) { func TestDropIndex(t *testing.T) { var ( options = []IndexOption{ - Comment("comment"), Options("options"), } index = dropIndex("drop", options) @@ -82,7 +75,6 @@ func TestDropIndex(t *testing.T) { assert.Equal(t, Index{ Op: SchemaDrop, Name: "drop", - Comment: "comment", Options: "options", }, index) } diff --git a/migrator/migrator.go b/migrator/migrator.go index 6f1b84ce..f163677f 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -46,8 +46,8 @@ type Migrator struct { versions versions } -// RegisterVersion registers a migration with an explicit version. -func (m *Migrator) RegisterVersion(v int, up func(schema *rel.Schema), down func(schema *rel.Schema)) { +// Register a migration. +func (m *Migrator) Register(v int, up func(schema *rel.Schema), down func(schema *rel.Schema)) { var upSchema, downSchema rel.Schema up(&upSchema) diff --git a/migrator/migrator_test.go b/migrator/migrator_test.go index 1feb7e58..1df3b27a 100644 --- a/migrator/migrator_test.go +++ b/migrator/migrator_test.go @@ -17,8 +17,8 @@ func TestMigrator(t *testing.T) { migrator = New(repo) ) - t.Run("RegisterVersion", func(t *testing.T) { - migrator.RegisterVersion(20200829084000, + t.Run("Register", func(t *testing.T) { + migrator.Register(20200829084000, func(schema *rel.Schema) { schema.CreateTable("users", func(t *rel.Table) { t.ID("id") @@ -29,7 +29,7 @@ func TestMigrator(t *testing.T) { }, ) - migrator.RegisterVersion(20200828100000, + migrator.Register(20200828100000, func(schema *rel.Schema) { schema.CreateTable("tags", func(t *rel.Table) { t.ID("id") @@ -40,7 +40,7 @@ func TestMigrator(t *testing.T) { }, ) - migrator.RegisterVersion(20200829115100, + migrator.Register(20200829115100, func(schema *rel.Schema) { schema.CreateTable("books", func(t *rel.Table) { t.ID("id") @@ -180,9 +180,9 @@ func TestMigrator_Sync(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { migrator := New(repo) - migrator.RegisterVersion(3, nfn, nfn) - migrator.RegisterVersion(2, nfn, nfn) - migrator.RegisterVersion(1, nfn, nfn) + migrator.Register(3, nfn, nfn) + migrator.Register(2, nfn, nfn) + migrator.Register(1, nfn, nfn) repo.ExpectFindAll(rel.NewSortAsc("version")).Result(test.applied) diff --git a/schema.go b/schema.go index 55da91a3..289c6f13 100644 --- a/schema.go +++ b/schema.go @@ -106,21 +106,6 @@ func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { // func (s *Schema) Exec(func(repo rel.Repository) error) { // } -// Comment options for table, column and index. -type Comment string - -func (c Comment) applyTable(table *Table) { - table.Comment = string(c) -} - -func (c Comment) applyColumn(column *Column) { - column.Comment = string(c) -} - -func (c Comment) applyIndex(index *Index) { - index.Comment = string(c) -} - // Options options for table, column and index. type Options string diff --git a/table.go b/table.go index 41802413..06f8209e 100644 --- a/table.go +++ b/table.go @@ -7,7 +7,6 @@ type Table struct { NewName string Definitions []interface{} Optional bool - Comment string Options string } @@ -57,11 +56,6 @@ func (t *Table) Text(name string, options ...ColumnOption) { t.Column(name, Text, options...) } -// Binary defines a column with name and Binary type. -func (t *Table) Binary(name string, options ...ColumnOption) { - t.Column(name, Binary, options...) -} - // Date defines a column with name and Date type. func (t *Table) Date(name string, options ...ColumnOption) { t.Column(name, Date, options...) diff --git a/table_test.go b/table_test.go index f2b2c64f..e0f63ec1 100644 --- a/table_test.go +++ b/table_test.go @@ -10,151 +10,127 @@ func TestTable(t *testing.T) { var table Table t.Run("Column", func(t *testing.T) { - table.Column("column", String, Comment("column")) + table.Column("column", String) assert.Equal(t, Column{ - Name: "column", - Type: String, - Comment: "column", + Name: "column", + Type: String, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Bool", func(t *testing.T) { - table.Bool("boolean", Comment("boolean")) + table.Bool("boolean") assert.Equal(t, Column{ - Name: "boolean", - Type: Bool, - Comment: "boolean", + Name: "boolean", + Type: Bool, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Int", func(t *testing.T) { - table.Int("integer", Comment("integer")) + table.Int("integer") assert.Equal(t, Column{ - Name: "integer", - Type: Int, - Comment: "integer", + Name: "integer", + Type: Int, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("BigInt", func(t *testing.T) { - table.BigInt("bigint", Comment("bigint")) + table.BigInt("bigint") assert.Equal(t, Column{ - Name: "bigint", - Type: BigInt, - Comment: "bigint", + Name: "bigint", + Type: BigInt, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Float", func(t *testing.T) { - table.Float("float", Comment("float")) + table.Float("float") assert.Equal(t, Column{ - Name: "float", - Type: Float, - Comment: "float", + Name: "float", + Type: Float, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Decimal", func(t *testing.T) { - table.Decimal("decimal", Comment("decimal")) + table.Decimal("decimal") assert.Equal(t, Column{ - Name: "decimal", - Type: Decimal, - Comment: "decimal", + Name: "decimal", + Type: Decimal, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("String", func(t *testing.T) { - table.String("string", Comment("string")) + table.String("string") assert.Equal(t, Column{ - Name: "string", - Type: String, - Comment: "string", + Name: "string", + Type: String, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Text", func(t *testing.T) { - table.Text("text", Comment("text")) - assert.Equal(t, Column{ - Name: "text", - Type: Text, - Comment: "text", - }, table.Definitions[len(table.Definitions)-1]) - }) - - t.Run("Binary", func(t *testing.T) { - table.Binary("binary", Comment("binary")) + table.Text("text") assert.Equal(t, Column{ - Name: "binary", - Type: Binary, - Comment: "binary", + Name: "text", + Type: Text, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Date", func(t *testing.T) { - table.Date("date", Comment("date")) + table.Date("date") assert.Equal(t, Column{ - Name: "date", - Type: Date, - Comment: "date", + Name: "date", + Type: Date, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("DateTime", func(t *testing.T) { - table.DateTime("datetime", Comment("datetime")) + table.DateTime("datetime") assert.Equal(t, Column{ - Name: "datetime", - Type: DateTime, - Comment: "datetime", + Name: "datetime", + Type: DateTime, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Time", func(t *testing.T) { - table.Time("time", Comment("time")) + table.Time("time") assert.Equal(t, Column{ - Name: "time", - Type: Time, - Comment: "time", + Name: "time", + Type: Time, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Timestamp", func(t *testing.T) { - table.Timestamp("timestamp", Comment("timestamp")) + table.Timestamp("timestamp") assert.Equal(t, Column{ - Name: "timestamp", - Type: Timestamp, - Comment: "timestamp", + Name: "timestamp", + Type: Timestamp, }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Index", func(t *testing.T) { - table.Index([]string{"id"}, PrimaryKey, Comment("primary key")) + table.Index([]string{"id"}, PrimaryKey) assert.Equal(t, Index{ Columns: []string{"id"}, Type: PrimaryKey, - Comment: "primary key", }, table.Definitions[len(table.Definitions)-1]) }) t.Run("Unique", func(t *testing.T) { - table.Unique([]string{"username"}, Comment("unique")) + table.Unique([]string{"username"}) assert.Equal(t, Index{ Columns: []string{"username"}, Type: UniqueIndex, - Comment: "unique", }, table.Definitions[len(table.Definitions)-1]) }) t.Run("PrimaryKey", func(t *testing.T) { - table.PrimaryKey([]string{"id"}, Comment("primary key")) + table.PrimaryKey([]string{"id"}) assert.Equal(t, Index{ Columns: []string{"id"}, Type: PrimaryKey, - Comment: "primary key", }, table.Definitions[len(table.Definitions)-1]) }) t.Run("ForeignKey", func(t *testing.T) { - table.ForeignKey("user_id", "users", "id", Comment("foreign key")) + table.ForeignKey("user_id", "users", "id") assert.Equal(t, Index{ Columns: []string{"user_id"}, Type: ForeignKey, @@ -162,7 +138,6 @@ func TestTable(t *testing.T) { Table: "users", Columns: []string{"id"}, }, - Comment: "foreign key", }, table.Definitions[len(table.Definitions)-1]) }) } @@ -180,12 +155,11 @@ func TestAlterTable(t *testing.T) { }) t.Run("AlterColumn", func(t *testing.T) { - table.AlterColumn("column", Bool, Comment("column")) + table.AlterColumn("column", Bool) assert.Equal(t, Column{ - Op: SchemaAlter, - Name: "column", - Type: Bool, - Comment: "column", + Op: SchemaAlter, + Name: "column", + Type: Bool, }, table.Definitions[len(table.Definitions)-1]) }) @@ -218,7 +192,6 @@ func TestAlterTable(t *testing.T) { func TestCreateTable(t *testing.T) { var ( options = []TableOption{ - Comment("comment"), Options("options"), } table = createTable("table", options) @@ -226,7 +199,6 @@ func TestCreateTable(t *testing.T) { assert.Equal(t, Table{ Name: "table", - Comment: "comment", Options: "options", }, table) } From 1fb4e25e2b11cb4336563c497017274e9fc0f0b1 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 13:19:42 +0700 Subject: [PATCH 20/44] fix test and refactor map column --- adapter/sql/builder_test.go | 2 +- adapter/sql/config.go | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 1a859b8c..ea00bd39 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -59,7 +59,7 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `binary` BINARY(255), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) COMMENT 'TEST' Engine=InnoDB;", + result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) Engine=InnoDB;", table: rel.Table{ Op: rel.SchemaAdd, Name: "columns", diff --git a/adapter/sql/config.go b/adapter/sql/config.go index 025c2d9e..bd8d1f4e 100644 --- a/adapter/sql/config.go +++ b/adapter/sql/config.go @@ -20,8 +20,9 @@ type Config struct { // MapColumn func. func MapColumn(column *rel.Column) (string, int, int) { var ( - typ string - m, n int + typ string + m, n int + timeLayout = "2006-01-02 15:04:05" ) switch column.Type { @@ -53,28 +54,21 @@ func MapColumn(column *rel.Column) (string, int, int) { m = column.Limit case rel.Date: typ = "DATE" - if t, ok := column.Default.(time.Time); ok { - column.Default = t.Format("2006-01-02") - } + timeLayout = "2006-01-02" case rel.DateTime: typ = "DATETIME" - if t, ok := column.Default.(time.Time); ok { - column.Default = t.Format("2006-01-02 15:04:05") - } case rel.Time: typ = "TIME" - if t, ok := column.Default.(time.Time); ok { - column.Default = t.Format("15:04:05") - } + timeLayout = "15:04:05" case rel.Timestamp: - // TODO: mysql automatically add on update options. typ = "TIMESTAMP" - if t, ok := column.Default.(time.Time); ok { - column.Default = t.Format("2006-01-02 15:04:05") - } default: typ = string(column.Type) } + if t, ok := column.Default.(time.Time); ok { + column.Default = t.Format(timeLayout) + } + return typ, m, n } From 76e49562c694970bc9facbc7536d5222da7a59c6 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 15:46:42 +0700 Subject: [PATCH 21/44] use strings.Builder --- adapter/sql/buffer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adapter/sql/buffer.go b/adapter/sql/buffer.go index 5d165ab9..191b625d 100644 --- a/adapter/sql/buffer.go +++ b/adapter/sql/buffer.go @@ -1,12 +1,12 @@ package sql import ( - "bytes" + "strings" ) // Buffer used to strings buffer and argument of the query. type Buffer struct { - bytes.Buffer + strings.Builder Arguments []interface{} } @@ -17,6 +17,6 @@ func (b *Buffer) Append(args ...interface{}) { // Reset buffer. func (b *Buffer) Reset() { - b.Buffer.Reset() + b.Builder.Reset() b.Arguments = nil } From 80775b55fea1b66345464a0bad4343db68a5d157 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 17:55:43 +0700 Subject: [PATCH 22/44] wip split index and key --- adapter/mysql/mysql.go | 2 +- adapter/postgres/postgres.go | 2 +- adapter/specs/migration.go | 7 +- adapter/specs/specs.go | 2 +- adapter/sql/adapter.go | 4 +- adapter/sql/adapter_test.go | 4 +- adapter/sql/builder.go | 134 +++++++++++++---------------------- adapter/sql/builder_test.go | 82 +++++++++++---------- adapter/sql/config.go | 1 + adapter/sql/sql.go | 40 +++++++++++ adapter/sql/util.go | 33 +++++++++ adapter/sqlite3/sqlite3.go | 2 +- column.go | 4 +- column_test.go | 4 +- index.go | 56 +++++---------- index_test.go | 27 +------ key.go | 109 ++++++++++++++++++++++++++++ key_test.go | 32 +++++++++ schema.go | 31 ++------ schema_test.go | 8 +-- table.go | 33 +++++---- table_test.go | 22 +----- 22 files changed, 376 insertions(+), 263 deletions(-) create mode 100644 adapter/sql/sql.go create mode 100644 key.go create mode 100644 key_test.go diff --git a/adapter/mysql/mysql.go b/adapter/mysql/mysql.go index 691cd369..d3e4a193 100644 --- a/adapter/mysql/mysql.go +++ b/adapter/mysql/mysql.go @@ -31,7 +31,7 @@ var _ rel.Adapter = (*Adapter)(nil) func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ - Config: &sql.Config{ + Config: sql.Config{ Placeholder: "?", EscapeChar: "`", IncrementFunc: incrementFunc, diff --git a/adapter/postgres/postgres.go b/adapter/postgres/postgres.go index 64cb8a40..a92c3a2b 100644 --- a/adapter/postgres/postgres.go +++ b/adapter/postgres/postgres.go @@ -32,7 +32,7 @@ var _ rel.Adapter = (*Adapter)(nil) func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ - Config: &sql.Config{ + Config: sql.Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index 987392f1..6d454bc7 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -82,7 +82,7 @@ func Migrate(t *testing.T, repo rel.Repository, rollback bool) { t.Int("primary2") t.String("data") - t.PrimaryKey([]string{"primary1", "primary2"}) + t.PrimaryKeys([]string{"primary1", "primary2"}) }) }, func(schema *rel.Schema) { @@ -121,7 +121,10 @@ func MigrateTable(t *testing.T, repo rel.Repository) { t.Timestamp("timestamp1") t.Timestamp("timestamp2", rel.Default(time.Now())) - t.Unique([]string{"int1"}) + // t.Index([]string{"int1"}, rel.SimpleIndex) + // t.Index([]string{"string1", "string2"}, rel.SimpleIndex) + + t.Unique([]string{"int2"}) t.Unique([]string{"bigint1", "bigint2"}) }) }, diff --git a/adapter/specs/specs.go b/adapter/specs/specs.go index 956be058..cce699cc 100644 --- a/adapter/specs/specs.go +++ b/adapter/specs/specs.go @@ -53,7 +53,7 @@ type Composite struct { } var ( - config = &sql.Config{ + config = sql.Config{ Placeholder: "?", EscapeChar: "`", } diff --git a/adapter/sql/adapter.go b/adapter/sql/adapter.go index 94505c9a..5ea8f6a8 100644 --- a/adapter/sql/adapter.go +++ b/adapter/sql/adapter.go @@ -13,7 +13,7 @@ import ( // Adapter definition for database database. type Adapter struct { Instrumenter rel.Instrumenter - Config *Config + Config Config DB *sql.DB Tx *sql.Tx savepoint int @@ -255,7 +255,7 @@ func (a *Adapter) Apply(ctx context.Context, table rel.Table) error { } // New initialize adapter without db. -func New(config *Config) *Adapter { +func New(config Config) *Adapter { adapter := &Adapter{ Config: config, } diff --git a/adapter/sql/adapter_test.go b/adapter/sql/adapter_test.go index 9ceb8b0b..58acf44e 100644 --- a/adapter/sql/adapter_test.go +++ b/adapter/sql/adapter_test.go @@ -14,7 +14,7 @@ import ( func open(t *testing.T) *Adapter { var ( err error - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", InsertDefaultValues: true, @@ -44,7 +44,7 @@ type Name struct { } func TestNew(t *testing.T) { - assert.NotNil(t, New(nil)) + assert.NotNil(t, New(Config{})) } func TestAdapter_Ping(t *testing.T) { diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index ae51649a..be1162c6 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -16,7 +16,7 @@ var fieldCache sync.Map // Builder defines information of query b. type Builder struct { - config *Config + config Config returnField string count int } @@ -26,15 +26,15 @@ func (b *Builder) Table(table rel.Table) string { var buffer Buffer switch table.Op { - case rel.SchemaAdd: + case rel.SchemaCreate: b.createTable(&buffer, table) case rel.SchemaAlter: b.alterTable(&buffer, table) case rel.SchemaRename: buffer.WriteString("RENAME TABLE ") - buffer.WriteString(b.escape(table.Name)) + buffer.WriteString(Escape(b.config, table.Name)) buffer.WriteString(" TO ") - buffer.WriteString(b.escape(table.NewName)) + buffer.WriteString(Escape(b.config, table.NewName)) buffer.WriteByte(';') case rel.SchemaDrop: buffer.WriteString("DROP TABLE ") @@ -43,7 +43,7 @@ func (b *Builder) Table(table rel.Table) string { buffer.WriteString("IF EXISTS ") } - buffer.WriteString(b.escape(table.Name)) + buffer.WriteString(Escape(b.config, table.Name)) buffer.WriteByte(';') } @@ -57,7 +57,7 @@ func (b *Builder) createTable(buffer *Buffer, table rel.Table) { buffer.WriteString("IF NOT EXISTS ") } - buffer.WriteString(b.escape(table.Name)) + buffer.WriteString(Escape(b.config, table.Name)) buffer.WriteString(" (") for i, def := range table.Definitions { @@ -67,8 +67,8 @@ func (b *Builder) createTable(buffer *Buffer, table rel.Table) { switch v := def.(type) { case rel.Column: b.column(buffer, v) - case rel.Index: - b.index(buffer, v) + case rel.Key: + b.key(buffer, v) case string: buffer.WriteString(v) } @@ -81,7 +81,7 @@ func (b *Builder) createTable(buffer *Buffer, table rel.Table) { func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { buffer.WriteString("ALTER TABLE ") - buffer.WriteString(b.escape(table.Name)) + buffer.WriteString(Escape(b.config, table.Name)) buffer.WriteByte(' ') for i, def := range table.Definitions { @@ -92,7 +92,7 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { switch v := def.(type) { case rel.Column: switch v.Op { - case rel.SchemaAdd: + case rel.SchemaCreate: buffer.WriteString("ADD COLUMN ") b.column(buffer, v) case rel.SchemaAlter: // TODO: use modify keyword? @@ -101,26 +101,26 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { case rel.SchemaRename: // Add Change buffer.WriteString("RENAME COLUMN ") - buffer.WriteString(b.escape(v.Name)) + buffer.WriteString(Escape(b.config, v.Name)) buffer.WriteString(" TO ") - buffer.WriteString(b.escape(v.NewName)) + buffer.WriteString(Escape(b.config, v.NewName)) case rel.SchemaDrop: buffer.WriteString("DROP COLUMN ") - buffer.WriteString(b.escape(v.Name)) + buffer.WriteString(Escape(b.config, v.Name)) } - case rel.Index: + case rel.Key: switch v.Op { - case rel.SchemaAdd: + case rel.SchemaCreate: buffer.WriteString("ADD ") - b.index(buffer, v) + b.key(buffer, v) case rel.SchemaRename: buffer.WriteString("RENAME INDEX ") - buffer.WriteString(b.escape(v.Name)) + buffer.WriteString(Escape(b.config, v.Name)) buffer.WriteString(" TO ") - buffer.WriteString(b.escape(v.NewName)) + buffer.WriteString(Escape(b.config, v.NewName)) case rel.SchemaDrop: buffer.WriteString("DROP INDEX ") - buffer.WriteString(b.escape(v.Name)) + buffer.WriteString(Escape(b.config, v.Name)) } } } @@ -134,7 +134,7 @@ func (b *Builder) column(buffer *Buffer, column rel.Column) { typ, m, n = b.config.MapColumnFunc(&column) ) - buffer.WriteString(b.escape(column.Name)) + buffer.WriteString(Escape(b.config, column.Name)) buffer.WriteByte(' ') buffer.WriteString(typ) @@ -175,52 +175,52 @@ func (b *Builder) column(buffer *Buffer, column rel.Column) { b.options(buffer, column.Options) } -func (b *Builder) index(buffer *Buffer, index rel.Index) { +func (b *Builder) key(buffer *Buffer, key rel.Key) { var ( - typ = string(index.Type) + typ = string(key.Type) ) buffer.WriteString(typ) - if index.Name != "" { + if key.Name != "" { buffer.WriteByte(' ') - buffer.WriteString(b.escape(index.Name)) + buffer.WriteString(Escape(b.config, key.Name)) } buffer.WriteString(" (") - for i, col := range index.Columns { + for i, col := range key.Columns { if i > 0 { buffer.WriteString(", ") } - buffer.WriteString(b.escape(col)) + buffer.WriteString(Escape(b.config, col)) } buffer.WriteString(")") - if index.Type == rel.ForeignKey { + if key.Type == rel.ForeignKey { buffer.WriteString(" REFERENCES ") - buffer.WriteString(b.escape(index.Reference.Table)) + buffer.WriteString(Escape(b.config, key.Reference.Table)) buffer.WriteString(" (") - for i, col := range index.Reference.Columns { + for i, col := range key.Reference.Columns { if i > 0 { buffer.WriteString(", ") } - buffer.WriteString(b.escape(col)) + buffer.WriteString(Escape(b.config, col)) } buffer.WriteString(")") - if onDelete := index.Reference.OnDelete; onDelete != "" { + if onDelete := key.Reference.OnDelete; onDelete != "" { buffer.WriteString(" ON DELETE ") buffer.WriteString(onDelete) } - if onUpdate := index.Reference.OnUpdate; onUpdate != "" { + if onUpdate := key.Reference.OnUpdate; onUpdate != "" { buffer.WriteString(" ON UPDATE ") buffer.WriteString(onUpdate) } } - b.options(buffer, index.Options) + b.options(buffer, key.Options) } func (b *Builder) options(buffer *Buffer, options string) { @@ -259,13 +259,13 @@ func (b *Builder) Aggregate(query rel.Query, mode string, field string) (string, buffer.WriteString("SELECT ") buffer.WriteString(mode) buffer.WriteByte('(') - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteString(") AS ") buffer.WriteString(mode) for _, f := range query.GroupQuery.Fields { buffer.WriteByte(',') - buffer.WriteString(b.escape(f)) + buffer.WriteString(Escape(b.config, f)) } b.query(&buffer, query) @@ -302,7 +302,7 @@ func (b *Builder) Insert(table string, mutates map[string]rel.Mutate) (string, [ ) buffer.WriteString("INSERT INTO ") - buffer.WriteString(b.escape(table)) + buffer.WriteString(Escape(b.config, table)) if count == 0 && b.config.InsertDefaultValues { buffer.WriteString(" DEFAULT VALUES") @@ -431,14 +431,14 @@ func (b *Builder) Update(table string, mutates map[string]rel.Mutate, filter rel for field, mut := range mutates { switch mut.Type { case rel.ChangeSetOp: - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteByte('=') buffer.WriteString(b.ph()) buffer.Append(mut.Value) case rel.ChangeIncOp: - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteByte('=') - buffer.WriteString(b.escape(field)) + buffer.WriteString(Escape(b.config, field)) buffer.WriteByte('+') buffer.WriteString(b.ph()) buffer.Append(mut.Value) @@ -496,7 +496,7 @@ func (b *Builder) fields(buffer *Buffer, distinct bool, fields []string) { l := len(fields) - 1 for i, f := range fields { - buffer.WriteString(b.escape(f)) + buffer.WriteString(Escape(b.config, f)) if i < l { buffer.WriteByte(',') @@ -518,8 +518,8 @@ func (b *Builder) join(buffer *Buffer, table string, joins []rel.JoinQuery) { for _, join := range joins { var ( - from = b.escape(join.From) - to = b.escape(join.To) + from = Escape(b.config, join.From) + to = Escape(b.config, join.To) ) // TODO: move this to core functionality, and infer join condition using assoc data. @@ -562,7 +562,7 @@ func (b *Builder) groupBy(buffer *Buffer, fields []string) { l := len(fields) - 1 for i, f := range fields { - buffer.WriteString(b.escape(f)) + buffer.WriteString(Escape(b.config, f)) if i < l { buffer.WriteByte(',') @@ -591,7 +591,7 @@ func (b *Builder) orderBy(buffer *Buffer, orders []rel.SortQuery) { buffer.WriteString(" ORDER BY") for i, order := range orders { buffer.WriteByte(' ') - buffer.WriteString(b.escape(order.Field)) + buffer.WriteString(Escape(b.config, order.Field)) if order.Asc() { buffer.WriteString(" ASC") @@ -634,21 +634,21 @@ func (b *Builder) filter(buffer *Buffer, filter rel.FilterQuery) { rel.FilterGteOp: b.buildComparison(buffer, filter) case rel.FilterNilOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" IS NULL") case rel.FilterNotNilOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" IS NOT NULL") case rel.FilterInOp, rel.FilterNinOp: b.buildInclusion(buffer, filter) case rel.FilterLikeOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" LIKE ") buffer.WriteString(b.ph()) buffer.Append(filter.Value) case rel.FilterNotLikeOp: - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) buffer.WriteString(" NOT LIKE ") buffer.WriteString(b.ph()) buffer.Append(filter.Value) @@ -683,7 +683,7 @@ func (b *Builder) build(buffer *Buffer, op string, inner []rel.FilterQuery) { } func (b *Builder) buildComparison(buffer *Buffer, filter rel.FilterQuery) { - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) switch filter.Type { case rel.FilterEqOp: @@ -709,7 +709,7 @@ func (b *Builder) buildInclusion(buffer *Buffer, filter rel.FilterQuery) { values = filter.Value.([]interface{}) ) - buffer.WriteString(b.escape(filter.Field)) + buffer.WriteString(Escape(b.config, filter.Field)) if filter.Type == rel.FilterInOp { buffer.WriteString(" IN (") @@ -735,38 +735,6 @@ func (b *Builder) ph() string { return b.config.Placeholder } -type fieldCacheKey struct { - field string - escape string -} - -func (b *Builder) escape(field string) string { - if b.config.EscapeChar == "" || field == "*" { - return field - } - - key := fieldCacheKey{field: field, escape: b.config.EscapeChar} - escapedField, ok := fieldCache.Load(key) - if ok { - return escapedField.(string) - } - - if len(field) > 0 && field[0] == UnescapeCharacter { - escapedField = field[1:] - } else if start, end := strings.IndexRune(field, '('), strings.IndexRune(field, ')'); start >= 0 && end >= 0 && end > start { - escapedField = field[:start+1] + b.escape(field[start+1:end]) + field[end:] - } else if strings.HasSuffix(field, "*") { - escapedField = b.config.EscapeChar + strings.Replace(field, ".", b.config.EscapeChar+".", 1) - } else { - escapedField = b.config.EscapeChar + - strings.Replace(field, ".", b.config.EscapeChar+"."+b.config.EscapeChar, 1) + - b.config.EscapeChar - } - - fieldCache.Store(key, escapedField) - return escapedField.(string) -} - // Returning append returning to insert rel. func (b *Builder) Returning(field string) *Builder { b.returnField = field @@ -774,7 +742,7 @@ func (b *Builder) Returning(field string) *Builder { } // NewBuilder create new SQL builder. -func NewBuilder(config *Config) *Builder { +func NewBuilder(config Config) *Builder { return &Builder{ config: config, } diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index ea00bd39..5b3e924b 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -12,7 +12,7 @@ import ( func BenchmarkBuilder_Find(b *testing.B) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -34,7 +34,7 @@ func BenchmarkBuilder_Find(b *testing.B) { func TestBuilder_Table(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", MapColumnFunc: MapColumn, @@ -48,20 +48,20 @@ func TestBuilder_Table(t *testing.T) { { result: "CREATE TABLE `products` (`id` INT, `name` VARCHAR(255), `description` TEXT, PRIMARY KEY (`id`));", table: rel.Table{ - Op: rel.SchemaAdd, + Op: rel.SchemaCreate, Name: "products", Definitions: []interface{}{ rel.Column{Name: "id", Type: rel.Int}, rel.Column{Name: "name", Type: rel.String}, rel.Column{Name: "description", Type: rel.Text}, - rel.Index{Columns: []string{"id"}, Type: rel.PrimaryKey}, + rel.Key{Columns: []string{"id"}, Type: rel.PrimaryKey}, }, }, }, { result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) Engine=InnoDB;", table: rel.Table{ - Op: rel.SchemaAdd, + Op: rel.SchemaCreate, Name: "columns", Definitions: []interface{}{ rel.Column{Name: "bool", Type: rel.Bool, Required: true, Default: false}, @@ -76,10 +76,8 @@ func TestBuilder_Table(t *testing.T) { rel.Column{Name: "time", Type: rel.Time}, rel.Column{Name: "timestamp", Type: rel.Timestamp}, rel.Column{Name: "blob", Type: "blob"}, - rel.Index{Columns: []string{"int"}, Type: rel.PrimaryKey}, - rel.Index{Columns: []string{"date"}, Type: rel.UniqueIndex, Name: "date_unique"}, - rel.Index{Columns: []string{"datetime"}, Type: rel.SimpleIndex}, - rel.Index{Columns: []string{"int", "string"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, + rel.Key{Columns: []string{"int"}, Type: rel.PrimaryKey}, + rel.Key{Columns: []string{"int", "string"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, }, Options: "Engine=InnoDB", }, @@ -90,7 +88,7 @@ func TestBuilder_Table(t *testing.T) { Op: rel.SchemaAlter, Name: "columns", Definitions: []interface{}{ - rel.Column{Name: "verified", Type: rel.Bool, Op: rel.SchemaAdd}, + rel.Column{Name: "verified", Type: rel.Bool, Op: rel.SchemaCreate}, rel.Column{Name: "string", NewName: "name", Op: rel.SchemaRename}, rel.Column{Name: "bool", Type: rel.Int, Op: rel.SchemaAlter}, rel.Column{Name: "blob", Op: rel.SchemaDrop}, @@ -103,7 +101,7 @@ func TestBuilder_Table(t *testing.T) { Op: rel.SchemaAlter, Name: "columns", Definitions: []interface{}{ - rel.Index{Name: "verified_int", Columns: []string{"verified", "int"}, Type: rel.SimpleIndex, Op: rel.SchemaAdd}, + rel.Index{Name: "verified_int", Columns: []string{"verified", "int"}, Type: rel.SimpleIndex, Op: rel.SchemaCreate}, }, }, }, @@ -158,7 +156,7 @@ func TestBuilder_Table(t *testing.T) { func TestBuilder_Find(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -242,7 +240,7 @@ func TestBuilder_Find(t *testing.T) { func TestBuilder_Find_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -323,7 +321,7 @@ func TestBuilder_Find_ordinal(t *testing.T) { func TestBuilder_Find_SQLQuery(t *testing.T) { var ( - config = &Config{} + config = Config{} builder = NewBuilder(config) query = rel.Build("", rel.SQL("SELECT * FROM `users` WHERE id=?;", 1)) qs, args = builder.Find(query) @@ -335,7 +333,7 @@ func TestBuilder_Find_SQLQuery(t *testing.T) { func BenchmarkBuilder_Aggregate(b *testing.B) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -349,7 +347,7 @@ func BenchmarkBuilder_Aggregate(b *testing.B) { func TestBuilder_Aggregate(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -372,7 +370,7 @@ func TestBuilder_Aggregate(t *testing.T) { func BenchmarkBuilder_Insert(b *testing.B) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -391,7 +389,7 @@ func BenchmarkBuilder_Insert(b *testing.B) { func TestBuilder_Insert(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -413,7 +411,7 @@ func TestBuilder_Insert(t *testing.T) { func TestBuilder_Insert_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -437,7 +435,7 @@ func TestBuilder_Insert_ordinal(t *testing.T) { func TestBuilder_Insert_defaultValuesDisabled(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", InsertDefaultValues: false, @@ -453,7 +451,7 @@ func TestBuilder_Insert_defaultValuesDisabled(t *testing.T) { func TestBuilder_Insert_defaultValuesEnabled(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", InsertDefaultValues: true, EscapeChar: "`", @@ -469,7 +467,7 @@ func TestBuilder_Insert_defaultValuesEnabled(t *testing.T) { func BenchmarkBuilder_InsertAll(b *testing.B) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -495,7 +493,7 @@ func BenchmarkBuilder_InsertAll(b *testing.B) { func TestBuilder_InsertAll(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -526,7 +524,7 @@ func TestBuilder_InsertAll(t *testing.T) { func TestBuilder_InsertAll_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -560,7 +558,7 @@ func TestBuilder_InsertAll_ordinal(t *testing.T) { func TestBuilder_Update(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -583,7 +581,7 @@ func TestBuilder_Update(t *testing.T) { func TestBuilder_Update_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -609,7 +607,7 @@ func TestBuilder_Update_ordinal(t *testing.T) { func TestBuilder_Update_incDecAndFragment(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -627,7 +625,7 @@ func TestBuilder_Update_incDecAndFragment(t *testing.T) { func TestBuilder_Delete(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -645,7 +643,7 @@ func TestBuilder_Delete(t *testing.T) { func TestBuilder_Delete_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -665,7 +663,7 @@ func TestBuilder_Delete_ordinal(t *testing.T) { func TestBuilder_Select(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -727,7 +725,7 @@ func TestBuilder_Select(t *testing.T) { func TestBuilder_From(t *testing.T) { var ( buffer Buffer - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -740,7 +738,7 @@ func TestBuilder_From(t *testing.T) { func TestBuilder_Join(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -790,7 +788,7 @@ func TestBuilder_Join(t *testing.T) { func TestBuilder_Where(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -830,7 +828,7 @@ func TestBuilder_Where(t *testing.T) { func TestBuilder_Where_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -873,7 +871,7 @@ func TestBuilder_Where_ordinal(t *testing.T) { func TestBuilder_GroupBy(t *testing.T) { var ( buffer Buffer - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -890,7 +888,7 @@ func TestBuilder_GroupBy(t *testing.T) { func TestBuilder_Having(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -930,7 +928,7 @@ func TestBuilder_Having(t *testing.T) { func TestBuilder_Having_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -973,7 +971,7 @@ func TestBuilder_Having_ordinal(t *testing.T) { func TestBuilder_OrderBy(t *testing.T) { var ( buffer Buffer - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -991,7 +989,7 @@ func TestBuilder_OrderBy(t *testing.T) { func TestBuilder_LimitOffset(t *testing.T) { var ( buffer Buffer - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -1008,7 +1006,7 @@ func TestBuilder_LimitOffset(t *testing.T) { func TestBuilder_Filter(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } @@ -1193,7 +1191,7 @@ func TestBuilder_Filter(t *testing.T) { func TestBuilder_Filter_ordinal(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "$", EscapeChar: "\"", Ordinal: true, @@ -1380,7 +1378,7 @@ func TestBuilder_Filter_ordinal(t *testing.T) { func TestBuilder_Lock(t *testing.T) { var ( - config = &Config{ + config = Config{ Placeholder: "?", EscapeChar: "`", } diff --git a/adapter/sql/config.go b/adapter/sql/config.go index bd8d1f4e..d0c3cd17 100644 --- a/adapter/sql/config.go +++ b/adapter/sql/config.go @@ -14,6 +14,7 @@ type Config struct { EscapeChar string ErrorFunc func(error) error IncrementFunc func(Adapter) int + IndexToSQL func(config Config, buffer *Buffer, index rel.Index) bool MapColumnFunc func(column *rel.Column) (string, int, int) } diff --git a/adapter/sql/sql.go b/adapter/sql/sql.go new file mode 100644 index 00000000..22152be3 --- /dev/null +++ b/adapter/sql/sql.go @@ -0,0 +1,40 @@ +package sql + +import "github.com/Fs02/rel" + +// IndexToSQL converts index struct as a sql string. +// Return true if it's an inline sql. +func IndexToSQL(config Config, index rel.Index) (string, bool) { + var ( + buffer Buffer + typ = string(index.Type) + ) + + buffer.WriteString(typ) + + if index.Name != "" { + buffer.WriteByte(' ') + buffer.WriteString(Escape(config, index.Name)) + } + + buffer.WriteString(" (") + for i, col := range index.Columns { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(Escape(config, col)) + } + buffer.WriteString(")") + + optionsToSQL(&buffer, index.Options) + return buffer.String(), true +} + +func optionsToSQL(buffer *Buffer, options string) { + if options == "" { + return + } + + buffer.WriteByte(' ') + buffer.WriteString(options) +} diff --git a/adapter/sql/util.go b/adapter/sql/util.go index f4ac1aad..a9b39ea2 100644 --- a/adapter/sql/util.go +++ b/adapter/sql/util.go @@ -18,6 +18,39 @@ func ExtractString(s, left, right string) string { return s[start+len(left) : end] } +type fieldCacheKey struct { + field string + escape string +} + +// Escape field or table name. +func Escape(config Config, field string) string { + if config.EscapeChar == "" || field == "*" { + return field + } + + key := fieldCacheKey{field: field, escape: config.EscapeChar} + escapedField, ok := fieldCache.Load(key) + if ok { + return escapedField.(string) + } + + if len(field) > 0 && field[0] == UnescapeCharacter { + escapedField = field[1:] + } else if start, end := strings.IndexRune(field, '('), strings.IndexRune(field, ')'); start >= 0 && end >= 0 && end > start { + escapedField = field[:start+1] + Escape(config, field[start+1:end]) + field[end:] + } else if strings.HasSuffix(field, "*") { + escapedField = config.EscapeChar + strings.Replace(field, ".", config.EscapeChar+".", 1) + } else { + escapedField = config.EscapeChar + + strings.Replace(field, ".", config.EscapeChar+"."+config.EscapeChar, 1) + + config.EscapeChar + } + + fieldCache.Store(key, escapedField) + return escapedField.(string) +} + func toInt64(i interface{}) int64 { var result int64 diff --git a/adapter/sqlite3/sqlite3.go b/adapter/sqlite3/sqlite3.go index cb571ce4..19f8767d 100644 --- a/adapter/sqlite3/sqlite3.go +++ b/adapter/sqlite3/sqlite3.go @@ -31,7 +31,7 @@ var _ rel.Adapter = (*Adapter)(nil) func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ - Config: &sql.Config{ + Config: sql.Config{ Placeholder: "?", EscapeChar: "`", InsertDefaultValues: true, diff --git a/column.go b/column.go index 29aae406..eb3e750e 100644 --- a/column.go +++ b/column.go @@ -45,9 +45,9 @@ type Column struct { Options string } -func addColumn(name string, typ ColumnType, options []ColumnOption) Column { +func createColumn(name string, typ ColumnType, options []ColumnOption) Column { column := Column{ - Op: SchemaAdd, + Op: SchemaCreate, Name: name, Type: typ, } diff --git a/column_test.go b/column_test.go index ffdafa0a..7815bcc2 100644 --- a/column_test.go +++ b/column_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAddColumn(t *testing.T) { +func TestCreateColumn(t *testing.T) { var ( options = []ColumnOption{ Required(true), @@ -17,7 +17,7 @@ func TestAddColumn(t *testing.T) { Default(0), Options("options"), } - column = addColumn("add", Decimal, options) + column = createColumn("add", Decimal, options) ) assert.Equal(t, Column{ diff --git a/index.go b/index.go index 9e43c5cf..246d9b84 100644 --- a/index.go +++ b/index.go @@ -8,34 +8,21 @@ const ( SimpleIndex IndexType = "INDEX" // UniqueIndex IndexType. UniqueIndex IndexType = "UNIQUE" - // PrimaryKey IndexType. - PrimaryKey IndexType = "PRIMARY KEY" - // ForeignKey IndexType. - ForeignKey IndexType = "FOREIGN KEY" ) -// ForeignKeyReference definition. -type ForeignKeyReference struct { - Table string - Columns []string - OnDelete string - OnUpdate string -} - // Index definition. type Index struct { - Op SchemaOp - Name string - Type IndexType - Columns []string - NewName string - Reference ForeignKeyReference - Options string + Op SchemaOp + Name string + Type IndexType + Columns []string + NewName string + Options string } -func addIndex(columns []string, typ IndexType, options []IndexOption) Index { +func createIndex(columns []string, typ IndexType, options []IndexOption) Index { index := Index{ - Op: SchemaAdd, + Op: SchemaCreate, Columns: columns, Type: typ, } @@ -44,22 +31,6 @@ func addIndex(columns []string, typ IndexType, options []IndexOption) Index { return index } -// TODO: support multi columns -func addForeignKey(column string, refTable string, refColumn string, options []IndexOption) Index { - index := Index{ - Op: SchemaAdd, - Type: ForeignKey, - Columns: []string{column}, - Reference: ForeignKeyReference{ - Table: refTable, - Columns: []string{refColumn}, - }, - } - - applyIndexOptions(&index, options) - return index -} - func renameIndex(name string, newName string, options []IndexOption) Index { index := Index{ Op: SchemaRename, @@ -92,3 +63,14 @@ func applyIndexOptions(index *Index, options []IndexOption) { options[i].applyIndex(index) } } + +// Name option for defining custom index name. +type Name string + +func (n Name) applyIndex(index *Index) { + index.Name = string(n) +} + +func (n Name) applyKey(key *Key) { + key.Name = string(n) +} diff --git a/index_test.go b/index_test.go index 3958ca3a..f1fb7e51 100644 --- a/index_test.go +++ b/index_test.go @@ -12,7 +12,7 @@ func TestAddIndex(t *testing.T) { Name("simple"), Options("options"), } - index = addIndex([]string{"add"}, SimpleIndex, options) + index = createIndex([]string{"add"}, SimpleIndex, options) ) assert.Equal(t, Index{ @@ -23,31 +23,6 @@ func TestAddIndex(t *testing.T) { }, index) } -func TestAddForeignKey(t *testing.T) { - var ( - options = []IndexOption{ - OnDelete("cascade"), - OnUpdate("cascade"), - Name("fk"), - Options("options"), - } - index = addForeignKey("table_id", "table", "id", options) - ) - - assert.Equal(t, Index{ - Type: ForeignKey, - Name: "fk", - Columns: []string{"table_id"}, - Reference: ForeignKeyReference{ - Table: "table", - Columns: []string{"id"}, - OnDelete: "cascade", - OnUpdate: "cascade", - }, - Options: "options", - }, index) -} - func TestRenameIndex(t *testing.T) { var ( options = []IndexOption{ diff --git a/key.go b/key.go new file mode 100644 index 00000000..5a276f87 --- /dev/null +++ b/key.go @@ -0,0 +1,109 @@ +package rel + +// KeyType definition. +type KeyType string + +const ( + // PrimaryKey KeyType. + PrimaryKey KeyType = "PRIMARY KEY" + // ForeignKey KeyType. + ForeignKey KeyType = "FOREIGN KEY" + // UniqueKey KeyType. + UniqueKey = "UNIQUE" +) + +// ForeignKeyReference definition. +type ForeignKeyReference struct { + Table string + Columns []string + OnDelete string + OnUpdate string +} + +// Key definition. +type Key struct { + Op SchemaOp + Name string + Type KeyType + Columns []string + NewName string + Reference ForeignKeyReference + Options string +} + +func createKeys(columns []string, typ KeyType, options []KeyOption) Key { + key := Key{ + Op: SchemaCreate, + Columns: columns, + Type: typ, + } + + applyKeyOptions(&key, options) + return key +} + +func createPrimaryKeys(columns []string, options []KeyOption) Key { + return createKeys(columns, PrimaryKey, options) +} + +func createForeignKey(column string, refTable string, refColumn string, options []KeyOption) Key { + key := Key{ + Op: SchemaCreate, + Type: ForeignKey, + Columns: []string{column}, + Reference: ForeignKeyReference{ + Table: refTable, + Columns: []string{refColumn}, + }, + } + + applyKeyOptions(&key, options) + return key +} + +func renameKey(name string, newName string, options []KeyOption) Key { + key := Key{ + Op: SchemaRename, + Name: name, + NewName: newName, + } + + applyKeyOptions(&key, options) + return key +} + +func dropKey(name string, options []KeyOption) Key { + key := Key{ + Op: SchemaDrop, + Name: name, + } + + applyKeyOptions(&key, options) + return key +} + +// KeyOption interface. +// Available options are: Comment, Options. +type KeyOption interface { + applyKey(key *Key) +} + +func applyKeyOptions(key *Key, options []KeyOption) { + for i := range options { + options[i].applyKey(key) + } +} + +// OnDelete option for foreign key. +type OnDelete string + +func (od OnDelete) applyKey(key *Key) { + key.Reference.OnDelete = string(od) +} + +// OnUpdate option for foreign key. +type OnUpdate string + +func (ou OnUpdate) applyKey(key *Key) { + key.Reference.OnUpdate = string(ou) +} diff --git a/key_test.go b/key_test.go new file mode 100644 index 00000000..3346c897 --- /dev/null +++ b/key_test.go @@ -0,0 +1,32 @@ +package rel + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func CreateForeignKey(t *testing.T) { + var ( + options = []KeyOption{ + OnDelete("cascade"), + OnUpdate("cascade"), + Name("fk"), + Options("options"), + } + index = createForeignKey("table_id", "table", "id", options) + ) + + assert.Equal(t, Key{ + Type: ForeignKey, + Name: "fk", + Columns: []string{"table_id"}, + Reference: ForeignKeyReference{ + Table: "table", + Columns: []string{"id"}, + OnDelete: "cascade", + OnUpdate: "cascade", + }, + Options: "options", + }, index) +} diff --git a/schema.go b/schema.go index 289c6f13..ca86bbd4 100644 --- a/schema.go +++ b/schema.go @@ -4,8 +4,8 @@ package rel type SchemaOp uint8 const ( - // SchemaAdd operation. - SchemaAdd SchemaOp = iota + // SchemaCreate operation. + SchemaCreate SchemaOp = iota // SchemaAlter operation. SchemaAlter // SchemaRename operation. @@ -83,7 +83,7 @@ func (s *Schema) DropColumn(table string, name string, options ...ColumnOption) // AddIndex for columns. func (s *Schema) AddIndex(table string, column []string, typ IndexType, options ...IndexOption) { at := alterTable(table, nil) - at.Index(column, typ, options...) + // at.Index(column, typ, options...) s.add(at.Table) } @@ -121,6 +121,10 @@ func (o Options) applyIndex(index *Index) { index.Options = string(o) } +func (o Options) applyKey(key *Key) { + key.Options = string(o) +} + // Required disallows nil values in the column. type Required bool @@ -162,27 +166,6 @@ func Default(def interface{}) ColumnOption { return defaultValue{value: def} } -// Name option for defining custom index name. -type Name string - -func (n Name) applyIndex(index *Index) { - index.Name = string(n) -} - -// OnDelete option for foreign key index. -type OnDelete string - -func (od OnDelete) applyIndex(index *Index) { - index.Reference.OnDelete = string(od) -} - -// OnUpdate option for foreign key index. -type OnUpdate string - -func (ou OnUpdate) applyIndex(index *Index) { - index.Reference.OnUpdate = string(ou) -} - // Optional option. // when used with create table, will create table only if it's not exists. // when used with drop table, will drop table only if it's exists. diff --git a/schema_test.go b/schema_test.go index 3a8a7cd5..0e441058 100644 --- a/schema_test.go +++ b/schema_test.go @@ -16,7 +16,7 @@ func TestSchema_CreateTable(t *testing.T) { }) assert.Equal(t, Table{ - Op: SchemaAdd, + Op: SchemaCreate, Name: "products", Definitions: []interface{}{ Column{Name: "id", Type: ID}, @@ -38,7 +38,7 @@ func TestSchema_AlterTable(t *testing.T) { Op: SchemaAlter, Name: "users", Definitions: []interface{}{ - Column{Name: "verified", Type: Bool, Op: SchemaAdd}, + Column{Name: "verified", Type: Bool, Op: SchemaCreate}, Column{Name: "name", NewName: "fullname", Op: SchemaRename}, }, }, schema.Pending[0]) @@ -76,7 +76,7 @@ func TestSchema_AddColumn(t *testing.T) { Op: SchemaAlter, Name: "products", Definitions: []interface{}{ - Column{Name: "description", Type: String, Op: SchemaAdd}, + Column{Name: "description", Type: String, Op: SchemaCreate}, }, }, schema.Pending[0]) } @@ -132,7 +132,7 @@ func TestSchema_AddIndex(t *testing.T) { Op: SchemaAlter, Name: "products", Definitions: []interface{}{ - Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: SchemaAdd}, + Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: SchemaCreate}, }, }, schema.Pending[0]) } diff --git a/table.go b/table.go index 06f8209e..0354f393 100644 --- a/table.go +++ b/table.go @@ -12,7 +12,7 @@ type Table struct { // Column defines a column with name and type. func (t *Table) Column(name string, typ ColumnType, options ...ColumnOption) { - t.Definitions = append(t.Definitions, addColumn(name, typ, options)) + t.Definitions = append(t.Definitions, createColumn(name, typ, options)) } // ID defines a column with name and ID type. @@ -76,24 +76,29 @@ func (t *Table) Timestamp(name string, options ...ColumnOption) { t.Column(name, Timestamp, options...) } -// Index defines an index for columns. -func (t *Table) Index(columns []string, typ IndexType, options ...IndexOption) { - t.Definitions = append(t.Definitions, addIndex(columns, typ, options)) -} +// // Index defines an index for columns. +// func (t *Table) Index(columns []string, typ IndexType, options ...IndexOption) { +// t.Definitions = append(t.Definitions, createIndex(columns, typ, options)) +// } -// Unique defines an unique index for columns. -func (t *Table) Unique(columns []string, options ...IndexOption) { - t.Index(columns, UniqueIndex, options...) +// PrimaryKey defines a primary key for table. +func (t *Table) PrimaryKey(column string, options ...KeyOption) { + t.PrimaryKeys([]string{column}, options...) } -// PrimaryKey defines an primary key for table. -func (t *Table) PrimaryKey(columns []string, options ...IndexOption) { - t.Index(columns, PrimaryKey, options...) +// PrimaryKeys defines composite primary keys for table. +func (t *Table) PrimaryKeys(columns []string, options ...KeyOption) { + t.Definitions = append(t.Definitions, createPrimaryKeys(columns, options)) } // ForeignKey defines foreign key index. -func (t *Table) ForeignKey(column string, refTable string, refColumn string, options ...IndexOption) { - t.Definitions = append(t.Definitions, addForeignKey(column, refTable, refColumn, options)) +func (t *Table) ForeignKey(column string, refTable string, refColumn string, options ...KeyOption) { + t.Definitions = append(t.Definitions, createForeignKey(column, refTable, refColumn, options)) +} + +// Unique defines an unique key for columns. +func (t *Table) Unique(columns []string, options ...KeyOption) { + t.Definitions = append(t.Definitions, createKeys(columns, UniqueKey, options)) } // Fragment defines anything using sql fragment. @@ -135,7 +140,7 @@ func (at *AlterTable) DropIndex(name string, options ...IndexOption) { func createTable(name string, options []TableOption) Table { table := Table{ - Op: SchemaAdd, + Op: SchemaCreate, Name: name, } diff --git a/table_test.go b/table_test.go index e0f63ec1..b2907cec 100644 --- a/table_test.go +++ b/table_test.go @@ -105,25 +105,9 @@ func TestTable(t *testing.T) { }, table.Definitions[len(table.Definitions)-1]) }) - t.Run("Index", func(t *testing.T) { - table.Index([]string{"id"}, PrimaryKey) - assert.Equal(t, Index{ - Columns: []string{"id"}, - Type: PrimaryKey, - }, table.Definitions[len(table.Definitions)-1]) - }) - - t.Run("Unique", func(t *testing.T) { - table.Unique([]string{"username"}) - assert.Equal(t, Index{ - Columns: []string{"username"}, - Type: UniqueIndex, - }, table.Definitions[len(table.Definitions)-1]) - }) - t.Run("PrimaryKey", func(t *testing.T) { - table.PrimaryKey([]string{"id"}) - assert.Equal(t, Index{ + table.PrimaryKey("id") + assert.Equal(t, Key{ Columns: []string{"id"}, Type: PrimaryKey, }, table.Definitions[len(table.Definitions)-1]) @@ -131,7 +115,7 @@ func TestTable(t *testing.T) { t.Run("ForeignKey", func(t *testing.T) { table.ForeignKey("user_id", "users", "id") - assert.Equal(t, Index{ + assert.Equal(t, Key{ Columns: []string{"user_id"}, Type: ForeignKey, Reference: ForeignKeyReference{ From be060160fd00e43aae5f5728cbc687ee3a9fbaf9 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 20:38:04 +0700 Subject: [PATCH 23/44] wip index, fix tests --- adapter/sql/builder.go | 4 +-- adapter/sql/builder_test.go | 37 +++++-------------- column.go | 2 ++ index.go | 25 +++++-------- index_test.go | 24 +++---------- key.go | 2 ++ migrator/migrator.go | 10 +++--- schema.go | 36 ++++++++----------- schema_test.go | 71 ++++++++++++++----------------------- table.go | 21 +++++------ table_test.go | 17 --------- 11 files changed, 84 insertions(+), 165 deletions(-) diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index be1162c6..50cba3e3 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -69,8 +69,8 @@ func (b *Builder) createTable(buffer *Buffer, table rel.Table) { b.column(buffer, v) case rel.Key: b.key(buffer, v) - case string: - buffer.WriteString(v) + case rel.Raw: + buffer.WriteString(string(v)) } } diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 5b3e924b..849b5b00 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -50,7 +50,7 @@ func TestBuilder_Table(t *testing.T) { table: rel.Table{ Op: rel.SchemaCreate, Name: "products", - Definitions: []interface{}{ + Definitions: []rel.TableDefinition{ rel.Column{Name: "id", Type: rel.Int}, rel.Column{Name: "name", Type: rel.String}, rel.Column{Name: "description", Type: rel.Text}, @@ -59,11 +59,11 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), UNIQUE `date_unique` (`date`), INDEX (`datetime`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE) Engine=InnoDB;", + result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (`date`)) Engine=InnoDB;", table: rel.Table{ Op: rel.SchemaCreate, Name: "columns", - Definitions: []interface{}{ + Definitions: []rel.TableDefinition{ rel.Column{Name: "bool", Type: rel.Bool, Required: true, Default: false}, rel.Column{Name: "int", Type: rel.Int, Limit: 11, Unsigned: true}, rel.Column{Name: "bigint", Type: rel.BigInt, Limit: 20, Unsigned: true}, @@ -78,6 +78,7 @@ func TestBuilder_Table(t *testing.T) { rel.Column{Name: "blob", Type: "blob"}, rel.Key{Columns: []string{"int"}, Type: rel.PrimaryKey}, rel.Key{Columns: []string{"int", "string"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, + rel.Key{Columns: []string{"date"}, Type: rel.UniqueKey}, }, Options: "Engine=InnoDB", }, @@ -87,7 +88,7 @@ func TestBuilder_Table(t *testing.T) { table: rel.Table{ Op: rel.SchemaAlter, Name: "columns", - Definitions: []interface{}{ + Definitions: []rel.TableDefinition{ rel.Column{Name: "verified", Type: rel.Bool, Op: rel.SchemaCreate}, rel.Column{Name: "string", NewName: "name", Op: rel.SchemaRename}, rel.Column{Name: "bool", Type: rel.Int, Op: rel.SchemaAlter}, @@ -96,32 +97,12 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "ALTER TABLE `columns` ADD INDEX `verified_int` (`verified`, `int`);", + result: "ALTER TABLE `transactions` ADD FOREIGN KEY (`user_id`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE;", table: rel.Table{ Op: rel.SchemaAlter, - Name: "columns", - Definitions: []interface{}{ - rel.Index{Name: "verified_int", Columns: []string{"verified", "int"}, Type: rel.SimpleIndex, Op: rel.SchemaCreate}, - }, - }, - }, - { - result: "ALTER TABLE `columns` RENAME INDEX `verified_int` TO `verified_int_index`;", - table: rel.Table{ - Op: rel.SchemaAlter, - Name: "columns", - Definitions: []interface{}{ - rel.Index{Name: "verified_int", NewName: "verified_int_index", Op: rel.SchemaRename}, - }, - }, - }, - { - result: "ALTER TABLE `columns` DROP INDEX `verified_int_index`;", - table: rel.Table{ - Op: rel.SchemaAlter, - Name: "columns", - Definitions: []interface{}{ - rel.Index{Name: "verified_int_index", Op: rel.SchemaDrop}, + Name: "transactions", + Definitions: []rel.TableDefinition{ + rel.Key{Columns: []string{"user_id"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, }, }, }, diff --git a/column.go b/column.go index eb3e750e..ab2ab0ec 100644 --- a/column.go +++ b/column.go @@ -45,6 +45,8 @@ type Column struct { Options string } +func (Column) internalTableDefinition() {} + func createColumn(name string, typ ColumnType, options []ColumnOption) Column { column := Column{ Op: SchemaCreate, diff --git a/index.go b/index.go index 246d9b84..76149aae 100644 --- a/index.go +++ b/index.go @@ -13,16 +13,19 @@ const ( // Index definition. type Index struct { Op SchemaOp + Table string Name string Type IndexType Columns []string - NewName string Options string } -func createIndex(columns []string, typ IndexType, options []IndexOption) Index { +func (Index) internalMigration() {} + +func createIndex(table string, columns []string, typ IndexType, options []IndexOption) Index { index := Index{ Op: SchemaCreate, + Table: table, Columns: columns, Type: typ, } @@ -31,21 +34,11 @@ func createIndex(columns []string, typ IndexType, options []IndexOption) Index { return index } -func renameIndex(name string, newName string, options []IndexOption) Index { - index := Index{ - Op: SchemaRename, - Name: name, - NewName: newName, - } - - applyIndexOptions(&index, options) - return index -} - -func dropIndex(name string, options []IndexOption) Index { +func dropIndex(table string, name string, options []IndexOption) Index { index := Index{ - Op: SchemaDrop, - Name: name, + Op: SchemaDrop, + Table: table, + Name: name, } applyIndexOptions(&index, options) diff --git a/index_test.go b/index_test.go index f1fb7e51..149b45f1 100644 --- a/index_test.go +++ b/index_test.go @@ -6,49 +6,35 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAddIndex(t *testing.T) { +func TestCreateIndex(t *testing.T) { var ( options = []IndexOption{ Name("simple"), Options("options"), } - index = createIndex([]string{"add"}, SimpleIndex, options) + index = createIndex("table", []string{"add"}, SimpleIndex, options) ) assert.Equal(t, Index{ Type: SimpleIndex, + Table: "table", Name: "simple", Columns: []string{"add"}, Options: "options", }, index) } -func TestRenameIndex(t *testing.T) { - var ( - options = []IndexOption{ - Options("options"), - } - index = renameIndex("add", "rename", options) - ) - - assert.Equal(t, Index{ - Op: SchemaRename, - Name: "add", - NewName: "rename", - Options: "options", - }, index) -} - func TestDropIndex(t *testing.T) { var ( options = []IndexOption{ Options("options"), } - index = dropIndex("drop", options) + index = dropIndex("table", "drop", options) ) assert.Equal(t, Index{ Op: SchemaDrop, + Table: "table", Name: "drop", Options: "options", }, index) diff --git a/key.go b/key.go index 5a276f87..7fee1c92 100644 --- a/key.go +++ b/key.go @@ -31,6 +31,8 @@ type Key struct { Options string } +func (Key) internalTableDefinition() {} + func createKeys(columns []string, typ KeyType, options []KeyOption) Key { key := Key{ Op: SchemaCreate, diff --git a/migrator/migrator.go b/migrator/migrator.go index f163677f..5f9b5612 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -67,7 +67,7 @@ func (m Migrator) buildVersionTableDefinition() rel.Table { t.Unique([]string{"version"}) }, rel.Optional(true)) - return schema.Pending[0].(rel.Table) + return schema.Migration[0].(rel.Table) } func (m *Migrator) sync(ctx context.Context) { @@ -109,9 +109,9 @@ func (m *Migrator) Migrate(ctx context.Context) { m.repo.MustInsert(ctx, &version{Version: step.Version}) adapter := m.repo.Adapter(ctx).(rel.Adapter) - for _, migrator := range step.up.Pending { + for _, migration := range step.up.Migration { // TODO: exec script - switch v := migrator.(type) { + switch v := migration.(type) { case rel.Table: check(adapter.Apply(ctx, v)) } @@ -138,8 +138,8 @@ func (m *Migrator) Rollback(ctx context.Context) { m.repo.MustDelete(ctx, &v) adapter := m.repo.Adapter(ctx).(rel.Adapter) - for _, migrator := range v.down.Pending { - switch v := migrator.(type) { + for _, migration := range v.down.Migration { + switch v := migration.(type) { case rel.Table: check(adapter.Apply(ctx, v)) } diff --git a/schema.go b/schema.go index ca86bbd4..3b3894dc 100644 --- a/schema.go +++ b/schema.go @@ -14,18 +14,18 @@ const ( SchemaDrop ) -// Migrator private interface. -type Migrator interface { - migrate() +// Migration definition. +type Migration interface { + internalMigration() } // Schema builder. type Schema struct { - Pending []Migrator + Migration []Migration } -func (s *Schema) add(migrator Migrator) { - s.Pending = append(s.Pending, migrator) +func (s *Schema) add(migration Migration) { + s.Migration = append(s.Migration, migration) } // CreateTable with name and its definition. @@ -80,25 +80,14 @@ func (s *Schema) DropColumn(table string, name string, options ...ColumnOption) s.add(at.Table) } -// AddIndex for columns. -func (s *Schema) AddIndex(table string, column []string, typ IndexType, options ...IndexOption) { - at := alterTable(table, nil) - // at.Index(column, typ, options...) - s.add(at.Table) -} - -// RenameIndex by name. -func (s *Schema) RenameIndex(table string, name string, newName string, options ...IndexOption) { - at := alterTable(table, nil) - at.RenameIndex(name, newName, options...) - s.add(at.Table) +// CreateIndex for columns on a table. +func (s *Schema) CreateIndex(table string, column []string, typ IndexType, options ...IndexOption) { + s.add(createIndex(table, column, typ, options)) } // DropIndex by name. func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { - at := alterTable(table, nil) - at.DropIndex(name, options...) - s.add(at.Table) + s.add(dropIndex(table, name, options)) } // Exec queries using repo. @@ -174,3 +163,8 @@ type Optional bool func (o Optional) applyTable(table *Table) { table.Optional = bool(o) } + +// Raw string +type Raw string + +func (r Raw) internalTableDefinition() {} diff --git a/schema_test.go b/schema_test.go index 0e441058..034d81c9 100644 --- a/schema_test.go +++ b/schema_test.go @@ -18,12 +18,12 @@ func TestSchema_CreateTable(t *testing.T) { assert.Equal(t, Table{ Op: SchemaCreate, Name: "products", - Definitions: []interface{}{ + Definitions: []TableDefinition{ Column{Name: "id", Type: ID}, Column{Name: "name", Type: String}, Column{Name: "description", Type: Text}, }, - }, schema.Pending[0]) + }, schema.Migration[0]) } func TestSchema_AlterTable(t *testing.T) { @@ -37,11 +37,11 @@ func TestSchema_AlterTable(t *testing.T) { assert.Equal(t, Table{ Op: SchemaAlter, Name: "users", - Definitions: []interface{}{ + Definitions: []TableDefinition{ Column{Name: "verified", Type: Bool, Op: SchemaCreate}, Column{Name: "name", NewName: "fullname", Op: SchemaRename}, }, - }, schema.Pending[0]) + }, schema.Migration[0]) } func TestSchema_RenameTable(t *testing.T) { @@ -53,7 +53,7 @@ func TestSchema_RenameTable(t *testing.T) { Op: SchemaRename, Name: "trxs", NewName: "transactions", - }, schema.Pending[0]) + }, schema.Migration[0]) } func TestSchema_DropTable(t *testing.T) { @@ -64,7 +64,7 @@ func TestSchema_DropTable(t *testing.T) { assert.Equal(t, Table{ Op: SchemaDrop, Name: "logs", - }, schema.Pending[0]) + }, schema.Migration[0]) } func TestSchema_AddColumn(t *testing.T) { @@ -75,10 +75,10 @@ func TestSchema_AddColumn(t *testing.T) { assert.Equal(t, Table{ Op: SchemaAlter, Name: "products", - Definitions: []interface{}{ + Definitions: []TableDefinition{ Column{Name: "description", Type: String, Op: SchemaCreate}, }, - }, schema.Pending[0]) + }, schema.Migration[0]) } func TestSchema_AlterColumn(t *testing.T) { @@ -89,10 +89,10 @@ func TestSchema_AlterColumn(t *testing.T) { assert.Equal(t, Table{ Op: SchemaAlter, Name: "products", - Definitions: []interface{}{ + Definitions: []TableDefinition{ Column{Name: "sale", Type: Bool, Op: SchemaAlter}, }, - }, schema.Pending[0]) + }, schema.Migration[0]) } func TestSchema_RenameColumn(t *testing.T) { @@ -103,10 +103,10 @@ func TestSchema_RenameColumn(t *testing.T) { assert.Equal(t, Table{ Op: SchemaAlter, Name: "users", - Definitions: []interface{}{ + Definitions: []TableDefinition{ Column{Name: "name", NewName: "fullname", Op: SchemaRename}, }, - }, schema.Pending[0]) + }, schema.Migration[0]) } func TestSchema_DropColumn(t *testing.T) { @@ -117,38 +117,23 @@ func TestSchema_DropColumn(t *testing.T) { assert.Equal(t, Table{ Op: SchemaAlter, Name: "users", - Definitions: []interface{}{ + Definitions: []TableDefinition{ Column{Name: "verified", Op: SchemaDrop}, }, - }, schema.Pending[0]) + }, schema.Migration[0]) } -func TestSchema_AddIndex(t *testing.T) { +func TestSchema_CreateIndex(t *testing.T) { var schema Schema - schema.AddIndex("products", []string{"sale"}, SimpleIndex) + schema.CreateIndex("products", []string{"sale"}, SimpleIndex) - assert.Equal(t, Table{ - Op: SchemaAlter, - Name: "products", - Definitions: []interface{}{ - Index{Columns: []string{"sale"}, Type: SimpleIndex, Op: SchemaCreate}, - }, - }, schema.Pending[0]) -} - -func TestSchema_RenameIndex(t *testing.T) { - var schema Schema - - schema.RenameIndex("products", "store_id", "fk_store_id") - - assert.Equal(t, Table{ - Op: SchemaAlter, - Name: "products", - Definitions: []interface{}{ - Index{Name: "store_id", NewName: "fk_store_id", Op: SchemaRename}, - }, - }, schema.Pending[0]) + assert.Equal(t, Index{ + Table: "products", + Columns: []string{"sale"}, + Type: SimpleIndex, + Op: SchemaCreate, + }, schema.Migration[0]) } func TestSchema_DropIndex(t *testing.T) { @@ -156,11 +141,9 @@ func TestSchema_DropIndex(t *testing.T) { schema.DropIndex("products", "sale") - assert.Equal(t, Table{ - Op: SchemaAlter, - Name: "products", - Definitions: []interface{}{ - Index{Name: "sale", Op: SchemaDrop}, - }, - }, schema.Pending[0]) + assert.Equal(t, Index{ + Table: "products", + Name: "sale", + Op: SchemaDrop, + }, schema.Migration[0]) } diff --git a/table.go b/table.go index 0354f393..cf5ca719 100644 --- a/table.go +++ b/table.go @@ -1,11 +1,16 @@ package rel +// TableDefinition interface. +type TableDefinition interface { + internalTableDefinition() +} + // Table definition. type Table struct { Op SchemaOp Name string NewName string - Definitions []interface{} + Definitions []TableDefinition Optional bool Options string } @@ -103,10 +108,10 @@ func (t *Table) Unique(columns []string, options ...KeyOption) { // Fragment defines anything using sql fragment. func (t *Table) Fragment(fragment string) { - t.Definitions = append(t.Definitions, fragment) + t.Definitions = append(t.Definitions, Raw(fragment)) } -func (t Table) migrate() {} +func (t Table) internalMigration() {} // AlterTable Migrator. type AlterTable struct { @@ -128,16 +133,6 @@ func (at *AlterTable) DropColumn(name string, options ...ColumnOption) { at.Definitions = append(at.Definitions, dropColumn(name, options)) } -// RenameIndex to a new name. -func (at *AlterTable) RenameIndex(name string, newName string, options ...IndexOption) { - at.Definitions = append(at.Definitions, renameIndex(name, newName, options)) -} - -// DropIndex from this table. -func (at *AlterTable) DropIndex(name string, options ...IndexOption) { - at.Definitions = append(at.Definitions, dropIndex(name, options)) -} - func createTable(name string, options []TableOption) Table { table := Table{ Op: SchemaCreate, diff --git a/table_test.go b/table_test.go index b2907cec..4d06fdbd 100644 --- a/table_test.go +++ b/table_test.go @@ -154,23 +154,6 @@ func TestAlterTable(t *testing.T) { Name: "column", }, table.Definitions[len(table.Definitions)-1]) }) - - t.Run("RenameIndex", func(t *testing.T) { - table.RenameIndex("index", "new_index") - assert.Equal(t, Index{ - Op: SchemaRename, - Name: "index", - NewName: "new_index", - }, table.Definitions[len(table.Definitions)-1]) - }) - - t.Run("DropIndex", func(t *testing.T) { - table.DropIndex("index") - assert.Equal(t, Index{ - Op: SchemaDrop, - Name: "index", - }, table.Definitions[len(table.Definitions)-1]) - }) } func TestCreateTable(t *testing.T) { From f3fa30bc0aef888734ea3b49862d5eb757b18f61 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 23:09:50 +0700 Subject: [PATCH 24/44] implement create index --- adapter.go | 2 +- adapter/mysql/mysql.go | 11 +++++----- adapter/specs/migration.go | 8 ++++--- adapter/sql/adapter.go | 14 ++++++++++--- adapter/sql/builder.go | 42 +++++++++++++++++++++++++++++++++++++ adapter/sql/builder_test.go | 5 ++--- adapter/sql/config.go | 1 + adapter_test.go | 4 ++-- index.go | 18 +++++++++------- index_test.go | 6 +++--- migrator/migrator.go | 38 +++++++++++++-------------------- reltest/nop_adapter.go | 2 +- schema.go | 12 +++++++---- schema_test.go | 23 ++++++++++---------- 14 files changed, 119 insertions(+), 67 deletions(-) diff --git a/adapter.go b/adapter.go index ab7c16f7..bda5b80e 100644 --- a/adapter.go +++ b/adapter.go @@ -19,5 +19,5 @@ type Adapter interface { Commit(ctx context.Context) error Rollback(ctx context.Context) error - Apply(ctx context.Context, table Table) error + Apply(ctx context.Context, migration Migration) error } diff --git a/adapter/mysql/mysql.go b/adapter/mysql/mysql.go index d3e4a193..1d175f07 100644 --- a/adapter/mysql/mysql.go +++ b/adapter/mysql/mysql.go @@ -32,11 +32,12 @@ func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ Config: sql.Config{ - Placeholder: "?", - EscapeChar: "`", - IncrementFunc: incrementFunc, - ErrorFunc: errorFunc, - MapColumnFunc: sql.MapColumn, + DropIndexOnTable: true, + Placeholder: "?", + EscapeChar: "`", + IncrementFunc: incrementFunc, + ErrorFunc: errorFunc, + MapColumnFunc: sql.MapColumn, }, DB: database, }, diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index 6d454bc7..4948ee4b 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -121,14 +121,16 @@ func MigrateTable(t *testing.T, repo rel.Repository) { t.Timestamp("timestamp1") t.Timestamp("timestamp2", rel.Default(time.Now())) - // t.Index([]string{"int1"}, rel.SimpleIndex) - // t.Index([]string{"string1", "string2"}, rel.SimpleIndex) - t.Unique([]string{"int2"}) t.Unique([]string{"bigint1", "bigint2"}) }) + + schema.CreateIndex("dummies", "int1_idx", []string{"int1"}) + schema.CreateIndex("dummies", "string1_string2_idx", []string{"string1", "string2"}) }, func(schema *rel.Schema) { + schema.DropIndex("dummies", "int1_idx") + schema.DropIndex("dummies", "string1_string2_idx") schema.DropTable("dummies") }, ) diff --git a/adapter/sql/adapter.go b/adapter/sql/adapter.go index 5ea8f6a8..81581340 100644 --- a/adapter/sql/adapter.go +++ b/adapter/sql/adapter.go @@ -245,12 +245,20 @@ func (a *Adapter) Rollback(ctx context.Context) error { } // Apply table. -func (a *Adapter) Apply(ctx context.Context, table rel.Table) error { +func (a *Adapter) Apply(ctx context.Context, migration rel.Migration) error { var ( - statement = NewBuilder(a.Config).Table(table) - _, _, err = a.Exec(ctx, statement, nil) + statement string + builder = NewBuilder(a.Config) ) + switch v := migration.(type) { + case rel.Table: + statement = builder.Table(v) + case rel.Index: + statement = builder.Index(v) + } + + _, _, err := a.Exec(ctx, statement, nil) return err } diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 50cba3e3..2e012b61 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -223,6 +223,48 @@ func (b *Builder) key(buffer *Buffer, key rel.Key) { b.options(buffer, key.Options) } +// Index generates query for index. +func (b *Builder) Index(index rel.Index) string { + var buffer Buffer + + switch index.Op { + case rel.SchemaCreate: + buffer.WriteString("CREATE ") + buffer.WriteString(string(index.Type)) + buffer.WriteByte(' ') + buffer.WriteString(Escape(b.config, index.Name)) + buffer.WriteString(" ON ") + buffer.WriteString(Escape(b.config, index.Table)) + + buffer.WriteString(" (") + for i, col := range index.Columns { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(Escape(b.config, col)) + } + buffer.WriteString(")") + case rel.SchemaDrop: + buffer.WriteString("DROP INDEX ") + + if index.Optional { + buffer.WriteString("IF EXISTS ") + } + + buffer.WriteString(Escape(b.config, index.Name)) + + if b.config.DropIndexOnTable { + buffer.WriteString(" ON ") + buffer.WriteString(Escape(b.config, index.Table)) + } + } + + b.options(&buffer, index.Options) + buffer.WriteByte(';') + + return buffer.String() +} + func (b *Builder) options(buffer *Buffer, options string) { if options == "" { return diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 849b5b00..d8482a61 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -46,15 +46,14 @@ func TestBuilder_Table(t *testing.T) { table rel.Table }{ { - result: "CREATE TABLE `products` (`id` INT, `name` VARCHAR(255), `description` TEXT, PRIMARY KEY (`id`));", + result: "CREATE TABLE `products` (`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(255), `description` TEXT);", table: rel.Table{ Op: rel.SchemaCreate, Name: "products", Definitions: []rel.TableDefinition{ - rel.Column{Name: "id", Type: rel.Int}, + rel.Column{Name: "id", Type: rel.ID}, rel.Column{Name: "name", Type: rel.String}, rel.Column{Name: "description", Type: rel.Text}, - rel.Key{Columns: []string{"id"}, Type: rel.PrimaryKey}, }, }, }, diff --git a/adapter/sql/config.go b/adapter/sql/config.go index d0c3cd17..95f8068a 100644 --- a/adapter/sql/config.go +++ b/adapter/sql/config.go @@ -11,6 +11,7 @@ type Config struct { Placeholder string Ordinal bool InsertDefaultValues bool + DropIndexOnTable bool EscapeChar string ErrorFunc func(error) error IncrementFunc func(Adapter) int diff --git a/adapter_test.go b/adapter_test.go index a0e0e03b..7f2a2cda 100644 --- a/adapter_test.go +++ b/adapter_test.go @@ -76,8 +76,8 @@ func (ta *testAdapter) Rollback(ctx context.Context) error { return args.Error(0) } -func (ta *testAdapter) Apply(ctx context.Context, table Table) error { - args := ta.Called(table) +func (ta *testAdapter) Apply(ctx context.Context, migration Migration) error { + args := ta.Called(migration) return args.Error(0) } diff --git a/index.go b/index.go index 76149aae..970809a1 100644 --- a/index.go +++ b/index.go @@ -12,22 +12,24 @@ const ( // Index definition. type Index struct { - Op SchemaOp - Table string - Name string - Type IndexType - Columns []string - Options string + Op SchemaOp + Table string + Name string + Type IndexType + Columns []string + Optional bool + Options string } func (Index) internalMigration() {} -func createIndex(table string, columns []string, typ IndexType, options []IndexOption) Index { +func createIndex(table string, name string, columns []string, options []IndexOption) Index { index := Index{ Op: SchemaCreate, Table: table, + Name: name, Columns: columns, - Type: typ, + Type: SimpleIndex, } applyIndexOptions(&index, options) diff --git a/index_test.go b/index_test.go index 149b45f1..30189104 100644 --- a/index_test.go +++ b/index_test.go @@ -7,18 +7,18 @@ import ( ) func TestCreateIndex(t *testing.T) { + // TODO: unique option var ( options = []IndexOption{ - Name("simple"), Options("options"), } - index = createIndex("table", []string{"add"}, SimpleIndex, options) + index = createIndex("table", "add_idx", []string{"add"}, options) ) assert.Equal(t, Index{ Type: SimpleIndex, Table: "table", - Name: "simple", + Name: "add_idx", Columns: []string{"add"}, Options: "options", }, index) diff --git a/migrator/migrator.go b/migrator/migrator.go index 5f9b5612..13ea8861 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -67,7 +67,7 @@ func (m Migrator) buildVersionTableDefinition() rel.Table { t.Unique([]string{"version"}) }, rel.Optional(true)) - return schema.Migration[0].(rel.Table) + return schema.Migrations[0].(rel.Table) } func (m *Migrator) sync(ctx context.Context) { @@ -100,23 +100,14 @@ func (m *Migrator) sync(ctx context.Context) { func (m *Migrator) Migrate(ctx context.Context) { m.sync(ctx) - for _, step := range m.versions { - if step.applied { + for _, v := range m.versions { + if v.applied { continue } err := m.repo.Transaction(ctx, func(ctx context.Context) error { - m.repo.MustInsert(ctx, &version{Version: step.Version}) - - adapter := m.repo.Adapter(ctx).(rel.Adapter) - for _, migration := range step.up.Migration { - // TODO: exec script - switch v := migration.(type) { - case rel.Table: - check(adapter.Apply(ctx, v)) - } - } - + m.repo.MustInsert(ctx, &version{Version: v.Version}) + m.run(ctx, v.up.Migrations) return nil }) @@ -136,15 +127,7 @@ func (m *Migrator) Rollback(ctx context.Context) { err := m.repo.Transaction(ctx, func(ctx context.Context) error { m.repo.MustDelete(ctx, &v) - - adapter := m.repo.Adapter(ctx).(rel.Adapter) - for _, migration := range v.down.Migration { - switch v := migration.(type) { - case rel.Table: - check(adapter.Apply(ctx, v)) - } - } - + m.run(ctx, v.down.Migrations) return nil }) @@ -155,6 +138,15 @@ func (m *Migrator) Rollback(ctx context.Context) { } } +func (m *Migrator) run(ctx context.Context, migrations []rel.Migration) { + adapter := m.repo.Adapter(ctx).(rel.Adapter) + for _, migration := range migrations { + // TODO: exec script + check(adapter.Apply(ctx, migration)) + } + +} + // New migrationr. func New(repo rel.Repository) Migrator { return Migrator{repo: repo} diff --git a/reltest/nop_adapter.go b/reltest/nop_adapter.go index 9cabab98..27c21c01 100644 --- a/reltest/nop_adapter.go +++ b/reltest/nop_adapter.go @@ -61,7 +61,7 @@ func (na *nopAdapter) Update(ctx context.Context, query rel.Query, mutates map[s return 1, nil } -func (na *nopAdapter) Apply(ctx context.Context, table rel.Table) error { +func (na *nopAdapter) Apply(ctx context.Context, migration rel.Migration) error { return nil } diff --git a/schema.go b/schema.go index 3b3894dc..6c938be3 100644 --- a/schema.go +++ b/schema.go @@ -21,11 +21,11 @@ type Migration interface { // Schema builder. type Schema struct { - Migration []Migration + Migrations []Migration } func (s *Schema) add(migration Migration) { - s.Migration = append(s.Migration, migration) + s.Migrations = append(s.Migrations, migration) } // CreateTable with name and its definition. @@ -81,8 +81,8 @@ func (s *Schema) DropColumn(table string, name string, options ...ColumnOption) } // CreateIndex for columns on a table. -func (s *Schema) CreateIndex(table string, column []string, typ IndexType, options ...IndexOption) { - s.add(createIndex(table, column, typ, options)) +func (s *Schema) CreateIndex(table string, name string, column []string, options ...IndexOption) { + s.add(createIndex(table, name, column, options)) } // DropIndex by name. @@ -164,6 +164,10 @@ func (o Optional) applyTable(table *Table) { table.Optional = bool(o) } +func (o Optional) applyIndex(index *Index) { + index.Optional = bool(o) +} + // Raw string type Raw string diff --git a/schema_test.go b/schema_test.go index 034d81c9..8b9cc732 100644 --- a/schema_test.go +++ b/schema_test.go @@ -23,7 +23,7 @@ func TestSchema_CreateTable(t *testing.T) { Column{Name: "name", Type: String}, Column{Name: "description", Type: Text}, }, - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_AlterTable(t *testing.T) { @@ -41,7 +41,7 @@ func TestSchema_AlterTable(t *testing.T) { Column{Name: "verified", Type: Bool, Op: SchemaCreate}, Column{Name: "name", NewName: "fullname", Op: SchemaRename}, }, - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_RenameTable(t *testing.T) { @@ -53,7 +53,7 @@ func TestSchema_RenameTable(t *testing.T) { Op: SchemaRename, Name: "trxs", NewName: "transactions", - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_DropTable(t *testing.T) { @@ -64,7 +64,7 @@ func TestSchema_DropTable(t *testing.T) { assert.Equal(t, Table{ Op: SchemaDrop, Name: "logs", - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_AddColumn(t *testing.T) { @@ -78,7 +78,7 @@ func TestSchema_AddColumn(t *testing.T) { Definitions: []TableDefinition{ Column{Name: "description", Type: String, Op: SchemaCreate}, }, - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_AlterColumn(t *testing.T) { @@ -92,7 +92,7 @@ func TestSchema_AlterColumn(t *testing.T) { Definitions: []TableDefinition{ Column{Name: "sale", Type: Bool, Op: SchemaAlter}, }, - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_RenameColumn(t *testing.T) { @@ -106,7 +106,7 @@ func TestSchema_RenameColumn(t *testing.T) { Definitions: []TableDefinition{ Column{Name: "name", NewName: "fullname", Op: SchemaRename}, }, - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_DropColumn(t *testing.T) { @@ -120,20 +120,21 @@ func TestSchema_DropColumn(t *testing.T) { Definitions: []TableDefinition{ Column{Name: "verified", Op: SchemaDrop}, }, - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_CreateIndex(t *testing.T) { var schema Schema - schema.CreateIndex("products", []string{"sale"}, SimpleIndex) + schema.CreateIndex("products", "sale_idx", []string{"sale"}) assert.Equal(t, Index{ Table: "products", + Name: "sale_idx", Columns: []string{"sale"}, Type: SimpleIndex, Op: SchemaCreate, - }, schema.Migration[0]) + }, schema.Migrations[0]) } func TestSchema_DropIndex(t *testing.T) { @@ -145,5 +146,5 @@ func TestSchema_DropIndex(t *testing.T) { Table: "products", Name: "sale", Op: SchemaDrop, - }, schema.Migration[0]) + }, schema.Migrations[0]) } From 3cd5e717b7c7b40722b1c2cc211efe20954b0851 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 23:29:52 +0700 Subject: [PATCH 25/44] add unique option --- adapter/sql/builder.go | 10 ++++-- adapter/sql/sql.go | 74 ++++++++++++++++++++---------------------- column.go | 53 ++++++++++++++++++++++++++++++ column_test.go | 2 ++ index.go | 26 ++++++++------- index_test.go | 19 +++++++++-- schema.go | 46 +++----------------------- schema_test.go | 28 +++++++++++++++- 8 files changed, 162 insertions(+), 96 deletions(-) diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 2e012b61..2c192d26 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -154,6 +154,10 @@ func (b *Builder) column(buffer *Buffer, column rel.Column) { buffer.WriteString(" UNSIGNED") } + if column.Unique { + buffer.WriteString(" UNIQUE") + } + if column.Required { buffer.WriteString(" NOT NULL") } @@ -230,8 +234,10 @@ func (b *Builder) Index(index rel.Index) string { switch index.Op { case rel.SchemaCreate: buffer.WriteString("CREATE ") - buffer.WriteString(string(index.Type)) - buffer.WriteByte(' ') + if index.Unique { + buffer.WriteString("UNIQUE ") + } + buffer.WriteString("INDEX ") buffer.WriteString(Escape(b.config, index.Name)) buffer.WriteString(" ON ") buffer.WriteString(Escape(b.config, index.Table)) diff --git a/adapter/sql/sql.go b/adapter/sql/sql.go index 22152be3..e482a324 100644 --- a/adapter/sql/sql.go +++ b/adapter/sql/sql.go @@ -1,40 +1,38 @@ package sql -import "github.com/Fs02/rel" - -// IndexToSQL converts index struct as a sql string. -// Return true if it's an inline sql. -func IndexToSQL(config Config, index rel.Index) (string, bool) { - var ( - buffer Buffer - typ = string(index.Type) - ) - - buffer.WriteString(typ) - - if index.Name != "" { - buffer.WriteByte(' ') - buffer.WriteString(Escape(config, index.Name)) - } - - buffer.WriteString(" (") - for i, col := range index.Columns { - if i > 0 { - buffer.WriteString(", ") - } - buffer.WriteString(Escape(config, col)) - } - buffer.WriteString(")") - - optionsToSQL(&buffer, index.Options) - return buffer.String(), true -} - -func optionsToSQL(buffer *Buffer, options string) { - if options == "" { - return - } - - buffer.WriteByte(' ') - buffer.WriteString(options) -} +// // IndexToSQL converts index struct as a sql string. +// // Return true if it's an inline sql. +// func IndexToSQL(config Config, index rel.Index) (string, bool) { +// var ( +// buffer Buffer +// typ = string(index.Type) +// ) + +// buffer.WriteString(typ) + +// if index.Name != "" { +// buffer.WriteByte(' ') +// buffer.WriteString(Escape(config, index.Name)) +// } + +// buffer.WriteString(" (") +// for i, col := range index.Columns { +// if i > 0 { +// buffer.WriteString(", ") +// } +// buffer.WriteString(Escape(config, col)) +// } +// buffer.WriteString(")") + +// optionsToSQL(&buffer, index.Options) +// return buffer.String(), true +// } + +// func optionsToSQL(buffer *Buffer, options string) { +// if options == "" { +// return +// } + +// buffer.WriteByte(' ') +// buffer.WriteString(options) +// } diff --git a/column.go b/column.go index ab2ab0ec..3d99e664 100644 --- a/column.go +++ b/column.go @@ -36,6 +36,7 @@ type Column struct { Name string Type ColumnType NewName string + Unique bool Required bool Unsigned bool Limit int @@ -101,3 +102,55 @@ func applyColumnOptions(column *Column, options []ColumnOption) { options[i].applyColumn(column) } } + +// Unique set column as unique. +type Unique bool + +func (r Unique) applyColumn(column *Column) { + column.Unique = bool(r) +} + +func (r Unique) applyIndex(index *Index) { + index.Unique = bool(r) +} + +// Required disallows nil values in the column. +type Required bool + +func (r Required) applyColumn(column *Column) { + column.Required = bool(r) +} + +// Unsigned sets integer column to be unsigned. +type Unsigned bool + +func (u Unsigned) applyColumn(column *Column) { + column.Unsigned = bool(u) +} + +// Precision defines the precision for the decimal fields, representing the total number of digits in the number. +type Precision int + +func (p Precision) applyColumn(column *Column) { + column.Precision = int(p) +} + +// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. +type Scale int + +func (s Scale) applyColumn(column *Column) { + column.Scale = int(s) +} + +type defaultValue struct { + value interface{} +} + +func (d defaultValue) applyColumn(column *Column) { + column.Default = d.value +} + +// Default allows to set a default value on the column.). +func Default(def interface{}) ColumnOption { + return defaultValue{value: def} +} diff --git a/column_test.go b/column_test.go index 7815bcc2..04a72ef3 100644 --- a/column_test.go +++ b/column_test.go @@ -9,6 +9,7 @@ import ( func TestCreateColumn(t *testing.T) { var ( options = []ColumnOption{ + Unique(true), Required(true), Unsigned(true), Limit(1000), @@ -23,6 +24,7 @@ func TestCreateColumn(t *testing.T) { assert.Equal(t, Column{ Name: "add", Type: Decimal, + Unique: true, Required: true, Unsigned: true, Limit: 1000, diff --git a/index.go b/index.go index 970809a1..8f68f2c2 100644 --- a/index.go +++ b/index.go @@ -1,21 +1,11 @@ package rel -// IndexType definition. -type IndexType string - -const ( - // SimpleIndex IndexType. - SimpleIndex IndexType = "INDEX" - // UniqueIndex IndexType. - UniqueIndex IndexType = "UNIQUE" -) - // Index definition. type Index struct { Op SchemaOp Table string Name string - Type IndexType + Unique bool Columns []string Optional bool Options string @@ -29,7 +19,19 @@ func createIndex(table string, name string, columns []string, options []IndexOpt Table: table, Name: name, Columns: columns, - Type: SimpleIndex, + } + + applyIndexOptions(&index, options) + return index +} + +func createUniqueIndex(table string, name string, columns []string, options []IndexOption) Index { + index := Index{ + Op: SchemaCreate, + Table: table, + Name: name, + Unique: true, + Columns: columns, } applyIndexOptions(&index, options) diff --git a/index_test.go b/index_test.go index 30189104..889cf88c 100644 --- a/index_test.go +++ b/index_test.go @@ -7,7 +7,6 @@ import ( ) func TestCreateIndex(t *testing.T) { - // TODO: unique option var ( options = []IndexOption{ Options("options"), @@ -16,7 +15,6 @@ func TestCreateIndex(t *testing.T) { ) assert.Equal(t, Index{ - Type: SimpleIndex, Table: "table", Name: "add_idx", Columns: []string{"add"}, @@ -24,6 +22,23 @@ func TestCreateIndex(t *testing.T) { }, index) } +func TestCreateUniqueIndex(t *testing.T) { + var ( + options = []IndexOption{ + Options("options"), + } + index = createUniqueIndex("table", "add_idx", []string{"add"}, options) + ) + + assert.Equal(t, Index{ + Table: "table", + Name: "add_idx", + Unique: true, + Columns: []string{"add"}, + Options: "options", + }, index) +} + func TestDropIndex(t *testing.T) { var ( options = []IndexOption{ diff --git a/schema.go b/schema.go index 6c938be3..87368b29 100644 --- a/schema.go +++ b/schema.go @@ -85,6 +85,11 @@ func (s *Schema) CreateIndex(table string, name string, column []string, options s.add(createIndex(table, name, column, options)) } +// CreateUniqueIndex for columns on a table. +func (s *Schema) CreateUniqueIndex(table string, name string, column []string, options ...IndexOption) { + s.add(createUniqueIndex(table, name, column, options)) +} + // DropIndex by name. func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { s.add(dropIndex(table, name, options)) @@ -114,47 +119,6 @@ func (o Options) applyKey(key *Key) { key.Options = string(o) } -// Required disallows nil values in the column. -type Required bool - -func (r Required) applyColumn(column *Column) { - column.Required = bool(r) -} - -// Unsigned sets integer column to be unsigned. -type Unsigned bool - -func (u Unsigned) applyColumn(column *Column) { - column.Unsigned = bool(u) -} - -// Precision defines the precision for the decimal fields, representing the total number of digits in the number. -type Precision int - -func (p Precision) applyColumn(column *Column) { - column.Precision = int(p) -} - -// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. -type Scale int - -func (s Scale) applyColumn(column *Column) { - column.Scale = int(s) -} - -type defaultValue struct { - value interface{} -} - -func (d defaultValue) applyColumn(column *Column) { - column.Default = d.value -} - -// Default allows to set a default value on the column.). -func Default(def interface{}) ColumnOption { - return defaultValue{value: def} -} - // Optional option. // when used with create table, will create table only if it's not exists. // when used with drop table, will drop table only if it's exists. diff --git a/schema_test.go b/schema_test.go index 8b9cc732..9f53d4b3 100644 --- a/schema_test.go +++ b/schema_test.go @@ -132,7 +132,33 @@ func TestSchema_CreateIndex(t *testing.T) { Table: "products", Name: "sale_idx", Columns: []string{"sale"}, - Type: SimpleIndex, + Op: SchemaCreate, + }, schema.Migrations[0]) +} + +func TestSchema_CreateIndex_unique(t *testing.T) { + var schema Schema + + schema.CreateIndex("products", "sale_idx", []string{"sale"}, Unique(true)) + + assert.Equal(t, Index{ + Table: "products", + Name: "sale_idx", + Unique: true, + Columns: []string{"sale"}, + Op: SchemaCreate, + }, schema.Migrations[0]) +} + +func TestSchema_CreateUniqueIndex(t *testing.T) { + var schema Schema + + schema.CreateUniqueIndex("products", "sale_idx", []string{"sale"}) + assert.Equal(t, Index{ + Table: "products", + Name: "sale_idx", + Unique: true, + Columns: []string{"sale"}, Op: SchemaCreate, }, schema.Migrations[0]) } From b2ff252cbaa31257916e2f47694d0753fb9dc500 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 23:36:44 +0700 Subject: [PATCH 26/44] move config to global variable --- adapter/mysql/mysql.go | 25 +++++++++++++++---------- adapter/postgres/postgres.go | 25 +++++++++++++++---------- adapter/sqlite3/sqlite3.go | 25 +++++++++++++++---------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/adapter/mysql/mysql.go b/adapter/mysql/mysql.go index 1d175f07..1d2e2689 100644 --- a/adapter/mysql/mysql.go +++ b/adapter/mysql/mysql.go @@ -25,21 +25,26 @@ type Adapter struct { *sql.Adapter } -var _ rel.Adapter = (*Adapter)(nil) +var ( + _ rel.Adapter = (*Adapter)(nil) + + // Config for mysql adapter. + Config = sql.Config{ + DropIndexOnTable: true, + Placeholder: "?", + EscapeChar: "`", + IncrementFunc: incrementFunc, + ErrorFunc: errorFunc, + MapColumnFunc: sql.MapColumn, + } +) // New is mysql adapter constructor. func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ - Config: sql.Config{ - DropIndexOnTable: true, - Placeholder: "?", - EscapeChar: "`", - IncrementFunc: incrementFunc, - ErrorFunc: errorFunc, - MapColumnFunc: sql.MapColumn, - }, - DB: database, + Config: Config, + DB: database, }, } } diff --git a/adapter/postgres/postgres.go b/adapter/postgres/postgres.go index a92c3a2b..8b644a7e 100644 --- a/adapter/postgres/postgres.go +++ b/adapter/postgres/postgres.go @@ -26,21 +26,26 @@ type Adapter struct { *sql.Adapter } -var _ rel.Adapter = (*Adapter)(nil) +var ( + _ rel.Adapter = (*Adapter)(nil) + + // Config for postgres adapter. + Config = sql.Config{ + Placeholder: "$", + EscapeChar: "\"", + Ordinal: true, + InsertDefaultValues: true, + ErrorFunc: errorFunc, + MapColumnFunc: mapColumnFunc, + } +) // New is postgres adapter constructor. func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ - Config: sql.Config{ - Placeholder: "$", - EscapeChar: "\"", - Ordinal: true, - InsertDefaultValues: true, - ErrorFunc: errorFunc, - MapColumnFunc: mapColumnFunc, - }, - DB: database, + Config: Config, + DB: database, }, } } diff --git a/adapter/sqlite3/sqlite3.go b/adapter/sqlite3/sqlite3.go index 19f8767d..789c190c 100644 --- a/adapter/sqlite3/sqlite3.go +++ b/adapter/sqlite3/sqlite3.go @@ -25,21 +25,26 @@ type Adapter struct { *sql.Adapter } -var _ rel.Adapter = (*Adapter)(nil) +var ( + _ rel.Adapter = (*Adapter)(nil) + + // Config for mysql adapter. + Config = sql.Config{ + Placeholder: "?", + EscapeChar: "`", + InsertDefaultValues: true, + IncrementFunc: incrementFunc, + ErrorFunc: errorFunc, + MapColumnFunc: mapColumnFunc, + } +) // New is mysql adapter constructor. func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ - Config: sql.Config{ - Placeholder: "?", - EscapeChar: "`", - InsertDefaultValues: true, - IncrementFunc: incrementFunc, - ErrorFunc: errorFunc, - MapColumnFunc: mapColumnFunc, - }, - DB: database, + Config: Config, + DB: database, }, } } From 2d5a9d56b80466e8fd5622fd842f2cca8b25a5b7 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Sun, 30 Aug 2020 23:39:45 +0700 Subject: [PATCH 27/44] fix comment --- adapter/mysql/mysql.go | 2 +- adapter/postgres/postgres.go | 2 +- adapter/sqlite3/sqlite3.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adapter/mysql/mysql.go b/adapter/mysql/mysql.go index 1d2e2689..7471ca45 100644 --- a/adapter/mysql/mysql.go +++ b/adapter/mysql/mysql.go @@ -39,7 +39,7 @@ var ( } ) -// New is mysql adapter constructor. +// New mysql adapter using existing connection. func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ diff --git a/adapter/postgres/postgres.go b/adapter/postgres/postgres.go index 8b644a7e..5e954af4 100644 --- a/adapter/postgres/postgres.go +++ b/adapter/postgres/postgres.go @@ -40,7 +40,7 @@ var ( } ) -// New is postgres adapter constructor. +// New postgres adapter using existing connection. func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ diff --git a/adapter/sqlite3/sqlite3.go b/adapter/sqlite3/sqlite3.go index 789c190c..31091588 100644 --- a/adapter/sqlite3/sqlite3.go +++ b/adapter/sqlite3/sqlite3.go @@ -39,7 +39,7 @@ var ( } ) -// New is mysql adapter constructor. +// New sqlite adapter using existing connection. func New(database *db.DB) *Adapter { return &Adapter{ Adapter: &sql.Adapter{ @@ -49,7 +49,7 @@ func New(database *db.DB) *Adapter { } } -// Open mysql connection using dsn. +// Open sqlite connection using dsn. func Open(dsn string) (*Adapter, error) { var database, err = db.Open("sqlite3", dsn) return New(database), err From a749bb59ddb006b3106d5a58d53c5a5364a08bde Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Mon, 31 Aug 2020 22:35:38 +0700 Subject: [PATCH 28/44] drop support for alter column on poc, bump sqlite3, more integration for migration --- adapter/mysql/mysql_test.go | 7 ++-- adapter/postgres/postgres_test.go | 6 ++-- adapter/specs/migration.go | 57 ++++++++++++++++++++++++------- adapter/specs/specs.go | 14 ++++++++ adapter/sql/builder.go | 21 ++++-------- adapter/sqlite3/sqlite3_test.go | 6 ++-- column.go | 11 ------ column_test.go | 28 --------------- go.mod | 2 +- go.sum | 11 ++++++ schema.go | 7 ---- schema_test.go | 14 -------- table.go | 5 --- table_test.go | 9 ----- 14 files changed, 88 insertions(+), 110 deletions(-) diff --git a/adapter/mysql/mysql_test.go b/adapter/mysql/mysql_test.go index 1a18d1cf..282708ad 100644 --- a/adapter/mysql/mysql_test.go +++ b/adapter/mysql/mysql_test.go @@ -31,11 +31,12 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) // Prepare tables - specs.Migrate(t, repo, false) - defer specs.Migrate(t, repo, true) + teardown := specs.Setup(t, repo) + defer teardown() // Migration Specs - specs.MigrateTable(t, repo) + // - Rename column is only supported by MySQL 8.0 + specs.Migrate(t, repo, specs.SkipRenameColumn) // Query Specs specs.Query(t, repo) diff --git a/adapter/postgres/postgres_test.go b/adapter/postgres/postgres_test.go index a4a36648..19abc18c 100644 --- a/adapter/postgres/postgres_test.go +++ b/adapter/postgres/postgres_test.go @@ -36,11 +36,11 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) // Prepare tables - specs.Migrate(t, repo, false) - defer specs.Migrate(t, repo, true) + teardown := specs.Setup(t, repo) + defer teardown() // Migration Specs - specs.MigrateTable(t, repo) + specs.Migrate(t, repo) // Query Specs specs.Query(t, repo) diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index 4948ee4b..01613c31 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -10,15 +10,8 @@ import ( var m migrator.Migrator -// Migrate database for specs execution. -func Migrate(t *testing.T, repo rel.Repository, rollback bool) { - if rollback { - for i := 0; i < 4; i++ { - m.Rollback(ctx) - } - return - } - +// Setup database for specs execution. +func Setup(t *testing.T, repo rel.Repository) func() { m = migrator.New(repo) m.Register(1, func(schema *rel.Schema) { @@ -91,10 +84,16 @@ func Migrate(t *testing.T, repo rel.Repository, rollback bool) { ) m.Migrate(ctx) + + return func() { + for i := 0; i < 4; i++ { + m.Rollback(ctx) + } + } } -// MigrateTable specs. -func MigrateTable(t *testing.T, repo rel.Repository) { +// Migrate specs. +func Migrate(t *testing.T, repo rel.Repository, flags ...Flag) { m.Register(5, func(schema *rel.Schema) { schema.CreateTable("dummies", func(t *rel.Table) { @@ -134,7 +133,41 @@ func MigrateTable(t *testing.T, repo rel.Repository) { schema.DropTable("dummies") }, ) + defer m.Rollback(ctx) + + m.Register(6, + func(schema *rel.Schema) { + schema.AlterTable("dummies", func(t *rel.AlterTable) { + t.Bool("new_column") + }) + }, + func(schema *rel.Schema) { + if SkipDropColumn.enabled(flags) { + schema.AlterTable("dummies", func(t *rel.AlterTable) { + t.DropColumn("new_column") + }) + } + }, + ) + defer m.Rollback(ctx) + + if SkipRenameColumn.enabled(flags) { + m.Register(7, + func(schema *rel.Schema) { + schema.AlterTable("dummies", func(t *rel.AlterTable) { + t.RenameColumn("text", "teks") + t.RenameColumn("date2", "date3") + }) + }, + func(schema *rel.Schema) { + schema.AlterTable("dummies", func(t *rel.AlterTable) { + t.RenameColumn("teks", "text") + t.RenameColumn("date3", "date2") + }) + }, + ) + defer m.Rollback(ctx) + } m.Migrate(ctx) - m.Rollback(ctx) } diff --git a/adapter/specs/specs.go b/adapter/specs/specs.go index cce699cc..5a3d14e9 100644 --- a/adapter/specs/specs.go +++ b/adapter/specs/specs.go @@ -14,6 +14,20 @@ import ( var ctx = context.TODO() +// Flag for configuration. +type Flag int + +func (f Flag) enabled(flags []Flag) bool { + return len(flags) > 0 && f&flags[0] == 0 +} + +const ( + // SkipDropColumn spec. + SkipDropColumn Flag = 1 << iota + // SkipRenameColumn spec. + SkipRenameColumn +) + // User defines users schema. type User struct { ID int64 diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 2c192d26..cc9f3d86 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -80,14 +80,10 @@ func (b *Builder) createTable(buffer *Buffer, table rel.Table) { } func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { - buffer.WriteString("ALTER TABLE ") - buffer.WriteString(Escape(b.config, table.Name)) - buffer.WriteByte(' ') - - for i, def := range table.Definitions { - if i > 0 { - buffer.WriteString(", ") - } + for _, def := range table.Definitions { + buffer.WriteString("ALTER TABLE ") + buffer.WriteString(Escape(b.config, table.Name)) + buffer.WriteByte(' ') switch v := def.(type) { case rel.Column: @@ -95,9 +91,6 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { case rel.SchemaCreate: buffer.WriteString("ADD COLUMN ") b.column(buffer, v) - case rel.SchemaAlter: // TODO: use modify keyword? - buffer.WriteString("MODIFY COLUMN ") - b.column(buffer, v) case rel.SchemaRename: // Add Change buffer.WriteString("RENAME COLUMN ") @@ -123,10 +116,10 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { buffer.WriteString(Escape(b.config, v.Name)) } } - } - b.options(buffer, table.Options) - buffer.WriteByte(';') + b.options(buffer, table.Options) + buffer.WriteByte(';') + } } func (b *Builder) column(buffer *Buffer, column rel.Column) { diff --git a/adapter/sqlite3/sqlite3_test.go b/adapter/sqlite3/sqlite3_test.go index cc146d1f..2c66c829 100644 --- a/adapter/sqlite3/sqlite3_test.go +++ b/adapter/sqlite3/sqlite3_test.go @@ -30,11 +30,11 @@ func TestAdapter_specs(t *testing.T) { repo := rel.New(adapter) // Prepare tables - specs.Migrate(t, repo, false) - defer specs.Migrate(t, repo, true) + teardown := specs.Setup(t, repo) + defer teardown() // Migration Specs - specs.MigrateTable(t, repo) + specs.Migrate(t, repo, specs.SkipDropColumn) // Query Specs specs.Query(t, repo) diff --git a/column.go b/column.go index 3d99e664..68db1b72 100644 --- a/column.go +++ b/column.go @@ -59,17 +59,6 @@ func createColumn(name string, typ ColumnType, options []ColumnOption) Column { return column } -func alterColumn(name string, typ ColumnType, options []ColumnOption) Column { - column := Column{ - Op: SchemaAlter, - Name: name, - Type: typ, - } - - applyColumnOptions(&column, options) - return column -} - func renameColumn(name string, newName string, options []ColumnOption) Column { column := Column{ Op: SchemaRename, diff --git a/column_test.go b/column_test.go index 04a72ef3..d84af397 100644 --- a/column_test.go +++ b/column_test.go @@ -35,34 +35,6 @@ func TestCreateColumn(t *testing.T) { }, column) } -func TestAlterColumn(t *testing.T) { - var ( - options = []ColumnOption{ - Required(true), - Unsigned(true), - Limit(1000), - Precision(5), - Scale(2), - Default(0), - Options("options"), - } - column = alterColumn("alter", Decimal, options) - ) - - assert.Equal(t, Column{ - Op: SchemaAlter, - Name: "alter", - Type: Decimal, - Required: true, - Unsigned: true, - Limit: 1000, - Precision: 5, - Scale: 2, - Default: 0, - Options: "options", - }, column) -} - func TestRenameColumn(t *testing.T) { var ( options = []ColumnOption{ diff --git a/go.mod b/go.mod index 4836ea45..26f403ec 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a github.com/kr/pretty v0.1.0 // indirect github.com/lib/pq v1.3.0 - github.com/mattn/go-sqlite3 v1.6.0 + github.com/mattn/go-sqlite3 v1.14.2 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.4.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect diff --git a/go.sum b/go.sum index e2a1fee9..29042db8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a h1:wYjvXrzEmkEe3kNQXUd2Nzt/EO28kqebKsUWjXH9Opk= github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a/go.mod h1:mUYWV9DG75bJ33LZlW1Je3MW64017zkfUFCf+QnCJs0= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c h1:7zL0ljVI6ads5EFvx+Oq+uompnFBMJqtbuHvyobbJ1Q= github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c/go.mod h1:iApMeoHF0YlMPzCwqH/d59E3w2s8SeO4rGK+iGClS8Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -19,6 +21,8 @@ github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.6.0 h1:TDwTWbeII+88Qy55nWlof0DclgAtI4LqGujkYMzmQII= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.2 h1:A2EQLwjYf/hfYaM20FVjs1UewCTTFR7RmjEHkLjldIA= +github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= @@ -29,6 +33,13 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/schema.go b/schema.go index 87368b29..16c77a2d 100644 --- a/schema.go +++ b/schema.go @@ -59,13 +59,6 @@ func (s *Schema) AddColumn(table string, name string, typ ColumnType, options .. s.add(at.Table) } -// AlterColumn by name. -func (s *Schema) AlterColumn(table string, name string, typ ColumnType, options ...ColumnOption) { - at := alterTable(table, nil) - at.AlterColumn(name, typ, options...) - s.add(at.Table) -} - // RenameColumn by name. func (s *Schema) RenameColumn(table string, name string, newName string, options ...ColumnOption) { at := alterTable(table, nil) diff --git a/schema_test.go b/schema_test.go index 9f53d4b3..b1797a35 100644 --- a/schema_test.go +++ b/schema_test.go @@ -81,20 +81,6 @@ func TestSchema_AddColumn(t *testing.T) { }, schema.Migrations[0]) } -func TestSchema_AlterColumn(t *testing.T) { - var schema Schema - - schema.AlterColumn("products", "sale", Bool) - - assert.Equal(t, Table{ - Op: SchemaAlter, - Name: "products", - Definitions: []TableDefinition{ - Column{Name: "sale", Type: Bool, Op: SchemaAlter}, - }, - }, schema.Migrations[0]) -} - func TestSchema_RenameColumn(t *testing.T) { var schema Schema diff --git a/table.go b/table.go index cf5ca719..a8e3e86e 100644 --- a/table.go +++ b/table.go @@ -123,11 +123,6 @@ func (at *AlterTable) RenameColumn(name string, newName string, options ...Colum at.Definitions = append(at.Definitions, renameColumn(name, newName, options)) } -// AlterColumn from this table. -func (at *AlterTable) AlterColumn(name string, typ ColumnType, options ...ColumnOption) { - at.Definitions = append(at.Definitions, alterColumn(name, typ, options)) -} - // DropColumn from this table. func (at *AlterTable) DropColumn(name string, options ...ColumnOption) { at.Definitions = append(at.Definitions, dropColumn(name, options)) diff --git a/table_test.go b/table_test.go index 4d06fdbd..f35fed19 100644 --- a/table_test.go +++ b/table_test.go @@ -138,15 +138,6 @@ func TestAlterTable(t *testing.T) { }, table.Definitions[len(table.Definitions)-1]) }) - t.Run("AlterColumn", func(t *testing.T) { - table.AlterColumn("column", Bool) - assert.Equal(t, Column{ - Op: SchemaAlter, - Name: "column", - Type: Bool, - }, table.Definitions[len(table.Definitions)-1]) - }) - t.Run("DropColumn", func(t *testing.T) { table.DropColumn("column") assert.Equal(t, Column{ From 9e47e615e6d8fd8ab09e4ea66f6d47ff687b531f Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Mon, 31 Aug 2020 22:42:25 +0700 Subject: [PATCH 29/44] go mod itdy, fix test --- adapter/sql/builder_test.go | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index d8482a61..cb8823cb 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -83,7 +83,7 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "ALTER TABLE `columns` ADD COLUMN `verified` BOOL, RENAME COLUMN `string` TO `name`, MODIFY COLUMN `bool` INT, DROP COLUMN `blob`;", + result: "ALTER TABLE `columns` ADD COLUMN `verified` BOOL;ALTER TABLE `columns` RENAME COLUMN `string` TO `name`;ALTER TABLE `columns` ;ALTER TABLE `columns` DROP COLUMN `blob`;", table: rel.Table{ Op: rel.SchemaAlter, Name: "columns", diff --git a/go.sum b/go.sum index 29042db8..235f62c4 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.6.0 h1:TDwTWbeII+88Qy55nWlof0DclgAtI4LqGujkYMzmQII= -github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.2 h1:A2EQLwjYf/hfYaM20FVjs1UewCTTFR7RmjEHkLjldIA= github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= From 3292054cd31a9e3a46376cc2c58ecc0c02f0c961 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Mon, 31 Aug 2020 22:44:30 +0700 Subject: [PATCH 30/44] bump sqlite3 on godep --- Gopkg.lock | 6 +++--- Gopkg.toml | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index df3a092d..604c4cd3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -54,12 +54,12 @@ version = "v1.3.0" [[projects]] - digest = "1:ee0845ea64262e3d1a6e2eab768fcb2008a0c8e571b7a3bebea554a1c031aeeb" + digest = "1:6a60be4b683dfa1d3e222b6ef96da4d3406280019731c6c1c23bd60cfb7928fe" name = "github.com/mattn/go-sqlite3" packages = ["."] pruneopts = "UT" - revision = "6c771bb9887719704b210e87e934f08be014bdb1" - version = "v1.6.0" + revision = "862b95943f99f3b40e317a79d41c27ac4b742011" + version = "v1.14.2" [[projects]] digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" diff --git a/Gopkg.toml b/Gopkg.toml index d7072c22..e129f8c3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -28,3 +28,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/mattn/go-sqlite3" + version = "1.14.2" From 1dd9c8d6fca1ecead616c511070bc9867eca4667 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Mon, 31 Aug 2020 23:05:32 +0700 Subject: [PATCH 31/44] fix rename table --- adapter/specs/migration.go | 27 ++++++++++++++++++++++----- adapter/sql/builder.go | 4 ++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index 01613c31..7c67b514 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -123,13 +123,8 @@ func Migrate(t *testing.T, repo rel.Repository, flags ...Flag) { t.Unique([]string{"int2"}) t.Unique([]string{"bigint1", "bigint2"}) }) - - schema.CreateIndex("dummies", "int1_idx", []string{"int1"}) - schema.CreateIndex("dummies", "string1_string2_idx", []string{"string1", "string2"}) }, func(schema *rel.Schema) { - schema.DropIndex("dummies", "int1_idx") - schema.DropIndex("dummies", "string1_string2_idx") schema.DropTable("dummies") }, ) @@ -169,5 +164,27 @@ func Migrate(t *testing.T, repo rel.Repository, flags ...Flag) { defer m.Rollback(ctx) } + m.Register(8, + func(schema *rel.Schema) { + schema.CreateIndex("dummies", "int1_idx", []string{"int1"}) + schema.CreateIndex("dummies", "string1_string2_idx", []string{"string1", "string2"}) + }, + func(schema *rel.Schema) { + schema.DropIndex("dummies", "int1_idx") + schema.DropIndex("dummies", "string1_string2_idx") + }, + ) + defer m.Rollback(ctx) + + m.Register(9, + func(schema *rel.Schema) { + schema.RenameTable("dummies", "new_dummies") + }, + func(schema *rel.Schema) { + schema.RenameTable("new_dummies", "dummies") + }, + ) + defer m.Rollback(ctx) + m.Migrate(ctx) } diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index cc9f3d86..1a8b8642 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -31,9 +31,9 @@ func (b *Builder) Table(table rel.Table) string { case rel.SchemaAlter: b.alterTable(&buffer, table) case rel.SchemaRename: - buffer.WriteString("RENAME TABLE ") + buffer.WriteString("ALTER TABLE ") buffer.WriteString(Escape(b.config, table.Name)) - buffer.WriteString(" TO ") + buffer.WriteString(" RENAME TO ") buffer.WriteString(Escape(b.config, table.NewName)) buffer.WriteByte(';') case rel.SchemaDrop: From 8e1918eb829968f2d53bb14ed80b2848137f536e Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Mon, 31 Aug 2020 23:14:13 +0700 Subject: [PATCH 32/44] add more test --- adapter/specs/migration.go | 4 ++++ adapter/sql/builder_test.go | 10 +++++----- adapter/sql/sql.go | 38 ------------------------------------- 3 files changed, 9 insertions(+), 43 deletions(-) delete mode 100644 adapter/sql/sql.go diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index 7c67b514..64df19e8 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -135,12 +135,14 @@ func Migrate(t *testing.T, repo rel.Repository, flags ...Flag) { schema.AlterTable("dummies", func(t *rel.AlterTable) { t.Bool("new_column") }) + schema.AddColumn("dummies", "new_column1", rel.Int, rel.Unsigned(true)) }, func(schema *rel.Schema) { if SkipDropColumn.enabled(flags) { schema.AlterTable("dummies", func(t *rel.AlterTable) { t.DropColumn("new_column") }) + schema.DropColumn("dummies", "new_column1") } }, ) @@ -153,12 +155,14 @@ func Migrate(t *testing.T, repo rel.Repository, flags ...Flag) { t.RenameColumn("text", "teks") t.RenameColumn("date2", "date3") }) + schema.RenameColumn("dummies", "decimal1", "decimal0") }, func(schema *rel.Schema) { schema.AlterTable("dummies", func(t *rel.AlterTable) { t.RenameColumn("teks", "text") t.RenameColumn("date3", "date2") }) + schema.RenameColumn("dummies", "decimal0", "decimal1") }, ) defer m.Rollback(ctx) diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index cb8823cb..efaabc9f 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -106,18 +106,18 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "RENAME TABLE `columns` TO `definitions`;", + result: "ALTER TABLE `table` RENAME TO `table1`;", table: rel.Table{ Op: rel.SchemaRename, - Name: "columns", - NewName: "definitions", + Name: "table", + NewName: "table1", }, }, { - result: "DROP TABLE `columns`;", + result: "DROP TABLE `table`;", table: rel.Table{ Op: rel.SchemaDrop, - Name: "columns", + Name: "table", }, }, } diff --git a/adapter/sql/sql.go b/adapter/sql/sql.go deleted file mode 100644 index e482a324..00000000 --- a/adapter/sql/sql.go +++ /dev/null @@ -1,38 +0,0 @@ -package sql - -// // IndexToSQL converts index struct as a sql string. -// // Return true if it's an inline sql. -// func IndexToSQL(config Config, index rel.Index) (string, bool) { -// var ( -// buffer Buffer -// typ = string(index.Type) -// ) - -// buffer.WriteString(typ) - -// if index.Name != "" { -// buffer.WriteByte(' ') -// buffer.WriteString(Escape(config, index.Name)) -// } - -// buffer.WriteString(" (") -// for i, col := range index.Columns { -// if i > 0 { -// buffer.WriteString(", ") -// } -// buffer.WriteString(Escape(config, col)) -// } -// buffer.WriteString(")") - -// optionsToSQL(&buffer, index.Options) -// return buffer.String(), true -// } - -// func optionsToSQL(buffer *Buffer, options string) { -// if options == "" { -// return -// } - -// buffer.WriteByte(' ') -// buffer.WriteString(options) -// } From 13a59aea12435463a4ecd4da8bd36cdd7aef967b Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Mon, 31 Aug 2020 23:42:54 +0700 Subject: [PATCH 33/44] add helper for optional create and drop table --- adapter/specs/migration.go | 27 +++++++++++++++++++++++++++ adapter/sql/builder.go | 5 +++++ index.go | 11 ++--------- index_test.go | 10 ++++++---- key_test.go | 2 +- schema.go | 12 ++++++++++++ schema_test.go | 21 +++++++++++++++++++++ table.go | 17 ++++++++++++----- table_test.go | 19 +++++++++++++++++-- 9 files changed, 103 insertions(+), 21 deletions(-) diff --git a/adapter/specs/migration.go b/adapter/specs/migration.go index 64df19e8..b5cfbf19 100644 --- a/adapter/specs/migration.go +++ b/adapter/specs/migration.go @@ -102,6 +102,7 @@ func Migrate(t *testing.T, repo rel.Repository, flags ...Flag) { t.Bool("bool2", rel.Default(true)) t.Int("int1") t.Int("int2", rel.Default(8), rel.Unsigned(true), rel.Limit(10)) + t.Int("int3", rel.Unique(true)) t.BigInt("bigint1") t.BigInt("bigint2", rel.Default(8), rel.Unsigned(true), rel.Limit(200)) t.Float("float1") @@ -190,5 +191,31 @@ func Migrate(t *testing.T, repo rel.Repository, flags ...Flag) { ) defer m.Rollback(ctx) + m.Register(10, + func(schema *rel.Schema) { + schema.CreateTableIfNotExists("dummies2", func(t *rel.Table) { + t.ID("id") + }) + }, + func(schema *rel.Schema) { + schema.DropTableIfExists("dummies2") + }, + ) + defer m.Rollback(ctx) + + m.Register(11, + func(schema *rel.Schema) { + schema.CreateTableIfNotExists("dummies2", func(t *rel.Table) { + t.ID("id") + t.Int("field1") + t.Int("field2") + }) + }, + func(schema *rel.Schema) { + schema.DropTableIfExists("dummies2") + }, + ) + defer m.Rollback(ctx) + m.Migrate(ctx) } diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 1a8b8642..fd54ff15 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -231,6 +231,11 @@ func (b *Builder) Index(index rel.Index) string { buffer.WriteString("UNIQUE ") } buffer.WriteString("INDEX ") + + if index.Optional { + buffer.WriteString("IF NOT EXISTS ") + } + buffer.WriteString(Escape(b.config, index.Name)) buffer.WriteString(" ON ") buffer.WriteString(Escape(b.config, index.Table)) diff --git a/index.go b/index.go index 8f68f2c2..b6ee56ac 100644 --- a/index.go +++ b/index.go @@ -26,15 +26,8 @@ func createIndex(table string, name string, columns []string, options []IndexOpt } func createUniqueIndex(table string, name string, columns []string, options []IndexOption) Index { - index := Index{ - Op: SchemaCreate, - Table: table, - Name: name, - Unique: true, - Columns: columns, - } - - applyIndexOptions(&index, options) + index := createIndex(table, name, columns, options) + index.Unique = true return index } diff --git a/index_test.go b/index_test.go index 889cf88c..f2a9601d 100644 --- a/index_test.go +++ b/index_test.go @@ -10,15 +10,17 @@ func TestCreateIndex(t *testing.T) { var ( options = []IndexOption{ Options("options"), + Optional(true), } index = createIndex("table", "add_idx", []string{"add"}, options) ) assert.Equal(t, Index{ - Table: "table", - Name: "add_idx", - Columns: []string{"add"}, - Options: "options", + Table: "table", + Name: "add_idx", + Columns: []string{"add"}, + Optional: true, + Options: "options", }, index) } diff --git a/key_test.go b/key_test.go index 3346c897..9e79de76 100644 --- a/key_test.go +++ b/key_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -func CreateForeignKey(t *testing.T) { +func TestCreateForeignKey(t *testing.T) { var ( options = []KeyOption{ OnDelete("cascade"), diff --git a/schema.go b/schema.go index 16c77a2d..7ca727c8 100644 --- a/schema.go +++ b/schema.go @@ -35,6 +35,13 @@ func (s *Schema) CreateTable(name string, fn func(t *Table), options ...TableOpt s.add(table) } +// CreateTableIfNotExists with name and its definition. +func (s *Schema) CreateTableIfNotExists(name string, fn func(t *Table), options ...TableOption) { + table := createTableIfNotExists(name, options) + fn(&table) + s.add(table) +} + // AlterTable with name and its definition. func (s *Schema) AlterTable(name string, fn func(t *AlterTable), options ...TableOption) { table := alterTable(name, options) @@ -52,6 +59,11 @@ func (s *Schema) DropTable(name string, options ...TableOption) { s.add(dropTable(name, options)) } +// DropTableIfExists by name. +func (s *Schema) DropTableIfExists(name string, options ...TableOption) { + s.add(dropTableIfExists(name, options)) +} + // AddColumn with name and type. func (s *Schema) AddColumn(table string, name string, typ ColumnType, options ...ColumnOption) { at := alterTable(table, nil) diff --git a/schema_test.go b/schema_test.go index b1797a35..39fb991e 100644 --- a/schema_test.go +++ b/schema_test.go @@ -24,6 +24,19 @@ func TestSchema_CreateTable(t *testing.T) { Column{Name: "description", Type: Text}, }, }, schema.Migrations[0]) + + schema.CreateTableIfNotExists("products", func(t *Table) { + t.ID("id") + }) + + assert.Equal(t, Table{ + Op: SchemaCreate, + Name: "products", + Optional: true, + Definitions: []TableDefinition{ + Column{Name: "id", Type: ID}, + }, + }, schema.Migrations[1]) } func TestSchema_AlterTable(t *testing.T) { @@ -65,6 +78,14 @@ func TestSchema_DropTable(t *testing.T) { Op: SchemaDrop, Name: "logs", }, schema.Migrations[0]) + + schema.DropTableIfExists("logs") + + assert.Equal(t, Table{ + Op: SchemaDrop, + Name: "logs", + Optional: true, + }, schema.Migrations[1]) } func TestSchema_AddColumn(t *testing.T) { diff --git a/table.go b/table.go index a8e3e86e..19a6410b 100644 --- a/table.go +++ b/table.go @@ -81,11 +81,6 @@ func (t *Table) Timestamp(name string, options ...ColumnOption) { t.Column(name, Timestamp, options...) } -// // Index defines an index for columns. -// func (t *Table) Index(columns []string, typ IndexType, options ...IndexOption) { -// t.Definitions = append(t.Definitions, createIndex(columns, typ, options)) -// } - // PrimaryKey defines a primary key for table. func (t *Table) PrimaryKey(column string, options ...KeyOption) { t.PrimaryKeys([]string{column}, options...) @@ -138,6 +133,12 @@ func createTable(name string, options []TableOption) Table { return table } +func createTableIfNotExists(name string, options []TableOption) Table { + table := createTable(name, options) + table.Optional = true + return table +} + func alterTable(name string, options []TableOption) AlterTable { table := Table{ Op: SchemaAlter, @@ -169,6 +170,12 @@ func dropTable(name string, options []TableOption) Table { return table } +func dropTableIfExists(name string, options []TableOption) Table { + table := dropTable(name, options) + table.Optional = true + return table +} + // TableOption interface. // Available options are: Comment, Options. type TableOption interface { diff --git a/table_test.go b/table_test.go index f35fed19..c623ca2d 100644 --- a/table_test.go +++ b/table_test.go @@ -124,6 +124,19 @@ func TestTable(t *testing.T) { }, }, table.Definitions[len(table.Definitions)-1]) }) + + t.Run("Unique", func(t *testing.T) { + table.Unique([]string{"username"}) + assert.Equal(t, Key{ + Columns: []string{"username"}, + Type: UniqueKey, + }, table.Definitions[len(table.Definitions)-1]) + }) + + t.Run("Fragment", func(t *testing.T) { + table.Fragment("SQL") + assert.Equal(t, Raw("SQL"), table.Definitions[len(table.Definitions)-1]) + }) } func TestAlterTable(t *testing.T) { @@ -151,12 +164,14 @@ func TestCreateTable(t *testing.T) { var ( options = []TableOption{ Options("options"), + Optional(true), } table = createTable("table", options) ) assert.Equal(t, Table{ - Name: "table", - Options: "options", + Name: "table", + Optional: true, + Options: "options", }, table) } From 2547ec2cea57c7580e58b6b9e592d562843e340b Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Mon, 31 Aug 2020 23:51:51 +0700 Subject: [PATCH 34/44] postpone rename and rop key --- adapter/sql/builder.go | 9 +-------- index.go | 4 ---- key.go | 21 +-------------------- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index fd54ff15..842099c8 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -102,18 +102,11 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { buffer.WriteString(Escape(b.config, v.Name)) } case rel.Key: + // TODO: Rename and Drop, PR welcomed. switch v.Op { case rel.SchemaCreate: buffer.WriteString("ADD ") b.key(buffer, v) - case rel.SchemaRename: - buffer.WriteString("RENAME INDEX ") - buffer.WriteString(Escape(b.config, v.Name)) - buffer.WriteString(" TO ") - buffer.WriteString(Escape(b.config, v.NewName)) - case rel.SchemaDrop: - buffer.WriteString("DROP INDEX ") - buffer.WriteString(Escape(b.config, v.Name)) } } diff --git a/index.go b/index.go index b6ee56ac..6193cb3e 100644 --- a/index.go +++ b/index.go @@ -57,10 +57,6 @@ func applyIndexOptions(index *Index, options []IndexOption) { // Name option for defining custom index name. type Name string -func (n Name) applyIndex(index *Index) { - index.Name = string(n) -} - func (n Name) applyKey(key *Key) { key.Name = string(n) } diff --git a/key.go b/key.go index 7fee1c92..1d3c983e 100644 --- a/key.go +++ b/key.go @@ -63,26 +63,7 @@ func createForeignKey(column string, refTable string, refColumn string, options return key } -func renameKey(name string, newName string, options []KeyOption) Key { - key := Key{ - Op: SchemaRename, - Name: name, - NewName: newName, - } - - applyKeyOptions(&key, options) - return key -} - -func dropKey(name string, options []KeyOption) Key { - key := Key{ - Op: SchemaDrop, - Name: name, - } - - applyKeyOptions(&key, options) - return key -} +// TODO: Rename and Drop, PR welcomed. // KeyOption interface. // Available options are: Comment, Options. From 7c6d524f3d7d4b2c8917b1aabdd503fd63ecd84c Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Tue, 1 Sep 2020 11:29:18 +0700 Subject: [PATCH 35/44] add more tests --- adapter/sql/builder_test.go | 109 +++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 3 deletions(-) diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index efaabc9f..70a91593 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -58,7 +58,7 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144), `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE (`date`)) Engine=InnoDB;", + result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144) UNIQUE, `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE `date_unique` (`date`)) Engine=InnoDB;", table: rel.Table{ Op: rel.SchemaCreate, Name: "columns", @@ -68,7 +68,7 @@ func TestBuilder_Table(t *testing.T) { rel.Column{Name: "bigint", Type: rel.BigInt, Limit: 20, Unsigned: true}, rel.Column{Name: "float", Type: rel.Float, Precision: 24, Unsigned: true}, rel.Column{Name: "decimal", Type: rel.Decimal, Precision: 6, Scale: 2, Unsigned: true}, - rel.Column{Name: "string", Type: rel.String, Limit: 144}, + rel.Column{Name: "string", Type: rel.String, Limit: 144, Unique: true}, rel.Column{Name: "text", Type: rel.Text, Limit: 1000}, rel.Column{Name: "date", Type: rel.Date}, rel.Column{Name: "datetime", Type: rel.DateTime}, @@ -77,11 +77,23 @@ func TestBuilder_Table(t *testing.T) { rel.Column{Name: "blob", Type: "blob"}, rel.Key{Columns: []string{"int"}, Type: rel.PrimaryKey}, rel.Key{Columns: []string{"int", "string"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, - rel.Key{Columns: []string{"date"}, Type: rel.UniqueKey}, + rel.Key{Columns: []string{"date"}, Name: "date_unique", Type: rel.UniqueKey}, }, Options: "Engine=InnoDB", }, }, + { + result: "CREATE TABLE IF NOT EXISTS `products` (`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `raw` BOOL);", + table: rel.Table{ + Op: rel.SchemaCreate, + Name: "products", + Optional: true, + Definitions: []rel.TableDefinition{ + rel.Column{Name: "id", Type: rel.ID}, + rel.Raw("`raw` BOOL"), + }, + }, + }, { result: "ALTER TABLE `columns` ADD COLUMN `verified` BOOL;ALTER TABLE `columns` RENAME COLUMN `string` TO `name`;ALTER TABLE `columns` ;ALTER TABLE `columns` DROP COLUMN `blob`;", table: rel.Table{ @@ -120,6 +132,14 @@ func TestBuilder_Table(t *testing.T) { Name: "table", }, }, + { + result: "DROP TABLE IF EXISTS `table`;", + table: rel.Table{ + Op: rel.SchemaDrop, + Name: "table", + Optional: true, + }, + }, } for _, test := range tests { @@ -134,6 +154,89 @@ func TestBuilder_Table(t *testing.T) { } } +func TestBuilder_Index(t *testing.T) { + var ( + config = Config{ + Placeholder: "?", + EscapeChar: "`", + MapColumnFunc: MapColumn, + DropIndexOnTable: true, + } + ) + + tests := []struct { + result string + index rel.Index + }{ + { + result: "CREATE INDEX `index` ON `table` (`column1`);", + index: rel.Index{ + Op: rel.SchemaCreate, + Table: "table", + Name: "index", + Columns: []string{"column1"}, + }, + }, + { + result: "CREATE UNIQUE INDEX `index` ON `table` (`column1`);", + index: rel.Index{ + Op: rel.SchemaCreate, + Table: "table", + Name: "index", + Unique: true, + Columns: []string{"column1"}, + }, + }, + { + result: "CREATE INDEX `index` ON `table` (`column1`, `column2`);", + index: rel.Index{ + Op: rel.SchemaCreate, + Table: "table", + Name: "index", + Columns: []string{"column1", "column2"}, + }, + }, + { + result: "CREATE INDEX IF NOT EXISTS `index` ON `table` (`column1`);", + index: rel.Index{ + Op: rel.SchemaCreate, + Table: "table", + Name: "index", + Optional: true, + Columns: []string{"column1"}, + }, + }, + { + result: "DROP INDEX `index` ON `table`;", + index: rel.Index{ + Op: rel.SchemaDrop, + Name: "index", + Table: "table", + }, + }, + { + result: "DROP INDEX IF EXISTS `index` ON `table`;", + index: rel.Index{ + Op: rel.SchemaDrop, + Name: "index", + Table: "table", + Optional: true, + }, + }, + } + + for _, test := range tests { + t.Run(test.result, func(t *testing.T) { + var ( + builder = NewBuilder(config) + result = builder.Index(test.index) + ) + + assert.Equal(t, test.result, result) + }) + } +} + func TestBuilder_Find(t *testing.T) { var ( config = Config{ From 0883ee27eb7dfe14f28d4508e37c564f7b5ef89c Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Tue, 1 Sep 2020 18:56:43 +0700 Subject: [PATCH 36/44] fix coverage --- adapter/sql/adapter_test.go | 29 +++++++++++++++++++++++++++++ adapter/sql/builder.go | 1 + adapter/sql/builder_test.go | 5 +++-- migrator/migrator.go | 21 +++++++++++++-------- 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/adapter/sql/adapter_test.go b/adapter/sql/adapter_test.go index 58acf44e..a700f84e 100644 --- a/adapter/sql/adapter_test.go +++ b/adapter/sql/adapter_test.go @@ -330,3 +330,32 @@ func TestAdapter_Exec_error(t *testing.T) { _, _, err := adapter.Exec(context.TODO(), "error", nil) assert.NotNil(t, err) } + +func TestAdapter_Apply(t *testing.T) { + var ( + ctx = context.TODO() + adapter = open(t) + ) + + defer adapter.Close() + + t.Run("Table", func(t *testing.T) { + adapter.Apply(ctx, rel.Table{ + Name: "tests", + Optional: true, + Definitions: []rel.TableDefinition{ + rel.Column{Name: "ID", Type: rel.ID}, + rel.Column{Name: "username", Type: rel.String}, + }, + }) + }) + + t.Run("Table", func(t *testing.T) { + adapter.Apply(ctx, rel.Index{ + Name: "username_idx", + Optional: true, + Table: "tests", + Columns: []string{"username"}, + }) + }) +} diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 842099c8..09799c05 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -152,6 +152,7 @@ func (b *Builder) column(buffer *Buffer, column rel.Column) { buffer.WriteString(" DEFAULT ") switch v := column.Default.(type) { case string: + // TODO: single quote only required by postgres. buffer.WriteByte('\'') buffer.WriteString(v) buffer.WriteByte('\'') diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index 70a91593..ed49fda4 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -3,6 +3,7 @@ package sql import ( "fmt" "testing" + "time" "github.com/Fs02/rel" "github.com/Fs02/rel/sort" @@ -58,7 +59,7 @@ func TestBuilder_Table(t *testing.T) { }, }, { - result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144) UNIQUE, `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP, `blob` blob, PRIMARY KEY (`int`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE `date_unique` (`date`)) Engine=InnoDB;", + result: "CREATE TABLE `columns` (`bool` BOOL NOT NULL DEFAULT false, `int` INT(11) UNSIGNED, `bigint` BIGINT(20) UNSIGNED, `float` FLOAT(24) UNSIGNED, `decimal` DECIMAL(6,2) UNSIGNED, `string` VARCHAR(144) UNIQUE, `text` TEXT(1000), `date` DATE, `datetime` DATETIME, `time` TIME, `timestamp` TIMESTAMP DEFAULT '2020-01-01 01:00:00', `blob` blob, PRIMARY KEY (`int`), FOREIGN KEY (`int`, `string`) REFERENCES `products` (`id`, `name`) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE `date_unique` (`date`)) Engine=InnoDB;", table: rel.Table{ Op: rel.SchemaCreate, Name: "columns", @@ -73,7 +74,7 @@ func TestBuilder_Table(t *testing.T) { rel.Column{Name: "date", Type: rel.Date}, rel.Column{Name: "datetime", Type: rel.DateTime}, rel.Column{Name: "time", Type: rel.Time}, - rel.Column{Name: "timestamp", Type: rel.Timestamp}, + rel.Column{Name: "timestamp", Type: rel.Timestamp, Default: time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC)}, rel.Column{Name: "blob", Type: "blob"}, rel.Key{Columns: []string{"int"}, Type: rel.PrimaryKey}, rel.Key{Columns: []string{"int", "string"}, Type: rel.ForeignKey, Reference: rel.ForeignKeyReference{Table: "products", Columns: []string{"id", "name"}, OnDelete: "CASCADE", OnUpdate: "CASCADE"}}, diff --git a/migrator/migrator.go b/migrator/migrator.go index 13ea8861..573610e0 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -42,8 +42,9 @@ func (v versions) Swap(i, j int) { // Migrator is a migration manager that handles migration logic. type Migrator struct { - repo rel.Repository - versions versions + repo rel.Repository + versions versions + versionTableExists bool } // Register a migration. @@ -58,14 +59,12 @@ func (m *Migrator) Register(v int, up func(schema *rel.Schema), down func(schema func (m Migrator) buildVersionTableDefinition() rel.Table { var schema rel.Schema - schema.CreateTable(versionTable, func(t *rel.Table) { + schema.CreateTableIfNotExists(versionTable, func(t *rel.Table) { t.ID("id") - t.Int("version") + t.Int("version", rel.Unique(true)) t.DateTime("created_at") t.DateTime("updated_at") - - t.Unique([]string{"version"}) - }, rel.Optional(true)) + }) return schema.Migrations[0].(rel.Table) } @@ -77,10 +76,16 @@ func (m *Migrator) sync(ctx context.Context) { adapter = m.repo.Adapter(ctx).(rel.Adapter) ) - check(adapter.Apply(ctx, m.buildVersionTableDefinition())) + if !m.versionTableExists { + check(adapter.Apply(ctx, m.buildVersionTableDefinition())) + m.versionTableExists = true + } + m.repo.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) sort.Sort(m.versions) + fmt.Println(versions) + for i := range m.versions { if vi < len(versions) && m.versions[i].Version == versions[vi].Version { m.versions[i].ID = versions[vi].ID From 3b34832d3a9e3bde98daaea84d43a6dc49d01ef3 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Tue, 1 Sep 2020 19:08:22 +0700 Subject: [PATCH 37/44] refactor NewName to Rename --- adapter/sql/builder.go | 4 ++-- adapter/sql/builder_test.go | 8 ++++---- column.go | 8 ++++---- column_test.go | 2 +- key.go | 2 +- schema_test.go | 10 +++++----- table.go | 8 ++++---- table_test.go | 6 +++--- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/adapter/sql/builder.go b/adapter/sql/builder.go index 09799c05..1813f135 100644 --- a/adapter/sql/builder.go +++ b/adapter/sql/builder.go @@ -34,7 +34,7 @@ func (b *Builder) Table(table rel.Table) string { buffer.WriteString("ALTER TABLE ") buffer.WriteString(Escape(b.config, table.Name)) buffer.WriteString(" RENAME TO ") - buffer.WriteString(Escape(b.config, table.NewName)) + buffer.WriteString(Escape(b.config, table.Rename)) buffer.WriteByte(';') case rel.SchemaDrop: buffer.WriteString("DROP TABLE ") @@ -96,7 +96,7 @@ func (b *Builder) alterTable(buffer *Buffer, table rel.Table) { buffer.WriteString("RENAME COLUMN ") buffer.WriteString(Escape(b.config, v.Name)) buffer.WriteString(" TO ") - buffer.WriteString(Escape(b.config, v.NewName)) + buffer.WriteString(Escape(b.config, v.Rename)) case rel.SchemaDrop: buffer.WriteString("DROP COLUMN ") buffer.WriteString(Escape(b.config, v.Name)) diff --git a/adapter/sql/builder_test.go b/adapter/sql/builder_test.go index ed49fda4..5ced9556 100644 --- a/adapter/sql/builder_test.go +++ b/adapter/sql/builder_test.go @@ -102,7 +102,7 @@ func TestBuilder_Table(t *testing.T) { Name: "columns", Definitions: []rel.TableDefinition{ rel.Column{Name: "verified", Type: rel.Bool, Op: rel.SchemaCreate}, - rel.Column{Name: "string", NewName: "name", Op: rel.SchemaRename}, + rel.Column{Name: "string", Rename: "name", Op: rel.SchemaRename}, rel.Column{Name: "bool", Type: rel.Int, Op: rel.SchemaAlter}, rel.Column{Name: "blob", Op: rel.SchemaDrop}, }, @@ -121,9 +121,9 @@ func TestBuilder_Table(t *testing.T) { { result: "ALTER TABLE `table` RENAME TO `table1`;", table: rel.Table{ - Op: rel.SchemaRename, - Name: "table", - NewName: "table1", + Op: rel.SchemaRename, + Name: "table", + Rename: "table1", }, }, { diff --git a/column.go b/column.go index 68db1b72..89d01c5c 100644 --- a/column.go +++ b/column.go @@ -35,7 +35,7 @@ type Column struct { Op SchemaOp Name string Type ColumnType - NewName string + Rename string Unique bool Required bool Unsigned bool @@ -61,9 +61,9 @@ func createColumn(name string, typ ColumnType, options []ColumnOption) Column { func renameColumn(name string, newName string, options []ColumnOption) Column { column := Column{ - Op: SchemaRename, - Name: name, - NewName: newName, + Op: SchemaRename, + Name: name, + Rename: newName, } applyColumnOptions(&column, options) diff --git a/column_test.go b/column_test.go index d84af397..53c58bf7 100644 --- a/column_test.go +++ b/column_test.go @@ -52,7 +52,7 @@ func TestRenameColumn(t *testing.T) { assert.Equal(t, Column{ Op: SchemaRename, Name: "add", - NewName: "rename", + Rename: "rename", Required: true, Unsigned: true, Limit: 1000, diff --git a/key.go b/key.go index 1d3c983e..53dc8e24 100644 --- a/key.go +++ b/key.go @@ -26,7 +26,7 @@ type Key struct { Name string Type KeyType Columns []string - NewName string + Rename string Reference ForeignKeyReference Options string } diff --git a/schema_test.go b/schema_test.go index 39fb991e..b75034f3 100644 --- a/schema_test.go +++ b/schema_test.go @@ -52,7 +52,7 @@ func TestSchema_AlterTable(t *testing.T) { Name: "users", Definitions: []TableDefinition{ Column{Name: "verified", Type: Bool, Op: SchemaCreate}, - Column{Name: "name", NewName: "fullname", Op: SchemaRename}, + Column{Name: "name", Rename: "fullname", Op: SchemaRename}, }, }, schema.Migrations[0]) } @@ -63,9 +63,9 @@ func TestSchema_RenameTable(t *testing.T) { schema.RenameTable("trxs", "transactions") assert.Equal(t, Table{ - Op: SchemaRename, - Name: "trxs", - NewName: "transactions", + Op: SchemaRename, + Name: "trxs", + Rename: "transactions", }, schema.Migrations[0]) } @@ -111,7 +111,7 @@ func TestSchema_RenameColumn(t *testing.T) { Op: SchemaAlter, Name: "users", Definitions: []TableDefinition{ - Column{Name: "name", NewName: "fullname", Op: SchemaRename}, + Column{Name: "name", Rename: "fullname", Op: SchemaRename}, }, }, schema.Migrations[0]) } diff --git a/table.go b/table.go index 19a6410b..231789cc 100644 --- a/table.go +++ b/table.go @@ -9,7 +9,7 @@ type TableDefinition interface { type Table struct { Op SchemaOp Name string - NewName string + Rename string Definitions []TableDefinition Optional bool Options string @@ -151,9 +151,9 @@ func alterTable(name string, options []TableOption) AlterTable { func renameTable(name string, newName string, options []TableOption) Table { table := Table{ - Op: SchemaRename, - Name: name, - NewName: newName, + Op: SchemaRename, + Name: name, + Rename: newName, } applyTableOptions(&table, options) diff --git a/table_test.go b/table_test.go index c623ca2d..f051de50 100644 --- a/table_test.go +++ b/table_test.go @@ -145,9 +145,9 @@ func TestAlterTable(t *testing.T) { t.Run("RenameColumn", func(t *testing.T) { table.RenameColumn("column", "new_column") assert.Equal(t, Column{ - Op: SchemaRename, - Name: "column", - NewName: "new_column", + Op: SchemaRename, + Name: "column", + Rename: "new_column", }, table.Definitions[len(table.Definitions)-1]) }) From 1f41be8177cc631ca53bec887a6e79698e271bd4 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Tue, 1 Sep 2020 23:48:52 +0700 Subject: [PATCH 38/44] accommodate timestamp version --- migrator/migrator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrator/migrator.go b/migrator/migrator.go index 573610e0..426db639 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -61,7 +61,7 @@ func (m Migrator) buildVersionTableDefinition() rel.Table { var schema rel.Schema schema.CreateTableIfNotExists(versionTable, func(t *rel.Table) { t.ID("id") - t.Int("version", rel.Unique(true)) + t.BigInt("version", rel.Unique(true)) t.DateTime("created_at") t.DateTime("updated_at") }) From 03d75edd711d409d5b46e9cf20c34e32eb0ae35a Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Tue, 1 Sep 2020 23:54:06 +0700 Subject: [PATCH 39/44] unsigned version --- migrator/migrator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrator/migrator.go b/migrator/migrator.go index 426db639..6e14e35c 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -61,7 +61,7 @@ func (m Migrator) buildVersionTableDefinition() rel.Table { var schema rel.Schema schema.CreateTableIfNotExists(versionTable, func(t *rel.Table) { t.ID("id") - t.BigInt("version", rel.Unique(true)) + t.BigInt("version", rel.Unsigned(true), rel.Unique(true)) t.DateTime("created_at") t.DateTime("updated_at") }) From b2592e6bfa41e7304386db9910e6edb0ff2661f2 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Wed, 2 Sep 2020 22:11:14 +0700 Subject: [PATCH 40/44] add schema.Exec and schema.Do --- adapter/sql/adapter.go | 2 ++ adapter/sql/adapter_test.go | 6 +++++- migrator/migrator.go | 13 +++++++++---- migrator/migrator_test.go | 5 +++++ schema.go | 19 +++++++++++++++---- schema_test.go | 16 ++++++++++++++++ 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/adapter/sql/adapter.go b/adapter/sql/adapter.go index 81581340..542798e3 100644 --- a/adapter/sql/adapter.go +++ b/adapter/sql/adapter.go @@ -256,6 +256,8 @@ func (a *Adapter) Apply(ctx context.Context, migration rel.Migration) error { statement = builder.Table(v) case rel.Index: statement = builder.Index(v) + case rel.Raw: + statement = string(v) } _, _, err := a.Exec(ctx, statement, nil) diff --git a/adapter/sql/adapter_test.go b/adapter/sql/adapter_test.go index a700f84e..3c5590db 100644 --- a/adapter/sql/adapter_test.go +++ b/adapter/sql/adapter_test.go @@ -350,7 +350,7 @@ func TestAdapter_Apply(t *testing.T) { }) }) - t.Run("Table", func(t *testing.T) { + t.Run("Index", func(t *testing.T) { adapter.Apply(ctx, rel.Index{ Name: "username_idx", Optional: true, @@ -358,4 +358,8 @@ func TestAdapter_Apply(t *testing.T) { Columns: []string{"username"}, }) }) + + t.Run("Rw", func(t *testing.T) { + adapter.Apply(ctx, rel.Raw("SELECT 1;")) + }) } diff --git a/migrator/migrator.go b/migrator/migrator.go index 573610e0..f1364850 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -84,8 +84,6 @@ func (m *Migrator) sync(ctx context.Context) { m.repo.MustFindAll(ctx, &versions, rel.NewSortAsc("version")) sort.Sort(m.versions) - fmt.Println(versions) - for i := range m.versions { if vi < len(versions) && m.versions[i].Version == versions[vi].Version { m.versions[i].ID = versions[vi].ID @@ -146,8 +144,11 @@ func (m *Migrator) Rollback(ctx context.Context) { func (m *Migrator) run(ctx context.Context, migrations []rel.Migration) { adapter := m.repo.Adapter(ctx).(rel.Adapter) for _, migration := range migrations { - // TODO: exec script - check(adapter.Apply(ctx, migration)) + if fn, ok := migration.(rel.Do); ok { + check(fn(m.repo)) + } else { + check(adapter.Apply(ctx, migration)) + } } } @@ -162,3 +163,7 @@ func check(err error) { panic(err) } } + +type doFn func(rel.Repository) + +func (df doFn) internalMigration() {} diff --git a/migrator/migrator_test.go b/migrator/migrator_test.go index 1df3b27a..6f3fb00c 100644 --- a/migrator/migrator_test.go +++ b/migrator/migrator_test.go @@ -34,6 +34,11 @@ func TestMigrator(t *testing.T) { schema.CreateTable("tags", func(t *rel.Table) { t.ID("id") }) + + schema.Do(func(repo rel.Repository) error { + assert.NotNil(t, repo) + return nil + }) }, func(schema *rel.Schema) { schema.DropTable("tags") diff --git a/schema.go b/schema.go index 7ca727c8..560bfd9c 100644 --- a/schema.go +++ b/schema.go @@ -100,10 +100,15 @@ func (s *Schema) DropIndex(table string, name string, options ...IndexOption) { s.add(dropIndex(table, name, options)) } -// Exec queries using repo. -// Useful for data migration. -// func (s *Schema) Exec(func(repo rel.Repository) error) { -// } +// Exec queries. +func (s *Schema) Exec(raw Raw) { + s.add(raw) +} + +// Do migration using golang codes. +func (s *Schema) Do(fn Do) { + s.add(fn) +} // Options options for table, column and index. type Options string @@ -140,4 +145,10 @@ func (o Optional) applyIndex(index *Index) { // Raw string type Raw string +func (r Raw) internalMigration() {} func (r Raw) internalTableDefinition() {} + +// Do used internally for schema migration. +type Do func(Repository) error + +func (d Do) internalMigration() {} diff --git a/schema_test.go b/schema_test.go index b75034f3..d77c450b 100644 --- a/schema_test.go +++ b/schema_test.go @@ -181,3 +181,19 @@ func TestSchema_DropIndex(t *testing.T) { Op: SchemaDrop, }, schema.Migrations[0]) } + +func TestSchema_Exec(t *testing.T) { + var schema Schema + + schema.Exec("RAW SQL") + assert.Equal(t, Raw("RAW SQL"), schema.Migrations[0]) +} + +func TestSchema_Do(t *testing.T) { + var ( + schema Schema + ) + + schema.Do(func(repo Repository) error { return nil }) + assert.NotNil(t, schema.Migrations[0]) +} From 98c7df2a42da461bec5e6179441bc8273d53b84f Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Wed, 2 Sep 2020 22:13:59 +0700 Subject: [PATCH 41/44] add feature to readme --- README.md | 2 +- docs/README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 339a206e..27ca215b 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ REL is golang orm-ish database layer for layered architecture. It's testable and - Multi adapter. - Soft Deletion. - Pagination. - +- Schema Migration. ## Install diff --git a/docs/README.md b/docs/README.md index 40e4e52a..a64b0fe4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,6 +14,7 @@ REL is golang orm-ish database layer for layered architecture. It's testable and - Multi adapter. - Soft Deletion. - Pagination. +- Schema Migration. ## Install From 1ba107693e953d96e1308e14e2a4494c82142823 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Wed, 2 Sep 2020 22:22:14 +0700 Subject: [PATCH 42/44] Update adapter_test.go --- adapter/sql/adapter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapter/sql/adapter_test.go b/adapter/sql/adapter_test.go index 3c5590db..43964c8d 100644 --- a/adapter/sql/adapter_test.go +++ b/adapter/sql/adapter_test.go @@ -359,7 +359,7 @@ func TestAdapter_Apply(t *testing.T) { }) }) - t.Run("Rw", func(t *testing.T) { + t.Run("Raw", func(t *testing.T) { adapter.Apply(ctx, rel.Raw("SELECT 1;")) }) } From 877b42b6c6e6b5dda7806072bd10b8a1c68894f1 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Wed, 2 Sep 2020 22:29:01 +0700 Subject: [PATCH 43/44] organize schema options into one file --- column.go | 64 -------------------- key.go | 26 --------- schema.go | 43 -------------- schema_options.go | 146 ++++++++++++++++++++++++++++++++++++++++++++++ table.go | 12 ---- 5 files changed, 146 insertions(+), 145 deletions(-) create mode 100644 schema_options.go diff --git a/column.go b/column.go index 89d01c5c..a903520a 100644 --- a/column.go +++ b/column.go @@ -79,67 +79,3 @@ func dropColumn(name string, options []ColumnOption) Column { applyColumnOptions(&column, options) return column } - -// ColumnOption interface. -// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. -type ColumnOption interface { - applyColumn(column *Column) -} - -func applyColumnOptions(column *Column, options []ColumnOption) { - for i := range options { - options[i].applyColumn(column) - } -} - -// Unique set column as unique. -type Unique bool - -func (r Unique) applyColumn(column *Column) { - column.Unique = bool(r) -} - -func (r Unique) applyIndex(index *Index) { - index.Unique = bool(r) -} - -// Required disallows nil values in the column. -type Required bool - -func (r Required) applyColumn(column *Column) { - column.Required = bool(r) -} - -// Unsigned sets integer column to be unsigned. -type Unsigned bool - -func (u Unsigned) applyColumn(column *Column) { - column.Unsigned = bool(u) -} - -// Precision defines the precision for the decimal fields, representing the total number of digits in the number. -type Precision int - -func (p Precision) applyColumn(column *Column) { - column.Precision = int(p) -} - -// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. -type Scale int - -func (s Scale) applyColumn(column *Column) { - column.Scale = int(s) -} - -type defaultValue struct { - value interface{} -} - -func (d defaultValue) applyColumn(column *Column) { - column.Default = d.value -} - -// Default allows to set a default value on the column.). -func Default(def interface{}) ColumnOption { - return defaultValue{value: def} -} diff --git a/key.go b/key.go index 53dc8e24..9d151571 100644 --- a/key.go +++ b/key.go @@ -64,29 +64,3 @@ func createForeignKey(column string, refTable string, refColumn string, options } // TODO: Rename and Drop, PR welcomed. - -// KeyOption interface. -// Available options are: Comment, Options. -type KeyOption interface { - applyKey(key *Key) -} - -func applyKeyOptions(key *Key, options []KeyOption) { - for i := range options { - options[i].applyKey(key) - } -} - -// OnDelete option for foreign key. -type OnDelete string - -func (od OnDelete) applyKey(key *Key) { - key.Reference.OnDelete = string(od) -} - -// OnUpdate option for foreign key. -type OnUpdate string - -func (ou OnUpdate) applyKey(key *Key) { - key.Reference.OnUpdate = string(ou) -} diff --git a/schema.go b/schema.go index 560bfd9c..2a0a1a82 100644 --- a/schema.go +++ b/schema.go @@ -109,46 +109,3 @@ func (s *Schema) Exec(raw Raw) { func (s *Schema) Do(fn Do) { s.add(fn) } - -// Options options for table, column and index. -type Options string - -func (o Options) applyTable(table *Table) { - table.Options = string(o) -} - -func (o Options) applyColumn(column *Column) { - column.Options = string(o) -} - -func (o Options) applyIndex(index *Index) { - index.Options = string(o) -} - -func (o Options) applyKey(key *Key) { - key.Options = string(o) -} - -// Optional option. -// when used with create table, will create table only if it's not exists. -// when used with drop table, will drop table only if it's exists. -type Optional bool - -func (o Optional) applyTable(table *Table) { - table.Optional = bool(o) -} - -func (o Optional) applyIndex(index *Index) { - index.Optional = bool(o) -} - -// Raw string -type Raw string - -func (r Raw) internalMigration() {} -func (r Raw) internalTableDefinition() {} - -// Do used internally for schema migration. -type Do func(Repository) error - -func (d Do) internalMigration() {} diff --git a/schema_options.go b/schema_options.go new file mode 100644 index 00000000..dcd26170 --- /dev/null +++ b/schema_options.go @@ -0,0 +1,146 @@ +package rel + +// TableOption interface. +// Available options are: Comment, Options. +type TableOption interface { + applyTable(table *Table) +} + +func applyTableOptions(table *Table, options []TableOption) { + for i := range options { + options[i].applyTable(table) + } +} + +// ColumnOption interface. +// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options. +type ColumnOption interface { + applyColumn(column *Column) +} + +func applyColumnOptions(column *Column, options []ColumnOption) { + for i := range options { + options[i].applyColumn(column) + } +} + +// KeyOption interface. +// Available options are: Comment, Options. +type KeyOption interface { + applyKey(key *Key) +} + +func applyKeyOptions(key *Key, options []KeyOption) { + for i := range options { + options[i].applyKey(key) + } +} + +// Unique set column as unique. +type Unique bool + +func (r Unique) applyColumn(column *Column) { + column.Unique = bool(r) +} + +func (r Unique) applyIndex(index *Index) { + index.Unique = bool(r) +} + +// Required disallows nil values in the column. +type Required bool + +func (r Required) applyColumn(column *Column) { + column.Required = bool(r) +} + +// Unsigned sets integer column to be unsigned. +type Unsigned bool + +func (u Unsigned) applyColumn(column *Column) { + column.Unsigned = bool(u) +} + +// Precision defines the precision for the decimal fields, representing the total number of digits in the number. +type Precision int + +func (p Precision) applyColumn(column *Column) { + column.Precision = int(p) +} + +// Scale Defines the scale for the decimal fields, representing the number of digits after the decimal point. +type Scale int + +func (s Scale) applyColumn(column *Column) { + column.Scale = int(s) +} + +type defaultValue struct { + value interface{} +} + +func (d defaultValue) applyColumn(column *Column) { + column.Default = d.value +} + +// Default allows to set a default value on the column.). +func Default(def interface{}) ColumnOption { + return defaultValue{value: def} +} + +// OnDelete option for foreign key. +type OnDelete string + +func (od OnDelete) applyKey(key *Key) { + key.Reference.OnDelete = string(od) +} + +// OnUpdate option for foreign key. +type OnUpdate string + +func (ou OnUpdate) applyKey(key *Key) { + key.Reference.OnUpdate = string(ou) +} + +// Options options for table, column and index. +type Options string + +func (o Options) applyTable(table *Table) { + table.Options = string(o) +} + +func (o Options) applyColumn(column *Column) { + column.Options = string(o) +} + +func (o Options) applyIndex(index *Index) { + index.Options = string(o) +} + +func (o Options) applyKey(key *Key) { + key.Options = string(o) +} + +// Optional option. +// when used with create table, will create table only if it's not exists. +// when used with drop table, will drop table only if it's exists. +type Optional bool + +func (o Optional) applyTable(table *Table) { + table.Optional = bool(o) +} + +func (o Optional) applyIndex(index *Index) { + index.Optional = bool(o) +} + +// Raw string +type Raw string + +func (r Raw) internalMigration() {} +func (r Raw) internalTableDefinition() {} + +// Do used internally for schema migration. +type Do func(Repository) error + +func (d Do) internalMigration() {} diff --git a/table.go b/table.go index 231789cc..54c5c10a 100644 --- a/table.go +++ b/table.go @@ -175,15 +175,3 @@ func dropTableIfExists(name string, options []TableOption) Table { table.Optional = true return table } - -// TableOption interface. -// Available options are: Comment, Options. -type TableOption interface { - applyTable(table *Table) -} - -func applyTableOptions(table *Table, options []TableOption) { - for i := range options { - options[i].applyTable(table) - } -} From e8bce29ea80fc3448c9249ec78121a86bc29edd9 Mon Sep 17 00:00:00 2001 From: Muhammad Surya Date: Fri, 4 Sep 2020 00:17:31 +0700 Subject: [PATCH 44/44] remove unused codes --- adapter/specs/specs.go | 1 - collection.go | 1 - context_wrapper.go | 4 ---- document.go | 2 -- migrator/migrator.go | 4 ---- reltest/nop_adapter.go | 1 - repository_test.go | 2 -- util.go | 2 +- 8 files changed, 1 insertion(+), 16 deletions(-) diff --git a/adapter/specs/specs.go b/adapter/specs/specs.go index 5a3d14e9..753519b8 100644 --- a/adapter/specs/specs.go +++ b/adapter/specs/specs.go @@ -71,7 +71,6 @@ var ( Placeholder: "?", EscapeChar: "`", } - builder = sql.NewBuilder(config) ) func assertConstraint(t *testing.T, err error, ctype rel.ConstraintType, key string) { diff --git a/collection.go b/collection.go index cb022abf..d8913cdc 100644 --- a/collection.go +++ b/collection.go @@ -18,7 +18,6 @@ type Collection struct { rv reflect.Value rt reflect.Type data documentData - index map[interface{}]int swapper func(i, j int) } diff --git a/context_wrapper.go b/context_wrapper.go index 664ca28f..ae1dee64 100644 --- a/context_wrapper.go +++ b/context_wrapper.go @@ -6,10 +6,6 @@ import ( type contextKey int8 -type contextData struct { - adapter Adapter -} - type contextWrapper struct { ctx context.Context adapter Adapter diff --git a/document.go b/document.go index 0242f3ae..c48a8060 100644 --- a/document.go +++ b/document.go @@ -33,8 +33,6 @@ const ( var ( tablesCache sync.Map primariesCache sync.Map - fieldsCache sync.Map - typesCache sync.Map documentDataCache sync.Map rtTime = reflect.TypeOf(time.Time{}) rtTable = reflect.TypeOf((*table)(nil)).Elem() diff --git a/migrator/migrator.go b/migrator/migrator.go index 7afee04d..0b27d916 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -163,7 +163,3 @@ func check(err error) { panic(err) } } - -type doFn func(rel.Repository) - -func (df doFn) internalMigration() {} diff --git a/reltest/nop_adapter.go b/reltest/nop_adapter.go index 27c21c01..b7ea8cc6 100644 --- a/reltest/nop_adapter.go +++ b/reltest/nop_adapter.go @@ -7,7 +7,6 @@ import ( ) type nopAdapter struct { - count int } func (na *nopAdapter) Instrumentation(instrumenter rel.Instrumenter) { diff --git a/repository_test.go b/repository_test.go index d45b14c7..8173dcea 100644 --- a/repository_test.go +++ b/repository_test.go @@ -18,8 +18,6 @@ func init() { } } -var repo = repository{} - func createCursor(row int) *testCursor { cur := &testCursor{} diff --git a/util.go b/util.go index f10fb098..400e5e5e 100644 --- a/util.go +++ b/util.go @@ -37,7 +37,7 @@ func isZero(value interface{}) bool { case nil: zero = true case bool: - zero = v == false + zero = !v case string: zero = v == "" case int: