Skip to content
46 changes: 46 additions & 0 deletions column.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,22 @@ const (
Time ColumnType = "TIME"
)

// AlterColumnMode enum.
type AlterColumnMode uint16

const (
// AlterColumnType operation.
AlterColumnType AlterColumnMode = iota + 1
// AlterColumnRequired operation.
AlterColumnRequired
// AlterColumnDefault operation.
AlterColumnDefault
)

// Column definition.
type Column struct {
Op SchemaOp
AlterMode AlterColumnMode
Name string
Type ColumnType
Rename string
Expand Down Expand Up @@ -75,6 +88,39 @@ func renameColumn(name string, newName string, options []ColumnOption) Column {
return column
}

func alterColumnType(name string, typ ColumnType, options []ColumnOption) []Column {
column := Column{
Op: SchemaAlter,
Name: name,
Type: typ,
AlterMode: AlterColumnType,
}
for _, option := range options {
if option.isConstraint() {
continue
}
option.applyColumn(&column)
}

return append([]Column{column}, alterColumn(name, options)...)
}

func alterColumn(name string, options []ColumnOption) []Column {
columns := make([]Column, 0, len(options))
for _, option := range options {
if !option.isConstraint() {
continue
}
column := Column{
Op: SchemaAlter,
Name: name,
}
option.applyColumn(&column)
columns = append(columns, column)
}
return columns
}

func dropColumn(name string, options []ColumnOption) Column {
column := Column{
Op: SchemaDrop,
Expand Down
26 changes: 26 additions & 0 deletions column_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ func TestRenameColumn(t *testing.T) {
}, column)
}

func TestAlterColumn(t *testing.T) {
var (
options = []ColumnOption{
Required(true),
Limit(1000),
}
columns = alterColumnType("alter", String, options)
)

assert.Equal(t, []Column{
{
Op: SchemaAlter,
AlterMode: AlterColumnType,
Type: String,
Name: "alter",
Limit: 1000,
},
{
Op: SchemaAlter,
AlterMode: AlterColumnRequired,
Name: "alter",
Required: true,
},
}, columns)
}

func TestDropColumn(t *testing.T) {
var (
options = []ColumnOption{
Expand Down
16 changes: 7 additions & 9 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ type QueryPopulator interface {

// Build for given table using given queriers.
func Build(table string, queriers ...Querier) Query {
var (
query = newQuery()
)
query := newQuery()

if len(queriers) > 0 {
_, query.empty = queriers[0].(Query)
Expand Down Expand Up @@ -255,9 +253,7 @@ func (q Query) Sort(fields ...string) Query {

// SortAsc query.
func (q Query) SortAsc(fields ...string) Query {
var (
offset = len(q.SortQuery)
)
offset := len(q.SortQuery)

q.SortQuery = append(q.SortQuery, make([]SortQuery, len(fields))...)
for i := range fields {
Expand All @@ -269,9 +265,7 @@ func (q Query) SortAsc(fields ...string) Query {

// SortDesc query.
func (q Query) SortDesc(fields ...string) Query {
var (
offset = len(q.SortQuery)
)
offset := len(q.SortQuery)

q.SortQuery = append(q.SortQuery, make([]SortQuery, len(fields))...)
for i := range fields {
Expand Down Expand Up @@ -538,6 +532,10 @@ func (l Limit) applyColumn(column *Column) {
column.Limit = int(l)
}

func (l Limit) isConstraint() bool {
return false
}

// Lock query.
// This query will be ignored if used outside of transaction.
type Lock string
Expand Down
25 changes: 25 additions & 0 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,31 @@ func (s *Schema) AddColumn(table string, name string, typ ColumnType, options ..
s.add(at.Table)
}

// AlterColumnType with name.
//
// Allows also changing other constraints like [rel.Default] and [rel.Required].
//
// WARNING: Not supported by SQLite driver.
func (s *Schema) AlterColumnType(table string, name string, typ ColumnType, options ...ColumnOption) {
at := alterTable(table, nil)
at.AlterColumnType(name, typ, options...)
s.add(at.Table)
}

// AlterColumn with name.
//
// Only [rel.Default] and [rel.Required] are supported.
// Support for underlying drivers might wary. For example PostgreSQL supports both,
// while Microsoft SQL Server and MySQL/MariaDB only supports [rel.Default].
// See [Schema.AlterColumnType] if other constraints need to be changed also.
//
// WARNING: Not supported by SQLite driver.
func (s *Schema) AlterColumn(table string, name string, options ...ColumnOption) {
at := alterTable(table, nil)
at.AlterColumn(name, options...)
s.add(at.Table)
}

// RenameColumn by name.
func (s *Schema) RenameColumn(table string, name string, newName string, options ...ColumnOption) {
at := alterTable(table, nil)
Expand Down
39 changes: 39 additions & 0 deletions schema_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func applyTableOptions(table *Table, options []TableOption) {
// Available options are: Nil, Unsigned, Limit, Precision, Scale, Default, Comment, Options.
type ColumnOption interface {
applyColumn(column *Column)
isConstraint() bool
}

func applyColumnOptions(column *Column, options []ColumnOption) {
Expand Down Expand Up @@ -43,13 +44,21 @@ func (r Primary) applyColumn(column *Column) {
column.Primary = bool(r)
}

func (r Primary) isConstraint() bool {
return false
}

// Unique set column as unique.
type Unique bool

func (r Unique) applyColumn(column *Column) {
column.Unique = bool(r)
}

func (r Unique) isConstraint() bool {
return false
}

func (r Unique) applyIndex(index *Index) {
index.Unique = bool(r)
}
Expand All @@ -59,6 +68,13 @@ type Required bool

func (r Required) applyColumn(column *Column) {
column.Required = bool(r)
if column.Op == SchemaAlter {
column.AlterMode = AlterColumnRequired
}
}

func (r Required) isConstraint() bool {
return true
}

// Unsigned sets integer column to be unsigned.
Expand All @@ -68,26 +84,45 @@ func (u Unsigned) applyColumn(column *Column) {
column.Unsigned = bool(u)
}

func (r Unsigned) isConstraint() bool {
return false
}

// 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)
}

func (p Precision) isConstraint() bool {
return false
}

// 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)
}

func (s Scale) isConstraint() bool {
return false
}

type defaultValue struct {
value any
}

func (d defaultValue) applyColumn(column *Column) {
column.Default = d.value
if column.Op == SchemaAlter {
column.AlterMode = AlterColumnDefault
}
}

func (d defaultValue) isConstraint() bool {
return true
}

// Default allows to set a default value on the column.).
Expand Down Expand Up @@ -120,6 +155,10 @@ func (o Options) applyColumn(column *Column) {
column.Options = string(o)
}

func (o Options) isConstraint() bool {
return false
}

func (o Options) applyIndex(index *Index) {
index.Options = string(o)
}
Expand Down
47 changes: 44 additions & 3 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,49 @@ func TestSchema_AddColumn(t *testing.T) {
}, schema.Migrations[0])
}

func TestSchema_AlterColumnTypeString(t *testing.T) {
var schema Schema

schema.AlterColumnType("products", "description", String, Limit(100), Unique(false), Primary(false))

assert.Equal(t, Table{
Op: SchemaAlter,
Name: "products",
Definitions: []TableDefinition{
Column{Name: "description", Type: String, Op: SchemaAlter, Limit: 100, AlterMode: AlterColumnType},
},
}, schema.Migrations[0])
}

func TestSchema_AlterColumnTypeNumber(t *testing.T) {
var schema Schema

schema.AlterColumnType("products", "description", Decimal, Scale(10), Precision(2), Unsigned(true), Options(""))

assert.Equal(t, Table{
Op: SchemaAlter,
Name: "products",
Definitions: []TableDefinition{
Column{Name: "description", Type: Decimal, Op: SchemaAlter, Scale: 10, Precision: 2, Unsigned: true, AlterMode: AlterColumnType},
},
}, schema.Migrations[0])
}

func TestSchema_AlterColumn(t *testing.T) {
var schema Schema

schema.AlterColumn("products", "description", Required(true), Default("<no description>"))

assert.Equal(t, Table{
Op: SchemaAlter,
Name: "products",
Definitions: []TableDefinition{
Column{Name: "description", Op: SchemaAlter, Required: true, AlterMode: AlterColumnRequired},
Column{Name: "description", Op: SchemaAlter, Default: "<no description>", AlterMode: AlterColumnDefault},
},
}, schema.Migrations[0])
}

func TestSchema_RenameColumn(t *testing.T) {
var schema Schema

Expand Down Expand Up @@ -218,9 +261,7 @@ func TestRaw_InternalTableDefinition(t *testing.T) {
}

func TestDo(t *testing.T) {
var (
schema Schema
)
var schema Schema

schema.Do(func(ctx context.Context, repo Repository) error { return nil })
assert.NotNil(t, schema.Migrations[0])
Expand Down
16 changes: 16 additions & 0 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,22 @@ func (at *AlterTable) RenameColumn(name string, newName string, options ...Colum
at.Definitions = append(at.Definitions, renameColumn(name, newName, options))
}

// AlterColumnType with name.
func (at *AlterTable) AlterColumnType(name string, typ ColumnType, options ...ColumnOption) {
defs := alterColumnType(name, typ, options)
for _, def := range defs {
at.Definitions = append(at.Definitions, def)
}
}

// AlterColumn with name.
func (at *AlterTable) AlterColumn(name string, options ...ColumnOption) {
defs := alterColumn(name, options)
for _, def := range defs {
at.Definitions = append(at.Definitions, def)
}
}

// DropColumn from this table.
func (at *AlterTable) DropColumn(name string, options ...ColumnOption) {
at.Definitions = append(at.Definitions, dropColumn(name, options))
Expand Down