diff --git a/.gitignore b/.gitignore index 8ec89c8..63d0c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ profile example/example cmd/templates.go db/ +install.sh diff --git a/README.md b/README.md index 2143205..e93f63a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +## FYI +This is just a MySQL Patch for [eaigner/hood](https://github.com/eaigner/hood).Let us known if you have any questions. + +- - - + + If you are looking for something more lightweight and flexible, have a look at [jet](http://github.com/eaigner/jet) For questions, suggestions and general topics visit the [group](https://groups.google.com/forum/#!forum/golang-hood). @@ -44,11 +50,11 @@ You can find the documentation over at [GoDoc](http://godoc.org/github.com/eaign If the dialect is registered, you can open the database directly using hd, err := hood.Open("postgres", "user= dbname=") - + or you can pass an existing database and dialect to `hood.New(*sql.DB, hood.Dialect)` hd := hood.New(db, NewPostgres()) - + ## Schemas Schemas can be declared using the following syntax (only for demonstration purposes, would not produce valid SQL since it has 2 primary keys) @@ -112,6 +118,7 @@ To use migrations, you first have to install the `hood` tool. To do that run the go get github.com/eaigner/hood cd $GOPATH/src/github.com/eaigner/hood + cp install.sample.sh install.sh ./install.sh Assuming you have your `$GOPATH/bin` directory in your `PATH`, you can now invoke the hood tool with `hood`. @@ -206,7 +213,7 @@ Besides the `sql:` struct tag, you can specify a `validate:` tag for model valid - `presence` validates that a field is set - `len(min:max)` validates that a `string` field’s length lies within the specified range - - `len(min:)` validates that it has the specified min length, + - `len(min:)` validates that it has the specified min length, - `len(:max)` or max length - `range(min:max)` validates that an `int` value lies in the specific range - `range(min:)` validates that it has the specified min value, diff --git a/base.go b/base.go index 2bfe6f8..c19163e 100644 --- a/base.go +++ b/base.go @@ -171,7 +171,7 @@ func (d *base) QuerySql(hood *Hood) (string, []interface{}) { return hood.substituteMarkers(strings.Join(query, " ")), args } -func (d *base) Insert(hood *Hood, model *Model) (Id, error) { +func (d *base) Insert(hood *Hood, model *Model) (interface{}, error) { sql, args := d.Dialect.InsertSql(model) result, err := hood.Exec(sql, args...) if err != nil { @@ -181,7 +181,7 @@ func (d *base) Insert(hood *Hood, model *Model) (Id, error) { if err != nil { return -1, err } - return Id(id), nil + return id, nil } func (d *base) InsertSql(model *Model) (string, []interface{}) { @@ -200,13 +200,13 @@ func (d *base) InsertSql(model *Model) (string, []interface{}) { return sql, values } -func (d *base) Update(hood *Hood, model *Model) (Id, error) { +func (d *base) Update(hood *Hood, model *Model) (interface{}, error) { sql, args := d.Dialect.UpdateSql(model) _, err := hood.Exec(sql, args...) if err != nil { return -1, err } - return model.Pk.Value.(Id), nil + return model.Pk.Value, nil } func (d *base) UpdateSql(model *Model) (string, []interface{}) { @@ -227,10 +227,10 @@ func (d *base) UpdateSql(model *Model) (string, []interface{}) { return sql, values } -func (d *base) Delete(hood *Hood, model *Model) (Id, error) { +func (d *base) Delete(hood *Hood, model *Model) (interface{}, error) { sql, args := d.Dialect.DeleteSql(model) _, err := hood.Exec(sql, args...) - return args[0].(Id), err + return args[0], err } func (d *base) DeleteSql(model *Model) (string, []interface{}) { @@ -410,12 +410,12 @@ func (d *base) CreateIndexSql(name, table string, unique bool, columns ...string return strings.Join(a, " ") } -func (d *base) DropIndex(hood *Hood, name string) error { - _, err := hood.Exec(d.Dialect.DropIndexSql(name)) +func (d *base) DropIndex(hood *Hood, table_name string, name string) error { + _, err := hood.Exec(d.Dialect.DropIndexSql(table_name, name)) return err } -func (d *base) DropIndexSql(name string) string { +func (d *base) DropIndexSql(table_name, name string) string { return fmt.Sprintf("DROP INDEX %v", d.Dialect.Quote(name)) } diff --git a/cmd/templates/_migration.go b/cmd/templates/_migration.go index 01bffe7..0957fd6 100644 --- a/cmd/templates/_migration.go +++ b/cmd/templates/_migration.go @@ -1,7 +1,7 @@ package main import ( - "github.com/eaigner/hood" + "github.com/xrangers/hood" ) func (m *M) {{.Name}}_{{.Timestamp}}_Up(hd *hood.Hood) { @@ -10,4 +10,4 @@ func (m *M) {{.Name}}_{{.Timestamp}}_Up(hd *hood.Hood) { func (m *M) {{.Name}}_{{.Timestamp}}_Down(hd *hood.Hood) { // TODO: implement -} \ No newline at end of file +} diff --git a/cmd/templates/_runner.go b/cmd/templates/_runner.go index 4be3bb3..f4d7045 100644 --- a/cmd/templates/_runner.go +++ b/cmd/templates/_runner.go @@ -2,7 +2,7 @@ package main import ( "flag" - "github.com/eaigner/hood" + "github.com/xrangers/hood" "io/ioutil" "log" "os/exec" @@ -56,7 +56,7 @@ func init() { type M struct{} type Migrations struct { - Id hood.Id + Id hood.Id `sql:"pk,autoincr"` Current int } diff --git a/dialect.go b/dialect.go index 13edf59..5b06afd 100644 --- a/dialect.go +++ b/dialect.go @@ -35,20 +35,20 @@ type Dialect interface { QuerySql(hood *Hood) (sql string, args []interface{}) // Insert inserts the values in model and returns the inserted rows Id. - Insert(hood *Hood, model *Model) (Id, error) + Insert(hood *Hood, model *Model) (interface{}, error) // InsertSql returns the sql for inserting the passed model. InsertSql(model *Model) (sql string, args []interface{}) // Update updates the values in the specified model and returns the // updated rows Id. - Update(hood *Hood, model *Model) (Id, error) + Update(hood *Hood, model *Model) (interface{}, error) // UpdateSql returns the sql for updating the specified model. UpdateSql(model *Model) (string, []interface{}) // Delete drops the row matching the primary key of model and returns the affected Id. - Delete(hood *Hood, model *Model) (Id, error) + Delete(hood *Hood, model *Model) (interface{}, error) // DeleteSql returns the sql for deleting the row matching model's primary key. DeleteSql(model *Model) (string, []interface{}) @@ -114,10 +114,10 @@ type Dialect interface { CreateIndexSql(name, table string, unique bool, columns ...string) string // DropIndex drops the index. - DropIndex(hood *Hood, name string) error + DropIndex(hood *Hood, table_name string, name string) error // DropIndexSql returns the sql for dropping the index. - DropIndexSql(name string) string + DropIndexSql(table_name, name string) string // KeywordNotNull returns the dialect specific keyword for 'NOT NULL'. KeywordNotNull() string diff --git a/dialects_test.go b/dialects_test.go index eafe357..1e8f6cd 100644 --- a/dialects_test.go +++ b/dialects_test.go @@ -414,7 +414,7 @@ func DoTestSaveDeleteAllAndHooks(t *testing.T, info dialectInfo) { t.Fatal("wrong id", x) } - hd.SaveAll(&models) // force update for hooks test + hd.SaveAll(&models) // force update for hooks test _, err = hd.DeleteAll(&models) if err != nil { diff --git a/hood.go b/hood.go index 149436d..8f2c028 100644 --- a/hood.go +++ b/hood.go @@ -184,6 +184,12 @@ func (field *ModelField) NotNull() bool { return ok } +// NotNull tests if the field is declared as NOT NULL +func (field *ModelField) AutoIncr() bool { + _, ok := field.SqlTags["autoincr"] + return ok +} + // Default returns the default value for the field func (field *ModelField) Default() string { return field.SqlTags["default"] @@ -569,7 +575,7 @@ L: "package db", "", "import (", - "\t\"github.com/eaigner/hood\"", + "\t\"github.com/xrangers/hood\"", } if timeRequired { head = append(head, "\t\"time\"") @@ -759,6 +765,9 @@ func (hood *Hood) FindSql(out interface{}, query string, args ...interface{}) er // Exec executes a raw sql query. func (hood *Hood) Exec(query string, args ...interface{}) (sql.Result, error) { + if hood.dryRun { + return nil, nil + } hood.mutex.Lock() defer hood.mutex.Unlock() defer hood.Reset() @@ -862,11 +871,61 @@ func callModelMethod(f interface{}, methodName string, isPrefix bool) error { return nil } +// INSERT + +func (hood *Hood) Insert(f interface{}) (interface{}, error) { + var ( + id int64 = -1 + err error + ) + model, err := interfaceToModel(f) + if err != nil { + return id, err + } + err = model.Validate() + if err != nil { + return id, err + } + if model.Pk == nil { + panic("no primary key field") + } + err = callModelMethod(f, "BeforeInsert", false) + if err != nil { + return id, err + } + now := time.Now() + for _, f := range model.Fields { + switch f.Value.(type) { + case Created, Updated: + f.Value = now + } + } + ifd, err := hood.Dialect.Insert(hood, model) + id, _ = ifd.(int64) + if err == nil { + err = callModelMethod(f, "AfterInsert", false) + } + + if id != -1 { + // update model id after save + structValue := reflect.Indirect(reflect.ValueOf(f)) + for i := 0; i < structValue.NumField(); i++ { + field := structValue.Field(i) + switch field.Interface().(type) { + case Created: + field.Set(reflect.ValueOf(Created{now})) + } + } + } + return id, err +} + // Save performs an INSERT, or UPDATE if the passed structs Id is set. -func (hood *Hood) Save(f interface{}) (Id, error) { +func (hood *Hood) Save(f interface{}) (interface{}, error) { var ( - id Id = -1 + id int64 = -1 err error + ifd interface{} ) model, err := interfaceToModel(f) if err != nil { @@ -896,7 +955,7 @@ func (hood *Hood) Save(f interface{}) (Id, error) { f.Value = now } } - id, err = hood.Dialect.Update(hood, model) + ifd, err = hood.Dialect.Update(hood, model) if err == nil { err = callModelMethod(f, "AfterUpdate", false) } @@ -911,7 +970,8 @@ func (hood *Hood) Save(f interface{}) (Id, error) { f.Value = now } } - id, err = hood.Dialect.Insert(hood, model) + ifd, err = hood.Dialect.Insert(hood, model) + id, _ = ifd.(int64) if err == nil { err = callModelMethod(f, "AfterInsert", false) } @@ -919,14 +979,12 @@ func (hood *Hood) Save(f interface{}) (Id, error) { if err == nil { err = callModelMethod(f, "AfterSave", false) } - if id != -1 { + if id != -1 || ifd != nil { // update model id after save structValue := reflect.Indirect(reflect.ValueOf(f)) for i := 0; i < structValue.NumField(); i++ { field := structValue.Field(i) switch field.Interface().(type) { - case Id: - field.SetInt(int64(id)) case Updated: field.Set(reflect.ValueOf(Updated{now})) case Created: @@ -936,10 +994,10 @@ func (hood *Hood) Save(f interface{}) (Id, error) { } } } - return id, err + return ifd, err } -func (hood *Hood) doAll(f interface{}, doFunc func(f2 interface{}) (Id, error)) ([]Id, error) { +func (hood *Hood) doAll(f interface{}, doFunc func(f2 interface{}) (interface{}, error)) ([]interface{}, error) { panicMsg := "expected pointer to struct slice *[]struct" if reflect.TypeOf(f).Kind() != reflect.Ptr { panic(panicMsg) @@ -949,7 +1007,7 @@ func (hood *Hood) doAll(f interface{}, doFunc func(f2 interface{}) (Id, error)) } sliceValue := reflect.ValueOf(f).Elem() sliceLen := sliceValue.Len() - ids := make([]Id, 0, sliceLen) + ids := make([]interface{}, 0, sliceLen) for i := 0; i < sliceLen; i++ { id, err := doFunc(sliceValue.Index(i).Addr().Interface()) if err != nil { @@ -961,14 +1019,14 @@ func (hood *Hood) doAll(f interface{}, doFunc func(f2 interface{}) (Id, error)) } // SaveAll performs an INSERT or UPDATE on a slice of structs. -func (hood *Hood) SaveAll(f interface{}) ([]Id, error) { - return hood.doAll(f, func(f2 interface{}) (Id, error) { +func (hood *Hood) SaveAll(f interface{}) ([]interface{}, error) { + return hood.doAll(f, func(f2 interface{}) (interface{}, error) { return hood.Save(f2) }) } // Delete deletes the row matching the specified structs Id. -func (hood *Hood) Delete(f interface{}) (Id, error) { +func (hood *Hood) Delete(f interface{}) (interface{}, error) { model, err := interfaceToModel(f) if err != nil { return -1, err @@ -988,8 +1046,8 @@ func (hood *Hood) Delete(f interface{}) (Id, error) { } // DeleteAll deletes the rows matching the specified struct slice Ids. -func (hood *Hood) DeleteAll(f interface{}) ([]Id, error) { - return hood.doAll(f, func(f2 interface{}) (Id, error) { +func (hood *Hood) DeleteAll(f interface{}) ([]interface{}, error) { + return hood.doAll(f, func(f2 interface{}) (interface{}, error) { return hood.Delete(f2) }) } @@ -1241,7 +1299,7 @@ func (hood *Hood) DropIndex(table interface{}, name string) error { if hood.dryRun { return nil } - return hood.Dialect.DropIndex(hood, name) + return hood.Dialect.DropIndex(hood, tn, name) } func (hood *Hood) substituteMarkers(query string) string { diff --git a/install.sample.sh b/install.sample.sh new file mode 100755 index 0000000..9e88a03 --- /dev/null +++ b/install.sample.sh @@ -0,0 +1,3 @@ +#!/bin/sh +go run cmd/gen/templates.go +go build -o $GOPATH/bin/hood github.com/xrangers/hood/cmd diff --git a/install.sh b/install.sh index 502d790..9e88a03 100755 --- a/install.sh +++ b/install.sh @@ -1,3 +1,3 @@ #!/bin/sh go run cmd/gen/templates.go -go build -o $GOPATH/bin/hood github.com/eaigner/hood/cmd +go build -o $GOPATH/bin/hood github.com/xrangers/hood/cmd diff --git a/mysql.go b/mysql.go index 64e7514..968805c 100644 --- a/mysql.go +++ b/mysql.go @@ -2,8 +2,11 @@ package hood import ( "fmt" + _ "github.com/ziutek/mymysql/godrv" "reflect" + "strings" "time" +_ "github.com/ziutek/mymysql/godrv" ) func init() { @@ -35,7 +38,7 @@ func (d *mysql) ParseBool(value reflect.Value) bool { func (d *mysql) SqlType(f interface{}, size int) string { switch f.(type) { case Id: - return "bigint" + return "int" case time.Time, Created, Updated: return "timestamp" case bool: @@ -55,7 +58,7 @@ func (d *mysql) SqlType(f interface{}, size int) string { if size > 0 && size < 65532 { return fmt.Sprintf("varchar(%d)", size) } - return "longtext" + return "text" } panic("invalid sql type") } @@ -63,3 +66,44 @@ func (d *mysql) SqlType(f interface{}, size int) string { func (d *mysql) KeywordAutoIncrement() string { return "AUTO_INCREMENT" } + +func (d *mysql) DropIndexSql(table_name, name string) string { + return fmt.Sprintf("DROP INDEX %v on %v", d.Quote(name), d.Quote(table_name)) +} + +func (d *mysql) CreateTable(hood *Hood, model *Model) error { + _, err := hood.Exec(d.CreateTableSql(model, false) + " CHARSET = 'utf8'") + return err +} + +func (d *mysql) CreateTableSql(model *Model, ifNotExists bool) string { + a := []string{"CREATE TABLE "} + if ifNotExists { + a = append(a, "IF NOT EXISTS ") + } + a = append(a, d.Quote(model.Table), " ( ") + for i, field := range model.Fields { + b := []string{ + d.Quote(field.Name), + d.SqlType(field.Value, field.Size()), + } + if field.NotNull() { + b = append(b, d.KeywordNotNull()) + } + if x := field.Default(); x != "" { + b = append(b, d.KeywordDefault(x)) + } + if field.PrimaryKey() { + b = append(b, d.KeywordPrimaryKey()) + } + if incKeyword := d.Dialect.KeywordAutoIncrement(); field.AutoIncr() && incKeyword != "" { + b = append(b, incKeyword) + } + a = append(a, strings.Join(b, " ")) + if i < len(model.Fields)-1 { + a = append(a, ", ") + } + } + a = append(a, " )") + return strings.Join(a, "") +} diff --git a/postgres.go b/postgres.go index 5f171c4..1caa338 100644 --- a/postgres.go +++ b/postgres.go @@ -2,7 +2,7 @@ package hood import ( "fmt" - _ "github.com/lib/pq" + // _ "github.com/lib/pq" "strings" "time" ) @@ -46,11 +46,11 @@ func (d *postgres) SqlType(f interface{}, size int) string { panic("invalid sql type") } -func (d *postgres) Insert(hood *Hood, model *Model) (Id, error) { +func (d *postgres) Insert(hood *Hood, model *Model) (interface{}, error) { sql, args := d.Dialect.InsertSql(model) var id int64 err := hood.QueryRow(sql, args...).Scan(&id) - return Id(id), err + return id, err } func (d *postgres) InsertSql(model *Model) (string, []interface{}) { diff --git a/util.go b/util.go index 7a66e0e..93c7b9b 100644 --- a/util.go +++ b/util.go @@ -49,11 +49,9 @@ func columnsMarkersAndValuesForModel(dialect Dialect, model *Model, markerPos *i markers := make([]string, 0, len(columns)) values := make([]interface{}, 0, len(columns)) for _, column := range model.Fields { - if !column.PrimaryKey() { - columns = append(columns, column.Name) - markers = append(markers, dialect.NextMarker(markerPos)) - values = append(values, column.Value) - } + columns = append(columns, column.Name) + markers = append(markers, dialect.NextMarker(markerPos)) + values = append(values, column.Value) } return columns, markers, values }