434 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2014 beego Author. All Rights Reserved.
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//      http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package orm
 | 
						|
 | 
						|
import (
 | 
						|
	"database/sql"
 | 
						|
	"reflect"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// Driver define database driver
 | 
						|
type Driver interface {
 | 
						|
	Name() string
 | 
						|
	Type() DriverType
 | 
						|
}
 | 
						|
 | 
						|
// Fielder define field info
 | 
						|
type Fielder interface {
 | 
						|
	String() string
 | 
						|
	FieldType() int
 | 
						|
	SetRaw(interface{}) error
 | 
						|
	RawValue() interface{}
 | 
						|
}
 | 
						|
 | 
						|
// Ormer define the orm interface
 | 
						|
type Ormer interface {
 | 
						|
	// read data to model
 | 
						|
	// for example:
 | 
						|
	//	this will find User by Id field
 | 
						|
	// 	u = &User{Id: user.Id}
 | 
						|
	// 	err = Ormer.Read(u)
 | 
						|
	//	this will find User by UserName field
 | 
						|
	// 	u = &User{UserName: "astaxie", Password: "pass"}
 | 
						|
	//	err = Ormer.Read(u, "UserName")
 | 
						|
	Read(md interface{}, cols ...string) error
 | 
						|
	// Like Read(), but with "FOR UPDATE" clause, useful in transaction.
 | 
						|
	// Some databases are not support this feature.
 | 
						|
	ReadForUpdate(md interface{}, cols ...string) error
 | 
						|
	// Try to read a row from the database, or insert one if it doesn't exist
 | 
						|
	ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)
 | 
						|
	// insert model data to database
 | 
						|
	// for example:
 | 
						|
	//  user := new(User)
 | 
						|
	//  id, err = Ormer.Insert(user)
 | 
						|
	//  user must a pointer and Insert will set user's pk field
 | 
						|
	Insert(interface{}) (int64, error)
 | 
						|
	// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
 | 
						|
	// if colu type is integer : can use(+-*/), string : convert(colu,"value")
 | 
						|
	// postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value")
 | 
						|
	// if colu type is integer : can use(+-*/), string : colu || "value"
 | 
						|
	InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error)
 | 
						|
	// insert some models to database
 | 
						|
	InsertMulti(bulk int, mds interface{}) (int64, error)
 | 
						|
	// update model to database.
 | 
						|
	// cols set the columns those want to update.
 | 
						|
	// find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns
 | 
						|
	// for example:
 | 
						|
	// user := User{Id: 2}
 | 
						|
	//	user.Langs = append(user.Langs, "zh-CN", "en-US")
 | 
						|
	//	user.Extra.Name = "beego"
 | 
						|
	//	user.Extra.Data = "orm"
 | 
						|
	//	num, err = Ormer.Update(&user, "Langs", "Extra")
 | 
						|
	Update(md interface{}, cols ...string) (int64, error)
 | 
						|
	// delete model in database
 | 
						|
	Delete(md interface{}, cols ...string) (int64, error)
 | 
						|
	// load related models to md model.
 | 
						|
	// args are limit, offset int and order string.
 | 
						|
	//
 | 
						|
	// example:
 | 
						|
	// 	Ormer.LoadRelated(post,"Tags")
 | 
						|
	// 	for _,tag := range post.Tags{...}
 | 
						|
	//args[0] bool true useDefaultRelsDepth ; false  depth 0
 | 
						|
	//args[0] int  loadRelationDepth
 | 
						|
	//args[1] int limit default limit 1000
 | 
						|
	//args[2] int offset default offset 0
 | 
						|
	//args[3] string order  for example : "-Id"
 | 
						|
	// make sure the relation is defined in model struct tags.
 | 
						|
	LoadRelated(md interface{}, name string, args ...interface{}) (int64, error)
 | 
						|
	// create a models to models queryer
 | 
						|
	// for example:
 | 
						|
	// 	post := Post{Id: 4}
 | 
						|
	// 	m2m := Ormer.QueryM2M(&post, "Tags")
 | 
						|
	QueryM2M(md interface{}, name string) QueryM2Mer
 | 
						|
	// return a QuerySeter for table operations.
 | 
						|
	// table name can be string or struct.
 | 
						|
	// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
 | 
						|
	QueryTable(ptrStructOrTableName interface{}) QuerySeter
 | 
						|
	// switch to another registered database driver by given name.
 | 
						|
	Using(name string) error
 | 
						|
	// begin transaction
 | 
						|
	// for example:
 | 
						|
	// 	o := NewOrm()
 | 
						|
	// 	err := o.Begin()
 | 
						|
	// 	...
 | 
						|
	// 	err = o.Rollback()
 | 
						|
	Begin() error
 | 
						|
	// commit transaction
 | 
						|
	Commit() error
 | 
						|
	// rollback transaction
 | 
						|
	Rollback() error
 | 
						|
	// return a raw query seter for raw sql string.
 | 
						|
	// for example:
 | 
						|
	//	 ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec()
 | 
						|
	//	// update user testing's name to slene
 | 
						|
	Raw(query string, args ...interface{}) RawSeter
 | 
						|
	Driver() Driver
 | 
						|
}
 | 
						|
 | 
						|
