v0.1.4 Added simple cascade load support; added more data type supports.

This commit is contained in:
Lunny Xiao 2013-06-04 16:56:59 +08:00
parent 75fe787648
commit 527b5b3f7a
10 changed files with 255 additions and 107 deletions

View File

@ -2,7 +2,7 @@
[中文](https://github.com/lunny/xorm/blob/master/README_CN.md)
xorm is an ORM for Go. It makes dabatabse operating simple.
xorm is a simple and powerful ORM for Go. It makes dabatabse operating simple.
It's not entirely ready for product use yet, but it's getting there.
@ -16,6 +16,7 @@ Drivers for Go's sql package which currently support database/sql includes:
## Changelog
* **v0.1.4** : Added simple cascade load support; added more data type supports.
* **v0.1.3** : Find function now supports both slice and map; Add Table function for multi tables and temperory tables support
* **v0.1.2** : Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction
* **v0.1.1** : Add Id, In functions and improved README
@ -33,6 +34,9 @@ Drivers for Go's sql package which currently support database/sql includes:
* Support Id, In, Where, Limit, Join, Having functions and sturct as query conditions
* Support simple cascade load just like Hibernate for Java
## Installing xorm
go get github.com/lunny/xorm
@ -111,11 +115,14 @@ var user = User{Name:"xlw"}
err := engine.Get(&user)
```
6.Fetch multipe objects, use Find
6.Fetch multipe objects into a slice or a map, use Find
```Go
var everyone []Userinfo
err := engine.Find(&everyone)
users := make(map[int64]Userinfo)
err := engine.Find(&users)
```
6.1 also you can use Where, Limit
@ -293,7 +300,7 @@ Another is use field tag, field tag support the below keywords which split with
<td>pk</td><td>the field is a primary key</td>
</tr>
<tr>
<td>int(11)/varchar(50)</td><td>column type</td>
<td>int(11)/varchar(50)/text/date/datetime/blob/decimal(26,2)</td><td>column type</td>
</tr>
<tr>
<td>autoincr</td><td>auto incrment</td>

View File

@ -16,6 +16,7 @@ xorm是一个Go语言的ORM库. 通过它可以使数据库操作非常简便。
## 更新日志
* **v0.1.4** : Get函数和Find函数新增简单的级联载入功能对更多的数据库类型支持。
* **v0.1.3** : Find函数现在支持传入Slice或者Map当传入Map时key为id新增Table函数以为多表和临时表进行支持。
* **v0.1.2** : Insert函数支持混合struct和slice指针传入并根据数据库类型自动批量插入同时自动添加事务
* **v0.1.1** : 添加 Id, In 函数,改善 README 文档
@ -108,11 +109,14 @@ var user = User{Name:"xlw"}
err := engine.Get(&user)
```
6.获取多个对象可以用Find方法
6.获取多个对象到一个Slice或一个Map对象中可以用Find方法
```Go
var everyone []Userinfo
err := engine.Find(&everyone)
users := make(map[int64]Userinfo)
err := engine.Find(&users)
```
6.1 你也可以使用Where和Limit方法设定条件和查询数量
@ -289,7 +293,7 @@ UserInfo中的成员UserName将会自动对应名为user_name的字段。
<td>pk</td><td>是否是Primary Key</td>
</tr>
<tr>
<td>int(11)/varchar(50)</td><td>字段类型</td>
<td>int(11)/varchar(50)/text/date/datetime/blob/decimal(26,2)</td><td>字段类型</td>
</tr>
<tr>
<td>autoincr</td><td>是否是自增</td>

View File

@ -16,11 +16,16 @@ const (
MYMYSQL = "mymysql"
)
type dialect interface {
SqlType(t *Column) string
}
type Engine struct {
Mapper IMapper
TagIdentifier string
DriverName string
DataSourceName string
Dialect dialect
Tables map[reflect.Type]Table
AutoIncrement string
ShowSQL bool
@ -119,8 +124,6 @@ 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.Columns = make(map[string]Column)
var pkCol *Column = nil
var pkstr = ""
for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag
@ -129,7 +132,8 @@ func (engine *Engine) MapType(t reflect.Type) Table {
fieldType := t.Field(i).Type
if ormTagStr != "" {
col = Column{FieldName: t.Field(i).Name, Nullable: true}
col = Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false,
IsAutoIncrement: false}
ormTagStr = strings.ToLower(ormTagStr)
tags := strings.Split(ormTagStr, " ")
// TODO:
@ -143,7 +147,6 @@ func (engine *Engine) MapType(t reflect.Type) Table {
case k == "pk":
col.IsPrimaryKey = true
col.Nullable = false
pkCol = &col
case k == "null":
col.Nullable = (tags[j-1] != "not")
case k == "autoincr":
@ -158,7 +161,7 @@ func (engine *Engine) MapType(t reflect.Type) Table {
col.Length, _ = strconv.Atoi(lens)
case strings.HasPrefix(k, "varchar"):
col.SQLType = Varchar
lens := k[len("decimal")+1 : len(k)-1]
lens := k[len("varchar")+1 : len(k)-1]
col.Length, _ = strconv.Atoi(lens)
case strings.HasPrefix(k, "decimal"):
col.SQLType = Decimal
@ -168,6 +171,10 @@ func (engine *Engine) MapType(t reflect.Type) Table {
col.Length2, _ = strconv.Atoi(twolen[1])
case k == "date":
col.SQLType = Date
case k == "datetime":
col.SQLType = DateTime
case k == "timestamp":
col.SQLType = TimeStamp
case k == "not":
default:
if k != col.Default {
@ -189,31 +196,23 @@ func (engine *Engine) MapType(t reflect.Type) Table {
if col.Name == "" {
col.Name = engine.Mapper.Obj2Table(t.Field(i).Name)
}
if col.IsPrimaryKey {
table.PrimaryKey = col.Name
}
}
if col.Name == "" {
} 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}
}
table.Columns[col.Name] = col
if strings.ToLower(t.Field(i).Name) == "id" {
pkstr = col.Name
}
}
if pkCol == nil {
if pkstr != "" {
col := table.Columns[pkstr]
if col.Name == "id" {
col.IsPrimaryKey = true
col.IsAutoIncrement = true
col.Nullable = false
col.Length = Int.DefaultLength
table.PrimaryKey = col.Name
}
} else {
table.PrimaryKey = pkCol.Name
}
table.Columns[col.Name] = col
}
return table

19
mysql.go Normal file
View File

@ -0,0 +1,19 @@
package xorm
import "strconv"
type mysql struct {
}
func (db mysql) SqlType(c *Column) string {
switch t := c.SQLType; t {
case Date, DateTime, TimeStamp:
return "DATETIME"
case Varchar:
return t.Name + "(" + strconv.Itoa(c.Length) + ")"
case Decimal:
return t.Name + "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
default:
return t.Name
}
}

View File

@ -59,6 +59,13 @@ func (session *Session) OrderBy(order string) *Session {
return session
}
func (session *Session) Cascade(trueOrFalse ...bool) *Session {
if len(trueOrFalse) >= 1 {
session.Statement.UseCascade = trueOrFalse[0]
}
return session
}
//The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (session *Session) Join(join_operator, tablename, condition string) *Session {
session.Statement.Join(join_operator, tablename, condition)
@ -130,6 +137,10 @@ func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]b
switch structField.Type().Kind() {
case reflect.Slice:
v = data
case reflect.Array:
if structField.Type().Elem() == reflect.TypeOf(b) {
v = data
}
case reflect.String:
v = string(data)
case reflect.Bool:
@ -160,10 +171,7 @@ func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]b
v = x
//Now only support Time type
case reflect.Struct:
if structField.Type().String() != "time.Time" {
return errors.New("unsupported struct type in Scan: " + structField.Type().String())
}
if structField.Type().String() == "time.Time" {
x, err := time.Parse("2006-01-02 15:04:05", string(data))
if err != nil {
x, err = time.Parse("2006-01-02 15:04:05.000 -0700", string(data))
@ -174,6 +182,33 @@ func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]b
}
v = x
} else if session.Statement.UseCascade {
session.Engine.AutoMapType(structField.Type())
if _, ok := session.Engine.Tables[structField.Type()]; ok {
x, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return errors.New("arg " + key + " as int: " + err.Error())
}
if x != 0 {
structInter := reflect.New(structField.Type())
session.Statement.Init()
err = session.Id(x).Get(structInter.Interface())
if err != nil {
return err
}
v = structInter.Elem().Interface()
} else {
//fmt.Println("zero value of struct type " + structField.Type().String())
continue
}
} else {
fmt.Println("unsupported struct type in Scan: " + structField.Type().String())
continue
}
}
default:
return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
}
@ -205,6 +240,7 @@ func (session *Session) Exec(sql string, args ...interface{}) (sql.Result, error
}
if session.Engine.ShowSQL {
fmt.Println(sql)
fmt.Println(args)
}
if session.IsAutoCommit {
return session.innerExec(sql, args...)
@ -226,6 +262,8 @@ func (session *Session) Get(bean interface{}) error {
defer statement.Init()
statement.Limit(1)
fmt.Println(bean)
sql, args := statement.genGetSql(bean)
resultsSlice, err := session.Query(sql, args...)
@ -321,6 +359,7 @@ func (session *Session) Query(sql string, paramStr ...interface{}) (resultsSlice
}
if session.Engine.ShowSQL {
fmt.Println(sql)
fmt.Println(paramStr)
}
s, err := session.Db.Prepare(sql)
if err != nil {
@ -377,9 +416,12 @@ func (session *Session) Query(sql string, paramStr ...interface{}) (resultsSlice
result[key] = []byte(str)
//时间类型
case reflect.Struct:
if aa.String() == "time.Time" {
str = rawValue.Interface().(time.Time).Format("2006-01-02 15:04:05.000 -0700")
result[key] = []byte(str)
}
}
//default:
}
resultsSlice = append(resultsSlice, result)
@ -465,7 +507,13 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) {
if col.IsAutoIncrement && fieldValue.Int() == 0 {
continue
}
if table, ok := session.Engine.Tables[fieldValue.Type()]; ok {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumn().FieldName)
fmt.Println(pkField.Interface())
args = append(args, pkField.Interface())
} else {
args = append(args, val)
}
colNames = append(colNames, col.Name)
cols = append(cols, col)
colPlaces = append(colPlaces, "?")
@ -477,7 +525,12 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) {
if col.IsAutoIncrement && fieldValue.Int() == 0 {
continue
}
if table, ok := session.Engine.Tables[fieldValue.Type()]; ok {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumn().FieldName)
args = append(args, pkField.Interface())
} else {
args = append(args, val)
}
colPlaces = append(colPlaces, "?")
}
}
@ -517,7 +570,12 @@ func (session *Session) InsertOne(bean interface{}) (int64, error) {
if col.IsAutoIncrement && fieldValue.Int() == 0 {
continue
}
if table, ok := session.Engine.Tables[fieldValue.Type()]; ok {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumn().FieldName)
args = append(args, pkField.Interface())
} else {
args = append(args, val)
}
colNames = append(colNames, col.Name)
colPlaces = append(colPlaces, "?")
}

