Skip to content

Commit 54da888

Browse files
Merge pull request #1 from wilburhimself/add_foreign_keys_and_indexes_to_migrations
Enhanced Migration System with Foreign Keys, Indexes, and Batch Support
2 parents a8ec2c0 + 022a292 commit 54da888

File tree

5 files changed

+741
-206
lines changed

5 files changed

+741
-206
lines changed

README.md

+75-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Theory
22

3-
A lightweight and intuitive ORM for Go, inspired by the original Java version.
3+
A lightweight and intuitive ORM for Go, inspired by (the original Java version)[https://github.com/wilburhimself/theory_java].
44

55
## Features
66

@@ -197,6 +197,22 @@ func createUserMigration() *migration.Migration {
197197
{Name: "name", Type: "TEXT", IsNull: false},
198198
{Name: "email", Type: "TEXT", IsNull: true},
199199
},
200+
ForeignKeys: []migration.ForeignKey{
201+
{
202+
Columns: []string{"team_id"},
203+
RefTable: "teams",
204+
RefColumns: []string{"id"},
205+
OnDelete: "CASCADE",
206+
OnUpdate: "CASCADE",
207+
},
208+
},
209+
Indexes: []migration.Index{
210+
{
211+
Name: "idx_users_email",
212+
Columns: []string{"email"},
213+
Unique: true,
214+
},
215+
},
200216
},
201217
}
202218

@@ -207,13 +223,69 @@ func createUserMigration() *migration.Migration {
207223

208224
return m
209225
}
226+
```
227+
228+
#### Running Migrations
229+
230+
Theory provides several ways to run migrations:
231+
232+
```go
233+
// Create a new migrator
234+
migrator := migration.NewMigrator(db)
210235