// Inserter insert prepared statement
 | 
						|
type Inserter interface {
 | 
						|
	Insert(interface{}) (int64, error)
 | 
						|
	Close() error
 | 
						|
}
 | 
						|
 | 
						|
// QuerySeter query seter
 | 
						|
type QuerySeter interface {
 | 
						|
	// add condition expression to QuerySeter.
 | 
						|
	// for example:
 | 
						|
	//	filter by UserName == 'slene'
 | 
						|
	//	qs.Filter("UserName", "slene")
 | 
						|
	//	sql : left outer join profile on t0.id1==t1.id2 where t1.age == 28
 | 
						|
	//	Filter("profile__Age", 28)
 | 
						|
	// 	 // time compare
 | 
						|
	//	qs.Filter("created", time.Now())
 | 
						|
	Filter(string, ...interface{}) QuerySeter
 | 
						|
	// add NOT condition to querySeter.
 | 
						|
	// have the same usage as Filter
 | 
						|
	Exclude(string, ...interface{}) QuerySeter
 | 
						|
	// set condition to QuerySeter.
 | 
						|
	// sql's where condition
 | 
						|
	//	cond := orm.NewCondition()
 | 
						|
	//	cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000)
 | 
						|
	//	//sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` >  2000
 | 
						|
	//	num, err := qs.SetCond(cond1).Count()
 | 
						|
	SetCond(*Condition) QuerySeter
 | 
						|
	// add LIMIT value.
 | 
						|
	// args[0] means offset, e.g. LIMIT num,offset.
 | 
						|
	// if Limit <= 0 then Limit will be set to default limit ,eg 1000
 | 
						|
	// if QuerySeter doesn't call Limit, the sql's Limit will be set to default limit, eg 1000
 | 
						|
	//  for example:
 | 
						|
	//	qs.Limit(10, 2)
 | 
						|
	//	// sql-> limit 10 offset 2
 | 
						|
	Limit(limit interface{}, args ...interface{}) QuerySeter
 | 
						|
	// add OFFSET value
 | 
						|
	// same as Limit function's args[0]
 | 
						|
	Offset(offset interface{}) QuerySeter
 | 
						|
	// add GROUP BY expression
 | 
						|
	// for example:
 | 
						|
	//	qs.GroupBy("id")
 | 
						|
	GroupBy(exprs ...string) QuerySeter
 | 
						|
	// add ORDER expression.
 | 
						|
	// "column" means ASC, "-column" means DESC.
 | 
						|
	// for example:
 | 
						|
	//	qs.OrderBy("-status")
 | 
						|
	OrderBy(exprs ...string) QuerySeter
 | 
						|
	// set relation model to query together.
 | 
						|
	// it will query relation models and assign to parent model.
 | 
						|
	// for example:
 | 
						|
	//	// will load all related fields use left join .
 | 
						|
	// 	qs.RelatedSel().One(&user)
 | 
						|
	//	// will  load related field only profile
 | 
						|
	//	qs.RelatedSel("profile").One(&user)
 | 
						|
	//	user.Profile.Age = 32
 | 
						|
	RelatedSel(params ...interface{}) QuerySeter
 | 
						|
	// Set Distinct
 | 
						|
	// for example:
 | 
						|
	//  o.QueryTable("policy").Filter("Groups__Group__Users__User", user).
 | 
						|
	//    Distinct().
 | 
						|
	//    All(&permissions)
 | 
						|
	Distinct() QuerySeter
 | 
						|
	// return QuerySeter execution result number
 | 
						|
	// for example:
 | 
						|
	//	num, err = qs.Filter("profile__age__gt", 28).Count()
 | 
						|
	Count() (int64, error)
 | 
						|
	// check result empty or not after QuerySeter executed
 | 
						|
	// the same as QuerySeter.Count > 0
 | 
						|
	Exist() bool
 | 
						|
	// execute update with parameters
 | 
						|
	// for example:
 | 
						|
	//	num, err = qs.Filter("user_name", "slene").Update(Params{
 | 
						|
	//		"Nums": ColValue(Col_Minus, 50),
 | 
						|
	//	}) // user slene's Nums will minus 50
 | 
						|
	//	num, err = qs.Filter("UserName", "slene").Update(Params{
 | 
						|
	//		"user_name": "slene2"
 | 
						|
	//	}) // user slene's  name will change to slene2
 | 
						|
	Update(values Params) (int64, error)
 | 
						|
	// delete from table
 | 
						|
	//for example:
 | 
						|
	//	num ,err = qs.Filter("user_name__in", "testing1", "testing2").Delete()
 | 
						|
	// 	//delete two user  who's name is testing1 or testing2
 | 
						|
	Delete() (int64, error)
 | 
						|
	// return a insert queryer.
 | 
						|
	// it can be used in times.
 | 
						|
	// example:
 | 
						|
	// 	i,err := sq.PrepareInsert()
 | 
						|
	// 	num, err = i.Insert(&user1) // user table will add one record user1 at once
 | 
						|
	//	num, err = i.Insert(&user2) // user table will add one record user2 at once
 | 
						|
	//	err = i.Close() //don't forget call Close
 | 
						|
	PrepareInsert() (Inserter, error)
 | 
						|
	// query all data and map to containers.
 | 
						|
	// cols means the columns when querying.
 | 
						|
	// for example:
 | 
						|
	//	var users []*User
 | 
						|
	//	qs.All(&users) // users[0],users[1],users[2] ...
 | 
						|
	All(container interface{}, cols ...string) (int64, error)
 | 
						|
	// query one row data and map to containers.
 | 
						|
	// cols means the columns when querying.
 | 
						|
	// for example:
 | 
						|
	//	var user User
 | 
						|
	//	qs.One(&user) //user.UserName == "slene"
 | 
						|
	One(container interface{}, cols ...string) error
 | 
						|
	// query all data and map to []map[string]interface.
 | 
						|
	// expres means condition expression.
 | 
						|
	// it converts data to []map[column]value.
 | 
						|
	// for example:
 | 
						|
	//	var maps []Params
 | 
						|
	//	qs.Values(&maps) //maps[0]["UserName"]=="slene"
 | 
						|
	Values(results *[]Params, exprs ...string) (int64, error)
 | 
						|
	// query all data and map to [][]interface
 | 
						|
	// it converts data to [][column_index]value
 | 
						|
	// for example:
 | 
						|
	//	var list []ParamsList
 | 
						|
	//	qs.ValuesList(&list) // list[0][1] == "slene"
 | 
						|
	ValuesList(results *[]ParamsList, exprs ...string) (int64, error)
 | 
						|
	// query all data and map to []interface.
 | 
						|
	// it's designed for one column record set, auto change to []value, not [][column]value.
 | 
						|
	// for example:
 | 
						|
	//	var list ParamsList
 | 
						|
	//	qs.ValuesFlat(&list, "UserName") // list[0] == "slene"
 | 
						|
	ValuesFlat(result *ParamsList, expr string) (int64, error)
 | 
						|
	// query all rows into map[string]interface with specify key and value column name.
 | 
						|
	// keyCol = "name", valueCol = "value"
 | 
						|
	// table data
 | 
						|
	// name  | value
 | 
						|
	// total | 100
 | 
						|
	// found | 200
 | 
						|
	// to map[string]interface{}{
 | 
						|
	// 	"total": 100,
 | 
						|
	// 	"found": 200,
 | 
						|
	// }
 | 
						|
	RowsToMap(result *Params, keyCol, valueCol string) (int64, error)
 | 
						|
	// query all rows into struct with specify key and value column name.
 | 
						|
	// keyCol = "name", valueCol = "value"
 | 
						|
	// table data
 | 
						|
	// name  | value
 | 
						|
	// total | 100
 | 
						|
	// found | 200
 | 
						|
	// to struct {
 | 
						|
	// 	Total int
 | 
						|
	// 	Found int
 | 
						|
	// }
 | 
						|
	RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error)
 | 
						|
}
 | 
						|
 | 
						|
