diff --git a/README.md b/README.md index d0ec995d..d85e445b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Drivers for Go's sql package which currently support database/sql includes: ## Changelog +* **v0.1.8** : Added union index and union unique supported, please see [Mapping Rules](#mapping). * **v0.1.7** : Added IConnectPool interface and NoneConnectPool, SysConnectPool, SimpleConnectPool the three implements. You can choose one of them and the default is SysConnectPool. You can customrize your own connection pool. struct Engine added Close method, It should be invoked before system exit. * **v0.1.6** : Added conversion interface support; added struct derive support; added single mapping support * **v0.1.5** : Added multi threads support; added Sql() function for struct query; Get function changed return inteface; MakeSession and Create are instead with NewSession and NewEngine. @@ -304,7 +305,7 @@ Another is use field tag, field tag support the below keywords which split with - + @@ -319,7 +320,10 @@ Another is use field tag, field tag support the below keywords which split with - + + + + @@ -334,7 +338,7 @@ For Example ```Go type Userinfo struct { Uid int `xorm:"id pk not null autoincr"` - Username string + Username string `xorm:"unique"` Departname string Alias string `xorm:"-"` Created time.Time diff --git a/README_CN.md b/README_CN.md index d5301117..7251f024 100644 --- a/README_CN.md +++ b/README_CN.md @@ -16,6 +16,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 更新日志 +* **v0.1.8** : 新增联合index,联合unique支持,请查看[映射规则](#mapping)。 * **v0.1.7** : 新增IConnectPool接口以及NoneConnectPool, SysConnectPool, SimpleConnectPool三种实现,可以选择不使用连接池,使用系统连接池和使用自带连接池三种实现,默认为SysConnectPool,即系统自带的连接池。同时支持自定义连接池。Engine新增Close方法,在系统退出时应调用此方法。 * **v0.1.6** : 新增Conversion,支持自定义类型到数据库类型的转换;新增查询结构体自动检测匿名成员支持;新增单向映射支持; * **v0.1.5** : 新增对多线程的支持;新增Sql()函数;支持任意sql语句的struct查询;Get函数返回值变动;MakeSession和Create函数被NewSession和NewEngine函数替代; @@ -68,7 +69,12 @@ import ( ) engine, err = xorm.NewEngine("sqlite3", "./test.db") defer engine.Close() -``` +``` + +具体连接请参考: +Mysql:https://github.com/go-sql-driver/mysql#dsn-data-source-name +Sqlite: + 1.1.默认将不会显示自动生成的SQL语句,如果要显示,则需要设置 @@ -305,10 +311,10 @@ UserInfo中的成员UserName将会自动对应名为user_name的字段。
namecolumn namenamecolumn name, if no this name, the name is auto generated according field name and mapper rule.
pkthe field is a primary key[not ]nullif column can be null value
uniqueuniqueunique or unique(uniquename)unique or union unique as uniquename
index or index(indexname)index or union index as indexname
extendsused in anonymous struct means mapping this struct's fields to table
- + - + @@ -320,7 +326,10 @@ UserInfo中的成员UserName将会自动对应名为user_name的字段。 - + + + + @@ -334,7 +343,7 @@ UserInfo中的成员UserName将会自动对应名为user_name的字段。 ```Go type Userinfo struct { Uid int `xorm:"id pk not null autoincr"` - Username string + Username string `xorm:"unique"` Departname string Alias string `xorm:"-"` Created time.Time diff --git a/engine.go b/engine.go index 9eca5b97..dab313be 100644 --- a/engine.go +++ b/engine.go @@ -116,6 +116,16 @@ func (engine *Engine) Id(id int64) *Session { return session.Id(id) } +func (engine *Engine) Charset(charset string) *Session { + session := engine.NewSession() + return session.Charset(charset) +} + +func (engine *Engine) StoreEngine(storeEngine string) *Session { + session := engine.NewSession() + return session.StoreEngine(storeEngine) +} + func (engine *Engine) In(column string, args ...interface{}) *Session { session := engine.NewSession() return session.In(column, args...) @@ -170,7 +180,8 @@ func (engine *Engine) AutoMap(bean interface{}) *Table { } func (engine *Engine) MapType(t reflect.Type) *Table { - table := &Table{Name: engine.Mapper.Obj2Table(t.Name()), Type: t} + table := &Table{Name: engine.Mapper.Obj2Table(t.Name()), Type: t, + Indexes: map[string][]string{}, Uniques: map[string][]string{}} table.Columns = make(map[string]Column) for i := 0; i < t.NumField(); i++ { @@ -227,23 +238,43 @@ func (engine *Engine) MapType(t reflect.Type) *Table { col.Length, _ = strconv.Atoi(lens) } case strings.HasPrefix(k, "varchar"): - col.SQLType = Varchar - lens := k[len("varchar")+1 : len(k)-1] - col.Length, _ = strconv.Atoi(lens) + if k == "varchar" { + col.SQLType = Varchar + col.Length = Varchar.DefaultLength + col.Length2 = Varchar.DefaultLength2 + } else { + col.SQLType = Varchar + lens := k[len("varchar")+1 : len(k)-1] + col.Length, _ = strconv.Atoi(lens) + } case strings.HasPrefix(k, "decimal"): col.SQLType = Decimal lens := k[len("decimal")+1 : len(k)-1] twolen := strings.Split(lens, ",") col.Length, _ = strconv.Atoi(twolen[0]) col.Length2, _ = strconv.Atoi(twolen[1]) + case strings.HasPrefix(k, "index"): + if k == "index" { + col.IndexName = "" + col.IndexType = SINGLEINDEX + } else { + col.IndexName = k[len("index")+1 : len(k)-1] + col.IndexType = UNIONINDEX + } + case strings.HasPrefix(k, "unique"): + if k == "unique" { + col.UniqueName = "" + col.UniqueType = SINGLEUNIQUE + } else { + col.UniqueName = k[len("unique")+1 : len(k)-1] + col.UniqueType = UNIONUNIQUE + } case k == "date": col.SQLType = Date case k == "datetime": col.SQLType = DateTime case k == "timestamp": col.SQLType = TimeStamp - case k == "unique": - col.IsUnique = true case k == "not": default: if k != col.Default { @@ -265,6 +296,28 @@ func (engine *Engine) MapType(t reflect.Type) *Table { if col.Name == "" { col.Name = engine.Mapper.Obj2Table(t.Field(i).Name) } + if col.IndexType == SINGLEINDEX { + col.IndexName = col.Name + table.Indexes[col.IndexName] = []string{col.Name} + } else if col.IndexType == UNIONINDEX { + if unionIdxes, ok := table.Indexes[col.IndexName]; ok { + table.Indexes[col.IndexName] = append(unionIdxes, col.Name) + } else { + table.Indexes[col.IndexName] = []string{col.Name} + } + } + + if col.UniqueType == SINGLEUNIQUE { + col.UniqueName = col.Name + table.Uniques[col.UniqueName] = []string{col.Name} + } else if col.UniqueType == UNIONUNIQUE { + if unionUniques, ok := table.Uniques[col.UniqueName]; ok { + table.Uniques[col.UniqueName] = append(unionUniques, col.Name) + } else { + table.Uniques[col.UniqueName] = []string{col.Name} + } + } + if col.IsPrimaryKey { table.PrimaryKey = col.Name } @@ -272,7 +325,7 @@ func (engine *Engine) MapType(t reflect.Type) *Table { } else { sqlType := Type2SQLType(fieldType) col = Column{engine.Mapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, - sqlType.DefaultLength, sqlType.DefaultLength2, true, "", false, false, false, TWOSIDES} + sqlType.DefaultLength, sqlType.DefaultLength2, true, "", NONEUNIQUE, "", NONEINDEX, "", false, false, TWOSIDES} if col.Name == "id" { col.IsPrimaryKey = true diff --git a/session.go b/session.go index 037b8708..42993d56 100644 --- a/session.go +++ b/session.go @@ -79,6 +79,16 @@ func (session *Session) OrderBy(order string) *Session { return session } +func (session *Session) StoreEngine(storeEngine string) *Session { + session.Statement.StoreEngine = storeEngine + return session +} + +func (session *Session) Charset(charset string) *Session { + session.Statement.Charset = charset + return session +} + func (session *Session) Cascade(trueOrFalse ...bool) *Session { if len(trueOrFalse) >= 1 { session.Statement.UseCascade = trueOrFalse[0] @@ -319,12 +329,33 @@ func (session *Session) Exec(sql string, args ...interface{}) (sql.Result, error return session.Tx.Exec(sql, args...) } +// this function create a table according a bean func (session *Session) CreateTable(bean interface{}) error { statement := session.Statement defer statement.Init() statement.RefTable = session.Engine.AutoMap(bean) sql := statement.genCreateSQL() - _, err := session.Exec(sql) + res, err := session.Exec(sql) + if err != nil { + return err + } + + affected, err := res.RowsAffected() + if err != nil { + return err + } + if affected > 0 { + sql = statement.genIndexSQL() + if len(sql) > 0 { + _, err = session.Exec(sql) + } + } + if err == nil && affected > 0 { + sql = statement.genUniqueSQL() + if len(sql) > 0 { + _, err = session.Exec(sql) + } + } return err } diff --git a/statement.go b/statement.go index 4edbcc38..b734a167 100644 --- a/statement.go +++ b/statement.go @@ -31,6 +31,9 @@ type Statement struct { RawSQL string RawParams []interface{} UseCascade bool + UseAutoJoin bool + StoreEngine string + Charset string BeanArgs []interface{} } @@ -142,7 +145,7 @@ func (statement *Statement) Id(id int64) { } func (statement *Statement) In(column string, args ...interface{}) { - inStr := fmt.Sprintf("%v in (%v)", column, strings.Join(MakeArray("?", len(args)), ",")) + inStr := fmt.Sprintf("%v IN (%v)", column, strings.Join(MakeArray("?", len(args)), ",")) if statement.WhereStr == "" { statement.WhereStr = inStr statement.Params = args @@ -199,9 +202,9 @@ func (statement *Statement) genColumnStr(col *Column) string { sql += "NOT NULL " } - if col.IsUnique { - sql += "Unique " - } + /*if col.UniqueType == SINGLEUNIQUE { + sql += "UNIQUE " + }*/ if col.Default != "" { sql += "DEFAULT " + col.Default + " " @@ -227,7 +230,32 @@ func (statement *Statement) genCreateSQL() string { sql = strings.TrimSpace(sql) sql += ", " } - sql = sql[:len(sql)-2] + ");" + sql = sql[:len(sql)-2] + ")" + if statement.StoreEngine != "" { + sql += " ENGINE=" + statement.StoreEngine + } + if statement.Charset != "" { + sql += " DEFAULT CHARSET " + statement.Charset + } + sql += ";" + return sql +} + +func (statement *Statement) genIndexSQL() string { + var sql string = "" + for indexName, cols := range statement.RefTable.Indexes { + sql += fmt.Sprintf("CREATE INDEX IF NOT EXISTS IDX_%v_%v ON %v (%v);", statement.TableName(), indexName, + statement.TableName(), strings.Join(cols, ",")) + } + return sql +} + +func (statement *Statement) genUniqueSQL() string { + var sql string = "" + for indexName, cols := range statement.RefTable.Uniques { + sql += fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS UQE_%v_%v ON %v (%v);", statement.TableName(), indexName, + statement.TableName(), strings.Join(cols, ",")) + } return sql } diff --git a/table.go b/table.go index c361b68b..f363b004 100644 --- a/table.go +++ b/table.go @@ -77,6 +77,18 @@ const ( ONLYFROMDB ) +const ( + NONEINDEX = iota + SINGLEINDEX + UNIONINDEX +) + +const ( + NONEUNIQUE = iota + SINGLEUNIQUE + UNIONUNIQUE +) + type Column struct { Name string FieldName string @@ -85,7 +97,10 @@ type Column struct { Length2 int Nullable bool Default string - IsUnique bool + UniqueType int + UniqueName string + IndexType int + IndexName string IsPrimaryKey bool IsAutoIncrement bool MapType int @@ -95,6 +110,8 @@ type Table struct { Name string Type reflect.Type Columns map[string]Column + Indexes map[string][]string + Uniques map[string][]string PrimaryKey string } diff --git a/testbase.go b/testbase.go index f56b8677..4e237f0f 100644 --- a/testbase.go +++ b/testbase.go @@ -23,8 +23,8 @@ CREATE TABLE `userdeatail` ( */ type Userinfo struct { - Uid int64 `xorm:"id pk not null autoincr"` - Username string + Uid int64 `xorm:"id pk not null autoincr"` + Username string `xorm:"unique"` Departname string Alias string `xorm:"-"` Created time.Time diff --git a/xorm.go b/xorm.go index 9692794e..94816f84 100644 --- a/xorm.go +++ b/xorm.go @@ -17,7 +17,7 @@ import ( ) const ( - version string = "0.1.6" + version string = "0.1.8" ) func NewEngine(driverName string, dataSourceName string) (*Engine, error) { @@ -26,16 +26,11 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { engine.Tables = make(map[reflect.Type]*Table) engine.mutex = &sync.Mutex{} - //engine.InsertMany = true engine.TagIdentifier = "xorm" - //engine.QuoteIdentifier = "`" if driverName == SQLITE { engine.Dialect = &sqlite3{} - //engine.AutoIncrement = "AUTOINCREMENT" - //engine.Pool = NoneConnectPool{} } else if driverName == MYSQL { engine.Dialect = &mysql{} - //engine.AutoIncrement = "AUTO_INCREMENT" } else { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) }
name当前field对应的字段的名称,可选name当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名
pk是否是Primary Keypk是否是Primary Key,当前仅支持int64类型
int(11)/varchar(50)/text/date/datetime/blob/decimal(26,2)字段类型[not ]null是否可以为空
unique是否是唯一unique或unique(uniquename)是否是唯一,如不加括号则该字段不允许重复,如加上括号,则括号中为联合唯一的名字,此时可以有另外一个或多个字段有相同的写法
index或index(indexname)是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时可以有另外一个或多个字段有相同的写法
extends应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中