package rest import ( "context" "encoding/csv" "encoding/json" "fmt" "git.nspix.com/golang/micro/gateway/http" errpkg "git.nspix.com/golang/rest/v3/error" "git.nspix.com/golang/rest/v3/inflector" "gorm.io/gorm" "gorm.io/gorm/clause" httppkg "net/http" "path" "reflect" "strconv" "strings" ) const ( HttpAccessDenied = 8004 //拒绝访问 HttpInvalidPayload = 8002 //请求内容无效 HttpRequestCallbackFailed = 8003 //执行回调失败 HttpValidateFailed = 8008 //数据校验失败 HttpDatabaseQueryFailed = 8010 //查询失败 HttpDatabaseFindFailed = 8011 //查找失败 HttpDatabaseCreateFailed = 8012 //创建失败 HttpDatabaseUpdateFailed = 8013 //更新失败 HttpDatabaseDeleteFailed = 8014 //删除失败 HttpDatabaseExportFailed = 8015 //数据导出失败 HTTPUnknownFailed = 9001 //未知错误 ) const ( ErrorAccessDeniedMessage = "access denied" ) const ( restCtxRequest = "rest-ctx-request" restCtxResponse = "rest-ctx-response" restCtxHttpctx = "rest-ctx-httpctx" ) const ( OperatorEqual = "eq" OperatorGreaterThan = "gt" OperatorGreaterEqual = "ge" OperatorLessThan = "lt" OperatorLessEqual = "le" OperatorLike = "like" OperatorBetween = "between" ) type ( DiffAttr struct { Column string `json:"column"` Label string `json:"label"` OldValue interface{} `json:"old_value"` NewValue interface{} `json:"new_value"` } Condition struct { Column string `json:"column"` Expr string `json:"expr"` Value interface{} `json:"value,omitempty"` Values []interface{} `json:"values,omitempty"` } Restful struct { model Model opts *Options reflectType reflect.Type reflectValue reflect.Value singularName string pluralizeName string statement *gorm.Statement primaryKey string } ) // getScenarioUrl 获取某个场景下HTTP请求的URL func (r *Restful) getScenarioUrl(scenario string) string { var uri string switch scenario { case ScenarioList: uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.pluralizeName case ScenarioView: uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "/:id" case ScenarioCreate: uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName case ScenarioUpdate: uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "/:id" case ScenarioDelete: uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "/:id" case ScenarioExport: uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "-export" } return path.Clean(uri) } // getScenarioHandle 获取某个场景下HTTP请求的处理回调 func (r *Restful) getScenarioHandle(scenario string) http.HandleFunc { var handleFunc http.HandleFunc switch scenario { case ScenarioList: handleFunc = r.actionIndex case ScenarioView: handleFunc = r.actionView case ScenarioCreate: handleFunc = r.actionCreate case ScenarioUpdate: handleFunc = r.actionUpdate case ScenarioDelete: handleFunc = r.actionDelete case ScenarioExport: handleFunc = r.actionExport } return handleFunc } func (r *Restful) setFieldValue(model reflect.Value, column string, value interface{}) { var ( name string ) refVal := reflect.Indirect(model) for _, field := range r.statement.Schema.Fields { if field.DBName == column { name = field.Name break } else if field.Name == column { name = column break } } if name == "" { return } fieldVal := refVal.FieldByName(name) if fieldVal.CanSet() { fieldVal.Set(reflect.ValueOf(value)) } } //getFieldValue get field value from reflect value func (r *Restful) getFieldValue(model reflect.Value, column string) interface{} { var ( name string ) refVal := reflect.Indirect(model) for _, field := range r.statement.Schema.Fields { if field.DBName == column { name = field.Name break } else if field.Name == column { name = column break } } if name == "" { return nil } fieldVal := refVal.FieldByName(name) return fieldVal.Interface() } func (r *Restful) hasScenario(s string) bool { if v, ok := r.model.(FlexibleModel); ok { for _, n := range v.Scenarios() { if s == n { return true } } return false } return true } func (r *Restful) findCondition(schema *Schema, conditions []*Condition) *Condition { for _, cond := range conditions { if cond.Column == schema.Column { return cond } } return nil } func (r *Restful) prepareConditions(ctx context.Context, requestCtx *http.Context, query *Query, schemas []*Schema) (err error) { var ( ok bool skip bool formValue string model interface{} activeModel ActiveModel ) model = reflect.New(r.reflectType).Interface() if r.opts.Delegate != nil { if err = r.opts.Delegate.BeforeQuery(ctx, query); err != nil { return } } if activeModel, ok = model.(ActiveModel); ok { if err = activeModel.OnBeforeQuery(ctx, query); err != nil { return } } if requestCtx.Request().Method == httppkg.MethodPut || requestCtx.Request().Method == httppkg.MethodPost { conditions := make([]*Condition, 0) if err = requestCtx.Bind(&conditions); err != nil { return } for _, schema := range schemas { cond := r.findCondition(schema, conditions) if cond == nil { continue } switch schema.Format { case FormatInteger, FormatFloat, FormatTimestamp, FormatDatetime, FormatDate, FormatTime: switch cond.Expr { case OperatorBetween: if len(cond.Values) == 2 { query.AndFilterWhere(NewCond(schema.Column, cond.Values[0]).WithExpr(">=")) query.AndFilterWhere(NewCond(schema.Column, cond.Values[1]).WithExpr("<=")) } case OperatorGreaterThan: query.AndFilterWhere(NewCond(schema.Column, cond.Value).WithExpr(">")) case OperatorGreaterEqual: query.AndFilterWhere(NewCond(schema.Column, cond.Value).WithExpr(">=")) case OperatorLessThan: query.AndFilterWhere(NewCond(schema.Column, cond.Value).WithExpr("<")) case OperatorLessEqual: query.AndFilterWhere(NewCond(schema.Column, cond.Value).WithExpr("<=")) default: query.AndFilterWhere(NewCond(schema.Column, cond.Value)) } default: switch cond.Expr { case OperatorLike: query.AndFilterWhere(NewCond(schema.Column, cond.Value).WithExpr("LIKE")) default: query.AndFilterWhere(NewCond(schema.Column, cond.Value)) } } } } else { //处理默认的搜索 for _, schema := range schemas { skip = false if skip { continue } if schema.Native == 0 { continue } formValue = requestCtx.FormValue(schema.Column) switch schema.Format { case FormatString, FormatText: if schema.Attribute.Match == MatchExactly { query.AndFilterWhere(NewCond(schema.Column, formValue)) } else { query.AndFilterWhere(NewCond(schema.Column, formValue).WithExpr("LIKE")) } case FormatTime, FormatDate, FormatDatetime, FormatTimestamp: var sep string seps := []byte{',', '/'} for _, s := range seps { if strings.IndexByte(formValue, s) > -1 { sep = string(s) } } if ss := strings.Split(formValue, sep); len(ss) == 2 { query.AndFilterWhere( NewCond(schema.Column, strings.TrimSpace(ss[0])).WithExpr(">="), NewCond(schema.Column, strings.TrimSpace(ss[1])).WithExpr("<="), ) } else { query.AndFilterWhere(NewCond(schema.Column, formValue)) } case FormatInteger, FormatFloat: query.AndFilterWhere(NewCond(schema.Column, formValue)) default: if schema.Type == TypeString { if schema.Attribute.Match == MatchExactly { query.AndFilterWhere(NewCond(schema.Column, formValue)) } else { query.AndFilterWhere(NewCond(schema.Column, formValue).WithExpr("LIKE")) } } else { query.AndFilterWhere(NewCond(schema.Column, formValue)) } } } } //处理排序 sortPar := requestCtx.FormValue("sort") if sortPar != "" { sorts := strings.Split(sortPar, ",") for _, s := range sorts { if s[0] == '-' { query.OrderBy(s[1:], "DESC") } else { if s[0] == '+' { query.OrderBy(s[1:], "ASC") } else { query.OrderBy(s, "ASC") } } } } if activeModel, ok = model.(ActiveModel); ok { err = activeModel.OnAfterQuery(ctx, query) } if r.opts.Delegate != nil { err = r.opts.Delegate.AfterQuery(ctx, query) } return } func (r *Restful) createContext(httpCtx *http.Context) context.Context { ctx := httpCtx.Request().Context() if ctx == nil { ctx = context.Background() } ctx = context.WithValue(ctx, restCtxRequest, httpCtx.Request()) ctx = context.WithValue(ctx, restCtxResponse, httpCtx.Response()) ctx = context.WithValue(ctx, restCtxHttpctx, httpCtx) return ctx } func (r *Restful) actionIndex(httpCtx *http.Context) (err error) { var ( page int pageSize int pageIndex int query *Query namespace string ) if !r.hasScenario(ScenarioList) { return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage) } ctx := r.createContext(httpCtx) namespace = httpCtx.ParamValue(NamespaceVariable) page, _ = strconv.Atoi(httpCtx.FormValue("page")) pageSize, _ = strconv.Atoi(httpCtx.FormValue("pagesize")) if pageSize <= 0 { pageSize = 15 } pageIndex = page if pageIndex > 0 { pageIndex-- } sliceValue := reflect.MakeSlice(reflect.SliceOf(r.reflectType), 0, 0) models := reflect.New(sliceValue.Type()) models.Elem().Set(sliceValue) query = NewQuery(r.opts.DB) searchSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioSearch) indexSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioList) if err = r.prepareConditions(ctx, httpCtx, query, searchSchemas); err != nil { return httpCtx.Error(HttpDatabaseQueryFailed, err.Error()) } if r.opts.EnableNamespace { query.AndFilterWhere(NewQueryCondition(NamespaceField, namespace)) } query.Offset(pageIndex * pageSize).Limit(pageSize) if err = query.All(models.Interface()); err != nil { return httpCtx.Error(HttpDatabaseQueryFailed, err.Error()) } resp := &ListResponse{ Page: page, PageSize: pageSize, TotalCount: query.Limit(0).Offset(0).Count(r.model), } if resp.TotalCount > 0 { resp.Data = r.opts.Formatter.formatModels(ctx, models.Interface(), indexSchemas, r.statement) } else { resp.Data = make([]string, 0) } return httpCtx.Success(resp) } func (r *Restful) actionCreate(httpCtx *http.Context) (err error) { var ( errTx error namespace string model interface{} schemas []*Schema refModel reflect.Value diffAttrs []*DiffAttr ) ctx := r.createContext(httpCtx) diffAttrs = make([]*DiffAttr, 0, 10) if !r.hasScenario(ScenarioCreate) { return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage) } namespace = httpCtx.ParamValue(NamespaceVariable) refModel = reflect.New(r.reflectType) model = refModel.Interface() if err = httpCtx.Bind(model); err != nil { return httpCtx.Error(HttpInvalidPayload, err.Error()) } schemas = r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioCreate) //global set field value r.setFieldValue(refModel, NamespaceField, namespace) r.setFieldValue(refModel, CreatedByField, httpCtx.ParamValue(UserVariable)) r.setFieldValue(refModel, CreatedDeptField, httpCtx.ParamValue(DepartmentVariable)) r.setFieldValue(refModel, UpdatedByField, httpCtx.ParamValue(UserVariable)) r.setFieldValue(refModel, UpdatedDeptField, httpCtx.ParamValue(DepartmentVariable)) if err = r.opts.DB.Transaction(func(tx *gorm.DB) error { if r.opts.Delegate != nil { if errTx = r.opts.Delegate.BeforeCreate(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } if errTx = r.opts.Delegate.BeforeSave(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } if activeModel, ok := model.(ActiveModel); ok { if errTx = activeModel.OnBeforeCreate(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } if errTx = activeModel.OnBeforeSave(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } //创建数据 if errTx = tx.Create(model).Error; errTx != nil { return errTx } //对比差异数据 for _, scm := range schemas { diffAttrs = append(diffAttrs, &DiffAttr{ Column: scm.Column, Label: scm.Label, OldValue: nil, NewValue: r.getFieldValue(refModel, scm.Column), }) } if activeModel, ok := model.(ActiveModel); ok { if errTx = activeModel.OnAfterCreate(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, err.Error()) } if errTx = activeModel.OnAfterSave(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, err.Error()) } } if r.opts.Delegate != nil { if errTx = r.opts.Delegate.AfterCreate(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, err.Error()) } if errTx = r.opts.Delegate.AfterSave(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, err.Error()) } } return errTx }); err == nil { pkVal := r.getFieldValue(refModel, r.primaryKey) return httpCtx.Success(&CreateResponse{ ID: pkVal, Topic: r.model.TableName(), State: "created", }) } //form validation if validateError, ok := err.(*errpkg.StructError); ok { httpCtx.Response().Header().Set("Content-Type", "application/json") return json.NewEncoder(httpCtx.Response()).Encode(map[string]interface{}{ "errno": HttpValidateFailed, "result": validateError, }) } return httpCtx.Error(HttpDatabaseCreateFailed, err.Error()) } func (r *Restful) actionUpdate(httpCtx *http.Context) (err error) { var ( errTx error namespace string model interface{} schemas []*Schema refModel reflect.Value oldValues = make(map[string]interface{}) diffs = make(map[string]interface{}) diffAttrs = make([]*DiffAttr, 0) ) ctx := r.createContext(httpCtx) if !r.hasScenario(ScenarioUpdate) { return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage) } namespace = httpCtx.ParamValue(NamespaceVariable) idStr := httpCtx.ParamValue("id") refModel = reflect.New(r.reflectType) model = refModel.Interface() //默认设置更新用户 r.setFieldValue(refModel, UpdatedByField, httpCtx.ParamValue(UserVariable)) r.setFieldValue(refModel, UpdatedDeptField, httpCtx.ParamValue(DepartmentVariable)) conditions := map[string]interface{}{ r.primaryKey: idStr, } if r.opts.EnableNamespace { conditions[NamespaceField] = namespace } if err = r.opts.DB.Where(conditions).First(model).Error; err != nil { return httpCtx.Error(HttpDatabaseFindFailed, err.Error()) } schemas = r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioUpdate) for _, scm := range schemas { oldValues[scm.Column] = r.getFieldValue(refModel, scm.Column) } if err = httpCtx.Bind(model); err != nil { return httpCtx.Error(HttpInvalidPayload, err.Error()) } if err = r.opts.DB.Transaction(func(tx *gorm.DB) error { if r.opts.Delegate != nil { if errTx = r.opts.Delegate.BeforeUpdate(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } if errTx = r.opts.Delegate.BeforeSave(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } if activeModel, ok := model.(ActiveModel); ok { if errTx = activeModel.OnBeforeUpdate(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } if errTx = activeModel.OnBeforeSave(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } //对比差异数据 for _, scm := range schemas { v := r.getFieldValue(refModel, scm.Column) if oldValues[scm.Column] != v { diffs[scm.Column] = v diffAttrs = append(diffAttrs, &DiffAttr{ Column: scm.Column, Label: scm.Label, OldValue: oldValues[scm.Column], NewValue: v, }) } } //进行局部数据更新 if len(diffs) > 0 { if errTx = tx.Model(model).Updates(diffs).Error; errTx != nil { return errTx } } if activeModel, ok := model.(ActiveModel); ok { if errTx = activeModel.OnAfterUpdate(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } if errTx = activeModel.OnAfterSave(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } if r.opts.Delegate != nil { if errTx = r.opts.Delegate.AfterUpdate(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } if errTx = r.opts.Delegate.AfterSave(ctx, model, diffAttrs); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } return errTx }); err == nil { pkVal := r.getFieldValue(refModel, r.primaryKey) return httpCtx.Success(&UpdateResponse{ ID: pkVal, Topic: r.model.TableName(), State: "updated", }) } //form validation if validateError, ok := err.(*errpkg.StructError); ok { httpCtx.Response().Header().Set("Content-Type", "application/json") return json.NewEncoder(httpCtx.Response()).Encode(map[string]interface{}{ "errno": HttpValidateFailed, "result": validateError, }) } return httpCtx.Error(HttpDatabaseUpdateFailed, err.Error()) } func (r *Restful) actionDelete(httpCtx *http.Context) (err error) { var ( errTx error model interface{} namespace string ) if !r.hasScenario(ScenarioDelete) { return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage) } ctx := r.createContext(httpCtx) idStr := httpCtx.ParamValue("id") namespace = httpCtx.ParamValue(NamespaceVariable) model = reflect.New(r.reflectType).Interface() conditions := map[string]interface{}{ r.primaryKey: idStr, } if r.opts.EnableNamespace { conditions[NamespaceField] = namespace } if err = r.opts.DB.Where(conditions).First(model).Error; err != nil { return httpCtx.Error(HttpDatabaseFindFailed, err.Error()) } if err = r.opts.DB.Transaction(func(tx *gorm.DB) error { if r.opts.Delegate != nil { if errTx = r.opts.Delegate.BeforeDelete(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } if activeModel, ok := model.(ActiveModel); ok { if errTx = activeModel.OnBeforeDelete(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } if errTx = tx.Delete(model).Error; errTx != nil { return errTx } if activeModel, ok := model.(ActiveModel); ok { if errTx = activeModel.OnAfterDelete(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } if r.opts.Delegate != nil { if errTx = r.opts.Delegate.AfterDelete(ctx, model); errTx != nil { return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error()) } } return errTx }); err == nil { return httpCtx.Success(&DeleteResponse{ ID: r.getFieldValue(reflect.ValueOf(model), r.primaryKey), Topic: r.model.TableName(), State: "updated", }) } else { return httpCtx.Error(HttpDatabaseDeleteFailed, err.Error()) } } func (r *Restful) actionExport(httpCtx *http.Context) (err error) { var ( query *Query namespace string ) if !r.hasScenario(ScenarioExport) { return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage) } ctx := r.createContext(httpCtx) namespace = httpCtx.ParamValue(NamespaceVariable) sliceValue := reflect.MakeSlice(reflect.SliceOf(r.reflectType), 0, 0) models := reflect.New(sliceValue.Type()) models.Elem().Set(sliceValue) query = NewQuery(r.opts.DB) searchSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioSearch) exportSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioList) if err = r.prepareConditions(ctx, httpCtx, query, searchSchemas); err != nil { return httpCtx.Error(HttpDatabaseQueryFailed, err.Error()) } if r.opts.EnableNamespace { query.AndFilterWhere(NewQueryCondition(NamespaceVariable, namespace)) } if err = query.All(models.Interface()); err != nil { return httpCtx.Error(HttpDatabaseExportFailed, err.Error()) } httpCtx.Response().Header().Set("Content-Type", "text/csv") httpCtx.Response().Header().Set("Access-Control-Expose-Headers", "Content-Disposition") httpCtx.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=%s.csv", r.singularName)) value := r.opts.Formatter.formatModels(ctx, models.Interface(), exportSchemas, r.statement) writer := csv.NewWriter(httpCtx.Response()) ss := make([]string, len(exportSchemas)) for i, field := range exportSchemas { ss[i] = field.Label } _ = writer.Write(ss) if values, ok := value.([]interface{}); ok { for _, val := range values { row, ok2 := val.(map[string]interface{}) if !ok2 { continue } for i, field := range exportSchemas { if v, ok := row[field.Column]; ok { ss[i] = fmt.Sprint(v) } else { ss[i] = "" } } _ = writer.Write(ss) } } writer.Flush() return } func (r *Restful) actionView(httpCtx *http.Context) (err error) { var ( model interface{} namespace string ) if !r.hasScenario(ScenarioView) { return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage) } ctx := r.createContext(httpCtx) namespace = httpCtx.ParamValue(NamespaceVariable) scenario := httpCtx.FormValue("scenario") idStr := httpCtx.ParamValue("id") model = reflect.New(r.reflectType).Interface() conditions := map[string]interface{}{ r.primaryKey: idStr, } if r.opts.EnableNamespace { conditions[NamespaceField] = namespace } if err = r.opts.DB.Where(conditions).First(model).Error; err != nil { return httpCtx.Error(HttpDatabaseFindFailed, err.Error()) } if httpCtx.FormValue("format") != "" { //获取指定场景下面的字段进行渲染显示 var schemas []*Schema if scenario == "" { schemas = r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioView) } else { schemas = r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), scenario) } return httpCtx.Success(r.opts.Formatter.formatModel(ctx, model, schemas, r.statement)) } return httpCtx.Success(model) } func newRestful(model Model, opts *Options) *Restful { var ( ok bool err error tableName string dynamicModel *Dynamic ) r := &Restful{ opts: opts, model: model, } if dynamicModel, ok = model.(*Dynamic); ok { r.reflectValue = reflect.New(dynamicModel.Type()).Elem() r.reflectType = dynamicModel.Type() } else { r.reflectValue = reflect.Indirect(reflect.ValueOf(model)) r.reflectType = r.reflectValue.Type() } tableName = model.TableName() r.singularName = inflector.Singularize(tableName) r.pluralizeName = inflector.Pluralize(tableName) r.statement = &gorm.Statement{ DB: r.opts.DB, ConnPool: r.opts.DB.ConnPool, Clauses: map[string]clause.Clause{}, } if dynamicModel, ok = model.(*Dynamic); ok { err = r.statement.Parse(dynamicModel.Interface()) } else { err = r.statement.Parse(model) } if err == nil { if r.statement.Schema != nil { if r.statement.Schema.PrimaryFieldDBNames != nil && len(r.statement.Schema.PrimaryFieldDBNames) > 0 { r.primaryKey = r.statement.Schema.PrimaryFieldDBNames[0] } } } return r }