// QueryM2Mer model to model query struct
 | 
						|
// all operations are on the m2m table only, will not affect the origin model table
 | 
						|
type QueryM2Mer interface {
 | 
						|
	// add models to origin models when creating queryM2M.
 | 
						|
	// example:
 | 
						|
	// 	m2m := orm.QueryM2M(post,"Tag")
 | 
						|
	// 	m2m.Add(&Tag1{},&Tag2{})
 | 
						|
	//  	for _,tag := range post.Tags{}{ ... }
 | 
						|
	// param could also be any of the follow
 | 
						|
	// 	[]*Tag{{Id:3,Name: "TestTag1"}, {Id:4,Name: "TestTag2"}}
 | 
						|
	//	&Tag{Id:5,Name: "TestTag3"}
 | 
						|
	//	[]interface{}{&Tag{Id:6,Name: "TestTag4"}}
 | 
						|
	// insert one or more rows to m2m table
 | 
						|
	// make sure the relation is defined in post model struct tag.
 | 
						|
	Add(...interface{}) (int64, error)
 | 
						|
	// remove models following the origin model relationship
 | 
						|
	// only delete rows from m2m table
 | 
						|
	// for example:
 | 
						|
	//tag3 := &Tag{Id:5,Name: "TestTag3"}
 | 
						|
	//num, err = m2m.Remove(tag3)
 | 
						|
	Remove(...interface{}) (int64, error)
 | 
						|
	// check model is existed in relationship of origin model
 | 
						|
	Exist(interface{}) bool
 | 
						|
	// clean all models in related of origin model
 | 
						|
	Clear() (int64, error)
 | 
						|
	// count all related models of origin model
 | 
						|
	Count() (int64, error)
 | 
						|
}
 | 
						|
 | 
						|