23
sqlite3.go Normal file
View File

@ -0,0 +1,23 @@
package xorm
type sqlite3 struct {
}
func (db sqlite3) SqlType(c *Column) string {
switch t := c.SQLType; t {
case Date, DateTime, TimeStamp:
return "NUMERIC"
case Char, Varchar, Text:
return "TEXT"
case TinyInt, SmallInt, MediumInt, Int, BigInt:
return "INTEGER"
case Float, Double:
return "REAL"
case Decimal:
return "NUMERIC"
case Blob:
return "BLOB"
default:
return t.Name
}
}

View File

@ -3,7 +3,7 @@ package xorm
import (
"fmt"
"reflect"
"strconv"
//"strconv"
"strings"
"time"
)
@ -21,6 +21,7 @@ type Statement struct {
HavingStr string
ColumnStr string
AltTableName string
UseCascade bool
BeanArgs []interface{}
}
@ -39,6 +40,7 @@ func (statement *Statement) Init() {
statement.WhereStr = ""
statement.Params = make([]interface{}, 0)
statement.OrderStr = ""
statement.UseCascade = true
statement.JoinStr = ""
statement.GroupByStr = ""
statement.HavingStr = ""
@ -82,7 +84,17 @@ func BuildConditions(engine *Engine, table *Table, bean interface{}) ([]string,
default:
continue
}
if table, ok := engine.Tables[fieldValue.Type()]; ok {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumn().FieldName)
fmt.Println(pkField.Interface())
if pkField.Int() != 0 {
args = append(args, pkField.Interface())
} else {
continue
}
} else {
args = append(args, val)
}
colNames = append(colNames, engine.QuoteIdentifier+col.Name+engine.QuoteIdentifier+"=?")
}
@ -150,38 +162,30 @@ func (statement *Statement) Having(conditions string) {
func (statement *Statement) genColumnStr(col *Column) string {
sql := "`" + col.Name + "` "
if col.SQLType == Date {
sql += " datetime "
} else {
if statement.Engine.DriverName == SQLITE && col.IsPrimaryKey {
sql += "integer"
} else {
sql += col.SQLType.Name
}
if statement.Engine.DriverName != SQLITE && col.SQLType != Text {
if col.SQLType != Decimal {
sql += "(" + strconv.Itoa(col.Length) + ")"
} else {
sql += "(" + strconv.Itoa(col.Length) + "," + strconv.Itoa(col.Length2) + ")"
}
}
}
if col.Nullable {
sql += " NULL "
} else {
sql += " NOT NULL "
}
//fmt.Println(key)
sql += statement.Engine.Dialect.SqlType(col) + " "
if col.IsPrimaryKey {
sql += "PRIMARY KEY "
}
if col.IsAutoIncrement {
sql += statement.Engine.AutoIncrement + " "
}
if col.Nullable {
sql += "NULL "
} else {
sql += "NOT NULL "
}
if col.IsUnique {
sql += "Unique "
}
if col.Default != "" {
sql += "DEFAULT " + col.Default + " "
}
return sql
}
@ -198,7 +202,8 @@ func (statement *Statement) genCreateSQL() string {
sql := "CREATE TABLE IF NOT EXISTS `" + statement.TableName() + "` ("
for _, col := range statement.RefTable.Columns {
sql += statement.genColumnStr(&col)
sql += ","
sql = strings.TrimSpace(sql)
sql += ", "
}
sql = sql[:len(sql)-2] + ");"
return sql

View File

@ -2,7 +2,7 @@ package xorm
import (
"reflect"
"strconv"
//"strconv"
//"strings"
"time"
)
@ -14,35 +14,49 @@ type SQLType struct {
}
var (
Int = SQLType{"int", 11, 0}
Char = SQLType{"char", 1, 0}
Bool = SQLType{"int", 1, 0}
Varchar = SQLType{"varchar", 50, 0}
Text = SQLType{"text", 16, 0}
Date = SQLType{"date", 24, 0}
Decimal = SQLType{"decimal", 26, 2}
Float = SQLType{"float", 31, 0}
Double = SQLType{"double", 31, 0}
TinyInt = SQLType{"TINYINT", 0, 0}
SmallInt = SQLType{"SMALLINT", 0, 0}
MediumInt = SQLType{"MEDIUMINT", 0, 0}
Int = SQLType{"INT", 11, 0}
BigInt = SQLType{"BIGINT", 0, 0}
Char = SQLType{"CHAR", 1, 0}
Varchar = SQLType{"VARCHAR", 64, 0}
Text = SQLType{"TEXT", 16, 0}
Date = SQLType{"DATE", 24, 0}
DateTime = SQLType{"DATETIME", 0, 0}
Decimal = SQLType{"DECIMAL", 26, 2}
Float = SQLType{"FLOAT", 31, 0}
Double = SQLType{"DOUBLE", 31, 0}
Blob = SQLType{"BLOB", 0, 0}
TimeStamp = SQLType{"TIMESTAMP", 0, 0}
)
func (sqlType SQLType) genSQL(length int) string {
if sqlType == Date {
return " datetime "
}
return sqlType.Name + "(" + strconv.Itoa(length) + ")"
}
var b byte
var tm time.Time
func Type2SQLType(t reflect.Type) (st SQLType) {
switch k := t.Kind(); k {
case reflect.Int, reflect.Int32, reflect.Int64:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
st = Int
case reflect.Int64, reflect.Uint64:
st = BigInt
case reflect.Float32:
st = Float
case reflect.Float64:
st = Double
case reflect.Complex64, reflect.Complex128:
st = Varchar
case reflect.Array, reflect.Slice:
if t.Elem() == reflect.TypeOf(b) {
st = Blob
}
case reflect.Bool:
st = Bool
st = TinyInt
case reflect.String:
st = Varchar
case reflect.Struct:
if t == reflect.TypeOf(time.Time{}) {
st = Date
if t == reflect.TypeOf(tm) {
st = DateTime
}
default:
st = Varchar

View File

@ -13,17 +13,14 @@ func Create(driverName string, dataSourceName string) Engine {
engine.InsertMany = true
engine.TagIdentifier = "xorm"
if driverName == SQLITE {
engine.Dialect = sqlite3{}
engine.AutoIncrement = "AUTOINCREMENT"
} else {
engine.Dialect = mysql{}
engine.AutoIncrement = "AUTO_INCREMENT"
}
if engine.DriverName == PQSQL {
engine.QuoteIdentifier = "\""
} else if engine.DriverName == MSSQL {
engine.QuoteIdentifier = ""
} else {
engine.QuoteIdentifier = "`"
}
return engine
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
_ "github.com/Go-SQL-Driver/MySQL"
_ "github.com/mattn/go-sqlite3"
"os"
"testing"
"time"
"xorm"
@ -26,15 +27,19 @@ CREATE TABLE `userdeatail` (
*/
type Userinfo struct {
Uid int `xorm:"id pk not null autoincr"`
Uid int64 `xorm:"id pk not null autoincr"`
Username string
Departname string
Alias string `xorm:"-"`
Created time.Time
Detail Userdetail `xorm:"detail_id int(11)"`
Height float64
Avatar []byte
IsMan bool
}
type Userdetail struct {
Uid int `xorm:"id pk not null"`
Id int64
Intro string `xorm:"text"`
Profile string `xorm:"varchar(2000)"`
}
@ -71,7 +76,8 @@ func mapper(t *testing.T) {
}
func insert(t *testing.T) {
user := Userinfo{1, "xiaolunwen", "dev", "lunny", time.Now()}
user := Userinfo{1, "xiaolunwen", "dev", "lunny", time.Now(),
Userdetail{Id: 1}, 1.78, []byte{1, 2, 3}, true}
_, err := engine.Insert(&user)
if err != nil {
t.Error(err)
@ -98,7 +104,8 @@ func exec(t *testing.T) {
func insertAutoIncr(t *testing.T) {
// auto increment insert
user := Userinfo{Username: "xiaolunwen", Departname: "dev", Alias: "lunny", Created: time.Now()}
user := Userinfo{Username: "xiaolunwen", Departname: "dev", Alias: "lunny", Created: time.Now(),
Detail: Userdetail{Id: 1}, Height: 1.78, Avatar: []byte{1, 2, 3}, IsMan: true}
_, err := engine.Insert(&user)
if err != nil {
t.Error(err)
@ -135,8 +142,9 @@ func insertMulti(t *testing.T) {
}
func insertTwoTable(t *testing.T) {
userinfo := Userinfo{Username: "xlw3", Departname: "dev", Alias: "lunny4", Created: time.Now()}
userdetail := Userdetail{Uid: 1, Intro: "I'm a very beautiful women.", Profile: "sfsaf"}
userdetail := Userdetail{Id: 1, Intro: "I'm a very beautiful women.", Profile: "sfsaf"}
userinfo := Userinfo{Username: "xlw3", Departname: "dev", Alias: "lunny4", Created: time.Now(), Detail: userdetail}
_, err := engine.Insert(&userinfo, &userdetail)
if err != nil {
t.Error(err)
@ -176,6 +184,16 @@ func get(t *testing.T) {
fmt.Println(user)
}
func cascadeGet(t *testing.T) {
user := Userinfo{Uid: 11}
err := engine.Get(&user)
if err != nil {
t.Error(err)
}
fmt.Println(user)
}
func find(t *testing.T) {
users := make([]Userinfo, 0)
@ -380,7 +398,7 @@ func createMultiTables(t *testing.T) {
user := &Userinfo{}
session.Begin()
for i := 0; i < 100; i++ {
for i := 0; i < 10; i++ {
err = session.Table(fmt.Sprintf("user_%v", i)).CreateTable(user)
if err != nil {
session.Rollback()
@ -425,6 +443,7 @@ func tableOp(t *testing.T) {
}
func TestMysql(t *testing.T) {
// You should drop all tables before executing this testing
engine = xorm.Create("mysql", "root:123@/test?charset=utf8")
engine.ShowSQL = true
@ -439,6 +458,7 @@ func TestMysql(t *testing.T) {
update(t)
delete(t)
get(t)
cascadeGet(t)
find(t)
findMap(t)
count(t)
@ -456,6 +476,7 @@ func TestMysql(t *testing.T) {
}
func TestSqlite(t *testing.T) {
os.Remove("./test.db")
engine = xorm.Create("sqlite3", "./test.db")
engine.ShowSQL = true
@ -470,6 +491,7 @@ func TestSqlite(t *testing.T) {
update(t)
delete(t)
get(t)
cascadeGet(t)
find(t)
findMap(t)
count(t)