Skip to content

Commit

Permalink
chore: add in-code documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
bevzzz committed Nov 8, 2024
1 parent c3320f6 commit b3a9697
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 23 deletions.
10 changes: 5 additions & 5 deletions internal/dbtest/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func formatType(c sqlschema.ColumnDefinition) string {
return fmt.Sprintf("%s(%d)", c.SQLType, c.VarcharLen)
}

func TestSchemaInspector_Inspect(t *testing.T) {
func TestBunModelInspector_Inspect(t *testing.T) {
testEachDialect(t, func(t *testing.T, dialectName string, dialect schema.Dialect) {
if _, ok := dialect.(sqlschema.InspectorDialect); !ok {
t.Skip(dialectName + " is not sqlschema.InspectorDialect")
Expand All @@ -420,7 +420,7 @@ func TestSchemaInspector_Inspect(t *testing.T) {

tables := schema.NewTables(dialect)
tables.Register((*Model)(nil))
inspector := sqlschema.NewSchemaInspector(tables)
inspector := sqlschema.NewBunModelInspector(tables)

want := map[string]sqlschema.ColumnDefinition{
"id": {
Expand Down Expand Up @@ -453,7 +453,7 @@ func TestSchemaInspector_Inspect(t *testing.T) {

tables := schema.NewTables(dialect)
tables.Register((*Model)(nil))
inspector := sqlschema.NewSchemaInspector(tables)
inspector := sqlschema.NewBunModelInspector(tables)

want := map[string]sqlschema.ColumnDefinition{
"id": {
Expand Down Expand Up @@ -488,7 +488,7 @@ func TestSchemaInspector_Inspect(t *testing.T) {

tables := schema.NewTables(dialect)
tables.Register((*Model)(nil))
inspector := sqlschema.NewSchemaInspector(tables)
inspector := sqlschema.NewBunModelInspector(tables)

want := sqlschema.TableDefinition{
Name: "models",
Expand Down Expand Up @@ -517,7 +517,7 @@ func TestSchemaInspector_Inspect(t *testing.T) {

tables := schema.NewTables(dialect)
tables.Register((*Model)(nil))
inspector := sqlschema.NewSchemaInspector(tables)
inspector := sqlschema.NewBunModelInspector(tables)
want := sqlschema.NewColumns("id", "email")

got, err := inspector.Inspect(context.Background())
Expand Down
32 changes: 31 additions & 1 deletion migrate/auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,36 @@ func WithMigrationsDirectoryAuto(directory string) AutoMigratorOption {
}
}

// AutoMigrator performs automated schema migrations.
//
// It is designed to be a drop-in replacement for some Migrator functionality and supports all existing
// configuration options.
// Similarly to Migrator, it has methods to create SQL migrations, write them to a file, and apply them.
// Unlike Migrator, it detects the differences between the state defined by bun models and the current
// database schema automatically.
//
// Usage:
// 1. Generate migrations and apply them au once with AutoMigrator.Migrate().
// 2. Create up- and down-SQL migration files and apply migrations using Migrator.Migrate().
//
// While both methods produce complete, reversible migrations (with entries in the database
// and SQL migration files), prefer creating migrations and applying them separately for
// any non-trivial cases to ensure AutoMigrator detects expected changes correctly.
//
// Limitations:
// - AutoMigrator only supports a subset of the possible ALTER TABLE modifications.
// - Some changes are not automatically reversible. For example, you would need to manually
// add a CREATE TABLE query to the .down migration file to revert a DROP TABLE migration.
// - Does not validate most dialect-specific constraints. For example, when changing column
// data type, make sure the data con be auto-casted to the new type.
// - Due to how the schema-state diff is calculated, it is not possible to rename a table and
// modify any of its columns' _data type_ in a single run. This will cause the AutoMigrator
// to drop and re-create the table under a different name; it is better to apply this change in 2 steps.
// Renaming a table and renaming its columns at the same time is possible.
// - Renaming table/column to an existing name, i.e. like this [A->B] [B->C], is not possible due to how
// AutoMigrator distinguishes "rename" and "unchanged" columns.
//
// Dialect must implement both sqlschema.Inspector and sqlschema.Migrator to be used with AutoMigrator.
type AutoMigrator struct {
db *bun.DB

Expand Down Expand Up @@ -122,7 +152,7 @@ func NewAutoMigrator(db *bun.DB, opts ...AutoMigratorOption) (*AutoMigrator, err

tables := schema.NewTables(db.Dialect())
tables.Register(am.includeModels...)
am.modelInspector = sqlschema.NewSchemaInspector(tables)
am.modelInspector = sqlschema.NewBunModelInspector(tables)

return am, nil
}
Expand Down
10 changes: 9 additions & 1 deletion migrate/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ RenameCreate:

// If wantTable does not exist in the database and was not renamed
// then we need to create this table in the database.
additional := wantTable.(sqlschema.ModelTable)
additional := wantTable.(sqlschema.BunTable)
d.changes.Add(&CreateTableOp{
FQN: wantTable.GetFQN(),
Model: additional.Model,
Expand Down Expand Up @@ -379,6 +379,11 @@ func (s *signature) Equals(other signature) bool {
return true
}

// refMap is a utility for tracking superficial changes in foreign keys,
// which do not require any modificiation in the database.
// Modern SQL dialects automatically updated foreign key constraints whenever
// a column or a table is renamed. Detector can use refMap to ignore any
// differences in foreign keys which were caused by renamed column/table.
type refMap map[*sqlschema.ForeignKey]string

func newRefMap(fks map[sqlschema.ForeignKey]string) refMap {
Expand All @@ -389,6 +394,7 @@ func newRefMap(fks map[sqlschema.ForeignKey]string) refMap {
return rm
}

// RenameT updates table name in all foreign key definions which depend on it.
func (rm refMap) RenameTable(table schema.FQN, newName string) {
for fk := range rm {
switch table {
Expand All @@ -400,6 +406,7 @@ func (rm refMap) RenameTable(table schema.FQN, newName string) {
}
}

// RenameColumn updates column name in all foreign key definions which depend on it.
func (rm refMap) RenameColumn(table schema.FQN, column, newName string) {
for fk := range rm {
if table == fk.From.FQN {
Expand All @@ -411,6 +418,7 @@ func (rm refMap) RenameColumn(table schema.FQN, column, newName string) {
}
}

// Deref returns copies of ForeignKey values to a map.
func (rm refMap) Deref() map[sqlschema.ForeignKey]string {
out := make(map[sqlschema.ForeignKey]string)
for fk, name := range rm {
Expand Down
43 changes: 40 additions & 3 deletions migrate/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,28 @@ import (

// Operation encapsulates the request to change a database definition
// and knowns which operation can revert it.
//
// It is useful to define "monolith" Operations whenever possible,
// even though they a dialect may require several distinct steps to apply them.
// For example, changing a primary key involves first dropping the old constraint
// before generating the new one. Yet, this is only an implementation detail and
// passing a higher-level ChangePrimaryKeyOp will give the dialect more information
// about the applied change.
//
// Some operations might be irreversible due to technical limitations. Returning
// a *comment from GetReverse() will add an explanatory note to the generate migation file.
//
// To declare dependency on another Operation, operations should implement
// { DependsOn(Operation) bool } interface, which Changeset will use to resolve dependencies.
type Operation interface {
GetReverse() Operation
}

// CreateTableOp
// CreateTableOp creates a new table in the schema.
//
// It does not report dependency on any other migration and may be executed first.
// Make sure the dialect does not include FOREIGN KEY constraints in the CREATE TABLE
// statement, as those may potentially reference not-yet-existing columns/tables.
type CreateTableOp struct {
FQN schema.FQN
Model interface{}
Expand All @@ -25,6 +42,7 @@ func (op *CreateTableOp) GetReverse() Operation {
return &DropTableOp{FQN: op.FQN}
}

// DropTableOp drops a database table. This operation is not reversible.
type DropTableOp struct {
FQN schema.FQN
}
Expand All @@ -43,6 +61,7 @@ func (op *DropTableOp) GetReverse() Operation {
return &c
}

// RenameTableOp renames the table. Note, that changing the "schema" part of the table's FQN is not allowed.
type RenameTableOp struct {
FQN schema.FQN
NewName string
Expand All @@ -57,7 +76,8 @@ func (op *RenameTableOp) GetReverse() Operation {
}
}

// RenameColumnOp.
// RenameColumnOp renames a column in the table. If the changeset includes a rename operation
// for the column's table, it should be executed first.
type RenameColumnOp struct {
FQN schema.FQN
OldName string
Expand All @@ -79,6 +99,7 @@ func (op *RenameColumnOp) DependsOn(another Operation) bool {
return ok && op.FQN.Schema == rename.FQN.Schema && op.FQN.Table == rename.NewName
}

// AddColumnOp adds a new column to the table.
type AddColumnOp struct {
FQN schema.FQN
Column string
Expand All @@ -95,6 +116,12 @@ func (op *AddColumnOp) GetReverse() Operation {
}
}

// DropColumnOp drop a column from the table.
//
// While some dialects allow DROP CASCADE to drop dependent constraints,
// explicit handling on constraints is preferred for transparency and debugging.
// DropColumnOp depends on DropForeignKeyOp, DropPrimaryKeyOp, and ChangePrimaryKeyOp
// if any of the constraints is defined on this table.
type DropColumnOp struct {
FQN schema.FQN
Column string
Expand Down Expand Up @@ -123,6 +150,7 @@ func (op *DropColumnOp) DependsOn(another Operation) bool {
return false
}

// AddForeignKey adds a new FOREIGN KEY constraint.
type AddForeignKeyOp struct {
ForeignKey sqlschema.ForeignKey
ConstraintName string
Expand Down Expand Up @@ -152,6 +180,7 @@ func (op *AddForeignKeyOp) GetReverse() Operation {
}
}

// DropForeignKeyOp drops a FOREIGN KEY constraint.
type DropForeignKeyOp struct {
ForeignKey sqlschema.ForeignKey
ConstraintName string
Expand All @@ -170,6 +199,7 @@ func (op *DropForeignKeyOp) GetReverse() Operation {
}
}

// AddUniqueConstraintOp adds new UNIQUE constraint to the table.
type AddUniqueConstraintOp struct {
FQN schema.FQN
Unique sqlschema.Unique
Expand Down Expand Up @@ -199,6 +229,7 @@ func (op *AddUniqueConstraintOp) DependsOn(another Operation) bool {

}

// DropUniqueConstraintOp drops a UNIQUE constraint.
type DropUniqueConstraintOp struct {
FQN schema.FQN
Unique sqlschema.Unique
Expand All @@ -220,7 +251,10 @@ func (op *DropUniqueConstraintOp) GetReverse() Operation {
}
}

// Change column type.
// ChangeColumnTypeOp set a new data type for the column.
// The two types should be such that the data can be auto-casted from one to another.
// E.g. reducing VARCHAR lenght is not possible in most dialects.
// AutoMigrator does not enforce or validate these rules.
type ChangeColumnTypeOp struct {
FQN schema.FQN
Column string
Expand All @@ -239,6 +273,7 @@ func (op *ChangeColumnTypeOp) GetReverse() Operation {
}
}

// DropPrimaryKeyOp drops the table's PRIMARY KEY.
type DropPrimaryKeyOp struct {
FQN schema.FQN
PrimaryKey sqlschema.PrimaryKey
Expand All @@ -253,6 +288,7 @@ func (op *DropPrimaryKeyOp) GetReverse() Operation {
}
}

// AddPrimaryKeyOp adds a new PRIMARY KEY to the table.
type AddPrimaryKeyOp struct {
FQN schema.FQN
PrimaryKey sqlschema.PrimaryKey
Expand All @@ -275,6 +311,7 @@ func (op *AddPrimaryKeyOp) DependsOn(another Operation) bool {
return false
}

// ChangePrimaryKeyOp changes the PRIMARY KEY of the table.
type ChangePrimaryKeyOp struct {
FQN schema.FQN
Old sqlschema.PrimaryKey
Expand Down
31 changes: 18 additions & 13 deletions migrate/sqlschema/inspector.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ type InspectorDialect interface {
EquivalentType(Column, Column) bool
}

// Inspector reads schema state.
type Inspector interface {
Inspect(ctx context.Context) (Schema, error)
}

// Schema is an abstract collection of database objects.
type Schema interface {
GetTables() []Table
GetForeignKeys() map[ForeignKey]string
Expand All @@ -48,10 +50,12 @@ type Column interface {
GetIsIdentity() bool
}

// inspector is opaque pointer to a databse inspector.
type inspector struct {
Inspector
}

// NewInspector creates a new database inspector, if the dialect supports it.
func NewInspector(db *bun.DB, excludeTables ...string) (Inspector, error) {
dialect, ok := (db.Dialect()).(InspectorDialect)
if !ok {
Expand All @@ -62,24 +66,25 @@ func NewInspector(db *bun.DB, excludeTables ...string) (Inspector, error) {
}, nil
}

// SchemaInspector creates the current project state from the passed bun.Models.
// Do not recycle SchemaInspector for different sets of models, as older models will not be de-registerred before the next run.
type SchemaInspector struct {
// BunModelInspector creates the current project state from the passed bun.Models.
// Do not recycle BunModelInspector for different sets of models, as older models will not be de-registerred before the next run.
type BunModelInspector struct {
tables *schema.Tables
}

var _ Inspector = (*SchemaInspector)(nil)
var _ Inspector = (*BunModelInspector)(nil)

func NewSchemaInspector(tables *schema.Tables) *SchemaInspector {
return &SchemaInspector{
func NewBunModelInspector(tables *schema.Tables) *BunModelInspector {
return &BunModelInspector{
tables: tables,
}
}

// BunModelSchema is the schema state derived from bun table models.
type BunModelSchema struct {
DatabaseSchema

ModelTables map[schema.FQN]ModelTable
ModelTables map[schema.FQN]BunTable
}

func (ms BunModelSchema) GetTables() []Table {
Expand All @@ -90,22 +95,22 @@ func (ms BunModelSchema) GetTables() []Table {
return tables
}

// ModelTable provides additional table metadata that is only accessible from scanning Go models.
type ModelTable struct {
// BunTable provides additional table metadata that is only accessible from scanning bun models.
type BunTable struct {
TableDefinition

// Model stores the zero interface to the underlying Go struct.
Model interface{}
}

func (si *SchemaInspector) Inspect(ctx context.Context) (Schema, error) {
func (bmi *BunModelInspector) Inspect(ctx context.Context) (Schema, error) {
state := BunModelSchema{
DatabaseSchema: DatabaseSchema{
ForeignKeys: make(map[ForeignKey]string),
},
ModelTables: make(map[schema.FQN]ModelTable),
ModelTables: make(map[schema.FQN]BunTable),
}
for _, t := range si.tables.All() {
for _, t := range bmi.tables.All() {
columns := make(map[string]ColumnDefinition)
for _, f := range t.Fields {

Expand Down Expand Up @@ -153,7 +158,7 @@ func (si *SchemaInspector) Inspect(ctx context.Context) (Schema, error) {
}

fqn := schema.FQN{Schema: t.Schema, Table: t.Name}
state.ModelTables[fqn] = ModelTable{
state.ModelTables[fqn] = BunTable{
TableDefinition: TableDefinition{
Schema: t.Schema,
Name: t.Name,
Expand Down
3 changes: 3 additions & 0 deletions migrate/sqlschema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
"github.com/uptrace/bun/schema"
)

// DatabaseSchema provides a default implementation of the Schema interface.
// Dialects which support schema inspection may return it directly from Inspect()
// or embed it in their custom schema structs.
type DatabaseSchema struct {
TableDefinitions map[schema.FQN]TableDefinition
ForeignKeys map[ForeignKey]string
Expand Down

0 comments on commit b3a9697

Please sign in to comment.