├── .gitignore ├── migrate ├── schema_test.go ├── migrations.yaml ├── schema.go ├── command.go ├── migrate.go ├── column.go └── migrations.orm.go ├── event ├── migration.go ├── dispatch.go ├── query.go └── event.go ├── _examples ├── models │ ├── roles.yaml │ ├── organizations.yaml │ ├── enterprises.yaml │ ├── users.yaml │ ├── roles.orm.go │ └── organizations.orm.go ├── go.mod ├── go.sum └── main.go ├── Makefile ├── query ├── join.go ├── order.go ├── condition_test.go ├── builder_test.go ├── database.go ├── misc.go ├── condition.go └── builder.go ├── go.mod ├── eloquent.go ├── generator ├── template │ ├── entity_test.go │ ├── template.go │ ├── wrap.go │ ├── scope.go │ ├── entity.go │ ├── relation.go │ └── model.go ├── parser.go ├── domain_test.go ├── context.go ├── domain.go └── helper.go ├── LICENSE ├── go.sum ├── database.go ├── cmd └── orm │ └── main.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.exe~ 3 | *.dll 4 | *.so 5 | *.dylib 6 | *.test 7 | *.out 8 | bin/* 9 | .idea/ 10 | .vscode/ -------------------------------------------------------------------------------- /migrate/schema_test.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSchema_Create(t *testing.T) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /event/migration.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | type MigrationsStartedEvent struct{} 4 | type MigrationsEndedEvent struct{} 5 | 6 | type MigrationStartedEvent struct { 7 | SQL string 8 | } 9 | type MigrationEndedEvent struct { 10 | SQL string 11 | } 12 | -------------------------------------------------------------------------------- /_examples/models/roles.yaml: -------------------------------------------------------------------------------- 1 | package: "models" 2 | 3 | meta: 4 | table_prefix: wz_ 5 | 6 | models: 7 | - name: Role 8 | relations: 9 | - model: user 10 | rel: 1-n 11 | definition: 12 | fields: 13 | - name: name 14 | type: string 15 | - name: description 16 | type: string 17 | -------------------------------------------------------------------------------- /_examples/models/organizations.yaml: -------------------------------------------------------------------------------- 1 | package: "models" 2 | 3 | meta: 4 | table_prefix: wz_ 5 | 6 | models: 7 | - name: organization 8 | relations: 9 | - model: user 10 | rel: belongsToMany 11 | definition: 12 | fields: 13 | - name: id 14 | type: int64 15 | - name: name 16 | type: string -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | run: generate-models 3 | go run _examples/main.go 4 | 5 | generate-models: build 6 | ./bin/eloquent gen --source "./_examples/models/*.yaml" 7 | go fmt ./_examples/models/*.go 8 | 9 | build: 10 | go build -o bin/eloquent cmd/orm/*.go 11 | 12 | init: build 13 | ./bin/eloquent gen --source "./migrate/*.yaml" 14 | go fmt ./migrate/migrations.orm.go 15 | 16 | .PHONY: build init generate-models run 17 | -------------------------------------------------------------------------------- /migrate/migrations.yaml: -------------------------------------------------------------------------------- 1 | 2 | package: "migrate" 3 | 4 | models: 5 | - name: migrations 6 | definition: 7 | table_name: migrations 8 | without_create_time: true 9 | without_update_time: true 10 | fields: 11 | - name: version 12 | type: string 13 | - name: migration 14 | type: string 15 | - name: table 16 | type: string 17 | - name: batch 18 | type: int64 -------------------------------------------------------------------------------- /_examples/models/enterprises.yaml: -------------------------------------------------------------------------------- 1 | package: "models" 2 | 3 | meta: 4 | table_prefix: wz_ 5 | 6 | models: 7 | - name: enterprise 8 | relations: 9 | - model: user 10 | rel: hasMany 11 | definition: 12 | soft_delete: true 13 | fields: 14 | - name: id 15 | type: int64 16 | - name: name 17 | type: string 18 | - name: address 19 | type: string 20 | - name: status 21 | type: int8 -------------------------------------------------------------------------------- /query/join.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type sqlJoin struct { 8 | joinType string 9 | tableName string 10 | on ConditionGroup 11 | } 12 | 13 | func (t sqlJoin) String(tableAlias string) (string, []interface{}) { 14 | newBuilder := ConditionBuilder() 15 | t.on(newBuilder) 16 | 17 | newCond, newParams := newBuilder.Resolve(tableAlias) 18 | sql := fmt.Sprintf("%s %s ON %s", t.joinType, t.tableName, newCond) 19 | return sql, newParams 20 | } 21 | -------------------------------------------------------------------------------- /event/dispatch.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | // Dispatcher is an interface for event dispatcher 4 | type Dispatcher interface { 5 | Publish(evt interface{}) 6 | } 7 | 8 | var dispatcher Dispatcher 9 | 10 | // SetDispatcher set a event dispatcher 11 | func SetDispatcher(disp Dispatcher) { 12 | dispatcher = disp 13 | } 14 | 15 | // Dispatch an event to Dispatcher 16 | func Dispatch(evt interface{}) { 17 | if dispatcher == nil { 18 | return 19 | } 20 | 21 | dispatcher.Publish(evt) 22 | } 23 | -------------------------------------------------------------------------------- /query/order.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type orderBys []sqlOrderBy 9 | 10 | func (ob orderBys) String(tableAlias string) string { 11 | var obs = make([]string, len(ob)) 12 | for i, o := range ob { 13 | if o.raw { 14 | obs[i] = o.Raw 15 | } else { 16 | obs[i] = fmt.Sprintf("%s %s", replaceTableField(tableAlias, o.Field), o.Direction) 17 | } 18 | } 19 | 20 | return strings.Join(obs, ",") 21 | } 22 | 23 | type sqlOrderBy struct { 24 | raw bool 25 | Raw string 26 | Field string 27 | Direction string 28 | } 29 | -------------------------------------------------------------------------------- /event/query.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // QueryExecutedEvent each SQL query executed by your application will publish a QueryExecutedEvent 8 | type QueryExecutedEvent struct { 9 | SQL string 10 | Bindings []interface{} 11 | Time time.Duration 12 | } 13 | 14 | // TransactionBeginningEvent fired when a new transaction started 15 | type TransactionBeginningEvent struct{} 16 | 17 | // TransactionCommittedEvent fired when a transaction has been committed 18 | type TransactionCommittedEvent struct{} 19 | 20 | // TransactionRolledBackEvent fired when a transaction has been rollback 21 | type TransactionRolledBackEvent struct{} 22 | -------------------------------------------------------------------------------- /_examples/go.mod: -------------------------------------------------------------------------------- 1 | module _examples 2 | 3 | go 1.19 4 | 5 | replace github.com/mylxsw/eloquent => ../ 6 | 7 | require ( 8 | github.com/go-sql-driver/mysql v1.7.0 9 | github.com/iancoleman/strcase v0.2.0 10 | github.com/mylxsw/asteria v1.0.2-0.20221222065801-3f0515da865f 11 | github.com/mylxsw/eloquent v0.0.0-00010101000000-000000000000 12 | github.com/mylxsw/go-utils v1.0.3 13 | gopkg.in/guregu/null.v3 v3.5.0 14 | ) 15 | 16 | require ( 17 | github.com/json-iterator/go v1.1.12 // indirect 18 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 19 | github.com/modern-go/reflect2 v1.0.2 // indirect 20 | golang.org/x/text v0.3.4 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /query/condition_test.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestConditionBuilder_Clone(t *testing.T) { 9 | builder := ConditionBuilder() 10 | builder.Where("field1", "=", 123) 11 | builder.WhereIn("field2", 134, 521, 341) 12 | 13 | sql, params := builder.Resolve("") 14 | res1 := fmt.Sprint(sql, params) 15 | 16 | newBuilder := builder.Clone() 17 | newBuilder.Where("field3", ">", 199) 18 | 19 | sql, params = newBuilder.Resolve("") 20 | res2 := fmt.Sprint(sql, params) 21 | 22 | sql, params = builder.Resolve("") 23 | res3 := fmt.Sprint(sql, params) 24 | 25 | if res1 != res3 || res1 == res2 { 26 | t.Errorf("test failed") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mylxsw/eloquent 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/iancoleman/strcase v0.2.0 7 | github.com/mylxsw/go-utils v1.0.3-0.20230227032219-b96a084bd6c3 8 | github.com/urfave/cli v1.22.5 9 | gopkg.in/guregu/null.v3 v3.5.0 10 | gopkg.in/yaml.v3 v3.0.1 11 | ) 12 | 13 | require ( 14 | github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/kr/pretty v0.1.0 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 19 | github.com/stretchr/testify v1.6.1 // indirect 20 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /eloquent.go: -------------------------------------------------------------------------------- 1 | package eloquent 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/mylxsw/eloquent/query" 7 | ) 8 | 9 | type Database interface { 10 | Query(ctx context.Context, builder QueryBuilder, cb func(row Scanner) (any, error)) ([]any, error) 11 | Insert(ctx context.Context, tableName string, kv query.KV) (int64, error) 12 | Delete(ctx context.Context, builder query.SQLBuilder) (int64, error) 13 | Update(ctx context.Context, builder query.SQLBuilder, kv query.KV) (int64, error) 14 | Statement(ctx context.Context, raw string, args ...any) error 15 | } 16 | 17 | type QueryBuilder interface { 18 | ResolveQuery() (sqlStr string, args []any) 19 | } 20 | 21 | // Scanner is an interface which wraps sql.Rows's Scan method 22 | type Scanner interface { 23 | Scan(dest ...any) error 24 | } 25 | -------------------------------------------------------------------------------- /generator/template/entity_test.go: -------------------------------------------------------------------------------- 1 | package template_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/mylxsw/go-utils/assert" 8 | "gopkg.in/guregu/null.v3" 9 | ) 10 | 11 | type testStruct struct { 12 | S1 null.String `json:"s1"` 13 | S2 null.Int 14 | } 15 | 16 | func TestEntity(t *testing.T) { 17 | s1 := null.StringFrom("Hello, world") 18 | s2 := null.StringFrom("Hello, world") 19 | 20 | assert.True(t, s1 == s2) 21 | 22 | s1.Valid = false 23 | assert.True(t, s1 != s2) 24 | 25 | ts := testStruct{S1: null.StringFrom("Hello, world"), S2: null.IntFrom(1230)} 26 | { 27 | data, _ := json.Marshal(ts) 28 | assert.Equal(t, `{"s1":"Hello, world","S2":1230}`, string(data)) 29 | } 30 | { 31 | ts.S2 = null.NewInt(0, false) 32 | 33 | data, _ := json.Marshal(ts) 34 | assert.Equal(t, `{"s1":"Hello, world","S2":null}`, string(data)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /generator/template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | var temp = ` 8 | package {{ .PackageName }} 9 | 10 | // !!! DO NOT EDIT THIS FILE 11 | 12 | import ( 13 | {{ range $i, $imp := packages }} 14 | "{{ $imp }}"{{ end }} 15 | ) 16 | 17 | func init() { 18 | {{ range $i, $m := .Models }}{{ if $m.Definition.SoftDelete }} 19 | // Add{{camel $m.Name }}GlobalScope assign a global scope to a model for soft delete 20 | AddGlobalScopeFor{{ camel $m.Name }}("soft_delete", func(builder query.Condition) { 21 | builder.WhereNull("deleted_at") 22 | }) 23 | {{ end }}{{ end }} 24 | } 25 | 26 | {{ range $i, $m := .Models }} 27 | %s 28 | %s 29 | %s 30 | %s 31 | %s 32 | {{ end }} 33 | ` 34 | 35 | func GetTemplate() string { 36 | return fmt.Sprintf( 37 | temp, 38 | GetEntityTemplate(), 39 | GetRelationTemplate(), 40 | GetScopeTemplate(), 41 | GetEntityPlainTemplate(), 42 | GetModelTemplate(), 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /generator/parser.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/iancoleman/strcase" 10 | ) 11 | 12 | var funcMap = template.FuncMap{ 13 | "implode": strings.Join, 14 | "trim": strings.Trim, 15 | "trim_right": strings.TrimRight, 16 | "trim_left": strings.TrimLeft, 17 | "trim_space": strings.TrimSpace, 18 | "lowercase": strings.ToLower, 19 | "format": fmt.Sprintf, 20 | "snake": strcase.ToSnake, 21 | "camel": strcase.ToCamel, 22 | "lower_camel": strcase.ToLowerCamel, 23 | } 24 | 25 | func AddFunc(name string, f interface{}) { 26 | funcMap[name] = f 27 | } 28 | 29 | // ParseTemplate 模板解析 30 | func ParseTemplate(templateContent string, data Domain) (string, error) { 31 | ctx := DomainContext{domain: data} 32 | ctx.Register() 33 | 34 | var buffer bytes.Buffer 35 | if err := template.Must(template.New("").Funcs(funcMap).Parse(templateContent)).Execute(&buffer, data); err != nil { 36 | return "", err 37 | } 38 | 39 | return buffer.String(), nil 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 管宜尧 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 | -------------------------------------------------------------------------------- /generator/template/wrap.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | func GetEntityPlainTemplate() string { 4 | return ` 5 | type {{ camel $m.Name }} struct { {{ range $j, $f := fields $m.Definition }} 6 | {{ camel $f.Name }} {{ $f.Type }} {{ tag $f }}{{ end }} 7 | } 8 | 9 | func (w {{ camel $m.Name }}) To{{ camel $m.Name }}N(allows ...string) {{ camel $m.Name }}N { 10 | if len(allows) == 0 { 11 | return {{ camel $m.Name }}N { 12 | {{ range $j, $f := fields $m.Definition }} 13 | {{ camel $f.Name }}: {{ wrap_type (printf "w.%s" $f.Name) $f.Type }},{{ end }} 14 | } 15 | } 16 | 17 | res := {{ camel $m.Name }}N{} 18 | for _, al := range allows { 19 | switch strcase.ToSnake(al) { 20 | {{ range $j, $f := fields $m.Definition }} 21 | case "{{ snake $f.Name }}": 22 | res.{{ camel $f.Name }} = {{ wrap_type (printf "w.%s" $f.Name) $f.Type }}{{ end }} 23 | default: 24 | } 25 | } 26 | 27 | return res 28 | } 29 | 30 | // As convert object to other type 31 | // dst must be a pointer to struct 32 | func (w {{ camel $m.Name }}) As(dst interface{}) error { 33 | return query.Copy(w, dst) 34 | } 35 | 36 | 37 | func (w *{{ camel $m.Name }}N) To{{ camel $m.Name }} () {{ camel $m.Name }} { 38 | return {{ camel $m.Name }} { 39 | {{ range $j, $f := fields $m.Definition }} 40 | {{ camel $f.Name }}: {{ unwrap_type $f.Name $f.Type }},{{ end }} 41 | } 42 | } 43 | ` 44 | } 45 | -------------------------------------------------------------------------------- /generator/domain_test.go: -------------------------------------------------------------------------------- 1 | package generator_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/mylxsw/eloquent/generator" 8 | "gopkg.in/yaml.v3" 9 | ) 10 | 11 | func TestDomain(t *testing.T) { 12 | domain := generator.Domain{ 13 | Imports: []string{ 14 | "github.com/mylxsw/eloquent", 15 | }, 16 | PackageName: "models", 17 | Models: []generator.Model{ 18 | { 19 | Name: "user", 20 | Relations: []generator.Relation{ 21 | { 22 | Model: "role", 23 | Rel: "n-1", 24 | ForeignKey: "role_id", 25 | OwnerKey: "id", 26 | }, 27 | }, 28 | Definition: generator.Definition{ 29 | TableName: "user", 30 | WithoutCreateTime: false, 31 | WithoutUpdateTime: false, 32 | SoftDelete: false, 33 | Fields: []generator.DefinitionField{ 34 | { 35 | Name: "id", 36 | Type: "int64", 37 | Tag: `json:"id"`, 38 | }, 39 | { 40 | Name: "name", 41 | Type: "string", 42 | Tag: `json:"name"`, 43 | }, 44 | { 45 | Name: "age", 46 | Type: "int64", 47 | Tag: `json:"age"`, 48 | }, 49 | }, 50 | }, 51 | }, 52 | }, 53 | Meta: generator.Meta{ 54 | TablePrefix: "el_", 55 | }, 56 | } 57 | 58 | marshal, err := yaml.Marshal(domain) 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | fmt.Println(string(marshal)) 64 | } 65 | -------------------------------------------------------------------------------- /_examples/models/users.yaml: -------------------------------------------------------------------------------- 1 | package: "models" 2 | 3 | meta: 4 | table_prefix: wz_ 5 | 6 | models: 7 | - name: User 8 | relations: 9 | - model: role 10 | rel: belongsTo 11 | foreign_key: role_id 12 | owner_key: id 13 | - model: enterprise 14 | rel: belongsTo 15 | foreign_key: enterprise_id 16 | owner_key: id 17 | - model: userExt 18 | rel: hasOne 19 | - model: organization 20 | rel: belongsToMany 21 | definition: 22 | soft_delete: true 23 | fields: 24 | - name: id 25 | type: int64 26 | tag: 'json:"id"' 27 | - name: name 28 | type: string 29 | - name: email 30 | type: string 31 | tag: 'json:"email"' 32 | - name: password 33 | type: string 34 | tag: 'json:"password" yaml:"password"' 35 | - name: role_id 36 | type: int64 37 | - name: enterprise_id 38 | type: int64 39 | - name: remember_token 40 | tag: 'json:"remember_token" yaml:"remember_token"' 41 | 42 | - name: UserExt 43 | relations: 44 | - model: user 45 | rel: belongsTo 46 | definition: 47 | fields: 48 | - name: address 49 | type: string 50 | - name: qq 51 | type: string 52 | - name: wechat 53 | type: string 54 | - name: user_id 55 | type: int64 56 | 57 | - name: PasswordReset 58 | definition: 59 | without_update_time: true 60 | fields: 61 | - name: email 62 | type: string 63 | - name: token 64 | type: string 65 | -------------------------------------------------------------------------------- /migrate/schema.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | type Schema struct { 4 | m *Manager 5 | version string 6 | 7 | tableName string 8 | } 9 | 10 | // Create creat a new table 11 | func (s *Schema) Create(table string, apply func(builder *Builder)) { 12 | builder := NewBuilder(table, s.m.Prefix).DefaultStringLength(s.m.DefaultStringLength) 13 | builder.Engine(s.m.Engine) 14 | builder.Charset(s.m.Charset) 15 | builder.Collation(s.m.Collation) 16 | 17 | builder.Create() 18 | apply(builder) 19 | 20 | s.tableName = table 21 | s.m.Execute(builder, s.version) 22 | } 23 | 24 | // Table update an existing table 25 | func (s *Schema) Table(table string, apply func(builder *Builder)) { 26 | builder := NewBuilder(table, s.m.Prefix).DefaultStringLength(s.m.DefaultStringLength) 27 | builder.Engine(s.m.Engine) 28 | builder.Charset(s.m.Charset) 29 | builder.Collation(s.m.Collation) 30 | 31 | apply(builder) 32 | 33 | s.tableName = table 34 | s.m.Execute(builder, s.version) 35 | } 36 | 37 | // Drop a table 38 | func (s *Schema) Drop(table string) { 39 | builder := NewBuilder(table, s.m.Prefix) 40 | builder.Drop() 41 | 42 | s.tableName = table 43 | s.m.Execute(builder, s.version) 44 | } 45 | 46 | // DropIfExists Drop a table if exists 47 | func (s *Schema) DropIfExists(table string) { 48 | builder := NewBuilder(table, s.m.Prefix) 49 | builder.DropIfExists() 50 | 51 | s.tableName = table 52 | s.m.Execute(builder, s.version) 53 | } 54 | 55 | // Raw execute a raw sql statements 56 | func (s *Schema) Raw(table string, apply func() []string) { 57 | s.m.ExecuteRaw(s.version, table, apply()...) 58 | } 59 | 60 | // NewSchema create a new Schema 61 | func NewSchema(m *Manager, v string) *Schema { 62 | return &Schema{version: v, m: m} 63 | } 64 | -------------------------------------------------------------------------------- /generator/context.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type DomainContext struct { 8 | domain Domain 9 | } 10 | 11 | func (d DomainContext) Register() { 12 | AddFunc("table", d.tableName) 13 | AddFunc("packages", d.importPackages) 14 | AddFunc("assignable_fields", d.assignableFields) 15 | } 16 | 17 | func (d DomainContext) tableName(i int) string { 18 | m := d.domain.Models[i] 19 | if m.Definition.TableName != "" { 20 | return d.domain.Meta.TablePrefix + m.Definition.TableName 21 | } 22 | 23 | return d.domain.Meta.TablePrefix + strings.ToLower(m.Name) 24 | } 25 | 26 | func (d DomainContext) assignableFields(def Definition) []DefinitionField { 27 | fields := make([]DefinitionField, 0) 28 | for _, f := range entityFields(def) { 29 | if f.Name == "Id" { 30 | continue 31 | } 32 | 33 | if !def.WithoutCreateTime && f.Name == "CreatedAt" { 34 | continue 35 | } 36 | 37 | if !def.WithoutUpdateTime && f.Name == "UpdatedAt" { 38 | continue 39 | } 40 | 41 | if def.SoftDelete && f.Name == "DeletedAt" { 42 | continue 43 | } 44 | 45 | fields = append(fields, f) 46 | } 47 | 48 | return fields 49 | } 50 | 51 | func (d DomainContext) importPackages() []string { 52 | var internalPackages = []string{ 53 | "context", 54 | "encoding/json", 55 | "gopkg.in/guregu/null.v3", 56 | "github.com/mylxsw/eloquent/query", 57 | "github.com/iancoleman/strcase", 58 | } 59 | 60 | for _, m := range d.domain.Models { 61 | if m.Definition.SoftDelete || !m.Definition.WithoutCreateTime || !m.Definition.WithoutUpdateTime { 62 | internalPackages = append(internalPackages, "time") 63 | } 64 | 65 | for _, rel := range m.Relations { 66 | internalPackages = append(internalPackages, rel.ImportPackages()...) 67 | } 68 | } 69 | 70 | internalPackages = append(internalPackages, d.domain.Imports...) 71 | 72 | return unique(internalPackages) 73 | } 74 | -------------------------------------------------------------------------------- /generator/template/scope.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | 4 | func GetScopeTemplate() string { 5 | return ` 6 | type {{ lower_camel $m.Name }}Scope struct { 7 | name string 8 | apply func(builder query.Condition) 9 | } 10 | 11 | var {{ lower_camel $m.Name }}GlobalScopes = make([]{{ lower_camel $m.Name }}Scope, 0) 12 | var {{ lower_camel $m.Name }}LocalScopes = make([]{{ lower_camel $m.Name }}Scope, 0) 13 | 14 | // AddGlobalScopeFor{{ camel $m.Name }} assign a global scope to a model 15 | func AddGlobalScopeFor{{ camel $m.Name }}(name string, apply func(builder query.Condition)) { 16 | {{ lower_camel $m.Name }}GlobalScopes = append({{ lower_camel $m.Name }}GlobalScopes, {{ lower_camel $m.Name }}Scope{name: name, apply: apply}) 17 | } 18 | 19 | // AddLocalScopeFor{{ camel $m.Name }} assign a local scope to a model 20 | func AddLocalScopeFor{{ camel $m.Name }}(name string, apply func(builder query.Condition)) { 21 | {{ lower_camel $m.Name }}LocalScopes = append({{ lower_camel $m.Name }}LocalScopes, {{ lower_camel $m.Name }}Scope{name: name, apply: apply}) 22 | } 23 | 24 | func (m *{{ camel $m.Name }}Model) applyScope() query.Condition { 25 | scopeCond := query.ConditionBuilder() 26 | for _, g := range {{ lower_camel $m.Name }}GlobalScopes { 27 | if m.globalScopeEnabled(g.name) { 28 | g.apply(scopeCond) 29 | } 30 | } 31 | 32 | for _, s := range {{ lower_camel $m.Name }}LocalScopes { 33 | if m.localScopeEnabled(s.name) { 34 | s.apply(scopeCond) 35 | } 36 | } 37 | 38 | return scopeCond 39 | } 40 | 41 | func (m *{{ camel $m.Name }}Model) localScopeEnabled(name string) bool { 42 | for _, n := range m.includeLocalScopes { 43 | if name == n { 44 | return true 45 | } 46 | } 47 | 48 | return false 49 | } 50 | 51 | func (m *{{ camel $m.Name }}Model) globalScopeEnabled(name string) bool { 52 | for _, n := range m.excludeGlobalScopes { 53 | if name == n { 54 | return false 55 | } 56 | } 57 | 58 | return true 59 | } 60 | ` 61 | } -------------------------------------------------------------------------------- /query/builder_test.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSQLBuilder_ResolveCount(t *testing.T) { 8 | builder := Builder().Table("users").Where("status", EQ, 1).Select("name", "id") 9 | 10 | sqlStr, _ := builder.ResolveCount() 11 | if sqlStr != "SELECT COUNT(1) as count FROM users WHERE users.`status` = ?" { 12 | t.Error("test failed") 13 | } 14 | 15 | sqlStr, _ = builder.ResolveQuery() 16 | if sqlStr != "SELECT users.`name`, users.`id` FROM users WHERE users.`status` = ?" { 17 | t.Error("test failed") 18 | } 19 | } 20 | 21 | func TestSQLBuilder_Where(t *testing.T) { 22 | sqlStr, _ := Builder().Table("users").Where("status", 1).ResolveAvg("age") 23 | if sqlStr != "SELECT AVG(age) as avg FROM users WHERE users.`status` = ?" { 24 | t.Error("test failed") 25 | } 26 | 27 | sqlStr, _ = Builder().Table("users").Where("status", GT, 1).ResolveSum("age") 28 | if sqlStr != "SELECT SUM(age) as sum FROM users WHERE users.`status` > ?" { 29 | t.Error("test failed") 30 | } 31 | 32 | sqlStr, _ = Builder().Table("users").Where("status", 1).WhereGroup(func(builder Condition) { 33 | builder.Where("phone", "123455").OrWhere("email", "xxx@xxx.xx") 34 | }).ResolveQuery() 35 | if sqlStr != "SELECT * FROM users WHERE users.`status` = ? AND ( users.`phone` = ? OR users.`email` = ?)" { 36 | t.Error("test failed") 37 | } 38 | } 39 | 40 | func TestSQLBuilder_WhereBetween(t *testing.T) { 41 | sqlStr, _ := Builder().Table("users").WhereBetween("age", 15, 20).OrWhereBetween("age", 30, 45).ResolveQuery() 42 | if sqlStr != "SELECT * FROM users WHERE users.`age` BETWEEN ? AND ? OR users.`age` BETWEEN ? AND ?" { 43 | t.Error("test failed") 44 | } 45 | } 46 | 47 | func TestSQLBuilder_Merge(t *testing.T) { 48 | sqlStr, _ := Builder().Table("test").Where("username", "zhangsan").Merge(Builder().Where("password", "123456")).ResolveQuery() 49 | if sqlStr != "SELECT * FROM test WHERE test.`username` = ? AND test.`password` = ?" { 50 | t.Error("test failed") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /generator/domain.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "github.com/iancoleman/strcase" 5 | ) 6 | 7 | type Domain struct { 8 | Imports []string `yaml:"imports,omitempty"` 9 | PackageName string `yaml:"package,omitempty"` 10 | Models []Model `yaml:"models,omitempty"` 11 | Meta Meta `yaml:"meta,omitempty"` 12 | } 13 | 14 | func (dom Domain) Init() Domain { 15 | for i, m := range dom.Models { 16 | for j, f := range m.Definition.Fields { 17 | dom.Models[i].Definition.Fields[j].Name = strcase.ToCamel(f.Name) 18 | if f.Type == "" { 19 | dom.Models[i].Definition.Fields[j].Type = "string" 20 | } 21 | } 22 | } 23 | 24 | return dom 25 | } 26 | 27 | type Meta struct { 28 | TablePrefix string `yaml:"table_prefix"` 29 | } 30 | 31 | type Model struct { 32 | Name string `yaml:"name,omitempty"` 33 | Relations []Relation `yaml:"relations,omitempty"` 34 | Definition Definition `yaml:"definition,omitempty"` 35 | } 36 | 37 | func (rel Relation) ImportPackages() []string { 38 | internalPackages := make([]string, 0) 39 | 40 | if rel.Package != "" { 41 | internalPackages = append(internalPackages, rel.Package) 42 | } 43 | 44 | if relationRel(rel) == "belongsToMany" { 45 | internalPackages = append(internalPackages, "github.com/mylxsw/eloquent") 46 | } 47 | 48 | return unique(internalPackages) 49 | } 50 | 51 | type Relation struct { 52 | Model string `yaml:"model,omitempty"` 53 | Rel string `yaml:"rel,omitempty"` 54 | 55 | ForeignKey string `yaml:"foreign_key,omitempty"` 56 | OwnerKey string `yaml:"owner_key,omitempty"` 57 | LocalKey string `yaml:"local_key,omitempty"` 58 | 59 | PivotTable string `yaml:"table,omitempty"` 60 | 61 | Package string `yaml:"package,omitempty"` 62 | Method string `yaml:"method,omitempty"` 63 | } 64 | 65 | type Definition struct { 66 | TableName string `yaml:"table_name,omitempty"` 67 | WithoutCreateTime bool `yaml:"without_create_time,omitempty"` 68 | WithoutUpdateTime bool `yaml:"without_update_time,omitempty"` 69 | SoftDelete bool `yaml:"soft_delete,omitempty"` 70 | Fields []DefinitionField `yaml:"fields,omitempty"` 71 | } 72 | 73 | type DefinitionField struct { 74 | Name string `yaml:"name,omitempty"` 75 | Type string `yaml:"type,omitempty"` 76 | Tag string `yaml:"tag,omitempty"` 77 | } 78 | -------------------------------------------------------------------------------- /query/database.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/mylxsw/eloquent/event" 10 | ) 11 | 12 | type Database interface { 13 | ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) 14 | QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) 15 | } 16 | 17 | type DatabaseWrap struct { 18 | db Database 19 | } 20 | 21 | func NewDatabaseWrap(db Database) *DatabaseWrap { 22 | return &DatabaseWrap{db: db} 23 | } 24 | 25 | func (d *DatabaseWrap) GetDB() Database { 26 | return d.db 27 | } 28 | 29 | func (d *DatabaseWrap) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { 30 | startTs := time.Now() 31 | defer func() { 32 | event.Dispatch(event.QueryExecutedEvent{ 33 | SQL: query, 34 | Bindings: args, 35 | Time: time.Now().Sub(startTs), 36 | }) 37 | }() 38 | 39 | return d.db.ExecContext(ctx, query, args...) 40 | } 41 | 42 | func (d *DatabaseWrap) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { 43 | startTs := time.Now() 44 | defer func() { 45 | event.Dispatch(event.QueryExecutedEvent{ 46 | SQL: query, 47 | Bindings: args, 48 | Time: time.Now().Sub(startTs), 49 | }) 50 | }() 51 | 52 | return d.db.QueryContext(ctx, query, args...) 53 | } 54 | 55 | // Transaction create a transaction with auto commit support 56 | func Transaction(db *sql.DB, cb func(tx Database) error) (err error) { 57 | tx, err := db.Begin() 58 | if err != nil { 59 | return err 60 | } 61 | 62 | event.Dispatch(event.TransactionBeginningEvent{}) 63 | 64 | defer func() { 65 | if err2 := recover(); err2 != nil { 66 | if err3 := tx.Rollback(); err3 != nil { 67 | err = fmt.Errorf("rollback (%s) failed: %s", err2, err3) 68 | } else { 69 | err = fmt.Errorf("%s", err2) 70 | event.Dispatch(event.TransactionRolledBackEvent{}) 71 | } 72 | } 73 | }() 74 | 75 | if err := cb(tx); err != nil { 76 | if err2 := tx.Rollback(); err2 != nil { 77 | return fmt.Errorf("rollback (%s) failed: %s", err, err2) 78 | } 79 | 80 | event.Dispatch(event.TransactionRolledBackEvent{}) 81 | 82 | return err 83 | } 84 | 85 | if err := tx.Commit(); err != nil { 86 | return fmt.Errorf("commit failed: %s", err) 87 | } 88 | 89 | event.Dispatch(event.TransactionCommittedEvent{}) 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Listener is a event listener 8 | type Listener interface{} 9 | 10 | // EventStore is a interface for event store 11 | type EventStore interface { 12 | Listen(eventName string, listener Listener) 13 | Publish(eventName string, evt interface{}) 14 | SetManager(*EventManager) 15 | } 16 | 17 | // EventManager is a manager for event dispatch 18 | type EventManager struct { 19 | store EventStore 20 | } 21 | 22 | // NewEventManager create a eventManager 23 | func NewEventManager(store EventStore) *EventManager { 24 | manager := &EventManager{ 25 | store: store, 26 | } 27 | 28 | store.SetManager(manager) 29 | 30 | return manager 31 | } 32 | 33 | // Listen create a relation from event to listners 34 | func (em *EventManager) Listen(listeners ...Listener) { 35 | for _, listener := range listeners { 36 | listenerType := reflect.TypeOf(listener) 37 | if listenerType.Kind() != reflect.Func { 38 | panic("listener must be a function") 39 | } 40 | 41 | if listenerType.NumIn() != 1 { 42 | panic("listener must be a function with only one arguemnt") 43 | } 44 | 45 | if listenerType.In(0).Kind() != reflect.Struct { 46 | panic("listener must be a function with only on argument of type struct") 47 | } 48 | 49 | em.store.Listen(listenerType.In(0).String(), listener) 50 | } 51 | } 52 | 53 | // Publish a event 54 | func (em *EventManager) Publish(evt interface{}) { 55 | em.store.Publish(reflect.TypeOf(evt).String(), evt) 56 | } 57 | 58 | // Call trigger listener to execute 59 | func (em *EventManager) Call(evt interface{}, listener Listener) { 60 | reflect.ValueOf(listener).Call([]reflect.Value{reflect.ValueOf(evt)}) 61 | } 62 | 63 | // MemoryEventStore is a event store for sync operations 64 | type MemoryEventStore struct { 65 | listeners map[string][]Listener 66 | manager *EventManager 67 | } 68 | 69 | // NewMemoryEventStore create a sync event store 70 | func NewMemoryEventStore() *MemoryEventStore { 71 | return &MemoryEventStore{ 72 | listeners: make(map[string][]Listener), 73 | } 74 | } 75 | 76 | // Listen add a listener to a event 77 | func (eventStore *MemoryEventStore) Listen(evtType string, listener Listener) { 78 | if _, ok := eventStore.listeners[evtType]; !ok { 79 | eventStore.listeners[evtType] = make([]Listener, 0) 80 | } 81 | 82 | eventStore.listeners[evtType] = append(eventStore.listeners[evtType], listener) 83 | } 84 | 85 | // Publish publish a event 86 | func (eventStore *MemoryEventStore) Publish(evtType string, evt interface{}) { 87 | if listeners, ok := eventStore.listeners[evtType]; ok { 88 | for _, listener := range listeners { 89 | eventStore.manager.Call(evt, listener) 90 | } 91 | } 92 | } 93 | 94 | // SetManager event manager 95 | func (eventStore *MemoryEventStore) SetManager(manager *EventManager) { 96 | eventStore.manager = manager 97 | } 98 | -------------------------------------------------------------------------------- /migrate/command.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | type Command struct { 4 | t *Builder 5 | 6 | CommandName string 7 | CommandIndex string 8 | CommandParameters []string 9 | CommandAlgorithm string 10 | 11 | CommandReferences []string 12 | CommandOnTable string 13 | CommandOnDelete string 14 | CommandOnUpdate string 15 | CommandNotInitiallyImmediate bool 16 | } 17 | 18 | func NewCommand(t *Builder) *Command { 19 | return &Command{ 20 | t: t, 21 | CommandParameters: make([]string, 0), 22 | } 23 | } 24 | 25 | func (c *Command) References(columns ...string) *Command { 26 | c.CommandReferences = columns 27 | return c 28 | } 29 | 30 | func (c *Command) On(table string) *Command { 31 | c.CommandOnTable = table 32 | return c 33 | } 34 | 35 | func (c *Command) OnDelete(action string) *Command { 36 | c.CommandOnDelete = action 37 | return c 38 | } 39 | 40 | func (c *Command) OnUpdate(action string) *Command { 41 | c.CommandOnUpdate = action 42 | return c 43 | } 44 | 45 | func (c *Command) NotInitiallyImmediate(value bool) *Command { 46 | c.CommandNotInitiallyImmediate = value 47 | return c 48 | } 49 | 50 | func (c *Command) Equal(name string) bool { 51 | return c.CommandName == name 52 | } 53 | 54 | func (c *Command) Name(name string) *Command { 55 | c.CommandName = name 56 | return c 57 | } 58 | 59 | func (c *Command) Index(name string) *Command { 60 | c.CommandIndex = name 61 | return c 62 | } 63 | 64 | func (c *Command) Columns(columns ...string) *Command { 65 | c.CommandParameters = columns 66 | return c 67 | } 68 | 69 | func (c *Command) Algorithm(algorithm string) *Command { 70 | c.CommandAlgorithm = algorithm 71 | return c 72 | } 73 | 74 | func (c *Command) Build() string { 75 | switch c.CommandName { 76 | case "index": 77 | return c.t.compileKey(c, "index") 78 | case "unique": 79 | return c.t.compileKey(c, "unique") 80 | case "primary": 81 | c.CommandIndex = "" 82 | return c.t.compileKey(c, "primary key") 83 | case "spatialIndex": 84 | return c.t.compileKey(c, "spatial index") 85 | case "dropTable": 86 | return c.t.compileDrop(c) 87 | case "dropColumn": 88 | return c.t.compileDropColumn(c) 89 | case "dropIndex", "dropUnique", "dropSpatialIndex": 90 | return c.t.compileDropIndex(c) 91 | case "dropPrimary": 92 | return c.t.compileDropPrimary(c) 93 | case "drop": 94 | return c.t.compileDrop(c) 95 | case "rename": 96 | return c.t.compileRename(c) 97 | case "create": 98 | return c.t.compileCreateCommand(false) 99 | case "createIfNotExists": 100 | return c.t.compileCreateCommand(true) 101 | case "add": 102 | return c.t.compileAdd() 103 | case "change": 104 | return c.t.compileChange() 105 | case "renameColumn": 106 | return c.t.compileRenameColumn(c.CommandParameters[0], c.CommandParameters[1]) 107 | case "foreign": 108 | return c.t.compileForeign(c) 109 | case "dropForeign": 110 | return c.t.compileDropForeign(c.CommandParameters[0]) 111 | } 112 | 113 | return "" 114 | } 115 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= 9 | github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 10 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 11 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 12 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 13 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 14 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 15 | github.com/mylxsw/go-utils v1.0.3-0.20230227032219-b96a084bd6c3 h1:rJLVaZy4900gRLPSXCq91aWEBjHx3JUt6cd/THcIDBU= 16 | github.com/mylxsw/go-utils v1.0.3-0.20230227032219-b96a084bd6c3/go.mod h1:F5pQ/vTAgccZxQA7jsIBXM6m2INAbqPKfzbNwQgqhzY= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 20 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 21 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 22 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 23 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 24 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 25 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 26 | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= 27 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 29 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 30 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/guregu/null.v3 v3.5.0 h1:xTcasT8ETfMcUHn0zTvIYtQud/9Mx5dJqD554SZct0o= 32 | gopkg.in/guregu/null.v3 v3.5.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= 33 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 34 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 36 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 37 | -------------------------------------------------------------------------------- /_examples/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 5 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 6 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 7 | github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= 8 | github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 9 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 10 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 11 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 12 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 16 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 17 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 18 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 19 | github.com/mylxsw/asteria v1.0.2-0.20221222065801-3f0515da865f h1:TZ6WUIW3C6o3n8p+FBaeU/i/RLbIuJ/aVU9s7QoVjJ0= 20 | github.com/mylxsw/asteria v1.0.2-0.20221222065801-3f0515da865f/go.mod h1:pmMRQjiOk1ZndmWnk7fDb4iIVrPhWCaWl6wV0R51zws= 21 | github.com/mylxsw/go-utils v1.0.3 h1:kL1n25xVzEDCjhtNx32dXXixFvuslCE5RGKMEUxeeJI= 22 | github.com/mylxsw/go-utils v1.0.3/go.mod h1:F5pQ/vTAgccZxQA7jsIBXM6m2INAbqPKfzbNwQgqhzY= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 27 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 28 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 29 | golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= 30 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 31 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/guregu/null.v3 v3.5.0 h1:xTcasT8ETfMcUHn0zTvIYtQud/9Mx5dJqD554SZct0o= 35 | gopkg.in/guregu/null.v3 v3.5.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= 36 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 37 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 38 | -------------------------------------------------------------------------------- /database.go: -------------------------------------------------------------------------------- 1 | package eloquent 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "github.com/mylxsw/eloquent/query" 8 | ) 9 | 10 | // Build create a SQLBuilder with table name 11 | func Build(tableName string) query.SQLBuilder { 12 | return query.Builder().Table(tableName) 13 | } 14 | 15 | // databaseImpl is a basic database query handler 16 | type databaseImpl struct { 17 | db *query.DatabaseWrap 18 | } 19 | 20 | // DB create a databaseImpl 21 | func DB(db query.Database) Database { 22 | return &databaseImpl{ 23 | db: query.NewDatabaseWrap(db), 24 | } 25 | } 26 | 27 | type rawQueryBuilder struct { 28 | sql string 29 | args []any 30 | } 31 | 32 | func Raw(sqlStr string, args ...any) QueryBuilder { 33 | return &rawQueryBuilder{sql: sqlStr, args: args} 34 | } 35 | 36 | func (r *rawQueryBuilder) ResolveQuery() (sqlStr string, args []any) { 37 | return r.sql, r.args 38 | } 39 | 40 | // Query run a basic query 41 | func (db *databaseImpl) Query(ctx context.Context, builder QueryBuilder, cb func(row Scanner) (any, error)) ([]any, error) { 42 | results := make([]any, 0) 43 | 44 | sqlStr, args := builder.ResolveQuery() 45 | rows, err := db.db.QueryContext(ctx, sqlStr, args...) 46 | if err != nil { 47 | return results, err 48 | } 49 | 50 | defer rows.Close() 51 | 52 | for rows.Next() { 53 | r, err := cb(rows) 54 | if err != nil { 55 | return results, err 56 | } 57 | 58 | results = append(results, r) 59 | } 60 | 61 | return results, nil 62 | } 63 | 64 | // Insert to execute an insert statement 65 | func (db *databaseImpl) Insert(ctx context.Context, tableName string, kv query.KV) (int64, error) { 66 | sqlStr, args := query.Builder().Table(tableName).ResolveInsert(kv) 67 | res, err := db.db.ExecContext(ctx, sqlStr, args...) 68 | if err != nil { 69 | return 0, err 70 | } 71 | 72 | return res.LastInsertId() 73 | } 74 | 75 | // Delete to execute an delete statement 76 | func (db *databaseImpl) Delete(ctx context.Context, builder query.SQLBuilder) (int64, error) { 77 | sqlStr, args := builder.ResolveDelete() 78 | res, err := db.db.ExecContext(ctx, sqlStr, args...) 79 | if err != nil { 80 | return 0, err 81 | } 82 | 83 | return res.RowsAffected() 84 | } 85 | 86 | // Update to execute an update statement 87 | func (db *databaseImpl) Update(ctx context.Context, builder query.SQLBuilder, kv query.KV) (int64, error) { 88 | sqlStr, args := builder.ResolveUpdate(kv) 89 | res, err := db.db.ExecContext(ctx, sqlStr, args...) 90 | if err != nil { 91 | return 0, err 92 | } 93 | 94 | return res.RowsAffected() 95 | } 96 | 97 | // Statement running a general statement which return no value 98 | func (db *databaseImpl) Statement(ctx context.Context, raw string, args ...any) error { 99 | _, err := db.db.ExecContext(ctx, raw, args...) 100 | return err 101 | } 102 | 103 | // Transaction start a transaction 104 | func Transaction(db *sql.DB, cb func(tx query.Database) error) (err error) { 105 | return query.Transaction(db, cb) 106 | } 107 | 108 | // Query run a basic query 109 | func Query[T any](ctx context.Context, db query.Database, builder QueryBuilder, cb func(row Scanner) (T, error)) ([]T, error) { 110 | results := make([]T, 0) 111 | 112 | sqlStr, args := builder.ResolveQuery() 113 | rows, err := db.QueryContext(ctx, sqlStr, args...) 114 | if err != nil { 115 | return results, err 116 | } 117 | 118 | defer rows.Close() 119 | 120 | for rows.Next() { 121 | r, err := cb(rows) 122 | if err != nil { 123 | return results, err 124 | } 125 | 126 | results = append(results, r) 127 | } 128 | 129 | return results, nil 130 | } 131 | -------------------------------------------------------------------------------- /cmd/orm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | 9 | "github.com/mylxsw/eloquent/generator" 10 | "github.com/mylxsw/eloquent/generator/template" 11 | "github.com/urfave/cli" 12 | "gopkg.in/yaml.v3" 13 | ) 14 | 15 | func main() { 16 | app := &cli.App{ 17 | Name: "Eloquent 命令行工具", 18 | Commands: []cli.Command{ 19 | { 20 | Name: "gen", 21 | Aliases: []string{"generate", "g"}, 22 | Usage: "根据模型文件定义生成模型对象", 23 | Flags: []cli.Flag{ 24 | cli.StringFlag{ 25 | Name: "source", 26 | Required: true, 27 | Usage: "模型定义所在文件的 Glob 表达式,比如 ./models/*.yaml", 28 | }, 29 | }, 30 | Action: func(c *cli.Context) error { 31 | source := c.String("source") 32 | matches, err := filepath.Glob(source) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | for _, m := range matches { 38 | dest := replaceExt(m, ".orm.go") 39 | 40 | input, err := os.ReadFile(m) 41 | assertError(err) 42 | 43 | var domain generator.Domain 44 | assertError(yaml.Unmarshal(input, &domain)) 45 | 46 | res, err := generator.ParseTemplate(template.GetTemplate(), domain.Init()) 47 | assertError(err) 48 | 49 | assertError(os.WriteFile(dest, []byte(res), os.ModePerm)) 50 | 51 | fmt.Println(dest) 52 | } 53 | 54 | return nil 55 | }, 56 | }, 57 | { 58 | Name: "create-model", 59 | Usage: "创建表模型定义文件", 60 | Flags: []cli.Flag{ 61 | cli.StringFlag{Name: "table", Required: true, Usage: "表名"}, 62 | cli.StringFlag{Name: "package", Required: true, Usage: "包名"}, 63 | cli.StringFlag{Name: "output", Usage: "输出目录"}, 64 | cli.StringFlag{Name: "table-prefix", Usage: "表前缀"}, 65 | cli.BoolFlag{Name: "no-created_at", Usage: "不自动添加 created_at 字段"}, 66 | cli.BoolFlag{Name: "no-updated_at", Usage: "不自动添加 updated_at 字段"}, 67 | cli.BoolFlag{Name: "soft-delete", Usage: "启用软删除支持"}, 68 | cli.StringSliceFlag{Name: "import", Usage: "引入包"}, 69 | }, 70 | Action: func(c *cli.Context) error { 71 | domain := generator.Domain{ 72 | PackageName: c.String("package"), 73 | Models: []generator.Model{ 74 | { 75 | Name: c.String("table"), 76 | Definition: generator.Definition{ 77 | TableName: c.String("table"), 78 | WithoutCreateTime: c.Bool("no-created_at"), 79 | WithoutUpdateTime: c.Bool("no-updated_at"), 80 | SoftDelete: c.BoolT("soft-delete"), 81 | Fields: []generator.DefinitionField{ 82 | {Name: "id", Type: "int64", Tag: `json:"id"`}, 83 | }, 84 | }, 85 | }, 86 | }, 87 | } 88 | 89 | imports := c.StringSlice("import") 90 | if len(imports) > 0 { 91 | domain.Imports = imports 92 | } 93 | 94 | tablePrefix := c.String("table-prefix") 95 | if tablePrefix != "" { 96 | domain.Meta = generator.Meta{TablePrefix: tablePrefix} 97 | } 98 | 99 | data, err := yaml.Marshal(domain) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | return os.WriteFile(filepath.Join(c.String("output"), c.String("table")+".yaml"), data, os.ModePerm) 105 | }, 106 | }, 107 | }, 108 | } 109 | 110 | if err := app.Run(os.Args); err != nil { 111 | panic(err) 112 | } 113 | 114 | } 115 | 116 | // replaceExt replace ext for src 117 | func replaceExt(src string, ext string) string { 118 | ext1 := path.Ext(src) 119 | 120 | return fmt.Sprintf("%s%s", src[:len(src)-len(ext1)], ext) 121 | } 122 | 123 | func assertError(err error) { 124 | if err != nil { 125 | panic(err) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /query/misc.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "strings" 7 | "unsafe" 8 | ) 9 | 10 | var ( 11 | // ErrModelNotSet means you are not set the model for the domain object 12 | ErrModelNotSet = errors.New("model not set") 13 | // ErrNoResult means there is no result for current query. 14 | ErrNoResult = errors.New("no result") 15 | 16 | ErrTargetIsNil = errors.New("target is nil") 17 | ErrTargetInvalid = errors.New("target must be a pointer to struct") 18 | ) 19 | 20 | const ( 21 | EQ = "=" 22 | NEQ = "!=" 23 | GT = ">" 24 | GTE = ">=" 25 | LT = "<" 26 | LTE = "<=" 27 | LIKE = "LIKE" 28 | ) 29 | 30 | type PaginateMeta struct { 31 | Page int64 `json:"page"` 32 | PerPage int64 `json:"per_page"` 33 | Total int64 `json:"total"` 34 | LastPage int64 `json:"last_page"` 35 | } 36 | 37 | // ToAnys convert []T to []any ([]any) 38 | func ToAnys[T any](items []T) []any { 39 | arr := make([]any, len(items)) 40 | for i, item := range items { 41 | arr[i] = item 42 | } 43 | 44 | return arr 45 | } 46 | 47 | // Copy exported properties(with same name and type) from source to target 48 | // target must be a pointer to struct 49 | func Copy(source interface{}, targets ...interface{}) error { 50 | sourceRefVal := reflect.Indirect(reflect.ValueOf(source)) 51 | // 如果 source 为 null,则不需要拷贝任何属性 52 | if !sourceRefVal.IsValid() { 53 | return nil 54 | } 55 | 56 | for _, target := range targets { 57 | targetRefVal := reflect.ValueOf(target) 58 | 59 | if !targetRefVal.IsValid() { 60 | return ErrTargetIsNil 61 | } 62 | 63 | if targetRefVal.Kind() != reflect.Ptr { 64 | return ErrTargetInvalid 65 | } 66 | 67 | targetVal := targetRefVal.Elem() 68 | targetType := targetVal.Type() 69 | 70 | for i := 0; i < targetType.NumField(); i++ { 71 | field := targetType.Field(i) 72 | fieldName := field.Name 73 | if fieldName[0] < 'A' || fieldName[0] > 'Z' { 74 | continue 75 | } 76 | 77 | dst := sourceRefVal.FieldByName(fieldName) 78 | if !dst.IsValid() || field.Type != dst.Type() { 79 | continue 80 | } 81 | 82 | reflect.NewAt(field.Type, unsafe.Pointer(targetVal.Field(i).UnsafeAddr())).Elem().Set(dst) 83 | } 84 | 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func isSubQuery(values []any) bool { 91 | if len(values) != 1 { 92 | return false 93 | } 94 | 95 | if _, ok := values[0].(SubQuery); ok { 96 | return true 97 | } 98 | 99 | return false 100 | } 101 | 102 | func replaceTableField(tableAlias string, name string) string { 103 | segs1 := strings.Split(name, " ") 104 | org := segs1[0] 105 | segs1len := len(segs1) 106 | if segs1len == 3 { 107 | return resolveOrgTableField(tableAlias, org) + " AS " + segs1[2] 108 | } else if segs1len == 2 { 109 | return resolveOrgTableField(tableAlias, org) + " AS " + segs1[1] 110 | } 111 | 112 | // a.b => a.`b` 113 | // b => alias.`b` 114 | // b as c => alias.`b` as c 115 | // a.b as c => a.`b` as c 116 | 117 | return resolveOrgTableField(tableAlias, org) 118 | } 119 | 120 | func resolveOrgTableField(tableAlias string, org string) string { 121 | segs := strings.Split(org, ".") 122 | if len(segs) > 1 { 123 | if segs[1] != "*" { 124 | segs[1] = "`" + segs[1] + "`" 125 | } 126 | } else if segs[0] != "*" { 127 | segs[0] = "`" + segs[0] + "`" 128 | } 129 | 130 | if tableAlias != "" && len(segs) == 1 { 131 | return tableAlias + "." + strings.Join(segs, ".") 132 | } 133 | 134 | return strings.Join(segs, ".") 135 | } 136 | 137 | func resolveTableAlias(name string) string { 138 | segs := strings.Split(name, " ") 139 | if len(segs) == 3 && strings.ToUpper(segs[1]) == "AS" { 140 | return segs[2] 141 | } else if len(segs) == 2 { 142 | return segs[1] 143 | } 144 | 145 | return segs[0] 146 | } 147 | -------------------------------------------------------------------------------- /generator/template/entity.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | func GetEntityTemplate() string { 4 | return ` 5 | // {{ camel $m.Name }}N is a {{ camel $m.Name }} object, all fields are nullable 6 | type {{ camel $m.Name }}N struct { 7 | original *{{ lower_camel $m.Name }}Original 8 | {{ lower_camel $m.Name }}Model *{{ camel $m.Name }}Model 9 | 10 | {{ range $j, $f := fields $m.Definition }} 11 | {{ camel $f.Name }} {{ sql_field_type $f.Type }} {{ tag $f }}{{ end }} 12 | } 13 | 14 | // As convert object to other type 15 | // dst must be a pointer to struct 16 | func (inst *{{ camel $m.Name }}N) As(dst interface{}) error { 17 | return query.Copy(inst, dst) 18 | } 19 | 20 | // SetModel set model for {{ camel $m.Name }} 21 | func (inst *{{ camel $m.Name }}N) SetModel({{ lower_camel $m.Name }}Model *{{ camel $m.Name }}Model) { 22 | inst.{{ lower_camel $m.Name }}Model = {{ lower_camel $m.Name }}Model 23 | } 24 | 25 | // {{ lower_camel $m.Name }}Original is an object which stores original {{ camel $m.Name }} from database 26 | type {{ lower_camel $m.Name }}Original struct { 27 | {{ range $j, $f := fields $m.Definition }} 28 | {{ camel $f.Name }} {{ sql_field_type $f.Type }}{{ end }} 29 | } 30 | 31 | // Staled identify whether the object has been modified 32 | func (inst *{{ camel $m.Name }}N) Staled(onlyFields ...string) bool { 33 | if inst.original == nil { 34 | inst.original = &{{ lower_camel $m.Name }}Original {} 35 | } 36 | 37 | if len(onlyFields) == 0 { 38 | {{ range $j, $f := fields $m.Definition }} 39 | if inst.{{ camel $f.Name }} != inst.original.{{ camel $f.Name }} { 40 | return true 41 | }{{ end }} 42 | } else { 43 | for _, f := range onlyFields { 44 | switch strcase.ToSnake(f) { 45 | {{ range $j, $f := fields $m.Definition }} 46 | case "{{ snake $f.Name }}": 47 | if inst.{{ camel $f.Name }} != inst.original.{{ camel $f.Name }} { 48 | return true 49 | }{{ end }} 50 | default: 51 | } 52 | } 53 | } 54 | 55 | return false 56 | } 57 | 58 | // StaledKV return all fields has been modified 59 | func (inst *{{ camel $m.Name }}N) StaledKV(onlyFields ...string) query.KV { 60 | kv := make(query.KV, 0) 61 | 62 | if inst.original == nil { 63 | inst.original = &{{ lower_camel $m.Name }}Original {} 64 | } 65 | 66 | if len(onlyFields) == 0 { 67 | {{ range $j, $f := fields $m.Definition }} 68 | if inst.{{ camel $f.Name }} != inst.original.{{ camel $f.Name }} { 69 | kv["{{ snake $f.Name }}"] = inst.{{ camel $f.Name }} 70 | }{{ end }} 71 | } else { 72 | for _, f := range onlyFields { 73 | switch strcase.ToSnake(f) { 74 | {{ range $j, $f := fields $m.Definition }} 75 | case "{{ snake $f.Name }}": 76 | if inst.{{ camel $f.Name }} != inst.original.{{ camel $f.Name }} { 77 | kv["{{ snake $f.Name }}"] = inst.{{ camel $f.Name }} 78 | }{{ end }} 79 | default: 80 | } 81 | } 82 | } 83 | 84 | 85 | return kv 86 | } 87 | 88 | // Save create a new model or update it 89 | func (inst *{{ camel $m.Name }}N) Save(ctx context.Context, onlyFields ...string) error { 90 | if inst.{{ lower_camel $m.Name }}Model == nil { 91 | return query.ErrModelNotSet 92 | } 93 | 94 | id, _, err := inst.{{ lower_camel $m.Name }}Model.SaveOrUpdate(ctx, *inst, onlyFields...) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | inst.Id = null.IntFrom(id) 100 | return nil 101 | } 102 | 103 | // Delete remove a {{ $m.Name }} 104 | func (inst *{{ camel $m.Name }}N) Delete(ctx context.Context) error { 105 | if inst.{{ lower_camel $m.Name }}Model == nil { 106 | return query.ErrModelNotSet 107 | } 108 | 109 | _, err := inst.{{ lower_camel $m.Name }}Model.DeleteById(ctx, inst.Id.Int64) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | return nil 115 | } 116 | 117 | // String convert instance to json string 118 | func (inst *{{ camel $m.Name }}N) String() string { 119 | rs, _ := json.Marshal(inst) 120 | return string(rs) 121 | } 122 | ` 123 | } 124 | -------------------------------------------------------------------------------- /migrate/migrate.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "strings" 7 | "time" 8 | 9 | "github.com/mylxsw/eloquent/event" 10 | "github.com/mylxsw/eloquent/query" 11 | ) 12 | 13 | type ExprType int 14 | 15 | type Expr struct { 16 | Type ExprType 17 | Value string 18 | } 19 | 20 | const ( 21 | ExprTypeString ExprType = iota 22 | ExprTypeRaw 23 | ) 24 | 25 | type Manager struct { 26 | db *sql.DB 27 | migrateRepo *MigrationsModel 28 | 29 | Engine string 30 | Charset string 31 | Collation string 32 | Prefix string 33 | DefaultStringLength int 34 | MigrationTable string 35 | migrateFuncs []func(ctx context.Context) error 36 | 37 | batch int64 38 | } 39 | 40 | func NewManager(db *sql.DB) *Manager { 41 | return &Manager{ 42 | db: db, 43 | migrateRepo: NewMigrationsModel(db), 44 | 45 | Engine: "InnoDB", 46 | Charset: "utf8mb4", 47 | Collation: "utf8mb4_unicode_ci", 48 | MigrationTable: "migrations", 49 | DefaultStringLength: 255, 50 | Prefix: "", 51 | migrateFuncs: make([]func(ctx context.Context) error, 0), 52 | } 53 | } 54 | 55 | func (m *Manager) Init(ctx context.Context) *Manager { 56 | builder := NewBuilder(m.MigrationTable, "").DefaultStringLength(m.DefaultStringLength) 57 | builder.Engine(m.Engine) 58 | builder.Charset(m.Charset) 59 | builder.Collation(m.Collation) 60 | 61 | builder.Increments("id") 62 | builder.String("table", 0) 63 | builder.String("version", 20) 64 | builder.LongText("migration") 65 | builder.Integer("batch", false, false) 66 | 67 | builder.CreateIfNotExists() 68 | 69 | for _, s := range builder.Build() { 70 | if _, err := m.db.ExecContext(ctx, s); err != nil { 71 | panic(err) 72 | } 73 | } 74 | 75 | return m 76 | } 77 | 78 | func (m *Manager) Schema(version string) *Schema { 79 | return NewSchema(m, version) 80 | } 81 | 82 | func (m *Manager) Execute(builder *Builder, version string) { 83 | m.migrateFuncs = append(m.migrateFuncs, func(ctx context.Context) error { 84 | tableName := builder.GetTableName() 85 | if m.HasVersion(ctx, version, tableName) { 86 | // fmt.Printf("ignore-version(%s)\n", version) 87 | return nil 88 | } 89 | 90 | sqls := builder.Build() 91 | 92 | if err := m.execute(ctx, sqls); err != nil { 93 | return err 94 | } 95 | 96 | if err := m.AddVersion(ctx, version, tableName, strings.Join(sqls, ";\n")+";"); err != nil { 97 | return err 98 | } 99 | 100 | return nil 101 | }) 102 | } 103 | 104 | func (m *Manager) ExecuteRaw(version string, table string, sqls ...string) { 105 | m.migrateFuncs = append(m.migrateFuncs, func(ctx context.Context) error { 106 | if m.HasVersion(ctx, version, table) { 107 | return nil 108 | } 109 | 110 | if err := m.execute(ctx, sqls); err != nil { 111 | return err 112 | } 113 | 114 | if err := m.AddVersion(ctx, version, table, strings.Join(sqls, ";\n")+";"); err != nil { 115 | return err 116 | } 117 | 118 | return nil 119 | }) 120 | } 121 | 122 | func (m *Manager) execute(ctx context.Context, sqls []string) error { 123 | event.Dispatch(event.MigrationsStartedEvent{}) 124 | 125 | for _, s := range sqls { 126 | // fmt.Printf("execute -> %s\n", s) 127 | event.Dispatch(event.MigrationStartedEvent{SQL: s}) 128 | if _, err := m.db.ExecContext(ctx, s); err != nil { 129 | return err 130 | } 131 | 132 | event.Dispatch(event.MigrationEndedEvent{SQL: s}) 133 | } 134 | 135 | event.Dispatch(event.MigrationsEndedEvent{}) 136 | 137 | return nil 138 | } 139 | 140 | func (m *Manager) HasVersion(ctx context.Context, version string, tableName string) bool { 141 | existed, err := m.migrateRepo. 142 | Condition(query.Builder().Where("version", version).Where("table", tableName)). 143 | Exists(ctx) 144 | if err != nil { 145 | panic(err) 146 | } 147 | 148 | return existed 149 | } 150 | 151 | func (m *Manager) AddVersion(ctx context.Context, version string, tableName string, sqlStr string) error { 152 | // fmt.Printf("add-version(%s)\n", version) 153 | _, err := m.migrateRepo.Save( 154 | ctx, 155 | Migrations{ 156 | Version: version, 157 | Table: tableName, 158 | Migration: sqlStr, 159 | Batch: m.batch, 160 | }.ToMigrationsN(), 161 | ) 162 | 163 | return err 164 | } 165 | 166 | func (m *Manager) Run(ctx context.Context) error { 167 | m.batch = time.Now().Unix() 168 | 169 | for _, f := range m.migrateFuncs { 170 | if err := f(ctx); err != nil { 171 | return err 172 | } 173 | } 174 | 175 | return nil 176 | } 177 | -------------------------------------------------------------------------------- /generator/helper.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/iancoleman/strcase" 8 | ) 9 | 10 | func init() { 11 | AddFunc("wrap_type", wrapType) 12 | AddFunc("unwrap_type", unWrapType) 13 | AddFunc("unique", unique) 14 | AddFunc("fields", entityFields) 15 | AddFunc("tag", entityTags) 16 | AddFunc("rel_owner_key", relationOwnerKey) 17 | AddFunc("rel_foreign_key", relationForeignKey) 18 | AddFunc("rel_foreign_key_rev", relationForeignKeyRev) 19 | AddFunc("rel_local_key", relationLocalKey) 20 | AddFunc("rel_package_prefix", relationPackagePrefix) 21 | AddFunc("rel_method", relationMethod) 22 | AddFunc("rel", relationRel) 23 | AddFunc("rel_belongs_to_name", relationBelongsToName) 24 | AddFunc("rel_has_many_name", relationHasManyName) 25 | AddFunc("rel_has_one_name", relationHasOneName) 26 | AddFunc("rel_belongs_to_many_name", relationBelongsToManyName) 27 | AddFunc("rel_pivot_table_name", relationPivotTable) 28 | AddFunc("sql_field_type", sqlFieldType) 29 | } 30 | 31 | func sqlFieldType(field string) string { 32 | switch field { 33 | case "int64", "int", "int8", "int32": 34 | return "null.Int" 35 | case "string": 36 | return "null.String" 37 | case "time.Time": 38 | return "null.Time" 39 | case "float32", "float64": 40 | return "null.Float" 41 | case "bool": 42 | return "null.Bool" 43 | } 44 | 45 | return field 46 | } 47 | 48 | func wrapType(f string, t string) string { 49 | tt := sqlFieldType(t) 50 | if tt == "null.Int" { 51 | f = fmt.Sprintf("int64(%s)", f) 52 | } 53 | if tt == "null.Float" { 54 | f = fmt.Sprintf("float64(%s)", f) 55 | } 56 | return fmt.Sprintf("%sFrom(%s)", tt, f) 57 | } 58 | 59 | func unWrapType(name string, t string) string { 60 | base := "w." + strcase.ToCamel(name) 61 | // w.{{ camel $f.ColumnName }}{{ wrap_type $f.Type | unwrap_type }} 62 | if strings.HasPrefix(t, "null.") { 63 | return base 64 | } 65 | 66 | switch sqlFieldType(t) { 67 | case "null.Int": 68 | if t == "int64" { 69 | return base + ".Int64" 70 | } 71 | return t + "(" + base + ".Int64)" 72 | case "null.String": 73 | return base + ".String" 74 | case "null.Time": 75 | return base + ".Time" 76 | case "null.Float": 77 | if t == "float64" { 78 | return base + ".Float64" 79 | } 80 | 81 | return t + "(" + base + ".Float64)" 82 | case "null.Bool": 83 | return base + ".Bool" 84 | } 85 | 86 | return base 87 | } 88 | 89 | func unique(elements []string) []string { 90 | encountered := map[string]bool{} 91 | var result []string 92 | 93 | for v := range elements { 94 | if encountered[elements[v]] { 95 | continue 96 | } 97 | 98 | encountered[elements[v]] = true 99 | result = append(result, elements[v]) 100 | } 101 | 102 | return result 103 | } 104 | 105 | func entityFields(def Definition) []DefinitionField { 106 | fields := make([]DefinitionField, 0) 107 | 108 | for _, f := range def.Fields { 109 | f.Name = strcase.ToCamel(f.Name) 110 | fields = append(fields, f) 111 | } 112 | 113 | fields = append(fields, DefinitionField{Name: "Id", Type: "int64"}) 114 | 115 | if !def.WithoutCreateTime { 116 | fields = append(fields, DefinitionField{Name: "CreatedAt", Type: "time.Time"}) 117 | } 118 | 119 | if !def.WithoutUpdateTime { 120 | fields = append(fields, DefinitionField{Name: "UpdatedAt", Type: "time.Time"}) 121 | } 122 | 123 | if def.SoftDelete { 124 | fields = append(fields, DefinitionField{Name: "DeletedAt", Type: "time.Time"}) 125 | } 126 | 127 | return uniqueFields(fields) 128 | } 129 | 130 | func uniqueFields(elements []DefinitionField) []DefinitionField { 131 | encountered := map[string]bool{} 132 | var result []DefinitionField 133 | 134 | for v := range elements { 135 | if encountered[elements[v].Name] { 136 | continue 137 | } 138 | 139 | encountered[elements[v].Name] = true 140 | result = append(result, elements[v]) 141 | } 142 | 143 | return result 144 | } 145 | 146 | func entityTags(field DefinitionField) string { 147 | if field.Tag == "" { 148 | return "" 149 | } 150 | 151 | return "`" + field.Tag + "`" 152 | } 153 | 154 | func relationForeignKeyRev(rel Relation, m Model) string { 155 | if rel.ForeignKey == "" { 156 | return strcase.ToSnake(m.Name) + "_id" 157 | } 158 | 159 | return rel.ForeignKey 160 | } 161 | 162 | func relationForeignKey(rel Relation) string { 163 | if rel.ForeignKey == "" { 164 | return strcase.ToSnake(rel.Model) + "_id" 165 | } 166 | 167 | return rel.ForeignKey 168 | } 169 | 170 | func relationOwnerKey(rel Relation) string { 171 | if rel.OwnerKey == "" { 172 | return "id" 173 | } 174 | 175 | return rel.OwnerKey 176 | } 177 | 178 | func relationLocalKey(rel Relation) string { 179 | if rel.LocalKey == "" { 180 | return "id" 181 | } 182 | 183 | return rel.LocalKey 184 | } 185 | 186 | func relationPackagePrefix(rel Relation) string { 187 | if rel.Package == "" { 188 | return "" 189 | } 190 | 191 | segs := strings.Split(rel.Package, "/") 192 | lastSeg := segs[len(segs)-1] 193 | 194 | return lastSeg + "." 195 | } 196 | 197 | func relationMethod(rel Relation) string { 198 | if rel.Method == "" { 199 | switch relationRel(rel) { 200 | case "belongsTo", "hasOne": 201 | return strcase.ToCamel(rel.Model) 202 | case "hasMany", "belongsToMany": 203 | return strcase.ToCamel(rel.Model) + "s" 204 | } 205 | } 206 | 207 | return strcase.ToCamel(rel.Method) 208 | } 209 | 210 | func relationRel(rel Relation) string { 211 | switch strings.ToLower(rel.Rel) { 212 | case "belongsto", "belongs_to", "n-1", "n:1", "*:1", "*-1", "-1": 213 | return "belongsTo" 214 | case "hasmany", "has_many", "1-n", "1:n", "1:*", "1-*", "1-": 215 | return "hasMany" 216 | case "hasone", "has_one", "1-1", "1:1": 217 | return "hasOne" 218 | case "belongs_to_many", "belongstomany", "n:n", "n-n", "*:*", "*-*": 219 | return "belongsToMany" 220 | } 221 | 222 | panic(fmt.Sprintf("not support: %s", rel.Rel)) 223 | } 224 | 225 | func relationBelongsToName(rel Relation, m Model) string { 226 | return fmt.Sprintf("%sBelongsTo%sRel", strcase.ToCamel(m.Name), strcase.ToCamel(rel.Model)) 227 | } 228 | 229 | func relationHasManyName(rel Relation, m Model) string { 230 | return fmt.Sprintf("%sHasMany%sRel", strcase.ToCamel(m.Name), strcase.ToCamel(rel.Model)) 231 | } 232 | 233 | func relationHasOneName(rel Relation, m Model) string { 234 | return fmt.Sprintf("%sHasOne%sRel", strcase.ToCamel(m.Name), strcase.ToCamel(rel.Model)) 235 | } 236 | 237 | func relationBelongsToManyName(rel Relation, m Model) string { 238 | return fmt.Sprintf("%sBelongsToMany%sRel", strcase.ToCamel(m.Name), strcase.ToCamel(rel.Model)) 239 | } 240 | 241 | func relationPivotTable(rel Relation, m Model) string { 242 | if rel.PivotTable != "" { 243 | return rel.PivotTable 244 | } 245 | 246 | t1 := strcase.ToSnake(rel.Model) 247 | t2 := strcase.ToSnake(m.Name) 248 | 249 | if strings.Compare(t1, t2) > 0 { 250 | return fmt.Sprintf("%s_%s_ref", t1, t2) 251 | } 252 | 253 | return fmt.Sprintf("%s_%s_ref", t2, t1) 254 | } 255 | -------------------------------------------------------------------------------- /migrate/column.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func StringExpr(value string) Expr { 9 | return Expr{ 10 | Type: ExprTypeString, 11 | Value: value, 12 | } 13 | } 14 | 15 | func RawExpr(value string) Expr { 16 | return Expr{ 17 | Type: ExprTypeRaw, 18 | Value: value, 19 | } 20 | } 21 | 22 | type ColumnDefinition struct { 23 | ColumnName string 24 | ColumnType string 25 | ColumnComment string 26 | ColumnAutoIncrement bool 27 | ColumnUnsigned bool 28 | ColumnNullable bool 29 | ColumnDefault Expr 30 | ColumnCharset string 31 | ColumnCollation string 32 | ColumnUseCurrent bool 33 | ColumnVirtualAs string 34 | ColumnStoredAs string 35 | ColumnAfter string 36 | ColumnFirst bool 37 | ColumnSrid int64 38 | 39 | ColumnIndex string 40 | ColumnPrimary bool 41 | ColumnUnique bool 42 | ColumnSpatialIndex bool 43 | 44 | ColumnChange bool 45 | } 46 | 47 | func (c *ColumnDefinition) Index(name string) *ColumnDefinition { 48 | c.ColumnIndex = name 49 | return c 50 | } 51 | 52 | func (c *ColumnDefinition) Primary() *ColumnDefinition { 53 | c.ColumnPrimary = true 54 | return c 55 | } 56 | 57 | func (c *ColumnDefinition) Unique() *ColumnDefinition { 58 | c.ColumnUnique = true 59 | return c 60 | } 61 | 62 | func (c *ColumnDefinition) SpatialIndex() *ColumnDefinition { 63 | c.ColumnSpatialIndex = true 64 | return c 65 | } 66 | 67 | func (c *ColumnDefinition) Build() string { 68 | sqlStr := "`" + c.ColumnName + "`" 69 | sqlStr += strings.ToUpper(c.Type()) 70 | 71 | sqlStr += c.modifyUnsigned() 72 | sqlStr += c.modifyVirtualAs() 73 | sqlStr += c.modifyStoredAs() 74 | sqlStr += c.modifyCharset() 75 | sqlStr += c.modifyCollate() 76 | sqlStr += c.modifyNullable() 77 | sqlStr += c.modifyDefault() 78 | sqlStr += c.modifyIncrement() 79 | sqlStr += c.modifyComment() 80 | sqlStr += c.modifyAfter() 81 | sqlStr += c.modifyFirst() 82 | sqlStr += c.modifySrid() 83 | 84 | return sqlStr 85 | } 86 | 87 | func (c *ColumnDefinition) Change() *ColumnDefinition { 88 | c.ColumnChange = true 89 | return c 90 | } 91 | 92 | func (c *ColumnDefinition) IsChange() bool { 93 | return c.ColumnChange 94 | } 95 | 96 | func (c *ColumnDefinition) Nullable(value bool) *ColumnDefinition { 97 | c.ColumnNullable = value 98 | return c 99 | } 100 | 101 | func (c *ColumnDefinition) After(name string) *ColumnDefinition { 102 | c.ColumnAfter = name 103 | return c 104 | } 105 | 106 | func (c *ColumnDefinition) AutoIncrement() *ColumnDefinition { 107 | c.ColumnAutoIncrement = true 108 | return c 109 | } 110 | 111 | func (c *ColumnDefinition) Charset(charset string) *ColumnDefinition { 112 | c.ColumnCharset = charset 113 | return c 114 | } 115 | 116 | func (c *ColumnDefinition) Collation(collation string) *ColumnDefinition { 117 | c.ColumnCollation = collation 118 | return c 119 | } 120 | 121 | func (c *ColumnDefinition) Comment(comment string) *ColumnDefinition { 122 | c.ColumnComment = comment 123 | return c 124 | } 125 | 126 | func (c *ColumnDefinition) Default(defaultVal Expr) *ColumnDefinition { 127 | c.ColumnDefault = defaultVal 128 | return c 129 | } 130 | 131 | func (c *ColumnDefinition) First() *ColumnDefinition { 132 | c.ColumnFirst = true 133 | return c 134 | } 135 | 136 | func (c *ColumnDefinition) StoredAs(expression string) *ColumnDefinition { 137 | c.ColumnStoredAs = expression 138 | return c 139 | } 140 | 141 | func (c *ColumnDefinition) Unsigned() *ColumnDefinition { 142 | c.ColumnUnsigned = true 143 | return c 144 | } 145 | 146 | func (c *ColumnDefinition) UseCurrent() *ColumnDefinition { 147 | c.ColumnUseCurrent = true 148 | return c 149 | } 150 | 151 | func (c *ColumnDefinition) VirtualAs(expression string) *ColumnDefinition { 152 | c.ColumnVirtualAs = expression 153 | return c 154 | } 155 | 156 | func (c *ColumnDefinition) GeneratedAs(expression string) *ColumnDefinition { 157 | return c 158 | } 159 | 160 | func (c *ColumnDefinition) Always() *ColumnDefinition { 161 | return c 162 | } 163 | 164 | func (c *ColumnDefinition) Type() string { 165 | if c.ColumnUseCurrent { 166 | return " " + c.ColumnType + " DEFAULT CURRENT_TIMESTAMP" 167 | } 168 | 169 | return " " + c.ColumnType 170 | } 171 | 172 | func (c *ColumnDefinition) modifyUnsigned() string { 173 | if c.ColumnUnsigned { 174 | return " UNSIGNED" 175 | } 176 | 177 | return "" 178 | } 179 | 180 | func (c *ColumnDefinition) modifyVirtualAs() string { 181 | if c.ColumnVirtualAs != "" { 182 | return fmt.Sprintf(" AS (%s)", c.ColumnVirtualAs) 183 | } 184 | return "" 185 | } 186 | 187 | func (c *ColumnDefinition) modifyStoredAs() string { 188 | if c.ColumnStoredAs != "" { 189 | return fmt.Sprintf(" AS (%s) stored", c.ColumnStoredAs) 190 | } 191 | return "" 192 | } 193 | 194 | func (c *ColumnDefinition) modifyCharset() string { 195 | if c.ColumnCharset != "" { 196 | return " CHARACTER SET " + c.ColumnCharset 197 | } 198 | return "" 199 | } 200 | 201 | func (c *ColumnDefinition) modifyCollate() string { 202 | if c.ColumnCollation != "" { 203 | return fmt.Sprintf(" COLLATE '%s'", c.ColumnCollation) 204 | } 205 | 206 | return "" 207 | } 208 | 209 | func (c *ColumnDefinition) modifyNullable() string { 210 | if c.ColumnVirtualAs == "" && c.ColumnStoredAs == "" { 211 | if c.ColumnNullable { 212 | return " NULL" 213 | } 214 | 215 | return " NOT NULL" 216 | } 217 | 218 | return "" 219 | } 220 | 221 | func (c *ColumnDefinition) modifyDefault() string { 222 | if c.ColumnDefault.Value != "" { 223 | var value string 224 | 225 | if c.ColumnDefault.Type == ExprTypeRaw { 226 | value = c.ColumnDefault.Value 227 | } else { 228 | value = "'" + c.ColumnDefault.Value + "'" 229 | } 230 | 231 | return " DEFAULT " + value 232 | } 233 | 234 | return "" 235 | } 236 | 237 | func (c *ColumnDefinition) modifyIncrement() string { 238 | if c.ColumnAutoIncrement { 239 | return " AUTO_INCREMENT PRIMARY KEY" 240 | } 241 | 242 | return "" 243 | } 244 | 245 | func (c *ColumnDefinition) modifyComment() string { 246 | if c.ColumnComment != "" { 247 | return " COMMENT '" + addSlashes(c.ColumnComment) + "'" 248 | } 249 | 250 | return "" 251 | } 252 | 253 | func (c *ColumnDefinition) modifyAfter() string { 254 | if c.ColumnAfter != "" { 255 | return " AFTER " + c.ColumnAfter 256 | } 257 | 258 | return "" 259 | } 260 | func (c *ColumnDefinition) modifyFirst() string { 261 | if c.ColumnFirst { 262 | return " FIRST" 263 | } 264 | 265 | return "" 266 | } 267 | 268 | func (c *ColumnDefinition) modifySrid() string { 269 | if c.ColumnSrid > 0 { 270 | return fmt.Sprintf(" SRID %d", c.ColumnSrid) 271 | } 272 | return "" 273 | } 274 | 275 | func addSlashes(str string) string { 276 | var tmpRune []rune 277 | strRune := []rune(str) 278 | for _, ch := range strRune { 279 | switch ch { 280 | case []rune{'\\'}[0], []rune{'"'}[0], []rune{'\''}[0]: 281 | tmpRune = append(tmpRune, []rune{'\\'}[0]) 282 | tmpRune = append(tmpRune, ch) 283 | default: 284 | tmpRune = append(tmpRune, ch) 285 | } 286 | } 287 | return string(tmpRune) 288 | } 289 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eloquent ORM 2 | 3 | Eloquent 是一款为 Golang 开发的基于代码生成的数据库 ORM 框架,它的设计灵感来源于著名的 PHP 开发框架 Laravel,支持 MySQL 等数据库。 4 | 5 | ## 模型定义 6 | 7 | Eloquent 使用 YAML 文件来定义模型结构,通过代码生成的方式来创建模型对象。下面是模型定义的基本格式: 8 | 9 | ```yaml 10 | package: models // 当前模型所在的包名 11 | imports: // 要 import 的包,当 models.definition.fields 中包含外部类型时,在这里引入外部的包,可选 12 | - github.com/mylxsw/eloquent 13 | meta: 14 | table_prefix: el_ // 表前缀,默认为空,可选 15 | models: // 模型定义部分 16 | - name: user // 模型名称,也是默认表名,模型对象会转换为首字母大写的驼峰命名格式 17 | relations: // 模型关联定义,可选 18 | - model: role 19 | rel: n-1 20 | foreign_key: role_id 21 | owner_key: id 22 | local_key: "" 23 | table: "" 24 | package: "" 25 | method: "" 26 | definition: // 模型基本信息 27 | table_name: user // 对应的数据库表名,不指定该选项则默认使用模型名 28 | without_create_time: false // 不添加 created_at 字段,默认会自动添加该字段,类型为 time.Time,插入数据时自动更新 29 | without_update_time: false // 不添加 updated_at 字段,默认会自动添加该字段,类型为 time.Time,更新数据时自动更新 30 | soft_delete: false // 是否启用软删除支持,启用软删除后,会自动添加 deleted_at 字段,类型为 time.Time 31 | fields: // 表字段对应关系 32 | - name: id // 主键 ID 33 | type: int64 // 主键 ID 类型 34 | tag: json:"id" // 主键 ID 类型的 Tags 35 | - name: name // 其它字段名 36 | type: string // 其它字段类型 37 | tag: json:"name" // 其它字段的 Tags 38 | - name: age 39 | type: int64 40 | tag: json:"age" 41 | ``` 42 | 43 | 你可以使用命令行工具 `eloquent create-model` 来生成模型定义文件 44 | 45 | ```bash 46 | $ eloquent create-model -h 47 | NAME: 48 | create-model - 创建表模型定义文件 49 | 50 | USAGE: 51 | create-model [command options] [arguments...] 52 | 53 | OPTIONS: 54 | --table value 表名 55 | --package value 包名 56 | --output value 输出目录 57 | --table-prefix value 表前缀 58 | --no-created_at 不自动添加 created_at 字段 59 | --no-updated_at 不自动添加 updated_at 字段 60 | --soft-delete 启用软删除支持 61 | --import value 引入包 62 | ``` 63 | 64 | 比如创建一个用户模型定义文件,表名为 `users`,包名为 `models`,启用软删除支持,输出到当前目录 65 | 66 | ```bash 67 | $ eloquent create-model --table users --package models --soft-delete --output . 68 | $ ls -al 69 | total 8 70 | -rwxr-xr-x 1 mylxsw wheel 162B 6 17 21:32 users.yml 71 | ``` 72 | 73 | 模型定义创建之后,默认是只有主键 id 的,我们手动修改模型定义文件,添加几个额外的字段, 74 | 75 | ```yaml 76 | package: models 77 | models: 78 | - name: users 79 | definition: 80 | table_name: users 81 | soft_delete: true 82 | fields: 83 | - name: id 84 | type: int64 85 | tag: json:"id" 86 | - name: name 87 | type: string 88 | - name: age 89 | type: int 90 | ``` 91 | 92 | 使用 `eloquent gen` 命令来生成模型文件 93 | 94 | ```bash 95 | $ eloquent gen -h 96 | NAME: 97 | gen - 根据模型文件定义生成模型对象 98 | 99 | USAGE: 100 | gen [command options] [arguments...] 101 | 102 | OPTIONS: 103 | --source value 模型定义所在文件的 Glob 表达式,比如 ./models/*.yml 104 | ``` 105 | 106 | 比如上面我们创建的 `users.yml` 模型定义,执行下面的命令创建模型对象 107 | 108 | ```bash 109 | $ eloquent gen --source './*.yml' 110 | users.orm.go 111 | $ ls -al 112 | total 40 113 | drwxr-xr-x 4 mylxsw wheel 128 6 17 21:36 . 114 | drwxrwxrwt 17 root wheel 544 6 17 21:32 .. 115 | -rwxr-xr-x 1 mylxsw wheel 15614 6 17 21:36 users.orm.go 116 | -rwxr-xr-x 1 mylxsw wheel 162 6 17 21:32 users.yml 117 | ``` 118 | 119 | 生成的模型对象会包含两个模型定义的结构体,一个是模型名称本身命名的结构体 Xxx,用于与数据库进行交互 120 | 121 | ```go 122 | type User struct { 123 | Id null.Int `json:"id"` 124 | Name null.String 125 | Age null.Int 126 | CreatedAt null.Time 127 | UpdatedAt null.Time 128 | DeletedAt null.Time 129 | } 130 | ``` 131 | 132 | > 这里的结构体字段类型使用了 `gopkg.in/guregu/null.v3` 对基本类型的封装,解决 Golang 基本类型字段不支持 `null` 值的问题,但是这样也给使用者带来了不便,我们必须使用 `Name.ValueOrZero()` 这种方式来获取字段的基本类型值。 133 | 134 | 另一个结构体是 XxxPlain,该结构体将 `null` 值转换为了字段的基本类型,当数据库中为 null 时,会返回字段的默认值。通过模型对象的 `ToXxxPlain()` 方法可以将模型对象转换为该结构体。 135 | 136 | ```go 137 | type UserPlain struct { 138 | Id int64 139 | Name string 140 | Age int 141 | CreatedAt time.Time 142 | UpdatedAt time.Time 143 | DeletedAt time.Time 144 | } 145 | ``` 146 | 147 | ## 模型使用 148 | 149 | 本文的讲解将基于前面创建的用户模型(`users.yml`),假定我们的模型目录为 `models`。在使用模型的 API 之前,需要先创建数据库连接对象,我们使用 Golang 标准库的 `database/sql` 包来创建 150 | 151 | ```go 152 | db, err := sql.Open("mysql", connURI) 153 | if err != nil { 154 | panic(err) 155 | } 156 | 157 | defer db.Close() 158 | ``` 159 | 160 | ### 创建模型实例 161 | 162 | 创建模型定义文件和生成模型对象文件后,使用 `models.NewXxxModel(db query.Database)` 来创建一个模型对象。 163 | 164 | ```go 165 | // 创建用户模型对象 166 | userModel := models.NewUsersModel(db) 167 | ``` 168 | 169 | ### 查询条件 170 | 171 | 在 Eloquent 中,查询条件使用 `query.Builder()` 方法来构建,该方法会生成一个 `SQLBuilder` 对象,使用它我们可以使用链式语法来构建查询条件 172 | 173 | ```go 174 | builder := query.Builder() 175 | ``` 176 | 177 | `SQLBuilder` 对象提供了一系列的方法来帮助我们构建灵活的查询条件 178 | 179 | - `WhereColumn(field, operator string, value string) Condition` 180 | - `OrWhereColumn(field, operator string, value string) Condition` 181 | - `OrWhereNotExist(subQuery SubQuery) Condition` 182 | - `OrWhereExist(subQuery SubQuery) Condition` 183 | - `WhereNotExist(subQuery SubQuery) Condition` 184 | - `WhereExist(subQuery SubQuery) Condition` 185 | - `OrWhereNotNull(field string) Condition` 186 | - `OrWhereNull(field string) Condition` 187 | - `WhereNotNull(field string) Condition` 188 | - `WhereNull(field string) Condition` 189 | - `OrWhereRaw(raw string, items ...interface{}) Condition` 190 | - `WhereRaw(raw string, items ...interface{}) Condition` 191 | - `OrWhereNotIn(field string, items ...interface{}) Condition` 192 | - `OrWhereIn(field string, items ...interface{}) Condition` 193 | - `WhereNotIn(field string, items ...interface{}) Condition` 194 | - `WhereIn(field string, items ...interface{}) Condition` 195 | - `WhereGroup(wc ConditionGroup) Condition` 196 | - `OrWhereGroup(wc ConditionGroup) Condition` 197 | - `Where(field string, value ...interface{}) Condition` 198 | - `OrWhere(field string, value ...interface{}) Condition` 199 | - `WhereBetween(field string, min, max interface{}) Condition` 200 | - `WhereNotBetween(field string, min, max interface{}) Condition` 201 | - `OrWhereBetween(field string, min, max interface{}) Condition` 202 | - `OrWhereNotBetween(field string, min, max interface{}) Condition` 203 | - `WhereCondition(cond sqlCondition) Condition` 204 | - `When(when When, cg ConditionGroup) Condition` 205 | - `OrWhen(when When, cg ConditionGroup) Condition` 206 | - `Get() []sqlCondition` 207 | - `Append(cond Condition) Condition` 208 | - `Resolve(tableAlias string) (string, []interface{})` 209 | 210 | 比如我们要查询用户名模糊匹配 `Tom`,年龄大于 30 岁的用户 211 | 212 | ```go 213 | query.Builder().Where(model.UserFieldName, "LIKE", "%Tom%").Where("age", ">", 30) 214 | ``` 215 | 216 | ### CRUD 217 | 218 | 查询用户列表 219 | 220 | ```go 221 | users, err := model.NewUsersModel(s.db).Get() 222 | for _, user := range users { 223 | // user.Id.ValueOrZero() 224 | // user.Name.ValueOrZero() 225 | 226 | // user.ToUserPlain().Name 227 | } 228 | ``` 229 | 230 | 查询第一个匹配的用户 231 | 232 | ```go 233 | user, err := model.NewUserModel(s.db).First(query.Builder().Where(model.UserFieldName, username)) 234 | ``` 235 | 236 | 创建一个用户 237 | 238 | ```go 239 | userID, err := model.NewUserModel(s.db).Save(model.User{ 240 | Account: null.StringFrom(username), 241 | Status: null.IntFrom(int64(UserStatusEnabled)), 242 | Password: null.StringFrom(password), 243 | }) 244 | 245 | // 也可以这样 246 | userID, err := model.NewUserModel(s.db).Create(query.KV{ 247 | model.UserFieldUuid: userInfo.Uuid, 248 | model.UserFieldName: userInfo.Name, 249 | model.UserFieldAccount: username, 250 | model.UserFieldStatus: userInfo.Status, 251 | model.UserFieldPassword: userInfo.Password, 252 | }) 253 | 254 | // 还可以这样 255 | user := models.UserPlain{ 256 | Name: "Tom", 257 | Age: 32, 258 | } 259 | 260 | userID, err := userModel.Save(user.ToUser()) 261 | ``` 262 | 263 | ## 数据库迁移 264 | 265 | Eloquent 支持与 Laravel 框架类似的数据库迁移功能,使用语法也基本一致。 266 | 267 | ```go 268 | m := migrate.NewManager(db).Init() 269 | 270 | m.Schema("202104222322").Create("user", func(builder *migrate.Builder) { 271 | builder.Increments("id") 272 | builder.String("uuid", 255).Comment("用户 uuid") 273 | builder.String("name", 100).Comment("用户名") 274 | builder.Timestamps(0) 275 | }) 276 | m.Schema("202106100943").Table("user", func(builder *migrate.Builder) { 277 | builder.String("account", 100).Comment("账号名") 278 | builder.TinyInteger("status", false, true).Default(migrate.RawExpr("1")).Comment("状态:0-禁用 1-启用") 279 | }) 280 | m.Schema("202106102309").Table("user", func(builder *migrate.Builder) { 281 | builder.String("password", 256).Nullable(true).Comment("密码") 282 | }) 283 | 284 | if err := m.Run(); err != nil { 285 | panic(err) 286 | } 287 | ``` 288 | 289 | ## 示例项目 290 | 291 | - [tech-share](https://github.com/mylxsw/tech-share) 这是一个简单的 web 项目,用于企业内部对技术分享的管理 -------------------------------------------------------------------------------- /_examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | 7 | "_examples/models" 8 | 9 | _ "github.com/go-sql-driver/mysql" 10 | "github.com/mylxsw/asteria/log" 11 | "github.com/mylxsw/eloquent" 12 | "github.com/mylxsw/eloquent/event" 13 | "github.com/mylxsw/eloquent/migrate" 14 | "github.com/mylxsw/eloquent/query" 15 | "github.com/mylxsw/go-utils/array" 16 | "gopkg.in/guregu/null.v3" 17 | ) 18 | 19 | func main() { 20 | 21 | //log.All().WithFileLine(true) 22 | 23 | createEventDispatcher() 24 | 25 | connURI := "root:@tcp(127.0.0.1:3306)/eloquent_example?parseTime=true" 26 | db, err := sql.Open("mysql", connURI) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | defer db.Close() 32 | 33 | createMigrate(db) 34 | databaseOperationExample(db) 35 | modelOperationExample(db) 36 | } 37 | 38 | type UserView struct { 39 | Name string 40 | Email string 41 | } 42 | 43 | func modelOperationExample(db *sql.DB) { 44 | err := eloquent.Transaction(db, func(tx query.Database) error { 45 | userModel := models.NewUserModel(tx) 46 | 47 | id, err := userModel.Save(context.TODO(), models.UserN{ 48 | Name: null.StringFrom("guan"), 49 | Email: null.StringFrom("guan@aicode.cc"), 50 | Password: null.StringFrom("88959q"), 51 | }) 52 | AssertError(err) 53 | 54 | log.Infof("Insert User ID=%d", id) 55 | 56 | user, err := userModel.Find(context.TODO(), id) 57 | AssertError(err) 58 | 59 | log.Infof("User id=%d, name=%s, email=%s", user.Id.Int64, user.Name.String, user.Email.String) 60 | 61 | roleId, err := user.Role().Create(context.TODO(), models.RoleN{ 62 | Name: null.StringFrom("admin"), 63 | Description: null.StringFrom("root user"), 64 | }) 65 | AssertError(err) 66 | 67 | log.Infof("Insert Role ID=%d", roleId) 68 | 69 | users, err := userModel.Get(context.TODO()) 70 | AssertError(err) 71 | 72 | ids := array.Map(users, func(user models.UserN, _ int) int64 { return user.Id.Int64 }) 73 | log.Infof("user ids: %v", ids) 74 | 75 | for _, user := range users { 76 | log.Infof("User id=%d, name=%s, email=%s, role_id=%d", user.Id.Int64, user.Name.String, user.Email.String, user.RoleId.Int64) 77 | var userView UserView 78 | AssertError(user.As(&userView)) 79 | 80 | log.Infof("UserView name=%s, email=%s", userView.Name, userView.Email) 81 | } 82 | 83 | // only specified fields 84 | users, err = userModel.Get(context.TODO(), query.Builder().Select("id", "name")) 85 | AssertError(err) 86 | 87 | for _, user := range users { 88 | log.Infof("User With Only id/name, id=%d, name=%s, email=%v(must be null)", user.Id, user.Name, user.Email) 89 | } 90 | 91 | _, err = userModel.DeleteById(context.TODO(), user.Id.Int64) 92 | AssertError(err) 93 | 94 | c1, err := userModel.Count(context.TODO()) 95 | AssertError(err) 96 | 97 | c2, err := userModel.WithTrashed().Count(context.TODO()) 98 | AssertError(err) 99 | 100 | log.Infof("After soft deleted count=%d/%d", c1, c2) 101 | 102 | _, err = userModel.ForceDeleteById(context.TODO(), user.Id.Int64) 103 | AssertError(err) 104 | 105 | c1, err = userModel.Count(context.TODO()) 106 | AssertError(err) 107 | 108 | c2, err = userModel.WithTrashed().Count(context.TODO()) 109 | AssertError(err) 110 | 111 | log.Infof("After force deleted count=%d/%d", c1, c2) 112 | 113 | _, err = userModel.Get(context.TODO(), query.Builder().WhereIn("id", []int{1, 2, 3})) 114 | AssertError(err) 115 | 116 | _, err = userModel.Get(context.TODO(), query.Builder().WhereIn("id", 1, 2, 3, 4)) 117 | AssertError(err) 118 | 119 | _, err = userModel.Get(context.TODO(), query.Builder().WhereIn("id", query.ToAnys([]int{1, 2, 3, 4, 5})...)) 120 | AssertError(err) 121 | 122 | return nil 123 | }) 124 | 125 | AssertError(err) 126 | } 127 | 128 | func databaseOperationExample(db *sql.DB) { 129 | err := eloquent.Transaction(db, func(tx query.Database) error { 130 | id, err := eloquent.DB(tx).Insert( 131 | context.TODO(), 132 | "wz_user", 133 | query.KV{ 134 | "name": "mylxsw", 135 | "email": "mylxsw@aicode.cc", 136 | "password": "123455", 137 | }, 138 | ) 139 | AssertError(err) 140 | 141 | log.Infof("Insert ID=%d", id) 142 | 143 | id, err = eloquent.DB(tx).Insert( 144 | context.TODO(), 145 | "wz_user", 146 | query.KV{ 147 | "name": "adanos", 148 | "email": "adanos@aicode.cc", 149 | "password": "123455", 150 | }, 151 | ) 152 | AssertError(err) 153 | 154 | log.Infof("Insert ID=%d", id) 155 | 156 | res, err := eloquent.DB(tx).Query( 157 | context.TODO(), 158 | eloquent.Build("wz_user").Select("id", "name", "email"), 159 | func(row eloquent.Scanner) (any, error) { 160 | user := models.User{} 161 | err := row.Scan(&user.Id, &user.Name, &user.Email) 162 | 163 | return user, err 164 | }, 165 | ) 166 | AssertError(err) 167 | 168 | users := array.Map(res, func(v any, _ int) models.User { return v.(models.User) }) 169 | array.Each(users, func(user models.User, _ int) { 170 | log.Infof("user_id=%d, name=%s, email=%s", user.Id, user.Name, user.Email) 171 | }) 172 | 173 | res, err = eloquent.DB(tx).Query(context.TODO(), eloquent.Raw("select count(*) from wz_user"), func(row eloquent.Scanner) (any, error) { 174 | var count int64 175 | err := row.Scan(&count) 176 | return count, err 177 | }) 178 | AssertError(err) 179 | 180 | log.Infof("user_count=%d", res[0].(int64)) 181 | 182 | affected, err := eloquent.DB(tx).Delete(context.TODO(), eloquent.Build("wz_user")) 183 | AssertError(err) 184 | 185 | log.Infof("Deleted rows %d", affected) 186 | 187 | return nil 188 | }) 189 | 190 | AssertError(err) 191 | } 192 | 193 | func createMigrate(db *sql.DB) { 194 | m := migrate.NewManager(db).Init(context.TODO()) 195 | m.Schema("2023112901").Raw("wz_storage_file", func() []string { 196 | return []string{ 197 | `CREATE TABLE wz_storage_file 198 | ( 199 | id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, 200 | user_id INT NOT NULL, 201 | file_key VARCHAR(255) NULL, 202 | hash VARCHAR(255) NULL, 203 | file_size INT NULL, 204 | bucket VARCHAR(100) NULL, 205 | name VARCHAR(255) NULL, 206 | status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-正常 2-禁用 3-REVIEW', 207 | note VARCHAR(255) NULL COMMENT '备注', 208 | channel VARCHAR(20) NULL COMMENT '上传渠道', 209 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 210 | updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 211 | )`, 212 | } 213 | }) 214 | m.Schema("20190692901").Create("wz_user", func(builder *migrate.Builder) { 215 | builder.Increments("id") 216 | builder.String("name", 255) 217 | builder.String("email", 255).Unique() 218 | builder.String("password", 255) 219 | builder.Timestamps(0) 220 | builder.RememberToken() 221 | 222 | builder.Index("", "name", "password") 223 | }) 224 | 225 | m.Schema("20190692902").Create("wz_password_reset", func(builder *migrate.Builder) { 226 | builder.Increments("id") 227 | builder.String("email", 255) 228 | builder.String("token", 255) 229 | builder.Timestamp("created_at", 0).Nullable(true) 230 | }) 231 | 232 | m.Schema("20190692903").Table("wz_user", func(builder *migrate.Builder) { 233 | builder.TinyInteger("status", false, true). 234 | Nullable(true). 235 | Default(migrate.StringExpr("1")). 236 | Comment("用户状态:0-未激活,1-已激活,2-已禁用") 237 | }) 238 | 239 | m.Schema("20190692904").Create("wz_role", func(builder *migrate.Builder) { 240 | builder.Increments("id") 241 | builder.String("name", 100).Comment("角色名") 242 | builder.Text("description").Comment("备注") 243 | builder.Timestamps(0) 244 | }) 245 | 246 | m.Schema("20190692905").Table("wz_user", func(builder *migrate.Builder) { 247 | builder.Integer("role_id", false, false).Nullable(true).Comment("角色ID") 248 | }) 249 | 250 | m.Schema("20190692906").Table("wz_user", func(builder *migrate.Builder) { 251 | builder.SoftDeletes("deleted_at", 0) 252 | }) 253 | 254 | m.Schema("2019063001").Create("wz_enterprise", func(builder *migrate.Builder) { 255 | builder.Increments("id") 256 | builder.String("name", 255).Comment("企业名称") 257 | builder.String("address", 255).Comment("企业地址") 258 | builder.TinyInteger("status", false, false).Default(migrate.StringExpr("0")).Comment("企业状态:0-未审核 1-审核未通过 2-审核通过") 259 | 260 | builder.SoftDeletes("deleted_at", 0) 261 | }) 262 | 263 | m.Schema("2019063002").Table("wz_user", func(builder *migrate.Builder) { 264 | builder.Integer("enterprise_id", false, true).Nullable(true).Comment("企业ID") 265 | }) 266 | 267 | if err := m.Run(context.TODO()); err != nil { 268 | panic(err) 269 | } 270 | } 271 | 272 | func createEventDispatcher() { 273 | // create event listener 274 | em := event.NewEventManager(event.NewMemoryEventStore()) 275 | event.SetDispatcher(em) 276 | 277 | em.Listen(func(evt event.MigrationStartedEvent) { 278 | log.Debugf("MigrationStartedEvent: %s", evt.SQL) 279 | }) 280 | 281 | em.Listen(func(evt event.QueryExecutedEvent) { 282 | log.WithFields(log.Fields{ 283 | "sql": evt.SQL, 284 | "bindings": evt.Bindings, 285 | "elapse": evt.Time.String(), 286 | }).Debugf("QueryExecutedEvent") 287 | }) 288 | 289 | em.Listen(func(evt event.TransactionBeginningEvent) { 290 | log.Debugf("Transaction starting") 291 | }) 292 | 293 | em.Listen(func(evt event.TransactionCommittedEvent) { 294 | log.Debugf("Transaction committed") 295 | }) 296 | 297 | em.Listen(func(evt event.TransactionRolledBackEvent) { 298 | log.Debugf("Transaction rollback") 299 | }) 300 | } 301 | 302 | func AssertError(err error) { 303 | if err != nil { 304 | panic(err) 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /generator/template/relation.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import "fmt" 4 | 5 | func GetRelationTemplate() string { 6 | temp := `{{ range $j, $rel := $m.Relations }}{{ if rel $rel | eq "belongsTo" }} 7 | %s 8 | {{ end }}{{ if rel $rel | eq "hasMany" }} 9 | %s 10 | {{ end }}{{ if rel $rel | eq "hasOne" }} 11 | %s 12 | {{ end }}{{ if rel $rel | eq "belongsToMany" }} 13 | %s 14 | {{ end }}{{ end }} 15 | ` 16 | return fmt.Sprintf( 17 | temp, 18 | getRelationBelongsToTemplate(), 19 | getRelationHasManyTemplate(), 20 | getRelationHasOneTemplate(), 21 | getRelationBelongsToManyTemplate(), 22 | ) 23 | } 24 | 25 | func getRelationBelongsToTemplate() string { 26 | return `{{ $relName := rel_belongs_to_name $rel $m }} 27 | func (inst *{{ camel $m.Name }}N) {{ rel_method $rel }}() *{{ $relName }} { 28 | return &{{ $relName }} { 29 | source: inst, 30 | relModel: {{ rel_package_prefix $rel }}New{{ camel $rel.Model }}Model(inst.{{ lower_camel $m.Name }}Model.GetDB()), 31 | } 32 | } 33 | 34 | type {{ $relName }} struct { 35 | source *{{ camel $m.Name }}N 36 | relModel *{{ rel_package_prefix $rel }}{{ camel $rel.Model }}Model 37 | } 38 | 39 | func (rel *{{ $relName }}) Create(ctx context.Context, target {{ camel $rel.Model }}N) (int64, error) { 40 | targetId, err := rel.relModel.Save(ctx, target) 41 | if err != nil { 42 | return 0, err 43 | } 44 | 45 | target.Id = null.IntFrom(targetId) 46 | 47 | rel.source.{{ rel_foreign_key $rel | camel }} = target.{{ rel_owner_key $rel | camel }} 48 | if err := rel.source.Save(ctx); err != nil { 49 | return targetId, err 50 | } 51 | 52 | return targetId, nil 53 | } 54 | 55 | func (rel *{{ $relName }}) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 56 | builder := query.Builder().Where("{{ rel_owner_key $rel | snake }}", rel.source.{{ rel_foreign_key $rel | camel }}).Merge(builders...) 57 | 58 | return rel.relModel.Exists(ctx, builder) 59 | } 60 | 61 | func (rel *{{ $relName }}) First(ctx context.Context, builders ...query.SQLBuilder) (*{{ camel $rel.Model }}N, error) { 62 | builder := query.Builder().Where("{{ rel_owner_key $rel | snake }}", rel.source.{{ rel_foreign_key $rel | camel }}).Limit(1).Merge(builders...) 63 | 64 | return rel.relModel.First(ctx, builder) 65 | } 66 | 67 | func (rel *{{ $relName }}) Associate(ctx context.Context, target {{ camel $rel.Model }}N) error { 68 | rel.source.{{ rel_foreign_key $rel | camel }} = target.{{ rel_owner_key $rel | camel }} 69 | return rel.source.Save(ctx) 70 | } 71 | 72 | func (rel *{{ $relName }}) Dissociate(ctx context.Context) error { 73 | rel.source.{{ rel_foreign_key $rel | camel }} = null.IntFrom(0) 74 | return rel.source.Save(ctx) 75 | } 76 | ` 77 | } 78 | 79 | func getRelationHasManyTemplate() string { 80 | return `{{ $relName := rel_has_many_name $rel $m }} 81 | func (inst *{{ camel $m.Name }}N) {{ rel_method $rel }}() *{{ $relName }} { 82 | return &{{ $relName }} { 83 | source: inst, 84 | relModel: {{ rel_package_prefix $rel }}New{{ camel $rel.Model }}Model(inst.{{ lower_camel $m.Name }}Model.GetDB()), 85 | } 86 | } 87 | 88 | type {{ $relName }} struct { 89 | source *{{ camel $m.Name }}N 90 | relModel *{{ rel_package_prefix $rel }}{{ camel $rel.Model }}Model 91 | } 92 | 93 | func (rel *{{ $relName }}) Get(ctx context.Context, builders ...query.SQLBuilder) ([]{{ camel $rel.Model }}N, error) { 94 | builder := query.Builder().Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_local_key $rel | camel }}).Merge(builders...) 95 | 96 | return rel.relModel.Get(ctx, builder) 97 | } 98 | 99 | func (rel *{{ $relName }}) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 100 | builder := query.Builder().Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_local_key $rel | camel }}).Merge(builders...) 101 | 102 | return rel.relModel.Count(ctx, builder) 103 | } 104 | 105 | func (rel *{{ $relName }}) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 106 | builder := query.Builder().Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_local_key $rel | camel }}).Merge(builders...) 107 | 108 | return rel.relModel.Exists(ctx, builder) 109 | } 110 | 111 | func (rel *{{ $relName }}) First(ctx context.Context, builders ...query.SQLBuilder) (*{{ camel $rel.Model }}N, error) { 112 | builder := query.Builder().Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_local_key $rel | camel }}).Limit(1).Merge(builders...) 113 | return rel.relModel.First(ctx, builder) 114 | } 115 | 116 | func (rel *{{ $relName }}) Create(ctx context.Context, target {{ camel $rel.Model }}N) (int64, error) { 117 | target.{{ rel_foreign_key_rev $rel $m | camel }} = rel.source.Id 118 | return rel.relModel.Save(ctx, target) 119 | } 120 | ` 121 | } 122 | 123 | func getRelationHasOneTemplate() string { 124 | return `{{ $relName := rel_has_one_name $rel $m }} 125 | func (inst *{{ camel $m.Name }}N) {{ rel_method $rel }}() *{{ $relName }} { 126 | return &{{ $relName }} { 127 | source: inst, 128 | relModel: {{ rel_package_prefix $rel }}New{{ camel $rel.Model }}Model(inst.{{ lower_camel $m.Name }}Model.GetDB()), 129 | } 130 | } 131 | 132 | type {{ $relName }} struct { 133 | source *{{ camel $m.Name }}N 134 | relModel *{{ rel_package_prefix $rel }}{{ camel $rel.Model }}Model 135 | } 136 | 137 | func (rel *{{ $relName }}) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 138 | builder := query.Builder().Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_local_key $rel | camel }}).Merge(builders...) 139 | 140 | return rel.relModel.Exists(ctx, builder) 141 | } 142 | 143 | func (rel *{{ $relName }}) First(ctx context.Context, builders ...query.SQLBuilder) (*{{ camel $rel.Model }}N, error) { 144 | builder := query.Builder().Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_local_key $rel | camel }}).Limit(1).Merge(builders...) 145 | return rel.relModel.First(ctx, builder) 146 | } 147 | 148 | func (rel *{{ $relName }}) Create(ctx context.Context, target {{ camel $rel.Model }}N) (int64, error) { 149 | target.{{ rel_foreign_key_rev $rel $m | camel }} = rel.source.{{ rel_local_key $rel | camel }} 150 | return rel.relModel.Save(ctx, target) 151 | } 152 | 153 | func (rel *{{ $relName }}) Associate(ctx context.Context, target {{ camel $rel.Model }}N) error { 154 | _, err := rel.relModel.UpdateFields( 155 | ctx, 156 | query.KV {"{{ rel_foreign_key_rev $rel $m | snake }}": rel.source.{{ rel_local_key $rel | camel }}, }, 157 | query.Builder().Where("id", target.Id), 158 | ) 159 | return err 160 | } 161 | 162 | func (rel *{{ $relName }}) Dissociate(ctx context.Context) error { 163 | _, err := rel.relModel.UpdateFields( 164 | ctx, 165 | query.KV {"{{ rel_foreign_key_rev $rel $m | snake }}": nil,}, 166 | query.Builder().Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_local_key $rel | camel }}), 167 | ) 168 | 169 | return err 170 | } 171 | ` 172 | } 173 | 174 | func getRelationBelongsToManyTemplate() string { 175 | return `{{ $relName := rel_belongs_to_many_name $rel $m }} 176 | func (inst *{{ camel $m.Name }}N) {{ rel_method $rel }}() *{{ $relName }} { 177 | return &{{ $relName }} { 178 | source: inst, 179 | pivotTable: "{{ rel_pivot_table_name $rel $m | snake }}", 180 | relModel: {{ rel_package_prefix $rel }}New{{ camel $rel.Model }}Model(inst.{{ lower_camel $m.Name }}Model.GetDB()), 181 | } 182 | } 183 | 184 | type {{ $relName }} struct { 185 | source *{{ camel $m.Name }}N 186 | pivotTable string 187 | relModel *{{ rel_package_prefix $rel }}{{ camel $rel.Model }}Model 188 | } 189 | 190 | func (rel *{{ $relName }}) Get(ctx context.Context, builders ...query.SQLBuilder) ([]{{ camel $rel.Model }}N, error) { 191 | res, err := eloquent.DB(rel.relModel.GetDB()).Query( 192 | ctx, 193 | query.Builder().Table(rel.pivotTable).Select("{{ rel_foreign_key $rel | snake }}").Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_owner_key $rel | camel }}), 194 | func(row eloquent.Scanner) (interface{}, error) { 195 | var k interface{} 196 | if err := row.Scan(&k); err != nil { 197 | return nil, err 198 | } 199 | 200 | return k, nil 201 | }, 202 | ) 203 | 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | return rel.relModel.Get(ctx, query.Builder().Merge(builders...).WhereIn("{{ rel_owner_key $rel | snake }}", res...)) 209 | } 210 | 211 | func (rel *{{ $relName }}) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 212 | res, err := eloquent.DB(rel.relModel.GetDB()).Query( 213 | ctx, 214 | query.Builder().Table(rel.pivotTable).Select(query.Raw("COUNT(1) as c")).Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_owner_key $rel | camel }}), 215 | func(row eloquent.Scanner) (interface{}, error) { 216 | var k int64 217 | if err := row.Scan(&k); err != nil { 218 | return nil, err 219 | } 220 | 221 | return k, nil 222 | }, 223 | ) 224 | 225 | if err != nil { 226 | return 0, err 227 | } 228 | 229 | return res[0].(int64), nil 230 | } 231 | 232 | func (rel *{{ $relName }}) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 233 | c, err := rel.Count(ctx, builders...) 234 | if err != nil { 235 | return false, err 236 | } 237 | 238 | return c > 0, nil 239 | } 240 | 241 | func (rel *{{ $relName }}) Attach(ctx context.Context, target {{ camel $rel.Model }}N) error { 242 | _, err := eloquent.DB(rel.relModel.GetDB()).Insert(ctx, rel.pivotTable, query.KV { 243 | "{{ rel_foreign_key $rel | snake }}": target.{{ rel_owner_key $rel | camel }}, 244 | "{{ rel_foreign_key_rev $rel $m | snake }}": rel.source.{{ rel_owner_key $rel | camel }}, 245 | }) 246 | 247 | return err 248 | } 249 | 250 | func (rel *{{ $relName }}) Detach(ctx context.Context, target {{ camel $rel.Model }}N) error { 251 | _, err := eloquent.DB(rel.relModel.GetDB()). 252 | Delete(ctx, eloquent.Build(rel.pivotTable). 253 | Where("{{ rel_foreign_key $rel | snake }}", target.{{ rel_owner_key $rel | camel }}). 254 | Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_owner_key $rel | camel }}),) 255 | 256 | return err 257 | } 258 | 259 | func (rel *{{ $relName }}) DetachAll(ctx context.Context) error { 260 | _, err := eloquent.DB(rel.relModel.GetDB()). 261 | Delete(ctx, eloquent.Build(rel.pivotTable). 262 | Where("{{ rel_foreign_key_rev $rel $m | snake }}", rel.source.{{ rel_owner_key $rel | camel }}),) 263 | return err 264 | } 265 | 266 | func (rel *{{ $relName }}) Create(ctx context.Context, target {{ camel $rel.Model }}N, builders ...query.SQLBuilder) (int64, error) { 267 | targetId, err := rel.relModel.Save(ctx, target) 268 | if err != nil { 269 | return 0, err 270 | } 271 | 272 | target.Id = null.IntFrom(targetId) 273 | 274 | err = rel.Attach(ctx, target) 275 | 276 | return targetId, err 277 | } 278 | ` 279 | } 280 | -------------------------------------------------------------------------------- /generator/template/model.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | func GetModelTemplate() string { 4 | return ` 5 | // {{ camel $m.Name }}Model is a model which encapsulates the operations of the object 6 | type {{ camel $m.Name }}Model struct { 7 | db *query.DatabaseWrap 8 | tableName string 9 | 10 | excludeGlobalScopes []string 11 | includeLocalScopes []string 12 | 13 | query query.SQLBuilder 14 | } 15 | 16 | var {{ lower_camel $m.Name }}TableName = "{{ table $i }}" 17 | 18 | // {{ camel $m.Name}}Table return table name for {{ camel $m.Name }} 19 | func {{ camel $m.Name}}Table() string { 20 | return {{ lower_camel $m.Name }}TableName 21 | } 22 | 23 | const ( 24 | {{ range $j, $f := fields $m.Definition }} 25 | Field{{ camel $m.Name }}{{ camel $f.Name }} = "{{ snake $f.Name }}"{{ end }} 26 | ) 27 | 28 | 29 | // {{ camel $m.Name }}Fields return all fields in {{ camel $m.Name }} model 30 | func {{ camel $m.Name }}Fields() []string { 31 | return []string{ {{ range $j, $f := fields $m.Definition }} 32 | "{{ snake $f.Name }}",{{ end }} 33 | } 34 | } 35 | 36 | func Set{{ camel $m.Name }}Table (tableName string) { 37 | {{ lower_camel $m.Name }}TableName = tableName 38 | } 39 | 40 | // New{{ camel $m.Name }}Model create a {{ camel $m.Name }}Model 41 | func New{{ camel $m.Name }}Model (db query.Database) *{{ camel $m.Name }}Model { 42 | return &{{ camel $m.Name }}Model { 43 | db: query.NewDatabaseWrap(db), 44 | tableName: {{ lower_camel $m.Name }}TableName, 45 | excludeGlobalScopes: make([]string, 0), 46 | includeLocalScopes: make([]string, 0), 47 | query: query.Builder(), 48 | } 49 | } 50 | 51 | // GetDB return database instance 52 | func (m *{{ camel $m.Name }}Model) GetDB() query.Database { 53 | return m.db.GetDB() 54 | } 55 | 56 | {{ if $m.Definition.SoftDelete }} 57 | // WithTrashed force soft deleted models to appear in a result set 58 | func (m *{{ camel $m.Name }}Model) WithTrashed() *{{ camel $m.Name }}Model { 59 | return m.WithoutGlobalScopes("soft_delete") 60 | } 61 | {{ end }} 62 | 63 | func (m *{{ camel $m.Name }}Model) clone() *{{ camel $m.Name }}Model { 64 | return &{{ camel $m.Name }}Model{ 65 | db: m.db, 66 | tableName: m.tableName, 67 | excludeGlobalScopes: append([]string{}, m.excludeGlobalScopes...), 68 | includeLocalScopes: append([]string{}, m.includeLocalScopes...), 69 | query: m.query, 70 | } 71 | } 72 | 73 | // WithoutGlobalScopes remove a global scope for given query 74 | func (m *{{ camel $m.Name }}Model) WithoutGlobalScopes(names ...string) *{{ camel $m.Name }}Model { 75 | mc := m.clone() 76 | mc.excludeGlobalScopes = append(mc.excludeGlobalScopes, names...) 77 | 78 | return mc 79 | } 80 | 81 | // WithLocalScopes add a local scope for given query 82 | func (m *{{ camel $m.Name }}Model) WithLocalScopes(names ...string) *{{ camel $m.Name }}Model { 83 | mc := m.clone() 84 | mc.includeLocalScopes = append(mc.includeLocalScopes, names...) 85 | 86 | return mc 87 | } 88 | 89 | // Condition add query builder to model 90 | func (m *{{ camel $m.Name }}Model) Condition(builder query.SQLBuilder) *{{ camel $m.Name }}Model { 91 | mm := m.clone() 92 | mm.query = mm.query.Merge(builder) 93 | 94 | return mm 95 | } 96 | 97 | // Find retrieve a model by its primary key 98 | func (m *{{ camel $m.Name }}Model) Find(ctx context.Context, id int64) (*{{ camel $m.Name }}N, error) { 99 | return m.First(ctx, m.query.Where("id", "=", id)) 100 | } 101 | 102 | // Exists return whether the records exists for a given query 103 | func (m *{{ camel $m.Name }}Model) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 104 | count, err := m.Count(ctx, builders...) 105 | return count > 0, err 106 | } 107 | 108 | // Count return model count for a given query 109 | func (m *{{ camel $m.Name }}Model) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 110 | sqlStr, params := m.query. 111 | Merge(builders...). 112 | Table(m.tableName). 113 | AppendCondition(m.applyScope()). 114 | ResolveCount() 115 | 116 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 117 | if err != nil { 118 | return 0, err 119 | } 120 | 121 | defer rows.Close() 122 | 123 | rows.Next() 124 | var res int64 125 | if err := rows.Scan(&res); err != nil { 126 | return 0, err 127 | } 128 | 129 | return res, nil 130 | } 131 | 132 | func (m *{{ camel $m.Name }}Model) Paginate(ctx context.Context, page int64, perPage int64, builders ...query.SQLBuilder) ([]{{ camel $m.Name }}N, query.PaginateMeta, error) { 133 | if page <= 0 { 134 | page = 1 135 | } 136 | 137 | if perPage <= 0 { 138 | perPage = 15 139 | } 140 | 141 | meta := query.PaginateMeta { 142 | PerPage: perPage, 143 | Page: page, 144 | } 145 | 146 | count, err := m.Count(ctx, builders...) 147 | if err != nil { 148 | return nil, meta, err 149 | } 150 | 151 | meta.Total = count 152 | meta.LastPage = count / perPage 153 | if count % perPage != 0 { 154 | meta.LastPage += 1 155 | } 156 | 157 | 158 | res, err := m.Get(ctx, append([]query.SQLBuilder{query.Builder().Limit(perPage).Offset((page - 1) * perPage)}, builders...)...) 159 | if err != nil { 160 | return res, meta, err 161 | } 162 | 163 | return res, meta, nil 164 | } 165 | 166 | // Get retrieve all results for given query 167 | func (m *{{ camel $m.Name }}Model) Get(ctx context.Context, builders ...query.SQLBuilder) ([]{{ camel $m.Name }}N, error) { 168 | b := m.query.Merge(builders...).Table(m.tableName).AppendCondition(m.applyScope()) 169 | if len(b.GetFields()) == 0 { 170 | b = b.Select({{ range $j, $f := fields $m.Definition }} 171 | "{{ snake $f.Name }}",{{ end }} 172 | ) 173 | } 174 | 175 | fields := b.GetFields() 176 | selectFields := make([]query.Expr, 0) 177 | 178 | for _, f := range fields { 179 | switch strcase.ToSnake(f.Value) { 180 | {{ range $j, $f := fields $m.Definition }} 181 | case "{{ snake $f.Name }}": 182 | selectFields = append(selectFields, f){{ end }} 183 | } 184 | } 185 | 186 | var createScanVar = func(fields []query.Expr) (*{{ camel $m.Name }}N, []interface{}) { 187 | var {{ lower_camel $m.Name }}Var {{ camel $m.Name }}N 188 | scanFields := make([]interface{}, 0) 189 | 190 | for _, f := range fields { 191 | switch strcase.ToSnake(f.Value) { 192 | {{ range $j, $f := fields $m.Definition }} 193 | case "{{ snake $f.Name }}": 194 | scanFields = append(scanFields, &{{ lower_camel $m.Name }}Var.{{ camel $f.Name }}){{ end }} 195 | } 196 | } 197 | 198 | return &{{ lower_camel $m.Name }}Var, scanFields 199 | } 200 | 201 | sqlStr, params := b.Fields(selectFields...).ResolveQuery() 202 | 203 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | defer rows.Close() 209 | 210 | {{ lower_camel $m.Name }}s := make([]{{ camel $m.Name }}N, 0) 211 | for rows.Next() { 212 | {{ lower_camel $m.Name }}Real, scanFields := createScanVar(fields) 213 | if err := rows.Scan(scanFields...); err != nil { 214 | return nil, err 215 | } 216 | 217 | {{ lower_camel $m.Name }}Real.original = &{{ lower_camel $m.Name }}Original{} 218 | _ = query.Copy({{ lower_camel $m.Name }}Real, {{ lower_camel $m.Name }}Real.original) 219 | 220 | {{ lower_camel $m.Name }}Real.SetModel(m) 221 | {{ lower_camel $m.Name }}s = append({{ lower_camel $m.Name }}s, *{{ lower_camel $m.Name }}Real) 222 | } 223 | 224 | return {{ lower_camel $m.Name }}s, nil 225 | } 226 | 227 | // First return first result for given query 228 | func (m *{{ camel $m.Name }}Model) First(ctx context.Context, builders ...query.SQLBuilder) (*{{ camel $m.Name }}N, error) { 229 | res, err := m.Get(ctx, append(builders, query.Builder().Limit(1))...) 230 | if err != nil { 231 | return nil, err 232 | } 233 | 234 | if len(res) == 0 { 235 | return nil, query.ErrNoResult 236 | } 237 | 238 | return &res[0], nil 239 | } 240 | 241 | // Create save a new {{ $m.Name }} to database 242 | func (m *{{ camel $m.Name }}Model) Create(ctx context.Context, kv query.KV) (int64, error) { 243 | {{ if not $m.Definition.WithoutCreateTime }} 244 | if _, ok := kv["created_at"]; !ok { 245 | kv["created_at"] = time.Now() 246 | } 247 | {{ end }} 248 | {{ if not $m.Definition.WithoutUpdateTime }} 249 | if _, ok := kv["updated_at"]; !ok { 250 | kv["updated_at"] = time.Now() 251 | } 252 | {{ end }} 253 | 254 | sqlStr, params := m.query.Table(m.tableName).ResolveInsert(kv) 255 | 256 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 257 | if err != nil { 258 | return 0, err 259 | } 260 | 261 | return res.LastInsertId() 262 | } 263 | 264 | // SaveAll save all {{ $m.Name }}s to database 265 | func (m *{{ camel $m.Name }}Model) SaveAll(ctx context.Context, {{ lower_camel $m.Name }}s []{{ camel $m.Name }}N) ([]int64, error) { 266 | ids := make([]int64, 0) 267 | for _, {{ lower_camel $m.Name }} := range {{ lower_camel $m.Name }}s { 268 | id, err := m.Save(ctx, {{ lower_camel $m.Name }}) 269 | if err != nil { 270 | return ids, err 271 | } 272 | 273 | ids = append(ids, id) 274 | } 275 | 276 | return ids, nil 277 | } 278 | 279 | // Save save a {{ $m.Name }} to database 280 | func (m *{{ camel $m.Name }}Model) Save(ctx context.Context, {{ lower_camel $m.Name }} {{ camel $m.Name }}N, onlyFields ...string) (int64, error) { 281 | return m.Create(ctx, {{ lower_camel $m.Name }}.StaledKV(onlyFields...)) 282 | } 283 | 284 | // SaveOrUpdate save a new {{ $m.Name }} or update it when it has a id > 0 285 | func (m *{{ camel $m.Name }}Model) SaveOrUpdate(ctx context.Context, {{ lower_camel $m.Name }} {{ camel $m.Name }}N, onlyFields ...string) (id int64, updated bool, err error) { 286 | if {{ lower_camel $m.Name }}.Id.Int64 > 0 { 287 | _, _err := m.UpdateById(ctx, {{ lower_camel $m.Name }}.Id.Int64, {{ lower_camel $m.Name }}, onlyFields...) 288 | return {{ lower_camel $m.Name }}.Id.Int64, true, _err 289 | } 290 | 291 | _id, _err := m.Save(ctx, {{ lower_camel $m.Name }}, onlyFields...) 292 | return _id, false, _err 293 | } 294 | 295 | // UpdateFields update kv for a given query 296 | func (m *{{ camel $m.Name }}Model) UpdateFields(ctx context.Context, kv query.KV, builders ...query.SQLBuilder) (int64, error) { 297 | if len(kv) == 0 { 298 | return 0, nil 299 | } 300 | 301 | {{ if not $m.Definition.WithoutUpdateTime }} 302 | kv["updated_at"] = time.Now() 303 | {{ end }} 304 | 305 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()). 306 | Table(m.tableName). 307 | ResolveUpdate(kv) 308 | 309 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 310 | if err != nil { 311 | return 0, err 312 | } 313 | 314 | return res.RowsAffected() 315 | } 316 | 317 | // Update update a model for given query 318 | func (m *{{ camel $m.Name }}Model) Update(ctx context.Context, builder query.SQLBuilder, {{ lower_camel $m.Name }} {{ camel $m.Name }}N, onlyFields ...string) (int64, error) { 319 | return m.UpdateFields(ctx, {{ lower_camel $m.Name }}.StaledKV(onlyFields...), builder) 320 | } 321 | 322 | // UpdateById update a model by id 323 | func (m *{{ camel $m.Name }}Model) UpdateById(ctx context.Context, id int64, {{ lower_camel $m.Name }} {{ camel $m.Name }}N, onlyFields ...string) (int64, error) { 324 | return m.Condition(query.Builder().Where("id", "=", id)).UpdateFields(ctx, {{ lower_camel $m.Name }}.StaledKV(onlyFields...)) 325 | } 326 | 327 | {{ if $m.Definition.SoftDelete }} 328 | // ForceDelete permanently remove a soft deleted model from the database 329 | func (m *{{ camel $m.Name }}Model) ForceDelete(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 330 | m2 := m.WithTrashed() 331 | 332 | sqlStr, params := m2.query.Merge(builders...).AppendCondition(m2.applyScope()).Table(m2.tableName).ResolveDelete() 333 | 334 | res, err := m2.db.ExecContext(ctx, sqlStr, params...) 335 | if err != nil { 336 | return 0, err 337 | } 338 | 339 | return res.RowsAffected() 340 | } 341 | 342 | // ForceDeleteById permanently remove a soft deleted model from the database by id 343 | func (m *{{ camel $m.Name }}Model) ForceDeleteById(ctx context.Context, id int64) (int64, error) { 344 | return m.Condition(query.Builder().Where("id", "=", id)).ForceDelete(ctx) 345 | } 346 | 347 | // Restore restore a soft deleted model into an active state 348 | func (m *{{ camel $m.Name }}Model) Restore(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 349 | m2 := m.WithTrashed() 350 | return m2.UpdateFields(ctx, query.KV { 351 | "deleted_at": nil, 352 | }, builders...) 353 | } 354 | 355 | // RestoreById restore a soft deleted model into an active state by id 356 | func (m *{{ camel $m.Name }}Model) RestoreById(ctx context.Context, id int64) (int64, error) { 357 | return m.Condition(query.Builder().Where("id", "=", id)).Restore(ctx) 358 | } 359 | {{ end }} 360 | 361 | // Delete remove a model 362 | func (m *{{ camel $m.Name }}Model) Delete(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 363 | {{ if $m.Definition.SoftDelete }} 364 | return m.UpdateFields(ctx, query.KV { 365 | "deleted_at": time.Now(), 366 | }, builders...) 367 | {{ else }} 368 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()).Table(m.tableName).ResolveDelete() 369 | 370 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 371 | if err != nil { 372 | return 0, err 373 | } 374 | 375 | return res.RowsAffected() 376 | {{ end }} 377 | } 378 | 379 | // DeleteById remove a model by id 380 | func (m *{{ camel $m.Name }}Model) DeleteById(ctx context.Context, id int64) (int64, error) { 381 | return m.Condition(query.Builder().Where("id", "=", id)).Delete(ctx) 382 | } 383 | ` 384 | } 385 | -------------------------------------------------------------------------------- /query/condition.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | type Condition interface { 10 | WhereColumn(field, operator string, value string) Condition 11 | OrWhereColumn(field, operator string, value string) Condition 12 | OrWhereNotExist(subQuery SubQuery) Condition 13 | OrWhereExist(subQuery SubQuery) Condition 14 | WhereNotExist(subQuery SubQuery) Condition 15 | WhereExist(subQuery SubQuery) Condition 16 | OrWhereNotNull(field string) Condition 17 | OrWhereNull(field string) Condition 18 | WhereNotNull(field string) Condition 19 | WhereNull(field string) Condition 20 | OrWhereRaw(raw string, items ...any) Condition 21 | WhereRaw(raw string, items ...any) Condition 22 | OrWhereNotIn(field string, items ...any) Condition 23 | OrWhereIn(field string, items ...any) Condition 24 | WhereNotIn(field string, items ...any) Condition 25 | WhereIn(field string, items ...any) Condition 26 | WhereGroup(wc ConditionGroup) Condition 27 | OrWhereGroup(wc ConditionGroup) Condition 28 | Where(field string, value ...any) Condition 29 | OrWhere(field string, value ...any) Condition 30 | WhereBetween(field string, min, max any) Condition 31 | WhereNotBetween(field string, min, max any) Condition 32 | OrWhereBetween(field string, min, max any) Condition 33 | OrWhereNotBetween(field string, min, max any) Condition 34 | 35 | WhereCondition(cond sqlCondition) Condition 36 | 37 | When(when When, cg ConditionGroup) Condition 38 | OrWhen(when When, cg ConditionGroup) Condition 39 | 40 | Get() []sqlCondition 41 | Append(cond Condition) Condition 42 | 43 | Clone() Condition 44 | Empty() bool 45 | Resolve(tableAlias string) (string, []any) 46 | } 47 | 48 | type When func() bool 49 | 50 | type connectType string 51 | type conditionType int 52 | 53 | const ( 54 | connectTypeAnd connectType = "AND" 55 | connectTypeOr connectType = "OR" 56 | ) 57 | 58 | const ( 59 | condTypeSimple conditionType = iota 60 | condTypeColumn 61 | condTypeRaw 62 | condTypeIn 63 | condTypeNotIn 64 | condTypeNull 65 | condTypeNotNull 66 | condTypeExists 67 | condTypeNotExists 68 | condTypeGroup 69 | condTypeBetween 70 | condTypeNotBetween 71 | ) 72 | 73 | type SubQuery interface { 74 | ResolveQuery() (string, []any) 75 | } 76 | 77 | type sqlCondition struct { 78 | Connector connectType 79 | Type conditionType 80 | Field string 81 | Operate string 82 | Values []any 83 | Nested ConditionGroup 84 | SubQuery SubQuery 85 | When When 86 | } 87 | 88 | type ConditionGroup func(builder Condition) 89 | 90 | type conditionBuilder struct { 91 | conditions []sqlCondition 92 | } 93 | 94 | func ConditionBuilder() Condition { 95 | return &conditionBuilder{ 96 | conditions: make([]sqlCondition, 0), 97 | } 98 | } 99 | 100 | func (builder *conditionBuilder) When(when When, wc ConditionGroup) Condition { 101 | return builder.WhereCondition(sqlCondition{ 102 | Connector: connectTypeAnd, 103 | Type: condTypeGroup, 104 | Nested: wc, 105 | When: when, 106 | }) 107 | } 108 | 109 | func (builder *conditionBuilder) OrWhen(when When, wc ConditionGroup) Condition { 110 | return builder.WhereCondition(sqlCondition{ 111 | Connector: connectTypeOr, 112 | Type: condTypeGroup, 113 | Nested: wc, 114 | When: when, 115 | }) 116 | } 117 | 118 | func (builder *conditionBuilder) Clone() Condition { 119 | newBuilder := &conditionBuilder{} 120 | newBuilder.conditions = make([]sqlCondition, len(builder.conditions)) 121 | 122 | for i, c := range builder.conditions { 123 | newBuilder.conditions[i] = sqlCondition{ 124 | Connector: c.Connector, 125 | Type: c.Type, 126 | Field: c.Field, 127 | Operate: c.Operate, 128 | Values: c.Values, 129 | Nested: c.Nested, 130 | SubQuery: c.SubQuery, 131 | When: c.When, 132 | } 133 | } 134 | 135 | return newBuilder 136 | } 137 | 138 | func (builder *conditionBuilder) Get() []sqlCondition { 139 | return builder.conditions 140 | } 141 | 142 | func (builder *conditionBuilder) Append(cond Condition) Condition { 143 | builder.conditions = append(builder.conditions, cond.Get()...) 144 | return builder 145 | } 146 | 147 | func (builder *conditionBuilder) Empty() bool { 148 | return len(builder.conditions) == 0 149 | } 150 | 151 | func (builder *conditionBuilder) WhereColumn(field, operator string, value string) Condition { 152 | return builder.WhereCondition(sqlCondition{ 153 | Connector: connectTypeAnd, 154 | Type: condTypeColumn, 155 | Field: field, 156 | Operate: operator, 157 | Values: []any{value}, 158 | }) 159 | } 160 | 161 | func (builder *conditionBuilder) OrWhereColumn(field, operator string, value string) Condition { 162 | return builder.WhereCondition(sqlCondition{ 163 | Connector: connectTypeOr, 164 | Type: condTypeColumn, 165 | Field: field, 166 | Operate: operator, 167 | Values: []any{value}, 168 | }) 169 | } 170 | 171 | // -------------- 172 | 173 | func (builder *conditionBuilder) OrWhereNotExist(subQuery SubQuery) Condition { 174 | return builder.WhereCondition(sqlCondition{ 175 | Connector: connectTypeOr, 176 | Type: condTypeNotExists, 177 | SubQuery: subQuery, 178 | }) 179 | } 180 | 181 | func (builder *conditionBuilder) OrWhereExist(subQuery SubQuery) Condition { 182 | return builder.WhereCondition(sqlCondition{ 183 | Connector: connectTypeOr, 184 | Type: condTypeExists, 185 | SubQuery: subQuery, 186 | }) 187 | } 188 | 189 | func (builder *conditionBuilder) WhereNotExist(subQuery SubQuery) Condition { 190 | return builder.WhereCondition(sqlCondition{ 191 | Connector: connectTypeAnd, 192 | Type: condTypeNotExists, 193 | SubQuery: subQuery, 194 | }) 195 | } 196 | 197 | func (builder *conditionBuilder) WhereExist(subQuery SubQuery) Condition { 198 | return builder.WhereCondition(sqlCondition{ 199 | Connector: connectTypeAnd, 200 | Type: condTypeExists, 201 | SubQuery: subQuery, 202 | }) 203 | } 204 | 205 | // -------------- 206 | 207 | func (builder *conditionBuilder) OrWhereNotNull(field string) Condition { 208 | return builder.WhereCondition(sqlCondition{ 209 | Connector: connectTypeOr, 210 | Type: condTypeNotNull, 211 | Field: field, 212 | }) 213 | } 214 | 215 | func (builder *conditionBuilder) OrWhereNull(field string) Condition { 216 | return builder.WhereCondition(sqlCondition{ 217 | Connector: connectTypeOr, 218 | Type: condTypeNull, 219 | Field: field, 220 | }) 221 | } 222 | 223 | func (builder *conditionBuilder) WhereNotNull(field string) Condition { 224 | return builder.WhereCondition(sqlCondition{ 225 | Connector: connectTypeAnd, 226 | Type: condTypeNotNull, 227 | Field: field, 228 | }) 229 | } 230 | 231 | func (builder *conditionBuilder) WhereNull(field string) Condition { 232 | return builder.WhereCondition(sqlCondition{ 233 | Connector: connectTypeAnd, 234 | Type: condTypeNull, 235 | Field: field, 236 | }) 237 | } 238 | 239 | func (builder *conditionBuilder) OrWhereRaw(raw string, items ...any) Condition { 240 | return builder.WhereCondition(sqlCondition{ 241 | Connector: connectTypeOr, 242 | Type: condTypeRaw, 243 | Field: raw, 244 | Values: arrayItems(items), 245 | }) 246 | } 247 | 248 | func (builder *conditionBuilder) WhereRaw(raw string, items ...any) Condition { 249 | return builder.WhereCondition(sqlCondition{ 250 | Connector: connectTypeAnd, 251 | Type: condTypeRaw, 252 | Field: raw, 253 | Values: arrayItems(items), 254 | }) 255 | } 256 | 257 | func (builder *conditionBuilder) OrWhereNotIn(field string, items ...any) Condition { 258 | return builder.WhereCondition(sqlCondition{ 259 | Connector: connectTypeOr, 260 | Type: condTypeNotIn, 261 | Field: field, 262 | Values: items, 263 | }) 264 | } 265 | 266 | func arrayItems(items []any) []any { 267 | if len(items) == 1 { 268 | s := reflect.ValueOf(items[0]) 269 | if s.Kind() == reflect.Slice { 270 | if s.IsNil() { 271 | return []any{} 272 | } 273 | 274 | r := make([]any, s.Len()) 275 | for i := 0; i < s.Len(); i++ { 276 | r[i] = s.Index(i).Interface() 277 | } 278 | 279 | return r 280 | } 281 | } 282 | 283 | return items 284 | } 285 | 286 | func (builder *conditionBuilder) OrWhereIn(field string, items ...any) Condition { 287 | return builder.WhereCondition(sqlCondition{ 288 | Connector: connectTypeOr, 289 | Type: condTypeIn, 290 | Field: field, 291 | Values: arrayItems(items), 292 | }) 293 | } 294 | 295 | func (builder *conditionBuilder) WhereNotIn(field string, items ...any) Condition { 296 | return builder.WhereCondition(sqlCondition{ 297 | Connector: connectTypeAnd, 298 | Type: condTypeNotIn, 299 | Field: field, 300 | Values: arrayItems(items), 301 | }) 302 | } 303 | 304 | func (builder *conditionBuilder) WhereIn(field string, items ...any) Condition { 305 | return builder.WhereCondition(sqlCondition{ 306 | Connector: connectTypeAnd, 307 | Type: condTypeIn, 308 | Field: field, 309 | Values: arrayItems(items), 310 | }) 311 | } 312 | 313 | func (builder *conditionBuilder) WhereGroup(wc ConditionGroup) Condition { 314 | return builder.WhereCondition(sqlCondition{ 315 | Connector: connectTypeAnd, 316 | Type: condTypeGroup, 317 | Nested: wc, 318 | }) 319 | } 320 | 321 | func (builder *conditionBuilder) OrWhereGroup(wc ConditionGroup) Condition { 322 | return builder.WhereCondition(sqlCondition{ 323 | Connector: connectTypeOr, 324 | Type: condTypeGroup, 325 | Nested: wc, 326 | }) 327 | } 328 | 329 | func (builder *conditionBuilder) Where(field string, value ...any) Condition { 330 | argCount := len(value) 331 | 332 | var operator string 333 | var values []any 334 | 335 | if argCount == 1 { 336 | operator = "=" 337 | values = value 338 | } else if argCount == 2 { 339 | o, ok := value[0].(string) 340 | if !ok { 341 | panic("invalid where condition") 342 | } 343 | 344 | operator = o 345 | values = value[1:] 346 | } else { 347 | panic("invalid where condition") 348 | } 349 | 350 | return builder.WhereCondition(sqlCondition{ 351 | Connector: connectTypeAnd, 352 | Type: condTypeSimple, 353 | Field: field, 354 | Operate: operator, 355 | Values: values, 356 | }) 357 | } 358 | 359 | func (builder *conditionBuilder) OrWhere(field string, value ...any) Condition { 360 | argCount := len(value) 361 | 362 | var operator string 363 | var values []any 364 | 365 | if argCount == 1 { 366 | operator = "=" 367 | values = value 368 | } else if argCount == 2 { 369 | o, ok := value[0].(string) 370 | if !ok { 371 | panic("invalid where condition") 372 | } 373 | 374 | operator = o 375 | values = value[1:] 376 | } else { 377 | panic("invalid where condition") 378 | } 379 | 380 | return builder.WhereCondition(sqlCondition{ 381 | Connector: connectTypeOr, 382 | Type: condTypeSimple, 383 | Field: field, 384 | Operate: operator, 385 | Values: values, 386 | }) 387 | } 388 | 389 | func (builder *conditionBuilder) WhereBetween(field string, min, max any) Condition { 390 | return builder.WhereCondition(sqlCondition{ 391 | Connector: connectTypeAnd, 392 | Type: condTypeBetween, 393 | Field: field, 394 | Values: []any{min, max}, 395 | }) 396 | } 397 | 398 | func (builder *conditionBuilder) OrWhereBetween(field string, min, max any) Condition { 399 | return builder.WhereCondition(sqlCondition{ 400 | Connector: connectTypeOr, 401 | Type: condTypeBetween, 402 | Field: field, 403 | Values: []any{min, max}, 404 | }) 405 | } 406 | 407 | func (builder *conditionBuilder) WhereNotBetween(field string, min, max any) Condition { 408 | return builder.WhereCondition(sqlCondition{ 409 | Connector: connectTypeAnd, 410 | Type: condTypeNotBetween, 411 | Field: field, 412 | Values: []any{min, max}, 413 | }) 414 | } 415 | 416 | func (builder *conditionBuilder) OrWhereNotBetween(field string, min, max any) Condition { 417 | return builder.WhereCondition(sqlCondition{ 418 | Connector: connectTypeOr, 419 | Type: condTypeNotBetween, 420 | Field: field, 421 | Values: []any{min, max}, 422 | }) 423 | } 424 | 425 | func (builder *conditionBuilder) WhereCondition(cond sqlCondition) Condition { 426 | if cond.When == nil { 427 | cond.When = func() bool { 428 | return true 429 | } 430 | } 431 | 432 | builder.conditions = append(builder.conditions, cond) 433 | return builder 434 | } 435 | 436 | func (builder *conditionBuilder) Resolve(tableAlias string) (string, []any) { 437 | var result = "" 438 | var params = make([]any, 0) 439 | for i, c := range builder.conditions { 440 | if !c.When() { 441 | continue 442 | } 443 | 444 | connector := c.Connector 445 | if i == 0 { 446 | connector = "" 447 | } 448 | 449 | r, p := builder.resolveCondition(tableAlias, connector, c) 450 | 451 | result += r 452 | params = append(params, p...) 453 | } 454 | 455 | return result, params 456 | } 457 | 458 | func (builder *conditionBuilder) resolveCondition(tableAlias string, connector connectType, c sqlCondition) (string, []any) { 459 | result := "" 460 | params := make([]any, 0) 461 | 462 | switch c.Type { 463 | case condTypeSimple: 464 | if isSubQuery(c.Values) { 465 | s, p := c.Values[0].(SubQuery).ResolveQuery() 466 | params = append(params, p...) 467 | result += fmt.Sprintf(" %s %s %s (%s)", connector, replaceTableField(tableAlias, c.Field), c.Operate, s) 468 | } else { 469 | result += fmt.Sprintf(" %s %s %s ?", connector, replaceTableField(tableAlias, c.Field), c.Operate) 470 | params = append(params, c.Values...) 471 | } 472 | case condTypeColumn: 473 | result += fmt.Sprintf(" %s %s %s %s", connector, replaceTableField(tableAlias, c.Field), c.Operate, replaceTableField(tableAlias, c.Values[0].(string))) 474 | case condTypeRaw: 475 | result += fmt.Sprintf(" %s %s", connector, c.Field) 476 | params = append(params, c.Values...) 477 | case condTypeIn, condTypeNotIn: 478 | operator := "IN" 479 | if c.Type == condTypeNotIn { 480 | operator = "NOT IN" 481 | } 482 | 483 | if isSubQuery(c.Values) { 484 | s, p := c.Values[0].(SubQuery).ResolveQuery() 485 | result += fmt.Sprintf(" %s %s %s (%s)", connector, replaceTableField(tableAlias, c.Field), operator, s) 486 | params = append(params, p...) 487 | } else { 488 | result += fmt.Sprintf( 489 | " %s %s %s (%s)", 490 | connector, 491 | replaceTableField(tableAlias, c.Field), 492 | operator, 493 | strings.Trim(strings.Repeat(", ?", len(c.Values)), ","), 494 | ) 495 | 496 | params = append(params, c.Values...) 497 | } 498 | 499 | case condTypeNull, condTypeNotNull: 500 | if c.Type == condTypeNull { 501 | result += fmt.Sprintf(" %s %s IS NULL", connector, replaceTableField(tableAlias, c.Field)) 502 | } else { 503 | result += fmt.Sprintf(" %s %s IS NOT NULL", connector, replaceTableField(tableAlias, c.Field)) 504 | } 505 | case condTypeExists, condTypeNotExists: 506 | s, p := c.SubQuery.ResolveQuery() 507 | params = append(params, p...) 508 | 509 | if c.Type == condTypeExists { 510 | result += fmt.Sprintf(" %s EXISTS (%s)", connector, s) 511 | } else { 512 | result += fmt.Sprintf(" %s NOT EXISTS (%s)", connector, s) 513 | } 514 | case condTypeGroup: 515 | newBuilder := ConditionBuilder() 516 | c.Nested(newBuilder) 517 | 518 | newCond, newParams := newBuilder.Resolve(tableAlias) 519 | params = append(params, newParams...) 520 | result += fmt.Sprintf(" %s (%s)", connector, newCond) 521 | case condTypeBetween, condTypeNotBetween: 522 | between := "BETWEEN" 523 | if condTypeNotBetween == c.Type { 524 | between = "NOT BETWEEN" 525 | } 526 | 527 | result += fmt.Sprintf(" %s %s %s ? AND ?", connector, replaceTableField(tableAlias, c.Field), between) 528 | params = append(params, c.Values...) 529 | } 530 | return result, params 531 | } 532 | -------------------------------------------------------------------------------- /query/builder.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type SQLBuilder struct { 9 | tableName string 10 | conditions Condition 11 | fields []Expr 12 | limit int64 13 | offset int64 14 | orders orderBys 15 | groups []string 16 | having ConditionGroup 17 | joins []sqlJoin 18 | unions []sqlUnion 19 | } 20 | 21 | type sqlUnion struct { 22 | Type string 23 | SubQuery SubQuery 24 | } 25 | 26 | type Expr struct { 27 | Value string 28 | Type exprType 29 | Bindings []interface{} 30 | } 31 | 32 | type exprType int 33 | 34 | const ( 35 | exprTypeString exprType = iota 36 | exprTypeRaw 37 | ) 38 | 39 | func Raw(rawStr string, bindings ...interface{}) Expr { 40 | return Expr{ 41 | Type: exprTypeRaw, 42 | Value: rawStr, 43 | Bindings: bindings, 44 | } 45 | } 46 | 47 | func Builder() SQLBuilder { 48 | return SQLBuilder{ 49 | conditions: ConditionBuilder(), 50 | fields: make([]Expr, 0), 51 | orders: make([]sqlOrderBy, 0), 52 | groups: make([]string, 0), 53 | joins: make([]sqlJoin, 0), 54 | unions: make([]sqlUnion, 0), 55 | limit: -1, 56 | offset: -1, 57 | } 58 | } 59 | 60 | func (builder SQLBuilder) Merge(b2s ...SQLBuilder) SQLBuilder { 61 | b := builder.Clone() 62 | for _, b2 := range b2s { 63 | b.conditions.Append(b2.conditions) 64 | b.fields = append(b.fields, b2.fields...) 65 | b.orders = append(b.orders, b2.orders...) 66 | b.groups = append(b.groups, b2.groups...) 67 | b.joins = append(b.joins, b2.joins...) 68 | b.unions = append(b.unions, b2.unions...) 69 | 70 | if b2.limit != -1 { 71 | b.limit = b2.limit 72 | } 73 | 74 | if b2.offset != -1 { 75 | b.offset = b2.offset 76 | } 77 | } 78 | 79 | return b 80 | } 81 | 82 | func (builder SQLBuilder) Clone() SQLBuilder { 83 | b := SQLBuilder{ 84 | conditions: builder.conditions.Clone(), 85 | tableName: builder.tableName, 86 | limit: builder.limit, 87 | offset: builder.offset, 88 | having: builder.having, 89 | fields: append([]Expr{}, builder.fields...), 90 | orders: append([]sqlOrderBy{}, builder.orders...), 91 | groups: append([]string{}, builder.groups...), 92 | joins: append([]sqlJoin{}, builder.joins...), 93 | unions: append([]sqlUnion{}, builder.unions...), 94 | } 95 | 96 | return b 97 | } 98 | 99 | type KV map[string]interface{} 100 | 101 | func (builder SQLBuilder) ResolveDelete() (string, []interface{}) { 102 | sqlStr := fmt.Sprintf("DELETE FROM %s", builder.tableName) 103 | values := make([]interface{}, 0) 104 | 105 | tableAlias := resolveTableAlias(builder.tableName) 106 | if !builder.conditions.Empty() { 107 | conditions, p := builder.conditions.Resolve(tableAlias) 108 | sqlStr += fmt.Sprintf(" WHERE %s", strings.TrimLeft(conditions, " ")) 109 | values = append(values, p...) 110 | } 111 | 112 | if len(builder.orders) > 0 { 113 | sqlStr += fmt.Sprintf(" ORDER BY %s", builder.orders.String(tableAlias)) 114 | } 115 | 116 | if builder.limit >= 0 { 117 | sqlStr += fmt.Sprintf(" LIMIT %d", builder.limit) 118 | } 119 | 120 | return sqlStr, values 121 | } 122 | 123 | func (builder SQLBuilder) ResolveUpdate(kvPairs KV) (string, []interface{}) { 124 | 125 | sqlStr := fmt.Sprintf("UPDATE %s", builder.tableName) 126 | 127 | fields, values := builder.resolveKvPairsForUpdate(kvPairs) 128 | sqlStr += fmt.Sprintf(" SET %s", strings.Join(fields, ", ")) 129 | 130 | if !builder.conditions.Empty() { 131 | conditions, p := builder.conditions.Resolve(resolveTableAlias(builder.tableName)) 132 | sqlStr += fmt.Sprintf(" WHERE %s", strings.TrimLeft(conditions, " ")) 133 | values = append(values, p...) 134 | } 135 | 136 | return sqlStr, values 137 | } 138 | 139 | func (builder SQLBuilder) resolveKvPairsForUpdate(kvPairs KV) ([]string, []interface{}) { 140 | fields := make([]string, len(kvPairs)) 141 | values := make([]interface{}, 0) 142 | var i = 0 143 | for k, v := range kvPairs { 144 | vv, ok := v.(Expr) 145 | if ok { 146 | switch vv.Type { 147 | case exprTypeString: 148 | fields[i] = "`" + k + "` = ?" 149 | values = append(values, vv.Value) 150 | case exprTypeRaw: 151 | fields[i] = "`" + k + "` = " + vv.Value 152 | values = append(values, vv.Bindings...) 153 | } 154 | } else { 155 | fields[i] = "`" + k + "` = ?" 156 | values = append(values, v) 157 | } 158 | 159 | i++ 160 | } 161 | return fields, values 162 | } 163 | 164 | func (builder SQLBuilder) resolveKvPairsForInsert(kvPairs KV) ([]string, []interface{}) { 165 | fields := make([]string, len(kvPairs)) 166 | values := make([]interface{}, 0) 167 | var i = 0 168 | for k, v := range kvPairs { 169 | fields[i] = "`" + k + "`" 170 | 171 | vv, ok := v.(Expr) 172 | if ok { 173 | values = append(values, vv.Value) 174 | if vv.Bindings != nil && len(vv.Bindings) > 0 { 175 | values = append(values, vv.Bindings...) 176 | } 177 | } else { 178 | values = append(values, v) 179 | } 180 | 181 | i++ 182 | } 183 | return fields, values 184 | } 185 | 186 | func (builder SQLBuilder) ResolveInsert(kvPairs KV) (string, []interface{}) { 187 | sqlStr := fmt.Sprintf("INSERT INTO %s", builder.tableName) 188 | 189 | fields, values := builder.resolveKvPairsForInsert(kvPairs) 190 | sqlStr += fmt.Sprintf(" (%s) VALUES (%s)", strings.Join(fields, ", "), strings.Trim(strings.Repeat("?,", len(values)), ",")) 191 | 192 | return sqlStr, values 193 | } 194 | 195 | func (builder SQLBuilder) ResolveCount() (string, []interface{}) { 196 | b := builder.Clone() 197 | b.fields = make([]Expr, 1) 198 | b.fields[0] = Raw("COUNT(1) as count") 199 | 200 | return b.ResolveQuery() 201 | } 202 | 203 | func (builder SQLBuilder) ResolveMax(field string) (string, []interface{}) { 204 | b := builder.Clone() 205 | b.fields = make([]Expr, 1) 206 | b.fields[0] = Raw(fmt.Sprintf("MAX(%s) as max", field)) 207 | 208 | return b.ResolveQuery() 209 | } 210 | 211 | func (builder SQLBuilder) ResolveMin(field string) (string, []interface{}) { 212 | b := builder.Clone() 213 | b.fields = make([]Expr, 1) 214 | b.fields[0] = Raw(fmt.Sprintf("MIN(%s) as min", field)) 215 | 216 | return b.ResolveQuery() 217 | } 218 | 219 | func (builder SQLBuilder) ResolveAvg(field string) (string, []interface{}) { 220 | b := builder.Clone() 221 | b.fields = make([]Expr, 1) 222 | b.fields[0] = Raw(fmt.Sprintf("AVG(%s) as avg", field)) 223 | 224 | return b.ResolveQuery() 225 | } 226 | 227 | func (builder SQLBuilder) ResolveSum(field string) (string, []interface{}) { 228 | b := builder.Clone() 229 | b.fields = make([]Expr, 1) 230 | b.fields[0] = Raw(fmt.Sprintf("SUM(%s) as sum", field)) 231 | 232 | return b.ResolveQuery() 233 | } 234 | 235 | func (builder SQLBuilder) ResolveQuery() (string, []interface{}) { 236 | tableAlias := resolveTableAlias(builder.tableName) 237 | 238 | params := make([]interface{}, 0) 239 | 240 | fields, p := builder.getFields(tableAlias) 241 | params = append(params, p...) 242 | sqlStr := fmt.Sprintf("SELECT %s FROM %s", fields, builder.tableName) 243 | 244 | if len(builder.joins) > 0 { 245 | for _, j := range builder.joins { 246 | s, p := j.String(tableAlias) 247 | sqlStr += " " + s 248 | params = append(params, p...) 249 | } 250 | } 251 | 252 | if !builder.conditions.Empty() { 253 | conditions, p := builder.conditions.Resolve(tableAlias) 254 | sqlStr += fmt.Sprintf(" WHERE %s", strings.TrimLeft(conditions, " ")) 255 | params = append(params, p...) 256 | } 257 | 258 | if len(builder.groups) > 0 { 259 | var groupBys = make([]string, len(builder.groups)) 260 | for i, g := range builder.groups { 261 | groupBys[i] = replaceTableField(tableAlias, g) 262 | } 263 | sqlStr += fmt.Sprintf(" GROUP BY %s", strings.Join(groupBys, ", ")) 264 | } 265 | 266 | if builder.having != nil { 267 | newBuilder := ConditionBuilder() 268 | builder.having(newBuilder) 269 | 270 | havingCond, havingParams := newBuilder.Resolve(tableAlias) 271 | if havingCond != "" { 272 | sqlStr += fmt.Sprintf(" HAVING %s", havingCond) 273 | params = append(params, havingParams...) 274 | } 275 | } 276 | 277 | if len(builder.orders) > 0 { 278 | sqlStr += fmt.Sprintf(" ORDER BY %s", builder.orders.String(tableAlias)) 279 | } 280 | 281 | if builder.limit >= 0 { 282 | sqlStr += fmt.Sprintf(" LIMIT %d", builder.limit) 283 | } 284 | if builder.offset >= 0 { 285 | sqlStr += fmt.Sprintf(" OFFSET %d", builder.offset) 286 | } 287 | 288 | if len(builder.unions) > 0 { 289 | sqlStr = "(" + sqlStr + ")" 290 | for _, u := range builder.unions { 291 | s, p := u.SubQuery.ResolveQuery() 292 | sqlStr += fmt.Sprintf(" UNION %s (%s)", u.Type, s) 293 | params = append(params, p...) 294 | } 295 | } 296 | 297 | return sqlStr, params 298 | } 299 | 300 | // GetFields return all fields for query 301 | func (builder SQLBuilder) GetFields() []Expr { 302 | return builder.fields 303 | } 304 | 305 | // Fields set fields for query 306 | func (builder SQLBuilder) Fields(fields ...Expr) SQLBuilder { 307 | b := builder.Clone() 308 | b.fields = fields 309 | 310 | return b 311 | } 312 | 313 | func (builder SQLBuilder) Condition(where Condition) SQLBuilder { 314 | b := builder.Clone() 315 | b.conditions = where 316 | 317 | return b 318 | } 319 | 320 | func (builder SQLBuilder) Table(name string) SQLBuilder { 321 | b := builder.Clone() 322 | b.tableName = name 323 | 324 | return b 325 | } 326 | 327 | func (builder SQLBuilder) LeftJoin(tableName string, on ConditionGroup) SQLBuilder { 328 | return builder.join(sqlJoin{ 329 | joinType: "LEFT JOIN", 330 | tableName: tableName, 331 | on: on, 332 | }) 333 | } 334 | 335 | func (builder SQLBuilder) RightJoin(tableName string, on ConditionGroup) SQLBuilder { 336 | return builder.join(sqlJoin{ 337 | joinType: "RIGHT JOIN", 338 | tableName: tableName, 339 | on: on, 340 | }) 341 | } 342 | 343 | func (builder SQLBuilder) InnerJoin(tableName string, on ConditionGroup) SQLBuilder { 344 | return builder.join(sqlJoin{ 345 | joinType: "INNER JOIN", 346 | tableName: tableName, 347 | on: on, 348 | }) 349 | } 350 | 351 | func (builder SQLBuilder) CrossJoin(tableName string, on ConditionGroup) SQLBuilder { 352 | return builder.join(sqlJoin{ 353 | joinType: "CROSS JOIN", 354 | tableName: tableName, 355 | on: on, 356 | }) 357 | } 358 | 359 | func (builder SQLBuilder) join(join sqlJoin) SQLBuilder { 360 | b := builder.Clone() 361 | b.joins = append(builder.joins, join) 362 | return b 363 | } 364 | 365 | func (builder SQLBuilder) Union(b2 SubQuery, distinct bool) SQLBuilder { 366 | union := sqlUnion{ 367 | SubQuery: b2, 368 | } 369 | 370 | if distinct { 371 | union.Type = "DISTINCT" 372 | } else { 373 | union.Type = "ALL" 374 | } 375 | 376 | b := builder.Clone() 377 | b.unions = append(builder.unions, union) 378 | 379 | return b 380 | } 381 | 382 | func (builder SQLBuilder) Select(fields ...interface{}) SQLBuilder { 383 | b := builder.Clone() 384 | for _, f := range fields { 385 | f1, ok := f.(Expr) 386 | if ok { 387 | b.fields = append(b.fields, f1) 388 | } else { 389 | b.fields = append(b.fields, Expr{ 390 | Type: exprTypeString, 391 | Value: f.(string), 392 | }) 393 | } 394 | } 395 | return b 396 | } 397 | 398 | func (builder SQLBuilder) Limit(limit int64) SQLBuilder { 399 | b := builder.Clone() 400 | b.limit = limit 401 | return b 402 | } 403 | 404 | func (builder SQLBuilder) Offset(offset int64) SQLBuilder { 405 | b := builder.Clone() 406 | b.offset = offset 407 | return b 408 | } 409 | 410 | func (builder SQLBuilder) OrderBy(field string, direction string) SQLBuilder { 411 | b := builder.Clone() 412 | b.orders = append(builder.orders, sqlOrderBy{raw: false, Field: field, Direction: direction}) 413 | return b 414 | } 415 | 416 | func (builder SQLBuilder) OrderByRaw(raw string) SQLBuilder { 417 | b := builder.Clone() 418 | b.orders = append(builder.orders, sqlOrderBy{raw: true, Raw: raw}) 419 | return b 420 | } 421 | 422 | func (builder SQLBuilder) GroupBy(fields ...string) SQLBuilder { 423 | b := builder.Clone() 424 | b.groups = append(builder.groups, fields...) 425 | return b 426 | } 427 | 428 | func (builder SQLBuilder) Having(closure ConditionGroup) SQLBuilder { 429 | b := builder.Clone() 430 | b.having = closure 431 | return b 432 | } 433 | 434 | func (builder SQLBuilder) getFields(tableAlias string) (string, []interface{}) { 435 | var params = make([]interface{}, 0) 436 | if len(builder.fields) == 0 { 437 | return "*", params 438 | } 439 | 440 | fields := make([]string, len(builder.fields)) 441 | for i, f := range builder.fields { 442 | switch f.Type { 443 | case exprTypeString: 444 | fields[i] = replaceTableField(tableAlias, f.Value) 445 | case exprTypeRaw: 446 | fields[i] = f.Value 447 | params = append(params, f.Bindings...) 448 | } 449 | } 450 | 451 | return strings.Join(fields, ", "), params 452 | } 453 | 454 | func (builder SQLBuilder) WhereColumn(field, operator string, value string) SQLBuilder { 455 | b := builder.Clone() 456 | b.conditions.WhereColumn(field, operator, value) 457 | 458 | return b 459 | } 460 | 461 | func (builder SQLBuilder) OrWhereColumn(field, operator string, value string) SQLBuilder { 462 | b := builder.Clone() 463 | b.conditions.OrWhereColumn(field, operator, value) 464 | 465 | return b 466 | } 467 | 468 | func (builder SQLBuilder) OrWhereNotExist(subQuery SubQuery) SQLBuilder { 469 | b := builder.Clone() 470 | b.conditions.OrWhereNotExist(subQuery) 471 | 472 | return b 473 | } 474 | 475 | func (builder SQLBuilder) OrWhereExist(subQuery SubQuery) SQLBuilder { 476 | b := builder.Clone() 477 | b.conditions.OrWhereExist(subQuery) 478 | 479 | return b 480 | } 481 | 482 | func (builder SQLBuilder) WhereNotExist(subQuery SubQuery) SQLBuilder { 483 | b := builder.Clone() 484 | b.conditions.WhereNotExist(subQuery) 485 | 486 | return b 487 | } 488 | 489 | func (builder SQLBuilder) WhereExist(subQuery SubQuery) SQLBuilder { 490 | b := builder.Clone() 491 | b.conditions.WhereExist(subQuery) 492 | 493 | return b 494 | } 495 | 496 | func (builder SQLBuilder) OrWhereNotNull(field string) SQLBuilder { 497 | b := builder.Clone() 498 | b.conditions.OrWhereNotNull(field) 499 | 500 | return b 501 | } 502 | 503 | func (builder SQLBuilder) OrWhereNull(field string) SQLBuilder { 504 | b := builder.Clone() 505 | b.conditions.OrWhereNull(field) 506 | 507 | return b 508 | } 509 | 510 | func (builder SQLBuilder) WhereNotNull(field string) SQLBuilder { 511 | b := builder.Clone() 512 | b.conditions.WhereNotNull(field) 513 | 514 | return b 515 | } 516 | 517 | func (builder SQLBuilder) WhereNull(field string) SQLBuilder { 518 | b := builder.Clone() 519 | b.conditions.WhereNull(field) 520 | 521 | return b 522 | } 523 | 524 | func (builder SQLBuilder) OrWhereRaw(raw string, items ...interface{}) SQLBuilder { 525 | b := builder.Clone() 526 | b.conditions.OrWhereRaw(raw, items...) 527 | 528 | return b 529 | } 530 | 531 | func (builder SQLBuilder) WhereRaw(raw string, items ...interface{}) SQLBuilder { 532 | b := builder.Clone() 533 | b.conditions.WhereRaw(raw, items...) 534 | 535 | return b 536 | } 537 | 538 | func (builder SQLBuilder) OrWhereNotIn(field string, items ...interface{}) SQLBuilder { 539 | b := builder.Clone() 540 | b.conditions.OrWhereNotIn(field, items...) 541 | 542 | return b 543 | } 544 | 545 | func (builder SQLBuilder) OrWhereIn(field string, items ...interface{}) SQLBuilder { 546 | b := builder.Clone() 547 | b.conditions.OrWhereIn(field, items...) 548 | 549 | return b 550 | } 551 | 552 | func (builder SQLBuilder) WhereNotIn(field string, items ...interface{}) SQLBuilder { 553 | b := builder.Clone() 554 | b.conditions.WhereNotIn(field, items...) 555 | 556 | return b 557 | } 558 | 559 | func (builder SQLBuilder) WhereIn(field string, items ...interface{}) SQLBuilder { 560 | b := builder.Clone() 561 | b.conditions.WhereIn(field, items...) 562 | 563 | return b 564 | } 565 | 566 | func (builder SQLBuilder) WhereGroup(wc ConditionGroup) SQLBuilder { 567 | b := builder.Clone() 568 | b.conditions.WhereGroup(wc) 569 | 570 | return b 571 | } 572 | 573 | func (builder SQLBuilder) OrWhereGroup(wc ConditionGroup) SQLBuilder { 574 | b := builder.Clone() 575 | b.conditions.OrWhereGroup(wc) 576 | 577 | return b 578 | } 579 | 580 | func (builder SQLBuilder) Where(field string, value ...interface{}) SQLBuilder { 581 | b := builder.Clone() 582 | b.conditions.Where(field, value...) 583 | 584 | return b 585 | } 586 | 587 | func (builder SQLBuilder) OrWhere(field string, value ...interface{}) SQLBuilder { 588 | b := builder.Clone() 589 | b.conditions.OrWhere(field, value...) 590 | 591 | return b 592 | } 593 | 594 | func (builder SQLBuilder) When(when When, cg ConditionGroup) SQLBuilder { 595 | b := builder.Clone() 596 | b.conditions.When(when, cg) 597 | 598 | return b 599 | } 600 | 601 | func (builder SQLBuilder) OrWhen(when When, cg ConditionGroup) SQLBuilder { 602 | b := builder.Clone() 603 | b.conditions.OrWhen(when, cg) 604 | 605 | return b 606 | } 607 | 608 | func (builder SQLBuilder) WhereBetween(field string, min interface{}, max interface{}) SQLBuilder { 609 | b := builder.Clone() 610 | b.conditions.WhereBetween(field, min, max) 611 | 612 | return b 613 | } 614 | 615 | func (builder SQLBuilder) OrWhereBetween(field string, min interface{}, max interface{}) SQLBuilder { 616 | b := builder.Clone() 617 | b.conditions.OrWhereBetween(field, min, max) 618 | 619 | return b 620 | } 621 | 622 | func (builder SQLBuilder) WhereNotBetween(field string, min interface{}, max interface{}) SQLBuilder { 623 | b := builder.Clone() 624 | b.conditions.WhereNotBetween(field, min, max) 625 | 626 | return b 627 | } 628 | 629 | func (builder SQLBuilder) OrWhereNotBetween(field string, min interface{}, max interface{}) SQLBuilder { 630 | b := builder.Clone() 631 | b.conditions.OrWhereNotBetween(field, min, max) 632 | 633 | return b 634 | } 635 | 636 | func (builder SQLBuilder) AppendCondition(cond Condition) SQLBuilder { 637 | b := builder.Clone() 638 | b.conditions.Append(cond) 639 | 640 | return b 641 | } 642 | -------------------------------------------------------------------------------- /migrate/migrations.orm.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | // !!! DO NOT EDIT THIS FILE 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "github.com/iancoleman/strcase" 9 | "github.com/mylxsw/eloquent/query" 10 | "gopkg.in/guregu/null.v3" 11 | ) 12 | 13 | func init() { 14 | 15 | } 16 | 17 | // MigrationsN is a Migrations object, all fields are nullable 18 | type MigrationsN struct { 19 | original *migrationsOriginal 20 | migrationsModel *MigrationsModel 21 | 22 | Version null.String 23 | Migration null.String 24 | Table null.String 25 | Batch null.Int 26 | Id null.Int 27 | } 28 | 29 | // As convert object to other type 30 | // dst must be a pointer to struct 31 | func (inst *MigrationsN) As(dst interface{}) error { 32 | return query.Copy(inst, dst) 33 | } 34 | 35 | // SetModel set model for Migrations 36 | func (inst *MigrationsN) SetModel(migrationsModel *MigrationsModel) { 37 | inst.migrationsModel = migrationsModel 38 | } 39 | 40 | // migrationsOriginal is an object which stores original Migrations from database 41 | type migrationsOriginal struct { 42 | Version null.String 43 | Migration null.String 44 | Table null.String 45 | Batch null.Int 46 | Id null.Int 47 | } 48 | 49 | // Staled identify whether the object has been modified 50 | func (inst *MigrationsN) Staled(onlyFields ...string) bool { 51 | if inst.original == nil { 52 | inst.original = &migrationsOriginal{} 53 | } 54 | 55 | if len(onlyFields) == 0 { 56 | 57 | if inst.Version != inst.original.Version { 58 | return true 59 | } 60 | if inst.Migration != inst.original.Migration { 61 | return true 62 | } 63 | if inst.Table != inst.original.Table { 64 | return true 65 | } 66 | if inst.Batch != inst.original.Batch { 67 | return true 68 | } 69 | if inst.Id != inst.original.Id { 70 | return true 71 | } 72 | } else { 73 | for _, f := range onlyFields { 74 | switch strcase.ToSnake(f) { 75 | 76 | case "version": 77 | if inst.Version != inst.original.Version { 78 | return true 79 | } 80 | case "migration": 81 | if inst.Migration != inst.original.Migration { 82 | return true 83 | } 84 | case "table": 85 | if inst.Table != inst.original.Table { 86 | return true 87 | } 88 | case "batch": 89 | if inst.Batch != inst.original.Batch { 90 | return true 91 | } 92 | case "id": 93 | if inst.Id != inst.original.Id { 94 | return true 95 | } 96 | default: 97 | } 98 | } 99 | } 100 | 101 | return false 102 | } 103 | 104 | // StaledKV return all fields has been modified 105 | func (inst *MigrationsN) StaledKV(onlyFields ...string) query.KV { 106 | kv := make(query.KV, 0) 107 | 108 | if inst.original == nil { 109 | inst.original = &migrationsOriginal{} 110 | } 111 | 112 | if len(onlyFields) == 0 { 113 | 114 | if inst.Version != inst.original.Version { 115 | kv["version"] = inst.Version 116 | } 117 | if inst.Migration != inst.original.Migration { 118 | kv["migration"] = inst.Migration 119 | } 120 | if inst.Table != inst.original.Table { 121 | kv["table"] = inst.Table 122 | } 123 | if inst.Batch != inst.original.Batch { 124 | kv["batch"] = inst.Batch 125 | } 126 | if inst.Id != inst.original.Id { 127 | kv["id"] = inst.Id 128 | } 129 | } else { 130 | for _, f := range onlyFields { 131 | switch strcase.ToSnake(f) { 132 | 133 | case "version": 134 | if inst.Version != inst.original.Version { 135 | kv["version"] = inst.Version 136 | } 137 | case "migration": 138 | if inst.Migration != inst.original.Migration { 139 | kv["migration"] = inst.Migration 140 | } 141 | case "table": 142 | if inst.Table != inst.original.Table { 143 | kv["table"] = inst.Table 144 | } 145 | case "batch": 146 | if inst.Batch != inst.original.Batch { 147 | kv["batch"] = inst.Batch 148 | } 149 | case "id": 150 | if inst.Id != inst.original.Id { 151 | kv["id"] = inst.Id 152 | } 153 | default: 154 | } 155 | } 156 | } 157 | 158 | return kv 159 | } 160 | 161 | // Save create a new model or update it 162 | func (inst *MigrationsN) Save(ctx context.Context, onlyFields ...string) error { 163 | if inst.migrationsModel == nil { 164 | return query.ErrModelNotSet 165 | } 166 | 167 | id, _, err := inst.migrationsModel.SaveOrUpdate(ctx, *inst, onlyFields...) 168 | if err != nil { 169 | return err 170 | } 171 | 172 | inst.Id = null.IntFrom(id) 173 | return nil 174 | } 175 | 176 | // Delete remove a migrations 177 | func (inst *MigrationsN) Delete(ctx context.Context) error { 178 | if inst.migrationsModel == nil { 179 | return query.ErrModelNotSet 180 | } 181 | 182 | _, err := inst.migrationsModel.DeleteById(ctx, inst.Id.Int64) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | return nil 188 | } 189 | 190 | // String convert instance to json string 191 | func (inst *MigrationsN) String() string { 192 | rs, _ := json.Marshal(inst) 193 | return string(rs) 194 | } 195 | 196 | type migrationsScope struct { 197 | name string 198 | apply func(builder query.Condition) 199 | } 200 | 201 | var migrationsGlobalScopes = make([]migrationsScope, 0) 202 | var migrationsLocalScopes = make([]migrationsScope, 0) 203 | 204 | // AddGlobalScopeForMigrations assign a global scope to a model 205 | func AddGlobalScopeForMigrations(name string, apply func(builder query.Condition)) { 206 | migrationsGlobalScopes = append(migrationsGlobalScopes, migrationsScope{name: name, apply: apply}) 207 | } 208 | 209 | // AddLocalScopeForMigrations assign a local scope to a model 210 | func AddLocalScopeForMigrations(name string, apply func(builder query.Condition)) { 211 | migrationsLocalScopes = append(migrationsLocalScopes, migrationsScope{name: name, apply: apply}) 212 | } 213 | 214 | func (m *MigrationsModel) applyScope() query.Condition { 215 | scopeCond := query.ConditionBuilder() 216 | for _, g := range migrationsGlobalScopes { 217 | if m.globalScopeEnabled(g.name) { 218 | g.apply(scopeCond) 219 | } 220 | } 221 | 222 | for _, s := range migrationsLocalScopes { 223 | if m.localScopeEnabled(s.name) { 224 | s.apply(scopeCond) 225 | } 226 | } 227 | 228 | return scopeCond 229 | } 230 | 231 | func (m *MigrationsModel) localScopeEnabled(name string) bool { 232 | for _, n := range m.includeLocalScopes { 233 | if name == n { 234 | return true 235 | } 236 | } 237 | 238 | return false 239 | } 240 | 241 | func (m *MigrationsModel) globalScopeEnabled(name string) bool { 242 | for _, n := range m.excludeGlobalScopes { 243 | if name == n { 244 | return false 245 | } 246 | } 247 | 248 | return true 249 | } 250 | 251 | type Migrations struct { 252 | Version string 253 | Migration string 254 | Table string 255 | Batch int64 256 | Id int64 257 | } 258 | 259 | func (w Migrations) ToMigrationsN(allows ...string) MigrationsN { 260 | if len(allows) == 0 { 261 | return MigrationsN{ 262 | 263 | Version: null.StringFrom(w.Version), 264 | Migration: null.StringFrom(w.Migration), 265 | Table: null.StringFrom(w.Table), 266 | Batch: null.IntFrom(int64(w.Batch)), 267 | Id: null.IntFrom(int64(w.Id)), 268 | } 269 | } 270 | 271 | res := MigrationsN{} 272 | for _, al := range allows { 273 | switch strcase.ToSnake(al) { 274 | 275 | case "version": 276 | res.Version = null.StringFrom(w.Version) 277 | case "migration": 278 | res.Migration = null.StringFrom(w.Migration) 279 | case "table": 280 | res.Table = null.StringFrom(w.Table) 281 | case "batch": 282 | res.Batch = null.IntFrom(int64(w.Batch)) 283 | case "id": 284 | res.Id = null.IntFrom(int64(w.Id)) 285 | default: 286 | } 287 | } 288 | 289 | return res 290 | } 291 | 292 | // As convert object to other type 293 | // dst must be a pointer to struct 294 | func (w Migrations) As(dst interface{}) error { 295 | return query.Copy(w, dst) 296 | } 297 | 298 | func (w *MigrationsN) ToMigrations() Migrations { 299 | return Migrations{ 300 | 301 | Version: w.Version.String, 302 | Migration: w.Migration.String, 303 | Table: w.Table.String, 304 | Batch: w.Batch.Int64, 305 | Id: w.Id.Int64, 306 | } 307 | } 308 | 309 | // MigrationsModel is a model which encapsulates the operations of the object 310 | type MigrationsModel struct { 311 | db *query.DatabaseWrap 312 | tableName string 313 | 314 | excludeGlobalScopes []string 315 | includeLocalScopes []string 316 | 317 | query query.SQLBuilder 318 | } 319 | 320 | var migrationsTableName = "migrations" 321 | 322 | // MigrationsTable return table name for Migrations 323 | func MigrationsTable() string { 324 | return migrationsTableName 325 | } 326 | 327 | const ( 328 | FieldMigrationsVersion = "version" 329 | FieldMigrationsMigration = "migration" 330 | FieldMigrationsTable = "table" 331 | FieldMigrationsBatch = "batch" 332 | FieldMigrationsId = "id" 333 | ) 334 | 335 | // MigrationsFields return all fields in Migrations model 336 | func MigrationsFields() []string { 337 | return []string{ 338 | "version", 339 | "migration", 340 | "table", 341 | "batch", 342 | "id", 343 | } 344 | } 345 | 346 | func SetMigrationsTable(tableName string) { 347 | migrationsTableName = tableName 348 | } 349 | 350 | // NewMigrationsModel create a MigrationsModel 351 | func NewMigrationsModel(db query.Database) *MigrationsModel { 352 | return &MigrationsModel{ 353 | db: query.NewDatabaseWrap(db), 354 | tableName: migrationsTableName, 355 | excludeGlobalScopes: make([]string, 0), 356 | includeLocalScopes: make([]string, 0), 357 | query: query.Builder(), 358 | } 359 | } 360 | 361 | // GetDB return database instance 362 | func (m *MigrationsModel) GetDB() query.Database { 363 | return m.db.GetDB() 364 | } 365 | 366 | func (m *MigrationsModel) clone() *MigrationsModel { 367 | return &MigrationsModel{ 368 | db: m.db, 369 | tableName: m.tableName, 370 | excludeGlobalScopes: append([]string{}, m.excludeGlobalScopes...), 371 | includeLocalScopes: append([]string{}, m.includeLocalScopes...), 372 | query: m.query, 373 | } 374 | } 375 | 376 | // WithoutGlobalScopes remove a global scope for given query 377 | func (m *MigrationsModel) WithoutGlobalScopes(names ...string) *MigrationsModel { 378 | mc := m.clone() 379 | mc.excludeGlobalScopes = append(mc.excludeGlobalScopes, names...) 380 | 381 | return mc 382 | } 383 | 384 | // WithLocalScopes add a local scope for given query 385 | func (m *MigrationsModel) WithLocalScopes(names ...string) *MigrationsModel { 386 | mc := m.clone() 387 | mc.includeLocalScopes = append(mc.includeLocalScopes, names...) 388 | 389 | return mc 390 | } 391 | 392 | // Condition add query builder to model 393 | func (m *MigrationsModel) Condition(builder query.SQLBuilder) *MigrationsModel { 394 | mm := m.clone() 395 | mm.query = mm.query.Merge(builder) 396 | 397 | return mm 398 | } 399 | 400 | // Find retrieve a model by its primary key 401 | func (m *MigrationsModel) Find(ctx context.Context, id int64) (*MigrationsN, error) { 402 | return m.First(ctx, m.query.Where("id", "=", id)) 403 | } 404 | 405 | // Exists return whether the records exists for a given query 406 | func (m *MigrationsModel) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 407 | count, err := m.Count(ctx, builders...) 408 | return count > 0, err 409 | } 410 | 411 | // Count return model count for a given query 412 | func (m *MigrationsModel) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 413 | sqlStr, params := m.query. 414 | Merge(builders...). 415 | Table(m.tableName). 416 | AppendCondition(m.applyScope()). 417 | ResolveCount() 418 | 419 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 420 | if err != nil { 421 | return 0, err 422 | } 423 | 424 | defer rows.Close() 425 | 426 | rows.Next() 427 | var res int64 428 | if err := rows.Scan(&res); err != nil { 429 | return 0, err 430 | } 431 | 432 | return res, nil 433 | } 434 | 435 | func (m *MigrationsModel) Paginate(ctx context.Context, page int64, perPage int64, builders ...query.SQLBuilder) ([]MigrationsN, query.PaginateMeta, error) { 436 | if page <= 0 { 437 | page = 1 438 | } 439 | 440 | if perPage <= 0 { 441 | perPage = 15 442 | } 443 | 444 | meta := query.PaginateMeta{ 445 | PerPage: perPage, 446 | Page: page, 447 | } 448 | 449 | count, err := m.Count(ctx, builders...) 450 | if err != nil { 451 | return nil, meta, err 452 | } 453 | 454 | meta.Total = count 455 | meta.LastPage = count / perPage 456 | if count%perPage != 0 { 457 | meta.LastPage += 1 458 | } 459 | 460 | res, err := m.Get(ctx, append([]query.SQLBuilder{query.Builder().Limit(perPage).Offset((page - 1) * perPage)}, builders...)...) 461 | if err != nil { 462 | return res, meta, err 463 | } 464 | 465 | return res, meta, nil 466 | } 467 | 468 | // Get retrieve all results for given query 469 | func (m *MigrationsModel) Get(ctx context.Context, builders ...query.SQLBuilder) ([]MigrationsN, error) { 470 | b := m.query.Merge(builders...).Table(m.tableName).AppendCondition(m.applyScope()) 471 | if len(b.GetFields()) == 0 { 472 | b = b.Select( 473 | "version", 474 | "migration", 475 | "table", 476 | "batch", 477 | "id", 478 | ) 479 | } 480 | 481 | fields := b.GetFields() 482 | selectFields := make([]query.Expr, 0) 483 | 484 | for _, f := range fields { 485 | switch strcase.ToSnake(f.Value) { 486 | 487 | case "version": 488 | selectFields = append(selectFields, f) 489 | case "migration": 490 | selectFields = append(selectFields, f) 491 | case "table": 492 | selectFields = append(selectFields, f) 493 | case "batch": 494 | selectFields = append(selectFields, f) 495 | case "id": 496 | selectFields = append(selectFields, f) 497 | } 498 | } 499 | 500 | var createScanVar = func(fields []query.Expr) (*MigrationsN, []interface{}) { 501 | var migrationsVar MigrationsN 502 | scanFields := make([]interface{}, 0) 503 | 504 | for _, f := range fields { 505 | switch strcase.ToSnake(f.Value) { 506 | 507 | case "version": 508 | scanFields = append(scanFields, &migrationsVar.Version) 509 | case "migration": 510 | scanFields = append(scanFields, &migrationsVar.Migration) 511 | case "table": 512 | scanFields = append(scanFields, &migrationsVar.Table) 513 | case "batch": 514 | scanFields = append(scanFields, &migrationsVar.Batch) 515 | case "id": 516 | scanFields = append(scanFields, &migrationsVar.Id) 517 | } 518 | } 519 | 520 | return &migrationsVar, scanFields 521 | } 522 | 523 | sqlStr, params := b.Fields(selectFields...).ResolveQuery() 524 | 525 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 526 | if err != nil { 527 | return nil, err 528 | } 529 | 530 | defer rows.Close() 531 | 532 | migrationss := make([]MigrationsN, 0) 533 | for rows.Next() { 534 | migrationsReal, scanFields := createScanVar(fields) 535 | if err := rows.Scan(scanFields...); err != nil { 536 | return nil, err 537 | } 538 | 539 | migrationsReal.original = &migrationsOriginal{} 540 | _ = query.Copy(migrationsReal, migrationsReal.original) 541 | 542 | migrationsReal.SetModel(m) 543 | migrationss = append(migrationss, *migrationsReal) 544 | } 545 | 546 | return migrationss, nil 547 | } 548 | 549 | // First return first result for given query 550 | func (m *MigrationsModel) First(ctx context.Context, builders ...query.SQLBuilder) (*MigrationsN, error) { 551 | res, err := m.Get(ctx, append(builders, query.Builder().Limit(1))...) 552 | if err != nil { 553 | return nil, err 554 | } 555 | 556 | if len(res) == 0 { 557 | return nil, query.ErrNoResult 558 | } 559 | 560 | return &res[0], nil 561 | } 562 | 563 | // Create save a new migrations to database 564 | func (m *MigrationsModel) Create(ctx context.Context, kv query.KV) (int64, error) { 565 | 566 | sqlStr, params := m.query.Table(m.tableName).ResolveInsert(kv) 567 | 568 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 569 | if err != nil { 570 | return 0, err 571 | } 572 | 573 | return res.LastInsertId() 574 | } 575 | 576 | // SaveAll save all migrationss to database 577 | func (m *MigrationsModel) SaveAll(ctx context.Context, migrationss []MigrationsN) ([]int64, error) { 578 | ids := make([]int64, 0) 579 | for _, migrations := range migrationss { 580 | id, err := m.Save(ctx, migrations) 581 | if err != nil { 582 | return ids, err 583 | } 584 | 585 | ids = append(ids, id) 586 | } 587 | 588 | return ids, nil 589 | } 590 | 591 | // Save save a migrations to database 592 | func (m *MigrationsModel) Save(ctx context.Context, migrations MigrationsN, onlyFields ...string) (int64, error) { 593 | return m.Create(ctx, migrations.StaledKV(onlyFields...)) 594 | } 595 | 596 | // SaveOrUpdate save a new migrations or update it when it has a id > 0 597 | func (m *MigrationsModel) SaveOrUpdate(ctx context.Context, migrations MigrationsN, onlyFields ...string) (id int64, updated bool, err error) { 598 | if migrations.Id.Int64 > 0 { 599 | _, _err := m.UpdateById(ctx, migrations.Id.Int64, migrations, onlyFields...) 600 | return migrations.Id.Int64, true, _err 601 | } 602 | 603 | _id, _err := m.Save(ctx, migrations, onlyFields...) 604 | return _id, false, _err 605 | } 606 | 607 | // UpdateFields update kv for a given query 608 | func (m *MigrationsModel) UpdateFields(ctx context.Context, kv query.KV, builders ...query.SQLBuilder) (int64, error) { 609 | if len(kv) == 0 { 610 | return 0, nil 611 | } 612 | 613 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()). 614 | Table(m.tableName). 615 | ResolveUpdate(kv) 616 | 617 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 618 | if err != nil { 619 | return 0, err 620 | } 621 | 622 | return res.RowsAffected() 623 | } 624 | 625 | // Update update a model for given query 626 | func (m *MigrationsModel) Update(ctx context.Context, builder query.SQLBuilder, migrations MigrationsN, onlyFields ...string) (int64, error) { 627 | return m.UpdateFields(ctx, migrations.StaledKV(onlyFields...), builder) 628 | } 629 | 630 | // UpdateById update a model by id 631 | func (m *MigrationsModel) UpdateById(ctx context.Context, id int64, migrations MigrationsN, onlyFields ...string) (int64, error) { 632 | return m.Condition(query.Builder().Where("id", "=", id)).UpdateFields(ctx, migrations.StaledKV(onlyFields...)) 633 | } 634 | 635 | // Delete remove a model 636 | func (m *MigrationsModel) Delete(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 637 | 638 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()).Table(m.tableName).ResolveDelete() 639 | 640 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 641 | if err != nil { 642 | return 0, err 643 | } 644 | 645 | return res.RowsAffected() 646 | 647 | } 648 | 649 | // DeleteById remove a model by id 650 | func (m *MigrationsModel) DeleteById(ctx context.Context, id int64) (int64, error) { 651 | return m.Condition(query.Builder().Where("id", "=", id)).Delete(ctx) 652 | } 653 | -------------------------------------------------------------------------------- /_examples/models/roles.orm.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // !!! DO NOT EDIT THIS FILE 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "github.com/iancoleman/strcase" 9 | "github.com/mylxsw/eloquent/query" 10 | "gopkg.in/guregu/null.v3" 11 | "time" 12 | ) 13 | 14 | func init() { 15 | 16 | } 17 | 18 | // RoleN is a Role object, all fields are nullable 19 | type RoleN struct { 20 | original *roleOriginal 21 | roleModel *RoleModel 22 | 23 | Name null.String 24 | Description null.String 25 | Id null.Int 26 | CreatedAt null.Time 27 | UpdatedAt null.Time 28 | } 29 | 30 | // As convert object to other type 31 | // dst must be a pointer to struct 32 | func (inst *RoleN) As(dst interface{}) error { 33 | return query.Copy(inst, dst) 34 | } 35 | 36 | // SetModel set model for Role 37 | func (inst *RoleN) SetModel(roleModel *RoleModel) { 38 | inst.roleModel = roleModel 39 | } 40 | 41 | // roleOriginal is an object which stores original Role from database 42 | type roleOriginal struct { 43 | Name null.String 44 | Description null.String 45 | Id null.Int 46 | CreatedAt null.Time 47 | UpdatedAt null.Time 48 | } 49 | 50 | // Staled identify whether the object has been modified 51 | func (inst *RoleN) Staled(onlyFields ...string) bool { 52 | if inst.original == nil { 53 | inst.original = &roleOriginal{} 54 | } 55 | 56 | if len(onlyFields) == 0 { 57 | 58 | if inst.Name != inst.original.Name { 59 | return true 60 | } 61 | if inst.Description != inst.original.Description { 62 | return true 63 | } 64 | if inst.Id != inst.original.Id { 65 | return true 66 | } 67 | if inst.CreatedAt != inst.original.CreatedAt { 68 | return true 69 | } 70 | if inst.UpdatedAt != inst.original.UpdatedAt { 71 | return true 72 | } 73 | } else { 74 | for _, f := range onlyFields { 75 | switch strcase.ToSnake(f) { 76 | 77 | case "name": 78 | if inst.Name != inst.original.Name { 79 | return true 80 | } 81 | case "description": 82 | if inst.Description != inst.original.Description { 83 | return true 84 | } 85 | case "id": 86 | if inst.Id != inst.original.Id { 87 | return true 88 | } 89 | case "created_at": 90 | if inst.CreatedAt != inst.original.CreatedAt { 91 | return true 92 | } 93 | case "updated_at": 94 | if inst.UpdatedAt != inst.original.UpdatedAt { 95 | return true 96 | } 97 | default: 98 | } 99 | } 100 | } 101 | 102 | return false 103 | } 104 | 105 | // StaledKV return all fields has been modified 106 | func (inst *RoleN) StaledKV(onlyFields ...string) query.KV { 107 | kv := make(query.KV, 0) 108 | 109 | if inst.original == nil { 110 | inst.original = &roleOriginal{} 111 | } 112 | 113 | if len(onlyFields) == 0 { 114 | 115 | if inst.Name != inst.original.Name { 116 | kv["name"] = inst.Name 117 | } 118 | if inst.Description != inst.original.Description { 119 | kv["description"] = inst.Description 120 | } 121 | if inst.Id != inst.original.Id { 122 | kv["id"] = inst.Id 123 | } 124 | if inst.CreatedAt != inst.original.CreatedAt { 125 | kv["created_at"] = inst.CreatedAt 126 | } 127 | if inst.UpdatedAt != inst.original.UpdatedAt { 128 | kv["updated_at"] = inst.UpdatedAt 129 | } 130 | } else { 131 | for _, f := range onlyFields { 132 | switch strcase.ToSnake(f) { 133 | 134 | case "name": 135 | if inst.Name != inst.original.Name { 136 | kv["name"] = inst.Name 137 | } 138 | case "description": 139 | if inst.Description != inst.original.Description { 140 | kv["description"] = inst.Description 141 | } 142 | case "id": 143 | if inst.Id != inst.original.Id { 144 | kv["id"] = inst.Id 145 | } 146 | case "created_at": 147 | if inst.CreatedAt != inst.original.CreatedAt { 148 | kv["created_at"] = inst.CreatedAt 149 | } 150 | case "updated_at": 151 | if inst.UpdatedAt != inst.original.UpdatedAt { 152 | kv["updated_at"] = inst.UpdatedAt 153 | } 154 | default: 155 | } 156 | } 157 | } 158 | 159 | return kv 160 | } 161 | 162 | // Save create a new model or update it 163 | func (inst *RoleN) Save(ctx context.Context, onlyFields ...string) error { 164 | if inst.roleModel == nil { 165 | return query.ErrModelNotSet 166 | } 167 | 168 | id, _, err := inst.roleModel.SaveOrUpdate(ctx, *inst, onlyFields...) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | inst.Id = null.IntFrom(id) 174 | return nil 175 | } 176 | 177 | // Delete remove a Role 178 | func (inst *RoleN) Delete(ctx context.Context) error { 179 | if inst.roleModel == nil { 180 | return query.ErrModelNotSet 181 | } 182 | 183 | _, err := inst.roleModel.DeleteById(ctx, inst.Id.Int64) 184 | if err != nil { 185 | return err 186 | } 187 | 188 | return nil 189 | } 190 | 191 | // String convert instance to json string 192 | func (inst *RoleN) String() string { 193 | rs, _ := json.Marshal(inst) 194 | return string(rs) 195 | } 196 | 197 | func (inst *RoleN) Users() *RoleHasManyUserRel { 198 | return &RoleHasManyUserRel{ 199 | source: inst, 200 | relModel: NewUserModel(inst.roleModel.GetDB()), 201 | } 202 | } 203 | 204 | type RoleHasManyUserRel struct { 205 | source *RoleN 206 | relModel *UserModel 207 | } 208 | 209 | func (rel *RoleHasManyUserRel) Get(ctx context.Context, builders ...query.SQLBuilder) ([]UserN, error) { 210 | builder := query.Builder().Where("role_id", rel.source.Id).Merge(builders...) 211 | 212 | return rel.relModel.Get(ctx, builder) 213 | } 214 | 215 | func (rel *RoleHasManyUserRel) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 216 | builder := query.Builder().Where("role_id", rel.source.Id).Merge(builders...) 217 | 218 | return rel.relModel.Count(ctx, builder) 219 | } 220 | 221 | func (rel *RoleHasManyUserRel) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 222 | builder := query.Builder().Where("role_id", rel.source.Id).Merge(builders...) 223 | 224 | return rel.relModel.Exists(ctx, builder) 225 | } 226 | 227 | func (rel *RoleHasManyUserRel) First(ctx context.Context, builders ...query.SQLBuilder) (*UserN, error) { 228 | builder := query.Builder().Where("role_id", rel.source.Id).Limit(1).Merge(builders...) 229 | return rel.relModel.First(ctx, builder) 230 | } 231 | 232 | func (rel *RoleHasManyUserRel) Create(ctx context.Context, target UserN) (int64, error) { 233 | target.RoleId = rel.source.Id 234 | return rel.relModel.Save(ctx, target) 235 | } 236 | 237 | type roleScope struct { 238 | name string 239 | apply func(builder query.Condition) 240 | } 241 | 242 | var roleGlobalScopes = make([]roleScope, 0) 243 | var roleLocalScopes = make([]roleScope, 0) 244 | 245 | // AddGlobalScopeForRole assign a global scope to a model 246 | func AddGlobalScopeForRole(name string, apply func(builder query.Condition)) { 247 | roleGlobalScopes = append(roleGlobalScopes, roleScope{name: name, apply: apply}) 248 | } 249 | 250 | // AddLocalScopeForRole assign a local scope to a model 251 | func AddLocalScopeForRole(name string, apply func(builder query.Condition)) { 252 | roleLocalScopes = append(roleLocalScopes, roleScope{name: name, apply: apply}) 253 | } 254 | 255 | func (m *RoleModel) applyScope() query.Condition { 256 | scopeCond := query.ConditionBuilder() 257 | for _, g := range roleGlobalScopes { 258 | if m.globalScopeEnabled(g.name) { 259 | g.apply(scopeCond) 260 | } 261 | } 262 | 263 | for _, s := range roleLocalScopes { 264 | if m.localScopeEnabled(s.name) { 265 | s.apply(scopeCond) 266 | } 267 | } 268 | 269 | return scopeCond 270 | } 271 | 272 | func (m *RoleModel) localScopeEnabled(name string) bool { 273 | for _, n := range m.includeLocalScopes { 274 | if name == n { 275 | return true 276 | } 277 | } 278 | 279 | return false 280 | } 281 | 282 | func (m *RoleModel) globalScopeEnabled(name string) bool { 283 | for _, n := range m.excludeGlobalScopes { 284 | if name == n { 285 | return false 286 | } 287 | } 288 | 289 | return true 290 | } 291 | 292 | type Role struct { 293 | Name string 294 | Description string 295 | Id int64 296 | CreatedAt time.Time 297 | UpdatedAt time.Time 298 | } 299 | 300 | func (w Role) ToRoleN(allows ...string) RoleN { 301 | if len(allows) == 0 { 302 | return RoleN{ 303 | 304 | Name: null.StringFrom(w.Name), 305 | Description: null.StringFrom(w.Description), 306 | Id: null.IntFrom(int64(w.Id)), 307 | CreatedAt: null.TimeFrom(w.CreatedAt), 308 | UpdatedAt: null.TimeFrom(w.UpdatedAt), 309 | } 310 | } 311 | 312 | res := RoleN{} 313 | for _, al := range allows { 314 | switch strcase.ToSnake(al) { 315 | 316 | case "name": 317 | res.Name = null.StringFrom(w.Name) 318 | case "description": 319 | res.Description = null.StringFrom(w.Description) 320 | case "id": 321 | res.Id = null.IntFrom(int64(w.Id)) 322 | case "created_at": 323 | res.CreatedAt = null.TimeFrom(w.CreatedAt) 324 | case "updated_at": 325 | res.UpdatedAt = null.TimeFrom(w.UpdatedAt) 326 | default: 327 | } 328 | } 329 | 330 | return res 331 | } 332 | 333 | // As convert object to other type 334 | // dst must be a pointer to struct 335 | func (w Role) As(dst interface{}) error { 336 | return query.Copy(w, dst) 337 | } 338 | 339 | func (w *RoleN) ToRole() Role { 340 | return Role{ 341 | 342 | Name: w.Name.String, 343 | Description: w.Description.String, 344 | Id: w.Id.Int64, 345 | CreatedAt: w.CreatedAt.Time, 346 | UpdatedAt: w.UpdatedAt.Time, 347 | } 348 | } 349 | 350 | // RoleModel is a model which encapsulates the operations of the object 351 | type RoleModel struct { 352 | db *query.DatabaseWrap 353 | tableName string 354 | 355 | excludeGlobalScopes []string 356 | includeLocalScopes []string 357 | 358 | query query.SQLBuilder 359 | } 360 | 361 | var roleTableName = "wz_role" 362 | 363 | // RoleTable return table name for Role 364 | func RoleTable() string { 365 | return roleTableName 366 | } 367 | 368 | const ( 369 | FieldRoleName = "name" 370 | FieldRoleDescription = "description" 371 | FieldRoleId = "id" 372 | FieldRoleCreatedAt = "created_at" 373 | FieldRoleUpdatedAt = "updated_at" 374 | ) 375 | 376 | // RoleFields return all fields in Role model 377 | func RoleFields() []string { 378 | return []string{ 379 | "name", 380 | "description", 381 | "id", 382 | "created_at", 383 | "updated_at", 384 | } 385 | } 386 | 387 | func SetRoleTable(tableName string) { 388 | roleTableName = tableName 389 | } 390 | 391 | // NewRoleModel create a RoleModel 392 | func NewRoleModel(db query.Database) *RoleModel { 393 | return &RoleModel{ 394 | db: query.NewDatabaseWrap(db), 395 | tableName: roleTableName, 396 | excludeGlobalScopes: make([]string, 0), 397 | includeLocalScopes: make([]string, 0), 398 | query: query.Builder(), 399 | } 400 | } 401 | 402 | // GetDB return database instance 403 | func (m *RoleModel) GetDB() query.Database { 404 | return m.db.GetDB() 405 | } 406 | 407 | func (m *RoleModel) clone() *RoleModel { 408 | return &RoleModel{ 409 | db: m.db, 410 | tableName: m.tableName, 411 | excludeGlobalScopes: append([]string{}, m.excludeGlobalScopes...), 412 | includeLocalScopes: append([]string{}, m.includeLocalScopes...), 413 | query: m.query, 414 | } 415 | } 416 | 417 | // WithoutGlobalScopes remove a global scope for given query 418 | func (m *RoleModel) WithoutGlobalScopes(names ...string) *RoleModel { 419 | mc := m.clone() 420 | mc.excludeGlobalScopes = append(mc.excludeGlobalScopes, names...) 421 | 422 | return mc 423 | } 424 | 425 | // WithLocalScopes add a local scope for given query 426 | func (m *RoleModel) WithLocalScopes(names ...string) *RoleModel { 427 | mc := m.clone() 428 | mc.includeLocalScopes = append(mc.includeLocalScopes, names...) 429 | 430 | return mc 431 | } 432 | 433 | // Condition add query builder to model 434 | func (m *RoleModel) Condition(builder query.SQLBuilder) *RoleModel { 435 | mm := m.clone() 436 | mm.query = mm.query.Merge(builder) 437 | 438 | return mm 439 | } 440 | 441 | // Find retrieve a model by its primary key 442 | func (m *RoleModel) Find(ctx context.Context, id int64) (*RoleN, error) { 443 | return m.First(ctx, m.query.Where("id", "=", id)) 444 | } 445 | 446 | // Exists return whether the records exists for a given query 447 | func (m *RoleModel) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 448 | count, err := m.Count(ctx, builders...) 449 | return count > 0, err 450 | } 451 | 452 | // Count return model count for a given query 453 | func (m *RoleModel) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 454 | sqlStr, params := m.query. 455 | Merge(builders...). 456 | Table(m.tableName). 457 | AppendCondition(m.applyScope()). 458 | ResolveCount() 459 | 460 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 461 | if err != nil { 462 | return 0, err 463 | } 464 | 465 | defer rows.Close() 466 | 467 | rows.Next() 468 | var res int64 469 | if err := rows.Scan(&res); err != nil { 470 | return 0, err 471 | } 472 | 473 | return res, nil 474 | } 475 | 476 | func (m *RoleModel) Paginate(ctx context.Context, page int64, perPage int64, builders ...query.SQLBuilder) ([]RoleN, query.PaginateMeta, error) { 477 | if page <= 0 { 478 | page = 1 479 | } 480 | 481 | if perPage <= 0 { 482 | perPage = 15 483 | } 484 | 485 | meta := query.PaginateMeta{ 486 | PerPage: perPage, 487 | Page: page, 488 | } 489 | 490 | count, err := m.Count(ctx, builders...) 491 | if err != nil { 492 | return nil, meta, err 493 | } 494 | 495 | meta.Total = count 496 | meta.LastPage = count / perPage 497 | if count%perPage != 0 { 498 | meta.LastPage += 1 499 | } 500 | 501 | res, err := m.Get(ctx, append([]query.SQLBuilder{query.Builder().Limit(perPage).Offset((page - 1) * perPage)}, builders...)...) 502 | if err != nil { 503 | return res, meta, err 504 | } 505 | 506 | return res, meta, nil 507 | } 508 | 509 | // Get retrieve all results for given query 510 | func (m *RoleModel) Get(ctx context.Context, builders ...query.SQLBuilder) ([]RoleN, error) { 511 | b := m.query.Merge(builders...).Table(m.tableName).AppendCondition(m.applyScope()) 512 | if len(b.GetFields()) == 0 { 513 | b = b.Select( 514 | "name", 515 | "description", 516 | "id", 517 | "created_at", 518 | "updated_at", 519 | ) 520 | } 521 | 522 | fields := b.GetFields() 523 | selectFields := make([]query.Expr, 0) 524 | 525 | for _, f := range fields { 526 | switch strcase.ToSnake(f.Value) { 527 | 528 | case "name": 529 | selectFields = append(selectFields, f) 530 | case "description": 531 | selectFields = append(selectFields, f) 532 | case "id": 533 | selectFields = append(selectFields, f) 534 | case "created_at": 535 | selectFields = append(selectFields, f) 536 | case "updated_at": 537 | selectFields = append(selectFields, f) 538 | } 539 | } 540 | 541 | var createScanVar = func(fields []query.Expr) (*RoleN, []interface{}) { 542 | var roleVar RoleN 543 | scanFields := make([]interface{}, 0) 544 | 545 | for _, f := range fields { 546 | switch strcase.ToSnake(f.Value) { 547 | 548 | case "name": 549 | scanFields = append(scanFields, &roleVar.Name) 550 | case "description": 551 | scanFields = append(scanFields, &roleVar.Description) 552 | case "id": 553 | scanFields = append(scanFields, &roleVar.Id) 554 | case "created_at": 555 | scanFields = append(scanFields, &roleVar.CreatedAt) 556 | case "updated_at": 557 | scanFields = append(scanFields, &roleVar.UpdatedAt) 558 | } 559 | } 560 | 561 | return &roleVar, scanFields 562 | } 563 | 564 | sqlStr, params := b.Fields(selectFields...).ResolveQuery() 565 | 566 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 567 | if err != nil { 568 | return nil, err 569 | } 570 | 571 | defer rows.Close() 572 | 573 | roles := make([]RoleN, 0) 574 | for rows.Next() { 575 | roleReal, scanFields := createScanVar(fields) 576 | if err := rows.Scan(scanFields...); err != nil { 577 | return nil, err 578 | } 579 | 580 | roleReal.original = &roleOriginal{} 581 | _ = query.Copy(roleReal, roleReal.original) 582 | 583 | roleReal.SetModel(m) 584 | roles = append(roles, *roleReal) 585 | } 586 | 587 | return roles, nil 588 | } 589 | 590 | // First return first result for given query 591 | func (m *RoleModel) First(ctx context.Context, builders ...query.SQLBuilder) (*RoleN, error) { 592 | res, err := m.Get(ctx, append(builders, query.Builder().Limit(1))...) 593 | if err != nil { 594 | return nil, err 595 | } 596 | 597 | if len(res) == 0 { 598 | return nil, query.ErrNoResult 599 | } 600 | 601 | return &res[0], nil 602 | } 603 | 604 | // Create save a new Role to database 605 | func (m *RoleModel) Create(ctx context.Context, kv query.KV) (int64, error) { 606 | 607 | if _, ok := kv["created_at"]; !ok { 608 | kv["created_at"] = time.Now() 609 | } 610 | 611 | if _, ok := kv["updated_at"]; !ok { 612 | kv["updated_at"] = time.Now() 613 | } 614 | 615 | sqlStr, params := m.query.Table(m.tableName).ResolveInsert(kv) 616 | 617 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 618 | if err != nil { 619 | return 0, err 620 | } 621 | 622 | return res.LastInsertId() 623 | } 624 | 625 | // SaveAll save all Roles to database 626 | func (m *RoleModel) SaveAll(ctx context.Context, roles []RoleN) ([]int64, error) { 627 | ids := make([]int64, 0) 628 | for _, role := range roles { 629 | id, err := m.Save(ctx, role) 630 | if err != nil { 631 | return ids, err 632 | } 633 | 634 | ids = append(ids, id) 635 | } 636 | 637 | return ids, nil 638 | } 639 | 640 | // Save save a Role to database 641 | func (m *RoleModel) Save(ctx context.Context, role RoleN, onlyFields ...string) (int64, error) { 642 | return m.Create(ctx, role.StaledKV(onlyFields...)) 643 | } 644 | 645 | // SaveOrUpdate save a new Role or update it when it has a id > 0 646 | func (m *RoleModel) SaveOrUpdate(ctx context.Context, role RoleN, onlyFields ...string) (id int64, updated bool, err error) { 647 | if role.Id.Int64 > 0 { 648 | _, _err := m.UpdateById(ctx, role.Id.Int64, role, onlyFields...) 649 | return role.Id.Int64, true, _err 650 | } 651 | 652 | _id, _err := m.Save(ctx, role, onlyFields...) 653 | return _id, false, _err 654 | } 655 | 656 | // UpdateFields update kv for a given query 657 | func (m *RoleModel) UpdateFields(ctx context.Context, kv query.KV, builders ...query.SQLBuilder) (int64, error) { 658 | if len(kv) == 0 { 659 | return 0, nil 660 | } 661 | 662 | kv["updated_at"] = time.Now() 663 | 664 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()). 665 | Table(m.tableName). 666 | ResolveUpdate(kv) 667 | 668 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 669 | if err != nil { 670 | return 0, err 671 | } 672 | 673 | return res.RowsAffected() 674 | } 675 | 676 | // Update update a model for given query 677 | func (m *RoleModel) Update(ctx context.Context, builder query.SQLBuilder, role RoleN, onlyFields ...string) (int64, error) { 678 | return m.UpdateFields(ctx, role.StaledKV(onlyFields...), builder) 679 | } 680 | 681 | // UpdateById update a model by id 682 | func (m *RoleModel) UpdateById(ctx context.Context, id int64, role RoleN, onlyFields ...string) (int64, error) { 683 | return m.Condition(query.Builder().Where("id", "=", id)).UpdateFields(ctx, role.StaledKV(onlyFields...)) 684 | } 685 | 686 | // Delete remove a model 687 | func (m *RoleModel) Delete(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 688 | 689 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()).Table(m.tableName).ResolveDelete() 690 | 691 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 692 | if err != nil { 693 | return 0, err 694 | } 695 | 696 | return res.RowsAffected() 697 | 698 | } 699 | 700 | // DeleteById remove a model by id 701 | func (m *RoleModel) DeleteById(ctx context.Context, id int64) (int64, error) { 702 | return m.Condition(query.Builder().Where("id", "=", id)).Delete(ctx) 703 | } 704 | -------------------------------------------------------------------------------- /_examples/models/organizations.orm.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // !!! DO NOT EDIT THIS FILE 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "github.com/iancoleman/strcase" 9 | "github.com/mylxsw/eloquent" 10 | "github.com/mylxsw/eloquent/query" 11 | "gopkg.in/guregu/null.v3" 12 | "time" 13 | ) 14 | 15 | func init() { 16 | 17 | } 18 | 19 | // OrganizationN is a Organization object, all fields are nullable 20 | type OrganizationN struct { 21 | original *organizationOriginal 22 | organizationModel *OrganizationModel 23 | 24 | Id null.Int 25 | Name null.String 26 | CreatedAt null.Time 27 | UpdatedAt null.Time 28 | } 29 | 30 | // As convert object to other type 31 | // dst must be a pointer to struct 32 | func (inst *OrganizationN) As(dst interface{}) error { 33 | return query.Copy(inst, dst) 34 | } 35 | 36 | // SetModel set model for Organization 37 | func (inst *OrganizationN) SetModel(organizationModel *OrganizationModel) { 38 | inst.organizationModel = organizationModel 39 | } 40 | 41 | // organizationOriginal is an object which stores original Organization from database 42 | type organizationOriginal struct { 43 | Id null.Int 44 | Name null.String 45 | CreatedAt null.Time 46 | UpdatedAt null.Time 47 | } 48 | 49 | // Staled identify whether the object has been modified 50 | func (inst *OrganizationN) Staled(onlyFields ...string) bool { 51 | if inst.original == nil { 52 | inst.original = &organizationOriginal{} 53 | } 54 | 55 | if len(onlyFields) == 0 { 56 | 57 | if inst.Id != inst.original.Id { 58 | return true 59 | } 60 | if inst.Name != inst.original.Name { 61 | return true 62 | } 63 | if inst.CreatedAt != inst.original.CreatedAt { 64 | return true 65 | } 66 | if inst.UpdatedAt != inst.original.UpdatedAt { 67 | return true 68 | } 69 | } else { 70 | for _, f := range onlyFields { 71 | switch strcase.ToSnake(f) { 72 | 73 | case "id": 74 | if inst.Id != inst.original.Id { 75 | return true 76 | } 77 | case "name": 78 | if inst.Name != inst.original.Name { 79 | return true 80 | } 81 | case "created_at": 82 | if inst.CreatedAt != inst.original.CreatedAt { 83 | return true 84 | } 85 | case "updated_at": 86 | if inst.UpdatedAt != inst.original.UpdatedAt { 87 | return true 88 | } 89 | default: 90 | } 91 | } 92 | } 93 | 94 | return false 95 | } 96 | 97 | // StaledKV return all fields has been modified 98 | func (inst *OrganizationN) StaledKV(onlyFields ...string) query.KV { 99 | kv := make(query.KV, 0) 100 | 101 | if inst.original == nil { 102 | inst.original = &organizationOriginal{} 103 | } 104 | 105 | if len(onlyFields) == 0 { 106 | 107 | if inst.Id != inst.original.Id { 108 | kv["id"] = inst.Id 109 | } 110 | if inst.Name != inst.original.Name { 111 | kv["name"] = inst.Name 112 | } 113 | if inst.CreatedAt != inst.original.CreatedAt { 114 | kv["created_at"] = inst.CreatedAt 115 | } 116 | if inst.UpdatedAt != inst.original.UpdatedAt { 117 | kv["updated_at"] = inst.UpdatedAt 118 | } 119 | } else { 120 | for _, f := range onlyFields { 121 | switch strcase.ToSnake(f) { 122 | 123 | case "id": 124 | if inst.Id != inst.original.Id { 125 | kv["id"] = inst.Id 126 | } 127 | case "name": 128 | if inst.Name != inst.original.Name { 129 | kv["name"] = inst.Name 130 | } 131 | case "created_at": 132 | if inst.CreatedAt != inst.original.CreatedAt { 133 | kv["created_at"] = inst.CreatedAt 134 | } 135 | case "updated_at": 136 | if inst.UpdatedAt != inst.original.UpdatedAt { 137 | kv["updated_at"] = inst.UpdatedAt 138 | } 139 | default: 140 | } 141 | } 142 | } 143 | 144 | return kv 145 | } 146 | 147 | // Save create a new model or update it 148 | func (inst *OrganizationN) Save(ctx context.Context, onlyFields ...string) error { 149 | if inst.organizationModel == nil { 150 | return query.ErrModelNotSet 151 | } 152 | 153 | id, _, err := inst.organizationModel.SaveOrUpdate(ctx, *inst, onlyFields...) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | inst.Id = null.IntFrom(id) 159 | return nil 160 | } 161 | 162 | // Delete remove a organization 163 | func (inst *OrganizationN) Delete(ctx context.Context) error { 164 | if inst.organizationModel == nil { 165 | return query.ErrModelNotSet 166 | } 167 | 168 | _, err := inst.organizationModel.DeleteById(ctx, inst.Id.Int64) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | return nil 174 | } 175 | 176 | // String convert instance to json string 177 | func (inst *OrganizationN) String() string { 178 | rs, _ := json.Marshal(inst) 179 | return string(rs) 180 | } 181 | 182 | func (inst *OrganizationN) Users() *OrganizationBelongsToManyUserRel { 183 | return &OrganizationBelongsToManyUserRel{ 184 | source: inst, 185 | pivotTable: "user_organization_ref", 186 | relModel: NewUserModel(inst.organizationModel.GetDB()), 187 | } 188 | } 189 | 190 | type OrganizationBelongsToManyUserRel struct { 191 | source *OrganizationN 192 | pivotTable string 193 | relModel *UserModel 194 | } 195 | 196 | func (rel *OrganizationBelongsToManyUserRel) Get(ctx context.Context, builders ...query.SQLBuilder) ([]UserN, error) { 197 | res, err := eloquent.DB(rel.relModel.GetDB()).Query( 198 | ctx, 199 | query.Builder().Table(rel.pivotTable).Select("user_id").Where("organization_id", rel.source.Id), 200 | func(row eloquent.Scanner) (interface{}, error) { 201 | var k interface{} 202 | if err := row.Scan(&k); err != nil { 203 | return nil, err 204 | } 205 | 206 | return k, nil 207 | }, 208 | ) 209 | 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | return rel.relModel.Get(ctx, query.Builder().Merge(builders...).WhereIn("id", res...)) 215 | } 216 | 217 | func (rel *OrganizationBelongsToManyUserRel) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 218 | res, err := eloquent.DB(rel.relModel.GetDB()).Query( 219 | ctx, 220 | query.Builder().Table(rel.pivotTable).Select(query.Raw("COUNT(1) as c")).Where("organization_id", rel.source.Id), 221 | func(row eloquent.Scanner) (interface{}, error) { 222 | var k int64 223 | if err := row.Scan(&k); err != nil { 224 | return nil, err 225 | } 226 | 227 | return k, nil 228 | }, 229 | ) 230 | 231 | if err != nil { 232 | return 0, err 233 | } 234 | 235 | return res[0].(int64), nil 236 | } 237 | 238 | func (rel *OrganizationBelongsToManyUserRel) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 239 | c, err := rel.Count(ctx, builders...) 240 | if err != nil { 241 | return false, err 242 | } 243 | 244 | return c > 0, nil 245 | } 246 | 247 | func (rel *OrganizationBelongsToManyUserRel) Attach(ctx context.Context, target UserN) error { 248 | _, err := eloquent.DB(rel.relModel.GetDB()).Insert(ctx, rel.pivotTable, query.KV{ 249 | "user_id": target.Id, 250 | "organization_id": rel.source.Id, 251 | }) 252 | 253 | return err 254 | } 255 | 256 | func (rel *OrganizationBelongsToManyUserRel) Detach(ctx context.Context, target UserN) error { 257 | _, err := eloquent.DB(rel.relModel.GetDB()). 258 | Delete(ctx, eloquent.Build(rel.pivotTable). 259 | Where("user_id", target.Id). 260 | Where("organization_id", rel.source.Id)) 261 | 262 | return err 263 | } 264 | 265 | func (rel *OrganizationBelongsToManyUserRel) DetachAll(ctx context.Context) error { 266 | _, err := eloquent.DB(rel.relModel.GetDB()). 267 | Delete(ctx, eloquent.Build(rel.pivotTable). 268 | Where("organization_id", rel.source.Id)) 269 | return err 270 | } 271 | 272 | func (rel *OrganizationBelongsToManyUserRel) Create(ctx context.Context, target UserN, builders ...query.SQLBuilder) (int64, error) { 273 | targetId, err := rel.relModel.Save(ctx, target) 274 | if err != nil { 275 | return 0, err 276 | } 277 | 278 | target.Id = null.IntFrom(targetId) 279 | 280 | err = rel.Attach(ctx, target) 281 | 282 | return targetId, err 283 | } 284 | 285 | type organizationScope struct { 286 | name string 287 | apply func(builder query.Condition) 288 | } 289 | 290 | var organizationGlobalScopes = make([]organizationScope, 0) 291 | var organizationLocalScopes = make([]organizationScope, 0) 292 | 293 | // AddGlobalScopeForOrganization assign a global scope to a model 294 | func AddGlobalScopeForOrganization(name string, apply func(builder query.Condition)) { 295 | organizationGlobalScopes = append(organizationGlobalScopes, organizationScope{name: name, apply: apply}) 296 | } 297 | 298 | // AddLocalScopeForOrganization assign a local scope to a model 299 | func AddLocalScopeForOrganization(name string, apply func(builder query.Condition)) { 300 | organizationLocalScopes = append(organizationLocalScopes, organizationScope{name: name, apply: apply}) 301 | } 302 | 303 | func (m *OrganizationModel) applyScope() query.Condition { 304 | scopeCond := query.ConditionBuilder() 305 | for _, g := range organizationGlobalScopes { 306 | if m.globalScopeEnabled(g.name) { 307 | g.apply(scopeCond) 308 | } 309 | } 310 | 311 | for _, s := range organizationLocalScopes { 312 | if m.localScopeEnabled(s.name) { 313 | s.apply(scopeCond) 314 | } 315 | } 316 | 317 | return scopeCond 318 | } 319 | 320 | func (m *OrganizationModel) localScopeEnabled(name string) bool { 321 | for _, n := range m.includeLocalScopes { 322 | if name == n { 323 | return true 324 | } 325 | } 326 | 327 | return false 328 | } 329 | 330 | func (m *OrganizationModel) globalScopeEnabled(name string) bool { 331 | for _, n := range m.excludeGlobalScopes { 332 | if name == n { 333 | return false 334 | } 335 | } 336 | 337 | return true 338 | } 339 | 340 | type Organization struct { 341 | Id int64 342 | Name string 343 | CreatedAt time.Time 344 | UpdatedAt time.Time 345 | } 346 | 347 | func (w Organization) ToOrganizationN(allows ...string) OrganizationN { 348 | if len(allows) == 0 { 349 | return OrganizationN{ 350 | 351 | Id: null.IntFrom(int64(w.Id)), 352 | Name: null.StringFrom(w.Name), 353 | CreatedAt: null.TimeFrom(w.CreatedAt), 354 | UpdatedAt: null.TimeFrom(w.UpdatedAt), 355 | } 356 | } 357 | 358 | res := OrganizationN{} 359 | for _, al := range allows { 360 | switch strcase.ToSnake(al) { 361 | 362 | case "id": 363 | res.Id = null.IntFrom(int64(w.Id)) 364 | case "name": 365 | res.Name = null.StringFrom(w.Name) 366 | case "created_at": 367 | res.CreatedAt = null.TimeFrom(w.CreatedAt) 368 | case "updated_at": 369 | res.UpdatedAt = null.TimeFrom(w.UpdatedAt) 370 | default: 371 | } 372 | } 373 | 374 | return res 375 | } 376 | 377 | // As convert object to other type 378 | // dst must be a pointer to struct 379 | func (w Organization) As(dst interface{}) error { 380 | return query.Copy(w, dst) 381 | } 382 | 383 | func (w *OrganizationN) ToOrganization() Organization { 384 | return Organization{ 385 | 386 | Id: w.Id.Int64, 387 | Name: w.Name.String, 388 | CreatedAt: w.CreatedAt.Time, 389 | UpdatedAt: w.UpdatedAt.Time, 390 | } 391 | } 392 | 393 | // OrganizationModel is a model which encapsulates the operations of the object 394 | type OrganizationModel struct { 395 | db *query.DatabaseWrap 396 | tableName string 397 | 398 | excludeGlobalScopes []string 399 | includeLocalScopes []string 400 | 401 | query query.SQLBuilder 402 | } 403 | 404 | var organizationTableName = "wz_organization" 405 | 406 | // OrganizationTable return table name for Organization 407 | func OrganizationTable() string { 408 | return organizationTableName 409 | } 410 | 411 | const ( 412 | FieldOrganizationId = "id" 413 | FieldOrganizationName = "name" 414 | FieldOrganizationCreatedAt = "created_at" 415 | FieldOrganizationUpdatedAt = "updated_at" 416 | ) 417 | 418 | // OrganizationFields return all fields in Organization model 419 | func OrganizationFields() []string { 420 | return []string{ 421 | "id", 422 | "name", 423 | "created_at", 424 | "updated_at", 425 | } 426 | } 427 | 428 | func SetOrganizationTable(tableName string) { 429 | organizationTableName = tableName 430 | } 431 | 432 | // NewOrganizationModel create a OrganizationModel 433 | func NewOrganizationModel(db query.Database) *OrganizationModel { 434 | return &OrganizationModel{ 435 | db: query.NewDatabaseWrap(db), 436 | tableName: organizationTableName, 437 | excludeGlobalScopes: make([]string, 0), 438 | includeLocalScopes: make([]string, 0), 439 | query: query.Builder(), 440 | } 441 | } 442 | 443 | // GetDB return database instance 444 | func (m *OrganizationModel) GetDB() query.Database { 445 | return m.db.GetDB() 446 | } 447 | 448 | func (m *OrganizationModel) clone() *OrganizationModel { 449 | return &OrganizationModel{ 450 | db: m.db, 451 | tableName: m.tableName, 452 | excludeGlobalScopes: append([]string{}, m.excludeGlobalScopes...), 453 | includeLocalScopes: append([]string{}, m.includeLocalScopes...), 454 | query: m.query, 455 | } 456 | } 457 | 458 | // WithoutGlobalScopes remove a global scope for given query 459 | func (m *OrganizationModel) WithoutGlobalScopes(names ...string) *OrganizationModel { 460 | mc := m.clone() 461 | mc.excludeGlobalScopes = append(mc.excludeGlobalScopes, names...) 462 | 463 | return mc 464 | } 465 | 466 | // WithLocalScopes add a local scope for given query 467 | func (m *OrganizationModel) WithLocalScopes(names ...string) *OrganizationModel { 468 | mc := m.clone() 469 | mc.includeLocalScopes = append(mc.includeLocalScopes, names...) 470 | 471 | return mc 472 | } 473 | 474 | // Condition add query builder to model 475 | func (m *OrganizationModel) Condition(builder query.SQLBuilder) *OrganizationModel { 476 | mm := m.clone() 477 | mm.query = mm.query.Merge(builder) 478 | 479 | return mm 480 | } 481 | 482 | // Find retrieve a model by its primary key 483 | func (m *OrganizationModel) Find(ctx context.Context, id int64) (*OrganizationN, error) { 484 | return m.First(ctx, m.query.Where("id", "=", id)) 485 | } 486 | 487 | // Exists return whether the records exists for a given query 488 | func (m *OrganizationModel) Exists(ctx context.Context, builders ...query.SQLBuilder) (bool, error) { 489 | count, err := m.Count(ctx, builders...) 490 | return count > 0, err 491 | } 492 | 493 | // Count return model count for a given query 494 | func (m *OrganizationModel) Count(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 495 | sqlStr, params := m.query. 496 | Merge(builders...). 497 | Table(m.tableName). 498 | AppendCondition(m.applyScope()). 499 | ResolveCount() 500 | 501 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 502 | if err != nil { 503 | return 0, err 504 | } 505 | 506 | defer rows.Close() 507 | 508 | rows.Next() 509 | var res int64 510 | if err := rows.Scan(&res); err != nil { 511 | return 0, err 512 | } 513 | 514 | return res, nil 515 | } 516 | 517 | func (m *OrganizationModel) Paginate(ctx context.Context, page int64, perPage int64, builders ...query.SQLBuilder) ([]OrganizationN, query.PaginateMeta, error) { 518 | if page <= 0 { 519 | page = 1 520 | } 521 | 522 | if perPage <= 0 { 523 | perPage = 15 524 | } 525 | 526 | meta := query.PaginateMeta{ 527 | PerPage: perPage, 528 | Page: page, 529 | } 530 | 531 | count, err := m.Count(ctx, builders...) 532 | if err != nil { 533 | return nil, meta, err 534 | } 535 | 536 | meta.Total = count 537 | meta.LastPage = count / perPage 538 | if count%perPage != 0 { 539 | meta.LastPage += 1 540 | } 541 | 542 | res, err := m.Get(ctx, append([]query.SQLBuilder{query.Builder().Limit(perPage).Offset((page - 1) * perPage)}, builders...)...) 543 | if err != nil { 544 | return res, meta, err 545 | } 546 | 547 | return res, meta, nil 548 | } 549 | 550 | // Get retrieve all results for given query 551 | func (m *OrganizationModel) Get(ctx context.Context, builders ...query.SQLBuilder) ([]OrganizationN, error) { 552 | b := m.query.Merge(builders...).Table(m.tableName).AppendCondition(m.applyScope()) 553 | if len(b.GetFields()) == 0 { 554 | b = b.Select( 555 | "id", 556 | "name", 557 | "created_at", 558 | "updated_at", 559 | ) 560 | } 561 | 562 | fields := b.GetFields() 563 | selectFields := make([]query.Expr, 0) 564 | 565 | for _, f := range fields { 566 | switch strcase.ToSnake(f.Value) { 567 | 568 | case "id": 569 | selectFields = append(selectFields, f) 570 | case "name": 571 | selectFields = append(selectFields, f) 572 | case "created_at": 573 | selectFields = append(selectFields, f) 574 | case "updated_at": 575 | selectFields = append(selectFields, f) 576 | } 577 | } 578 | 579 | var createScanVar = func(fields []query.Expr) (*OrganizationN, []interface{}) { 580 | var organizationVar OrganizationN 581 | scanFields := make([]interface{}, 0) 582 | 583 | for _, f := range fields { 584 | switch strcase.ToSnake(f.Value) { 585 | 586 | case "id": 587 | scanFields = append(scanFields, &organizationVar.Id) 588 | case "name": 589 | scanFields = append(scanFields, &organizationVar.Name) 590 | case "created_at": 591 | scanFields = append(scanFields, &organizationVar.CreatedAt) 592 | case "updated_at": 593 | scanFields = append(scanFields, &organizationVar.UpdatedAt) 594 | } 595 | } 596 | 597 | return &organizationVar, scanFields 598 | } 599 | 600 | sqlStr, params := b.Fields(selectFields...).ResolveQuery() 601 | 602 | rows, err := m.db.QueryContext(ctx, sqlStr, params...) 603 | if err != nil { 604 | return nil, err 605 | } 606 | 607 | defer rows.Close() 608 | 609 | organizations := make([]OrganizationN, 0) 610 | for rows.Next() { 611 | organizationReal, scanFields := createScanVar(fields) 612 | if err := rows.Scan(scanFields...); err != nil { 613 | return nil, err 614 | } 615 | 616 | organizationReal.original = &organizationOriginal{} 617 | _ = query.Copy(organizationReal, organizationReal.original) 618 | 619 | organizationReal.SetModel(m) 620 | organizations = append(organizations, *organizationReal) 621 | } 622 | 623 | return organizations, nil 624 | } 625 | 626 | // First return first result for given query 627 | func (m *OrganizationModel) First(ctx context.Context, builders ...query.SQLBuilder) (*OrganizationN, error) { 628 | res, err := m.Get(ctx, append(builders, query.Builder().Limit(1))...) 629 | if err != nil { 630 | return nil, err 631 | } 632 | 633 | if len(res) == 0 { 634 | return nil, query.ErrNoResult 635 | } 636 | 637 | return &res[0], nil 638 | } 639 | 640 | // Create save a new organization to database 641 | func (m *OrganizationModel) Create(ctx context.Context, kv query.KV) (int64, error) { 642 | 643 | if _, ok := kv["created_at"]; !ok { 644 | kv["created_at"] = time.Now() 645 | } 646 | 647 | if _, ok := kv["updated_at"]; !ok { 648 | kv["updated_at"] = time.Now() 649 | } 650 | 651 | sqlStr, params := m.query.Table(m.tableName).ResolveInsert(kv) 652 | 653 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 654 | if err != nil { 655 | return 0, err 656 | } 657 | 658 | return res.LastInsertId() 659 | } 660 | 661 | // SaveAll save all organizations to database 662 | func (m *OrganizationModel) SaveAll(ctx context.Context, organizations []OrganizationN) ([]int64, error) { 663 | ids := make([]int64, 0) 664 | for _, organization := range organizations { 665 | id, err := m.Save(ctx, organization) 666 | if err != nil { 667 | return ids, err 668 | } 669 | 670 | ids = append(ids, id) 671 | } 672 | 673 | return ids, nil 674 | } 675 | 676 | // Save save a organization to database 677 | func (m *OrganizationModel) Save(ctx context.Context, organization OrganizationN, onlyFields ...string) (int64, error) { 678 | return m.Create(ctx, organization.StaledKV(onlyFields...)) 679 | } 680 | 681 | // SaveOrUpdate save a new organization or update it when it has a id > 0 682 | func (m *OrganizationModel) SaveOrUpdate(ctx context.Context, organization OrganizationN, onlyFields ...string) (id int64, updated bool, err error) { 683 | if organization.Id.Int64 > 0 { 684 | _, _err := m.UpdateById(ctx, organization.Id.Int64, organization, onlyFields...) 685 | return organization.Id.Int64, true, _err 686 | } 687 | 688 | _id, _err := m.Save(ctx, organization, onlyFields...) 689 | return _id, false, _err 690 | } 691 | 692 | // UpdateFields update kv for a given query 693 | func (m *OrganizationModel) UpdateFields(ctx context.Context, kv query.KV, builders ...query.SQLBuilder) (int64, error) { 694 | if len(kv) == 0 { 695 | return 0, nil 696 | } 697 | 698 | kv["updated_at"] = time.Now() 699 | 700 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()). 701 | Table(m.tableName). 702 | ResolveUpdate(kv) 703 | 704 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 705 | if err != nil { 706 | return 0, err 707 | } 708 | 709 | return res.RowsAffected() 710 | } 711 | 712 | // Update update a model for given query 713 | func (m *OrganizationModel) Update(ctx context.Context, builder query.SQLBuilder, organization OrganizationN, onlyFields ...string) (int64, error) { 714 | return m.UpdateFields(ctx, organization.StaledKV(onlyFields...), builder) 715 | } 716 | 717 | // UpdateById update a model by id 718 | func (m *OrganizationModel) UpdateById(ctx context.Context, id int64, organization OrganizationN, onlyFields ...string) (int64, error) { 719 | return m.Condition(query.Builder().Where("id", "=", id)).UpdateFields(ctx, organization.StaledKV(onlyFields...)) 720 | } 721 | 722 | // Delete remove a model 723 | func (m *OrganizationModel) Delete(ctx context.Context, builders ...query.SQLBuilder) (int64, error) { 724 | 725 | sqlStr, params := m.query.Merge(builders...).AppendCondition(m.applyScope()).Table(m.tableName).ResolveDelete() 726 | 727 | res, err := m.db.ExecContext(ctx, sqlStr, params...) 728 | if err != nil { 729 | return 0, err 730 | } 731 | 732 | return res.RowsAffected() 733 | 734 | } 735 | 736 | // DeleteById remove a model by id 737 | func (m *OrganizationModel) DeleteById(ctx context.Context, id int64) (int64, error) { 738 | return m.Condition(query.Builder().Where("id", "=", id)).Delete(ctx) 739 | } 740 | --------------------------------------------------------------------------------