// RawPreparer raw query statement
 | 
						|
type RawPreparer interface {
 | 
						|
	Exec(...interface{}) (sql.Result, error)
 | 
						|
	Close() error
 | 
						|
}
 | 
						|
 | 
						|
// RawSeter raw query seter
 | 
						|
// create From Ormer.Raw
 | 
						|
// for example:
 | 
						|
//  sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q)
 | 
						|
//  rs := Ormer.Raw(sql, 1)
 | 
						|
type RawSeter interface {
 | 
						|
	//execute sql and get result
 | 
						|
	Exec() (sql.Result, error)
 | 
						|
	//query data and map to container
 | 
						|
	//for example:
 | 
						|
	//	var name string
 | 
						|
	//	var id int
 | 
						|
	//	rs.QueryRow(&id,&name) // id==2 name=="slene"
 | 
						|
	QueryRow(containers ...interface{}) error
 | 
						|
 | 
						|
	// query data rows and map to container
 | 
						|
	//	var ids []int
 | 
						|
	//	var names []int
 | 
						|
	//	query = fmt.Sprintf("SELECT 'id','name' FROM %suser%s", Q, Q)
 | 
						|
	//	num, err = dORM.Raw(query).QueryRows(&ids,&names) // ids=>{1,2},names=>{"nobody","slene"}
 | 
						|
	QueryRows(containers ...interface{}) (int64, error)
 | 
						|
	SetArgs(...interface{}) RawSeter
 | 
						|
	// query data to []map[string]interface
 | 
						|
	// see QuerySeter's Values
 | 
						|
	Values(container *[]Params, cols ...string) (int64, error)
 | 
						|
	// query data to [][]interface
 | 
						|
	// see QuerySeter's ValuesList
 | 
						|
	ValuesList(container *[]ParamsList, cols ...string) (int64, error)
 | 
						|
	// query data to []interface
 | 
						|
	// see QuerySeter's ValuesFlat
 | 
						|
	ValuesFlat(container *ParamsList, cols ...string) (int64, error)
 | 
						|
	// query all rows into map[string]interface with specify key and value column name.
 | 
						|
	// keyCol = "name", valueCol = "value"
 | 
						|
	// table data
 | 
						|
	// name  | value
 | 
						|
	// total | 100
 | 
						|
	// found | 200
 | 
						|
	// to map[string]interface{}{
 | 
						|
	// 	"total": 100,
 | 
						|
	// 	"found": 200,
 | 
						|
	// }
 | 
						|
	RowsToMap(result *Params, keyCol, valueCol string) (int64, error)
 | 
						|
	// query all rows into struct with specify key and value column name.
 | 
						|
	// keyCol = "name", valueCol = "value"
 | 
						|
	// table data
 | 
						|
	// name  | value
 | 
						|
	// total | 100
 | 
						|
	// found | 200
 | 
						|
	// to struct {
 | 
						|
	// 	Total int
 | 
						|
	// 	Found int
 | 
						|
	// }
 | 
						|
	RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error)
 | 
						|
 | 
						|
	// return prepared raw statement for used in times.
 | 
						|
	// for example:
 | 
						|
	// 	pre, err := dORM.Raw("INSERT INTO tag (name) VALUES (?)").Prepare()
 | 
						|
	// 	r, err := pre.Exec("name1") // INSERT INTO tag (name) VALUES (`name1`)
 | 
						|
	Prepare() (RawPreparer, error)
 | 
						|
}
 | 
						|
 | 
						|
