├── .gitignore ├── qson ├── TODO.md ├── qson_test.go ├── parser.go └── qson.go ├── query_builder.go ├── encoder_test.go ├── cache_test.go ├── ROADMAP.md ├── go.mod ├── examples ├── types.go └── example_test.go ├── types └── slice.go ├── date_test.go ├── util └── string_builder.go ├── TODO.md ├── expr └── expr.go ├── paginator.go ├── schema.go ├── cursor.go ├── LICENSE ├── struct_tag_test.go ├── date.go ├── decoder_test.go ├── dialect.go ├── utils_test.go ├── statement.go ├── struct_tag.go ├── db ├── conn.go └── db.go ├── filter_test.go ├── test ├── model.go ├── postgres_test.go └── mysql_test.go ├── filter.go ├── stmtblr.go ├── cache.go ├── entity.go ├── struct_codec_test.go ├── enc.go ├── table.go ├── utils.go ├── dialect_mysql.go ├── CHANGELOG.md ├── iterator.go ├── encoder.go ├── db.go ├── struct_codec.go ├── dialect_sequel.go ├── decoder.go ├── query.go └── dialect_postgres.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.sql 2 | *.txt 3 | .vscode -------------------------------------------------------------------------------- /qson/TODO.md: -------------------------------------------------------------------------------- 1 | // ParseSlice (for select) 2 | -------------------------------------------------------------------------------- /query_builder.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | // QueryBuilder : 4 | type QueryBuilder struct { 5 | } 6 | -------------------------------------------------------------------------------- /encoder_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import "testing" 4 | 5 | func TestSaveStruct(t *testing.T) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "cloud.google.com/go/datastore" 8 | ) 9 | 10 | // A : 11 | type A struct { 12 | Key *datastore.Key `datastore:"__key__"` 13 | } 14 | 15 | func TestCode(t *testing.T) { 16 | codecByType(reflect.TypeOf(A{})) 17 | } 18 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | - Eager loading 2 | - Support JSON filter in where statement 3 | - Support GeoLocation filter 4 | - Support JSON index 5 | - Enhance func `Table` 6 | - Support data type struct on `Filter` or `Update` 7 | 8 | **Struct Codec** 9 | 10 | - primary key check 11 | - softDelete check 12 | - fix column paths 13 | - Added cache mechanism 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/si3nloong/goloquent 2 | 3 | go 1.16 4 | 5 | require ( 6 | cloud.google.com/go v0.94.1 // indirect 7 | cloud.google.com/go/datastore v1.5.0 8 | github.com/brianvoe/gofakeit v3.18.0+incompatible 9 | github.com/bxcodec/faker v2.0.1+incompatible 10 | github.com/go-sql-driver/mysql v1.6.0 11 | github.com/lib/pq v1.10.3 12 | ) 13 | -------------------------------------------------------------------------------- /examples/types.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "time" 5 | 6 | "cloud.google.com/go/datastore" 7 | "github.com/si3nloong/goloquent" 8 | ) 9 | 10 | // User : 11 | type User struct { 12 | Key *datastore.Key `goloquent:"__key__"` 13 | Name string 14 | Status string 15 | CreatedAt time.Time 16 | Deleted goloquent.SoftDelete 17 | } 18 | -------------------------------------------------------------------------------- /types/slice.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // StringSlice : 4 | type StringSlice []string 5 | 6 | // IndexOf : 7 | func (slice StringSlice) IndexOf(search string) (idx int) { 8 | idx = -1 9 | length := len(slice) 10 | for i := 0; i < length; i++ { 11 | if slice[i] == search { 12 | idx = i 13 | break 14 | } 15 | } 16 | return 17 | } 18 | 19 | // Splice : 20 | func (slice *StringSlice) Splice(idx int) { 21 | *slice = append((*slice)[:idx], (*slice)[idx+1:]...) 22 | } 23 | -------------------------------------------------------------------------------- /date_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestDate(t *testing.T) { 10 | var i struct { 11 | Birthdate Date `json:"birthdate"` 12 | } 13 | if err := json.Unmarshal([]byte(`{"birthdate":"2006-01-02"}`), &i); err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | if err := json.Unmarshal([]byte(`{"birthdate":"2006-21-02"}`), &i); err == nil { 18 | t.Fatal(fmt.Errorf("Unexpected result, Date should error")) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /util/string_builder.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "unsafe" 7 | ) 8 | 9 | var ( 10 | strBldrPool = &sync.Pool{ 11 | New: func() interface{} { 12 | return new(strings.Builder) 13 | }, 14 | } 15 | ) 16 | 17 | // AcquireString : 18 | func AcquireString() *strings.Builder { 19 | return strBldrPool.Get().(*strings.Builder) 20 | } 21 | 22 | // ReleaseString : 23 | func ReleaseString(x *strings.Builder) { 24 | if x != nil { 25 | defer strBldrPool.Put(x) 26 | x.Reset() 27 | } 28 | } 29 | 30 | // UnsafeString : 31 | func UnsafeString(b []byte) string { 32 | return *(*string)(unsafe.Pointer(&b)) 33 | } 34 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ### Pending 2 | 3 | - [ok] Create 4 | - [ok] Upsert 5 | - [ok] Save 6 | - [ok] UpdateMulti 7 | - [ok] Delete Multi 8 | - [ok] ParseQuery 9 | - [ok] Logger 10 | - [ok] SoftDelete 11 | - [ok] - Support option tag shorthand `index` 12 | - [fix] Sort struct properties in sequence (semantic) 13 | - [fix] struct_codec.go (recheck on commit d0ef13f) 14 | - [fix] Selected fields should follow sequence 15 | - [fix] Flatten struct childs values always null 16 | - TestCase (20%) 17 | 18 | // TODO: 19 | 20 | - Filter json 21 | - Filter geolocation (structure with spatial or json?) 22 | - Added cache mechanism 23 | - index name?? 24 | 25 | Bugs : 26 | Flatten []struct childs values always null 27 | Update with limit (POSTGRES) [ok] 28 | -------------------------------------------------------------------------------- /expr/expr.go: -------------------------------------------------------------------------------- 1 | package expr 2 | 3 | import "reflect" 4 | 5 | // F : 6 | type F struct { 7 | Name string 8 | Values []reflect.Value 9 | } 10 | 11 | const ( 12 | Ascending Direction = iota 13 | Descending 14 | ) 15 | 16 | type Direction int 17 | 18 | type Sort struct { 19 | Name string 20 | Direction Direction 21 | } 22 | 23 | func Field(name string, vals interface{}) (f F) { 24 | v := reflect.ValueOf(vals) 25 | k := v.Kind() 26 | if k != reflect.Slice && k != reflect.Array { 27 | panic("expr: invalid data type for Field") 28 | } 29 | f.Name = name 30 | length := v.Len() 31 | if length < 1 { 32 | panic("expr: empty values") 33 | } 34 | for i := 0; i < length; i++ { 35 | f.Values = append(f.Values, v.Index(i)) 36 | } 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /paginator.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | const ( 4 | defaultLimit = 100 5 | ) 6 | 7 | // Pagination : 8 | type Pagination struct { 9 | query *Query 10 | Cursor string 11 | Limit uint 12 | count uint 13 | nxtCursor Cursor 14 | } 15 | 16 | // SetQuery : 17 | func (p *Pagination) SetQuery(q *Query) { 18 | if q == nil { 19 | return 20 | } 21 | p.query = q 22 | } 23 | 24 | // Reset : reset all the value in pagination to default value 25 | func (p *Pagination) Reset() { 26 | pp := new(Pagination) 27 | pp.Limit = defaultLimit 28 | *p = *pp 29 | } 30 | 31 | // NextCursor : next record set cursor 32 | func (p *Pagination) NextCursor() string { 33 | return p.nxtCursor.String() 34 | } 35 | 36 | // Count : record count in this pagination record set 37 | func (p *Pagination) Count() uint { 38 | return p.count 39 | } 40 | -------------------------------------------------------------------------------- /schema.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import "reflect" 4 | 5 | var ( 6 | utf8CharSet = CharSet{"utf8", "utf8_unicode_ci"} 7 | utf8mb4CharSet = CharSet{"utf8mb4", "utf8mb4_unicode_ci"} 8 | latin2CharSet = CharSet{"latin2", "latin2_general_ci"} 9 | latin1CharSet = CharSet{"latin1", "latin1_bin"} 10 | ) 11 | 12 | // OmitDefault : 13 | type OmitDefault interface{} 14 | 15 | // CharSet : 16 | type CharSet struct { 17 | Encoding string 18 | Collation string 19 | } 20 | 21 | // Schema : 22 | type Schema struct { 23 | Name string 24 | DataType string 25 | DefaultValue interface{} 26 | IsUnsigned bool 27 | IsNullable bool 28 | IsIndexed bool 29 | CharSet 30 | } 31 | 32 | // IsOmitEmpty : 33 | func (s Schema) IsOmitEmpty() bool { 34 | return reflect.TypeOf(s.DefaultValue) == reflect.TypeOf(OmitDefault(nil)) 35 | } 36 | -------------------------------------------------------------------------------- /cursor.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | 9 | "cloud.google.com/go/datastore" 10 | ) 11 | 12 | // Cursor : 13 | type Cursor struct { 14 | cc []byte 15 | Signature string `json:"signature"` 16 | Key *datastore.Key `json:"next"` 17 | } 18 | 19 | // String : 20 | func (c Cursor) String() string { 21 | if c.cc == nil { 22 | return "" 23 | } 24 | return strings.TrimRight(base64.URLEncoding.EncodeToString(c.cc), "=") 25 | } 26 | 27 | // DecodeCursor : 28 | func DecodeCursor(c string) (Cursor, error) { 29 | if c == "" { 30 | return Cursor{}, nil 31 | } 32 | if n := len(c) % 4; n != 0 { 33 | c += strings.Repeat("=", 4-n) 34 | } 35 | b, err := base64.URLEncoding.DecodeString(c) 36 | if err != nil { 37 | return Cursor{}, fmt.Errorf("goloquent: invalid cursor") 38 | } 39 | cc := new(Cursor) 40 | cc.cc = b 41 | if err := json.Unmarshal(b, cc); err != nil { 42 | return Cursor{}, fmt.Errorf("goloquent: invalid cursor") 43 | } 44 | return *cc, nil 45 | } 46 | -------------------------------------------------------------------------------- /qson/qson_test.go: -------------------------------------------------------------------------------- 1 | package qson 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | type testUser struct { 9 | Email string `json:"email"` 10 | private string 11 | Skip string `json:"-"` 12 | Credential struct { 13 | Hash string `json:"hash"` 14 | Salt string `json:"salt"` 15 | Password string `json:"password"` 16 | } `json:"credential"` 17 | Address []struct { 18 | Line1 string `json:"line1"` 19 | } `json:"address"` 20 | Status string `json:"status"` 21 | } 22 | 23 | // QSON : Query JSON 24 | 25 | func TestProperty(t *testing.T) { 26 | var i testUser 27 | parser, err := New(i) 28 | if err != nil { 29 | return 30 | } 31 | 32 | fields, err := parser.Parse([]byte(`{ 33 | "email":"sianloong90@gmail.com", 34 | "status":{"$in":["OK","FAILED"]} 35 | }`)) 36 | for _, ff := range fields { 37 | fmt.Println(ff.Name(), ff.Operator(), ff.Value()) 38 | } 39 | sorts, err := parser.ParseSort([]string{ 40 | "-credential.password", 41 | "email", 42 | }) 43 | fmt.Println(sorts, err) 44 | 45 | for _, ss := range sorts { 46 | fmt.Println(ss.Name(), ss.IsAscending()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SianLoong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /struct_tag_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestStructTagWithSkip(t *testing.T) { 10 | var i testUser 11 | vt := reflect.ValueOf(i).Type() 12 | tag := newTag(vt.Field(0)) 13 | if !tag.isSkip() { 14 | t.Fatal(fmt.Sprintf("Expected tag have skip, but end up with %v", tag.name)) 15 | } 16 | 17 | } 18 | 19 | func TestStructTagWithCharSet(t *testing.T) { 20 | var i testUser 21 | vt := reflect.ValueOf(i).Type() 22 | tag := newTag(vt.Field(2)) 23 | if tag.Get("charset") != "latin1" { 24 | t.Fatal(fmt.Sprintf("Expected tag have %q charset, but end up with %v", "latin1", tag.Get("charset"))) 25 | } 26 | } 27 | 28 | func TestStructTagWithLongText(t *testing.T) { 29 | var i testUser 30 | vt := reflect.ValueOf(i).Type() 31 | tag := newTag(vt.Field(5)) 32 | if !tag.IsLongText() { 33 | t.Fatal("Expected tag have longtext, but end up with no longtext") 34 | } 35 | } 36 | 37 | func TestStructTagWithIndex(t *testing.T) { 38 | var i testUser 39 | vt := reflect.ValueOf(i).Type() 40 | tag := newTag(vt.Field(3)) 41 | if !tag.IsIndex() { 42 | t.Fatal("Expected tag have index, but end up with noindex") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /date.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "time" 8 | ) 9 | 10 | var typeOfDate = reflect.TypeOf(Date(time.Time{})) 11 | 12 | // Date : 13 | type Date time.Time 14 | 15 | // UnmarshalJSON : 16 | func (d *Date) UnmarshalJSON(b []byte) error { 17 | if b == nil || b2s(b) == "null" { 18 | return nil 19 | } 20 | if !regexp.MustCompile(`^\"\d{4}\-\d{2}\-\d{2}\"$`).Match(b) { 21 | return fmt.Errorf("goloquent: invalid date value %q", b) 22 | } 23 | dt, err := time.Parse("2006-01-02", escape(b)) 24 | if err != nil { 25 | return err 26 | } 27 | *d = Date(dt) 28 | return nil 29 | } 30 | 31 | // MarshalJSON : 32 | func (d Date) MarshalJSON() ([]byte, error) { 33 | return []byte(`"` + time.Time(d).Format("2006-01-02") + `"`), nil 34 | } 35 | 36 | // UnmarshalText : 37 | func (d *Date) UnmarshalText(b []byte) error { 38 | dt, err := time.Parse("2006-01-02", b2s(b)) 39 | if err != nil { 40 | return err 41 | } 42 | *d = Date(dt) 43 | return nil 44 | } 45 | 46 | // MarshalText : 47 | func (d Date) MarshalText() ([]byte, error) { 48 | return []byte(time.Time(d).Format("2006-01-02")), nil 49 | } 50 | 51 | // String : 52 | func (d Date) String() string { 53 | return time.Time(d).Format("2006-01-02") 54 | } 55 | -------------------------------------------------------------------------------- /decoder_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestEscape(t *testing.T) { 9 | txt := "hello word" 10 | if escape([]byte(fmt.Sprintf(`"%s"`, txt))) != txt { 11 | t.Errorf(errUnexpectedResult, "escape") 12 | } 13 | } 14 | 15 | func TestInitStruct(t *testing.T) { 16 | 17 | } 18 | 19 | func TestIterator(t *testing.T) { 20 | // var i testUser 21 | 22 | // email := `test@hotmail.com` 23 | // it := &Iterator{} 24 | // it.put(0, "Email", []byte(email)) 25 | // it.put(0, "Age", []byte(`100`)) 26 | // it.put(0, "IsSingle", nil) 27 | 28 | // if err := it.Scan(&i); err != nil { 29 | // t.Errorf("") 30 | // } 31 | 32 | // if i.Email != email { 33 | // t.Error() 34 | // } 35 | // fmt.Println(i) 36 | } 37 | 38 | func TestValueToInterface(t *testing.T) { 39 | // var i testUser 40 | // vt := reflect.TypeOf(i) 41 | // vv, _ := valueToInterface(vt.Field(0).Type, []byte(`178330303`), true) 42 | // if vv != "178330303" { 43 | // log.Fatal(fmt.Sprintf("Unexpected value using valueToInterface %v", vv)) 44 | // } 45 | 46 | // vv, _ = valueToInterface(vt.Field(2).Type, []byte(`Joe`), true) 47 | // if vv != "Joe" { 48 | // log.Fatal(fmt.Sprintf("Unexpected value using valueToInterface %v", vv)) 49 | // } 50 | } 51 | 52 | func TestLoadStructField(t *testing.T) {} 53 | 54 | func TestLoadField(t *testing.T) { 55 | 56 | } 57 | -------------------------------------------------------------------------------- /dialect.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "reflect" 7 | ) 8 | 9 | // Dialect : 10 | type Dialect interface { 11 | Open(c Config) (*sql.DB, error) 12 | SetDB(db Client) 13 | GetTable(ns string) string 14 | Version() (ver string) 15 | CurrentDB() (n string) 16 | Quote(n string) string 17 | Bind(i uint) string 18 | FilterJSON(f Filter) (s string, args []interface{}, err error) 19 | JSONMarshal(i interface{}) (b json.RawMessage) 20 | Value(v interface{}) string 21 | GetSchema(c Column) []Schema 22 | DataType(s Schema) string 23 | HasTable(tb string) bool 24 | HasIndex(tb, idx string) bool 25 | GetColumns(tb string) (cols []string) 26 | GetIndexes(tb string) (idxs []string) 27 | CreateTable(tb string, cols []Column) error 28 | AlterTable(tb string, cols []Column, unsafe bool) error 29 | OnConflictUpdate(tb string, cols []string) string 30 | UpdateWithLimit() bool 31 | ReplaceInto(src, dst string) error 32 | } 33 | 34 | var ( 35 | dialects = make(map[string]Dialect) 36 | ) 37 | 38 | // RegisterDialect : 39 | func RegisterDialect(driver string, d Dialect) { 40 | dialects[driver] = d 41 | } 42 | 43 | // GetDialect : 44 | func GetDialect(driver string) (d Dialect, isValid bool) { 45 | d, isValid = dialects[driver] 46 | if isValid { 47 | // Clone a new dialect 48 | d = reflect.New(reflect.TypeOf(d).Elem()).Interface().(Dialect) 49 | } 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "testing" 5 | 6 | "cloud.google.com/go/datastore" 7 | ) 8 | 9 | const errUnexpectedResult = `function %q produce unexpected result` 10 | 11 | func TestUtils(t *testing.T) { 12 | // if isNameKey("Table1,'email'/Table2,9100222") { 13 | // t.Errorf("invalid expected result") 14 | // } 15 | 16 | // if !isNameKey("Table1,'email'/Table2,9100222") { 17 | // t.Errorf("invalid expected result") 18 | // } 19 | 20 | idKey := datastore.IDKey("Kind", int64(192839128), nil) 21 | if stringifyKey(idKey) != "Kind,192839128" { 22 | t.Errorf(errUnexpectedResult, "stringifyKey") 23 | } 24 | if StringKey(idKey) != "192839128" { 25 | t.Errorf(errUnexpectedResult, "StringKey") 26 | } 27 | 28 | nameKey := datastore.NameKey("Kind", "test@hotmail.com", nil) 29 | if stringifyKey(nameKey) != "Kind,'test@hotmail.com'" { 30 | t.Errorf(errUnexpectedResult, "stringifyKey") 31 | } 32 | if StringKey(nameKey) != "test@hotmail.com" { 33 | t.Errorf(errUnexpectedResult, "StringKey") 34 | } 35 | 36 | symbolKey := datastore.NameKey("Kind", `VEknBYnisrgS0w3pjiibNBmOhU9HgTWpSDQtg7w/b0recIBLkjp+lf5RRw97zeHH`, nil) 37 | if stringifyKey(symbolKey) != "Kind,'VEknBYnisrgS0w3pjiibNBmOhU9HgTWpSDQtg7w%2Fb0recIBLkjp+lf5RRw97zeHH'" { 38 | t.Errorf(errUnexpectedResult, "StringKey") 39 | } 40 | } 41 | 42 | func TestEscapeSingleQuote(t *testing.T) { 43 | str := `message is 'helllo's world'` 44 | if escapeSingleQuote(str) != `message is ''helllo''s world''` { 45 | t.Fatal(`Unexpected error occur in "escapeSingleQuote"`) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /statement.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type stmt struct { 11 | statement *bytes.Buffer 12 | arguments []interface{} 13 | } 14 | 15 | func (s stmt) string() string { 16 | return s.statement.String() 17 | } 18 | 19 | func (s stmt) isZero() bool { 20 | return !(s.statement.Len() > 0) 21 | } 22 | 23 | type replacer interface { 24 | Bind(uint) string 25 | Value(interface{}) string 26 | } 27 | 28 | // Stmt : 29 | type Stmt struct { 30 | stmt 31 | crud string 32 | replacer replacer 33 | startTime time.Time 34 | endTime time.Time 35 | Result sql.Result 36 | } 37 | 38 | func (s *Stmt) startTrace() { 39 | s.startTime = time.Now().UTC() 40 | } 41 | 42 | func (s *Stmt) stopTrace() { 43 | s.endTime = time.Now().UTC() 44 | } 45 | 46 | // TimeElapse : 47 | func (s Stmt) TimeElapse() time.Duration { 48 | return s.endTime.Sub(s.startTime) 49 | } 50 | 51 | // Raw : 52 | func (s *Stmt) Raw() string { 53 | buf := new(bytes.Buffer) 54 | if len(s.arguments) <= 0 { 55 | return s.string() 56 | } 57 | arr := strings.Split(s.string(), variable) 58 | for i := 0; i < len(arr); i++ { 59 | str := arr[i] + s.replacer.Bind(uint(i+1)) 60 | if i >= len(arr)-1 { 61 | str = arr[i] 62 | } 63 | buf.WriteString(str) 64 | } 65 | return buf.String() 66 | } 67 | 68 | // String : 69 | func (s *Stmt) String() string { 70 | buf := new(bytes.Buffer) 71 | arr := strings.Split(s.string(), variable) 72 | for i, aa := range s.arguments { 73 | str := arr[i] + s.replacer.Value(aa) 74 | buf.WriteString(str) 75 | } 76 | buf.WriteString(arr[len(arr)-1]) 77 | return buf.String() 78 | } 79 | 80 | // Arguments : 81 | func (s Stmt) Arguments() []interface{} { 82 | return s.arguments 83 | } 84 | -------------------------------------------------------------------------------- /struct_tag.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | type tag struct { 10 | name string 11 | options map[string]bool 12 | others map[string]string 13 | } 14 | 15 | // TODO: Eager loading tag 16 | 17 | func newTag(sf reflect.StructField) tag { 18 | name := sf.Name 19 | 20 | t := strings.TrimSpace(sf.Tag.Get("goloquent")) 21 | paths := strings.Split(t, ",") 22 | if strings.TrimSpace(paths[0]) != "" { 23 | name = paths[0] 24 | } 25 | 26 | options := map[string]bool{ 27 | "index": false, 28 | "flatten": false, 29 | "omitempty": false, 30 | "unsigned": false, 31 | "longtext": false, 32 | } 33 | 34 | others := make(map[string]string) 35 | paths = paths[1:] 36 | for _, k := range paths { 37 | k = strings.ToLower(k) 38 | if _, isValid := options[k]; isValid { 39 | options[k] = true 40 | } else { 41 | rgx := regexp.MustCompile(`(datatype|charset|collate)\=.+`) 42 | if rgx.MatchString(k) { 43 | rgx = regexp.MustCompile(`(\w+)=(.+)`) 44 | result := rgx.FindStringSubmatch(k) 45 | others[result[1]] = result[2] 46 | } 47 | } 48 | } 49 | 50 | return tag{ 51 | name: name, 52 | options: options, 53 | others: others, 54 | } 55 | } 56 | 57 | func (t tag) Get(k string) string { 58 | return t.others[k] 59 | } 60 | 61 | func (t tag) isPrimaryKey() bool { 62 | return t.name == keyFieldName 63 | } 64 | 65 | func (t tag) isSkip() bool { 66 | return t.name == "-" 67 | } 68 | 69 | func (t tag) isFlatten() bool { 70 | return t.options["flatten"] 71 | } 72 | 73 | func (t tag) IsIndex() bool { 74 | return t.options["index"] 75 | } 76 | 77 | func (t tag) IsOmitEmpty() bool { 78 | return t.options["omitempty"] 79 | } 80 | 81 | func (t tag) IsUnsigned() bool { 82 | return t.options["unsigned"] 83 | } 84 | 85 | func (t tag) IsLongText() bool { 86 | return t.options["longtext"] 87 | } 88 | -------------------------------------------------------------------------------- /db/conn.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "sync" 7 | 8 | "github.com/si3nloong/goloquent" 9 | ) 10 | 11 | var ( 12 | defaultDB *goloquent.DB 13 | connPool *sync.Map // database connection pools 14 | ) 15 | 16 | func init() { 17 | connPool = new(sync.Map) 18 | } 19 | 20 | // Config : 21 | type Config struct { 22 | Username string 23 | Password string 24 | Host string 25 | Port string 26 | Database string 27 | UnixSocket string 28 | TLSConfig string 29 | CharSet *goloquent.CharSet 30 | Logger goloquent.LogHandler 31 | Native goloquent.NativeHandler 32 | } 33 | 34 | // Open : 35 | func Open(driver string, conf Config) (*goloquent.DB, error) { 36 | driver = strings.TrimSpace(strings.ToLower(driver)) 37 | dialect, ok := goloquent.GetDialect(driver) 38 | if !ok { 39 | panic(fmt.Errorf("goloquent: unsupported database driver %q", driver)) 40 | } 41 | 42 | pool := make(map[string]*goloquent.DB) 43 | if p, ok := connPool.Load(driver); ok { 44 | pool = p.(map[string]*goloquent.DB) 45 | } 46 | 47 | config := goloquent.Config{ 48 | Username: conf.Username, 49 | Password: conf.Password, 50 | Host: conf.Host, 51 | Port: conf.Port, 52 | TLSConfig: conf.TLSConfig, 53 | Database: conf.Database, 54 | UnixSocket: conf.UnixSocket, 55 | CharSet: conf.CharSet, 56 | Logger: conf.Logger, 57 | } 58 | config.Normalize() 59 | conn, err := dialect.Open(config) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | if conf.Native != nil { 65 | conf.Native(conn) 66 | } 67 | 68 | if err := conn.Ping(); err != nil { 69 | return nil, fmt.Errorf("goloquent: %s server has not response", driver) 70 | } 71 | 72 | db := goloquent.NewDB(driver, *config.CharSet, conn, dialect, conf.Logger) 73 | pool[conf.Database] = db 74 | connPool.Store(driver, pool) 75 | // Override defaultDB whenever we initialise a new connection 76 | defaultDB = db 77 | return db, nil 78 | } 79 | -------------------------------------------------------------------------------- /filter_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNormalizeValue(t *testing.T) { 9 | var ( 10 | i interface{} 11 | err error 12 | ) 13 | 14 | i64 := int64(16) 15 | i, err = normalizeValue(int8(16)) 16 | if err != nil { 17 | t.Fatalf("Unexpected err, %v", err) 18 | } 19 | if !reflect.DeepEqual(i, i64) { 20 | t.Fatal("Unexpected error on normalize integer") 21 | } 22 | i8 := int8(16) 23 | i, err = normalizeValue(&i8) 24 | if err != nil { 25 | t.Fatalf("Unexpected err, %v", err) 26 | } 27 | if !reflect.DeepEqual(reflect.Indirect(reflect.ValueOf(i)).Interface(), i64) { 28 | t.Fatal("Unexpected error on normalize integer") 29 | } 30 | 31 | i, err = normalizeValue(int16(16)) 32 | if err != nil { 33 | t.Fatalf("Unexpected err, %v", err) 34 | } 35 | if !reflect.DeepEqual(i, i64) { 36 | t.Fatal("Unexpected error on normalize integer") 37 | } 38 | 39 | i, err = normalizeValue(int(16)) 40 | if err != nil { 41 | t.Fatalf("Unexpected err, %v", err) 42 | } 43 | if !reflect.DeepEqual(i, i64) { 44 | t.Fatal("Unexpected error on normalize integer") 45 | } 46 | 47 | i, err = normalizeValue(int32(16)) 48 | if err != nil { 49 | t.Fatalf("Unexpected err, %v", err) 50 | } 51 | if !reflect.DeepEqual(i, i64) { 52 | t.Fatal("Unexpected error on normalize integer") 53 | } 54 | 55 | ui64 := uint64(55) 56 | i, err = normalizeValue(uint(55)) 57 | if err != nil { 58 | t.Fatalf("Unexpected err, %v", err) 59 | } 60 | if !reflect.DeepEqual(i, ui64) { 61 | t.Fatal("Unexpected error on normalize unsigned integer") 62 | } 63 | 64 | i, err = normalizeValue(uint8(55)) 65 | if err != nil { 66 | t.Fatalf("Unexpected err, %v", err) 67 | } 68 | if !reflect.DeepEqual(i, ui64) { 69 | t.Fatal("Unexpected error on normalize unsigned integer") 70 | } 71 | 72 | i, err = normalizeValue(uint16(55)) 73 | if err != nil { 74 | t.Fatalf("Unexpected err, %v", err) 75 | } 76 | if !reflect.DeepEqual(i, ui64) { 77 | t.Fatal("Unexpected error on normalize unsigned integer") 78 | } 79 | 80 | i, err = normalizeValue(uint32(55)) 81 | if err != nil { 82 | t.Fatalf("Unexpected err, %v", err) 83 | } 84 | if !reflect.DeepEqual(i, ui64) { 85 | t.Fatal("Unexpected error on normalize unsigned integer") 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /examples/example_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | "time" 8 | 9 | "cloud.google.com/go/datastore" 10 | "github.com/brianvoe/gofakeit" 11 | "github.com/si3nloong/goloquent" 12 | "github.com/si3nloong/goloquent/db" 13 | "github.com/si3nloong/goloquent/expr" 14 | 15 | // "database/sql" 16 | 17 | _ "github.com/go-sql-driver/mysql" 18 | ) 19 | 20 | func TestExamples(t *testing.T) { 21 | 22 | // mysql.RegisterTLSConfig("custom", &tls.Config{}) 23 | conn, err := db.Open("mysql", db.Config{ 24 | Username: "root", 25 | Password: "abcd1234", 26 | Host: "localhost", 27 | Port: "3306", 28 | // TLSConfig: "", 29 | Database: "goloquent", 30 | Logger: func(stmt *goloquent.Stmt) { 31 | log.Println(stmt.TimeElapse()) // elapse time in time.Duration 32 | log.Println(stmt.String()) // Sql string without any ? 33 | log.Println(stmt.Raw()) // Sql prepare statement 34 | log.Println(stmt.Arguments()) // Sql prepare statement's arguments 35 | log.Println(fmt.Sprintf("[%.3fms] %s", stmt.TimeElapse().Seconds()*1000, stmt.String())) 36 | }, 37 | }) 38 | // defer conn.Close() 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | db.Migrate(new(User)) 44 | db.Truncate("User") 45 | u := new(User) 46 | err = db.MatchAgainst([]string{"Name", "Username"}, "value", "value2").Find(datastore.IDKey("test", 100, nil), u) 47 | log.Println(err) 48 | 49 | users := [...]User{ 50 | newUser(), 51 | newUser(), 52 | newUser(), 53 | newUser(), 54 | newUser(), 55 | } 56 | db.Create(&users) 57 | usrs := []User{} 58 | db.NewQuery().OrderBy( 59 | expr.Field("Status", []string{ 60 | "A", "B", "C", 61 | }), 62 | "-CreatedAt", 63 | ).Get(&usrs) 64 | 65 | query := db.NewQuery().OrderBy( 66 | "-CreatedAt", 67 | ) 68 | pg := &goloquent.Pagination{Limit: 1} 69 | err = query.Paginate(pg, &usrs) 70 | 71 | pg.Cursor = pg.NextCursor() 72 | err = query.Paginate(pg, &usrs) 73 | 74 | pg.Cursor = pg.NextCursor() 75 | err = query.Paginate(pg, &usrs) 76 | 77 | log.Println(err) 78 | log.Println(usrs) 79 | // db.Create() 80 | log.Println(conn) 81 | } 82 | 83 | func newUser() (u User) { 84 | u.Name = "" 85 | u.CreatedAt = time.Now() 86 | u.Status = gofakeit.RandString([]string{ 87 | "A", 88 | "B", 89 | "C", 90 | }) 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /test/model.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "cloud.google.com/go/datastore" 9 | "github.com/bxcodec/faker" 10 | "github.com/si3nloong/goloquent" 11 | ) 12 | 13 | var ( 14 | my *goloquent.DB 15 | nameKey = datastore.NameKey("Name", "hIL0O7zfZP", nil) 16 | symbolKey = datastore.NameKey("Name", "VEknB=YnisrgS0w'9Hg,TWpSQtg7w/b0recIBLkjp+lf", nil) 17 | idKey = datastore.IDKey("ID", int64(5116745034367558422), nil) 18 | ) 19 | 20 | // Address : 21 | type Address struct { 22 | Line1 string 23 | Line2 string 24 | Country string 25 | PostCode uint 26 | Region struct { 27 | TimeZone time.Time 28 | Keys []*datastore.Key `goloquent:"keys"` 29 | IndependantDate goloquent.Date 30 | CountryCode string `goloquent:"regionCode"` 31 | Geolocation datastore.GeoPoint `goloquent:"geo"` 32 | } `goloquent:"region"` 33 | } 34 | 35 | // Email : 36 | type Email string 37 | 38 | // User : 39 | type User struct { 40 | ID int64 41 | Key *datastore.Key `goloquent:"__key__" faker:"-"` 42 | Username string `faker:"username"` 43 | Name string `goloquent:",charset=utf8,collate=utf8_bin" faker:"name"` 44 | Password string `goloquent:",datatype=varchar(100)" faker:"password"` 45 | Nickname *string 46 | Age uint8 `` 47 | CreditLimit float64 `goloquent:",unsigned"` 48 | Address Address `faker:"-"` 49 | Birthdate goloquent.Date `faker:"-"` 50 | PrimaryEmail Email `faker:"email"` 51 | Emails []string `goloquent:""` 52 | Information json.RawMessage `faker:"-"` 53 | ExtraInformation *json.RawMessage `faker:"-"` 54 | Status string `goloquent:",charset=latin1" faker:""` 55 | UpdatedDateTime time.Time 56 | DeleteDateTime goloquent.SoftDelete `faker:"-"` 57 | } 58 | 59 | // TempUser : 60 | type TempUser struct { 61 | User 62 | } 63 | 64 | func getFakeUser() *User { 65 | u := new(User) 66 | faker.FakeData(u) 67 | date, _ := time.Parse("2006-01-02", "1957-08-31") 68 | u.Username = fmt.Sprintf("%d", time.Now().UnixNano()) 69 | u.Birthdate = goloquent.Date(time.Now()) 70 | u.Information = json.RawMessage(`{"nickname":"John Doe"}`) 71 | u.Address.Line1 = "7812, Jalan Section 22" 72 | u.Address.Region.IndependantDate = goloquent.Date(date) 73 | u.Emails = []string{"support@hotmail.com", "support@gmail.com"} 74 | u.Status = "ACTIVE" 75 | return u 76 | } 77 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | 8 | "cloud.google.com/go/datastore" 9 | ) 10 | 11 | // Filter : 12 | type Filter struct { 13 | field string 14 | operator operator 15 | value interface{} 16 | isJSON bool 17 | raw string 18 | } 19 | 20 | // Field : 21 | func (f Filter) Field() string { 22 | return f.field 23 | } 24 | 25 | // IsJSON : 26 | func (f Filter) IsJSON() bool { 27 | return f.isJSON 28 | } 29 | 30 | // JSON : 31 | type JSON struct { 32 | } 33 | 34 | // JSON : 35 | func (f Filter) JSON() *JSON { 36 | return &JSON{} 37 | } 38 | 39 | // Interface : 40 | func (f *Filter) Interface() (interface{}, error) { 41 | v, err := normalizeValue(f.value) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return interfaceToValue(v) 46 | } 47 | 48 | // final data type : 49 | // string, bool, uint64, int64, float64, []byte 50 | // time.Time, *datastore.Key, datastore.GeoPoint, []interface{} 51 | func normalizeValue(val interface{}) (interface{}, error) { 52 | if val == nil { 53 | return nil, nil 54 | } 55 | v := reflect.ValueOf(val) 56 | var it interface{} 57 | t := v.Type() 58 | switch vi := v.Interface().(type) { 59 | case *datastore.Key: 60 | if vi == nil { 61 | return nil, nil 62 | } 63 | it = vi 64 | case datastore.GeoPoint: 65 | it = geoLocation{vi.Lat, vi.Lng} 66 | case time.Time: 67 | it = vi 68 | case Date: 69 | it = vi 70 | default: 71 | switch t.Kind() { 72 | case reflect.String: 73 | it = v.String() 74 | case reflect.Bool: 75 | it = v.Bool() 76 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 77 | it = v.Uint() 78 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 79 | it = v.Int() 80 | case reflect.Float32, reflect.Float64: 81 | it = v.Float() 82 | case reflect.Slice, reflect.Array: 83 | if t.Elem().Kind() == reflect.Uint8 { 84 | return v.Bytes(), nil 85 | } 86 | arr := make([]interface{}, 0, v.Len()) 87 | for i := 0; i < v.Len(); i++ { 88 | vv := v.Index(i) 89 | var vi, err = normalizeValue(vv.Interface()) 90 | if err != nil { 91 | return vi, err 92 | } 93 | arr = append(arr, vi) 94 | } 95 | it = arr 96 | case reflect.Ptr: 97 | if v.IsNil() { 98 | return nil, nil 99 | } 100 | var val, err = normalizeValue(v.Elem().Interface()) 101 | if err != nil { 102 | return nil, err 103 | } 104 | vi := reflect.ValueOf(val).Interface() 105 | it = &vi 106 | 107 | default: 108 | return nil, fmt.Errorf("goloquent: unsupported data type %v", t) 109 | } 110 | } 111 | 112 | return it, nil 113 | } 114 | -------------------------------------------------------------------------------- /stmtblr.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "reflect" 8 | "sync" 9 | 10 | "github.com/si3nloong/goloquent/expr" 11 | ) 12 | 13 | var stmtRegistry *StmtRegistry 14 | 15 | type Writer interface { 16 | io.Writer 17 | io.StringWriter 18 | io.ByteWriter 19 | } 20 | 21 | type writerFunc func(Writer, reflect.Value) ([]interface{}, error) 22 | 23 | type StmtRegistry struct { 24 | sync.Mutex 25 | typeEncoders map[reflect.Type]writerFunc 26 | kindEncoders map[reflect.Kind]writerFunc 27 | } 28 | 29 | func init() { 30 | stmtRegistry = NewStmtRegistry() 31 | stmtRegistry.SetDefaultEncoders() 32 | } 33 | 34 | func NewStmtRegistry() *StmtRegistry { 35 | return &StmtRegistry{ 36 | typeEncoders: make(map[reflect.Type]writerFunc), 37 | kindEncoders: make(map[reflect.Kind]writerFunc), 38 | } 39 | } 40 | 41 | func (r *StmtRegistry) SetDefaultEncoders() { 42 | enc := DefaultStmtEncoder{registry: defaultRegistry} 43 | r.SetTypeEncoder(reflect.TypeOf(expr.F{}), enc.encodeField) 44 | r.SetTypeEncoder(reflect.TypeOf(expr.Sort{}), enc.encodeSort) 45 | r.SetKindEncoder(reflect.String, enc.encodeString) 46 | } 47 | 48 | func (r *StmtRegistry) SetTypeEncoder(t reflect.Type, f writerFunc) { 49 | r.Lock() 50 | defer r.Unlock() 51 | r.typeEncoders[t] = f 52 | } 53 | 54 | func (r *StmtRegistry) SetKindEncoder(k reflect.Kind, f writerFunc) { 55 | r.Lock() 56 | defer r.Unlock() 57 | r.kindEncoders[k] = f 58 | } 59 | 60 | func (r *StmtRegistry) BuildStatement(w Writer, v reflect.Value) ([]interface{}, error) { 61 | if encoder, isOk := r.typeEncoders[v.Type()]; isOk { 62 | return encoder(w, v) 63 | } 64 | if encoder, isOk := r.kindEncoders[v.Kind()]; isOk { 65 | return encoder(w, v) 66 | } 67 | return nil, fmt.Errorf("unsupported data type: %v", v) 68 | } 69 | 70 | type DefaultStmtEncoder struct { 71 | registry *Registry 72 | } 73 | 74 | func (enc DefaultStmtEncoder) encodeString(w Writer, v reflect.Value) ([]interface{}, error) { 75 | w.WriteString("`" + v.String() + "`") 76 | return nil, nil 77 | } 78 | 79 | func (enc DefaultStmtEncoder) encodeSort(w Writer, v reflect.Value) ([]interface{}, error) { 80 | x := v.Interface().(expr.Sort) 81 | w.WriteString("`" + x.Name + "`") 82 | if x.Direction == expr.Descending { 83 | w.WriteString(" DESC") 84 | } 85 | return nil, nil 86 | } 87 | 88 | func (enc DefaultStmtEncoder) encodeField(w Writer, v reflect.Value) ([]interface{}, error) { 89 | x, isOk := v.Interface().(expr.F) 90 | if !isOk { 91 | return nil, errors.New("invalid data type") 92 | } 93 | w.WriteString("FIELD") 94 | w.WriteByte('(') 95 | w.WriteString("`" + x.Name + "`") 96 | vals := make([]interface{}, 0) 97 | for _, vv := range x.Values { 98 | w.WriteByte(',') 99 | w.WriteString(variable) 100 | it, err := enc.registry.EncodeValue(vv) 101 | if err != nil { 102 | return nil, err 103 | } 104 | vals = append(vals, it) 105 | } 106 | w.WriteByte(')') 107 | return vals, nil 108 | } 109 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "reflect" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | type structDefinition struct { 12 | fields []structField 13 | } 14 | 15 | type structField struct { 16 | t reflect.Type 17 | tag structTag 18 | def *structDefinition 19 | } 20 | 21 | type structTag struct { 22 | name string 23 | opts map[string]string 24 | } 25 | 26 | func parseStructTag(tags []string, f reflect.StructField) (t structTag) { 27 | t.name = f.Name 28 | t.opts = make(map[string]string) 29 | 30 | var paths []string 31 | for _, tag := range tags { 32 | v, ok := f.Tag.Lookup(tag) 33 | if !ok { 34 | continue 35 | } 36 | paths = strings.Split(v, ",") 37 | paths[0] = strings.TrimSpace(paths[0]) 38 | if len(paths[0]) > 0 { 39 | t.name = paths[0] 40 | paths = paths[1:] 41 | } 42 | 43 | for _, p := range paths { 44 | t.opts[p] = p 45 | } 46 | } 47 | 48 | return 49 | } 50 | 51 | type entityCache struct { 52 | mu sync.Mutex 53 | tag []string 54 | cache map[reflect.Type]*structDefinition 55 | } 56 | 57 | var ec = &entityCache{ 58 | tag: []string{"db", "datastore", "goloquent"}, 59 | cache: make(map[reflect.Type]*structDefinition), 60 | } 61 | 62 | func codecByType(t reflect.Type) *structDefinition { 63 | sd, ok := ec.cache[t] 64 | if ok { 65 | return sd 66 | } 67 | ec.mu.Lock() 68 | defer ec.mu.Unlock() 69 | sd, err := getCodec(ec.tag, t) 70 | if err != nil { 71 | panic(err) 72 | } 73 | ec.cache[t] = sd 74 | return ec.cache[t] 75 | } 76 | 77 | type typeQueue struct { 78 | t reflect.Type 79 | parentPath string 80 | } 81 | 82 | func getCodec(tagName []string, t reflect.Type) (*structDefinition, error) { 83 | queue := []typeQueue{} 84 | queue = append(queue, typeQueue{elem(t), ""}) 85 | 86 | sd := new(structDefinition) 87 | 88 | for len(queue) > 0 { 89 | q := queue[0] 90 | 91 | sf := structField{} 92 | for i := 0; i < q.t.NumField(); i++ { 93 | f := q.t.Field(i) 94 | 95 | // skip unexported fields 96 | if len(f.PkgPath) != 0 && !f.Anonymous { 97 | continue 98 | } 99 | log.Println(f) 100 | 101 | tag := parseStructTag(tagName, f) 102 | 103 | switch { 104 | case tag.name == "-": 105 | continue 106 | case !isValidFieldName(tag.name): 107 | return nil, fmt.Errorf("goloquent: struct tag has invalid field name: %q", tag.name) 108 | case isReserveFieldName(tag.name): 109 | return nil, fmt.Errorf("goloquent: struct tag has reserved field name: %q", tag.name) 110 | // case st.isPrimaryKey(): 111 | // if sf.Type != typeOfPtrKey { 112 | // return nil, fmt.Errorf("goloquent: %s field on struct %v must be *datastore.Key", keyFieldName, ft) 113 | // } 114 | } 115 | 116 | sf.t = f.Type 117 | sf.tag = tag 118 | 119 | ft := elem(f.Type) 120 | if ft.Kind() == reflect.Struct { 121 | 122 | } 123 | 124 | sd.fields = append(sd.fields, sf) 125 | } 126 | 127 | queue = queue[1:] 128 | } 129 | 130 | log.Println(sd) 131 | return sd, nil 132 | } 133 | 134 | func elem(t reflect.Type) reflect.Type { 135 | for t.Kind() == reflect.Ptr { 136 | t = t.Elem() 137 | } 138 | return t 139 | } 140 | -------------------------------------------------------------------------------- /entity.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // Column : 10 | type Column struct { 11 | names []string 12 | field field 13 | } 14 | 15 | // Name : 16 | func (c Column) Name() string { 17 | return strings.Join(c.names, ".") 18 | } 19 | 20 | func getColumns(prefix []string, codec *StructCodec) []Column { 21 | columns := make([]Column, 0) 22 | for _, f := range codec.fields { 23 | c := make([]Column, 0) 24 | if f.getRoot().isFlatten() && f.StructCodec != nil { 25 | c = getColumns(append(prefix, f.name), f.StructCodec) 26 | } else { 27 | c = append(c, Column{ 28 | names: append(prefix, f.name), 29 | field: f, 30 | }) 31 | } 32 | columns = append(columns, c...) 33 | } 34 | 35 | return columns 36 | } 37 | 38 | // convertMulti will convert any single model to pointer of []model 39 | func convertMulti(v reflect.Value) reflect.Value { 40 | vi := reflect.MakeSlice(reflect.SliceOf(v.Type()), 1, 1) 41 | vi.Index(0).Set(v) 42 | vv := reflect.New(vi.Type()) 43 | vv.Elem().Set(vi) 44 | return vv 45 | } 46 | 47 | type entity struct { 48 | name string 49 | typeOf reflect.Type 50 | isMultiPtr bool 51 | slice reflect.Value 52 | codec *StructCodec 53 | fields map[string]Column 54 | columns []Column 55 | } 56 | 57 | // TODO: check primary key must present 58 | func newEntity(it interface{}) (*entity, error) { 59 | v := reflect.ValueOf(it) 60 | if v.Kind() != reflect.Ptr { 61 | return nil, fmt.Errorf("goloquent: model is not addressable") 62 | } 63 | 64 | isMultiPtr := false 65 | t := v.Type().Elem() 66 | switch t.Kind() { 67 | case reflect.Slice, reflect.Array: 68 | t = t.Elem() 69 | if t.Kind() == reflect.Ptr { 70 | isMultiPtr = true 71 | t = t.Elem() 72 | } 73 | if t.Kind() != reflect.Struct { 74 | return nil, fmt.Errorf("goloquent: invalid entity data type : %v, it should be struct", t) 75 | } 76 | case reflect.Struct: 77 | isMultiPtr = true 78 | v = convertMulti(v) 79 | default: 80 | return nil, fmt.Errorf("goloquent: invalid entity data type : %v, it should be struct", t) 81 | } 82 | 83 | codec, err := getStructCodec(reflect.New(t).Interface()) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | fields := make(map[string]Column) 89 | cols := getColumns(nil, codec) 90 | for _, c := range cols { 91 | fields[c.Name()] = c 92 | } 93 | 94 | if _, hasKey := fields[keyFieldName]; !hasKey { 95 | return nil, fmt.Errorf("goloquent: entity %v doesn't has primary key property", t) 96 | } 97 | 98 | return &entity{ 99 | name: t.Name(), 100 | typeOf: t, 101 | isMultiPtr: isMultiPtr, 102 | codec: codec, 103 | slice: v, 104 | fields: fields, 105 | columns: cols, 106 | }, nil 107 | } 108 | 109 | func (e *entity) hasSoftDelete() (isExist bool) { 110 | _, isExist = e.fields[softDeleteColumn] 111 | return 112 | } 113 | 114 | func (e *entity) setName(name string) { 115 | name = strings.TrimSpace(name) 116 | if name != "" { 117 | e.name = name 118 | } 119 | } 120 | 121 | func (e *entity) field(key string) field { 122 | return e.fields[key].field 123 | } 124 | 125 | func (e *entity) Name() string { 126 | return e.name 127 | } 128 | 129 | func (e *entity) Columns() (cols []string) { 130 | cols = make([]string, 0, len(e.columns)) 131 | for _, c := range e.columns { 132 | if c.Name() == keyFieldName { 133 | cols = append(cols, pkColumn) 134 | continue 135 | } 136 | cols = append(cols, c.Name()) 137 | } 138 | return 139 | } 140 | -------------------------------------------------------------------------------- /struct_codec_test.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "cloud.google.com/go/datastore" 11 | ) 12 | 13 | type Nested struct { 14 | testUser 15 | } 16 | 17 | type TestModel struct { 18 | CreatedAt time.Time 19 | UpdatedAt time.Time 20 | } 21 | 22 | type TestString string 23 | 24 | type testUser struct { 25 | ID string `goloquent:"-"` 26 | Key *datastore.Key `goloquent:"__key__"` 27 | Name TestString `goloquent:",charset=latin1"` 28 | Username string `goloquent:",index"` 29 | Password []byte `goloquent:"Secret"` 30 | Biography TestString `goloquent:",longtext"` 31 | priv *bool 32 | Nickname []string `` 33 | Age uint8 34 | CreditLimit float64 `goloquent:",unsigned"` 35 | Addresses []struct { 36 | AddressLine1 string 37 | AddressLine2 *string 38 | PostCode uint32 39 | Country string 40 | } 41 | // House []*datastore.Key 42 | IsSingle bool 43 | LastLoginAt *time.Time 44 | TestModel 45 | DeleteAt SoftDelete 46 | } 47 | 48 | func (x *testUser) Load() error { 49 | x.ID = StringKey(x.Key) 50 | return nil 51 | } 52 | 53 | func (x *testUser) Save() error { 54 | // x.CreditLimit = 0 55 | return nil 56 | } 57 | 58 | type testCodec struct { 59 | name string 60 | paths []int 61 | isPtrChild bool 62 | isIndex bool 63 | isSlice bool 64 | } 65 | 66 | func TestStructCodec(t *testing.T) { 67 | var i testUser 68 | cc, err := getStructCodec(&i) 69 | if err != nil { 70 | log.Fatal("Expected error free, but instead err :", err) 71 | } 72 | 73 | list := []testCodec{ 74 | testCodec{"__key__", []int{1}, false, false, false}, 75 | testCodec{"Name", []int{2}, false, false, false}, 76 | testCodec{"Username", []int{3}, false, true, false}, 77 | testCodec{"Secret", []int{4}, false, false, false}, 78 | testCodec{"Biography", []int{5}, false, false, false}, 79 | testCodec{"Nickname", []int{7}, false, false, true}, 80 | testCodec{"Age", []int{8}, false, false, false}, 81 | testCodec{"CreditLimit", []int{9}, false, false, false}, 82 | testCodec{"Addresses", []int{10}, false, false, true}, 83 | testCodec{"IsSingle", []int{11}, false, false, false}, 84 | testCodec{"LastLoginAt", []int{12}, false, false, false}, 85 | testCodec{"CreatedAt", []int{13, 0}, false, false, false}, 86 | testCodec{"UpdatedAt", []int{13, 1}, false, false, false}, 87 | testCodec{"$Deleted", []int{14}, false, false, false}, 88 | } 89 | 90 | if len(list) != len(cc.fields) { 91 | log.Fatal("Unmatched number of property") 92 | } 93 | 94 | for i, f := range cc.fields { 95 | ff := list[i] 96 | if ff.name != f.name { 97 | log.Fatal(fmt.Sprintf("Unexpected property name value, expected %q, but get %q", ff.name, f.name)) 98 | } 99 | if ff.isIndex != f.IsIndex() { 100 | log.Fatal(fmt.Sprintf("Unexpected property tag value, expected %v, but get %v", ff.isIndex, f.IsIndex())) 101 | } 102 | if ff.isSlice != f.isSlice() { 103 | log.Fatal(fmt.Sprintf("Unexpected property data type value, expected `slice`, but get %v", f.typeOf)) 104 | } 105 | if ff.isPtrChild != f.isPtrChild { 106 | log.Fatal(fmt.Sprintf("Unexpected property ptr child value, expected %v, but get %v", ff.isPtrChild, f.isPtrChild)) 107 | } 108 | if !reflect.DeepEqual(ff.paths, f.paths) { 109 | log.Fatal(fmt.Sprintf("Unexpected property index paths, expected %v, but get %v", ff.paths, f.paths)) 110 | } 111 | } 112 | 113 | var j Nested 114 | _, err = getStructCodec(&j) 115 | if err != nil { 116 | log.Fatal("Expected error free, but instead err :", err) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /qson/parser.go: -------------------------------------------------------------------------------- 1 | package qson 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | type direction int 13 | 14 | const ( 15 | ascending direction = iota 16 | descending 17 | ) 18 | 19 | // Parser : 20 | type Parser struct { 21 | codec map[string]*Property 22 | } 23 | 24 | // New : 25 | func New(src interface{}) (*Parser, error) { 26 | v := reflect.Indirect(reflect.ValueOf(src)) 27 | if v.Type().Kind() != reflect.Struct { 28 | return nil, fmt.Errorf("qson: invalid data type %v", v.Type()) 29 | } 30 | return &Parser{ 31 | codec: getProperty(v.Type()), 32 | }, nil 33 | } 34 | 35 | // Parse : 36 | func (p *Parser) Parse(b []byte) ([]Field, error) { 37 | b = bytes.TrimSpace(b) 38 | if len(b) <= 0 || string(b) == `{}` { 39 | return nil, nil 40 | } 41 | 42 | l := make(map[string]interface{}) 43 | if err := json.Unmarshal(b, &l); err != nil { 44 | return nil, fmt.Errorf("qson: unable to unmarshal query to json") 45 | } 46 | 47 | fields := make([]Field, 0) 48 | for k, v := range l { 49 | p, isValid := p.codec[k] 50 | if !isValid { 51 | return nil, fmt.Errorf("qson: invalid filter field %q", k) 52 | } 53 | 54 | name := p.QSON() 55 | switch vi := v.(type) { 56 | case map[string]interface{}: 57 | for op, vv := range vi { 58 | if !validOperator(op) { 59 | return nil, fmt.Errorf("qson: json key %q has invalid operator %q", k, op) 60 | } 61 | 62 | if op == in || op == nin { 63 | x, isOk := vv.([]interface{}) 64 | if !isOk { 65 | return nil, fmt.Errorf("qson: json key %q has invalid value %v", k, vv) 66 | } 67 | 68 | arr := reflect.MakeSlice(reflect.SliceOf(p.typeOf), len(x), len(x)) 69 | for i, xx := range x { 70 | it, err := convertToInterface(p.typeOf, xx) 71 | if err != nil { 72 | return nil, err 73 | } 74 | arr.Index(i).Set(reflect.ValueOf(it)) 75 | } 76 | 77 | fields = append(fields, Field{name, op, arr.Interface()}) 78 | continue 79 | } 80 | 81 | it, err := convertToInterface(p.typeOf, vv) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | fields = append(fields, Field{name, op, it}) 87 | } 88 | default: 89 | it, err := convertToInterface(p.typeOf, vi) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | fields = append(fields, Field{name, eq, it}) 95 | } 96 | } 97 | 98 | sort.Slice(fields, func(i, j int) bool { 99 | return fmt.Sprintf("%s,%s", fields[i].Name(), fields[i].Operator()) < 100 | fmt.Sprintf("%s,%s", fields[j].Name(), fields[j].Operator()) 101 | }) 102 | return fields, nil 103 | } 104 | 105 | // Sort : 106 | type Sort struct { 107 | field string 108 | dir direction 109 | } 110 | 111 | // Name : 112 | func (s Sort) Name() string { 113 | return s.field 114 | } 115 | 116 | // IsAscending : 117 | func (s Sort) IsAscending() bool { 118 | return s.dir == ascending 119 | } 120 | 121 | // ParseSort : 122 | func (p *Parser) ParseSort(fields []string) ([]Sort, error) { 123 | sorts := make([]Sort, 0, len(fields)) 124 | dict := make(map[string]bool) 125 | for _, ff := range fields { 126 | ff = strings.Trim(strings.TrimSpace(ff), `"`) 127 | dir := ascending 128 | if ff == "" { 129 | continue 130 | } 131 | if ff[0] == '-' { 132 | dir = descending 133 | ff = ff[1:] 134 | } 135 | c, isExist := p.codec[ff] 136 | if !isExist { 137 | return nil, fmt.Errorf("qson: invalid order field %q", ff) 138 | } 139 | name := c.QSON() 140 | if dict[name] { 141 | continue 142 | } 143 | sorts = append(sorts, Sort{name, dir}) 144 | dict[name] = true 145 | } 146 | return sorts, nil 147 | } 148 | -------------------------------------------------------------------------------- /enc.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | var defaultRegistry *Registry 13 | 14 | type encodeFunc func(reflect.Value) (interface{}, error) 15 | 16 | type Registry struct { 17 | sync.Mutex 18 | typeEncoders map[reflect.Type]encodeFunc 19 | kindEncoders map[reflect.Kind]encodeFunc 20 | } 21 | 22 | func init() { 23 | defaultRegistry = NewRegistry() 24 | defaultRegistry.SetDefaultEncoders() 25 | } 26 | 27 | func NewRegistry() *Registry { 28 | return &Registry{ 29 | typeEncoders: make(map[reflect.Type]encodeFunc), 30 | kindEncoders: make(map[reflect.Kind]encodeFunc), 31 | } 32 | } 33 | 34 | func (r *Registry) SetDefaultEncoders() { 35 | enc := DefaultEncoder{registry: r} 36 | r.SetTypeEncoder(reflect.TypeOf([]byte{}), enc.encodeByte) 37 | r.SetKindEncoder(reflect.String, enc.encodeString) 38 | r.SetKindEncoder(reflect.Bool, enc.encodeBool) 39 | r.SetKindEncoder(reflect.Int, enc.encodeInteger) 40 | r.SetKindEncoder(reflect.Int8, enc.encodeInteger) 41 | r.SetKindEncoder(reflect.Int16, enc.encodeInteger) 42 | r.SetKindEncoder(reflect.Int32, enc.encodeInteger) 43 | r.SetKindEncoder(reflect.Int64, enc.encodeInteger) 44 | r.SetKindEncoder(reflect.Uint, enc.encodeUInteger) 45 | r.SetKindEncoder(reflect.Uint8, enc.encodeUInteger) 46 | r.SetKindEncoder(reflect.Uint16, enc.encodeUInteger) 47 | r.SetKindEncoder(reflect.Uint32, enc.encodeUInteger) 48 | r.SetKindEncoder(reflect.Uint64, enc.encodeUInteger) 49 | r.SetKindEncoder(reflect.Float32, enc.encodeFloat) 50 | r.SetKindEncoder(reflect.Float64, enc.encodeFloat) 51 | r.SetKindEncoder(reflect.Ptr, enc.encodePtr) 52 | r.SetKindEncoder(reflect.Array, enc.encodeArray) 53 | r.SetKindEncoder(reflect.Slice, enc.encodeSlice) 54 | } 55 | 56 | func (r *Registry) SetTypeEncoder(t reflect.Type, f encodeFunc) { 57 | r.Lock() 58 | defer r.Unlock() 59 | r.typeEncoders[t] = f 60 | } 61 | 62 | func (r *Registry) SetKindEncoder(k reflect.Kind, f encodeFunc) { 63 | r.Lock() 64 | defer r.Unlock() 65 | r.kindEncoders[k] = f 66 | } 67 | 68 | func (r *Registry) EncodeValue(v reflect.Value) (interface{}, error) { 69 | if encoder, isOk := r.typeEncoders[v.Type()]; isOk { 70 | return encoder(v) 71 | } 72 | if encoder, isOk := r.kindEncoders[v.Kind()]; isOk { 73 | return encoder(v) 74 | } 75 | return nil, fmt.Errorf("unsupported data type: %v", v) 76 | } 77 | 78 | type DefaultEncoder struct { 79 | registry *Registry 80 | } 81 | 82 | func (enc DefaultEncoder) encodeString(v reflect.Value) (interface{}, error) { 83 | return v.String(), nil 84 | } 85 | 86 | func (enc DefaultEncoder) encodeByte(v reflect.Value) (interface{}, error) { 87 | if v.IsNil() { 88 | return "null", nil 89 | } 90 | return base64.StdEncoding.EncodeToString(v.Bytes()), nil 91 | } 92 | 93 | func (enc DefaultEncoder) encodeInteger(v reflect.Value) (interface{}, error) { 94 | return v.Int(), nil 95 | } 96 | 97 | func (enc DefaultEncoder) encodeUInteger(v reflect.Value) (interface{}, error) { 98 | return v.Uint(), nil 99 | } 100 | 101 | func (enc DefaultEncoder) encodeBool(v reflect.Value) (interface{}, error) { 102 | return v.Bool(), nil 103 | } 104 | 105 | func (enc DefaultEncoder) encodeFloat(v reflect.Value) (interface{}, error) { 106 | return v.Float(), nil 107 | } 108 | 109 | func (enc DefaultEncoder) encodePtr(v reflect.Value) (interface{}, error) { 110 | if v.IsNil() { 111 | return nil, nil 112 | } 113 | v = v.Elem() 114 | return enc.registry.EncodeValue(v) 115 | } 116 | 117 | func (enc DefaultEncoder) encodeArray(v reflect.Value) (interface{}, error) { 118 | return enc.encodeArrayOrSlice(v) 119 | } 120 | 121 | func (enc DefaultEncoder) encodeSlice(v reflect.Value) (interface{}, error) { 122 | if v.IsNil() { 123 | return "null", nil 124 | } 125 | return enc.encodeArrayOrSlice(v) 126 | } 127 | 128 | func (enc DefaultEncoder) encodeArrayOrSlice(v reflect.Value) (interface{}, error) { 129 | blr := new(strings.Builder) 130 | blr.WriteByte('[') 131 | var ( 132 | length = v.Len() 133 | vv interface{} 134 | err error 135 | ) 136 | for i := 0; i < length; i++ { 137 | if i > 0 { 138 | blr.WriteByte(',') 139 | } 140 | vv, err = enc.registry.EncodeValue(v.Index(i)) 141 | if err != nil { 142 | return nil, err 143 | } 144 | blr.WriteString(convertToString(vv)) 145 | } 146 | blr.WriteByte(']') 147 | return blr.String(), nil 148 | } 149 | 150 | func convertToString(it interface{}) string { 151 | switch vi := it.(type) { 152 | case string: 153 | return strconv.Quote(vi) 154 | case bool: 155 | return strconv.FormatBool(vi) 156 | case int64: 157 | return strconv.FormatInt(vi, 10) 158 | case uint64: 159 | return strconv.FormatUint(vi, 10) 160 | case []byte: 161 | return strconv.Quote(string(vi)) 162 | case nil: 163 | return "null" 164 | default: 165 | return "{}" 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strings" 7 | 8 | "cloud.google.com/go/datastore" 9 | "github.com/si3nloong/goloquent" 10 | ) 11 | 12 | // Connection : 13 | func Connection(driver string) *goloquent.DB { 14 | driver = strings.TrimSpace(driver) 15 | paths := strings.SplitN(driver, ":", 2) 16 | if len(paths) != 2 { 17 | panic(fmt.Errorf("goloquent: invalid connection name %q", driver)) 18 | } 19 | x, isOk := connPool.Load(driver) 20 | if !isOk { 21 | panic(fmt.Errorf("goloquent: connection not found")) 22 | } 23 | pool := x.(map[string]*goloquent.DB) 24 | for k, v := range pool { 25 | if k == paths[1] { 26 | return v 27 | } 28 | // return v 29 | } 30 | return nil 31 | } 32 | 33 | // Query : 34 | func Query(stmt string, args ...interface{}) (*sql.Rows, error) { 35 | return defaultDB.Query(stmt, args...) 36 | } 37 | 38 | // Exec : 39 | func Exec(stmt string, args ...interface{}) (sql.Result, error) { 40 | return defaultDB.Exec(stmt, args...) 41 | } 42 | 43 | // Table : 44 | func Table(name string) *goloquent.Table { 45 | return defaultDB.Table(name) 46 | } 47 | 48 | // Migrate : 49 | func Migrate(model ...interface{}) error { 50 | return defaultDB.Migrate(model...) 51 | } 52 | 53 | // Omit : 54 | func Omit(fields ...string) goloquent.Replacer { 55 | return defaultDB.Omit(fields...) 56 | } 57 | 58 | // Create : 59 | func Create(model interface{}, parentKey ...*datastore.Key) error { 60 | if parentKey == nil { 61 | return defaultDB.Create(model) 62 | } 63 | return defaultDB.Create(model, parentKey...) 64 | } 65 | 66 | // Upsert : 67 | func Upsert(model interface{}, parentKey ...*datastore.Key) error { 68 | if parentKey == nil { 69 | return defaultDB.Upsert(model) 70 | } 71 | return defaultDB.Upsert(model, parentKey...) 72 | } 73 | 74 | // Delete : 75 | func Delete(model interface{}) error { 76 | return defaultDB.Delete(model) 77 | } 78 | 79 | // Destroy : 80 | func Destroy(model interface{}) error { 81 | return defaultDB.Destroy(model) 82 | } 83 | 84 | // Save : 85 | func Save(model interface{}) error { 86 | return defaultDB.Save(model) 87 | } 88 | 89 | // Find : 90 | func Find(key *datastore.Key, model interface{}) error { 91 | return defaultDB.Find(key, model) 92 | } 93 | 94 | // First : 95 | func First(model interface{}) error { 96 | return defaultDB.First(model) 97 | } 98 | 99 | // Get : 100 | func Get(model interface{}) error { 101 | return defaultDB.Get(model) 102 | } 103 | 104 | // Paginate : 105 | func Paginate(p *goloquent.Pagination, model interface{}) error { 106 | return defaultDB.Paginate(p, model) 107 | } 108 | 109 | // NewQuery : 110 | func NewQuery() *goloquent.Query { 111 | return defaultDB.NewQuery() 112 | } 113 | 114 | // Select : 115 | func Select(fields ...string) *goloquent.Query { 116 | return defaultDB.Select(fields...) 117 | } 118 | 119 | // Ancestor : 120 | func Ancestor(ancestor *datastore.Key) *goloquent.Query { 121 | return defaultDB.NewQuery().Ancestor(ancestor) 122 | } 123 | 124 | // AnyOfAncestor : 125 | func AnyOfAncestor(ancestors ...*datastore.Key) *goloquent.Query { 126 | return defaultDB.NewQuery().AnyOfAncestor(ancestors...) 127 | } 128 | 129 | // Unscoped : 130 | func Unscoped() *goloquent.Query { 131 | return defaultDB.NewQuery().Unscoped() 132 | } 133 | 134 | // DistinctOn : 135 | func DistinctOn(fields ...string) *goloquent.Query { 136 | return defaultDB.NewQuery().DistinctOn(fields...) 137 | } 138 | 139 | // Where : 140 | func Where(field string, operator string, value interface{}) *goloquent.Query { 141 | return defaultDB.Where(field, operator, value) 142 | } 143 | 144 | // WhereEqual : 145 | func WhereEqual(field string, value interface{}) *goloquent.Query { 146 | return defaultDB.NewQuery().WhereEqual(field, value) 147 | } 148 | 149 | // WhereNotEqual : 150 | func WhereNotEqual(field string, value interface{}) *goloquent.Query { 151 | return defaultDB.NewQuery().WhereNotEqual(field, value) 152 | } 153 | 154 | // WhereNull : 155 | func WhereNull(field string) *goloquent.Query { 156 | return defaultDB.NewQuery().WhereNull(field) 157 | } 158 | 159 | // WhereNotNull : 160 | func WhereNotNull(field string) *goloquent.Query { 161 | return defaultDB.NewQuery().WhereNotNull(field) 162 | } 163 | 164 | // WhereJSON : 165 | func WhereJSON(field string, operator string, value interface{}) *goloquent.Query { 166 | return defaultDB.NewQuery().WhereJSON(field, operator, value) 167 | } 168 | 169 | // MatchAgainst : 170 | func MatchAgainst(fields []string, value ...string) *goloquent.Query { 171 | return defaultDB.NewQuery().MatchAgainst(fields, value...) 172 | } 173 | 174 | // OrderBy : 175 | func OrderBy(fields ...interface{}) *goloquent.Query { 176 | return defaultDB.NewQuery().OrderBy(fields...) 177 | } 178 | 179 | // Limit : 180 | func Limit(limit int) *goloquent.Query { 181 | return defaultDB.NewQuery().Limit(limit) 182 | } 183 | 184 | // Offset : 185 | func Offset(offset int) *goloquent.Query { 186 | return defaultDB.NewQuery().Offset(offset) 187 | } 188 | 189 | // RunInTransaction : 190 | func RunInTransaction(cb goloquent.TransactionHandler) error { 191 | return defaultDB.RunInTransaction(cb) 192 | } 193 | 194 | // Truncate : 195 | func Truncate(model ...interface{}) error { 196 | return defaultDB.Truncate(model...) 197 | } 198 | -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "cloud.google.com/go/datastore" 5 | ) 6 | 7 | // Table : 8 | type Table struct { 9 | name string 10 | db *DB 11 | } 12 | 13 | func (t *Table) newQuery() *Query { 14 | q := t.db.NewQuery() 15 | q.table = t.name 16 | return q 17 | } 18 | 19 | // Create : 20 | func (t *Table) Create(model interface{}, parentKey ...*datastore.Key) error { 21 | return newBuilder(t.newQuery()).put(model, parentKey) 22 | } 23 | 24 | // Upsert : 25 | func (t *Table) Upsert(model interface{}, parentKey ...*datastore.Key) error { 26 | return newBuilder(t.newQuery()).upsert(model, parentKey) 27 | } 28 | 29 | // Migrate : 30 | func (t *Table) Migrate(model interface{}) error { 31 | return newBuilder(t.newQuery()).migrate(model) 32 | } 33 | 34 | // Exists : 35 | func (t *Table) Exists() bool { 36 | return t.db.dialect.HasTable(t.name) 37 | } 38 | 39 | // DropIfExists : 40 | func (t *Table) DropIfExists() error { 41 | return newBuilder(t.newQuery()).dropTableIfExists(t.name) 42 | } 43 | 44 | // Truncate : 45 | func (t *Table) Truncate() error { 46 | return newBuilder(t.newQuery()).truncate(t.name) 47 | } 48 | 49 | // // Rename : 50 | // func (t *Table) Rename(name string) error { 51 | // return newBuilder(t.newQuery()).renameTable(t.name, name) 52 | // } 53 | 54 | // AddIndex : 55 | func (t *Table) AddIndex(fields ...string) error { 56 | return newBuilder(t.newQuery()).addIndex(fields, bTreeIdx) 57 | } 58 | 59 | // AddUniqueIndex : 60 | func (t *Table) AddUniqueIndex(fields ...string) error { 61 | return newBuilder(t.newQuery()).addIndex(fields, uniqueIdx) 62 | } 63 | 64 | // Select : 65 | func (t *Table) Select(fields ...string) *Query { 66 | return t.newQuery().Select(fields...) 67 | } 68 | 69 | // DistinctOn : 70 | func (t *Table) DistinctOn(fields ...string) *Query { 71 | return t.newQuery().DistinctOn(fields...) 72 | } 73 | 74 | // Omit : 75 | func (t *Table) Omit(fields ...string) *Query { 76 | return t.newQuery().Omit(fields...) 77 | } 78 | 79 | // Unscoped : 80 | func (t *Table) Unscoped() *Query { 81 | return t.newQuery().Unscoped() 82 | } 83 | 84 | // Find : 85 | func (t *Table) Find(key *datastore.Key, model interface{}) error { 86 | return t.newQuery().Find(key, model) 87 | } 88 | 89 | // First : 90 | func (t *Table) First(model interface{}) error { 91 | return t.newQuery().First(model) 92 | } 93 | 94 | // Get : 95 | func (t *Table) Get(model interface{}) error { 96 | return t.newQuery().Get(model) 97 | } 98 | 99 | // Paginate : 100 | func (t *Table) Paginate(p *Pagination, model interface{}) error { 101 | return t.newQuery().Paginate(p, model) 102 | } 103 | 104 | // AnyOfAncestor : 105 | func (t *Table) AnyOfAncestor(ancestors ...*datastore.Key) *Query { 106 | return t.newQuery().AnyOfAncestor(ancestors...) 107 | } 108 | 109 | // Ancestor : 110 | func (t *Table) Ancestor(ancestor *datastore.Key) *Query { 111 | return t.newQuery().Ancestor(ancestor) 112 | } 113 | 114 | // Where : 115 | func (t *Table) Where(field, op string, value interface{}) *Query { 116 | return t.newQuery().Where(field, op, value) 117 | } 118 | 119 | // WhereEqual : 120 | func (t *Table) WhereEqual(field string, v interface{}) *Query { 121 | return t.newQuery().WhereEqual(field, v) 122 | } 123 | 124 | // WhereNotEqual : 125 | func (t *Table) WhereNotEqual(field string, v interface{}) *Query { 126 | return t.newQuery().WhereNotEqual(field, v) 127 | } 128 | 129 | // WhereNull : 130 | func (t *Table) WhereNull(field string) *Query { 131 | return t.newQuery().WhereNull(field) 132 | } 133 | 134 | // WhereNotNull : 135 | func (t *Table) WhereNotNull(field string) *Query { 136 | return t.newQuery().WhereNotNull(field) 137 | } 138 | 139 | // WhereIn : 140 | func (t *Table) WhereIn(field string, v []interface{}) *Query { 141 | return t.newQuery().WhereIn(field, v) 142 | } 143 | 144 | // WhereNotIn : 145 | func (t *Table) WhereNotIn(field string, v []interface{}) *Query { 146 | return t.newQuery().WhereNotIn(field, v) 147 | } 148 | 149 | // WhereLike : 150 | func (t *Table) WhereLike(field, v string) *Query { 151 | return t.newQuery().WhereLike(field, v) 152 | } 153 | 154 | // WhereNotLike : 155 | func (t *Table) WhereNotLike(field, v string) *Query { 156 | return t.newQuery().WhereNotLike(field, v) 157 | } 158 | 159 | // WhereJSONEqual : 160 | func (t *Table) WhereJSONEqual(field string, v interface{}) *Query { 161 | return t.newQuery().WhereJSONEqual(field, v) 162 | } 163 | 164 | // Lock : 165 | func (t *Table) Lock(mode locked) *Query { 166 | return t.newQuery().Lock(mode) 167 | } 168 | 169 | // WLock : 170 | func (t *Table) WLock() *Query { 171 | return t.newQuery().WLock() 172 | } 173 | 174 | // RLock : 175 | func (t *Table) RLock() *Query { 176 | return t.newQuery().RLock() 177 | } 178 | 179 | // OrderBy : 180 | func (t *Table) OrderBy(fields ...interface{}) *Query { 181 | return t.newQuery().OrderBy(fields...) 182 | } 183 | 184 | // Limit : 185 | func (t *Table) Limit(limit int) *Query { 186 | return t.newQuery().Limit(limit) 187 | } 188 | 189 | // Offset : 190 | func (t *Table) Offset(offset int) *Query { 191 | return t.newQuery().Offset(offset) 192 | } 193 | 194 | // ReplaceInto : 195 | func (t *Table) ReplaceInto(table string) error { 196 | return t.newQuery().ReplaceInto(table) 197 | } 198 | 199 | // InsertInto : 200 | func (t *Table) InsertInto(table string) error { 201 | return t.newQuery().InsertInto(table) 202 | } 203 | 204 | // Update : 205 | func (t *Table) Update(v interface{}) error { 206 | return t.newQuery().Update(v) 207 | } 208 | 209 | // Save : 210 | func (t *Table) Save(model interface{}) error { 211 | return newBuilder(t.newQuery()).save(model) 212 | } 213 | 214 | // Scan : 215 | func (t *Table) Scan(dest ...interface{}) error { 216 | return t.newQuery().Scan(dest...) 217 | } 218 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | "time" 10 | "unsafe" 11 | 12 | "cloud.google.com/go/datastore" 13 | ) 14 | 15 | // Dictionary : 16 | type dictionary map[string]bool 17 | 18 | func newDictionary(v []string) dictionary { 19 | l := make(map[string]bool) 20 | for _, vv := range v { 21 | vv = strings.TrimSpace(vv) 22 | if vv == "" { 23 | continue 24 | } 25 | l[vv] = true 26 | } 27 | return dictionary(l) 28 | } 29 | 30 | func (d dictionary) add(k string) { 31 | if !d.has(k) { 32 | d[k] = true 33 | } 34 | } 35 | 36 | // Has : 37 | func (d dictionary) has(k string) bool { 38 | return d[k] 39 | } 40 | 41 | // Delete : 42 | func (d dictionary) delete(k string) { 43 | delete(d, k) 44 | } 45 | 46 | // Keys : 47 | func (d dictionary) keys() []string { 48 | arr := make([]string, 0, len(d)) 49 | for k := range d { 50 | arr = append(arr, k) 51 | } 52 | return arr 53 | } 54 | 55 | func b2s(b []byte) string { 56 | return *(*string)(unsafe.Pointer(&b)) 57 | } 58 | 59 | // StringKey : 60 | func StringKey(key *datastore.Key) string { 61 | if key == nil { 62 | return "" 63 | } 64 | if key.Name != "" { 65 | return key.Name 66 | } 67 | return strconv.FormatInt(key.ID, 10) 68 | } 69 | 70 | // minimum and maximum value for random seed 71 | const ( 72 | minSeed = int64(100000000) 73 | maxSeed = int64(999999999) 74 | ) 75 | 76 | // newPrimaryKey will generate a new key if the key provided was incomplete 77 | // and it will ensure the key will not be incomplete 78 | func newPrimaryKey(table string, parentKey *datastore.Key) *datastore.Key { 79 | if parentKey != nil && ((parentKey.Kind == table && parentKey.Name != "") || 80 | (parentKey.Kind == table && parentKey.ID > 0)) { 81 | return parentKey 82 | } 83 | 84 | rand.Seed(time.Now().UnixNano()) 85 | strID := strconv.FormatInt(time.Now().Unix(), 10) + strconv.FormatInt(rand.Int63n(maxSeed-minSeed)+minSeed, 10) 86 | id, _ := strconv.ParseInt(strID, 10, 64) 87 | if parentKey != nil && parentKey.Kind == table { 88 | parentKey.ID = id 89 | return parentKey 90 | } 91 | 92 | key := new(datastore.Key) 93 | key.Kind = table 94 | key.ID = id 95 | if parentKey != nil { 96 | key.Parent = parentKey 97 | } 98 | return key 99 | } 100 | 101 | func isNameKey(strKey string) bool { 102 | if strKey == "" { 103 | return false 104 | } 105 | if strings.HasPrefix(strKey, "name=") { 106 | return true 107 | } 108 | _, err := strconv.ParseInt(strKey, 10, 64) 109 | if err != nil { 110 | return true 111 | } 112 | paths := strings.Split(strKey, "/") 113 | if len(paths) != 2 { 114 | return strings.HasPrefix(strKey, "'") && strings.HasSuffix(strKey, "'") 115 | } 116 | lastPath := strings.Split(paths[len(paths)-1], ",")[1] 117 | return strings.HasPrefix(lastPath, "'") || strings.HasSuffix(lastPath, "'") 118 | } 119 | 120 | // ParseKey : 121 | func ParseKey(str string) (*datastore.Key, error) { 122 | return parseKey(str) 123 | } 124 | 125 | // parseKey will parse any key string to *datastore.Key, 126 | // it will return null *datastore.Key if the key string is empty 127 | func parseKey(str string) (*datastore.Key, error) { 128 | str = strings.Trim(strings.TrimSpace(str), `"`) 129 | if str == "" { 130 | var k *datastore.Key 131 | return k, nil 132 | } 133 | 134 | paths := strings.Split(strings.Trim(str, "/"), "/") 135 | parentKey := new(datastore.Key) 136 | for _, p := range paths { 137 | path := strings.Split(p, ",") 138 | if len(path) != 2 { 139 | return nil, fmt.Errorf("goloquent: incorrect key value: %q, suppose %q", p, "table,value") 140 | } 141 | 142 | kind, value := path[0], path[1] 143 | if kind == "" || value == "" { 144 | return nil, fmt.Errorf("goloquent: invalid key value format: %q, suppose %q", p, "table,value") 145 | } 146 | key := new(datastore.Key) 147 | key.Kind = kind 148 | if isNameKey(value) { 149 | name, err := url.PathUnescape(strings.Trim(value, `'`)) 150 | if err != nil { 151 | return nil, err 152 | } 153 | key.Name = name 154 | } else { 155 | n, err := strconv.ParseInt(value, 10, 64) 156 | if err != nil { 157 | return nil, fmt.Errorf("goloquent: incorrect key id, %v", value) 158 | } 159 | key.ID = n 160 | } 161 | 162 | if !parentKey.Incomplete() { 163 | key.Parent = parentKey 164 | } 165 | parentKey = key 166 | } 167 | 168 | return parentKey, nil 169 | } 170 | 171 | // StringifyKey : 172 | func StringifyKey(key *datastore.Key) string { 173 | return stringifyKey(key) 174 | } 175 | 176 | // stringifyKey, will transform key to either string or empty string 177 | func stringifyKey(key *datastore.Key) string { 178 | paths := make([]string, 0) 179 | parentKey := key 180 | 181 | for parentKey != nil { 182 | var k string 183 | if parentKey.Name != "" { 184 | name := url.PathEscape(parentKey.Name) 185 | k = parentKey.Kind + ",'" + name + "'" 186 | } else { 187 | k = parentKey.Kind + "," + strconv.FormatInt(parentKey.ID, 10) 188 | } 189 | paths = append([]string{k}, paths...) 190 | parentKey = parentKey.Parent 191 | } 192 | 193 | if len(paths) > 0 { 194 | return strings.Join(paths, "/") 195 | } 196 | 197 | return "" 198 | } 199 | 200 | func splitKey(k *datastore.Key) (key string, parent string) { 201 | if k == nil { 202 | return "", "" 203 | } 204 | if k.ID > 0 { 205 | if isPkSimple { 206 | return strconv.FormatInt(k.ID, 10), stringifyKey(k.Parent) 207 | } 208 | return k.Kind + "," + strconv.FormatInt(k.ID, 10), stringifyKey(k.Parent) 209 | } 210 | name := url.PathEscape(k.Name) 211 | if isPkSimple { 212 | return "'" + name + "'", stringifyKey(k.Parent) 213 | } 214 | return k.Kind + ",'" + name + "'", stringifyKey(k.Parent) 215 | } 216 | 217 | func stringPk(k *datastore.Key) string { 218 | kk, pp := splitKey(k) 219 | return strings.Trim(pp+keyDelimeter+kk, keyDelimeter) 220 | } 221 | 222 | // compareVersion: is compare using semantic versioning 223 | // if a > b, result will be -1 224 | // if b < a, result will be 1 225 | // if a = b, result will be 0 226 | func compareVersion(a, b string) (ret int) { 227 | as := strings.Split(a, ".") 228 | bs := strings.Split(b, ".") 229 | loopMax := len(bs) 230 | if len(as) > len(bs) { 231 | loopMax = len(as) 232 | } 233 | for i := 0; i < loopMax; i++ { 234 | var x, y string 235 | if len(as) > i { 236 | x = as[i] 237 | } 238 | if len(bs) > i { 239 | y = bs[i] 240 | } 241 | xi, _ := strconv.Atoi(x) 242 | yi, _ := strconv.Atoi(y) 243 | if xi > yi { 244 | ret = -1 245 | } else if xi < yi { 246 | ret = 1 247 | } 248 | if ret != 0 { 249 | break 250 | } 251 | } 252 | return 253 | } 254 | 255 | func escapeSingleQuote(v string) string { 256 | return strings.Replace(v, `'`, `''`, -1) 257 | } 258 | -------------------------------------------------------------------------------- /dialect_mysql.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "reflect" 9 | "regexp" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/si3nloong/goloquent/types" 14 | ) 15 | 16 | type mysql struct { 17 | sequel 18 | } 19 | 20 | const minVersion = "5.7" 21 | 22 | var _ Dialect = new(mysql) 23 | 24 | func init() { 25 | RegisterDialect("mysql", new(mysql)) 26 | } 27 | 28 | // Open : 29 | func (s *mysql) Open(conf Config) (*sql.DB, error) { 30 | addr, buf := "@", new(bytes.Buffer) 31 | buf.WriteString(conf.Username + ":" + conf.Password) 32 | if conf.UnixSocket != "" { 33 | addr += fmt.Sprintf("unix(%s)", conf.UnixSocket) 34 | } else { 35 | host, port := "localhost", "3306" 36 | if conf.Host != "" { 37 | host = conf.Host 38 | } 39 | if conf.Port != "" { 40 | port = conf.Port 41 | } 42 | addr += fmt.Sprintf("tcp(%s:%s)", host, port) 43 | } 44 | buf.WriteString(addr + "/" + conf.Database) 45 | buf.WriteString("?parseTime=true") 46 | buf.WriteString("&charset=utf8mb4&collation=utf8mb4_unicode_ci") 47 | if conf.TLSConfig != "" { 48 | buf.WriteString("&tls=" + conf.TLSConfig) 49 | } 50 | log.Println("Connection String :", buf.String()) 51 | client, err := sql.Open("mysql", buf.String()) 52 | if err != nil { 53 | return nil, err 54 | } 55 | client.SetMaxOpenConns(300) 56 | return client, nil 57 | } 58 | 59 | // Version : 60 | func (s mysql) Version() (version string) { 61 | verRgx := regexp.MustCompile(`(\d\.\d)`) 62 | s.db.QueryRow("SELECT VERSION();").Scan(&version) 63 | log.Println("MySQL version :", version) 64 | if compareVersion(verRgx.FindStringSubmatch(version)[0], minVersion) > 0 { 65 | panic(fmt.Errorf("require at least %s version of mysql", minVersion)) 66 | } 67 | return 68 | } 69 | 70 | // Quote : 71 | func (s mysql) Quote(n string) string { 72 | return "`" + n + "`" 73 | } 74 | 75 | // Bind : 76 | func (s mysql) Bind(uint) string { 77 | return "?" 78 | } 79 | 80 | // DataType : 81 | func (s mysql) DataType(sc Schema) string { 82 | buf := new(bytes.Buffer) 83 | buf.WriteString(sc.DataType) 84 | if sc.IsUnsigned { 85 | buf.WriteString(" UNSIGNED") 86 | } 87 | if sc.CharSet.Encoding != "" && sc.CharSet.Collation != "" { 88 | buf.WriteString(fmt.Sprintf(" CHARACTER SET %s COLLATE %s", 89 | s.Quote(sc.CharSet.Encoding), 90 | s.Quote(sc.CharSet.Collation))) 91 | } 92 | if !sc.IsNullable { 93 | buf.WriteString(" NOT NULL") 94 | t := reflect.TypeOf(sc.DefaultValue) 95 | if t != reflect.TypeOf(OmitDefault(nil)) { 96 | buf.WriteString(fmt.Sprintf(" DEFAULT %s", s.ToString(sc.DefaultValue))) 97 | } 98 | } 99 | return buf.String() 100 | } 101 | 102 | func (s mysql) OnConflictUpdate(table string, cols []string) string { 103 | buf := new(bytes.Buffer) 104 | buf.WriteString("ON DUPLICATE KEY UPDATE ") 105 | for _, c := range cols { 106 | buf.WriteString(fmt.Sprintf("%s=VALUES(%s),", s.Quote(c), s.Quote(c))) 107 | } 108 | buf.Truncate(buf.Len() - 1) 109 | return buf.String() 110 | } 111 | 112 | func (s mysql) CreateTable(table string, columns []Column) error { 113 | buf := new(bytes.Buffer) 114 | buf.WriteString(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (", s.GetTable(table))) 115 | for _, c := range columns { 116 | for _, ss := range s.GetSchema(c) { 117 | buf.WriteString(fmt.Sprintf("%s %s,", s.Quote(ss.Name), s.DataType(ss))) 118 | if ss.IsIndexed || c.field.typeOf == typeOfSoftDelete { 119 | idx := fmt.Sprintf("%s_%s_%s", table, ss.Name, "idx") 120 | buf.WriteString(fmt.Sprintf("INDEX %s (%s),", s.Quote(idx), s.Quote(ss.Name))) 121 | } 122 | } 123 | } 124 | buf.WriteString(fmt.Sprintf("PRIMARY KEY (%s)", s.Quote(pkColumn))) 125 | buf.WriteString(fmt.Sprintf(") ENGINE=InnoDB DEFAULT CHARSET=%s COLLATE=%s;", 126 | s.Quote(s.db.CharSet.Encoding), s.Quote(s.db.CharSet.Collation))) 127 | return s.db.execStmt(&stmt{statement: buf}) 128 | } 129 | 130 | func (s *mysql) AlterTable(table string, columns []Column, unsafe bool) error { 131 | cols := types.StringSlice(s.GetColumns(table)) 132 | idxs := types.StringSlice(s.GetIndexes(table)) 133 | 134 | var idx string 135 | blr := new(bytes.Buffer) 136 | blr.WriteString(`ALTER TABLE ` + s.GetTable(table) + ` `) 137 | suffix := "FIRST" 138 | for _, c := range columns { 139 | for _, ss := range s.GetSchema(c) { 140 | if cols.IndexOf(ss.Name) > -1 { 141 | blr.WriteString(`MODIFY`) 142 | } else { 143 | blr.WriteString(`ADD`) 144 | } 145 | 146 | blr.WriteString(` ` + s.Quote(ss.Name) + ` `) 147 | blr.WriteString(s.DataType(ss) + ` ` + suffix) 148 | suffix = `AFTER ` + s.Quote(ss.Name) 149 | 150 | if ss.IsIndexed || c.field.typeOf == typeOfSoftDelete { 151 | idx = table + `_` + ss.Name + `_idx` 152 | if idxs.IndexOf(idx) < 0 { 153 | blr.WriteRune(',') 154 | blr.WriteString(`ADD INDEX ` + s.Quote(idx)) 155 | blr.WriteString(` (` + s.Quote(ss.Name) + `)`) 156 | } 157 | } 158 | blr.WriteRune(',') 159 | } 160 | } 161 | 162 | // for _, col := range cols.keys() { 163 | // blr.WriteString(fmt.Sprintf("DROP COLUMN %s,", s.Quote(col))) 164 | // } 165 | // for _, idx := range idxs.keys() { 166 | // buf.WriteString(fmt.Sprintf("DROP INDEX %s,", s.Quote(idx))) 167 | // } 168 | 169 | blr.WriteString(` CHARACTER SET ` + s.Quote(s.db.CharSet.Encoding)) 170 | blr.WriteString(` COLLATE ` + s.Quote(s.db.CharSet.Collation)) 171 | blr.WriteRune(';') 172 | return s.db.execStmt(&stmt{statement: blr}) 173 | } 174 | 175 | func (s mysql) ToString(it interface{}) string { 176 | var v string 177 | switch vi := it.(type) { 178 | case string: 179 | v = fmt.Sprintf("%q", vi) 180 | case bool: 181 | v = fmt.Sprintf("%t", vi) 182 | case uint, uint8, uint16, uint32, uint64: 183 | v = fmt.Sprintf("%d", vi) 184 | case int, int8, int16, int32, int64: 185 | v = fmt.Sprintf("%d", vi) 186 | case float32: 187 | v = strconv.FormatFloat(float64(vi), 'f', -1, 64) 188 | case float64: 189 | v = strconv.FormatFloat(vi, 'f', -1, 64) 190 | case time.Time: 191 | v = fmt.Sprintf(`"%s"`, vi.Format("2006-01-02 15:04:05")) 192 | // case json.RawMessage: 193 | case []interface{}: 194 | v = fmt.Sprintf(`"%s"`, "[]") 195 | case map[string]interface{}: 196 | v = fmt.Sprintf(`"%s"`, "{}") 197 | case nil: 198 | v = "NULL" 199 | default: 200 | v = fmt.Sprintf("%v", vi) 201 | } 202 | return v 203 | } 204 | 205 | func (s mysql) UpdateWithLimit() bool { 206 | return true 207 | } 208 | 209 | func (s mysql) ReplaceInto(src, dst string) error { 210 | src, dst = s.GetTable(src), s.GetTable(dst) 211 | buf := new(bytes.Buffer) 212 | buf.WriteString("REPLACE INTO ") 213 | buf.WriteString(dst + " ") 214 | buf.WriteString("SELECT * FROM ") 215 | buf.WriteString(src) 216 | buf.WriteString(";") 217 | return s.db.execStmt(&stmt{ 218 | statement: buf, 219 | }) 220 | } 221 | -------------------------------------------------------------------------------- /qson/qson.go: -------------------------------------------------------------------------------- 1 | package qson 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "time" 8 | 9 | "cloud.google.com/go/datastore" 10 | ) 11 | 12 | func isBaseType(t reflect.Type) bool { 13 | return t == typeOfTime || t == typeOfPtrKey 14 | } 15 | 16 | // Property : 17 | type Property struct { 18 | key string 19 | name string 20 | typeOf reflect.Type 21 | tag reflect.StructTag 22 | } 23 | 24 | func (p *Property) getName(name string) string { 25 | n := strings.Split(p.tag.Get(name), ",")[0] 26 | n = strings.TrimSpace(n) 27 | if n == "" { 28 | return p.key 29 | } 30 | return n 31 | } 32 | 33 | // JSON : 34 | func (p *Property) JSON() string { 35 | return p.getName("json") 36 | } 37 | 38 | // QSON : 39 | func (p *Property) QSON() string { 40 | return p.getName("qson") 41 | } 42 | 43 | // Tag : 44 | func (p *Property) Tag() reflect.StructTag { 45 | return p.tag 46 | } 47 | 48 | // Name : 49 | func (p *Property) Name() string { 50 | return p.name 51 | } 52 | 53 | // Field : 54 | type Field struct { 55 | name string 56 | operator string 57 | value interface{} 58 | } 59 | 60 | // Name : 61 | func (f Field) Name() string { 62 | return f.name 63 | } 64 | 65 | // Operator : 66 | func (f Field) Operator() string { 67 | return f.operator 68 | } 69 | 70 | // Value : 71 | func (f Field) Value() interface{} { 72 | return f.value 73 | } 74 | 75 | const ( 76 | eq = "$eq" 77 | ne = "$ne" 78 | not = "$not" 79 | gt = "$gt" 80 | gte = "$gte" 81 | lt = "$lt" 82 | lte = "$lte" 83 | like = "$like" 84 | nlike = "$nlike" 85 | in = "$in" 86 | nin = "$nin" 87 | ) 88 | 89 | func validOperator(op string) (isOk bool) { 90 | return op == eq || op == ne || op == not || 91 | op == gt || op == gte || op == lt || op == lte || 92 | op == like || op == nlike || 93 | op == in || op == nin 94 | } 95 | 96 | var ( 97 | typeOfByte = reflect.TypeOf([]byte(nil)) 98 | typeOfTime = reflect.TypeOf(time.Time{}) 99 | typeOfPtrKey = reflect.TypeOf(new(datastore.Key)) 100 | ) 101 | 102 | func convertToInterface(t reflect.Type, v interface{}) (interface{}, error) { 103 | var it interface{} 104 | 105 | switch t { 106 | case typeOfPtrKey: 107 | x, isOk := v.(string) 108 | if !isOk { 109 | return nil, unmatchDataType(t, x) 110 | } 111 | var err error 112 | it, err = datastore.DecodeKey(x) 113 | if err != nil { 114 | return nil, fmt.Errorf("qson: unable to decode %s to %v", x, t) 115 | } 116 | case typeOfByte: 117 | x, isOk := v.(string) 118 | if !isOk { 119 | return nil, unmatchDataType(t, x) 120 | } 121 | it = []byte(x) 122 | case typeOfTime: 123 | x, isOk := v.(string) 124 | if !isOk { 125 | return nil, unmatchDataType(t, x) 126 | } 127 | vv, err := time.Parse(time.RFC3339, x) 128 | if err != nil { 129 | return nil, fmt.Errorf("qson: unable to convert %s to %v", x, t) 130 | } 131 | it = vv 132 | default: 133 | switch t.Kind() { 134 | case reflect.Ptr: 135 | if v == nil { 136 | return reflect.Zero(t).Interface(), nil 137 | } 138 | x, err := convertToInterface(t.Elem(), v) 139 | if err != nil { 140 | return nil, err 141 | } 142 | vv := reflect.ValueOf(x) 143 | vi := reflect.New(vv.Type()) 144 | vi.Elem().Set(vv) 145 | it = vi.Interface() 146 | case reflect.String: 147 | x, isOk := v.(string) 148 | if !isOk { 149 | return nil, unmatchDataType(t, x) 150 | } 151 | it = x 152 | case reflect.Bool: 153 | x, isOk := v.(bool) 154 | if !isOk { 155 | return nil, unmatchDataType(t, x) 156 | } 157 | it = x 158 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 159 | x, isOk := v.(float64) 160 | if !isOk { 161 | return nil, unmatchDataType(t, v) 162 | } 163 | if x < 0 { 164 | return nil, fmt.Errorf("qson: %s value has negative value, %v", t.Kind(), x) 165 | } 166 | v := reflect.New(t).Elem() 167 | if v.OverflowUint(uint64(x)) { 168 | return nil, fmt.Errorf("qson: %s value overflow, %v", t.Kind(), x) 169 | } 170 | v.SetUint(uint64(x)) 171 | it = v.Interface() 172 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 173 | x, isOk := v.(float64) 174 | if !isOk { 175 | return nil, unmatchDataType(t, v) 176 | } 177 | v := reflect.New(t).Elem() 178 | if v.OverflowInt(int64(x)) { 179 | return nil, fmt.Errorf("qson: %s value overflow, %v", t.Kind(), x) 180 | } 181 | v.SetInt(int64(x)) 182 | it = v.Interface() 183 | case reflect.Float32, reflect.Float64: 184 | x, isOk := v.(float64) 185 | if !isOk { 186 | return nil, unmatchDataType(t, x) 187 | } 188 | v := reflect.New(t).Elem() 189 | if v.OverflowFloat(x) { 190 | return nil, fmt.Errorf("qson: %s value overflow, %v", t.Kind(), x) 191 | } 192 | v.SetFloat(x) 193 | it = v.Interface() 194 | case reflect.Slice, reflect.Array: 195 | x, isOk := v.([]interface{}) 196 | if !isOk { 197 | return nil, unmatchDataType(t, x) 198 | } 199 | for i, xx := range x { 200 | var err error 201 | x[i], err = convertToInterface(t.Elem(), xx) 202 | if err != nil { 203 | return nil, err 204 | } 205 | } 206 | it = x 207 | 208 | default: 209 | return nil, fmt.Errorf("qson: unsupported data type %v", t) 210 | } 211 | } 212 | 213 | return it, nil 214 | } 215 | 216 | func unmatchDataType(o reflect.Type, p interface{}) error { 217 | return fmt.Errorf("qson: unmatched data type of original, %v versus %v", o, reflect.TypeOf(p)) 218 | } 219 | 220 | type structScan struct { 221 | name []string 222 | typeOf reflect.Type 223 | } 224 | 225 | func getProperty(t reflect.Type) map[string]*Property { 226 | scans := append(make([]*structScan, 0), &structScan{nil, t}) 227 | props := make(map[string]*Property) 228 | 229 | for len(scans) > 0 { 230 | first := scans[0] 231 | t := first.typeOf 232 | for i := 0; i < t.NumField(); i++ { 233 | f := t.Field(i) 234 | 235 | // Skip if not anonymous private property 236 | isExported := (f.PkgPath == "") 237 | if !isExported && !f.Anonymous { 238 | continue 239 | } 240 | 241 | name := strings.Split(f.Tag.Get("json"), ",")[0] 242 | qson := strings.Split(f.Tag.Get("qson"), ",")[0] 243 | if name == "-" || qson == "-" { 244 | continue 245 | } 246 | 247 | if name == "" { 248 | name = f.Name 249 | } 250 | 251 | if f.Type.Kind() == reflect.Struct && !isBaseType(f.Type) { 252 | if f.Anonymous { 253 | if !isExported { 254 | continue 255 | } 256 | scans = append(scans, &structScan{first.name, f.Type}) 257 | continue 258 | } 259 | scans = append(scans, &structScan{append(first.name, name), f.Type}) 260 | continue 261 | } 262 | 263 | name = strings.Join(append(first.name, name), ".") 264 | p := &Property{ 265 | key: f.Name, 266 | name: name, 267 | typeOf: f.Type, 268 | tag: f.Tag, 269 | } 270 | 271 | props[name] = p 272 | } 273 | 274 | scans = scans[1:] // unshift 275 | } 276 | 277 | return props 278 | } 279 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Bug / Issue 2 | 3 | - Set primary key on insert 4 | - Primary key at insertion is wrong, always create new key 5 | - Load relative / Eager loading 6 | - Not checking present of `__key__` 7 | - Not checking `SoftDelete` 8 | - (2018-06-03) Fix empty table name when using `Flush` func. 9 | - (2018-06-10) Fix panic occur on func `StringKey` when input parameter `*datastore.Key` is `nil` 10 | - (2018-06-18) Primary key should omitted in operation `Upsert` 11 | - (2018-06-18) Fix logger `String` func is output unexpected string when using `postgres` driver 12 | - (2018-06-19) Fix flatten struct bug, flatten column using root data type instead of the subsequent data type 13 | - (2018-06-19) Fix primary key bug when using `WHERE $Key IN (?)`, key is not convert to primary key format 14 | - (2018-06-21) Fix alter table character set and collation bug, change from `ALTER TABLE xxx CONVERT TO CHARACTER SET utf8` to `ALTER TABLE xxx CHARACTER SET utf8` 15 | - (2018-06-21) Fix mysql panic even is 5.7 or above `eg: GAE return 5.7.14-google-log instead 5.7.14` will mismatch in the string comparison 16 | - (2018-06-22) Fix `Paginate` bug, model slice is appending instead of get replace 17 | - (2018-06-25) Fix struct property sequence bug 18 | - (2018-06-27) Fix struct codec, func `Select` and func `DistinctOn` 19 | - (2018-06-28) Fix `postgres` update with limit clause bug. Only mysql support `UPDATE xxx SET xxx LIMIT 10`. Postgres instead will use `UPDATE xxx SET xxx WHERE key IN (SELECT xxx FROM xxx LIMIT 10)`. 20 | - (2018-06-28) Fix `Paginate` bug, invalid cursor signature due to `qson` package didn't sort the filter fields 21 | - (2018-07-02) Fix `panic: reflect: Field index out of range` on embeded struct, code paths is invalid 22 | - (2018-07-02) Fix entity doesn't execute `Save` func even it implement `Saver` interface when it's not a pointer struct (eg: []Struct) 23 | - (2018-07-05) Fix `postgres` `GetColumns` bug, it return empty array even database have records 24 | - (2018-07-11) Fix `Update` func bug. It doesn't marshal the map[string]interface nor []interface{} to string after normalization 25 | - (2018-07-13) Fix func `Unmarshal` of data type `Date`. It suppose using `YYYY-MM-DD` format. 26 | - (2018-07-17) Fix panic when value of `WhereIn` or `WhereNotIn` contains `nil` value. 27 | - (2018-07-17) Fix `First` func bug. Entity value doesn't override if the result is empty. 28 | - (2018-07-18) Fix panic when `Where` value is pointer of `int`, `int8`, `int16`, `int32`, `uint`, `uint8`, `uint16`, `uint32`, `float32`. 29 | - (2018-07-23) Fix `DB` connection bug when passing empty port and postgres unable to establish connection thru unix socket. 30 | - (2018-08-16) Fix panic when `nil` value passing in with func `Create` or `Upsert`, it should return error instead. 31 | - (2018-08-17) Fix invalid sql statement on `Paginate()` when using next `Cursor` from `NextCursor()`. 32 | - (2018-08-23) Fix unicode string cannot save to `mysql`. 33 | - (2018-09-06) Fix incorrect mysql schema for signed and unsigned integer data type. 34 | 35 | - (2018-09-10) Fix `postgres` schema bug. Schema for unsigned integer should be greater and equal to zero instead of greater than zero. `CHECK (value >= 0)`. 36 | - (2018-09-13) Fix `newPrimaryKey` logic error. ID key with 0 shouldn't nested again. 37 | - (2018-09-21) Fix `Paginate` soft delete bugs. Soft deleted records shouldn't get from api `Paginate`. 38 | - (2018-10-06) Fix `Date` not able to decode when it's nested inside struct, fix json `null` not able to decode back to value (Array, Slice, Struct, GeoPoint, etc) and improve string concatenate performance. 39 | - (2018-12-31) Change id key random algorithms. 40 | - (2018-12-31) Remove signature checking on pagination 41 | 42 | # Breaking Changes 43 | 44 | - Drop `Count` api. 45 | - Drop `Union` api. 46 | - Drop single `Update` api. 47 | - Drop `SetDebug(boolean)` api. 48 | - Drop `datastore` support. 49 | - `Delete` function using entity model instead of `*datastore.Key`. 50 | - Rename params in function `RunInTransaction` from `*goloquent.Connection` to `*goloquent.DB` 51 | - Rename api `LockForUpdate` to `WLock`. 52 | - Rename api `LockForShared` to `RLock`. 53 | - Change function single entity `Update` to `Save` 54 | - Change `Loader` interface `Load([]datastore.Property) error` to `Load() error` 55 | - Change `Saver` interface `Save() ([]datastore.Property,error)` to `Save() error` 56 | - Change second parameter **parentKey** `*datastore.Key` to optional on function `Create` nor `Upsert` 57 | - (2018-06-16) No longer support mysql 5.6 and below (at least 5.7) 58 | - (2018-06-19) Table is now by default using `utf8mb4` encoding 59 | - (2018-06-20) Replace `Next` func in `Pagination` struct with `NextCursor` 60 | - (2018-06-21) Support extra option `datatype`, `charset`, `collate` on struct property, but it only limited to datatype of `string` 61 | - (2018-06-21) Allow `*` on func `Select` 62 | - (2018-06-24) Replace offset pagination with cursor pagination 63 | - (2018-07-05) Rename api `WhereNe` to `WhereNotEqual`. 64 | - (2018-07-08) Rename api `WhereEq` to `WhereEqual`. 65 | - (2018-07-08) Replace return parameter `Query` to `Table` on func `Table` of `goloquent.DB` 66 | - (2018-07-17) Expose operator to public. 67 | - (2018-08-24) Change api `AnyOfAncestor` params data type from `[]*datastore.Key` to `...*datastore.Key`. 68 | - (2018-08-24) Change `Truncate` api to support multiple entity. 69 | - (2018-08-24) Increase maximum limit of `Pagination` to `10000`. 70 | - (2018-09-02) Change api `WhereIn` and `WhereNotIn` value param from `[]interface{}` to `interface{}`. 71 | - (2018-09-10) `Date` no longer convert to UTC before format. 72 | - (2018-09-10) `Date` will have default value `"0001-01-01"` if it's not pointer. 73 | - (2018-09-13) Name key will escape using `url.PathEscape` to avoid misinterpret when it contains symbol characters. 74 | 75 | # New Features 76 | 77 | - Introduce `Select` api. 78 | - Introduce `DistinctOn` api. 79 | - Introduce `Lock` api. 80 | - Introduce `Truncate` api. 81 | - Introduce `Flush` api. 82 | - Introduce package `db`. 83 | - Introduce package `qson`. (Query JSON) 84 | - Replace statement debug using `LogHandler`. 85 | - Support unsigned integer, uint, uint8, uint16, uint32, uint64 86 | - Support any pointer of base data type and struct 87 | - (2018-06-14) Support **Postgres** driver. 88 | - (2018-06-18) Introduce `Scan` api. 89 | - (2018-06-22) Introduce hard delete api `Destroy`. 90 | - (2018-06-24) Introduce `Unscoped` api. 91 | - (2018-07-05) Support **JSON** filter. 92 | - (2018-07-05) Introduce `WhereJSONEqual` api. 93 | - (2018-07-08) Introduce new struct `Table` with new api, such as `Exists`, `DropIfExists`, `Truncate`, `AddIndex`, `AddUniqueIndex` 94 | - (2018-07-08) Introduce new data type `Date`. 95 | - (2018-07-17) Enhance data type `Date`, add func `MarshalText` and `UnmarshalText`. 96 | - (2018-07-18) Introduce JSON filtering api `WhereJSON`, `WhereJSONNotEqual`, `WhereJSONIn`, `WhereNotIn`, `WhereJSONContainAny`, `WhereJSONType`, `WhereJSONIsObject`, `WhereJSONIsArray` 97 | - (2018-08-16) Introduce new api `AnyOfAncestor` and `WhereAnyLike`. 98 | - (2018-08-24) Introduce new api `ReplaceInto` for `mysql` driver. 99 | - (2018-09-10) Support `json.RawMessage` for `mysql` driver. 100 | - (2018-09-18) Introduce new api `InsertInto`. 101 | - (2018-09-18) Enable api `Migrate` and `Create` to `Table`. 102 | 103 | -------------------------------------------------------------------------------- /iterator.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | "strings" 9 | "time" 10 | 11 | "cloud.google.com/go/datastore" 12 | ) 13 | 14 | // Loader : 15 | type Loader interface { 16 | Load() error 17 | } 18 | 19 | // Saver : 20 | type Saver interface { 21 | Save() error 22 | } 23 | 24 | // Iterator : 25 | type Iterator struct { 26 | table string 27 | stmt *Stmt 28 | sign string 29 | position int // current record position 30 | columns []string 31 | results []map[string][]byte 32 | } 33 | 34 | func (it *Iterator) patchKey() { 35 | pos := len(it.results) - 1 36 | l := it.results[pos] 37 | if _, isOk := l[pkColumn]; !isOk { 38 | return 39 | } 40 | paths := bytes.Split(l[pkColumn], []byte(keyDelimeter)) 41 | last := len(paths) - 1 42 | kk := paths[last] 43 | paths = paths[:last] 44 | buf := new(bytes.Buffer) 45 | buf.Write(bytes.Join(paths, []byte(keyDelimeter))) 46 | buf.WriteString(keyDelimeter) 47 | if bytes.Contains(kk, []byte(",")) { 48 | buf.Write(kk) 49 | } else { 50 | buf.WriteString(it.table + ",") 51 | buf.Write(kk) 52 | } 53 | l[keyFieldName] = buf.Bytes() 54 | it.results[pos] = l 55 | } 56 | 57 | func toByte(v interface{}) []byte { 58 | var b []byte 59 | switch vi := v.(type) { 60 | case nil: 61 | case time.Time: 62 | b = []byte(vi.Format("2006-01-02 15:04:05")) 63 | case []byte: 64 | b = vi 65 | default: 66 | b = []byte(fmt.Sprintf("%v", vi)) 67 | } 68 | return b 69 | } 70 | 71 | func (it *Iterator) put(pos int, k string, v interface{}) error { 72 | diff := pos - len(it.results) + 1 73 | for i := 0; i < diff; i++ { 74 | it.results = append(it.results, make(map[string][]byte)) 75 | } 76 | l := it.results[pos] 77 | l[k] = toByte(v) 78 | it.results[pos] = l 79 | return nil 80 | } 81 | 82 | // First : 83 | func (it *Iterator) First() *Iterator { 84 | it.position = 0 85 | if len(it.results) <= 0 { 86 | return nil 87 | } 88 | return it 89 | } 90 | 91 | // Last : 92 | func (it *Iterator) Last() *Iterator { 93 | i := len(it.results) - 1 94 | if i < 0 { 95 | return nil 96 | } 97 | it.position = i 98 | return it 99 | } 100 | 101 | // Get : get value by key 102 | func (it Iterator) Get(k string) []byte { 103 | l := it.results[it.position] 104 | return l[k] 105 | } 106 | 107 | // Count : return the records count 108 | func (it Iterator) Count() uint { 109 | return uint(len(it.results)) 110 | } 111 | 112 | func (it *Iterator) signature() string { 113 | if it.sign == "" { 114 | it.sign = sha1Sign(it.stmt) 115 | } 116 | return it.sign 117 | } 118 | 119 | // Cursor : 120 | func (it *Iterator) Cursor() (Cursor, error) { 121 | if it.position+1 > len(it.results)-1 { 122 | return Cursor{}, fmt.Errorf("goloquent: interator out of index result range") 123 | } 124 | key, err := parseKey(string(it.results[it.position+1][keyFieldName])) 125 | if err != nil { 126 | return Cursor{}, fmt.Errorf("goloquent: missing cursor key") 127 | } 128 | c := Cursor{ 129 | Signature: it.signature(), 130 | Key: key, 131 | } 132 | c.cc, _ = json.Marshal(c) 133 | return c, nil 134 | } 135 | 136 | // Next : go next record 137 | func (it *Iterator) Next() bool { 138 | it.position++ 139 | if it.position > len(it.results)-1 { 140 | return false 141 | } 142 | return true 143 | } 144 | 145 | func (it *Iterator) scan(src interface{}) (map[string]interface{}, error) { 146 | v := reflect.ValueOf(src) 147 | if v.Type().Kind() != reflect.Ptr { 148 | return nil, fmt.Errorf("goloquent: struct is not addressable") 149 | } 150 | codec, err := getStructCodec(src) 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | nv := reflect.New(v.Type().Elem()) 156 | data := make(map[string]interface{}) 157 | for _, f := range codec.fields { 158 | fv := getField(nv.Elem(), f.paths) 159 | props := getTypes(nil, f, f.isFlatten()) 160 | for i, p := range props { 161 | k := p.Name() 162 | b := it.Get(k) 163 | var vv, err = valueToInterface(p.typeOf, b, false) 164 | if err != nil { 165 | return nil, err 166 | } 167 | props[i].Value = vv 168 | } 169 | 170 | vi := denormalize(f, props) 171 | data[f.name] = vi 172 | if err := loadField(fv, vi); err != nil { 173 | return nil, err 174 | } 175 | } 176 | 177 | if l, isOk := nv.Interface().(Loader); isOk { 178 | if err := l.Load(); err != nil { 179 | return nil, fmt.Errorf("goloquent: %v", err) 180 | } 181 | } 182 | 183 | v.Elem().Set(nv.Elem()) 184 | return data, nil 185 | } 186 | 187 | // Scan : set the model value 188 | func (it *Iterator) Scan(src interface{}) error { 189 | if _, err := it.scan(src); err != nil { 190 | return err 191 | } 192 | return nil 193 | } 194 | 195 | func getField(v reflect.Value, path []int) reflect.Value { 196 | for i, p := range path { 197 | v = v.Field(p) 198 | if v.Kind() == reflect.Ptr { 199 | if v.IsNil() { 200 | v.Set(reflect.New(v.Type().Elem())) 201 | } 202 | if i < len(path)-1 { 203 | v = v.Elem() 204 | } 205 | } 206 | } 207 | return v 208 | } 209 | 210 | // Property : 211 | type Property struct { 212 | name []string 213 | typeOf reflect.Type 214 | Value interface{} 215 | } 216 | 217 | func (p Property) isZero() bool { 218 | return interfaceIsZero(p.Value) 219 | } 220 | 221 | // Name : 222 | func (p Property) Name() string { 223 | return strings.Join(p.name, ".") 224 | } 225 | 226 | // Interface : 227 | func (p Property) Interface() (interface{}, error) { 228 | vv, err := interfaceToValue(p.Value) 229 | if err != nil { 230 | return nil, err 231 | } 232 | vv, err = marshal(vv) 233 | if err != nil { 234 | return nil, err 235 | } 236 | return vv, err 237 | } 238 | 239 | func getTypes(ns []string, f field, isFlatten bool) []Property { 240 | props := make([]Property, 0) 241 | if isFlatten && f.StructCodec != nil { 242 | codec := f.StructCodec 243 | for _, sf := range codec.fields { 244 | dd := getTypes(append(ns, f.name), sf, isFlatten) 245 | props = append(props, dd...) 246 | } 247 | return props 248 | } 249 | 250 | t := f.typeOf 251 | root := f.getRoot() 252 | if isFlatten { 253 | if root.isSlice() && root.typeOf != f.typeOf { 254 | t = reflect.MakeSlice(reflect.SliceOf(t), 0, 0).Type() 255 | } 256 | } 257 | 258 | d := Property{append(ns, f.name), t, nil} 259 | props = append(props, d) 260 | return props 261 | } 262 | 263 | func interfaceIsZero(it interface{}) bool { 264 | var zero bool 265 | switch vi := it.(type) { 266 | case string: 267 | zero = len(vi) == 0 268 | case bool: 269 | zero = vi == false 270 | case []byte: 271 | zero = len(vi) == 0 272 | case json.RawMessage: 273 | zero = len(vi) == 0 274 | case int64: 275 | zero = vi == 0 276 | case uint64: 277 | zero = vi == 0 278 | case float64: 279 | zero = vi == 0 280 | case SoftDelete: 281 | zero = vi == SoftDelete(nil) 282 | case *datastore.Key: 283 | zero = vi == nil || (*vi) == datastore.Key{} 284 | case datastore.GeoPoint: 285 | zero = vi == datastore.GeoPoint{} 286 | case time.Time: 287 | zero = vi == time.Time{} 288 | case []interface{}: 289 | zero = len(vi) == 0 290 | case map[string]interface{}: 291 | if len(vi) == 0 { 292 | return true 293 | } 294 | allZero := true 295 | for _, v := range vi { 296 | if isZero := interfaceIsZero(v); !isZero { 297 | return false 298 | } 299 | } 300 | zero = allZero 301 | default: 302 | vv := reflect.ValueOf(vi) 303 | if vv.Type().Kind() == reflect.Ptr && vv.IsNil() { 304 | return true 305 | } 306 | 307 | return reflect.DeepEqual(it, reflect.Zero(vv.Type()).Interface()) 308 | } 309 | return zero 310 | } 311 | -------------------------------------------------------------------------------- /encoder.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "reflect" 8 | "time" 9 | 10 | "cloud.google.com/go/datastore" 11 | ) 12 | 13 | func flatMap(data map[string]interface{}) map[string]interface{} { 14 | for k, value := range data { 15 | switch vi := value.(type) { 16 | case map[string]interface{}: 17 | fm := flatMap(vi) 18 | for kk, nv := range fm { 19 | data[k+"."+kk] = nv 20 | } 21 | delete(data, k) 22 | default: 23 | data[k] = value 24 | } 25 | } 26 | 27 | return data 28 | } 29 | 30 | func normalize(f field, it interface{}) ([]Property, error) { 31 | props := make([]Property, 0) 32 | if f.isFlatten() { 33 | if f.isSlice() { 34 | vals := make(map[string][]interface{}) 35 | for _, vi := range it.([]interface{}) { 36 | l := flatMap(vi.(map[string]interface{})) 37 | for k, vv := range l { 38 | name := f.name + "." + k 39 | arr := vals[name] 40 | arr = append(arr, vv) 41 | vals[name] = arr 42 | } 43 | } 44 | 45 | for k, vv := range vals { 46 | props = append(props, Property{[]string{k}, f.typeOf, vv}) 47 | } 48 | return props, nil 49 | } 50 | 51 | for k, vv := range flatMap(it.(map[string]interface{})) { 52 | props = append(props, Property{[]string{f.name, k}, f.typeOf, vv}) 53 | } 54 | return props, nil 55 | } 56 | 57 | props = append(props, Property{[]string{f.name}, f.typeOf, it}) 58 | return props, nil 59 | } 60 | 61 | func marshal(it interface{}) (interface{}, error) { 62 | switch v := it.(type) { 63 | case []interface{}, map[string]interface{}, json.RawMessage: 64 | b, err := json.Marshal(v) 65 | if err != nil { 66 | return nil, fmt.Errorf("goloquent: unable to marshal the value %v", v) 67 | } 68 | return b2s(b), nil 69 | } 70 | return it, nil 71 | } 72 | 73 | // SaveStruct : 74 | func SaveStruct(src interface{}) (map[string]Property, error) { 75 | vi := reflect.Indirect(reflect.ValueOf(src)) 76 | vv := reflect.New(vi.Type()) 77 | vv.Elem().Set(vi) // copy the value to new struct 78 | 79 | ety, err := newEntity(vv.Interface()) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | data := make(map[string]Property) 85 | for _, f := range ety.codec.fields { 86 | fv := getFieldByIndex(vv.Elem(), f.paths) 87 | var it, err = saveField(f, fv) 88 | if err != nil { 89 | return nil, err 90 | } 91 | props, err := normalize(f, it) 92 | if err != nil { 93 | return nil, err 94 | } 95 | for _, p := range props { 96 | data[p.Name()] = p 97 | } 98 | } 99 | 100 | vi.Set(vv.Elem()) 101 | return data, nil 102 | } 103 | 104 | func mapToValue(data map[string]interface{}) (map[string]interface{}, error) { 105 | for k, val := range data { 106 | var ( 107 | it interface{} 108 | err error 109 | ) 110 | switch vi := val.(type) { 111 | case map[string]interface{}: 112 | it, err = mapToValue(vi) 113 | if err != nil { 114 | return nil, err 115 | } 116 | default: 117 | it, err = interfaceToValue(val) 118 | if err != nil { 119 | return nil, err 120 | } 121 | } 122 | 123 | data[k] = it 124 | } 125 | return data, nil 126 | } 127 | 128 | // LoadStruct : 129 | func LoadStruct(src interface{}, data map[string]interface{}) error { 130 | v := reflect.ValueOf(src) 131 | if v.Type().Kind() != reflect.Ptr { 132 | return fmt.Errorf("goloquent: struct is not addressable") 133 | } 134 | codec, err := getStructCodec(src) 135 | if err != nil { 136 | return err 137 | } 138 | 139 | nv := reflect.New(v.Type().Elem()) 140 | for _, f := range codec.fields { 141 | fv := getField(nv.Elem(), f.paths) 142 | if err := loadField(fv, data[f.name]); err != nil { 143 | return err 144 | } 145 | } 146 | 147 | v.Elem().Set(nv.Elem()) 148 | return nil 149 | } 150 | 151 | func interfaceToValue(it interface{}) (interface{}, error) { 152 | var value interface{} 153 | 154 | switch vi := it.(type) { 155 | case nil: 156 | value = vi 157 | case string: 158 | value = vi 159 | case bool: 160 | value = vi 161 | case int, int8, int16, int32, int64: 162 | value = vi 163 | case uint, uint8, uint16, uint32, uint64: 164 | value = vi 165 | case float32, float64: 166 | value = vi 167 | case json.RawMessage: 168 | value = vi 169 | case []byte: 170 | value = base64.StdEncoding.EncodeToString(vi) 171 | case *datastore.Key: 172 | str := stringifyKey(vi) 173 | if str == "" { 174 | value = nil 175 | } else { 176 | value = str 177 | } 178 | case SoftDelete: 179 | vv := reflect.ValueOf(it) 180 | if vv.IsNil() { 181 | return nil, nil 182 | } 183 | value = (*SoftDelete(vi)).UTC().Format("2006-01-02 15:04:05") 184 | case Date: 185 | value = time.Time(vi).Format("2006-01-02") 186 | case time.Time: 187 | value = vi.UTC().Format("2006-01-02 15:04:05") 188 | case geoLocation: 189 | b, _ := json.Marshal(vi) 190 | value = json.RawMessage(b) 191 | case []interface{}: 192 | slice := make([]interface{}, 0, len(vi)) 193 | for _, elem := range vi { 194 | s, err := interfaceToValue(elem) 195 | if err != nil { 196 | return nil, err 197 | } 198 | slice = append(slice, s) 199 | } 200 | value = slice 201 | case map[string]interface{}: // Nested struct 202 | var list, err = mapToValue(vi) 203 | if err != nil { 204 | return nil, err 205 | } 206 | value = list 207 | default: 208 | vv := reflect.ValueOf(it) 209 | if vv.Type().Kind() != reflect.Ptr { 210 | return nil, fmt.Errorf("goloquent: invalid data type %v", vv.Type()) 211 | } 212 | if vv.IsNil() { 213 | return nil, nil 214 | } 215 | it, err := interfaceToValue(vv.Elem().Interface()) 216 | if err != nil { 217 | return nil, err 218 | } 219 | return it, nil 220 | } 221 | 222 | return value, nil 223 | } 224 | 225 | func saveSliceField(f field, v reflect.Value) ([]interface{}, error) { 226 | // if it's struct, f.StructCodec is not nil 227 | if v.Len() <= 0 { 228 | return make([]interface{}, 0), nil 229 | } 230 | 231 | slice := make([]interface{}, 0) 232 | for i := 0; i < v.Len(); i++ { 233 | var it, err = saveField(f, v.Index(i)) 234 | if err != nil { 235 | return nil, err 236 | } 237 | slice = append(slice, it) 238 | } 239 | 240 | return slice, nil 241 | } 242 | 243 | func saveStructField(sc *StructCodec, v reflect.Value) (map[string]interface{}, error) { 244 | data := make(map[string]interface{}) 245 | for _, f := range sc.fields { 246 | fv := getFieldByIndex(v, f.paths) 247 | if !fv.IsValid() { 248 | continue 249 | } 250 | 251 | var it, err = saveField(f, fv) 252 | if err != nil { 253 | return nil, err 254 | } 255 | 256 | data[f.name] = it 257 | } 258 | 259 | return data, nil 260 | } 261 | 262 | func saveField(f field, v reflect.Value) (interface{}, error) { 263 | var it interface{} 264 | t := v.Type() 265 | 266 | switch vi := v.Interface().(type) { 267 | case *datastore.Key, time.Time: 268 | it = vi 269 | case json.RawMessage: 270 | if vi == nil { 271 | return json.RawMessage("null"), nil 272 | } 273 | it = vi 274 | case Date: 275 | it = vi 276 | case datastore.GeoPoint: 277 | it = geoLocation{vi.Lat, vi.Lng} 278 | case SoftDelete: 279 | if v.IsNil() { 280 | return reflect.Zero(typeOfSoftDelete).Interface(), nil 281 | } 282 | it = vi 283 | default: 284 | switch t.Kind() { 285 | case reflect.String: 286 | it = v.String() 287 | case reflect.Bool: 288 | it = v.Bool() 289 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 290 | it = v.Int() 291 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 292 | it = v.Uint() 293 | case reflect.Float32, reflect.Float64: 294 | it = v.Float() 295 | case reflect.Slice, reflect.Array: 296 | if t.Elem().Kind() == reflect.Uint8 { 297 | it = v.Bytes() 298 | } else { 299 | v = initSlice(v) // initialize the slice if it's nil 300 | return saveSliceField(f, v) 301 | } 302 | case reflect.Ptr: 303 | elem := t.Elem() 304 | if isBaseType(elem) { 305 | if v.IsNil() { // return zero which has datatype 306 | return reflect.Zero(t).Interface(), nil 307 | } 308 | return saveField(f, v.Elem()) 309 | } 310 | if elem.Kind() != reflect.Struct { 311 | return nil, fmt.Errorf("goloquent: unsupported struct field data type %q", t.String()) 312 | } 313 | if v.IsNil() { 314 | return reflect.Zero(t).Interface(), nil 315 | } 316 | v = v.Elem() 317 | fallthrough 318 | case reflect.Struct: 319 | data, err := saveStructField(f.StructCodec, v) 320 | if err != nil { 321 | return nil, err 322 | } 323 | it = data 324 | 325 | default: 326 | return nil, fmt.Errorf("goloquent: unsupported struct field data type %q", t.String()) 327 | } 328 | } 329 | 330 | return it, nil 331 | } 332 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | "reflect" 9 | "strings" 10 | "time" 11 | 12 | "cloud.google.com/go/datastore" 13 | ) 14 | 15 | var isPkSimple = true 16 | 17 | // SetPKSimple : 18 | func SetPKSimple(flag bool) { 19 | isPkSimple = flag 20 | } 21 | 22 | // TransactionHandler : 23 | type TransactionHandler func(*DB) error 24 | 25 | // LogHandler : 26 | type LogHandler func(*Stmt) 27 | 28 | // NativeHandler : 29 | type NativeHandler func(*sql.DB) 30 | 31 | // public constant variables : 32 | const ( 33 | pkLen = 512 34 | pkColumn = "$Key" 35 | softDeleteColumn = "$Deleted" 36 | keyDelimeter = "/" 37 | ) 38 | 39 | // CommonError : 40 | var ( 41 | ErrNoSuchEntity = fmt.Errorf("goloquent: entity not found") 42 | ErrInvalidCursor = fmt.Errorf("goloquent: invalid cursor") 43 | ) 44 | 45 | // Config : 46 | type Config struct { 47 | Username string 48 | Password string 49 | Host string 50 | Port string 51 | Database string 52 | UnixSocket string 53 | TLSConfig string 54 | CharSet *CharSet 55 | Logger LogHandler 56 | } 57 | 58 | // Normalize : 59 | func (c *Config) Normalize() { 60 | c.Username = strings.TrimSpace(c.Username) 61 | c.Host = strings.TrimSpace(strings.ToLower(c.Host)) 62 | c.Port = strings.TrimSpace(c.Port) 63 | c.Database = strings.TrimSpace(c.Database) 64 | c.UnixSocket = strings.TrimSpace(c.UnixSocket) 65 | c.TLSConfig = strings.TrimSpace(c.TLSConfig) 66 | if c.CharSet != nil && c.CharSet.Encoding != "" && c.CharSet.Collation != "" { 67 | c.CharSet.Collation = strings.TrimSpace(c.CharSet.Collation) 68 | c.CharSet.Encoding = strings.TrimSpace(c.CharSet.Encoding) 69 | } else { 70 | charset := utf8mb4CharSet 71 | c.CharSet = &charset 72 | } 73 | } 74 | 75 | // Replacer : 76 | type Replacer interface { 77 | Upsert(model interface{}, k ...*datastore.Key) error 78 | Save(model interface{}) error 79 | } 80 | 81 | // Client : 82 | type Client struct { 83 | driver string 84 | sqlCommon 85 | CharSet 86 | dialect Dialect 87 | logger LogHandler 88 | } 89 | 90 | func (c Client) consoleLog(s *Stmt) { 91 | if c.logger != nil { 92 | c.logger(s) 93 | } 94 | } 95 | 96 | func (c *Client) compileStmt(query string, args ...interface{}) *Stmt { 97 | buf := new(bytes.Buffer) 98 | buf.WriteString(query) 99 | ss := &Stmt{ 100 | stmt: stmt{ 101 | statement: buf, 102 | arguments: args, 103 | }, 104 | replacer: c.dialect, 105 | } 106 | return ss 107 | } 108 | 109 | func (c Client) execStmt(s *stmt) error { 110 | ss := &Stmt{ 111 | stmt: *s, 112 | replacer: c.dialect, 113 | } 114 | ss.startTrace() 115 | defer func() { 116 | ss.stopTrace() 117 | c.consoleLog(ss) 118 | }() 119 | result, err := c.PrepareExec(ss.Raw(), ss.arguments...) 120 | if err != nil { 121 | return err 122 | } 123 | ss.Result = result 124 | return nil 125 | } 126 | 127 | func (c Client) execQuery(s *stmt) (*sql.Rows, error) { 128 | ss := &Stmt{ 129 | stmt: *s, 130 | replacer: c.dialect, 131 | } 132 | ss.startTrace() 133 | defer func() { 134 | ss.stopTrace() 135 | c.consoleLog(ss) 136 | }() 137 | var rows, err = c.Query(ss.Raw(), ss.arguments...) 138 | if err != nil { 139 | return nil, err 140 | } 141 | return rows, nil 142 | } 143 | 144 | func (c *Client) execQueryRow(s *stmt) *sql.Row { 145 | ss := &Stmt{ 146 | stmt: *s, 147 | replacer: c.dialect, 148 | } 149 | ss.startTrace() 150 | defer func() { 151 | ss.stopTrace() 152 | c.consoleLog(ss) 153 | }() 154 | return c.QueryRow(ss.Raw(), ss.arguments...) 155 | } 156 | 157 | // PrepareExec : 158 | func (c Client) PrepareExec(query string, args ...interface{}) (sql.Result, error) { 159 | conn, err := c.sqlCommon.Prepare(query) 160 | if err != nil { 161 | return nil, fmt.Errorf("goloquent: unable to prepare sql statement : %v", err) 162 | } 163 | defer conn.Close() 164 | result, err := conn.Exec(args...) 165 | if err != nil { 166 | return nil, fmt.Errorf("goloquent: %v", err) 167 | } 168 | return result, nil 169 | } 170 | 171 | // Exec : 172 | func (c Client) Exec(query string, args ...interface{}) (sql.Result, error) { 173 | result, err := c.sqlCommon.Exec(query, args...) 174 | if err != nil { 175 | return nil, fmt.Errorf("goloquent: %v", err) 176 | } 177 | return result, nil 178 | } 179 | 180 | // Query : 181 | func (c Client) Query(query string, args ...interface{}) (*sql.Rows, error) { 182 | rows, err := c.sqlCommon.Query(query, args...) 183 | if err != nil { 184 | return nil, fmt.Errorf("goloquent: %v", err) 185 | } 186 | return rows, nil 187 | } 188 | 189 | // QueryRow : 190 | func (c Client) QueryRow(query string, args ...interface{}) *sql.Row { 191 | return c.sqlCommon.QueryRow(query, args...) 192 | } 193 | 194 | // DB : 195 | type DB struct { 196 | id string 197 | driver string 198 | name string 199 | replica string 200 | client Client 201 | dialect Dialect 202 | omits []string 203 | } 204 | 205 | // NewDB : 206 | func NewDB(driver string, charset CharSet, conn sqlCommon, dialect Dialect, logHandler LogHandler) *DB { 207 | client := Client{ 208 | driver: driver, 209 | sqlCommon: conn, 210 | CharSet: charset, 211 | dialect: dialect, 212 | logger: logHandler, 213 | } 214 | dialect.SetDB(client) 215 | return &DB{ 216 | id: fmt.Sprintf("%s:%d", driver, time.Now().UnixNano()), 217 | driver: driver, 218 | name: dialect.CurrentDB(), 219 | client: client, 220 | dialect: dialect, 221 | } 222 | } 223 | 224 | // clone a new connection 225 | func (db *DB) clone() *DB { 226 | return &DB{ 227 | id: db.id, 228 | driver: db.driver, 229 | name: db.name, 230 | replica: fmt.Sprintf("%d", time.Now().Unix()), 231 | client: db.client, 232 | dialect: db.dialect, 233 | } 234 | } 235 | 236 | // ID : 237 | func (db DB) ID() string { 238 | return db.id 239 | } 240 | 241 | // Name : 242 | func (db DB) Name() string { 243 | return db.name 244 | } 245 | 246 | // NewQuery : 247 | func (db *DB) NewQuery() *Query { 248 | return newQuery(db) 249 | } 250 | 251 | // Query : 252 | func (db *DB) Query(stmt string, args ...interface{}) (*sql.Rows, error) { 253 | return db.client.Query(stmt, args...) 254 | } 255 | 256 | // Exec : 257 | func (db *DB) Exec(stmt string, args ...interface{}) (sql.Result, error) { 258 | return db.client.Exec(stmt, args...) 259 | } 260 | 261 | // Table : 262 | func (db *DB) Table(name string) *Table { 263 | return &Table{name, db} 264 | } 265 | 266 | // Migrate : 267 | func (db *DB) Migrate(model ...interface{}) error { 268 | return newBuilder(db.NewQuery()).migrateMultiple(model) 269 | } 270 | 271 | // Omit : 272 | func (db *DB) Omit(fields ...string) Replacer { 273 | ff := newDictionary(fields) 274 | clone := db.clone() 275 | ff.delete(keyFieldName) 276 | ff.delete(pkColumn) 277 | clone.omits = ff.keys() 278 | return clone 279 | } 280 | 281 | // Create : 282 | func (db *DB) Create(model interface{}, parentKey ...*datastore.Key) error { 283 | if parentKey == nil { 284 | return newBuilder(db.NewQuery()).put(model, nil) 285 | } 286 | return newBuilder(db.NewQuery()).put(model, parentKey) 287 | } 288 | 289 | // Upsert : 290 | func (db *DB) Upsert(model interface{}, parentKey ...*datastore.Key) error { 291 | if parentKey == nil { 292 | return newBuilder(db.NewQuery().Omit(db.omits...)).upsert(model, nil) 293 | } 294 | return newBuilder(db.NewQuery().Omit(db.omits...)).upsert(model, parentKey) 295 | } 296 | 297 | // Save : 298 | func (db *DB) Save(model interface{}) error { 299 | if err := checkSinglePtr(model); err != nil { 300 | return err 301 | } 302 | return newBuilder(db.NewQuery().Omit(db.omits...)).save(model) 303 | } 304 | 305 | // Delete : 306 | func (db *DB) Delete(model interface{}) error { 307 | return newBuilder(db.NewQuery()).delete(model, true) 308 | } 309 | 310 | // Destroy : 311 | func (db *DB) Destroy(model interface{}) error { 312 | return newBuilder(db.NewQuery()).delete(model, false) 313 | } 314 | 315 | // Truncate : 316 | func (db *DB) Truncate(model ...interface{}) error { 317 | ns := make([]string, 0, len(model)) 318 | for _, m := range model { 319 | var table string 320 | v := reflect.Indirect(reflect.ValueOf(m)) 321 | switch v.Type().Kind() { 322 | case reflect.String: 323 | table = v.String() 324 | case reflect.Struct: 325 | table = v.Type().Name() 326 | default: 327 | return errors.New("goloquent: unsupported model") 328 | } 329 | 330 | table = strings.TrimSpace(table) 331 | if table == "" { 332 | return errors.New("goloquent: missing table name") 333 | } 334 | ns = append(ns, table) 335 | } 336 | return newBuilder(db.NewQuery()).truncate(ns...) 337 | } 338 | 339 | // Select : 340 | func (db *DB) Select(fields ...string) *Query { 341 | return db.NewQuery().Select(fields...) 342 | } 343 | 344 | // Find : 345 | func (db *DB) Find(key *datastore.Key, model interface{}) error { 346 | return db.NewQuery().Find(key, model) 347 | } 348 | 349 | // First : 350 | func (db *DB) First(model interface{}) error { 351 | return db.NewQuery().First(model) 352 | } 353 | 354 | // Get : 355 | func (db *DB) Get(model interface{}) error { 356 | return db.NewQuery().Get(model) 357 | } 358 | 359 | // Paginate : 360 | func (db *DB) Paginate(p *Pagination, model interface{}) error { 361 | return db.NewQuery().Paginate(p, model) 362 | } 363 | 364 | // Ancestor : 365 | func (db *DB) Ancestor(ancestor *datastore.Key) *Query { 366 | return db.NewQuery().Ancestor(ancestor) 367 | } 368 | 369 | // AnyOfAncestor : 370 | func (db *DB) AnyOfAncestor(ancestors ...*datastore.Key) *Query { 371 | return db.NewQuery().AnyOfAncestor(ancestors...) 372 | } 373 | 374 | // Where : 375 | func (db *DB) Where(field string, operator string, value interface{}) *Query { 376 | return db.NewQuery().Where(field, operator, value) 377 | } 378 | 379 | // Where : 380 | func (db *DB) MatchAgainst(fields []string, value ...string) *Query { 381 | return db.NewQuery().MatchAgainst(fields, value...) 382 | } 383 | 384 | // RunInTransaction : 385 | func (db *DB) RunInTransaction(cb TransactionHandler) error { 386 | return newBuilder(db.NewQuery()).runInTransaction(cb) 387 | } 388 | 389 | // Close : 390 | func (db *DB) Close() error { 391 | x, isOk := db.client.sqlCommon.(*sql.DB) 392 | if !isOk { 393 | return nil 394 | } 395 | return x.Close() 396 | } 397 | -------------------------------------------------------------------------------- /struct_codec.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "reflect" 7 | "sort" 8 | "strings" 9 | "time" 10 | "unicode" 11 | 12 | "cloud.google.com/go/datastore" 13 | ) 14 | 15 | var ( 16 | typeOfByte = reflect.TypeOf([]byte(nil)) 17 | typeOfTime = reflect.TypeOf(time.Time{}) 18 | typeOfPtrKey = reflect.TypeOf(&datastore.Key{}) 19 | typeOfGeoPoint = reflect.TypeOf(datastore.GeoPoint{}) 20 | typeOfSoftDelete = reflect.TypeOf(SoftDelete(nil)) 21 | typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage(nil)) 22 | ) 23 | 24 | type field struct { 25 | tag 26 | names []string 27 | parent *field 28 | paths []int 29 | sequence []int 30 | typeOf reflect.Type 31 | isPtrChild bool 32 | *StructCodec 33 | } 34 | 35 | func newField(st tag, parent *field, path []int, sequence []int, t reflect.Type, isPtr bool, s *StructCodec) field { 36 | return field{ 37 | tag: st, 38 | parent: parent, 39 | paths: path, 40 | sequence: sequence, 41 | typeOf: t, 42 | isPtrChild: isPtr, 43 | StructCodec: s, 44 | } 45 | } 46 | 47 | func (f field) getFullPath() []field { 48 | fields := append(make([]field, 0), f) 49 | parent := f.parent 50 | for parent != nil { 51 | fields = append(fields, *parent) 52 | if parent.parent == nil { 53 | break 54 | } 55 | parent = parent.parent 56 | } 57 | return fields 58 | } 59 | 60 | func (f field) getRoot() *field { 61 | parent := f.parent 62 | if parent == nil { 63 | return &f 64 | } 65 | 66 | for parent != nil { 67 | if parent.parent == nil { 68 | break 69 | } 70 | parent = parent.parent 71 | } 72 | 73 | return parent 74 | } 75 | 76 | func (f field) isFlatten() bool { 77 | root := f.getRoot() 78 | return root.StructCodec != nil && f.tag.isFlatten() 79 | } 80 | 81 | func (f field) isSlice() bool { 82 | k := f.typeOf.Kind() 83 | return f.typeOf != typeOfByte && (k == reflect.Slice || k == reflect.Array) 84 | } 85 | 86 | // StructCodec : 87 | type StructCodec struct { 88 | parentField *field 89 | value reflect.Value 90 | fields []field 91 | } 92 | 93 | func newStructCodec(v reflect.Value) *StructCodec { 94 | return &StructCodec{ 95 | value: v, 96 | } 97 | } 98 | 99 | func (sc *StructCodec) findField(name string) (*field, error) { 100 | for _, f := range sc.fields { 101 | if f.name == name { 102 | return &f, nil 103 | } 104 | } 105 | return nil, fmt.Errorf("goloquent: struct code cannot find field, %q", name) 106 | } 107 | 108 | func isValidFieldName(name string) bool { 109 | if name == "" { 110 | return false 111 | } 112 | for _, s := range strings.Split(name, ".") { 113 | if s == "" { 114 | return false 115 | } 116 | first := true 117 | for _, c := range s { 118 | if first { 119 | first = false 120 | if c != '_' && !unicode.IsLetter(c) { 121 | return false 122 | } 123 | } else { 124 | if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) { 125 | return false 126 | } 127 | } 128 | } 129 | } 130 | return true 131 | } 132 | 133 | func isReserveFieldName(name string) bool { 134 | m := map[string]bool{ 135 | strings.ToLower(pkColumn): true, 136 | strings.ToLower(softDeleteColumn): true, 137 | } 138 | return m[strings.ToLower(name)] 139 | } 140 | 141 | func isBaseType(t reflect.Type) bool { 142 | k := t.Kind() 143 | switch true { 144 | case k == reflect.String: 145 | return true 146 | case t == typeOfByte: 147 | return true 148 | case t == typeOfJSONRawMessage: 149 | return true 150 | case k == reflect.Bool: 151 | return true 152 | case k == reflect.Uint, k == reflect.Uint8, k == reflect.Uint16, k == reflect.Uint32, k == reflect.Uint64: 153 | return true 154 | case k == reflect.Int, k == reflect.Int8, k == reflect.Int16, k == reflect.Int32, k == reflect.Int64: 155 | return true 156 | case k == reflect.Float32, k == reflect.Float64: 157 | return true 158 | case t == typeOfPtrKey || t == typeOfTime || t == typeOfGeoPoint: 159 | return true 160 | case t == typeOfDate: 161 | return true 162 | case t == typeOfSoftDelete: 163 | return true 164 | } 165 | return false 166 | } 167 | 168 | type structScan struct { 169 | path []int 170 | sequence []int 171 | typeOf reflect.Type 172 | field *field 173 | isPtrChild bool 174 | StructCodec *StructCodec 175 | } 176 | 177 | func getStructCodec(it interface{}) (*StructCodec, error) { 178 | v := reflect.Indirect(reflect.ValueOf(it)) 179 | rt := v.Type() 180 | if rt.Kind() != reflect.Struct { 181 | return nil, fmt.Errorf("goloquent: invalid %q", rt.String()) 182 | } 183 | 184 | structs := newStructCodec(v) 185 | structScans := append(make([]structScan, 0), structScan{[]int{}, []int{}, rt, nil, false, structs}) 186 | for len(structScans) > 0 { 187 | first := structScans[0] 188 | st := first.typeOf 189 | 190 | fields := first.StructCodec.fields 191 | for i := 0; i < st.NumField(); i++ { 192 | sf := st.Field(i) 193 | 194 | // Skip if not anonymous private property 195 | isExported := (sf.PkgPath == "") 196 | if !isExported && !sf.Anonymous { 197 | continue 198 | } 199 | 200 | ft := sf.Type 201 | st := newTag(sf) 202 | 203 | switch { 204 | case st.isSkip(): 205 | continue 206 | case st.isPrimaryKey(): 207 | if sf.Type != typeOfPtrKey { 208 | return nil, fmt.Errorf("goloquent: %s field on struct %v must be *datastore.Key", keyFieldName, ft) 209 | } 210 | case !isValidFieldName(st.name): 211 | return nil, fmt.Errorf("goloquent: struct tag has invalid field name: %q", st.name) 212 | case isReserveFieldName(st.name): 213 | return nil, fmt.Errorf("goloquent: struct tag has reserved field name: %q", st.name) 214 | } 215 | 216 | if ft == typeOfSoftDelete { 217 | st.name = softDeleteColumn 218 | } 219 | 220 | seq := append(first.sequence, i) 221 | k := ft.Kind() 222 | if isBaseType(ft) { 223 | fields = append(fields, newField(st, first.field, append(first.path, i), seq, ft, first.isPtrChild, nil)) 224 | continue 225 | } 226 | 227 | isPtr := false 228 | switch { 229 | case k == reflect.Slice, k == reflect.Array: 230 | // isSlice = true 231 | elem := ft.Elem() 232 | switch { 233 | case st.isPrimaryKey(): 234 | return nil, fmt.Errorf("goloquent: nested primary key in slice is not allow") 235 | case elem.Kind() == reflect.Interface: 236 | fallthrough 237 | case isBaseType(elem): 238 | fields = append(fields, newField(st, first.field, append(first.path, i), seq, sf.Type, first.isPtrChild, nil)) 239 | continue 240 | case elem.Kind() == reflect.Ptr: 241 | isPtr = true 242 | if isBaseType(elem.Elem()) { 243 | fields = append(fields, newField(st, first.field, append(first.path, i), seq, sf.Type, first.isPtrChild, nil)) 244 | continue 245 | } 246 | elem = elem.Elem() 247 | fallthrough 248 | default: 249 | if elem.Kind() == reflect.Struct { 250 | sc := newStructCodec(reflect.New(ft)) 251 | f := newField(st, first.field, append(first.path, i), seq, sf.Type, first.isPtrChild, sc) 252 | fields = append(fields, f) 253 | structScans = append(structScans, structScan{[]int{}, seq, elem, &f, isPtr, sc}) 254 | continue 255 | } 256 | } 257 | 258 | return nil, fmt.Errorf("goloquent: struct has invalid data type %v", ft) 259 | case k == reflect.Ptr: 260 | isPtr = true 261 | ft = ft.Elem() 262 | switch { 263 | case isBaseType(ft) && ft != typeOfByte: 264 | fields = append(fields, newField(st, first.field, append(first.path, i), seq, sf.Type, first.isPtrChild, nil)) 265 | continue 266 | case ft.Kind() == reflect.Struct: 267 | default: 268 | return nil, fmt.Errorf("goloquent: pointer has invalid data type %q", ft.String()) 269 | } 270 | fallthrough 271 | case k == reflect.Struct: 272 | if sf.Anonymous { 273 | if !isExported { 274 | continue 275 | } 276 | structScans = append(structScans, structScan{append(first.path, i), seq, ft, first.field, isPtr, first.StructCodec}) 277 | continue 278 | } 279 | 280 | sc := newStructCodec(reflect.New(ft)) 281 | f := newField(st, first.field, append(first.path, i), seq, sf.Type, first.isPtrChild, sc) 282 | fields = append(fields, f) 283 | sc.parentField = &f 284 | // reset the position when it's another struct 285 | structScans = append(structScans, structScan{[]int{}, seq, ft, &f, isPtr, sc}) 286 | continue 287 | default: 288 | return nil, fmt.Errorf("goloquent: invalid %q", ft.String()) 289 | } 290 | } 291 | 292 | // Sort the column follow by the sequence of struct property 293 | sort.Slice(fields, func(i, j int) bool { 294 | return compareVersion(strings.Trim(strings.Join(strings.Fields(fmt.Sprint(fields[i].sequence)), "."), "[]"), 295 | strings.Trim(strings.Join(strings.Fields(fmt.Sprint(fields[j].sequence)), "."), "[]")) > 0 296 | // return fields[i].sequence[0] < fields[j].sequence[0] 297 | }) 298 | 299 | first.StructCodec.fields = fields 300 | structScans = structScans[1:] // unshift item 301 | } 302 | 303 | return structs, nil 304 | } 305 | 306 | func initAny(v reflect.Value) reflect.Value { 307 | t := v.Type() 308 | if t.Kind() == reflect.Ptr { 309 | if v.IsNil() { 310 | v.Set(reflect.New(t.Elem())) 311 | } 312 | } 313 | return v 314 | } 315 | 316 | func mustGetField(v reflect.Value, f field) reflect.Value { 317 | v = initAny(v) 318 | for _, p := range f.paths { 319 | v = reflect.Indirect(v).Field(p) 320 | } 321 | return v 322 | } 323 | 324 | // FieldByIndex panic when the path has nil value in between (*type), 325 | // however getFieldByIndex will traverse Field by Field to check whether the value is valid 326 | // and it will return zero if the subsequent field is zero 327 | func getFieldByIndex(v reflect.Value, path []int) reflect.Value { 328 | for _, p := range path { 329 | v = v.Field(p) 330 | if v.Kind() == reflect.Ptr { 331 | if v.IsNil() { 332 | return reflect.Zero(v.Type()) 333 | } 334 | } 335 | } 336 | return v 337 | } 338 | 339 | // initialize empty slice when the slice is nil and addressable 340 | // json.Marshal likely to interpret as null if the slice have no initial value 341 | func initSlice(v reflect.Value) reflect.Value { 342 | v = reflect.Indirect(v) 343 | if v.IsNil() { 344 | s := reflect.MakeSlice(reflect.SliceOf(v.Type().Elem()), 0, 0) 345 | if v.CanSet() { 346 | v.Set(s) 347 | return v 348 | } 349 | return s 350 | } 351 | return v 352 | } 353 | -------------------------------------------------------------------------------- /dialect_sequel.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | func checkMultiPtr(v reflect.Value) (isPtr bool, t reflect.Type) { 15 | t = v.Type().Elem() 16 | if t.Kind() == reflect.Ptr { 17 | t = t.Elem() 18 | isPtr = true 19 | } 20 | return 21 | } 22 | 23 | type sqlCommon interface { 24 | Prepare(query string) (*sql.Stmt, error) 25 | Exec(query string, args ...interface{}) (sql.Result, error) 26 | Query(query string, args ...interface{}) (*sql.Rows, error) 27 | QueryRow(query string, args ...interface{}) *sql.Row 28 | } 29 | 30 | type sqlExtra interface { 31 | sqlCommon 32 | Log() 33 | } 34 | 35 | // sequel : 36 | type sequel struct { 37 | dbName string 38 | db Client 39 | } 40 | 41 | var _ Dialect = new(sequel) 42 | 43 | func init() { 44 | RegisterDialect("common", new(sequel)) 45 | } 46 | 47 | // SetDB : 48 | func (s *sequel) SetDB(db Client) { 49 | s.db = db 50 | } 51 | 52 | func (s *sequel) Open(conf Config) (*sql.DB, error) { 53 | connStr := conf.Username + ":" + conf.Password + "@/" + conf.Database 54 | client, err := sql.Open("common", connStr) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return client, nil 59 | } 60 | 61 | // GetTable : 62 | func (s *sequel) GetTable(name string) string { 63 | return fmt.Sprintf("%s.%s", s.Quote(s.dbName), s.Quote(name)) 64 | } 65 | 66 | // Version : 67 | func (s *sequel) Version() (version string) { 68 | s.db.QueryRow("SELECT VERSION();").Scan(&version) 69 | return 70 | } 71 | 72 | // CurrentDB : 73 | func (s *sequel) CurrentDB() (name string) { 74 | if s.dbName != "" { 75 | name = s.dbName 76 | return 77 | } 78 | 79 | s.db.QueryRow("SELECT DATABASE();").Scan(&name) 80 | s.dbName = name 81 | return 82 | } 83 | 84 | // Quote : 85 | func (s *sequel) Quote(n string) string { 86 | return fmt.Sprintf("`%s`", n) 87 | } 88 | 89 | // Bind : 90 | func (s *sequel) Bind(uint) string { 91 | return "?" 92 | } 93 | 94 | func (s *sequel) SplitJSON(name string) string { 95 | paths := strings.SplitN(name, ">", 2) 96 | if len(paths) <= 1 { 97 | return s.Quote(paths[0]) 98 | } 99 | return fmt.Sprintf("%s->>%q", 100 | s.Quote(strings.TrimSpace(paths[0])), 101 | fmt.Sprintf("$.%s", strings.TrimSpace(paths[1]))) 102 | } 103 | 104 | func (s sequel) JSONMarshal(v interface{}) (b json.RawMessage) { 105 | switch vi := v.(type) { 106 | case json.RawMessage: 107 | return vi 108 | case nil: 109 | b = json.RawMessage("null") 110 | case string: 111 | b = json.RawMessage(fmt.Sprintf("%q", vi)) 112 | default: 113 | b = json.RawMessage(fmt.Sprintf("%v", vi)) 114 | } 115 | return 116 | } 117 | 118 | func (s sequel) FilterJSON(f Filter) (string, []interface{}, error) { 119 | vv, err := f.Interface() 120 | if err != nil { 121 | return "", nil, err 122 | } 123 | if vv == nil { 124 | vv = json.RawMessage("null") 125 | } 126 | name := s.SplitJSON(f.Field()) 127 | buf, args := new(bytes.Buffer), make([]interface{}, 0) 128 | switch f.operator { 129 | case Equal: 130 | buf.WriteString(fmt.Sprintf("(%s) = %s", name, variable)) 131 | case NotEqual: 132 | buf.WriteString(fmt.Sprintf("(%s) <> %s", name, variable)) 133 | case GreaterThan: 134 | buf.WriteString(fmt.Sprintf("(%s) > %s", name, variable)) 135 | case GreaterEqual: 136 | buf.WriteString(fmt.Sprintf("(%s) >= %s", name, variable)) 137 | case In: 138 | x, isOk := vv.([]interface{}) 139 | if !isOk { 140 | x = append(x, vv) 141 | } 142 | if len(x) <= 0 { 143 | return "", nil, fmt.Errorf(`goloquent: value for "In" operator cannot be empty`) 144 | } 145 | buf.WriteString("(") 146 | for i := 0; i < len(x); i++ { 147 | buf.WriteString(fmt.Sprintf("JSON_CONTAINS(%s, %s) OR ", name, variable)) 148 | args = append(args, s.JSONMarshal(x[i])) 149 | } 150 | buf.Truncate(buf.Len() - 4) 151 | buf.WriteString(")") 152 | return buf.String(), args, nil 153 | case NotIn: 154 | x, isOk := vv.([]interface{}) 155 | if !isOk { 156 | x = append(x, vv) 157 | } 158 | if len(x) <= 0 { 159 | return "", nil, fmt.Errorf(`goloquent: value for "NotIn" operator cannot be empty`) 160 | } 161 | buf.WriteString("(") 162 | for i := 0; i < len(x); i++ { 163 | buf.WriteString(fmt.Sprintf("%s <> %s AND ", name, variable)) 164 | args = append(args, s.JSONMarshal(x[i])) 165 | } 166 | buf.Truncate(buf.Len() - 4) 167 | buf.WriteString(")") 168 | return buf.String(), args, nil 169 | case ContainAny: 170 | x, isOk := vv.([]interface{}) 171 | if !isOk { 172 | x = append(x, vv) 173 | } 174 | if len(x) <= 0 { 175 | return "", nil, fmt.Errorf(`goloquent: value for "ContainAny" operator cannot be empty`) 176 | } 177 | buf.WriteString("(") 178 | for i := 0; i < len(x); i++ { 179 | buf.WriteString(fmt.Sprintf("JSON_CONTAINS(%s, %s) OR ", name, variable)) 180 | args = append(args, s.JSONMarshal(x[i])) 181 | } 182 | buf.Truncate(buf.Len() - 4) 183 | buf.WriteString(")") 184 | return buf.String(), args, nil 185 | case IsType: 186 | buf.WriteString(fmt.Sprintf("JSON_TYPE(%s) = UPPER(%s)", name, variable)) 187 | case IsObject: 188 | vv = "OBJECT" 189 | buf.WriteString(fmt.Sprintf("JSON_TYPE(%s) = %s", name, variable)) 190 | case IsArray: 191 | vv = "ARRAY" 192 | buf.WriteString(fmt.Sprintf("JSON_TYPE(%s) = %s", name, variable)) 193 | default: 194 | return "", nil, fmt.Errorf("unsupported operator") 195 | } 196 | 197 | args = append(args, vv) 198 | return buf.String(), args, nil 199 | } 200 | 201 | func (s *sequel) Value(it interface{}) string { 202 | var str string 203 | switch vi := it.(type) { 204 | case nil: 205 | str = "NULL" 206 | case json.RawMessage: 207 | str = fmt.Sprintf("%q", vi) 208 | case string, []byte: 209 | str = fmt.Sprintf("%q", vi) 210 | case float32: 211 | str = strconv.FormatFloat(float64(vi), 'f', -1, 64) 212 | case float64: 213 | str = strconv.FormatFloat(vi, 'f', -1, 64) 214 | default: 215 | str = fmt.Sprintf("%v", vi) 216 | } 217 | return str 218 | } 219 | 220 | // DataType : 221 | func (s *sequel) DataType(sc Schema) string { 222 | buf := new(bytes.Buffer) 223 | buf.WriteString(sc.DataType) 224 | if sc.IsUnsigned { 225 | buf.WriteString(" UNSIGNED") 226 | } 227 | if sc.CharSet.Encoding != "" && sc.CharSet.Collation != "" { 228 | buf.WriteString(fmt.Sprintf(" CHARACTER SET %s COLLATE %s", 229 | s.Quote(sc.CharSet.Encoding), 230 | s.Quote(sc.CharSet.Collation))) 231 | } 232 | if !sc.IsNullable { 233 | buf.WriteString(" NOT NULL") 234 | if !sc.IsOmitEmpty() { 235 | buf.WriteString(fmt.Sprintf(" DEFAULT %s", s.ToString(sc.DefaultValue))) 236 | } 237 | } 238 | return buf.String() 239 | } 240 | 241 | func (s *sequel) ToString(it interface{}) string { 242 | var v string 243 | switch vi := it.(type) { 244 | case string: 245 | v = fmt.Sprintf(`'%s'`, vi) 246 | case bool: 247 | v = fmt.Sprintf("%t", vi) 248 | case uint, uint8, uint16, uint32, uint64: 249 | v = fmt.Sprintf("%d", vi) 250 | case int, int8, int16, int32, int64: 251 | v = fmt.Sprintf("%d", vi) 252 | case float32, float64: 253 | v = fmt.Sprintf("%v", vi) 254 | case time.Time: 255 | v = fmt.Sprintf(`'%s'`, vi.Format("2006-01-02 15:04:05")) 256 | case []interface{}: 257 | v = fmt.Sprintf(`'%s'`, "[]") 258 | case nil: 259 | v = "NULL" 260 | default: 261 | v = fmt.Sprintf("%v", vi) 262 | } 263 | return v 264 | } 265 | 266 | // GetSchema : 267 | func (s *sequel) GetSchema(c Column) []Schema { 268 | f := c.field 269 | root := f.getRoot() 270 | t := root.typeOf 271 | if root.isFlatten() { 272 | if !root.isSlice() { 273 | t = f.typeOf 274 | } 275 | } 276 | 277 | sc := Schema{ 278 | Name: c.Name(), 279 | IsNullable: f.isPtrChild, 280 | IsIndexed: f.IsIndex(), 281 | } 282 | if t.Kind() == reflect.Ptr { 283 | sc.IsNullable = true 284 | if t == typeOfPtrKey { 285 | sc.IsIndexed = true 286 | sc.DataType = fmt.Sprintf("varchar(%d)", pkLen) 287 | sc.CharSet = latin1CharSet 288 | if f.name == keyFieldName { 289 | sc.Name = pkColumn 290 | sc.DefaultValue = OmitDefault(nil) 291 | sc.IsIndexed = false 292 | } 293 | return []Schema{sc} 294 | } 295 | t = t.Elem() 296 | } 297 | 298 | switch t { 299 | case typeOfJSONRawMessage: 300 | sc.DefaultValue = OmitDefault(nil) 301 | sc.DataType = "json" 302 | case typeOfByte: 303 | sc.DefaultValue = OmitDefault(nil) 304 | sc.DataType = "mediumblob" 305 | case typeOfDate: 306 | sc.DefaultValue = "0001-01-01" 307 | sc.DataType = "date" 308 | case typeOfTime: 309 | sc.DefaultValue = time.Time{} 310 | sc.DataType = "datetime" 311 | case typeOfSoftDelete: 312 | sc.DefaultValue = OmitDefault(nil) 313 | sc.IsNullable = true 314 | sc.IsIndexed = true 315 | sc.DataType = "datetime" 316 | default: 317 | switch t.Kind() { 318 | case reflect.String: 319 | sc.DefaultValue = "" 320 | sc.DataType = fmt.Sprintf("varchar(%d)", 191) 321 | if f.IsLongText() { 322 | sc.DefaultValue = nil 323 | sc.DataType = "text" 324 | } 325 | if f.Get("datatype") != "" { 326 | sc.DataType = f.Get("datatype") 327 | } 328 | sc.CharSet = utf8mb4CharSet 329 | charset := f.Get("charset") 330 | if charset != "" { 331 | sc.CharSet.Encoding = charset 332 | sc.CharSet.Collation = fmt.Sprintf("%s_general_ci", charset) 333 | if f.Get("collate") != "" { 334 | sc.CharSet.Collation = f.Get("collate") 335 | } 336 | } 337 | case reflect.Bool: 338 | sc.DefaultValue = false 339 | sc.DataType = "boolean" 340 | case reflect.Int: 341 | sc.DefaultValue = int(0) 342 | sc.DataType = "int" 343 | case reflect.Int8: 344 | sc.DefaultValue = int8(0) 345 | sc.DataType = "tinyint" 346 | case reflect.Int16: 347 | sc.DefaultValue = int16(0) 348 | sc.DataType = "smallint" 349 | case reflect.Int32: 350 | sc.DefaultValue = int32(0) 351 | sc.DataType = "mediumint" 352 | case reflect.Int64: 353 | sc.DefaultValue = int64(0) 354 | sc.DataType = "bigint" 355 | case reflect.Uint: 356 | sc.DefaultValue = uint(0) 357 | sc.DataType = "int" 358 | sc.IsUnsigned = true 359 | case reflect.Uint8: 360 | sc.DefaultValue = uint8(0) 361 | sc.DataType = "tinyint" 362 | sc.IsUnsigned = true 363 | case reflect.Uint16: 364 | sc.DefaultValue = uint16(0) 365 | sc.DataType = "smallint" 366 | sc.IsUnsigned = true 367 | case reflect.Uint32: 368 | sc.DefaultValue = uint32(0) 369 | sc.DataType = "mediumint" 370 | sc.IsUnsigned = true 371 | case reflect.Uint64: 372 | sc.DefaultValue = uint64(0) 373 | sc.DataType = "bigint" 374 | sc.IsUnsigned = true 375 | case reflect.Float32, reflect.Float64: 376 | sc.DefaultValue = float64(0) 377 | sc.DataType = "double" 378 | sc.IsUnsigned = f.IsUnsigned() 379 | case reflect.Slice, reflect.Array: 380 | sc.DefaultValue = OmitDefault(nil) 381 | sc.DataType = "json" 382 | default: 383 | sc.DefaultValue = OmitDefault(nil) 384 | sc.DataType = "json" 385 | } 386 | } 387 | 388 | return []Schema{sc} 389 | } 390 | 391 | // GetColumns : 392 | func (s *sequel) GetColumns(table string) (columns []string) { 393 | stmt := "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?;" 394 | rows, _ := s.db.Query(stmt, s.CurrentDB(), table) 395 | defer rows.Close() 396 | for i := 0; rows.Next(); i++ { 397 | columns = append(columns, "") 398 | rows.Scan(&columns[i]) 399 | } 400 | return 401 | } 402 | 403 | // GetIndexes : 404 | func (s *sequel) GetIndexes(table string) (idxs []string) { 405 | stmt := "SELECT DISTINCT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME <> ?;" 406 | rows, _ := s.db.Query(stmt, s.CurrentDB(), table, "PRIMARY") 407 | defer rows.Close() 408 | for i := 0; rows.Next(); i++ { 409 | idxs = append(idxs, "") 410 | rows.Scan(&idxs[i]) 411 | } 412 | return 413 | } 414 | 415 | func (s *sequel) HasTable(table string) bool { 416 | var count int 417 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?", s.CurrentDB(), table).Scan(&count) 418 | return count > 0 419 | } 420 | 421 | func (s *sequel) HasIndex(table, idx string) bool { 422 | var count int 423 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME = ?", s.CurrentDB(), table, idx).Scan(&count) 424 | return count > 0 425 | } 426 | 427 | // OnConflictUpdate : 428 | func (s *sequel) OnConflictUpdate(table string, cols []string) string { 429 | buf := new(bytes.Buffer) 430 | buf.WriteString("ON DUPLICATE KEY UPDATE ") 431 | for _, c := range cols { 432 | buf.WriteString(fmt.Sprintf("%s=VALUES(%s),", s.Quote(c), s.Quote(c))) 433 | } 434 | buf.Truncate(buf.Len() - 1) 435 | return buf.String() 436 | } 437 | 438 | func (s *sequel) CreateTable(string, []Column) error { 439 | return nil 440 | } 441 | 442 | func (s *sequel) AlterTable(string, []Column, bool) error { 443 | return nil 444 | } 445 | 446 | func (s sequel) UpdateWithLimit() bool { 447 | return false 448 | } 449 | 450 | func (s sequel) ReplaceInto(src, dst string) error { 451 | return nil 452 | } 453 | -------------------------------------------------------------------------------- /decoder.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "reflect" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "cloud.google.com/go/datastore" 15 | ) 16 | 17 | // SoftDelete : 18 | type SoftDelete *time.Time 19 | 20 | type geoLocation struct { 21 | Latitude float64 `json:"latitude"` 22 | Longitude float64 `json:"longitude"` 23 | } 24 | 25 | func unmarshalStruct(t reflect.Type, l map[string]*json.RawMessage, esc bool) (map[string]interface{}, error) { 26 | codec, err := getStructCodec(reflect.New(t).Interface()) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | data := make(map[string]interface{}) 32 | for _, f := range codec.fields { 33 | b, isOk := l[f.name] 34 | if !isOk { 35 | continue 36 | } 37 | var it, err = valueToInterface(f.typeOf, getByte(b), esc) 38 | if err != nil { 39 | return nil, err 40 | } 41 | data[f.name] = it 42 | } 43 | 44 | return data, nil 45 | } 46 | 47 | func getByte(v *json.RawMessage) []byte { 48 | var b []byte 49 | if v == nil { 50 | return b 51 | } 52 | return []byte(*v) 53 | } 54 | 55 | func escape(b []byte) string { 56 | return strings.Trim(strings.TrimSpace(b2s(b)), `"`) 57 | } 58 | 59 | // covert byte to standard data type 60 | // string, bool, int64, float64, []byte 61 | // *datastore.Key, time.Time, datastore.GeoPoint 62 | // []interface{}, *struct 63 | func valueToInterface(t reflect.Type, v []byte, esc bool) (interface{}, error) { 64 | var it interface{} 65 | 66 | switch t { 67 | case typeOfPtrKey: 68 | if v == nil { 69 | var key *datastore.Key 70 | return key, nil 71 | } 72 | var k, err = parseKey(b2s(v)) 73 | if err != nil { 74 | return nil, err 75 | } 76 | it = k 77 | case typeOfJSONRawMessage: 78 | if v == nil || b2s(v) == "null" { 79 | return json.RawMessage(nil), nil 80 | } 81 | it = json.RawMessage(v) 82 | case typeOfTime: 83 | if v == nil { 84 | return time.Time{}, nil 85 | } 86 | var dt, err = time.Parse("2006-01-02 15:04:05", escape(v)) 87 | if err != nil { 88 | return nil, fmt.Errorf("goloquent: unable to parse %q to date time", b2s(v)) 89 | } 90 | it = dt 91 | case typeOfDate: 92 | if v == nil { 93 | return Date(time.Time{}), nil 94 | } 95 | 96 | vv := escape(v) 97 | switch { 98 | case regexp.MustCompile(`^\d{4}\-\d{2}\-\d{2} \d{2}\:\d{2}\:\d{2}$`).MatchString(vv): 99 | var dt, err = time.Parse("2006-01-02 15:04:05", vv) 100 | if err != nil { 101 | return nil, fmt.Errorf("goloquent: unable to parse %q to date", vv) 102 | } 103 | it = Date(dt) 104 | case regexp.MustCompile(`^\d{4}\-\d{2}\-\d{2}$`).MatchString(vv): 105 | var dt, err = time.Parse("2006-01-02", vv) 106 | if err != nil { 107 | return nil, fmt.Errorf("goloquent: unable to parse %q to date", vv) 108 | } 109 | it = Date(dt) 110 | default: 111 | return nil, fmt.Errorf("goloquent: invalid date value %q", v) 112 | } 113 | 114 | case typeOfSoftDelete: 115 | if v == nil { 116 | return SoftDelete(nil), nil 117 | } 118 | var dt, err = time.Parse("2006-01-02 15:04:05", escape(v)) 119 | if err != nil { 120 | return nil, fmt.Errorf("goloquent: unable to parse %q to soft delete date time", b2s(v)) 121 | } 122 | it = SoftDelete(&dt) 123 | case typeOfByte: 124 | if v == nil { 125 | var b []byte 126 | return b, nil 127 | } 128 | var b, err = base64.StdEncoding.DecodeString(escape(v)) 129 | if err != nil { 130 | return nil, fmt.Errorf("goloquent: corrupted bytes, %q", b2s(v)) 131 | } 132 | it = b 133 | case typeOfGeoPoint: 134 | if v == nil || b2s(v) == "null" { 135 | return datastore.GeoPoint{}, nil 136 | } 137 | var g geoLocation 138 | if err := json.Unmarshal(bytes.Trim(v, `"`), &g); err != nil { 139 | return nil, fmt.Errorf("goloquent: corrupted geolocation value, %s", b2s(v)) 140 | } 141 | it = datastore.GeoPoint{Lat: g.Latitude, Lng: g.Longitude} 142 | default: 143 | switch t.Kind() { 144 | case reflect.String: 145 | if v == nil { 146 | return "", nil 147 | } 148 | if esc { 149 | var str string 150 | if err := json.Unmarshal(v, &str); err != nil { 151 | return nil, err 152 | } 153 | it = str 154 | } else { 155 | it = escape(v) 156 | } 157 | case reflect.Bool: 158 | if v == nil { 159 | return false, nil 160 | } 161 | var b, err = strconv.ParseBool(escape(v)) 162 | if err != nil { 163 | return nil, fmt.Errorf("goloquent: unable to parse %q to boolean", b2s(v)) 164 | } 165 | it = b 166 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 167 | if v == nil { 168 | return int64(0), nil 169 | } 170 | var n, err = strconv.ParseFloat(escape(v), 64) 171 | if err != nil { 172 | return nil, fmt.Errorf("goloquent: unable to parse %q to int64", b2s(v)) 173 | } 174 | it = int64(n) 175 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 176 | if v == nil { 177 | return uint64(0), nil 178 | } 179 | var n, err = strconv.ParseFloat(escape(v), 64) 180 | if err != nil { 181 | return nil, fmt.Errorf("goloquent: unable to parse %q to uint64", b2s(v)) 182 | } 183 | it = uint64(n) 184 | case reflect.Float32, reflect.Float64: 185 | if v == nil { 186 | return float64(0), nil 187 | } 188 | var f, err = strconv.ParseFloat(escape(v), 64) 189 | if err != nil { 190 | return nil, err 191 | } 192 | it = f 193 | case reflect.Slice, reflect.Array: 194 | if v == nil || b2s(v) == "null" { 195 | var arr []interface{} 196 | return arr, nil 197 | } 198 | var b []*json.RawMessage 199 | if err := json.Unmarshal(v, &b); err != nil { 200 | return nil, fmt.Errorf("goloquent: corrupted slice value, %v", err) 201 | } 202 | 203 | arr := make([]interface{}, 0, len(b)) 204 | for i := 0; i < len(b); i++ { 205 | var vv, err = valueToInterface(t.Elem(), getByte(b[i]), true) 206 | if err != nil { 207 | return nil, err 208 | } 209 | arr = append(arr, vv) 210 | } 211 | it = arr 212 | case reflect.Ptr: 213 | if isBaseType(t.Elem()) { 214 | if v == nil { 215 | return reflect.Zero(t).Interface(), nil 216 | } 217 | var it, err = valueToInterface(t.Elem(), v, esc) 218 | if err != nil { 219 | return nil, err 220 | } 221 | return &it, nil 222 | } 223 | if t.Elem().Kind() != reflect.Struct { 224 | return nil, fmt.Errorf("goloquent: unsupported struct field data type %q", t.String()) 225 | } 226 | 227 | if v == nil || b2s(v) == "null" { 228 | return reflect.Zero(t).Interface(), nil 229 | } 230 | t = t.Elem() 231 | fallthrough 232 | case reflect.Struct: 233 | if v == nil || b2s(v) == "null" { 234 | var l map[string]interface{} 235 | return l, nil 236 | } 237 | var l = make(map[string]*json.RawMessage) 238 | if err := json.Unmarshal(v, &l); err != nil { 239 | return nil, fmt.Errorf("goloquent: unmatched struct layout with value") 240 | } 241 | if len(l) <= 0 { 242 | return make(map[string]interface{}), nil 243 | } 244 | 245 | var err error 246 | it, err = unmarshalStruct(t, l, true) 247 | if err != nil { 248 | return nil, err 249 | } 250 | default: 251 | return nil, fmt.Errorf("goloquent: unmatched data type %q", t.String()) 252 | } 253 | } 254 | 255 | return it, nil 256 | } 257 | 258 | func loadStructField(v reflect.Value, l map[string]interface{}) error { 259 | var codec, err = getStructCodec(v.Interface()) 260 | if err != nil { 261 | return err 262 | } 263 | 264 | for _, f := range codec.fields { 265 | val, isOk := l[f.name] 266 | vi := getField(v, f.paths) 267 | if !isOk { 268 | // vi.Set(reflect.Zero(vi.Type())) 269 | continue 270 | } 271 | 272 | if err := loadField(vi, val); err != nil { 273 | return err 274 | } 275 | } 276 | 277 | return nil 278 | } 279 | 280 | func isZero(v reflect.Value) bool { 281 | switch v.Kind() { 282 | case reflect.Func, reflect.Map, reflect.Slice: 283 | return v.IsNil() 284 | case reflect.Array: 285 | z := true 286 | for i := 0; i < v.Len(); i++ { 287 | z = z && isZero(v.Index(i)) 288 | } 289 | return z 290 | case reflect.Struct: 291 | z := true 292 | for i := 0; i < v.NumField(); i++ { 293 | z = z && isZero(v.Field(i)) 294 | } 295 | return z 296 | } 297 | z := reflect.Zero(v.Type()) 298 | return v.Interface() == z.Interface() 299 | } 300 | 301 | func loadField(v reflect.Value, it interface{}) error { 302 | switch v.Kind() { 303 | case reflect.String: 304 | x, isOk := it.(string) 305 | if !isOk { 306 | return unmatchDataType(x, it) 307 | } 308 | v.SetString(x) 309 | case reflect.Bool: 310 | x, isOk := it.(bool) 311 | if !isOk { 312 | return unmatchDataType(x, it) 313 | } 314 | v.SetBool(x) 315 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 316 | x, isOk := it.(int64) 317 | if !isOk { 318 | return unmatchDataType(x, it) 319 | } 320 | if v.OverflowInt(x) { 321 | return fmt.Errorf("goloquent: overflow %s value %v", v.Kind(), it) 322 | } 323 | v.SetInt(x) 324 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 325 | x, isOk := it.(uint64) 326 | if !isOk { 327 | return unmatchDataType(x, it) 328 | } 329 | if v.OverflowUint(x) { 330 | return fmt.Errorf("goloquent: overflow %s value %v", v.Kind(), it) 331 | } 332 | v.SetUint(x) 333 | case reflect.Float32, reflect.Float64: 334 | x, isOk := it.(float64) 335 | if !isOk { 336 | return unmatchDataType(x, it) 337 | } 338 | if v.OverflowFloat(x) { 339 | return fmt.Errorf("goloquent: overflow %s value %v", v.Kind(), it) 340 | } 341 | v.SetFloat(x) 342 | case reflect.Ptr: 343 | vi := reflect.ValueOf(it) 344 | if vi.IsNil() { 345 | v.Set(reflect.New(v.Type()).Elem()) 346 | return nil 347 | } 348 | 349 | elem := v.Type().Elem() 350 | switch { 351 | case v.Type() == typeOfPtrKey: 352 | x, isOk := it.(*datastore.Key) 353 | if !isOk { 354 | return unmatchDataType(x, it) 355 | } 356 | v.Set(reflect.ValueOf(x)) 357 | case isBaseType(elem): 358 | if err := loadField(v.Elem(), reflect.ValueOf(it).Elem().Interface()); err != nil { 359 | return err 360 | } 361 | case elem.Kind() == reflect.Struct: 362 | if vi.IsNil() { 363 | v.Set(reflect.Zero(v.Type())) 364 | return nil 365 | } 366 | 367 | v = initStruct(v) 368 | x, isOk := it.(map[string]interface{}) 369 | if !isOk { 370 | return unmatchDataType(x, it) 371 | } 372 | 373 | if err := loadStructField(v.Elem(), x); err != nil { 374 | return err 375 | } 376 | default: 377 | return unmatchDataType(v, it) 378 | } 379 | 380 | case reflect.Struct: 381 | switch v.Type() { 382 | case typeOfGeoPoint: 383 | x, isOk := it.(datastore.GeoPoint) 384 | if !isOk { 385 | return unmatchDataType(x, it) 386 | } 387 | v.Set(reflect.ValueOf(x)) 388 | case typeOfTime: 389 | x, isOk := it.(time.Time) 390 | if !isOk { 391 | return unmatchDataType(x, it) 392 | } 393 | v.Set(reflect.ValueOf(x)) 394 | case typeOfDate: 395 | x, isOk := it.(Date) 396 | if !isOk { 397 | return unmatchDataType(x, it) 398 | } 399 | v.Set(reflect.ValueOf(x)) 400 | case typeOfSoftDelete: 401 | x, isOk := it.(SoftDelete) 402 | if !isOk { 403 | return unmatchDataType(x, it) 404 | } 405 | v.Set(reflect.ValueOf(x)) 406 | default: 407 | x, isOk := it.(map[string]interface{}) 408 | if !isOk { 409 | return unmatchDataType(x, it) 410 | } 411 | 412 | v = initStruct(v) 413 | if err := loadStructField(v, x); err != nil { 414 | return err 415 | } 416 | } 417 | 418 | case reflect.Slice, reflect.Array: 419 | switch v.Type() { 420 | case typeOfByte: 421 | x, isOk := it.([]byte) 422 | if !isOk { 423 | return unmatchDataType(x, it) 424 | } 425 | v.SetBytes(x) 426 | case typeOfJSONRawMessage: 427 | x, isOk := it.(json.RawMessage) 428 | if !isOk { 429 | return unmatchDataType(x, it) 430 | } 431 | v.Set(reflect.ValueOf(x)) 432 | 433 | default: 434 | x, isOk := it.([]interface{}) 435 | if !isOk { 436 | return unmatchDataType(x, it) 437 | } 438 | 439 | arr := reflect.MakeSlice(v.Type(), len(x), len(x)) 440 | for i, xv := range x { 441 | if err := loadField(arr.Index(i), xv); err != nil { 442 | return err 443 | } 444 | } 445 | v.Set(arr) 446 | 447 | } 448 | 449 | default: 450 | return fmt.Errorf("goloquent: unsupported data type, %v", v.Type()) 451 | } 452 | 453 | return nil 454 | } 455 | 456 | func initStruct(v reflect.Value) reflect.Value { 457 | t := v.Type() 458 | if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { 459 | if v.IsNil() { 460 | v.Set(reflect.New(t.Elem())) 461 | } 462 | } 463 | return v 464 | } 465 | 466 | func unmatchDataType(o interface{}, p interface{}) error { 467 | return fmt.Errorf("goloquent: unmatched data type of original, %v versus %v", reflect.TypeOf(o), reflect.TypeOf(p)) 468 | } 469 | 470 | func unflatMap(l map[string]interface{}, names []string, it interface{}) { 471 | for i, k := range names { 472 | if i == len(names)-1 { 473 | l[k] = it 474 | continue 475 | } 476 | _, isExist := l[k] 477 | if !isExist { 478 | l[k] = make(map[string]interface{}) 479 | } 480 | l = (l[k]).(map[string]interface{}) 481 | } 482 | } 483 | 484 | // Denormalize flatten field 485 | // from []interface{} to []map[string]interface{} 486 | // or from interface{} to map[string]interface{} 487 | func denormalize(f field, values []Property) interface{} { 488 | if f.isFlatten() { 489 | if f.isSlice() { 490 | arr := make([]interface{}, 0) 491 | for _, vv := range values { 492 | for i, vi := range vv.Value.([]interface{}) { 493 | if i > len(arr)-1 { 494 | arr = append(arr, make(map[string]interface{})) 495 | } 496 | l := arr[i].(map[string]interface{}) 497 | unflatMap(l, vv.name[1:], vi) 498 | arr[i] = l 499 | } 500 | } 501 | return arr 502 | } 503 | 504 | l := make(map[string]interface{}) 505 | for _, v := range values { 506 | unflatMap(l, v.name[1:], v.Value) 507 | } 508 | return l 509 | } 510 | 511 | return values[0].Value 512 | } 513 | -------------------------------------------------------------------------------- /test/postgres_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "testing" 8 | "time" 9 | 10 | _ "github.com/lib/pq" 11 | "github.com/si3nloong/goloquent" 12 | "github.com/si3nloong/goloquent/db" 13 | ) 14 | 15 | var ( 16 | pg *goloquent.DB 17 | ) 18 | 19 | func TestPostgresConn(t *testing.T) { 20 | conn, err := db.Open("postgres", db.Config{ 21 | Username: "sianloong", 22 | Database: "goloquent", 23 | Logger: func(stmt *goloquent.Stmt) { 24 | log.Println(fmt.Sprintf("[%.3fms] %s", stmt.TimeElapse().Seconds()*1000, stmt.String())) 25 | }, 26 | }) 27 | if err != nil { 28 | panic(err) 29 | } 30 | pg = conn 31 | } 32 | 33 | func TestPostgresDropTableIfExists(t *testing.T) { 34 | if err := pg.Table("User").DropIfExists(); err != nil { 35 | t.Fatal(err) 36 | } 37 | } 38 | 39 | func TestPostgresMigration(t *testing.T) { 40 | if err := pg.Migrate(new(User), new(TempUser)); err != nil { 41 | t.Fatal(err) 42 | } 43 | } 44 | 45 | func TestPostgresTableExists(t *testing.T) { 46 | if isExist := pg.Table("User").Exists(); isExist != true { 47 | t.Fatal(fmt.Errorf("Unexpected error, table %q should exists", "User")) 48 | } 49 | } 50 | 51 | func TestPostgresTruncate(t *testing.T) { 52 | if err := pg.Truncate(new(User), TempUser{}); err != nil { 53 | t.Fatal(err) 54 | } 55 | } 56 | 57 | func TestPostgresAddIndex(t *testing.T) { 58 | if err := pg.Table("User"). 59 | AddUniqueIndex("Username"); err != nil { 60 | t.Fatal(err) 61 | } 62 | if err := pg.Table("User"). 63 | AddIndex("Age"); err != nil { 64 | t.Fatal(err) 65 | } 66 | } 67 | 68 | func TestPostgresEmptyInsertOrUpsert(t *testing.T) { 69 | var users []User 70 | if err := pg.Create(&users); err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | if err := pg.Upsert(&users); err != nil { 75 | t.Fatal(err) 76 | } 77 | } 78 | 79 | func TestPostgresCreate(t *testing.T) { 80 | u := getFakeUser() 81 | if err := pg.Create(u); err != nil { 82 | t.Fatal(err) 83 | } 84 | 85 | u = getFakeUser() 86 | if err := pg.Create(u, nameKey); err != nil { 87 | t.Fatal(err) 88 | } 89 | 90 | u = getFakeUser() 91 | if err := pg.Create(u, idKey); err != nil { 92 | t.Fatal(err) 93 | } 94 | 95 | users := []*User{getFakeUser(), getFakeUser()} 96 | if err := pg.Create(&users); err != nil { 97 | t.Fatal(err) 98 | } 99 | } 100 | 101 | // func TestPostgresReplaceInto(t *testing.T) { 102 | // if err := pg.Table("User"). 103 | // AnyOfAncestor(nameKey, idKey). 104 | // ReplaceInto("TempUser"); err != nil { 105 | // t.Fatal(err) 106 | // } 107 | // } 108 | 109 | func TestPostgresSelect(t *testing.T) { 110 | u := new(User) 111 | if err := pg. 112 | Select("*", "Name").First(u); err != nil { 113 | t.Fatal(err) 114 | } 115 | } 116 | 117 | func TestPostgresDistinctOn(t *testing.T) { 118 | u := new(User) 119 | if err := pg.NewQuery(). 120 | DistinctOn("*").First(u); err == nil { 121 | t.Fatal("Expected `DistinctOn` cannot allow *") 122 | } 123 | 124 | if err := pg.NewQuery(). 125 | DistinctOn("").First(u); err == nil { 126 | t.Fatal("Expected `DistinctOn` cannot have empty") 127 | } 128 | 129 | if err := pg.NewQuery(). 130 | DistinctOn("Name", "Password").First(u); err != nil { 131 | t.Fatal(err) 132 | } 133 | } 134 | 135 | func TestPostgresGet(t *testing.T) { 136 | u := new(User) 137 | users := new([]User) 138 | if err := pg.First(u); err != nil { 139 | t.Fatal(err) 140 | } 141 | 142 | if err := pg.Find(u.Key, u); err != nil { 143 | t.Fatal(err) 144 | } 145 | 146 | if err := pg.Get(users); err != nil { 147 | t.Fatal(err) 148 | } 149 | 150 | if err := pg.NewQuery().Unscoped().Get(users); err != nil { 151 | t.Fatal(err) 152 | } 153 | } 154 | 155 | func TestPostgresWhereFilter(t *testing.T) { 156 | age := uint8(85) 157 | creditLimit := float64(100.015) 158 | dob, _ := time.Parse("2006-01-02", "1900-10-01") 159 | 160 | u := getFakeUser() 161 | u.Age = age 162 | u.Nickname = nil 163 | u.CreditLimit = creditLimit 164 | u.Birthdate = goloquent.Date(dob) 165 | 166 | pg.Create(u) 167 | 168 | users := new([]User) 169 | if err := pg.Where("Age", "=", &age). 170 | Get(users); err != nil { 171 | t.Fatal(err) 172 | } 173 | if len(*users) <= 0 { 174 | t.Fatal(`Unexpected result from filter using "Where"`) 175 | } 176 | 177 | if err := pg.Where("Birthdate", "=", goloquent.Date(dob)). 178 | Get(users); err != nil { 179 | t.Fatal(err) 180 | } 181 | if len(*users) <= 0 { 182 | t.Fatal(`Unexpected result from filter using "Where"`) 183 | } 184 | 185 | var nilNickname *string 186 | if err := pg.Where("Nickname", "=", nilNickname). 187 | Get(users); err != nil { 188 | t.Fatal(err) 189 | } 190 | if len(*users) <= 0 { 191 | t.Fatal(`Unexpected result from filter using "Where"`) 192 | } 193 | 194 | if err := pg.Where("CreditLimit", "=", &creditLimit). 195 | Get(users); err != nil { 196 | t.Fatal(err) 197 | } 198 | if len(*users) <= 0 { 199 | t.Fatal(`Unexpected result from filter using "Where"`) 200 | } 201 | } 202 | 203 | func TestPostgresWhereAnyLike(t *testing.T) { 204 | users := new([]User) 205 | 206 | u := getFakeUser() 207 | u.PrimaryEmail = "sianloong@hotmail.com" 208 | if err := pg.Create(u); err != nil { 209 | t.Fatal(err) 210 | } 211 | 212 | if err := pg.NewQuery(). 213 | WhereAnyLike("PrimaryEmail", []string{ 214 | "lzPskFb@OOxzA.net", 215 | "sianloong%", 216 | }).Get(users); err != nil { 217 | t.Fatal(err) 218 | } 219 | 220 | if len(*users) <= 0 { 221 | t.Fatal(`Unexpected result from filter using "WhereAnyLike"`) 222 | } 223 | } 224 | func TestPostgresJSONRawMessage(t *testing.T) { 225 | u := getFakeUser() 226 | if err := pg.Upsert(u); err != nil { 227 | t.Fatal(err) 228 | } 229 | u.Information = nil 230 | if err := pg.Upsert(u); err != nil { 231 | t.Fatal(err) 232 | } 233 | u.Information = json.RawMessage(`[]`) 234 | if err := pg.Upsert(u); err != nil { 235 | t.Fatal(err) 236 | } 237 | u.Information = json.RawMessage(`{}`) 238 | if err := pg.Upsert(u); err != nil { 239 | t.Fatal(err) 240 | } 241 | u.Information = json.RawMessage(`null`) 242 | if err := pg.Upsert(u); err != nil { 243 | t.Fatal(err) 244 | } 245 | u.Information = json.RawMessage(`notvalid`) 246 | if err := pg.Upsert(u); err == nil { 247 | t.Fatal(err) 248 | } 249 | } 250 | 251 | func TestPostgresEmptySliceInJSON(t *testing.T) { 252 | u := new(User) 253 | if err := pg.First(u); err != nil { 254 | t.Fatal(err) 255 | } 256 | if u.Emails == nil { 257 | t.Fatal(fmt.Errorf("empty slice should init on any `Get` func")) 258 | } 259 | 260 | u2 := getFakeUser() 261 | u2.Emails = nil 262 | u2.PrimaryEmail = "sianloong@hotmail.com" 263 | if err := pg.Create(u2); err != nil { 264 | t.Fatal(err) 265 | } 266 | if u2.Emails == nil { 267 | t.Fatal(fmt.Errorf("empty slice should init on any `Create` func")) 268 | } 269 | } 270 | 271 | func TestPostgresJSONEqual(t *testing.T) { 272 | var emptyStr string 273 | 274 | users := new([]User) 275 | if err := pg.NewQuery(). 276 | WhereJSONEqual("Address>PostCode", int32(85)). 277 | Get(users); err != nil { 278 | t.Fatal(err) 279 | } 280 | 281 | if err := pg.NewQuery(). 282 | WhereJSONEqual("Address>PostCode", uint32(85)). 283 | Get(users); err != nil { 284 | t.Fatal(err) 285 | } 286 | 287 | postCode := uint32(85) 288 | if err := pg.NewQuery(). 289 | WhereJSONEqual("Address>PostCode", &postCode). 290 | Get(users); err != nil { 291 | t.Fatal(err) 292 | } 293 | 294 | if err := pg.NewQuery(). 295 | WhereJSONEqual("Address>Line1", "7812, Jalan Section 22"). 296 | Get(users); err != nil { 297 | t.Fatal(err) 298 | } 299 | if len(*users) <= 0 { 300 | t.Fatal("JSON equal has unexpected result") 301 | } 302 | 303 | if err := pg.NewQuery(). 304 | WhereJSONEqual("Address>Line2", emptyStr). 305 | Get(users); err != nil { 306 | t.Fatal(err) 307 | } 308 | if len(*users) <= 0 { 309 | t.Fatal("JSON equal has unexpected result") 310 | } 311 | 312 | timeZone := new(time.Time) 313 | if err := pg.NewQuery(). 314 | WhereJSONEqual("Address>region.TimeZone", timeZone). 315 | Get(users); err != nil { 316 | t.Fatal(err) 317 | } 318 | if len(*users) <= 0 { 319 | t.Fatal("JSON equal has unexpected result") 320 | } 321 | } 322 | 323 | func TestPostgresJSONNotEqual(t *testing.T) { 324 | var timeZone *time.Time 325 | users := new([]User) 326 | if err := pg.NewQuery(). 327 | WhereJSONNotEqual("Address>region.TimeZone", timeZone). 328 | Get(users); err != nil { 329 | t.Fatal(err) 330 | } 331 | if len(*users) <= 0 { 332 | t.Fatal("JSON equal has unexpected result") 333 | } 334 | 335 | if err := pg.NewQuery(). 336 | WhereJSONNotEqual("Address>Country", ""). 337 | Get(users); err != nil { 338 | t.Fatal(err) 339 | } 340 | if len(*users) > 0 { 341 | t.Fatal("JSON equal has unexpected result") 342 | } 343 | } 344 | 345 | func TestPostgresJSONIn(t *testing.T) { 346 | users := new([]User) 347 | if err := pg.NewQuery(). 348 | WhereJSONIn("Address>PostCode", []interface{}{0, 10, 20}). 349 | Get(users); err != nil { 350 | t.Fatal(err) 351 | } 352 | if len(*users) <= 0 { 353 | t.Fatal("JSON contain any has unexpected result") 354 | } 355 | } 356 | 357 | func TestPostgresJSONNotIn(t *testing.T) { 358 | users := new([]User) 359 | if err := pg.NewQuery(). 360 | WhereJSONNotIn("Address>Line1", []interface{}{"PJ", "KL", "Cheras"}). 361 | Get(users); err != nil { 362 | t.Fatal(err) 363 | } 364 | if len(*users) <= 0 { 365 | t.Fatal("JSON contain any has unexpected result") 366 | } 367 | } 368 | 369 | func TestPostgresJSONContainAny(t *testing.T) { 370 | users := new([]User) 371 | if err := pg.NewQuery(). 372 | WhereJSONContainAny("Emails", []Email{ 373 | "support@hotmail.com", 374 | "invalid@gmail.com", 375 | }).Get(users); err != nil { 376 | t.Fatal(err) 377 | } 378 | if len(*users) <= 0 { 379 | t.Fatal("JSON contain any has unexpected result") 380 | } 381 | 382 | if err := pg.NewQuery(). 383 | WhereJSONContainAny("Emails", []Email{ 384 | "invalid@gmail.com", 385 | "invalid@hotmail.com", 386 | }).Get(users); err != nil { 387 | t.Fatal(err) 388 | } 389 | if len(*users) > 0 { 390 | t.Fatal("JSON contain any has unexpected result") 391 | } 392 | } 393 | 394 | func TestPostgresJSONType(t *testing.T) { 395 | users := new([]User) 396 | if err := pg.NewQuery(). 397 | WhereJSONType("Address>region", "OBJECT"). 398 | Get(users); err != nil { 399 | t.Fatal(err) 400 | } 401 | if len(*users) <= 0 { 402 | t.Fatal("JSON isObject has unexpected result") 403 | } 404 | } 405 | 406 | func TestPostgresJSONIsObject(t *testing.T) { 407 | users := new([]User) 408 | if err := pg.NewQuery(). 409 | WhereJSONIsObject("Address>region"). 410 | Get(users); err != nil { 411 | t.Fatal(err) 412 | } 413 | if len(*users) <= 0 { 414 | t.Fatal("JSON isObject has unexpected result") 415 | } 416 | } 417 | 418 | func TestPostgresJSONIsArray(t *testing.T) { 419 | users := new([]User) 420 | if err := pg.NewQuery(). 421 | WhereJSONIsArray("Address>region.keys"). 422 | Get(users); err != nil { 423 | t.Fatal(err) 424 | } 425 | if len(*users) <= 0 { 426 | t.Fatal("JSON isArray has unexpected result") 427 | } 428 | } 429 | 430 | func TestPostgresPaginate(t *testing.T) { 431 | users := new([]User) 432 | 433 | uu := []*User{getFakeUser(), getFakeUser(), getFakeUser()} 434 | if err := pg.Create(&uu, nameKey); err != nil { 435 | t.Fatal(err) 436 | } 437 | p := &goloquent.Pagination{ 438 | Limit: 1, 439 | } 440 | if err := pg.Ancestor(nameKey). 441 | Paginate(p, users); err != nil { 442 | t.Fatal(err) 443 | } 444 | if len(*(users)) <= 0 { 445 | t.Fatal(fmt.Errorf("paginate record set shouldn't empty")) 446 | } 447 | 448 | // p.Cursor = p.NextCursor() 449 | // if err := pg.Ancestor(nameKey). 450 | // Paginate(p, users); err != nil { 451 | // t.Fatal(err) 452 | // } 453 | // if len(*(users)) <= 0 { 454 | // t.Fatal(fmt.Errorf("paginate record set shouldn't empty")) 455 | // } 456 | } 457 | 458 | func TestPostgresUpsert(t *testing.T) { 459 | u := getFakeUser() 460 | if err := pg.Upsert(u); err != nil { 461 | t.Fatal(err) 462 | } 463 | 464 | u = getFakeUser() 465 | if err := pg.Upsert(u, idKey); err != nil { 466 | t.Fatal(err) 467 | } 468 | 469 | u = getFakeUser() 470 | if err := pg.Upsert(u, nameKey); err != nil { 471 | t.Fatal(err) 472 | } 473 | 474 | users := []*User{getFakeUser(), getFakeUser()} 475 | if err := pg.Upsert(&users); err != nil { 476 | t.Fatal(err) 477 | } 478 | 479 | uu := []User{*getFakeUser(), *getFakeUser()} 480 | if err := pg.Upsert(&uu); err != nil { 481 | t.Fatal(err) 482 | } 483 | 484 | uuu := []User{*getFakeUser(), *getFakeUser()} 485 | if err := pg.Upsert(&uuu, idKey); err != nil { 486 | t.Fatal(err) 487 | } 488 | 489 | uuu = []User{*getFakeUser(), *getFakeUser()} 490 | if err := pg.Upsert(&uuu, nameKey); err != nil { 491 | t.Fatal(err) 492 | } 493 | } 494 | 495 | func TestPostgresUpdate(t *testing.T) { 496 | if err := pg.Table("User").Limit(1). 497 | Where("Name", "=", "Dr. Antoinette Zboncak"). 498 | Update(map[string]interface{}{ 499 | "Name": "sianloong", 500 | }); err != nil { 501 | t.Fatal(err) 502 | } 503 | } 504 | 505 | func TestPostgresSoftDelete(t *testing.T) { 506 | u := getFakeUser() 507 | if err := pg.Create(u); err != nil { 508 | t.Fatal(err) 509 | } 510 | if err := pg.Delete(u); err != nil { 511 | t.Fatal(err) 512 | } 513 | } 514 | 515 | func TestPostgresHardDelete(t *testing.T) { 516 | u := new(User) 517 | if err := pg.First(u); err != nil { 518 | t.Fatal(err) 519 | } 520 | if err := pg.Destroy(u); err != nil { 521 | t.Fatal(err) 522 | } 523 | } 524 | 525 | // func TestPostgresTable(t *testing.T) { 526 | // users := new([]User) 527 | // if err := pg.Table("User"). 528 | // WhereLike("Name", "nick%"). 529 | // Get(users); err != nil { 530 | // t.Fatal(err) 531 | // } 532 | 533 | // if err := pg.Table("User"). 534 | // Where("Age", ">", 0). 535 | // Get(users); err != nil { 536 | // t.Fatal(err) 537 | // } 538 | 539 | // user := new(User) 540 | // if err := pg.Table("User"). 541 | // First(user); err != nil { 542 | // t.Fatal(err) 543 | // } 544 | // } 545 | 546 | func TestPostgresRunInTransaction(t *testing.T) { 547 | if err := pg.RunInTransaction(func(txn *goloquent.DB) error { 548 | u := new(User) 549 | if err := txn.NewQuery(). 550 | WLock().First(u); err != nil { 551 | return err 552 | } 553 | 554 | u.Name = "NewName" 555 | u.UpdatedDateTime = time.Now().UTC() 556 | return txn.Save(u) 557 | }); err != nil { 558 | t.Fatal(err) 559 | } 560 | } 561 | 562 | func TestPostgresScan(t *testing.T) { 563 | var count, sum uint 564 | if err := pg.Table("User"). 565 | Select("COALESCE(COUNT(*),0)", `COALESCE(SUM("Age"),0)`). 566 | Scan(&count, &sum); err != nil { 567 | t.Fatal(err) 568 | } 569 | log.Println("Count :", count, ", Sum :", sum) 570 | } 571 | 572 | func TestPostgresClose(t *testing.T) { 573 | defer pg.Close() 574 | } 575 | -------------------------------------------------------------------------------- /query.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | "strconv" 9 | "strings" 10 | 11 | "cloud.google.com/go/datastore" 12 | "github.com/si3nloong/goloquent/expr" 13 | ) 14 | 15 | type operator int 16 | 17 | // JSON : 18 | const ( 19 | Equal operator = iota 20 | EqualTo 21 | NotEqual 22 | LessThan 23 | LessEqual 24 | GreaterThan 25 | GreaterEqual 26 | AnyLike 27 | Like 28 | NotLike 29 | ContainAny 30 | ContainAll 31 | In 32 | NotIn 33 | IsObject 34 | IsArray 35 | IsType 36 | MatchAgainst 37 | ) 38 | 39 | type sortDirection int 40 | 41 | const ( 42 | ascending sortDirection = iota 43 | descending 44 | ) 45 | 46 | type order struct { 47 | field string 48 | direction sortDirection 49 | } 50 | 51 | type locked int 52 | 53 | // lock mode 54 | const ( 55 | ReadLock locked = iota + 1 56 | WriteLock 57 | ) 58 | 59 | const ( 60 | maxLimit = 10000 61 | keyFieldName = "__key__" 62 | ) 63 | 64 | func checkSinglePtr(it interface{}) error { 65 | v := reflect.ValueOf(it) 66 | if v.Kind() != reflect.Ptr { 67 | return fmt.Errorf("goloquent: entity must be addressable") 68 | } 69 | v = v.Elem() 70 | if v.Kind() != reflect.Struct || isBaseType(v.Type()) { 71 | return fmt.Errorf("goloquent: entity data type must be struct") 72 | } 73 | return nil 74 | } 75 | 76 | type group struct { 77 | isGroup bool 78 | data []interface{} 79 | } 80 | 81 | type scope struct { 82 | table string 83 | distinctOn []string 84 | projection []string 85 | omits []string 86 | ancestors []group 87 | filters []Filter 88 | orders []interface{} 89 | limit int32 90 | offset int32 91 | errs []error 92 | noScope bool 93 | lockMode locked 94 | } 95 | 96 | // Query : 97 | type Query struct { 98 | db *DB 99 | scope 100 | } 101 | 102 | func newQuery(db *DB) *Query { 103 | return &Query{ 104 | db: db.clone(), 105 | scope: scope{ 106 | limit: -1, 107 | offset: -1, 108 | }, 109 | } 110 | } 111 | 112 | func (q *Query) clone() *Query { 113 | ss := q.scope 114 | return &Query{ 115 | db: q.db.clone(), 116 | scope: ss, 117 | } 118 | } 119 | 120 | func (q *Query) append(query *Query) *Query { 121 | q.scope.projection = append(q.scope.projection, query.scope.projection...) 122 | q.scope.filters = append(q.scope.filters, query.scope.filters...) 123 | q.scope.orders = append(q.scope.orders, query.scope.orders...) 124 | return q 125 | } 126 | 127 | func (q *Query) getError() error { 128 | if len(q.errs) > 0 { 129 | buf := new(bytes.Buffer) 130 | for _, err := range q.errs { 131 | buf.WriteString(fmt.Sprintf("%v", err)) 132 | } 133 | return fmt.Errorf("%s", buf.String()) 134 | } 135 | return nil 136 | } 137 | 138 | // Select : 139 | func (q *Query) Select(fields ...string) *Query { 140 | q = q.clone() 141 | arr := make([]string, 0, len(fields)) 142 | for _, f := range fields { 143 | f := strings.TrimSpace(f) 144 | if f == "" { 145 | q.errs = append(q.errs, fmt.Errorf("goloquent: invalid `Select` value %q", f)) 146 | return q 147 | } 148 | arr = append(arr, f) 149 | } 150 | q.projection = append(q.projection, arr...) 151 | return q 152 | } 153 | 154 | // DistinctOn : 155 | func (q *Query) DistinctOn(fields ...string) *Query { 156 | q = q.clone() 157 | arr := make([]string, 0, len(fields)) 158 | for _, f := range fields { 159 | f := strings.TrimSpace(f) 160 | if f == "" || f == "*" { 161 | q.errs = append(q.errs, fmt.Errorf("goloquent: invalid `DistinctOn` value %q", f)) 162 | return q 163 | } 164 | arr = append(arr, f) 165 | } 166 | q.distinctOn = append(q.distinctOn, arr...) 167 | return q 168 | } 169 | 170 | // Omit : 171 | func (q *Query) Omit(fields ...string) *Query { 172 | q = q.clone() 173 | arr := make([]string, 0, len(fields)) 174 | for _, f := range fields { 175 | f := strings.TrimSpace(f) 176 | if f == "" || f == "*" { 177 | q.errs = append(q.errs, fmt.Errorf("goloquent: invalid omit value %v", f)) 178 | return q 179 | } 180 | arr = append(arr, f) 181 | } 182 | // Primary key cannot be omited 183 | dict := newDictionary(append(q.projection, arr...)) 184 | dict.delete(keyFieldName) 185 | dict.delete(pkColumn) 186 | q.omits = dict.keys() 187 | return q 188 | } 189 | 190 | // Unscoped : 191 | func (q *Query) Unscoped() *Query { 192 | q.noScope = true 193 | return q 194 | } 195 | 196 | // Find : 197 | func (q *Query) Find(key *datastore.Key, model interface{}) error { 198 | if err := q.getError(); err != nil { 199 | return err 200 | } 201 | if err := checkSinglePtr(model); err != nil { 202 | return err 203 | } 204 | if key == nil || key.Incomplete() { 205 | return fmt.Errorf("goloquent: find action with invalid key value, %q", key) 206 | } 207 | q = q.Where(keyFieldName, "=", key).Limit(1) 208 | return newBuilder(q).get(model, true) 209 | } 210 | 211 | // First : 212 | func (q *Query) First(model interface{}) error { 213 | q = q.clone() 214 | if err := q.getError(); err != nil { 215 | return err 216 | } 217 | if err := checkSinglePtr(model); err != nil { 218 | return err 219 | } 220 | q.Limit(1) 221 | return newBuilder(q).get(model, false) 222 | } 223 | 224 | // Get : 225 | func (q *Query) Get(model interface{}) error { 226 | q = q.clone() 227 | if err := q.getError(); err != nil { 228 | return err 229 | } 230 | return newBuilder(q).getMulti(model) 231 | } 232 | 233 | // Paginate : 234 | func (q *Query) Paginate(p *Pagination, model interface{}) error { 235 | if err := q.getError(); err != nil { 236 | return err 237 | } 238 | q = q.clone() 239 | if p.query != nil { 240 | q = q.append(p.query) 241 | } 242 | if p.Limit > maxLimit { 243 | return fmt.Errorf("goloquent: limit overflow : %d, maximum limit : %d", p.Limit, maxLimit) 244 | } else if p.Limit <= 0 { 245 | p.Limit = defaultLimit 246 | } 247 | q = q.Limit(int(p.Limit) + 1) 248 | if len(q.orders) > 0 { 249 | lastField := q.orders[len(q.orders)-1] 250 | x, isOk := lastField.(expr.Sort) 251 | if isOk && x.Name != pkColumn { 252 | k := pkColumn 253 | if x.Direction == expr.Descending { 254 | k = "-" + k 255 | } 256 | q = q.OrderBy(k) 257 | } 258 | } else { 259 | q = q.OrderBy(pkColumn) 260 | } 261 | return newBuilder(q).paginate(p, model) 262 | } 263 | 264 | // Ancestor : 265 | func (q *Query) Ancestor(ancestor *datastore.Key) *Query { 266 | if ancestor == nil { 267 | q.errs = append(q.errs, errors.New("goloquent: ancestor key cannot be nil")) 268 | return q 269 | } 270 | if ancestor.Incomplete() { 271 | q.errs = append(q.errs, fmt.Errorf("goloquent: ancestor key is incomplete, %v", ancestor)) 272 | return q 273 | } 274 | q = q.clone() 275 | q.ancestors = append(q.ancestors, group{false, []interface{}{ancestor}}) 276 | return q 277 | } 278 | 279 | // AnyOfAncestor : 280 | func (q *Query) AnyOfAncestor(ancestors ...*datastore.Key) *Query { 281 | if len(ancestors) <= 0 { 282 | q.errs = append(q.errs, errors.New(`goloquent: "AnyOfAncestor" cannot be empty`)) 283 | return q 284 | } 285 | g := group{true, make([]interface{}, 0)} 286 | for _, a := range ancestors { 287 | if a == nil { 288 | q.errs = append(q.errs, errors.New("goloquent: ancestor key cannot be nil")) 289 | return q 290 | } 291 | if a.Incomplete() { 292 | q.errs = append(q.errs, fmt.Errorf("goloquent: ancestor key is incomplete, %v", a)) 293 | return q 294 | } 295 | g.data = append(g.data, a) 296 | } 297 | q = q.clone() 298 | q.ancestors = append(q.ancestors, g) 299 | return q 300 | } 301 | 302 | func (q *Query) where(field, op string, value interface{}, isJSON bool) *Query { 303 | op = strings.TrimSpace(strings.ToLower(op)) 304 | var optr operator 305 | 306 | switch op { 307 | case "=", "eq", "$eq", "equal": 308 | optr = Equal 309 | case "!=", "<>", "ne", "$ne", "notequal", "not equal": 310 | optr = NotEqual 311 | case ">", "!<", "gt", "$gt": 312 | optr = GreaterThan 313 | case "<", "!>", "lt", "$lt": 314 | optr = LessThan 315 | case ">=", "gte", "$gte": 316 | optr = GreaterEqual 317 | case "<=", "lte", "$lte": 318 | optr = LessEqual 319 | case "in", "$in": 320 | optr = In 321 | case "nin", "!in", "$nin", "not in", "notin": 322 | optr = NotIn 323 | case "anylike": 324 | optr = AnyLike 325 | case "like", "$like": 326 | if isJSON { 327 | q.errs = append(q.errs, fmt.Errorf("goloquent: invalid operator %q for json", op)) 328 | return q 329 | } 330 | optr = Like 331 | case "nlike", "!like", "$nlike": 332 | if isJSON { 333 | q.errs = append(q.errs, fmt.Errorf("goloquent: invalid operator %q for json", op)) 334 | return q 335 | } 336 | optr = NotLike 337 | case "match": 338 | optr = MatchAgainst 339 | default: 340 | if !isJSON { 341 | q.errs = append(q.errs, fmt.Errorf("goloquent: invalid operator %q", op)) 342 | return q 343 | } 344 | 345 | switch op { 346 | case "containany": 347 | optr = ContainAny 348 | case "istype": 349 | optr = IsType 350 | case "isobject": 351 | optr = IsObject 352 | case "isarray": 353 | optr = IsArray 354 | default: 355 | q.errs = append(q.errs, fmt.Errorf("goloquent: invalid operator %q for json", op)) 356 | return q 357 | } 358 | } 359 | 360 | q.filters = append(q.filters, Filter{ 361 | field: field, 362 | operator: optr, 363 | value: value, 364 | isJSON: isJSON, 365 | }) 366 | return q 367 | } 368 | 369 | // Where : 370 | func (q *Query) Where(field string, op string, value interface{}) *Query { 371 | q = q.clone() 372 | return q.where(field, op, value, false) 373 | } 374 | 375 | // WhereEqual : 376 | func (q *Query) WhereEqual(field string, v interface{}) *Query { 377 | return q.Where(field, "=", v) 378 | } 379 | 380 | // WhereNotEqual : 381 | func (q *Query) WhereNotEqual(field string, v interface{}) *Query { 382 | return q.Where(field, "!=", v) 383 | } 384 | 385 | // WhereNull : 386 | func (q *Query) WhereNull(field string) *Query { 387 | return q.Where(field, "=", nil) 388 | } 389 | 390 | // WhereNotNull : 391 | func (q *Query) WhereNotNull(field string) *Query { 392 | return q.Where(field, "<>", nil) 393 | } 394 | 395 | // WhereIn : 396 | func (q *Query) WhereIn(field string, v interface{}) *Query { 397 | vv := reflect.Indirect(reflect.ValueOf(v)) 398 | t := vv.Type() 399 | if !vv.IsValid() || (t.Kind() != reflect.Slice && t.Kind() != reflect.Array) { 400 | q.errs = append(q.errs, fmt.Errorf(`goloquent: value must be either slice or array for "WhereIn"`)) 401 | return q 402 | } 403 | return q.Where(field, "in", v) 404 | } 405 | 406 | // WhereNotIn : 407 | func (q *Query) WhereNotIn(field string, v interface{}) *Query { 408 | vv := reflect.Indirect(reflect.ValueOf(v)) 409 | t := vv.Type() 410 | if !vv.IsValid() || (t.Kind() != reflect.Slice && t.Kind() != reflect.Array) { 411 | q.errs = append(q.errs, fmt.Errorf(`goloquent: value must be either slice or array for "WhereNotIn"`)) 412 | return q 413 | } 414 | return q.Where(field, "nin", v) 415 | } 416 | 417 | // WhereLike : 418 | func (q *Query) WhereLike(field, v string) *Query { 419 | return q.Where(field, "like", v) 420 | } 421 | 422 | // WhereNotLike : 423 | func (q *Query) WhereNotLike(field, v string) *Query { 424 | return q.Where(field, "nlike", v) 425 | } 426 | 427 | // WhereAnyLike : 428 | func (q *Query) WhereAnyLike(field string, v interface{}) *Query { 429 | vv := reflect.Indirect(reflect.ValueOf(v)) 430 | t := vv.Type() 431 | if !vv.IsValid() || (t.Kind() != reflect.Slice && t.Kind() != reflect.Array) { 432 | q.errs = append(q.errs, fmt.Errorf(`goloquent: value must be either slice or array for "WhereAnyLike"`)) 433 | return q 434 | } 435 | return q.Where(field, "anylike", v) 436 | } 437 | 438 | // WhereJSON : 439 | func (q *Query) WhereJSON(field, op string, v interface{}) *Query { 440 | return q.where(field, op, v, true) 441 | } 442 | 443 | // WhereJSONEqual : 444 | func (q *Query) WhereJSONEqual(field string, v interface{}) *Query { 445 | return q.WhereJSON(field, "=", v) 446 | } 447 | 448 | // WhereJSONNotEqual : 449 | func (q *Query) WhereJSONNotEqual(field string, v interface{}) *Query { 450 | return q.WhereJSON(field, "!=", v) 451 | } 452 | 453 | // WhereJSONIn : 454 | func (q *Query) WhereJSONIn(field string, v []interface{}) *Query { 455 | return q.WhereJSON(field, "in", v) 456 | } 457 | 458 | // WhereJSONNotIn : 459 | func (q *Query) WhereJSONNotIn(field string, v []interface{}) *Query { 460 | return q.WhereJSON(field, "nin", v) 461 | } 462 | 463 | // WhereJSONContainAny : 464 | func (q *Query) WhereJSONContainAny(field string, v interface{}) *Query { 465 | return q.WhereJSON(field, "containAny", v) 466 | } 467 | 468 | // WhereJSONType : 469 | func (q *Query) WhereJSONType(field, typ string) *Query { 470 | return q.WhereJSON(field, "isType", strings.TrimSpace(strings.ToLower(typ))) 471 | } 472 | 473 | // WhereJSONIsObject : 474 | func (q *Query) WhereJSONIsObject(field string) *Query { 475 | return q.WhereJSON(field, "isObject", nil) 476 | } 477 | 478 | // WhereJSONIsArray : 479 | func (q *Query) WhereJSONIsArray(field string) *Query { 480 | return q.WhereJSON(field, "isArray", nil) 481 | } 482 | 483 | // MatchAgainst : 484 | func (q *Query) MatchAgainst(fields []string, values ...string) *Query { 485 | f := Filter{} 486 | f.operator = MatchAgainst 487 | buf := new(bytes.Buffer) 488 | buf.WriteString("MATCH(") 489 | 490 | for i, field := range fields { 491 | if i > 0 { 492 | buf.WriteByte(',') 493 | } 494 | buf.WriteString("`" + field + "`") 495 | } 496 | buf.WriteString(") AGAINST(") 497 | v := "" 498 | for i, _ := range values { 499 | if i > 0 { 500 | buf.WriteByte(' ') 501 | } 502 | 503 | // buf.WriteString(strconv.Quote(val)) 504 | values[i] = strconv.Quote(values[i]) 505 | if i != len(values)-1 { 506 | v += strconv.Quote(values[i]) + "," 507 | } else { 508 | v += strconv.Quote(values[i]) 509 | } 510 | } 511 | buf.WriteString("?)") 512 | f.raw = buf.String() 513 | f.value = v 514 | q.filters = append(q.filters, f) 515 | return q 516 | } 517 | 518 | // Lock : 519 | func (q *Query) Lock(mode locked) *Query { 520 | q.lockMode = mode 521 | return q 522 | } 523 | 524 | // RLock : 525 | func (q *Query) RLock() *Query { 526 | q.lockMode = ReadLock 527 | return q 528 | } 529 | 530 | // WLock : 531 | func (q *Query) WLock() *Query { 532 | q.lockMode = WriteLock 533 | return q 534 | } 535 | 536 | // OrderBy : 537 | // OrderBy(expr.Field("Status", []string{})) 538 | func (q *Query) OrderBy(values ...interface{}) *Query { 539 | if len(values) <= 0 { 540 | return q 541 | } 542 | for _, v := range values { 543 | var it interface{} 544 | switch vi := v.(type) { 545 | case string: 546 | sort := expr.Sort{Direction: expr.Ascending} 547 | if vi[0] == '-' { 548 | vi = vi[1:] 549 | sort.Direction = expr.Descending 550 | } 551 | sort.Name = vi 552 | it = sort 553 | default: 554 | it = vi 555 | } 556 | q.orders = append(q.orders, it) 557 | } 558 | // for _, ff := range fields { 559 | // q = q.clone() 560 | // name, dir := strings.TrimSpace(ff), ascending 561 | // if strings.HasPrefix(name, "+") { 562 | // name, dir = strings.TrimSpace(name[1:]), ascending 563 | // } else if strings.HasPrefix(name, "-") { 564 | // name, dir = strings.TrimSpace(name[1:]), descending 565 | // } 566 | 567 | // q.orders = append(q.orders, order{ 568 | // field: name, 569 | // direction: dir, 570 | // }) 571 | // } 572 | return q 573 | } 574 | 575 | // Limit : 576 | func (q *Query) Limit(limit int) *Query { 577 | q.limit = int32(limit) 578 | return q 579 | } 580 | 581 | // Offset : 582 | func (q *Query) Offset(offset int) *Query { 583 | q.offset = int32(offset) 584 | return q 585 | } 586 | 587 | // ReplaceInto : 588 | func (q *Query) ReplaceInto(table string) error { 589 | return newBuilder(q).replaceInto(table) 590 | } 591 | 592 | // InsertInto : 593 | func (q *Query) InsertInto(table string) error { 594 | return newBuilder(q).insertInto(table) 595 | } 596 | 597 | // Update : 598 | func (q *Query) Update(v interface{}) error { 599 | if err := q.getError(); err != nil { 600 | return err 601 | } 602 | // q = q.OrderBy(pkColumn) 603 | return newBuilder(q).updateMulti(v) 604 | } 605 | 606 | // Flush : 607 | func (q *Query) Flush() error { 608 | if err := q.getError(); err != nil { 609 | return err 610 | } 611 | if q.table == "" { 612 | return fmt.Errorf("goloquent: unable to perform delete without table name") 613 | } 614 | return newBuilder(q).deleteByQuery() 615 | } 616 | 617 | // Scan : 618 | func (q *Query) Scan(dest ...interface{}) error { 619 | return newBuilder(q).scan(dest...) 620 | } 621 | -------------------------------------------------------------------------------- /dialect_postgres.go: -------------------------------------------------------------------------------- 1 | package goloquent 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "reflect" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | type postgres struct { 15 | sequel 16 | } 17 | 18 | var _ Dialect = new(postgres) 19 | 20 | func init() { 21 | RegisterDialect("postgres", new(postgres)) 22 | } 23 | 24 | func (p postgres) escapeSingleQuote(n string) string { 25 | return strings.Replace(n, `'`, `\'`, -1) 26 | } 27 | 28 | // Open : 29 | func (p *postgres) Open(conf Config) (*sql.DB, error) { 30 | buf := new(bytes.Buffer) 31 | buf.WriteString(fmt.Sprintf("user='%s' ", p.escapeSingleQuote(conf.Username))) 32 | buf.WriteString(fmt.Sprintf("password='%s' ", p.escapeSingleQuote(conf.Password))) 33 | if conf.UnixSocket != "" { 34 | buf.WriteString(fmt.Sprintf("host=/%s ", strings.Trim(conf.UnixSocket, `/`))) 35 | } else { 36 | host, port := "localhost", "5432" 37 | if conf.Host != "" { 38 | host = conf.Host 39 | } 40 | if conf.Port != "" { 41 | port = conf.Port 42 | } 43 | buf.WriteString(fmt.Sprintf("host=%s port=%s ", host, port)) 44 | } 45 | buf.WriteString(fmt.Sprintf("dbname='%s' ", p.escapeSingleQuote(conf.Database))) 46 | buf.WriteString("sslmode=disable") 47 | log.Println("Connection String :", buf.String()) 48 | client, err := sql.Open("postgres", buf.String()) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return client, nil 53 | } 54 | 55 | // GetTable : 56 | func (p postgres) GetTable(name string) string { 57 | return p.Quote(name) 58 | } 59 | 60 | // CurrentDB : 61 | func (p *postgres) CurrentDB() (name string) { 62 | if p.dbName != "" { 63 | name = p.dbName 64 | return 65 | } 66 | 67 | p.db.QueryRow("SELECT current_database();").Scan(&name) 68 | p.dbName = name 69 | return 70 | } 71 | 72 | func (p postgres) Quote(n string) string { 73 | return fmt.Sprintf(`"%s"`, n) 74 | } 75 | 76 | func (p postgres) Bind(i uint) string { 77 | return fmt.Sprintf("$%d", i) 78 | } 79 | 80 | func (p postgres) SplitJSON(name string) string { 81 | paths := strings.SplitN(name, ">", 2) 82 | if len(paths) <= 1 { 83 | return p.Quote(paths[0]) 84 | } 85 | vv := strings.Split(strings.TrimSpace(paths[1]), `.`) 86 | return fmt.Sprintf(`%s->%s`, 87 | p.Quote(strings.TrimSpace(paths[0])), 88 | `'`+strings.Join(vv, p.Value(`->`))+`'`) 89 | } 90 | 91 | func (p postgres) JSONMarshal(v interface{}) (b json.RawMessage) { 92 | switch vi := v.(type) { 93 | case json.RawMessage: 94 | return vi 95 | case nil: 96 | b = json.RawMessage("null") 97 | case string: 98 | b = json.RawMessage(fmt.Sprintf("%q", vi)) 99 | default: 100 | b = json.RawMessage(fmt.Sprintf("%v", vi)) 101 | } 102 | return 103 | } 104 | 105 | func (p postgres) FilterJSON(f Filter) (string, []interface{}, error) { 106 | vv, err := f.Interface() 107 | if err != nil { 108 | return "", nil, err 109 | } 110 | if vv == nil { 111 | vv = json.RawMessage("null") 112 | } 113 | name := p.SplitJSON(f.Field()) 114 | buf, args := new(bytes.Buffer), make([]interface{}, 0) 115 | switch f.operator { 116 | case Equal: 117 | buf.WriteString(fmt.Sprintf("(%s) = %s", name, variable)) 118 | case NotEqual: 119 | buf.WriteString(fmt.Sprintf("(%s) <> %s", name, variable)) 120 | case GreaterThan: 121 | buf.WriteString(fmt.Sprintf("(%s) > %s", name, variable)) 122 | case GreaterEqual: 123 | buf.WriteString(fmt.Sprintf("(%s) >= %s", name, variable)) 124 | case In: 125 | x, isOk := vv.([]interface{}) 126 | if !isOk { 127 | x = append(x, vv) 128 | } 129 | if len(x) <= 0 { 130 | return "", nil, fmt.Errorf(`goloquent: value for "In" operator cannot be empty`) 131 | } 132 | buf.WriteString("(") 133 | for i := 0; i < len(x); i++ { 134 | buf.WriteString(fmt.Sprintf("(%s = %s) OR ", name, variable)) 135 | args = append(args, p.JSONMarshal(x[i])) 136 | } 137 | buf.Truncate(buf.Len() - 4) 138 | buf.WriteString(")") 139 | return buf.String(), args, nil 140 | case NotIn: 141 | x, isOk := vv.([]interface{}) 142 | if !isOk { 143 | x = append(x, vv) 144 | } 145 | if len(x) <= 0 { 146 | return "", nil, fmt.Errorf(`goloquent: value for "In" operator cannot be empty`) 147 | } 148 | buf.WriteString("(") 149 | for i := 0; i < len(x); i++ { 150 | buf.WriteString(fmt.Sprintf("(%s <> %s) AND ", name, variable)) 151 | args = append(args, p.JSONMarshal(x[i])) 152 | } 153 | buf.Truncate(buf.Len() - 4) 154 | buf.WriteString(")") 155 | return buf.String(), args, nil 156 | case ContainAny: 157 | x, isOk := vv.([]interface{}) 158 | if !isOk { 159 | x = append(x, vv) 160 | } 161 | if len(x) <= 0 { 162 | return "", nil, fmt.Errorf(`goloquent: value for "In" operator cannot be empty`) 163 | } 164 | buf.WriteString(fmt.Sprintf("%s ?| array[", name)) 165 | for i := 0; i < len(x); i++ { 166 | buf.WriteString(variable + ",") 167 | args = append(args, x[i]) 168 | } 169 | buf.Truncate(buf.Len() - 1) 170 | buf.WriteString("]") 171 | return buf.String(), args, nil 172 | case IsType: 173 | args = append(args, vv) 174 | buf.WriteString(fmt.Sprintf("jsonb_typeof((%s)::jsonb) = LOWER(%s)", name, variable)) 175 | return buf.String(), args, nil 176 | case IsObject: 177 | vv = json.RawMessage([]byte("{}")) 178 | buf.WriteString(fmt.Sprintf("(%s)::jsonb @> %s::jsonb", name, variable)) 179 | case IsArray: 180 | vv = json.RawMessage([]byte("[]")) 181 | buf.WriteString(fmt.Sprintf("(%s)::jsonb @> %s::jsonb", name, variable)) 182 | default: 183 | return "", nil, fmt.Errorf("unsupported operator") 184 | } 185 | 186 | args = append(args, p.JSONMarshal(vv)) 187 | return buf.String(), args, nil 188 | } 189 | 190 | func (p postgres) Value(it interface{}) string { 191 | var str string 192 | switch vi := it.(type) { 193 | case nil: 194 | str = "NULL" 195 | case json.RawMessage: 196 | str = fmt.Sprintf(`'%s'`, escapeSingleQuote(fmt.Sprintf(`%s`, vi))) 197 | case string, []byte: 198 | str = fmt.Sprintf(`'%s'`, escapeSingleQuote(fmt.Sprintf(`%s`, vi))) 199 | default: 200 | str = fmt.Sprintf("%v", vi) 201 | } 202 | return str 203 | } 204 | 205 | // DataType : 206 | func (p postgres) DataType(sc Schema) string { 207 | buf := new(bytes.Buffer) 208 | buf.WriteString(sc.DataType) 209 | if sc.IsUnsigned { 210 | buf.WriteString(fmt.Sprintf(" CHECK (%s >= 0)", p.Quote(sc.Name))) 211 | } 212 | if !sc.IsNullable { 213 | buf.WriteString(" NOT NULL") 214 | t := reflect.TypeOf(sc.DefaultValue) 215 | if t != reflect.TypeOf(OmitDefault(nil)) { 216 | buf.WriteString(fmt.Sprintf(" DEFAULT %s", p.ToString(sc.DefaultValue))) 217 | } 218 | } 219 | return buf.String() 220 | } 221 | 222 | func (p postgres) OnConflictUpdate(table string, cols []string) string { 223 | buf := new(bytes.Buffer) 224 | buf.WriteString(fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", p.Quote(pkColumn))) 225 | for _, c := range cols { 226 | buf.WriteString(fmt.Sprintf("%s = %s.%s,", p.Quote(c), p.GetTable(table), p.Quote(c))) 227 | } 228 | buf.Truncate(buf.Len() - 1) 229 | return buf.String() 230 | } 231 | 232 | func (p postgres) GetSchema(c Column) []Schema { 233 | f := c.field 234 | root := f.getRoot() 235 | t := root.typeOf 236 | if root.isFlatten() { 237 | if !root.isSlice() { 238 | t = f.typeOf 239 | } 240 | } 241 | 242 | sc := Schema{ 243 | Name: c.Name(), 244 | IsNullable: f.isPtrChild, 245 | } 246 | 247 | if t.Kind() == reflect.Ptr { 248 | sc.IsNullable = true 249 | if t == typeOfPtrKey { 250 | if f.name == keyFieldName { 251 | return []Schema{ 252 | Schema{pkColumn, fmt.Sprintf("varchar(%d)", pkLen), OmitDefault(nil), false, false, false, latin1CharSet}, 253 | } 254 | } 255 | sc.IsIndexed = true 256 | sc.DataType = fmt.Sprintf("varchar(%d)", pkLen) 257 | sc.CharSet = latin1CharSet 258 | return []Schema{sc} 259 | } 260 | t = t.Elem() 261 | } 262 | 263 | switch t { 264 | case typeOfJSONRawMessage: 265 | sc.DefaultValue = OmitDefault(nil) 266 | sc.DataType = "jsonb" 267 | case typeOfByte: 268 | sc.DefaultValue = OmitDefault(nil) 269 | sc.DataType = "bytea" 270 | case typeOfDate: 271 | sc.DefaultValue = "0001-01-01" 272 | sc.DataType = "date" 273 | case typeOfTime: 274 | sc.DefaultValue = time.Time{} 275 | sc.DataType = "timestamp" 276 | case typeOfSoftDelete: 277 | sc.DefaultValue = OmitDefault(nil) 278 | sc.IsNullable = true 279 | sc.IsIndexed = true 280 | sc.DataType = "timestamp" 281 | default: 282 | switch t.Kind() { 283 | case reflect.String: 284 | sc.DefaultValue = "" 285 | sc.DataType = fmt.Sprintf("varchar(%d)", 191) 286 | if f.IsLongText() { 287 | sc.DefaultValue = nil 288 | sc.DataType = "text" 289 | } 290 | case reflect.Bool: 291 | sc.DefaultValue = false 292 | sc.DataType = "bool" 293 | case reflect.Int: 294 | sc.DefaultValue = int(0) 295 | sc.DataType = "integer" 296 | case reflect.Int8: 297 | sc.DefaultValue = int8(0) 298 | sc.DataType = "smallint" 299 | case reflect.Int16: 300 | sc.DefaultValue = int16(0) 301 | sc.DataType = "smallint" 302 | case reflect.Int32: 303 | sc.DefaultValue = int32(0) 304 | sc.DataType = "integer" 305 | case reflect.Int64: 306 | sc.DefaultValue = int64(0) 307 | sc.DataType = "bigint" 308 | case reflect.Uint: 309 | sc.DefaultValue = uint(0) 310 | sc.DataType = "integer" 311 | sc.IsUnsigned = true 312 | case reflect.Uint8: 313 | sc.DefaultValue = uint8(0) 314 | sc.DataType = "smallint" 315 | sc.IsUnsigned = true 316 | case reflect.Uint16: 317 | sc.DefaultValue = uint16(0) 318 | sc.DataType = "smallint" 319 | sc.IsUnsigned = true 320 | case reflect.Uint32: 321 | sc.DefaultValue = uint32(0) 322 | sc.DataType = "integer" 323 | sc.IsUnsigned = true 324 | case reflect.Uint64: 325 | sc.DefaultValue = uint64(0) 326 | sc.DataType = "bigint" 327 | sc.IsUnsigned = true 328 | case reflect.Float32, reflect.Float64: 329 | sc.DefaultValue = float64(0) 330 | sc.DataType = "real" 331 | default: 332 | sc.DataType = "jsonb" 333 | } 334 | } 335 | 336 | return []Schema{sc} 337 | } 338 | 339 | // GetColumns : 340 | func (p *postgres) GetColumns(table string) (columns []string) { 341 | stmt := "SELECT column_name FROM INFORMATION_SCHEMA.columns WHERE table_schema = CURRENT_SCHEMA() AND table_name = $1;" 342 | rows, _ := p.db.Query(stmt, table) 343 | defer rows.Close() 344 | for i := 0; rows.Next(); i++ { 345 | columns = append(columns, "") 346 | rows.Scan(&columns[i]) 347 | } 348 | return 349 | } 350 | 351 | // GetIndexes : 352 | func (p *postgres) GetIndexes(table string) (idxs []string) { 353 | stmt := "SELECT indexname FROM pg_indexes WHERE schemaname = CURRENT_SCHEMA() AND tablename = $1;" 354 | rows, _ := p.db.Query(stmt, table) 355 | defer rows.Close() 356 | for i := 0; rows.Next(); i++ { 357 | idxs = append(idxs, "") 358 | rows.Scan(&idxs[i]) 359 | } 360 | return 361 | } 362 | 363 | func (p *postgres) HasTable(table string) bool { 364 | var count int 365 | p.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_type = 'BASE TABLE' AND table_schema = CURRENT_SCHEMA() AND table_name = $1;", table).Scan(&count) 366 | return count > 0 367 | } 368 | 369 | func (p *postgres) HasIndex(table, idx string) bool { 370 | var count int 371 | p.db.QueryRow("SELECT count(*) FROM pg_indexes WHERE tablename = $1 AND indexname = $2 AND schemaname = CURRENT_SCHEMA()", table, idx).Scan(&count) 372 | return count > 0 373 | } 374 | 375 | func (p *postgres) ToString(it interface{}) string { 376 | var v string 377 | switch vi := it.(type) { 378 | case nil: 379 | v = "NULL" 380 | case json.RawMessage: 381 | v = fmt.Sprintf(`'%s'`, vi) 382 | case string: 383 | v = fmt.Sprintf(`'%s'`, vi) 384 | case bool: 385 | v = fmt.Sprintf("%t", vi) 386 | case uint, uint8, uint16, uint32, uint64: 387 | v = fmt.Sprintf("%d", vi) 388 | case int, int8, int16, int32, int64: 389 | v = fmt.Sprintf("%d", vi) 390 | case float32, float64: 391 | v = fmt.Sprintf("%v", vi) 392 | case time.Time: 393 | v = fmt.Sprintf(`'%s'`, vi.Format("2006-01-02 15:04:05")) 394 | case []interface{}: 395 | v = fmt.Sprintf(`'%s'`, "[]") 396 | default: 397 | v = fmt.Sprintf("%v", vi) 398 | } 399 | return v 400 | } 401 | 402 | func (p *postgres) CreateTable(table string, columns []Column) error { 403 | idxs := make([]string, 0, len(columns)) 404 | conn := p.db.sqlCommon.(*sql.DB) 405 | tx, err := conn.Begin() 406 | if err != nil { 407 | return err 408 | } 409 | defer tx.Rollback() 410 | 411 | buf := new(bytes.Buffer) 412 | buf.WriteString(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (", p.GetTable(table))) 413 | for _, c := range columns { 414 | for _, ss := range p.GetSchema(c) { 415 | buf.WriteString(fmt.Sprintf("%s %s,", 416 | p.Quote(ss.Name), 417 | p.DataType(ss))) 418 | 419 | if ss.IsIndexed { 420 | idx := fmt.Sprintf("%s_%s_%s", table, ss.Name, "Idx") 421 | stmt := fmt.Sprintf("CREATE INDEX %s ON %s (%s);", 422 | p.Quote(idx), p.GetTable(table), p.Quote(ss.Name)) 423 | idxs = append(idxs, stmt) 424 | } 425 | } 426 | } 427 | buf.WriteString(fmt.Sprintf("PRIMARY KEY (%s)", p.Quote(pkColumn))) 428 | buf.WriteString(");") 429 | log.Println(buf.String()) 430 | if _, err := tx.Exec(buf.String()); err != nil { 431 | return err 432 | } 433 | 434 | for _, idx := range idxs { 435 | if _, err := tx.Exec(idx); err != nil { 436 | return err 437 | } 438 | } 439 | 440 | return tx.Commit() 441 | } 442 | 443 | func (p *postgres) AlterTable(table string, columns []Column, unsafe bool) error { 444 | cols := newDictionary(p.GetColumns(table)) 445 | idxs := newDictionary(p.GetIndexes(table)) 446 | idxs.delete(fmt.Sprintf("%s_pkey", table)) 447 | buf := new(bytes.Buffer) 448 | buf.WriteString(fmt.Sprintf("ALTER TABLE %s ", p.GetTable(table))) 449 | for _, c := range columns { 450 | for _, ss := range p.GetSchema(c) { 451 | if !cols.has(ss.Name) { 452 | buf.WriteString(fmt.Sprintf("ADD COLUMN %s %s", p.Quote(ss.Name), ss.DataType)) 453 | if !ss.IsNullable { 454 | buf.WriteString(" NOT NULL") 455 | if !ss.IsOmitEmpty() { 456 | buf.WriteString(fmt.Sprintf(" DEFAULT %s", 457 | p.ToString(ss.DefaultValue))) 458 | } 459 | } 460 | buf.WriteString(",") 461 | } else { 462 | prefix := fmt.Sprintf("ALTER COLUMN %s", p.Quote(ss.Name)) 463 | buf.WriteString(fmt.Sprintf("%s TYPE %s", prefix, ss.DataType)) 464 | buf.WriteString(",") 465 | if !ss.IsNullable { 466 | buf.WriteString(prefix + " SET NOT NULL,") 467 | if !ss.IsOmitEmpty() { 468 | buf.WriteString(fmt.Sprintf("%s SET DEFAULT %s,", 469 | prefix, p.ToString(ss.DefaultValue))) 470 | } 471 | } 472 | } 473 | 474 | if ss.IsIndexed { 475 | idx := fmt.Sprintf("%s_%s_%s", table, ss.Name, "idx") 476 | if idxs.has(idx) { 477 | idxs.delete(idx) 478 | } else { 479 | 480 | // buf.WriteString(fmt.Sprintf( 481 | // " CREATE INDEX %s ON (%s);", 482 | // p.Quote(idx), 483 | // p.Quote(ss.Name))) 484 | } 485 | } 486 | cols.delete(ss.Name) 487 | } 488 | } 489 | 490 | for _, col := range cols.keys() { 491 | buf.WriteString(fmt.Sprintf(" DROP COLUMN %s,", p.Quote(col))) 492 | } 493 | 494 | buf.Truncate(buf.Len() - 1) 495 | buf.WriteString(";") 496 | 497 | log.Println(idxs.keys()) 498 | return p.db.execStmt(&stmt{ 499 | statement: buf, 500 | }) 501 | 502 | // for _, idx := range idxs.keys() { 503 | // buff := new(bytes.Buffer) 504 | // buff.WriteString(fmt.Sprintf("DROP INDEX %s;", p.Quote(idx))) 505 | // p.db.ConsoleLog(&Stmt{buff, nil, nil}) 506 | // if _, err := tx.Exec(buff.String()); err != nil { 507 | // return err 508 | // } 509 | // } 510 | 511 | // for _, idx := range idxs.keys() { 512 | // stmt := fmt.Sprintf("CREATE INDEX %s ON %s ();", p.Quote(idx), p.Quote(table)) 513 | // if _, err := tx.Exec(stmt); err != nil { 514 | // return err 515 | // } 516 | // } 517 | } 518 | 519 | func (p *postgres) ReplaceInto(src, dst string) error { 520 | cols := p.GetColumns(src) 521 | pk := p.Quote(pkColumn) 522 | src, dst = p.GetTable(src), p.GetTable(dst) 523 | buf := new(bytes.Buffer) 524 | buf.WriteString("WITH patch AS (") 525 | buf.WriteString("UPDATE " + dst + " SET ") 526 | for _, c := range cols { 527 | if c == pkColumn { 528 | continue 529 | } 530 | cc := p.Quote(c) 531 | buf.WriteString(cc + " = " + src + "." + cc + ",") 532 | } 533 | buf.Truncate(buf.Len() - 1) 534 | buf.WriteString(" FROM " + src + " ") 535 | buf.WriteString("WHERE " + src + "." + pk + " = " + dst + "." + pk + " ") 536 | buf.WriteString("RETURNING " + src + "." + pk + ") ") 537 | buf.WriteString("INSERT INTO " + dst + " ") 538 | buf.WriteString("SELECT * FROM " + src + " ") 539 | buf.WriteString("WHERE NOT EXISTS ") 540 | buf.WriteString("(SELECT 1 FROM patch WHERE " + pk + " = " + src + "." + pk + ")") 541 | buf.WriteString(";") 542 | return p.db.execStmt(&stmt{ 543 | statement: buf, 544 | }) 545 | } 546 | -------------------------------------------------------------------------------- /test/mysql_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "testing" 9 | "time" 10 | 11 | "cloud.google.com/go/datastore" 12 | _ "github.com/go-sql-driver/mysql" 13 | "github.com/si3nloong/goloquent" 14 | "github.com/si3nloong/goloquent/db" 15 | ) 16 | 17 | func TestMySQLConn(t *testing.T) { 18 | conn, err := db.Open("mysql", db.Config{ 19 | Username: "root", 20 | Password: "abcd1234", 21 | Database: "goloquent", 22 | Logger: func(stmt *goloquent.Stmt) { 23 | log.Println(fmt.Sprintf("[%.3fms] %s", stmt.TimeElapse().Seconds()*1000, stmt.String())) 24 | }, 25 | }) 26 | if err != nil { 27 | panic(err) 28 | } 29 | my = conn 30 | } 31 | 32 | func TestMySQLDropTableIfExists(t *testing.T) { 33 | if err := my.Table("User").DropIfExists(); err != nil { 34 | t.Fatal(err) 35 | } 36 | } 37 | 38 | func TestMySQLMigration(t *testing.T) { 39 | if err := my.Migrate(new(User), new(TempUser)); err != nil { 40 | t.Fatal(err) 41 | } 42 | } 43 | 44 | func TestMySQLTableExists(t *testing.T) { 45 | if isExist := my.Table("User").Exists(); isExist != true { 46 | t.Fatal(fmt.Errorf("Unexpected error, table %q should exists", "User")) 47 | } 48 | } 49 | 50 | func TestMySQLTruncate(t *testing.T) { 51 | if err := my.Truncate(new(User), TempUser{}); err != nil { 52 | t.Fatal(err) 53 | } 54 | } 55 | 56 | func TestMySQLAddIndex(t *testing.T) { 57 | if err := my.Table("User"). 58 | AddUniqueIndex("Username"); err != nil { 59 | t.Fatal(err) 60 | } 61 | if err := my.Table("User"). 62 | AddIndex("Age"); err != nil { 63 | t.Fatal(err) 64 | } 65 | } 66 | 67 | func TestMySQLEmptyInsertOrUpsert(t *testing.T) { 68 | var users []User 69 | if err := my.Create(&users); err != nil { 70 | t.Fatal(err) 71 | } 72 | 73 | if err := my.Upsert(&users); err != nil { 74 | t.Fatal(err) 75 | } 76 | } 77 | 78 | func TestMySQLCreate(t *testing.T) { 79 | u := getFakeUser() 80 | if err := my.Create(u); err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | u = getFakeUser() 85 | if err := my.Create(u, nameKey); err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | u = getFakeUser() 90 | if err := my.Create(u, idKey); err != nil { 91 | t.Fatal(err) 92 | } 93 | 94 | uu := []User{*getFakeUser(), *getFakeUser()} 95 | if err := my.Create(&uu); err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | users := []*User{getFakeUser(), getFakeUser()} 100 | if err := my.Create(&users); err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | var i *User 105 | if err := my.Create(i); err == nil { 106 | t.Fatal(err) 107 | } 108 | 109 | users = []*User{nil, nil} 110 | if err := my.Create(&users); err == nil { 111 | t.Fatal(err) 112 | } 113 | 114 | users = []*User{getFakeUser(), getFakeUser()} 115 | if err := my.Create(&users, symbolKey); err != nil { 116 | t.Fatal(err) 117 | } 118 | } 119 | 120 | func TestMySQLReplaceInto(t *testing.T) { 121 | if err := my.Table("User"). 122 | AnyOfAncestor(nameKey, idKey). 123 | ReplaceInto("TempUser"); err != nil { 124 | t.Fatal(err) 125 | } 126 | } 127 | 128 | func TestMySQLInsertInto(t *testing.T) { 129 | if err := my.Table("ArchiveUser"). 130 | Migrate(new(User)); err != nil { 131 | t.Fatal(err) 132 | } 133 | if err := my.Table("User"). 134 | AnyOfAncestor(nameKey, idKey). 135 | InsertInto("ArchiveUser"); err != nil { 136 | t.Fatal(err) 137 | } 138 | } 139 | 140 | func TestMySQLSave(t *testing.T) { 141 | var u User 142 | if err := my.Save(u); err == nil { 143 | t.Fatal(errors.New("`Save` func must addressable")) 144 | } 145 | if err := my.Save(nil); err == nil { 146 | t.Fatal(errors.New("nil entity suppose not allow in `Save` func")) 147 | } 148 | 149 | if err := my.Create(&u); err != nil { 150 | t.Fatal(err) 151 | } 152 | u.Name = "Something" 153 | if err := my.Save(&u); err != nil { 154 | t.Fatal(err) 155 | } 156 | } 157 | 158 | func TestMySQLSelect(t *testing.T) { 159 | u := new(User) 160 | if err := my.Select("*", "Name").First(u); err != nil { 161 | t.Fatal(err) 162 | } 163 | } 164 | 165 | func TestMySQLSubQuery(t *testing.T) { 166 | users := new([]User) 167 | if err := my.Where("PrimaryEmail", "in", 168 | db.Table("TestUser"). 169 | Select("Email"). 170 | WhereNotNull("Email"). 171 | WhereIn("Email", []string{ 172 | "DgHlUKz@pYEXo.ru", 173 | "sianloong@hotmail.com", 174 | })).Get(users); err != nil { 175 | t.Fatal(err) 176 | } 177 | } 178 | 179 | func TestMySQLDistinctOn(t *testing.T) { 180 | u := new(User) 181 | if err := my.NewQuery(). 182 | DistinctOn("*").First(u); err == nil { 183 | t.Fatal("Expected `DistinctOn` cannot allow *") 184 | } 185 | 186 | if err := my.NewQuery(). 187 | DistinctOn("").First(u); err == nil { 188 | t.Fatal("Expected `DistinctOn` cannot have empty") 189 | } 190 | 191 | if err := my.NewQuery(). 192 | DistinctOn("Name", "Password").First(u); err != nil { 193 | t.Fatal(err) 194 | } 195 | } 196 | 197 | func TestMySQLGet(t *testing.T) { 198 | type NewUser struct { 199 | User 200 | Arr []string 201 | Struct struct { 202 | Name string 203 | } 204 | Data json.RawMessage 205 | Geo datastore.GeoPoint 206 | } 207 | 208 | nu := new(NewUser) 209 | if err := my.Table("User").Migrate(nu); err != nil { 210 | t.Fatal(err) 211 | } 212 | 213 | // json null shouldn't run panic when retrieve out 214 | o := new(NewUser) 215 | if err := db.Table("User").First(o); err != nil { 216 | t.Fatal(err) 217 | } 218 | 219 | if o.Key == nil || len(o.Arr) != 0 { 220 | t.Fatal(errors.New("unexpected result")) 221 | } 222 | 223 | u := new(User) 224 | // restore back to original structure 225 | if err := my.Migrate(u); err != nil { 226 | t.Fatal(err) 227 | } 228 | if err := my.First(u); err != nil { 229 | t.Fatal(err) 230 | } 231 | 232 | if err := my.Find(u.Key, u); err != nil { 233 | t.Fatal(err) 234 | } 235 | 236 | users := new([]User) 237 | if err := my.Get(users); err != nil { 238 | t.Fatal(err) 239 | } 240 | 241 | if err := my.NewQuery().Unscoped().Get(users); err != nil { 242 | t.Fatal(err) 243 | } 244 | 245 | u2 := getFakeUser() 246 | u2.Key = symbolKey 247 | if err := my.Create(u2); err != nil { 248 | t.Fatal(err) 249 | } 250 | 251 | if err := my.Find(u2.Key, u2); err != nil { 252 | t.Fatal(err) 253 | } 254 | 255 | if err := my.Where("$Key", "=", u2.Key).First(u); err != nil { 256 | t.Fatal(err) 257 | } 258 | if u.Key == nil { 259 | t.Fatal("unexpected result") 260 | } 261 | } 262 | 263 | func TestMySQLAncestor(t *testing.T) { 264 | users := new([]User) 265 | if err := my.Ancestor(idKey). 266 | Get(users); err != nil { 267 | t.Fatal(err) 268 | } 269 | if len(*users) <= 0 { 270 | t.Fatal(`Unexpected result from filter "Ancestor" using id key`) 271 | } 272 | 273 | if err := my.Ancestor(nameKey). 274 | Get(users); err != nil { 275 | t.Fatal(err) 276 | } 277 | if len(*users) <= 0 { 278 | t.Fatal(`Unexpected result from filter "Ancestor" using name key`) 279 | } 280 | 281 | if err := my.AnyOfAncestor(idKey, nameKey).Get(users); err != nil { 282 | t.Fatal(err) 283 | } 284 | 285 | if err := my.Ancestor(symbolKey).Get(users); err != nil { 286 | t.Fatal(err) 287 | } 288 | if len(*users) <= 0 { 289 | t.Fatal(`Unexpected result from filter "Ancestor" using name key with symbol`) 290 | } 291 | } 292 | 293 | func TestMySQLWhereFilter(t *testing.T) { 294 | age := uint8(85) 295 | creditLimit := float64(100.015) 296 | dob, _ := time.Parse("2006-01-02", "1900-10-01") 297 | 298 | u := getFakeUser() 299 | u.Age = age 300 | u.Nickname = nil 301 | u.CreditLimit = creditLimit 302 | u.Birthdate = goloquent.Date(dob) 303 | 304 | my.Create(u) 305 | 306 | users := new([]User) 307 | if err := my.Where("Age", "=", &age). 308 | Get(users); err != nil { 309 | t.Fatal(err) 310 | } 311 | if len(*users) <= 0 { 312 | t.Fatal(`Unexpected result from filter using "Where"`) 313 | } 314 | 315 | if err := my.Where("Birthdate", "=", goloquent.Date(dob)). 316 | Get(users); err != nil { 317 | t.Fatal(err) 318 | } 319 | if len(*users) <= 0 { 320 | t.Fatal(`Unexpected result from filter using "Where"`) 321 | } 322 | 323 | var nilNickname *string 324 | if err := my.Where("Nickname", "=", nilNickname). 325 | Get(users); err != nil { 326 | t.Fatal(err) 327 | } 328 | if len(*users) <= 0 { 329 | t.Fatal(`Unexpected result from filter using "Where"`) 330 | } 331 | 332 | if err := my.Where("CreditLimit", "=", &creditLimit). 333 | Get(users); err != nil { 334 | t.Fatal(err) 335 | } 336 | if len(*users) <= 0 { 337 | t.Fatal(`Unexpected result from filter using "Where"`) 338 | } 339 | } 340 | 341 | func TestMySQLWhereAnyLike(t *testing.T) { 342 | users := new([]User) 343 | 344 | u := getFakeUser() 345 | u.PrimaryEmail = "sianloong@hotmail.com" 346 | if err := my.Create(u); err != nil { 347 | t.Fatal(err) 348 | } 349 | 350 | if err := my.NewQuery(). 351 | WhereAnyLike("PrimaryEmail", []string{ 352 | "lzPskFb@OOxzA.net", 353 | "sianloong%", 354 | }).Get(users); err != nil { 355 | t.Fatal(err) 356 | } 357 | 358 | if len(*users) <= 0 { 359 | t.Fatal(`Unexpected result from filter using "WhereAnyLike"`) 360 | } 361 | } 362 | 363 | func TestMySQLJSONRawMessage(t *testing.T) { 364 | u := getFakeUser() 365 | if err := my.Upsert(u); err != nil { 366 | t.Fatal(err) 367 | } 368 | u.Information = nil 369 | if err := my.Upsert(u); err != nil { 370 | t.Fatal(err) 371 | } 372 | u.Information = json.RawMessage(`[]`) 373 | if err := my.Upsert(u); err != nil { 374 | t.Fatal(err) 375 | } 376 | u.Information = json.RawMessage(`{}`) 377 | if err := my.Upsert(u); err != nil { 378 | t.Fatal(err) 379 | } 380 | u.Information = json.RawMessage(`null`) 381 | if err := my.Upsert(u); err != nil { 382 | t.Fatal(err) 383 | } 384 | u.Information = json.RawMessage(`notvalid`) 385 | if err := my.Upsert(u); err == nil { 386 | t.Fatal(err) 387 | } 388 | } 389 | 390 | func TestMySQLEmptySliceInJSON(t *testing.T) { 391 | u := new(User) 392 | if err := my.First(u); err != nil { 393 | t.Fatal(err) 394 | } 395 | if u.Emails == nil { 396 | t.Fatal(fmt.Errorf("empty slice should init on any `Get` func")) 397 | } 398 | 399 | u2 := getFakeUser() 400 | u2.Emails = nil 401 | u2.PrimaryEmail = "sianloong@hotmail.com" 402 | if err := my.Create(u2); err != nil { 403 | t.Fatal(err) 404 | } 405 | if u2.Emails == nil { 406 | t.Fatal(fmt.Errorf("empty slice should init on any `Create` func")) 407 | } 408 | } 409 | 410 | func TestMySQLJSONEqual(t *testing.T) { 411 | users := new([]User) 412 | if err := my.NewQuery(). 413 | WhereJSONEqual("Address>PostCode", int32(85)). 414 | Get(users); err != nil { 415 | t.Fatal(err) 416 | } 417 | 418 | if err := my.NewQuery(). 419 | WhereJSONEqual("Address>PostCode", uint32(85)). 420 | Get(users); err != nil { 421 | t.Fatal(err) 422 | } 423 | 424 | postCode := uint32(85) 425 | if err := my.NewQuery(). 426 | WhereJSONEqual("Address>PostCode", &postCode). 427 | Get(users); err != nil { 428 | t.Fatal(err) 429 | } 430 | 431 | if err := my.NewQuery(). 432 | WhereJSONEqual("Address>Line1", "7812, Jalan Section 22"). 433 | Get(users); err != nil { 434 | t.Fatal(err) 435 | } 436 | if len(*users) <= 0 { 437 | t.Fatal("JSON equal has unexpected result") 438 | } 439 | 440 | var emptyStr string 441 | if err := my.NewQuery(). 442 | WhereJSONEqual("Address>Line2", emptyStr). 443 | Get(users); err != nil { 444 | t.Fatal(err) 445 | } 446 | if len(*users) <= 0 { 447 | t.Fatal("JSON equal has unexpected result") 448 | } 449 | 450 | timeZone := new(time.Time) 451 | if err := my.NewQuery(). 452 | WhereJSONEqual("Address>region.TimeZone", timeZone). 453 | Get(users); err != nil { 454 | t.Fatal(err) 455 | } 456 | if len(*users) <= 0 { 457 | t.Fatal("JSON equal has unexpected result") 458 | } 459 | } 460 | 461 | func TestMySQLJSONNotEqual(t *testing.T) { 462 | var timeZone *time.Time 463 | users := new([]User) 464 | if err := my.NewQuery(). 465 | WhereJSONNotEqual("Address>region.TimeZone", timeZone). 466 | Get(users); err != nil { 467 | t.Fatal(err) 468 | } 469 | if len(*users) <= 0 { 470 | t.Fatal("JSON equal has unexpected result") 471 | } 472 | 473 | if err := my.NewQuery(). 474 | WhereJSONNotEqual("Address>Country", ""). 475 | Get(users); err != nil { 476 | t.Fatal(err) 477 | } 478 | if len(*users) > 0 { 479 | t.Fatal("JSON equal has unexpected result") 480 | } 481 | } 482 | 483 | func TestMySQLJSONIn(t *testing.T) { 484 | users := new([]User) 485 | if err := my.NewQuery(). 486 | WhereJSONIn("Address>PostCode", []interface{}{0, 10, 20}). 487 | Get(users); err != nil { 488 | t.Fatal(err) 489 | } 490 | if len(*users) <= 0 { 491 | t.Fatal("JSON in has unexpected result") 492 | } 493 | } 494 | 495 | func TestMySQLJSONNotIn(t *testing.T) { 496 | users := new([]User) 497 | if err := my.NewQuery(). 498 | WhereJSONNotIn("Address>Line1", []interface{}{"PJ", "KL", "Cheras"}). 499 | Get(users); err != nil { 500 | t.Fatal(err) 501 | } 502 | if len(*users) <= 0 { 503 | t.Fatal("JSON contain any has unexpected result") 504 | } 505 | } 506 | 507 | func TestMySQLJSONContainAny(t *testing.T) { 508 | users := new([]User) 509 | if err := my.NewQuery(). 510 | WhereJSONContainAny("Emails", []Email{ 511 | "support@hotmail.com", 512 | "invalid@gmail.com", 513 | }).Get(users); err != nil { 514 | t.Fatal(err) 515 | } 516 | if len(*users) <= 0 { 517 | t.Fatal("JSON contain any has unexpected result") 518 | } 519 | 520 | if err := my.NewQuery(). 521 | WhereJSONContainAny("Emails", []Email{ 522 | "invalid@gmail.com", 523 | "invalid@hotmail.com", 524 | }).Get(users); err != nil { 525 | t.Fatal(err) 526 | } 527 | if len(*users) > 0 { 528 | t.Fatal("JSON contain any has unexpected result") 529 | } 530 | } 531 | 532 | func TestMySQLJSONType(t *testing.T) { 533 | users := new([]User) 534 | if err := my.NewQuery(). 535 | WhereJSONType("Address>region", "OBJECT"). 536 | Get(users); err != nil { 537 | t.Fatal(err) 538 | } 539 | if len(*users) <= 0 { 540 | t.Fatal("JSON isObject has unexpected result") 541 | } 542 | } 543 | 544 | func TestMySQLJSONIsObject(t *testing.T) { 545 | users := new([]User) 546 | if err := my.NewQuery(). 547 | WhereJSONIsObject("Address>region"). 548 | Get(users); err != nil { 549 | t.Fatal(err) 550 | } 551 | if len(*users) <= 0 { 552 | t.Fatal("JSON isObject has unexpected result") 553 | } 554 | } 555 | 556 | func TestMySQLJSONIsArray(t *testing.T) { 557 | users := new([]User) 558 | if err := my.NewQuery(). 559 | WhereJSONIsArray("Address>region.keys"). 560 | Get(users); err != nil { 561 | t.Fatal(err) 562 | } 563 | if len(*users) <= 0 { 564 | t.Fatal("JSON isArray has unexpected result") 565 | } 566 | } 567 | 568 | func TestMySQLPaginate(t *testing.T) { 569 | users := new([]User) 570 | p := &goloquent.Pagination{ 571 | Limit: 1, 572 | } 573 | if err := my.Paginate(p, users); err != nil { 574 | t.Fatal(err) 575 | } 576 | if len(*(users)) <= 0 { 577 | t.Fatal(fmt.Errorf("paginate record set shouldn't empty")) 578 | } 579 | 580 | p.Cursor = p.NextCursor() 581 | if err := my.Paginate(p, users); err != nil { 582 | t.Fatal(err) 583 | } 584 | if len(*(users)) <= 0 { 585 | t.Fatal(fmt.Errorf("paginate record set shouldn't empty")) 586 | } 587 | 588 | p2 := &goloquent.Pagination{ 589 | Limit: 1, 590 | } 591 | if err := my.Ancestor(nameKey). 592 | Paginate(p2, users); err != nil { 593 | t.Fatal(err) 594 | } 595 | if len(*(users)) <= 0 { 596 | t.Fatal(fmt.Errorf("paginate record set shouldn't empty")) 597 | } 598 | 599 | p2.Cursor = p.NextCursor() 600 | if err := my.Paginate(p2, users); err != nil { 601 | t.Fatal(err) 602 | } 603 | if len(*(users)) <= 0 { 604 | t.Fatal(fmt.Errorf("paginate record set shouldn't empty")) 605 | } 606 | } 607 | 608 | func TestMySQLUpsert(t *testing.T) { 609 | u := getFakeUser() 610 | if err := my.Upsert(u); err != nil { 611 | t.Fatal(err) 612 | } 613 | 614 | u = getFakeUser() 615 | if err := my.Upsert(u, idKey); err != nil { 616 | t.Fatal(err) 617 | } 618 | 619 | u = getFakeUser() 620 | if err := my.Upsert(u, nameKey); err != nil { 621 | t.Fatal(err) 622 | } 623 | 624 | users := []*User{getFakeUser(), getFakeUser()} 625 | if err := my.Upsert(&users); err != nil { 626 | t.Fatal(err) 627 | } 628 | 629 | uu := []User{*getFakeUser(), *getFakeUser()} 630 | if err := my.Upsert(&uu); err != nil { 631 | t.Fatal(err) 632 | } 633 | 634 | uuu := []User{*getFakeUser(), *getFakeUser()} 635 | if err := my.Upsert(&uuu, idKey); err != nil { 636 | t.Fatal(err) 637 | } 638 | 639 | uuu = []User{*getFakeUser(), *getFakeUser()} 640 | if err := my.Upsert(&uuu, nameKey); err != nil { 641 | t.Fatal(err) 642 | } 643 | } 644 | 645 | func TestMySQLUpdate(t *testing.T) { 646 | if err := my.Table("User").Limit(1). 647 | Where("Name", "=", "Dr. Antoinette Zboncak"). 648 | Update(map[string]interface{}{ 649 | "Name": "sianloong", 650 | }); err != nil { 651 | t.Fatal(err) 652 | } 653 | 654 | if err := my.Table("User").Limit(1). 655 | Update(map[string]interface{}{ 656 | "Emails": []string{"abc@gmail.com", "abc@hotmail.com", "abc@yahoo.com"}, 657 | }); err != nil { 658 | t.Fatal(err) 659 | } 660 | 661 | // TODO: support struct 662 | // if err := my.Table("User").Limit(1). 663 | // Update(map[string]interface{}{ 664 | // "Address": Address{"", "Line2", "", 63000}, 665 | // }); err != nil { 666 | // t.Fatal(err) 667 | // } 668 | } 669 | func TestMySQLSoftDelete(t *testing.T) { 670 | u := getFakeUser() 671 | if err := my.Create(u); err != nil { 672 | t.Fatal(err) 673 | } 674 | if err := my.Delete(u); err != nil { 675 | t.Fatal(err) 676 | } 677 | } 678 | 679 | func TestMySQLHardDelete(t *testing.T) { 680 | u := new(User) 681 | if err := my.First(u); err != nil { 682 | t.Fatal(err) 683 | } 684 | if err := my.Destroy(u); err != nil { 685 | t.Fatal(err) 686 | } 687 | } 688 | 689 | func TestMySQLTable(t *testing.T) { 690 | uuu := []*User{getFakeUser(), getFakeUser()} 691 | if err := my.Table("TempUser").Create(&uuu); err != nil { 692 | t.Fatal(err) 693 | } 694 | 695 | users := new([]User) 696 | if err := my.Table("User"). 697 | WhereLike("Name", "nick%"). 698 | Get(users); err != nil { 699 | t.Fatal(err) 700 | } 701 | 702 | if err := my.Table("User"). 703 | Where("Age", ">", 0). 704 | Get(users); err != nil { 705 | t.Fatal(err) 706 | } 707 | 708 | user := new(User) 709 | if err := my.Table("User"). 710 | First(user); err != nil { 711 | t.Fatal(err) 712 | } 713 | } 714 | 715 | func TestMySQLRunInTransaction(t *testing.T) { 716 | if err := my.RunInTransaction(func(txn *goloquent.DB) error { 717 | u := new(User) 718 | if err := txn.NewQuery(). 719 | WLock().First(u); err != nil { 720 | return err 721 | } 722 | 723 | u.Name = "NewName" 724 | u.UpdatedDateTime = time.Now().UTC() 725 | return txn.Save(u) 726 | }); err != nil { 727 | t.Fatal(err) 728 | } 729 | } 730 | 731 | func TestMySQLScan(t *testing.T) { 732 | var count, sum uint 733 | if err := my.Table("User"). 734 | Select("COALESCE(COUNT(*),0), COALESCE(SUM(Age),0)"). 735 | Scan(&count, &sum); err != nil { 736 | t.Fatal(err) 737 | } 738 | log.Println("Count :", count, ", Sum :", sum) 739 | } 740 | 741 | func TestMySQLClose(t *testing.T) { 742 | defer my.Close() 743 | } 744 | --------------------------------------------------------------------------------