Skip to content

Commit

Permalink
feat: [#280] Add Foreign methods (#715)
Browse files Browse the repository at this point in the history
  • Loading branch information
hwbrzzl authored Nov 8, 2024
1 parent 0a28acb commit 7674cb5
Show file tree
Hide file tree
Showing 12 changed files with 869 additions and 0 deletions.
4 changes: 4 additions & 0 deletions contracts/database/schema/blueprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ type Blueprint interface {
Create()
// DropIfExists Indicate that the table should be dropped if it exists.
DropIfExists()
// Foreign Specify a foreign key for the table.
Foreign(column ...string) ForeignKeyDefinition
// GetAddedColumns Get the added columns.
GetAddedColumns() []ColumnDefinition
// GetCommands Get the commands.
GetCommands() []*Command
// GetPrefix Get the table prefix.
GetPrefix() string
// GetTableName Get the table name with prefix.
GetTableName() string
// HasCommand Determine if the blueprint has a specific command.
Expand Down
2 changes: 2 additions & 0 deletions contracts/database/schema/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type Grammar interface {
CompileDropAllViews(views []string) string
// CompileDropIfExists Compile a drop table (if exists) command.
CompileDropIfExists(blueprint Blueprint) string
// CompileForeign Compile a foreign key command.
CompileForeign(blueprint Blueprint, command *Command) string
// CompileIndex Compile a plain index key command.
CompileIndex(blueprint Blueprint, command *Command) string
// CompileIndexes Compile the query to determine the indexes.
Expand Down
13 changes: 13 additions & 0 deletions contracts/database/schema/index.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package schema

type ForeignKeyDefinition interface {
CascadeOnDelete() ForeignKeyDefinition
CascadeOnUpdate() ForeignKeyDefinition
On(table string) ForeignKeyDefinition
Name(name string) ForeignKeyDefinition
NoActionOnDelete() ForeignKeyDefinition
NoActionOnUpdate() ForeignKeyDefinition
NullOnDelete() ForeignKeyDefinition
References(columns ...string) ForeignKeyDefinition
RestrictOnDelete() ForeignKeyDefinition
RestrictOnUpdate() ForeignKeyDefinition
}

type IndexDefinition interface {
Algorithm(algorithm string) IndexDefinition
Name(name string) IndexDefinition
Expand Down
12 changes: 12 additions & 0 deletions database/schema/blueprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ func (r *Blueprint) DropIfExists() {
})
}

func (r *Blueprint) Foreign(column ...string) schema.ForeignKeyDefinition {
command := r.indexCommand(constants.CommandForeign, column)

return NewForeignKeyDefinition(command)
}

func (r *Blueprint) GetAddedColumns() []schema.ColumnDefinition {
var columns []schema.ColumnDefinition
for _, column := range r.columns {
Expand All @@ -73,6 +79,10 @@ func (r *Blueprint) GetCommands() []*schema.Command {
return r.commands
}

func (r *Blueprint) GetPrefix() string {
return r.prefix
}

func (r *Blueprint) GetTableName() string {
// TODO Add schema for Postgres
return r.prefix + r.table
Expand Down Expand Up @@ -149,6 +159,8 @@ func (r *Blueprint) ToSql(grammar schema.Grammar) []string {
statements = append(statements, grammar.CompileCreate(r))
case constants.CommandDropIfExists:
statements = append(statements, grammar.CompileDropIfExists(r))
case constants.CommandForeign:
statements = append(statements, grammar.CompileForeign(r, command))
case constants.CommandIndex:
statements = append(statements, grammar.CompileIndex(r, command))
case constants.CommandPrimary:
Expand Down
1 change: 1 addition & 0 deletions database/schema/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const (
CommandComment = "comment"
CommandCreate = "create"
CommandDropIfExists = "dropIfExists"
CommandForeign = "foreign"
CommandIndex = "index"
CommandPrimary = "primary"
DefaultStringLength = 255
Expand Down
17 changes: 17 additions & 0 deletions database/schema/grammars/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ func (r *Postgres) CompileDropIfExists(blueprint schema.Blueprint) string {
return fmt.Sprintf("drop table if exists %s", blueprint.GetTableName())
}

func (r *Postgres) CompileForeign(blueprint schema.Blueprint, command *schema.Command) string {
sql := fmt.Sprintf("alter table %s add constraint %s foreign key (%s) references %s (%s)",
blueprint.GetTableName(),
command.Index,
strings.Join(command.Columns, ", "),
fmt.Sprintf("%s%s", blueprint.GetPrefix(), command.On),
strings.Join(command.References, ", "))
if command.OnDelete != "" {
sql += " on delete " + command.OnDelete
}
if command.OnUpdate != "" {
sql += " on update " + command.OnUpdate
}

return sql
}

func (r *Postgres) CompileIndex(blueprint schema.Blueprint, command *schema.Command) string {
var algorithm string
if command.Algorithm != "" {
Expand Down
48 changes: 48 additions & 0 deletions database/schema/grammars/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,54 @@ func (s *PostgresSuite) TestCompileDropIfExists() {
s.Equal("drop table if exists users", s.grammar.CompileDropIfExists(mockBlueprint))
}

func (s *PostgresSuite) TestCompileForeign() {
var mockBlueprint *mocksschema.Blueprint

beforeEach := func() {
mockBlueprint = mocksschema.NewBlueprint(s.T())
mockBlueprint.EXPECT().GetPrefix().Return("goravel_").Once()
mockBlueprint.EXPECT().GetTableName().Return("users").Once()
}

tests := []struct {
name string
command *contractsschema.Command
expectSql string
}{
{
name: "with on delete and on update",
command: &contractsschema.Command{
Index: "fk_users_role_id",
Columns: []string{"role_id"},
On: "roles",
References: []string{"id"},
OnDelete: "cascade",
OnUpdate: "restrict",
},
expectSql: "alter table users add constraint fk_users_role_id foreign key (role_id) references goravel_roles (id) on delete cascade on update restrict",
},
{
name: "without on delete and on update",
command: &contractsschema.Command{
Index: "fk_users_role_id",
Columns: []string{"role_id"},
On: "roles",
References: []string{"id"},
},
expectSql: "alter table users add constraint fk_users_role_id foreign key (role_id) references goravel_roles (id)",
},
}

for _, test := range tests {
s.Run(test.name, func() {
beforeEach()

sql := s.grammar.CompileForeign(mockBlueprint, test.command)
s.Equal(test.expectSql, sql)
})
}
}

func (s *PostgresSuite) TestEscapeNames() {
// SingleName
names := []string{"username"}
Expand Down
70 changes: 70 additions & 0 deletions database/schema/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,76 @@ import (
"github.com/goravel/framework/contracts/database/schema"
)

type ForeignKeyDefinition struct {
command *schema.Command
}

func NewForeignKeyDefinition(command *schema.Command) schema.ForeignKeyDefinition {
return &ForeignKeyDefinition{
command: command,
}
}

func (f *ForeignKeyDefinition) CascadeOnDelete() schema.ForeignKeyDefinition {
f.command.OnDelete = "cascade"

return f
}

func (f *ForeignKeyDefinition) CascadeOnUpdate() schema.ForeignKeyDefinition {
f.command.OnUpdate = "cascade"

return f
}

func (f *ForeignKeyDefinition) On(table string) schema.ForeignKeyDefinition {
f.command.On = table

return f
}

func (f *ForeignKeyDefinition) Name(name string) schema.ForeignKeyDefinition {
f.command.Index = name

return f
}

func (f *ForeignKeyDefinition) NoActionOnDelete() schema.ForeignKeyDefinition {
f.command.OnDelete = "no action"

return f
}

func (f *ForeignKeyDefinition) NoActionOnUpdate() schema.ForeignKeyDefinition {
f.command.OnUpdate = "no action"

return f
}

func (f *ForeignKeyDefinition) NullOnDelete() schema.ForeignKeyDefinition {
f.command.OnDelete = "set null"

return f
}

func (f *ForeignKeyDefinition) References(columns ...string) schema.ForeignKeyDefinition {
f.command.References = columns

return f
}

func (f *ForeignKeyDefinition) RestrictOnDelete() schema.ForeignKeyDefinition {
f.command.OnDelete = "restrict"

return f
}

func (f *ForeignKeyDefinition) RestrictOnUpdate() schema.ForeignKeyDefinition {
f.command.OnUpdate = "restrict"

return f
}

type IndexDefinition struct {
command *schema.Command
}
Expand Down
28 changes: 28 additions & 0 deletions database/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,34 @@ func (s *SchemaSuite) TestDropAllViews() {

}

func (s *SchemaSuite) TestForeign() {
for driver, testQuery := range s.driverToTestQuery {
s.Run(driver.String(), func() {
schema := GetTestSchema(testQuery, s.driverToTestQuery)
table1 := "foreign1"

err := schema.Create(table1, func(table contractsschema.Blueprint) {
table.ID()
table.String("name")
})

s.Require().Nil(err)
s.Require().True(schema.HasTable(table1))

table2 := "foreign2"
err = schema.Create(table2, func(table contractsschema.Blueprint) {
table.ID()
table.String("name")
table.Integer("foreign1_id")
table.Foreign("foreign1_id").References("id").On(table1)
})

s.Require().Nil(err)
s.Require().True(schema.HasTable(table2))
})
}
}

func (s *SchemaSuite) TestPrimary() {
for driver, testQuery := range s.driverToTestQuery {
s.Run(driver.String(), func() {
Expand Down
106 changes: 106 additions & 0 deletions mocks/database/schema/Blueprint.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7674cb5

Please sign in to comment.