// stmtQuerier statement querier
 | 
						|
type stmtQuerier interface {
 | 
						|
	Close() error
 | 
						|
	Exec(args ...interface{}) (sql.Result, error)
 | 
						|
	Query(args ...interface{}) (*sql.Rows, error)
 | 
						|
	QueryRow(args ...interface{}) *sql.Row
 | 
						|
}
 | 
						|
 | 
						|
// db querier
 | 
						|
type dbQuerier interface {
 | 
						|
	Prepare(query string) (*sql.Stmt, error)
 | 
						|
	Exec(query string, args ...interface{}) (sql.Result, error)
 | 
						|
	Query(query string, args ...interface{}) (*sql.Rows, error)
 | 
						|
	QueryRow(query string, args ...interface{}) *sql.Row
 | 
						|
}
 | 
						|
 | 
						|
// type DB interface {
 | 
						|
// 	Begin() (*sql.Tx, error)
 | 
						|
// 	Prepare(query string) (stmtQuerier, error)
 | 
						|
// 	Exec(query string, args ...interface{}) (sql.Result, error)
 | 
						|
// 	Query(query string, args ...interface{}) (*sql.Rows, error)
 | 
						|
// 	QueryRow(query string, args ...interface{}) *sql.Row
 | 
						|
// }
 | 
						|
 | 
						|
// transaction beginner
 | 
						|
type txer interface {
 | 
						|
	Begin() (*sql.Tx, error)
 | 
						|
}
 | 
						|
 | 
						|
// transaction ending
 | 
						|
type txEnder interface {
 | 
						|
	Commit() error
 | 
						|
	Rollback() error
 | 
						|
}
 | 
						|
 | 
						|
// base database struct
 | 
						|
type dbBaser interface {
 | 
						|
	Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error
 | 
						|
	Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
 | 
						|
	InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
 | 
						|
	InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
 | 
						|
	InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
 | 
						|
	InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
 | 
						|
	Update(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
 | 
						|
	Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
 | 
						|
	ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error)
 | 
						|
	SupportUpdateJoin() bool
 | 
						|
	UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
 | 
						|
	DeleteBatch(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
 | 
						|
	Count(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
 | 
						|
	OperatorSQL(string) string
 | 
						|
	GenerateOperatorSQL(*modelInfo, *fieldInfo, string, []interface{}, *time.Location) (string, []interface{})
 | 
						|
	GenerateOperatorLeftCol(*fieldInfo, string, *string)
 | 
						|
	PrepareInsert(dbQuerier, *modelInfo) (stmtQuerier, string, error)
 | 
						|
	ReadValues(dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error)
 | 
						|
	RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error)
 | 
						|
	MaxLimit() uint64
 | 
						|
	TableQuote() string
 | 
						|
	ReplaceMarks(*string)
 | 
						|
	HasReturningID(*modelInfo, *string) bool
 | 
						|
	TimeFromDB(*time.Time, *time.Location)
 | 
						|
	TimeToDB(*time.Time, *time.Location)
 | 
						|
	DbTypes() map[string]string
 | 
						|
	GetTables(dbQuerier) (map[string]bool, error)
 | 
						|
	GetColumns(dbQuerier, string) (map[string][3]string, error)
 | 
						|
	ShowTablesQuery() string
 | 
						|
	ShowColumnsQuery(string) string
 | 
						|
	IndexExists(dbQuerier, string, string) bool
 | 
						|
	collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error)
 | 
						|
	setval(dbQuerier, *modelInfo, []string) error
 | 
						|
}
 |