211-
// Add and run migration
212-
migrator := db.Migrator()
236+
// Add migrations
213237
migrator.Add(createUserMigration())
238+
migrator.Add(createTeamMigration())
239+
240+
// Run all pending migrations in a transaction
214241
err := migrator.Up()
242+
if err != nil {
243+
panic(err)
244+
}
245+
246+
// Roll back the last batch of migrations
247+
err = migrator.Down()
248+
if err != nil {
249+
panic(err)
250+
}
251+
252+
// Check migration status
253+
status, err := migrator.Status()
254+
if err != nil {
255+
panic(err)
256+
}
257+
for _, s := range status {
258+
fmt.Printf("Migration: %s, Applied: %v, Batch: %d\n",
259+
s.Migration.Name,
260+
s.Applied != nil,
261+
s.Batch)
262+
}
215263
```
216264

265+
#### Migration Features
266+
267+
Theory's migration system supports:
268+
269+
- **Foreign Keys**: Define relationships between tables with ON DELETE and ON UPDATE actions
270+
- **Indexes**: Create and drop indexes, including unique constraints
271+
- **Batch Migrations**: Run multiple migrations as a single transaction
272+
- **Rollback Support**: Easily roll back migrations by batch
273+
- **Migration Status**: Track which migrations have been applied and when
274+
- **Error Handling**: Robust error handling with descriptive messages
275+
- **Validation**: Type validation for SQLite column types
276+
277+
#### Migration Operations
278+
279+
Available migration operations:
280+
281+
- `CreateTable`: Create a new table with columns, foreign keys, and indexes
282+
- `DropTable`: Remove an existing table
283+
- `AddColumn`: Add a new column to an existing table
284+
- `ModifyColumn`: Modify an existing column's properties
285+
- `CreateIndex`: Create a new index on specified columns
286+
- `DropIndex`: Remove an existing index
287+
- `AddForeignKey`: Add a new foreign key constraint
288+
217289
## Error Handling
218290

219291
Theory provides clear error types for common scenarios:

migration/migration.go

+134-3
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ type Operation interface {
2626

2727
// CreateTable operation creates a new table
2828
type CreateTable struct {
29-
Name string
30-
Columns []Column
29+
Name string
30+
Columns []Column
31+
ForeignKeys []ForeignKey
32+
Indexes []Index
3133
}
3234

3335
// Column represents a table column
@@ -40,6 +42,22 @@ type Column struct {
4042
MaxLength int
4143
}
4244

45+
// ForeignKey represents a foreign key constraint
46+
type ForeignKey struct {
47+
Columns []string
48+
RefTable string
49+
RefColumns []string
50+
OnDelete string // CASCADE, SET NULL, RESTRICT, NO ACTION
51+
OnUpdate string // CASCADE, SET NULL, RESTRICT, NO ACTION
52+
}
53+
54+
// Index represents a table index
55+
type Index struct {
56+
Name string
57+
Columns []string
58+
IsUnique bool
59+
}
60+
4361
// DropTable operation drops a table
4462
type DropTable struct {
4563
Name string
@@ -64,6 +82,30 @@ type ModifyColumn struct {
6482
NewColumn Column
6583
}
6684

85+
// CreateIndex operation creates an index
86+
type CreateIndex struct {
87+
Table string
88+
Index Index
89+
}
90+
91+
// DropIndex operation drops an index
92+
type DropIndex struct {
93+
Table string
94+
Name string
95+
}
96+
97+
// AddForeignKey operation adds a foreign key constraint
98+
type AddForeignKey struct {
99+
Table string
100+
ForeignKey ForeignKey
101+
}
102+
103+
// DropForeignKey operation drops a foreign key constraint
104+
type DropForeignKey struct {
105+
Table string
106+
Name string
107+
}
108+
67109
// SQL generates SQL for CreateTable operation
68110
func (op *CreateTable) SQL() string {
69111
var cols []string
@@ -81,7 +123,41 @@ func (op *CreateTable) SQL() string {
81123
}
82124
cols = append(cols, def)
83125
}
84-
return fmt.Sprintf("CREATE TABLE %s (\n\t%s\n)", op.Name, strings.Join(cols, ",\n\t"))
126+
127+
// Add foreign key constraints
128+
for _, fk := range op.ForeignKeys {
129+
def := fmt.Sprintf("FOREIGN KEY (%s) REFERENCES %s (%s)",
130+
strings.Join(fk.Columns, ", "),
131+
fk.RefTable,
132+
strings.Join(fk.RefColumns, ", "))
133+
134+
if fk.OnDelete != "" {
135+
def += fmt.Sprintf(" ON DELETE %s", fk.OnDelete)
136+
}
137+
if fk.OnUpdate != "" {
138+
def += fmt.Sprintf(" ON UPDATE %s", fk.OnUpdate)
139+
}
140+
cols = append(cols, def)
141+
}
142+
143+
sql := fmt.Sprintf("CREATE TABLE %s (\n\t%s\n)", op.Name, strings.Join(cols, ",\n\t"))
144+
145+
// Create indexes
146+
var indexes []string
147+
for _, idx := range op.Indexes {
148+
idxSQL := fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)",
149+
map[bool]string{true: "UNIQUE ", false: ""}[idx.IsUnique],
150+
idx.Name,
151+
op.Name,
152+
strings.Join(idx.Columns, ", "))
153+
indexes = append(indexes, idxSQL)
154+
}
155+
156+
if len(indexes) > 0 {
157+
sql += ";\n" + strings.Join(indexes, ";\n")
158+
}
159+
160+
return sql
85161
}
86162

87163
func (c *CreateTable) Args() []interface{} {
@@ -128,6 +204,61 @@ func (m *ModifyColumn) Args() []interface{} {
128204
return nil
129205
}
130206

207+
// SQL generates SQL for CreateIndex operation
208+
func (c *CreateIndex) SQL() string {
209+
return fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)",
210+
map[bool]string{true: "UNIQUE ", false: ""}[c.Index.IsUnique],
211+
c.Index.Name,
212+
c.Table,
213+
strings.Join(c.Index.Columns, ", "))
214+
}
215+
216+
func (c *CreateIndex) Args() []interface{} {
217+
return nil
218+
}
219+
220+
// SQL generates SQL for DropIndex operation
221+
func (d *DropIndex) SQL() string {
222+
return fmt.Sprintf("DROP INDEX %s", d.Name)
223+
}
224+
225+
func (d *DropIndex) Args() []interface{} {
226+
return nil
227+
}
228+
229+
// SQL generates SQL for AddForeignKey operation
230+
func (a *AddForeignKey) SQL() string {
231+
sql := fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s_%s_fk FOREIGN KEY (%s) REFERENCES %s (%s)",
232+
a.Table,
233+
a.Table,
234+
strings.Join(a.ForeignKey.Columns, "_"),
235+
strings.Join(a.ForeignKey.Columns, ", "),
236+
a.ForeignKey.RefTable,
237+
strings.Join(a.ForeignKey.RefColumns, ", "))
238+
239+
if a.ForeignKey.OnDelete != "" {
240+
sql += fmt.Sprintf(" ON DELETE %s", a.ForeignKey.OnDelete)
241+
}
242+
if a.ForeignKey.OnUpdate != "" {
243+
sql += fmt.Sprintf(" ON UPDATE %s", a.ForeignKey.OnUpdate)
244+
}
245+
246+
return sql
247+
}
248+
249+
func (a *AddForeignKey) Args() []interface{} {
250+
return nil
251+
}
252+
253+
// SQL generates SQL for DropForeignKey operation
254+
func (d *DropForeignKey) SQL() string {
255+
return fmt.Sprintf("ALTER TABLE %s DROP CONSTRAINT %s", d.Table, d.Name)
256+
}
257+
258+
func (d *DropForeignKey) Args() []interface{} {
259+
return nil
260+
}
261+
131262
// NewMigration creates a new migration with the given name
132263
func NewMigration(name string) *Migration {
133264
return &Migration{

0 commit comments

Comments
 (0)