From fcac4c057c05b8c8e1fdd101f8658df6045acfb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 26 Mar 2015 12:41:43 +0100 Subject: [PATCH] updated xorm, go-sqlite3 dependencies --- Godeps/Godeps.json | 8 +- .../src/github.com/go-xorm/core/README.md | 114 +++ .../src/github.com/go-xorm/core/column.go | 15 + .../src/github.com/go-xorm/core/dialect.go | 63 +- .../src/github.com/go-xorm/core/index.go | 25 +- .../src/github.com/go-xorm/core/mapper.go | 134 ++- .../github.com/go-xorm/core/mapper_test.go | 45 + .../src/github.com/go-xorm/core/pk.go | 17 +- .../src/github.com/go-xorm/core/pk_test.go | 11 + .../src/github.com/go-xorm/core/table.go | 11 +- .../src/github.com/go-xorm/core/type.go | 6 +- .../src/github.com/go-xorm/xorm/README.md | 4 +- .../src/github.com/go-xorm/xorm/README_CN.md | 16 +- .../src/github.com/go-xorm/xorm/VERSION | 2 +- .../src/github.com/go-xorm/xorm/doc.go | 13 +- .../src/github.com/go-xorm/xorm/engine.go | 90 +- .../src/github.com/go-xorm/xorm/helpers.go | 216 ++++ .../github.com/go-xorm/xorm/mssql_dialect.go | 2 +- .../github.com/go-xorm/xorm/mysql_dialect.go | 8 +- .../github.com/go-xorm/xorm/oracle_dialect.go | 159 ++- .../src/github.com/go-xorm/xorm/rows.go | 9 +- .../src/github.com/go-xorm/xorm/session.go | 946 ++++++++++-------- .../go-xorm/xorm/sqlite3_dialect.go | 18 +- .../src/github.com/go-xorm/xorm/statement.go | 114 ++- .../src/github.com/go-xorm/xorm/xorm.go | 13 +- .../src/github.com/mattn/go-sqlite3/README.md | 8 +- .../src/github.com/mattn/go-sqlite3/backup.go | 2 +- .../github.com/mattn/go-sqlite3/error_test.go | 6 + .../{sqlite3.c => sqlite3-binding.c} | 0 .../{sqlite3.h => sqlite3-binding.h} | 0 .../github.com/mattn/go-sqlite3/sqlite3.go | 188 +++- .../mattn/go-sqlite3/sqlite3_fts3_test.go | 83 ++ .../mattn/go-sqlite3/sqlite3_other.go | 2 +- .../mattn/go-sqlite3/sqlite3_test.go | 203 ++++ .../mattn/go-sqlite3/sqlite3_windows.go | 2 +- .../github.com/mattn/go-sqlite3/sqlite3ext.h | 2 +- 36 files changed, 1875 insertions(+), 680 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/go-xorm/core/README.md create mode 100644 Godeps/_workspace/src/github.com/go-xorm/core/mapper_test.go rename Godeps/_workspace/src/github.com/mattn/go-sqlite3/{sqlite3.c => sqlite3-binding.c} (100%) rename Godeps/_workspace/src/github.com/mattn/go-sqlite3/{sqlite3.h => sqlite3-binding.h} (100%) create mode 100644 Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_fts3_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 79b49db2f0a..857b045ddd8 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -25,12 +25,12 @@ }, { "ImportPath": "github.com/go-xorm/core", - "Rev": "a949e067ced1cb6e6ef5c38b6f28b074fa718f1e" + "Rev": "be6e7ac47dc57bd0ada25322fa526944f66ccaa6" }, { "ImportPath": "github.com/go-xorm/xorm", - "Comment": "v0.4.1-19-g5c23849", - "Rev": "5c23849a66f4593e68909bb6c1fa30651b5b0541" + "Comment": "v0.4.2-58-ge2889e5", + "Rev": "e2889e5517600b82905f1d2ba8b70deb71823ffe" }, { "ImportPath": "github.com/jtolds/gls", @@ -51,7 +51,7 @@ }, { "ImportPath": "github.com/mattn/go-sqlite3", - "Rev": "d10e2c8f62100097910367dee90a9bd89d426a44" + "Rev": "e28cd440fabdd39b9520344bc26829f61db40ece" }, { "ImportPath": "github.com/smartystreets/goconvey/convey", diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/README.md b/Godeps/_workspace/src/github.com/go-xorm/core/README.md new file mode 100644 index 00000000000..0ae94a584ab --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-xorm/core/README.md @@ -0,0 +1,114 @@ +Core is a lightweight wrapper of sql.DB. + +# Open +```Go +db, _ := core.Open(db, connstr) +``` + +# SetMapper +```Go +db.SetMapper(SameMapper()) +``` + +## Scan usage + +### Scan +```Go +rows, _ := db.Query() +for rows.Next() { + rows.Scan() +} +``` + +### ScanMap +```Go +rows, _ := db.Query() +for rows.Next() { + rows.ScanMap() +``` + +### ScanSlice + +You can use `[]string`, `[][]byte`, `[]interface{}`, `[]*string`, `[]sql.NullString` to ScanSclice. Notice, slice's length should be equal or less than select columns. + +```Go +rows, _ := db.Query() +cols, _ := rows.Columns() +for rows.Next() { + var s = make([]string, len(cols)) + rows.ScanSlice(&s) +} +``` + +```Go +rows, _ := db.Query() +cols, _ := rows.Columns() +for rows.Next() { + var s = make([]*string, len(cols)) + rows.ScanSlice(&s) +} +``` + +### ScanStruct +```Go +rows, _ := db.Query() +for rows.Next() { + rows.ScanStructByName() + rows.ScanStructByIndex() +} +``` + +## Query usage +```Go +rows, err := db.Query("select * from table where name = ?", name) + +user = User{ + Name:"lunny", +} +rows, err := db.QueryStruct("select * from table where name = ?Name", + &user) + +var user = map[string]interface{}{ + "name": "lunny", +} +rows, err = db.QueryMap("select * from table where name = ?name", + &user) +``` + +## QueryRow usage +```Go +row := db.QueryRow("select * from table where name = ?", name) + +user = User{ + Name:"lunny", +} +row := db.QueryRowStruct("select * from table where name = ?Name", + &user) + +var user = map[string]interface{}{ + "name": "lunny", +} +row = db.QueryRowMap("select * from table where name = ?name", + &user) +``` + +## Exec usage +```Go +db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", name, title, age, alias...) + +user = User{ + Name:"lunny", + Title:"test", + Age: 18, +} +result, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) + +var user = map[string]interface{}{ + "Name": "lunny", + "Title": "test", + "Age": 18, +} +result, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name,created) values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) +``` \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/column.go b/Godeps/_workspace/src/github.com/go-xorm/core/column.go index 18921ca7c4c..52468aa20e6 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/column.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/column.go @@ -121,6 +121,21 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { col.fieldPath = strings.Split(col.FieldName, ".") } + if dataStruct.Type().Kind() == reflect.Map { + var keyValue reflect.Value + + if len(col.fieldPath) == 1 { + keyValue = reflect.ValueOf(col.FieldName) + } else if len(col.fieldPath) == 2 { + keyValue = reflect.ValueOf(col.fieldPath[1]) + } else { + return nil, fmt.Errorf("Unsupported mutliderive %v", col.FieldName) + } + + fieldValue = dataStruct.MapIndex(keyValue) + return &fieldValue, nil + } + if len(col.fieldPath) == 1 { fieldValue = dataStruct.FieldByName(col.FieldName) } else if len(col.fieldPath) == 2 { diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/dialect.go b/Godeps/_workspace/src/github.com/go-xorm/core/dialect.go index 05375642610..43a22670913 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/dialect.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/dialect.go @@ -47,15 +47,13 @@ type Dialect interface { SupportInsertMany() bool SupportEngine() bool SupportCharset() bool + SupportDropIfExists() bool IndexOnTable() bool ShowCreateNull() bool IndexCheckSql(tableName, idxName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{}) - //ColumnCheckSql(tableName, colName string) (string, []interface{}) - //IsTableExist(tableName string) (bool, error) - //IsIndexExist(tableName string, idx *Index) (bool, error) IsColumnExist(tableName string, col *Column) (bool, error) CreateTableSql(table *Table, tableName, storeEngine, charset string) string @@ -65,15 +63,13 @@ type Dialect interface { ModifyColumnSql(tableName string, col *Column) string + //CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error + //MustDropTable(tableName string) error + GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) GetIndexes(tableName string) (map[string]*Index, error) - // Get data from db cell to a struct's field - //GetData(col *Column, fieldValue *reflect.Value, cellData interface{}) error - // Set field data to db - //SetData(col *Column, fieldValue *refelct.Value) (interface{}, error) - Filters() []Filter } @@ -144,6 +140,10 @@ func (db *Base) RollBackStr() string { return "ROLL BACK" } +func (db *Base) SupportDropIfExists() bool { + return true +} + func (db *Base) DropTableSql(tableName string) string { return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) } @@ -170,35 +170,52 @@ func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { return db.HasRecords(query, db.DbName, tableName, col.Name) } +/* +func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error { + sql, args := db.dialect.TableCheckSql(tableName) + rows, err := db.DB().Query(sql, args...) + if db.Logger != nil { + db.Logger.Info("[sql]", sql, args) + } + if err != nil { + return err + } + defer rows.Close() + + if rows.Next() { + return nil + } + + sql = db.dialect.CreateTableSql(table, tableName, storeEngine, charset) + _, err = db.DB().Exec(sql) + if db.Logger != nil { + db.Logger.Info("[sql]", sql) + } + return err +}*/ + func (db *Base) CreateIndexSql(tableName string, index *Index) string { quote := db.dialect.Quote var unique string var idxName string if index.Type == UniqueType { unique = " UNIQUE" - idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) - } else { - idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } - return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique, + idxName = index.XName(tableName) + return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique, quote(idxName), quote(tableName), quote(strings.Join(index.Cols, quote(",")))) } func (db *Base) DropIndexSql(tableName string, index *Index) string { quote := db.dialect.Quote - //var unique string - var idxName string = index.Name - if !strings.HasPrefix(idxName, "UQE_") && - !strings.HasPrefix(idxName, "IDX_") { - if index.Type == UniqueType { - idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) - } else { - idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) - } + var name string + if index.IsRegular { + name = index.XName(tableName) + } else { + name = index.Name } - return fmt.Sprintf("DROP INDEX %v ON %s", - quote(idxName), quote(tableName)) + return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName)) } func (db *Base) ModifyColumnSql(tableName string, col *Column) string { diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/index.go b/Godeps/_workspace/src/github.com/go-xorm/core/index.go index e8f447d7031..73b95175adc 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/index.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/index.go @@ -1,7 +1,9 @@ package core import ( + "fmt" "sort" + "strings" ) const ( @@ -11,9 +13,21 @@ const ( // database index type Index struct { - Name string - Type int - Cols []string + IsRegular bool + Name string + Type int + Cols []string +} + +func (index *Index) XName(tableName string) string { + if !strings.HasPrefix(index.Name, "UQE_") && + !strings.HasPrefix(index.Name, "IDX_") { + if index.Type == UniqueType { + return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) + } + return fmt.Sprintf("IDX_%v_%v", tableName, index.Name) + } + return index.Name } // add columns which will be composite index @@ -24,6 +38,9 @@ func (index *Index) AddColumn(cols ...string) { } func (index *Index) Equal(dst *Index) bool { + if index.Type != dst.Type { + return false + } if len(index.Cols) != len(dst.Cols) { return false } @@ -40,5 +57,5 @@ func (index *Index) Equal(dst *Index) bool { // new an index func NewIndex(name string, indexType int) *Index { - return &Index{name, indexType, make([]string, 0)} + return &Index{true, name, indexType, make([]string, 0)} } diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/mapper.go b/Godeps/_workspace/src/github.com/go-xorm/core/mapper.go index c00dc395211..bb72a156624 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/mapper.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/mapper.go @@ -9,7 +9,6 @@ import ( type IMapper interface { Obj2Table(string) string Table2Obj(string) string - TableName(string) string } type CacheMapper struct { @@ -56,10 +55,6 @@ func (m *CacheMapper) Table2Obj(t string) string { return o } -func (m *CacheMapper) TableName(t string) string { - return t -} - // SameMapper implements IMapper and provides same name between struct and // database table type SameMapper struct { @@ -73,10 +68,6 @@ func (m SameMapper) Table2Obj(t string) string { return t } -func (m SameMapper) TableName(t string) string { - return t -} - // SnakeMapper implements IMapper and provides name transaltion between // struct and database table type SnakeMapper struct { @@ -97,25 +88,6 @@ func snakeCasedName(name string) string { return string(newstr) } -/*func pascal2Sql(s string) (d string) { - d = "" - lastIdx := 0 - for i := 0; i < len(s); i++ { - if s[i] >= 'A' && s[i] <= 'Z' { - if lastIdx < i { - d += s[lastIdx+1 : i] - } - if i != 0 { - d += "_" - } - d += string(s[i] + 32) - lastIdx = i - } - } - d += s[lastIdx+1:] - return -}*/ - func (mapper SnakeMapper) Obj2Table(name string) string { return snakeCasedName(name) } @@ -148,9 +120,103 @@ func (mapper SnakeMapper) Table2Obj(name string) string { return titleCasedName(name) } -func (mapper SnakeMapper) TableName(t string) string { - return t +// GonicMapper implements IMapper. It will consider initialisms when mapping names. +// E.g. id -> ID, user -> User and to table names: UserID -> user_id, MyUID -> my_uid +type GonicMapper map[string]bool + +func isASCIIUpper(r rune) bool { + return 'A' <= r && r <= 'Z' } + +func toASCIIUpper(r rune) rune { + if 'a' <= r && r <= 'z' { + r -= ('a' - 'A') + } + return r +} + +func gonicCasedName(name string) string { + newstr := make([]rune, 0, len(name)+3) + for idx, chr := range name { + if isASCIIUpper(chr) && idx > 0 { + if !isASCIIUpper(newstr[len(newstr)-1]) { + newstr = append(newstr, '_') + } + } + + if !isASCIIUpper(chr) && idx > 1 { + l := len(newstr) + if isASCIIUpper(newstr[l-1]) && isASCIIUpper(newstr[l-2]) { + newstr = append(newstr, newstr[l-1]) + newstr[l-1] = '_' + } + } + + newstr = append(newstr, chr) + } + return strings.ToLower(string(newstr)) +} + +func (mapper GonicMapper) Obj2Table(name string) string { + return gonicCasedName(name) +} + +func (mapper GonicMapper) Table2Obj(name string) string { + newstr := make([]rune, 0) + + name = strings.ToLower(name) + parts := strings.Split(name, "_") + + for _, p := range parts { + _, isInitialism := mapper[strings.ToUpper(p)] + for i, r := range p { + if i == 0 || isInitialism { + r = toASCIIUpper(r) + } + newstr = append(newstr, r) + } + } + + return string(newstr) +} + +// A GonicMapper that contains a list of common initialisms taken from golang/lint +var LintGonicMapper = GonicMapper{ + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SSH": true, + "TLS": true, + "TTL": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "XSRF": true, + "XSS": true, +} + // provide prefix table name support type PrefixMapper struct { Mapper IMapper @@ -165,10 +231,6 @@ func (mapper PrefixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) } -func (mapper PrefixMapper) TableName(name string) string { - return mapper.Prefix + name -} - func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper { return PrefixMapper{mapper, prefix} } @@ -187,10 +249,6 @@ func (mapper SuffixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) } -func (mapper SuffixMapper) TableName(name string) string { - return name + mapper.Suffix -} - func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { return SuffixMapper{mapper, suffix} } diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/mapper_test.go b/Godeps/_workspace/src/github.com/go-xorm/core/mapper_test.go new file mode 100644 index 00000000000..043087a2af1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-xorm/core/mapper_test.go @@ -0,0 +1,45 @@ +package core + +import ( + "testing" +) + +func TestGonicMapperFromObj(t *testing.T) { + testCases := map[string]string{ + "HTTPLib": "http_lib", + "id": "id", + "ID": "id", + "IDa": "i_da", + "iDa": "i_da", + "IDAa": "id_aa", + "aID": "a_id", + "aaID": "aa_id", + "aaaID": "aaa_id", + "MyREalFunkYLONgNAME": "my_r_eal_funk_ylo_ng_name", + } + + for in, expected := range testCases { + out := gonicCasedName(in) + if out != expected { + t.Errorf("Given %s, expected %s but got %s", in, expected, out) + } + } +} + +func TestGonicMapperToObj(t *testing.T) { + testCases := map[string]string{ + "http_lib": "HTTPLib", + "id": "ID", + "ida": "Ida", + "id_aa": "IDAa", + "aa_id": "AaID", + "my_r_eal_funk_ylo_ng_name": "MyREalFunkYloNgName", + } + + for in, expected := range testCases { + out := LintGonicMapper.Table2Obj(in) + if out != expected { + t.Errorf("Given %s, expected %s but got %s", in, expected, out) + } + } +} diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/pk.go b/Godeps/_workspace/src/github.com/go-xorm/core/pk.go index 61d1371e67c..1810dd944be 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/pk.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/pk.go @@ -1,7 +1,8 @@ package core import ( - "encoding/json" + "bytes" + "encoding/gob" ) type PK []interface{} @@ -12,14 +13,14 @@ func NewPK(pks ...interface{}) *PK { } func (p *PK) ToString() (string, error) { - bs, err := json.Marshal(*p) - if err != nil { - return "", nil - } - - return string(bs), nil + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(*p) + return buf.String(), err } func (p *PK) FromString(content string) error { - return json.Unmarshal([]byte(content), p) + dec := gob.NewDecoder(bytes.NewBufferString(content)) + err := dec.Decode(p) + return err } diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/pk_test.go b/Godeps/_workspace/src/github.com/go-xorm/core/pk_test.go index 5245e574800..05486086e6a 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/pk_test.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/pk_test.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "reflect" "testing" ) @@ -19,4 +20,14 @@ func TestPK(t *testing.T) { t.Error(err) } fmt.Println(s) + + if len(*p) != len(*s) { + t.Fatal("p", *p, "should be equal", *s) + } + + for i, ori := range *p { + if ori != (*s)[i] { + t.Fatal("ori", ori, reflect.ValueOf(ori), "should be equal", (*s)[i], reflect.ValueOf((*s)[i])) + } + } } diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/table.go b/Godeps/_workspace/src/github.com/go-xorm/core/table.go index f7c9d464842..aba1f96e748 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/table.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/table.go @@ -65,13 +65,18 @@ func (table *Table) GetColumnIdx(name string, idx int) *Column { // if has primary key, return column func (table *Table) PKColumns() []*Column { - columns := make([]*Column, 0) - for _, name := range table.PrimaryKeys { - columns = append(columns, table.GetColumn(name)) + columns := make([]*Column, len(table.PrimaryKeys)) + for i, name := range table.PrimaryKeys { + columns[i] = table.GetColumn(name) } return columns } +func (table *Table) ColumnType(name string) reflect.Type { + t, _ := table.Type.FieldByName(name) + return t.Type +} + func (table *Table) AutoIncrColumn() *Column { return table.GetColumn(table.AutoIncrement) } diff --git a/Godeps/_workspace/src/github.com/go-xorm/core/type.go b/Godeps/_workspace/src/github.com/go-xorm/core/type.go index ee7656824c1..73b9921ee63 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/core/type.go +++ b/Godeps/_workspace/src/github.com/go-xorm/core/type.go @@ -70,6 +70,7 @@ var ( NVarchar = "NVARCHAR" TinyText = "TINYTEXT" Text = "TEXT" + Clob = "CLOB" MediumText = "MEDIUMTEXT" LongText = "LONGTEXT" Uuid = "UUID" @@ -120,6 +121,7 @@ var ( MediumText: TEXT_TYPE, LongText: TEXT_TYPE, Uuid: TEXT_TYPE, + Clob: TEXT_TYPE, Date: TIME_TYPE, DateTime: TIME_TYPE, @@ -250,7 +252,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.String: st = SQLType{Varchar, 255, 0} case reflect.Struct: - if t == reflect.TypeOf(c_TIME_DEFAULT) { + if t.ConvertibleTo(reflect.TypeOf(c_TIME_DEFAULT)) { st = SQLType{DateTime, 0, 0} } else { // TODO need to handle association struct @@ -303,7 +305,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid: + case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob: return reflect.TypeOf("") case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: return reflect.TypeOf([]byte{}) diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/README.md b/Godeps/_workspace/src/github.com/go-xorm/xorm/README.md index 158f5c11dda..fe8aca3c374 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/README.md +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/README.md @@ -82,11 +82,13 @@ Or # Cases +* [Wego](http://github.com/go-tango/wego) + * [Docker.cn](https://docker.cn/) * [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) -* [Gorevel](http://http://gorevel.cn/) - [github.com/goofcc/gorevel](http://github.com/goofcc/gorevel) +* [Gorevel](http://gorevel.cn/) - [github.com/goofcc/gorevel](http://github.com/goofcc/gorevel) * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/README_CN.md b/Godeps/_workspace/src/github.com/go-xorm/xorm/README_CN.md index 5def1c38a57..5a167f9b148 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/README_CN.md +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/README_CN.md @@ -44,16 +44,10 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 更新日志 -* **v0.4.0 RC1** - 新特性: - * 移动xorm cmd [github.com/go-xorm/cmd](github.com/go-xorm/cmd) - * 在重构一般DB操作核心库 [github.com/go-xorm/core](https://github.com/go-xorm/core) - * 移动测试github.com/复XORM/测试 [github.com/go-xorm/tests](github.com/go-xorm/tests) - - 改进: - * Prepared statement 缓存 - * 添加 Incr API - * 指定时区位置 +* **v0.4.2** + 新特性: + * deleted标记 + * bug fixed [更多更新日志...](https://github.com/go-xorm/manual-zh-CN/tree/master/chapter-16) @@ -78,6 +72,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 案例 +* [Wego](http://github.com/go-tango/wego) + * [Docker.cn](https://docker.cn/) * [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/VERSION b/Godeps/_workspace/src/github.com/go-xorm/xorm/VERSION index af81cfc7d96..4e64e0e9e47 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/VERSION +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/VERSION @@ -1 +1 @@ -xorm v0.4.1 +xorm v0.4.2.0225 diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/doc.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/doc.go index adc1d2d54e1..722088ca775 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/doc.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/doc.go @@ -63,21 +63,22 @@ There are 7 major ORM methods and many helpful methods to use to operate databas // SELECT * FROM user 4. Query multiple records and record by record handle, there two methods, one is Iterate, -another is Raws +another is Rows err := engine.Iterate(...) // SELECT * FROM user - raws, err := engine.Raws(...) + rows, err := engine.Rows(...) // SELECT * FROM user + defer rows.Close() bean := new(Struct) - for raws.Next() { - err = raws.Scan(bean) + for rows.Next() { + err = rows.Scan(bean) } 5. Update one or more records - affected, err := engine.Update(&user) + affected, err := engine.Id(...).Update(&user) // UPDATE user SET ... 6. Delete one or more records, Delete MUST has conditon @@ -150,6 +151,6 @@ Attention: the above 7 methods should be the last chainable method. engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find() //SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id -More usage, please visit https://github.com/go-xorm/xorm/blob/master/docs/QuickStartEn.md +More usage, please visit http://xorm.io/docs */ package xorm diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/engine.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/engine.go index 700f46a1606..8f0c805dc34 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/engine.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/engine.go @@ -344,7 +344,7 @@ func (engine *Engine) DBMetas() ([]*core.Table, error) { if col := table.GetColumn(name); col != nil { col.Indexes[index.Name] = true } else { - return nil, fmt.Errorf("Unknown col "+name+" in indexes %v", index) + return nil, fmt.Errorf("Unknown col "+name+" in indexes %v of table", index, table.ColumnsSeq()) } } } @@ -352,6 +352,9 @@ func (engine *Engine) DBMetas() ([]*core.Table, error) { return tables, nil } +/* +dump database all table structs and data to a file +*/ func (engine *Engine) DumpAllToFile(fp string) error { f, err := os.Create(fp) if err != nil { @@ -361,6 +364,9 @@ func (engine *Engine) DumpAllToFile(fp string) error { return engine.DumpAll(f) } +/* +dump database all table structs and data to w +*/ func (engine *Engine) DumpAll(w io.Writer) error { tables, err := engine.DBMetas() if err != nil { @@ -558,6 +564,13 @@ func (engine *Engine) Decr(column string, arg ...interface{}) *Session { return session.Decr(column, arg...) } +// Method SetExpr provides a update string like "column = {expression}" +func (engine *Engine) SetExpr(column string, expression string) *Session { + session := engine.NewSession() + session.IsAutoClose = true + return session.SetExpr(column, expression) +} + // Temporarily change the Get, Find, Update's table func (engine *Engine) Table(tableNameOrBean interface{}) *Session { session := engine.NewSession() @@ -766,7 +779,12 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { col.IsPrimaryKey = true col.Nullable = false case k == "NULL": - col.Nullable = (strings.ToUpper(tags[j-1]) != "NOT") + if j == 0 { + col.Nullable = true + } else { + col.Nullable = (strings.ToUpper(tags[j-1]) != "NOT") + } + // TODO: for postgres how add autoincr? /*case strings.HasPrefix(k, "AUTOINCR(") && strings.HasSuffix(k, ")"): col.IsAutoIncrement = true @@ -915,7 +933,7 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { table.AddColumn(col) - if fieldType.Kind() == reflect.Int64 && (col.FieldName == "Id" || strings.HasSuffix(col.FieldName, ".Id")) { + if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) { idFieldColName = col.Name } } // end for @@ -959,40 +977,25 @@ func (engine *Engine) mapping(beans ...interface{}) (e error) { // If a table has any reocrd func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) { - v := rValue(bean) - t := v.Type() - if t.Kind() != reflect.Struct { - return false, errors.New("bean should be a struct or struct's point") - } - engine.autoMapType(v) session := engine.NewSession() defer session.Close() - rows, err := session.Count(bean) - return rows == 0, err + return session.IsTableEmpty(bean) } // If a table is exist -func (engine *Engine) IsTableExist(bean interface{}) (bool, error) { - v := rValue(bean) - var tableName string - if v.Type().Kind() == reflect.String { - tableName = bean.(string) - } else if v.Type().Kind() == reflect.Struct { - table := engine.autoMapType(v) - tableName = table.Name - } else { - return false, errors.New("bean should be a struct or struct's point") - } - +func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { session := engine.NewSession() defer session.Close() - has, err := session.isTableExist(tableName) - return has, err + return session.IsTableExist(beanOrTableName) } func (engine *Engine) IdOf(bean interface{}) core.PK { - table := engine.TableInfo(bean) - v := reflect.Indirect(reflect.ValueOf(bean)) + return engine.IdOfV(reflect.ValueOf(bean)) +} + +func (engine *Engine) IdOfV(rv reflect.Value) core.PK { + v := reflect.Indirect(rv) + table := engine.autoMapType(v) pk := make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { pkField := v.FieldByName(col.FieldName) @@ -1109,7 +1112,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { session := engine.NewSession() session.Statement.RefTable = table defer session.Close() - isExist, err := session.isColumnExist(table.Name, col) + isExist, err := session.Engine.dialect.IsColumnExist(table.Name, col) if err != nil { return err } @@ -1222,8 +1225,9 @@ func (engine *Engine) CreateTables(beans ...interface{}) error { func (engine *Engine) DropTables(beans ...interface{}) error { session := engine.NewSession() - err := session.Begin() defer session.Close() + + err := session.Begin() if err != nil { return err } @@ -1258,13 +1262,6 @@ func (engine *Engine) Query(sql string, paramStr ...interface{}) (resultsSlice [ return session.Query(sql, paramStr...) } -// Exec a raw sql and return records as []map[string]string -func (engine *Engine) Q(sql string, paramStr ...interface{}) (resultsSlice []map[string]string, err error) { - session := engine.NewSession() - defer session.Close() - return session.Q(sql, paramStr...) -} - // Insert one or more records func (engine *Engine) Insert(beans ...interface{}) (int64, error) { session := engine.NewSession() @@ -1371,18 +1368,11 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { scanner.Split(semiColSpliter) - session := engine.NewSession() - defer session.Close() - err := session.newDb() - if err != nil { - return results, err - } - for scanner.Scan() { query := scanner.Text() query = strings.Trim(query, " \t") if len(query) > 0 { - result, err := session.Db.Exec(query) + result, err := engine.DB().Exec(query) results = append(results, result) if err != nil { lastError = err @@ -1409,7 +1399,15 @@ func (engine *Engine) NowTime(sqlTypeName string) interface{} { return engine.FormatTime(sqlTypeName, t) } +func (engine *Engine) NowTime2(sqlTypeName string) (interface{}, time.Time) { + t := time.Now() + return engine.FormatTime(sqlTypeName, t), t +} + func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) { + if engine.dialect.DBType() == core.ORACLE { + return t + } switch sqlTypeName { case core.Time: s := engine.TZTime(t).Format("2006-01-02 15:04:05") //time.RFC3339 @@ -1419,6 +1417,8 @@ func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{} case core.DateTime, core.TimeStamp: if engine.dialect.DBType() == "ql" { v = engine.TZTime(t) + } else if engine.dialect.DBType() == "sqlite3" { + v = engine.TZTime(t).UTC().Format("2006-01-02 15:04:05") } else { v = engine.TZTime(t).Format("2006-01-02 15:04:05") } @@ -1430,6 +1430,8 @@ func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{} } else { v = engine.TZTime(t).Format(time.RFC3339Nano) } + case core.BigInt, core.Int: + v = engine.TZTime(t).Unix() default: v = engine.TZTime(t) } diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/helpers.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/helpers.go index 4d20141cad3..7eaa1dd1c21 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/helpers.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/helpers.go @@ -11,6 +11,43 @@ import ( "github.com/go-xorm/core" ) +func isZero(k interface{}) bool { + switch k.(type) { + case int: + return k.(int) == 0 + case int8: + return k.(int8) == 0 + case int16: + return k.(int16) == 0 + case int32: + return k.(int32) == 0 + case int64: + return k.(int64) == 0 + case uint: + return k.(uint) == 0 + case uint8: + return k.(uint8) == 0 + case uint16: + return k.(uint16) == 0 + case uint32: + return k.(uint32) == 0 + case uint64: + return k.(uint64) == 0 + case string: + return k.(string) == "" + } + return false +} + +func isPKZero(pk core.PK) bool { + for _, k := range pk { + if isZero(k) { + return true + } + } + return false +} + func indexNoCase(s, sep string) int { return strings.Index(strings.ToLower(s), strings.ToLower(sep)) } @@ -163,3 +200,182 @@ func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { return resultsSlice, nil } + +func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { + result := make(map[string][]byte) + scanResultContainers := make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var scanResultContainer interface{} + scanResultContainers[i] = &scanResultContainer + } + if err := rows.Scan(scanResultContainers...); err != nil { + return nil, err + } + + for ii, key := range fields { + rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + //if row is null then ignore + if rawValue.Interface() == nil { + //fmt.Println("ignore ...", key, rawValue) + continue + } + + if data, err := value2Bytes(&rawValue); err == nil { + result[key] = data + } else { + return nil, err // !nashtsai! REVIEW, should return err or just error log? + } + } + return result, nil +} + +func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { + result := make(map[string]string) + scanResultContainers := make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var scanResultContainer interface{} + scanResultContainers[i] = &scanResultContainer + } + if err := rows.Scan(scanResultContainers...); err != nil { + return nil, err + } + + for ii, key := range fields { + rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + //if row is null then ignore + if rawValue.Interface() == nil { + //fmt.Println("ignore ...", key, rawValue) + continue + } + + if data, err := value2String(&rawValue); err == nil { + result[key] = data + } else { + return nil, err // !nashtsai! REVIEW, should return err or just error log? + } + } + return result, nil +} + +func txQuery2(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) { + rows, err := tx.Query(sqlStr, params...) + if err != nil { + return nil, err + } + defer rows.Close() + + return rows2Strings(rows) +} + +func query2(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) { + s, err := db.Prepare(sqlStr) + if err != nil { + return nil, err + } + defer s.Close() + rows, err := s.Query(params...) + if err != nil { + return nil, err + } + defer rows.Close() + return rows2Strings(rows) +} + +func setColumnTime(bean interface{}, col *core.Column, t time.Time) { + v, err := col.ValueOf(bean) + if err != nil { + return + } + if v.CanSet() { + switch v.Type().Kind() { + case reflect.Struct: + v.Set(reflect.ValueOf(t).Convert(v.Type())) + case reflect.Int, reflect.Int64, reflect.Int32: + v.SetInt(t.Unix()) + case reflect.Uint, reflect.Uint64, reflect.Uint32: + v.SetUint(uint64(t.Unix())) + } + } +} + +func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) { + colNames := make([]string, 0) + args := make([]interface{}, 0) + + for _, col := range table.Columns() { + lColName := strings.ToLower(col.Name) + if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated { + if _, ok := session.Statement.columnMap[lColName]; !ok { + continue + } + } + if col.MapType == core.ONLYFROMDB { + continue + } + + fieldValuePtr, err := col.ValueOf(bean) + if err != nil { + session.Engine.LogError(err) + continue + } + fieldValue := *fieldValuePtr + + if col.IsAutoIncrement { + switch fieldValue.Type().Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: + if fieldValue.Int() == 0 { + continue + } + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: + if fieldValue.Uint() == 0 { + continue + } + case reflect.String: + if len(fieldValue.String()) == 0 { + continue + } + } + } + + if col.IsDeleted { + continue + } + + if session.Statement.ColumnStr != "" { + if _, ok := session.Statement.columnMap[lColName]; !ok { + continue + } + } + if session.Statement.OmitStr != "" { + if _, ok := session.Statement.columnMap[lColName]; ok { + continue + } + } + + if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { + val, t := session.Engine.NowTime2(col.SQLType.Name) + args = append(args, val) + + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) + } else if col.IsVersion && session.Statement.checkVersion { + args = append(args, 1) + } else { + arg, err := session.value2Interface(col, fieldValue) + if err != nil { + return colNames, args, err + } + args = append(args, arg) + } + + if includeQuote { + colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?") + } else { + colNames = append(colNames, col.Name) + } + } + return colNames, args, nil +} diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/mssql_dialect.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/mssql_dialect.go index ceb7c5de917..6fe50fc8c0d 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/mssql_dialect.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/mssql_dialect.go @@ -270,7 +270,7 @@ func (db *mssql) IsReserved(name string) bool { } func (db *mssql) Quote(name string) string { - return "[" + name + "]" + return "\"" + name + "\"" } func (db *mssql) QuoteStr() string { diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/mysql_dialect.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/mysql_dialect.go index 4d32186b6ab..602cd0ecff7 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/mysql_dialect.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/mysql_dialect.go @@ -218,6 +218,9 @@ func (db *mysql) SqlType(c *core.Column) string { res += ")" case core.NVarchar: res = core.Varchar + case core.Uuid: + res = core.Varchar + c.Length = 40 default: res = t } @@ -317,7 +320,6 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column if err != nil { return nil, nil, err } - //fmt.Println(columnName, isNullable, colType, colKey, extra, colDefault) col.Name = strings.Trim(columnName, "` ") if "YES" == isNullable { col.Nullable = true @@ -467,15 +469,17 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { } colName = strings.Trim(colName, "` ") - + var isRegular bool if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { indexName = indexName[5+len(tableName) : len(indexName)] + isRegular = true } var index *core.Index var ok bool if index, ok = indexes[indexName]; !ok { index = new(core.Index) + index.IsRegular = isRegular index.Type = indexType index.Name = indexName indexes[indexName] = index diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/oracle_dialect.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/oracle_dialect.go index f599dce93ed..be71c288d0c 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/oracle_dialect.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/oracle_dialect.go @@ -509,7 +509,7 @@ func (db *oracle) SqlType(c *core.Column) string { var res string switch t := c.SQLType.Name; t { case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool, core.Serial, core.BigSerial: - return "NUMBER" + res = "NUMBER" case core.Binary, core.VarBinary, core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob, core.Bytea: return core.Blob case core.Time, core.DateTime, core.TimeStamp: @@ -521,7 +521,7 @@ func (db *oracle) SqlType(c *core.Column) string { case core.Text, core.MediumText, core.LongText: res = "CLOB" case core.Char, core.Varchar, core.TinyText: - return "VARCHAR2" + res = "VARCHAR2" default: res = t } @@ -536,6 +536,10 @@ func (db *oracle) SqlType(c *core.Column) string { return res } +func (db *oracle) AutoIncrStr() string { + return "AUTO_INCREMENT" +} + func (db *oracle) SupportInsertMany() bool { return true } @@ -553,10 +557,6 @@ func (db *oracle) QuoteStr() string { return "\"" } -func (db *oracle) AutoIncrStr() string { - return "" -} - func (db *oracle) SupportEngine() bool { return false } @@ -565,19 +565,94 @@ func (db *oracle) SupportCharset() bool { return false } +func (db *oracle) SupportDropIfExists() bool { + return false +} + func (db *oracle) IndexOnTable() bool { return false } +func (db *oracle) DropTableSql(tableName string) string { + return fmt.Sprintf("DROP TABLE `%s`", tableName) +} + +func (b *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { + var sql string + sql = "CREATE TABLE " + if tableName == "" { + tableName = table.Name + } + + sql += b.Quote(tableName) + " (" + + pkList := table.PrimaryKeys + + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + /*if col.IsPrimaryKey && len(pkList) == 1 { + sql += col.String(b.dialect) + } else {*/ + sql += col.StringNoPk(b) + //} + sql = strings.TrimSpace(sql) + sql += ", " + } + + if len(pkList) > 0 { + sql += "PRIMARY KEY ( " + sql += b.Quote(strings.Join(pkList, b.Quote(","))) + sql += " ), " + } + + sql = sql[:len(sql)-2] + ")" + if b.SupportEngine() && storeEngine != "" { + sql += " ENGINE=" + storeEngine + } + if b.SupportCharset() { + if len(charset) == 0 { + charset = b.URI().Charset + } + if len(charset) > 0 { + sql += " DEFAULT CHARSET " + charset + } + } + return sql +} + func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(idxName)} + args := []interface{}{tableName, idxName} return `SELECT INDEX_NAME FROM USER_INDEXES ` + - `WHERE TABLE_NAME = ? AND INDEX_NAME = ?`, args + `WHERE TABLE_NAME = :1 AND INDEX_NAME = :2`, args } func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{strings.ToUpper(tableName)} - return `SELECT table_name FROM user_tables WHERE table_name = ?`, args + args := []interface{}{tableName} + return `SELECT table_name FROM user_tables WHERE table_name = :1`, args +} + +func (db *oracle) MustDropTable(tableName string) error { + sql, args := db.TableCheckSql(tableName) + if db.Logger != nil { + db.Logger.Info("[sql]", sql, args) + } + + rows, err := db.DB().Query(sql, args...) + if err != nil { + return err + } + defer rows.Close() + + if !rows.Next() { + return nil + } + + sql = "Drop Table \"" + tableName + "\"" + if db.Logger != nil { + db.Logger.Info("[sql]", sql) + } + _, err = db.DB().Exec(sql) + return err } /*func (db *oracle) ColumnCheckSql(tableName, colName string) (string, []interface{}) { @@ -587,9 +662,9 @@ func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) { }*/ func (db *oracle) IsColumnExist(tableName string, col *core.Column) (bool, error) { - args := []interface{}{strings.ToUpper(tableName), strings.ToUpper(col.Name)} - query := "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = ?" + - " AND column_name = ?" + args := []interface{}{tableName, col.Name} + query := "SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = :1" + + " AND column_name = :2" rows, err := db.DB().Query(query, args...) if db.Logger != nil { db.Logger.Info("[sql]", query, args) @@ -606,7 +681,7 @@ func (db *oracle) IsColumnExist(tableName string, col *core.Column) (bool, error } func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { - args := []interface{}{strings.ToUpper(tableName)} + args := []interface{}{tableName} s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" @@ -625,7 +700,7 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum col := new(core.Column) col.Indexes = make(map[string]bool) - var colName, colDefault, nullable, dataType, dataPrecision, dataScale string + var colName, colDefault, nullable, dataType, dataPrecision, dataScale *string var dataLen int err = rows.Scan(&colName, &colDefault, &dataType, &dataLen, &dataPrecision, @@ -634,36 +709,66 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum return nil, nil, err } - col.Name = strings.Trim(colName, `" `) - col.Default = colDefault + col.Name = strings.Trim(*colName, `" `) + if colDefault != nil { + col.Default = *colDefault + col.DefaultIsEmpty = false + } - if nullable == "Y" { + if *nullable == "Y" { col.Nullable = true } else { col.Nullable = false } - switch dataType { + var ignore bool + + var dt string + var len1, len2 int + dts := strings.Split(*dataType, "(") + dt = dts[0] + if len(dts) > 1 { + lens := strings.Split(dts[1][:len(dts[1])-1], ",") + if len(lens) > 1 { + len1, _ = strconv.Atoi(lens[0]) + len2, _ = strconv.Atoi(lens[1]) + } else { + len1, _ = strconv.Atoi(lens[0]) + } + } + + switch dt { case "VARCHAR2": - col.SQLType = core.SQLType{core.Varchar, 0, 0} + col.SQLType = core.SQLType{core.Varchar, len1, len2} case "TIMESTAMP WITH TIME ZONE": col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + case "NUMBER": + col.SQLType = core.SQLType{core.Double, len1, len2} + case "LONG", "LONG RAW": + col.SQLType = core.SQLType{core.Text, 0, 0} + case "RAW": + col.SQLType = core.SQLType{core.Binary, 0, 0} + case "ROWID": + col.SQLType = core.SQLType{core.Varchar, 18, 0} + case "AQ$_SUBSCRIBERS": + ignore = true default: - col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} + col.SQLType = core.SQLType{strings.ToUpper(dt), len1, len2} } + + if ignore { + continue + } + if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", dataType)) + return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v %v", *dataType, col.SQLType)) } col.Length = dataLen if col.SQLType.IsText() || col.SQLType.IsTime() { - if col.Default != "" { + if !col.DefaultIsEmpty { col.Default = "'" + col.Default + "'" - } else { - if col.DefaultIsEmpty { - col.Default = "''" - } } } cols[col.Name] = col diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/rows.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/rows.go index c566b125bbc..0def55757c8 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/rows.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/rows.go @@ -25,11 +25,6 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { rows.session = session rows.beanType = reflect.Indirect(reflect.ValueOf(bean)).Type() - err := rows.session.newDb() - if err != nil { - return nil, err - } - defer rows.session.Statement.Init() var sqlStr string @@ -47,8 +42,8 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { } rows.session.Engine.logSQL(sqlStr, args) - - rows.stmt, err = rows.session.Db.Prepare(sqlStr) + var err error + rows.stmt, err = rows.session.DB().Prepare(sqlStr) if err != nil { rows.lastError = err defer rows.Close() diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/session.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/session.go index e5ec7fd0701..0d11d99fd0f 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/session.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/session.go @@ -17,7 +17,7 @@ import ( // Struct Session keep a pointer to sql.DB and provides all execution of all // kind of database operations. type Session struct { - Db *core.DB + db *core.DB Engine *Engine Tx *core.Tx Statement Statement @@ -66,9 +66,9 @@ func (session *Session) Close() { v.Close() } - if session.Db != nil { + if session.db != nil { //session.Engine.Pool.ReleaseDB(session.Engine, session.Db) - session.Db = nil + session.db = nil session.Tx = nil session.stmtCache = nil session.Init() @@ -158,6 +158,12 @@ func (session *Session) Decr(column string, arg ...interface{}) *Session { return session } +// Method SetExpr provides a query string like "column = {expression}" +func (session *Session) SetExpr(column string, expression string) *Session { + session.Statement.SetExpr(column, expression) + return session +} + // Method Cols provides some columns to special func (session *Session) Cols(columns ...string) *Session { session.Statement.Cols(columns...) @@ -280,26 +286,18 @@ func (session *Session) Having(conditions string) *Session { return session } -func (session *Session) newDb() error { - if session.Db == nil { - /*db, err := session.Engine.Pool.RetrieveDB(session.Engine) - if err != nil { - return err - }*/ - session.Db = session.Engine.db +func (session *Session) DB() *core.DB { + if session.db == nil { + session.db = session.Engine.db session.stmtCache = make(map[uint32]*core.Stmt, 0) } - return nil + return session.db } // Begin a transaction func (session *Session) Begin() error { - err := session.newDb() - if err != nil { - return err - } if session.IsAutoCommit { - tx, err := session.Db.Begin() + tx, err := session.DB().Begin() if err != nil { return err } @@ -450,6 +448,13 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er return session.Engine.LogSQLExecutionTime(sqlStr, args, func() (sql.Result, error) { if session.IsAutoCommit { + //oci8 can not auto commit (github.com/mattn/go-oci8) + if session.Engine.dialect.DBType() == core.ORACLE { + session.Begin() + r, err := session.Tx.Exec(sqlStr, args...) + session.Commit() + return r, err + } return session.innerExec(sqlStr, args...) } return session.Tx.Exec(sqlStr, args...) @@ -458,10 +463,6 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er // Exec raw sql func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, error) { - err := session.newDb() - if err != nil { - return nil, err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -474,10 +475,6 @@ func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, er func (session *Session) CreateTable(bean interface{}) error { session.Statement.RefTable = session.Engine.TableInfo(bean) - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -490,10 +487,6 @@ func (session *Session) CreateTable(bean interface{}) error { func (session *Session) CreateIndexes(bean interface{}) error { session.Statement.RefTable = session.Engine.TableInfo(bean) - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -501,7 +494,7 @@ func (session *Session) CreateIndexes(bean interface{}) error { sqls := session.Statement.genIndexSQL() for _, sqlStr := range sqls { - _, err = session.exec(sqlStr) + _, err := session.exec(sqlStr) if err != nil { return err } @@ -513,10 +506,6 @@ func (session *Session) CreateIndexes(bean interface{}) error { func (session *Session) CreateUniques(bean interface{}) error { session.Statement.RefTable = session.Engine.TableInfo(bean) - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -524,7 +513,7 @@ func (session *Session) CreateUniques(bean interface{}) error { sqls := session.Statement.genUniqueSQL() for _, sqlStr := range sqls { - _, err = session.exec(sqlStr) + _, err := session.exec(sqlStr) if err != nil { return err } @@ -541,10 +530,6 @@ func (session *Session) createOneTable() error { // to be deleted func (session *Session) createAll() error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -562,10 +547,6 @@ func (session *Session) createAll() error { // drop indexes func (session *Session) DropIndexes(bean interface{}) error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -573,7 +554,7 @@ func (session *Session) DropIndexes(bean interface{}) error { sqls := session.Statement.genDelIndexSQL() for _, sqlStr := range sqls { - _, err = session.exec(sqlStr) + _, err := session.exec(sqlStr) if err != nil { return err } @@ -581,31 +562,29 @@ func (session *Session) DropIndexes(bean interface{}) error { return nil } -// DropTable drop a table and all indexes of the table -func (session *Session) DropTable(bean interface{}) error { - err := session.newDb() +// drop table will drop table if exist, if drop failed, it will return error +func (session *Session) DropTable(beanOrTableName interface{}) error { + tableName, err := session.Engine.tableName(beanOrTableName) if err != nil { return err } - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() + var needDrop = true + if !session.Engine.dialect.SupportDropIfExists() { + sqlStr, args := session.Engine.dialect.TableCheckSql(tableName) + results, err := session.query(sqlStr, args...) + if err != nil { + return err + } + needDrop = len(results) > 0 } - t := reflect.Indirect(reflect.ValueOf(bean)).Type() - defer session.resetStatement() - if t.Kind() == reflect.String { - session.Statement.AltTableName = bean.(string) - } else if t.Kind() == reflect.Struct { - session.Statement.RefTable = session.Engine.TableInfo(bean) - } else { - return errors.New("Unsupported type") + if needDrop { + sqlStr := session.Engine.Dialect().DropTableSql(tableName) + _, err = session.exec(sqlStr) + return err } - - sqlStr := session.Statement.genDropSQL() - _, err = session.exec(sqlStr) - return err + return nil } func (statement *Statement) JoinColumns(cols []*core.Column) string { @@ -642,11 +621,6 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf return false, ErrCacheFailed } - // TODO: remove this after support multi pk cache - /*if len(session.Statement.RefTable.PrimaryKeys) != 1 { - return false, ErrCacheFailed - }*/ - for _, filter := range session.Engine.dialect.Filters() { sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) } @@ -662,7 +636,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf table := session.Statement.RefTable if err != nil { var res = make([]string, len(table.PrimaryKeys)) - rows, err := session.Db.Query(newsql, args...) + rows, err := session.DB().Query(newsql, args...) if err != nil { return false, err } @@ -747,11 +721,6 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in return ErrCacheFailed } - // TODO: remove this after multi pk supported - /*if len(session.Statement.RefTable.PrimaryKeys) != 1 { - return ErrCacheFailed - }*/ - for _, filter := range session.Engine.dialect.Filters() { sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) } @@ -765,7 +734,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in cacher := session.Engine.getCacher2(table) ids, err := core.GetCacheSql(cacher, session.Statement.TableName(), newsql, args) if err != nil { - rows, err := session.Db.Query(newsql, args...) + rows, err := session.DB().Query(newsql, args...) if err != nil { return err } @@ -909,32 +878,27 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean)))) } } else if sliceValue.Kind() == reflect.Map { - var key core.PK - if table.PrimaryKeys[0] != "" { - key = ids[j] - } - + var key core.PK = ids[j] + keyType := sliceValue.Type().Key() + var ikey interface{} if len(key) == 1 { - ikey, err := strconv.ParseInt(fmt.Sprintf("%v", key[0]), 10, 64) + ikey, err = Atot(fmt.Sprintf("%v", key[0]), keyType) if err != nil { return err } - if t.Kind() == reflect.Ptr { - sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.ValueOf(bean)) - } else { - sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.Indirect(reflect.ValueOf(bean))) - } } else { - return errors.New("table have multiple primary keys") + if keyType.Kind() != reflect.Slice { + return errors.New("table have multiple primary keys, key is not core.PK or slice") + } + ikey = key + } + + if t.Kind() == reflect.Ptr { + sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.ValueOf(bean)) + } else { + sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.Indirect(reflect.ValueOf(bean))) } } - /*} else { - session.Engine.LogDebug("[xorm:cacheFind] cache delete:", tableName, ides[j]) - cacher.DelBean(tableName, ids[j]) - - session.Engine.LogDebug("[xorm:cacheFind] cache clear:", tableName) - cacher.ClearIds(tableName) - }*/ } return nil @@ -981,7 +945,7 @@ func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { var has bool stmt, has = session.stmtCache[crc] if !has { - stmt, err = session.Db.Prepare(sqlStr) + stmt, err = session.DB().Prepare(sqlStr) if err != nil { return nil, err } @@ -993,11 +957,6 @@ func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { // get retrieve one record from database, bean's non-empty fields // will be as conditions func (session *Session) Get(bean interface{}) (bool, error) { - err := session.newDb() - if err != nil { - return false, err - } - defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1030,6 +989,7 @@ func (session *Session) Get(bean interface{}) (bool, error) { } var rawRows *core.Rows + var err error session.queryPreprocess(&sqlStr, args...) if session.IsAutoCommit { stmt, err := session.doPrepare(sqlStr) @@ -1059,11 +1019,6 @@ func (session *Session) Get(bean interface{}) (bool, error) { // Count counts the records. bean's non-empty fields // are conditions. func (session *Session) Count(bean interface{}) (int64, error) { - err := session.newDb() - if err != nil { - return 0, err - } - defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1095,14 +1050,79 @@ func (session *Session) Count(bean interface{}) (int64, error) { return int64(total), err } +func Atot(s string, tp reflect.Type) (interface{}, error) { + var err error + var result interface{} + switch tp.Kind() { + case reflect.Int: + result, err = strconv.Atoi(s) + if err != nil { + return nil, errors.New("convert " + s + " as int: " + err.Error()) + } + case reflect.Int8: + x, err := strconv.Atoi(s) + if err != nil { + return nil, errors.New("convert " + s + " as int16: " + err.Error()) + } + result = int8(x) + case reflect.Int16: + x, err := strconv.Atoi(s) + if err != nil { + return nil, errors.New("convert " + s + " as int16: " + err.Error()) + } + result = int16(x) + case reflect.Int32: + x, err := strconv.Atoi(s) + if err != nil { + return nil, errors.New("convert " + s + " as int32: " + err.Error()) + } + result = int32(x) + case reflect.Int64: + result, err = strconv.ParseInt(s, 10, 64) + if err != nil { + return nil, errors.New("convert " + s + " as int64: " + err.Error()) + } + case reflect.Uint: + x, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, errors.New("convert " + s + " as uint: " + err.Error()) + } + result = uint(x) + case reflect.Uint8: + x, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, errors.New("convert " + s + " as uint8: " + err.Error()) + } + result = uint8(x) + case reflect.Uint16: + x, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, errors.New("convert " + s + " as uint16: " + err.Error()) + } + result = uint16(x) + case reflect.Uint32: + x, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, errors.New("convert " + s + " as uint32: " + err.Error()) + } + result = uint32(x) + case reflect.Uint64: + result, err = strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, errors.New("convert " + s + " as uint64: " + err.Error()) + } + case reflect.String: + result = s + default: + panic("unsupported convert type") + } + return result, nil +} + // Find retrieve records from table, condiBeans's non-empty fields // are conditions. beans could be []Struct, []*Struct, map[int64]Struct // map[int64]*Struct func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1143,10 +1163,9 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) } else { // !oinume! Add " IS NULL" to WHERE whatever condiBean is given. // See https://github.com/go-xorm/xorm/issues/179 - for _, col := range table.Columns() { - if col.IsDeleted && !session.Statement.unscoped { // tag "deleted" is enabled - session.Statement.ConditionStr = fmt.Sprintf("(%v IS NULL or %v = '0001-01-01 00:00:00') ", session.Engine.Quote(col.Name), session.Engine.Quote(col.Name)) - } + if col := table.DeletedColumn(); col != nil && !session.Statement.unscoped { // tag "deleted" is enabled + session.Statement.ConditionStr = fmt.Sprintf("(%v IS NULL or %v = '0001-01-01 00:00:00') ", + session.Engine.Quote(col.Name), session.Engine.Quote(col.Name)) } } @@ -1156,11 +1175,19 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) var columnStr string = session.Statement.ColumnStr if session.Statement.JoinStr == "" { if columnStr == "" { - columnStr = session.Statement.genColumnStr() + if session.Statement.GroupByStr != "" { + columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) + } else { + columnStr = session.Statement.genColumnStr() + } } } else { if columnStr == "" { - columnStr = "*" + if session.Statement.GroupByStr != "" { + columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) + } else { + columnStr = "*" + } } } @@ -1178,6 +1205,7 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) args = session.Statement.RawParams } + var err error if session.Statement.JoinStr == "" { if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache && @@ -1259,7 +1287,9 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) return err } - for i, results := range resultsSlice { + keyType := sliceValue.Type().Key() + + for _, results := range resultsSlice { var newValue reflect.Value if sliceElementType.Kind() == reflect.Ptr { newValue = reflect.New(sliceElementType.Elem()) @@ -1270,18 +1300,29 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) if err != nil { return err } - var key int64 + var key interface{} // if there is only one pk, we can put the id as map key. - // TODO: should know if the column is ints if len(table.PrimaryKeys) == 1 { - x, err := strconv.ParseInt(string(results[table.PrimaryKeys[0]]), 10, 64) + key, err = Atot(string(results[table.PrimaryKeys[0]]), keyType) if err != nil { - return errors.New("pk " + table.PrimaryKeys[0] + " as int64: " + err.Error()) + return err } - key = x } else { - key = int64(i) + if keyType.Kind() != reflect.Slice { + panic("don't support multiple primary key's map has non-slice key type") + } else { + keys := core.PK{} + for _, pk := range table.PrimaryKeys { + skey, err := Atot(string(results[pk]), keyType) + if err != nil { + return err + } + keys = append(keys, skey) + } + key = keys + } } + if sliceElementType.Kind() == reflect.Ptr { sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(newValue.Interface())) } else { @@ -1308,23 +1349,16 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) // Test if database is ok func (session *Session) Ping() error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() } - return session.Db.Ping() + return session.DB().Ping() } +/* func (session *Session) isColumnExist(tableName string, col *core.Column) (bool, error) { - err := session.newDb() - if err != nil { - return false, err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1333,13 +1367,29 @@ func (session *Session) isColumnExist(tableName string, col *core.Column) (bool, //sqlStr, args := session.Engine.dialect.ColumnCheckSql(tableName, colName) //results, err := session.query(sqlStr, args...) //return len(results) > 0, err +}*/ + +func (engine *Engine) tableName(beanOrTableName interface{}) (string, error) { + v := rValue(beanOrTableName) + if v.Type().Kind() == reflect.String { + return beanOrTableName.(string), nil + } else if v.Type().Kind() == reflect.Struct { + table := engine.autoMapType(v) + return table.Name, nil + } + return "", errors.New("bean should be a struct or struct's point") } -func (session *Session) isTableExist(tableName string) (bool, error) { - err := session.newDb() +func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) { + tableName, err := session.Engine.tableName(beanOrTableName) if err != nil { return false, err } + + return session.isTableExist(tableName) +} + +func (session *Session) isTableExist(tableName string) (bool, error) { defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1349,11 +1399,38 @@ func (session *Session) isTableExist(tableName string) (bool, error) { return len(results) > 0, err } -func (session *Session) isIndexExist(tableName, idxName string, unique bool) (bool, error) { - err := session.newDb() - if err != nil { - return false, err +func (session *Session) IsTableEmpty(bean interface{}) (bool, error) { + v := rValue(bean) + t := v.Type() + + if t.Kind() == reflect.String { + return session.isTableEmpty(bean.(string)) + } else if t.Kind() == reflect.Struct { + session.Engine.autoMapType(v) + rows, err := session.Count(bean) + return rows == 0, err } + return false, errors.New("bean should be a struct or struct's point") +} + +func (session *Session) isTableEmpty(tableName string) (bool, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + var total int64 + sql := fmt.Sprintf("select count(*) from %s", session.Engine.Quote(tableName)) + err := session.DB().QueryRow(sql).Scan(&total) + session.Engine.logSQL(sql) + if err != nil { + return true, err + } + + return total == 0, nil +} + +func (session *Session) isIndexExist(tableName, idxName string, unique bool) (bool, error) { defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1371,6 +1448,11 @@ func (session *Session) isIndexExist(tableName, idxName string, unique bool) (bo // find if index is exist according cols func (session *Session) isIndexExist2(tableName string, cols []string, unique bool) (bool, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + indexes, err := session.Engine.dialect.GetIndexes(tableName) if err != nil { return false, err @@ -1389,10 +1471,6 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo } func (session *Session) addColumn(colName string) error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1400,47 +1478,35 @@ func (session *Session) addColumn(colName string) error { col := session.Statement.RefTable.GetColumn(colName) sql, args := session.Statement.genAddColumnStr(col) - _, err = session.exec(sql, args...) + _, err := session.exec(sql, args...) return err } func (session *Session) addIndex(tableName, idxName string) error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() } index := session.Statement.RefTable.Indexes[idxName] sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) - //genAddIndexStr(indexName(tableName, idxName), cols) - _, err = session.exec(sqlStr) + + _, err := session.exec(sqlStr) return err } func (session *Session) addUnique(tableName, uqeName string) error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() } index := session.Statement.RefTable.Indexes[uqeName] sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) - _, err = session.exec(sqlStr) + _, err := session.exec(sqlStr) return err } // To be deleted func (session *Session) dropAll() error { - err := session.newDb() - if err != nil { - return err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1449,7 +1515,7 @@ func (session *Session) dropAll() error { for _, table := range session.Engine.Tables { session.Statement.Init() session.Statement.RefTable = table - sqlStr := session.Statement.genDropSQL() + sqlStr := session.Engine.Dialect().DropTableSql(session.Statement.TableName()) _, err := session.exec(sqlStr) if err != nil { return err @@ -1458,62 +1524,6 @@ func (session *Session) dropAll() error { return nil } -func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { - result := make(map[string]string) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for ii, key := range fields { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - //if row is null then ignore - if rawValue.Interface() == nil { - //fmt.Println("ignore ...", key, rawValue) - continue - } - - if data, err := value2String(&rawValue); err == nil { - result[key] = data - } else { - return nil, err // !nashtsai! REVIEW, should return err or just error log? - } - } - return result, nil -} - -func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { - result := make(map[string][]byte) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for ii, key := range fields { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - //if row is null then ignore - if rawValue.Interface() == nil { - //fmt.Println("ignore ...", key, rawValue) - continue - } - - if data, err := value2Bytes(&rawValue); err == nil { - result[key] = data - } else { - return nil, err // !nashtsai! REVIEW, should return err or just error log? - } - } - return result, nil -} - func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) *reflect.Value { var col *core.Column if col = table.GetColumnIdx(key, idx); col == nil { @@ -1566,7 +1576,6 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i } func (session *Session) _row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}, dataStruct *reflect.Value, table *core.Table) error { - scanResults := make([]interface{}, fieldsCount) for i := 0; i < len(fields); i++ { var cell interface{} @@ -1686,42 +1695,73 @@ func (session *Session) _row2Bean(rows *core.Rows, fields []string, fieldsCount fieldValue.SetUint(uint64(vv.Int())) } case reflect.Struct: - if fieldType == core.TimeType { + if fieldType.ConvertibleTo(core.TimeType) { if rawValueType == core.TimeType { hasAssigned = true - t := vv.Interface().(time.Time) + t := vv.Convert(core.TimeType).Interface().(time.Time) z, _ := t.Zone() if len(z) == 0 || t.Year() == 0 { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location session.Engine.LogDebug("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) - tt := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), + t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.Local) - vv = reflect.ValueOf(tt) } // !nashtsai! convert to engine location - t = vv.Interface().(time.Time).In(session.Engine.TZLocation) - vv = reflect.ValueOf(t) - fieldValue.Set(vv) + t = t.In(session.Engine.TZLocation) + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) // t = fieldValue.Interface().(time.Time) // z, _ = t.Zone() // session.Engine.LogDebug("fieldValue key[%v]: %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) + } else if rawValueType == core.IntType || rawValueType == core.Int64Type || + rawValueType == core.Int32Type { + hasAssigned = true + t := time.Unix(vv.Int(), 0).In(session.Engine.TZLocation) + vv = reflect.ValueOf(t) + fieldValue.Set(vv) } } else if session.Statement.UseCascade { table := session.Engine.autoMapType(*fieldValue) if table != nil { - var x int64 - if rawValueType.Kind() == reflect.Int64 { - x = vv.Int() + if len(table.PrimaryKeys) > 1 { + panic("unsupported composited primary key cascade") } - if x != 0 { + var pk = make(core.PK, len(table.PrimaryKeys)) + switch rawValueType.Kind() { + case reflect.Int64: + pk[0] = vv.Int() + case reflect.Int: + pk[0] = int(vv.Int()) + case reflect.Int32: + pk[0] = int32(vv.Int()) + case reflect.Int16: + pk[0] = int16(vv.Int()) + case reflect.Int8: + pk[0] = int8(vv.Int()) + case reflect.Uint64: + pk[0] = vv.Uint() + case reflect.Uint: + pk[0] = uint(vv.Uint()) + case reflect.Uint32: + pk[0] = uint32(vv.Uint()) + case reflect.Uint16: + pk[0] = uint16(vv.Uint()) + case reflect.Uint8: + pk[0] = uint8(vv.Uint()) + case reflect.String: + pk[0] = vv.String() + default: + panic("unsupported primary key type cascade") + } + + if !isPKZero(pk) { // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne // property to be fetched lazily structInter := reflect.New(fieldValue.Type()) newsession := session.Engine.NewSession() defer newsession.Close() - has, err := newsession.Id(x).Get(structInter.Interface()) + has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface()) if err != nil { return err } @@ -1882,7 +1922,7 @@ func (session *Session) query(sqlStr string, paramStr ...interface{}) (resultsSl session.queryPreprocess(&sqlStr, paramStr...) if session.IsAutoCommit { - return session.innerQuery(session.Db, sqlStr, paramStr...) + return session.innerQuery(session.DB(), sqlStr, paramStr...) } return session.txQuery(session.Tx, sqlStr, paramStr...) } @@ -1898,7 +1938,6 @@ func (session *Session) txQuery(tx *core.Tx, sqlStr string, params ...interface{ } func (session *Session) innerQuery(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { - stmt, rows, err := session.Engine.LogSQLQueryTime(sqlStr, params, func() (*core.Stmt, *core.Rows, error) { stmt, err := db.Prepare(sqlStr) if err != nil { @@ -1922,10 +1961,6 @@ func (session *Session) innerQuery(db *core.DB, sqlStr string, params ...interfa // Exec a raw sql and return records as []map[string][]byte func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { - err = session.newDb() - if err != nil { - return nil, err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1941,56 +1976,15 @@ func (session *Session) query2(sqlStr string, paramStr ...interface{}) (resultsS session.queryPreprocess(&sqlStr, paramStr...) if session.IsAutoCommit { - return query2(session.Db, sqlStr, paramStr...) + return query2(session.DB(), sqlStr, paramStr...) } return txQuery2(session.Tx, sqlStr, paramStr...) } -func txQuery2(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) { - rows, err := tx.Query(sqlStr, params...) - if err != nil { - return nil, err - } - defer rows.Close() - - return rows2Strings(rows) -} - -func query2(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) { - s, err := db.Prepare(sqlStr) - if err != nil { - return nil, err - } - defer s.Close() - rows, err := s.Query(params...) - if err != nil { - return nil, err - } - defer rows.Close() - return rows2Strings(rows) -} - -// Exec a raw sql and return records as []map[string]string -func (session *Session) Q(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string]string, err error) { - err = session.newDb() - if err != nil { - return nil, err - } - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - return session.query2(sqlStr, paramStr...) -} - // insert one or more beans func (session *Session) Insert(beans ...interface{}) (int64, error) { var affected int64 = 0 - var err error = nil - err = session.newDb() - if err != nil { - return 0, err - } + var err error defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -1999,20 +1993,22 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) { for _, bean := range beans { sliceValue := reflect.Indirect(reflect.ValueOf(bean)) if sliceValue.Kind() == reflect.Slice { - if session.Engine.SupportInsertMany() { - cnt, err := session.innerInsertMulti(bean) - if err != nil { - return affected, err - } - affected += cnt - } else { - size := sliceValue.Len() - for i := 0; i < size; i++ { - cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) + size := sliceValue.Len() + if size > 0 { + if session.Engine.SupportInsertMany() { + cnt, err := session.innerInsertMulti(bean) if err != nil { return affected, err } affected += cnt + } else { + for i := 0; i < size; i++ { + cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) + if err != nil { + return affected, err + } + affected += cnt + } } } } else { @@ -2071,13 +2067,28 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.MapType == core.ONLYFROMDB { continue } + if col.IsDeleted { + continue + } if session.Statement.ColumnStr != "" { if _, ok := session.Statement.columnMap[col.Name]; !ok { continue } } + if session.Statement.OmitStr != "" { + if _, ok := session.Statement.columnMap[col.Name]; ok { + continue + } + } if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { - args = append(args, session.Engine.NowTime(col.SQLType.Name)) + val, t := session.Engine.NowTime2(col.SQLType.Name) + args = append(args, val) + + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) } else { arg, err := session.value2Interface(col, fieldValue) if err != nil { @@ -2099,13 +2110,28 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.MapType == core.ONLYFROMDB { continue } + if col.IsDeleted { + continue + } if session.Statement.ColumnStr != "" { if _, ok := session.Statement.columnMap[col.Name]; !ok { continue } } + if session.Statement.OmitStr != "" { + if _, ok := session.Statement.columnMap[col.Name]; ok { + continue + } + } if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { - args = append(args, session.Engine.NowTime(col.SQLType.Name)) + val, t := session.Engine.NowTime2(col.SQLType.Name) + args = append(args, val) + + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) } else { arg, err := session.value2Interface(col, fieldValue) if err != nil { @@ -2173,16 +2199,20 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error // Insert multiple records func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { - err := session.newDb() - if err != nil { - return 0, err + sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) + if sliceValue.Kind() == reflect.Slice { + if sliceValue.Len() > 0 { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + return session.innerInsertMulti(rowsSlicePtr) + } else { + return 0, nil + } + } else { + return 0, ErrParamsType } - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - return session.innerInsertMulti(rowsSlicePtr) } func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) { @@ -2352,28 +2382,96 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, fieldValue.SetUint(x) //Currently only support Time type case reflect.Struct: - if fieldType == core.TimeType { + if fieldType.ConvertibleTo(core.TimeType) { x, err := session.byte2Time(col, data) if err != nil { return err } v = x - fieldValue.Set(reflect.ValueOf(v)) + fieldValue.Set(reflect.ValueOf(v).Convert(fieldType)) } else if session.Statement.UseCascade { table := session.Engine.autoMapType(*fieldValue) if table != nil { - x, err := strconv.ParseInt(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) + if len(table.PrimaryKeys) > 1 { + panic("unsupported composited primary key cascade") } - if x != 0 { + var pk = make(core.PK, len(table.PrimaryKeys)) + rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) + switch rawValueType.Kind() { + case reflect.Int64: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = x + case reflect.Int: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = int(x) + case reflect.Int32: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = int32(x) + case reflect.Int16: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = int16(x) + case reflect.Int8: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = int8(x) + case reflect.Uint64: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = x + case reflect.Uint: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = uint(x) + case reflect.Uint32: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = uint32(x) + case reflect.Uint16: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = uint16(x) + case reflect.Uint8: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + pk[0] = uint8(x) + case reflect.String: + pk[0] = string(data) + default: + panic("unsupported primary key type cascade") + } + + if !isPKZero(pk) { // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne // property to be fetched lazily structInter := reflect.New(fieldValue.Type()) newsession := session.Engine.NewSession() defer newsession.Close() - has, err := newsession.Id(x).Get(structInter.Interface()) + has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface()) if err != nil { return err } @@ -2637,17 +2735,95 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, structInter := reflect.New(fieldType.Elem()) table := session.Engine.autoMapType(structInter.Elem()) if table != nil { - x, err := strconv.ParseInt(string(data), 10, 64) - if err != nil { - return fmt.Errorf("arg %v as int: %s", key, err.Error()) + if len(table.PrimaryKeys) > 1 { + panic("unsupported composited primary key cascade") } - if x != 0 { + var pk = make(core.PK, len(table.PrimaryKeys)) + rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) + switch rawValueType.Kind() { + case reflect.Int64: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = x + case reflect.Int: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = int(x) + case reflect.Int32: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = int32(x) + case reflect.Int16: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = int16(x) + case reflect.Int8: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = x + case reflect.Uint64: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = x + case reflect.Uint: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = uint(x) + case reflect.Uint32: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = uint32(x) + case reflect.Uint16: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = uint16(x) + case reflect.Uint8: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + + pk[0] = uint8(x) + case reflect.String: + pk[0] = string(data) + default: + panic("unsupported primary key type cascade") + } + + if !isPKZero(pk) { // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne // property to be fetched lazily newsession := session.Engine.NewSession() defer newsession.Close() - has, err := newsession.Id(x).Get(structInter.Interface()) + has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface()) if err != nil { return err } @@ -2804,8 +2980,29 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { return 0, err } - colPlaces := strings.Repeat("?, ", len(colNames)) - colPlaces = colPlaces[0 : len(colPlaces)-2] + // insert expr columns, override if exists + exprColumns := session.Statement.getExpr() + exprColVals := make([]string, 0, len(exprColumns)) + for _, v := range exprColumns { + // remove the expr columns + for i, colName := range colNames { + if colName == v.colName { + colNames = append(colNames[:i], colNames[i+1:]...) + args = append(args[:i], args[i+1:]...) + } + } + + // append expr column to the end + colNames = append(colNames, v.colName) + exprColVals = append(exprColVals, v.expr) + } + + colPlaces := strings.Repeat("?, ", len(colNames)-len(exprColumns)) + if len(exprColVals) > 0 { + colPlaces = colPlaces + strings.Join(exprColVals, ", ") + } else { + colPlaces = colPlaces[0 : len(colPlaces)-2] + } sqlStr := fmt.Sprintf("INSERT INTO %v%v%v (%v%v%v) VALUES (%v)", session.Engine.QuoteStr(), @@ -2970,10 +3167,6 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { // The in parameter bean must a struct or a point to struct. The return // parameter is inserted and error func (session *Session) InsertOne(bean interface{}) (int64, error) { - err := session.newDb() - if err != nil { - return 0, err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -3068,7 +3261,7 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { session.Engine.LogDebug("[cacheUpdate] get cache sql", newsql, args[nStart:]) ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) if err != nil { - rows, err := session.Db.Query(newsql, args[nStart:]...) + rows, err := session.DB().Query(newsql, args[nStart:]...) if err != nil { return err } @@ -3167,10 +3360,6 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { // You should call UseBool if you have bool to use. // 2.float32 & float64 may be not inexact as conditions func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int64, error) { - err := session.newDb() - if err != nil { - return 0, err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -3192,6 +3381,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } // -- + var err error if t.Kind() == reflect.Struct { table = session.Engine.TableInfo(bean) session.Statement.RefTable = table @@ -3199,7 +3389,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if session.Statement.ColumnStr == "" { colNames, args = buildUpdates(session.Engine, table, bean, false, false, false, false, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.mustColumnMap, true) + session.Statement.mustColumnMap, session.Statement.columnMap, true) } else { colNames, args, err = genCols(table, session, bean, true, true) if err != nil { @@ -3225,7 +3415,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if session.Statement.UseAutoTime && table.Updated != "" { colNames = append(colNames, session.Engine.Quote(table.Updated)+" = ?") - args = append(args, session.Engine.NowTime(table.UpdatedColumn().SQLType.Name)) + col := table.UpdatedColumn() + val, t := session.Engine.NowTime2(col.SQLType.Name) + args = append(args, val) + + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) } //for update action to like "column = column + ?" @@ -3240,6 +3438,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" - ?") args = append(args, v.arg) } + //for update action to like "column = expression" + exprColumns := session.Statement.getExpr() + for _, v := range exprColumns { + colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+v.expr) + } var condiColNames []string var condiArgs []interface{} @@ -3289,6 +3492,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } + if st.LimitN > 0 { + condition = condition + fmt.Sprintf(" LIMIT %d", st.LimitN) + } + sqlStr = fmt.Sprintf("UPDATE %v SET %v, %v %v", session.Engine.Quote(session.Statement.TableName()), strings.Join(colNames, ", "), @@ -3315,6 +3522,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 } } + if st.LimitN > 0 { + condition = condition + fmt.Sprintf(" LIMIT %d", st.LimitN) + } + sqlStr = fmt.Sprintf("UPDATE %v SET %v %v", session.Engine.Quote(session.Statement.TableName()), strings.Join(colNames, ", "), @@ -3329,7 +3540,9 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if err != nil { return 0, err } else if doIncVer { - verValue.SetInt(verValue.Int() + 1) + if verValue != nil && verValue.IsValid() && verValue.CanSet() { + verValue.SetInt(verValue.Int() + 1) + } } if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { @@ -3426,10 +3639,6 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { // Delete records, bean's non-empty fields are conditions func (session *Session) Delete(bean interface{}) (int64, error) { - err := session.newDb() - if err != nil { - return 0, err - } defer session.resetStatement() if session.IsAutoClose { defer session.Close() @@ -3502,7 +3711,15 @@ func (session *Session) Delete(bean interface{}) (int64, error) { session.Statement.Params = append(session.Statement.Params, "") paramsLen := len(session.Statement.Params) copy(session.Statement.Params[1:paramsLen], session.Statement.Params[0:paramsLen-1]) - session.Statement.Params[0] = session.Engine.NowTime(deletedColumn.SQLType.Name) + + val, t := session.Engine.NowTime2(deletedColumn.SQLType.Name) + session.Statement.Params[0] = val + + var colName = deletedColumn.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) } args = append(session.Statement.Params, args...) @@ -3534,7 +3751,6 @@ func (session *Session) Delete(bean interface{}) (int64, error) { copy(afterClosures, session.afterClosures) session.afterDeleteBeans[bean] = &afterClosures } - } else { if _, ok := interface{}(bean).(AfterInsertProcessor); ok { session.afterDeleteBeans[bean] = nil @@ -3716,79 +3932,3 @@ func (session *Session) Unscoped() *Session { session.Statement.Unscoped() return session } - -func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, includeQuote bool) ([]string, []interface{}, error) { - colNames := make([]string, 0) - args := make([]interface{}, 0) - - for _, col := range table.Columns() { - lColName := strings.ToLower(col.Name) - if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated { - if _, ok := session.Statement.columnMap[lColName]; !ok { - continue - } - } - if col.MapType == core.ONLYFROMDB { - continue - } - - fieldValuePtr, err := col.ValueOf(bean) - if err != nil { - session.Engine.LogError(err) - continue - } - fieldValue := *fieldValuePtr - - if col.IsAutoIncrement { - switch fieldValue.Type().Kind() { - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: - if fieldValue.Int() == 0 { - continue - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: - if fieldValue.Uint() == 0 { - continue - } - case reflect.String: - if len(fieldValue.String()) == 0 { - continue - } - } - } - - if col.IsDeleted { - continue - } - - if session.Statement.ColumnStr != "" { - if _, ok := session.Statement.columnMap[lColName]; !ok { - continue - } - } - if session.Statement.OmitStr != "" { - if _, ok := session.Statement.columnMap[lColName]; ok { - continue - } - } - - if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { - args = append(args, session.Engine.NowTime(col.SQLType.Name)) - } else if col.IsVersion && session.Statement.checkVersion { - args = append(args, 1) - //} else if !col.DefaultIsEmpty { - } else { - arg, err := session.value2Interface(col, fieldValue) - if err != nil { - return colNames, args, err - } - args = append(args, arg) - } - - if includeQuote { - colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?") - } else { - colNames = append(colNames, col.Name) - } - } - return colNames, args, nil -} diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/sqlite3_dialect.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/sqlite3_dialect.go index 1a0282999c3..cb9e7f54a78 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/sqlite3_dialect.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/sqlite3_dialect.go @@ -1,6 +1,7 @@ package xorm import ( + "database/sql" "errors" "fmt" "strings" @@ -152,7 +153,7 @@ func (db *sqlite3) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName st func (db *sqlite3) SqlType(c *core.Column) string { switch t := c.SQLType.Name; t { case core.Date, core.DateTime, core.TimeStamp, core.Time: - return core.Numeric + return core.DateTime case core.TimeStampz: return core.Text case core.Char, core.Varchar, core.NVarchar, core.TinyText, core.Text, core.MediumText, core.LongText: @@ -297,6 +298,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu col := new(core.Column) col.Indexes = make(map[string]bool) col.Nullable = true + col.DefaultIsEmpty = true for idx, field := range fields { if idx == 0 { col.Name = strings.Trim(field, "`[] ") @@ -315,8 +317,14 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu } else { col.Nullable = true } + case "DEFAULT": + col.Default = fields[idx+1] + col.DefaultIsEmpty = false } } + if !col.SQLType.IsNumeric() && !col.DefaultIsEmpty { + col.Default = "'" + col.Default + "'" + } cols[col.Name] = col colSeq = append(colSeq, col.Name) } @@ -366,15 +374,16 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) indexes := make(map[string]*core.Index, 0) for rows.Next() { - var sql string - err = rows.Scan(&sql) + var tmpSql sql.NullString + err = rows.Scan(&tmpSql) if err != nil { return nil, err } - if sql == "" { + if !tmpSql.Valid { continue } + sql := tmpSql.String index := new(core.Index) nNStart := strings.Index(sql, "INDEX") @@ -384,7 +393,6 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) } indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") - //fmt.Println(indexName) if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { index.Name = indexName[5+len(tableName) : len(indexName)] } else { diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/statement.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/statement.go index 6c586ae6686..b8f859714ea 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/statement.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/statement.go @@ -26,6 +26,11 @@ type decrParam struct { arg interface{} } +type exprParam struct { + colName string + expr string +} + // statement save all the sql info for executing SQL type Statement struct { RefTable *core.Table @@ -63,6 +68,7 @@ type Statement struct { inColumns map[string]*inParam incrColumns map[string]incrParam decrColumns map[string]decrParam + exprColumns map[string]exprParam } // init @@ -98,6 +104,7 @@ func (statement *Statement) Init() { statement.inColumns = make(map[string]*inParam) statement.incrColumns = make(map[string]incrParam) statement.decrColumns = make(map[string]decrParam) + statement.exprColumns = make(map[string]exprParam) } // add the raw sql statement @@ -153,9 +160,6 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { t := v.Type() if t.Kind() == reflect.String { statement.AltTableName = tableNameOrBean.(string) - if statement.AltTableName[0] == '~' { - statement.AltTableName = statement.Engine.TableMapper.TableName(statement.AltTableName[1:]) - } } else if t.Kind() == reflect.Struct { statement.RefTable = statement.Engine.autoMapType(v) } @@ -282,7 +286,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { func buildUpdates(engine *Engine, table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, - mustColumnMap map[string]bool, update bool) ([]string, []interface{}) { + mustColumnMap map[string]bool, columnMap map[string]bool, update bool) ([]string, []interface{}) { colNames := make([]string, 0) var args = make([]interface{}, 0) @@ -302,6 +306,9 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if col.IsDeleted { continue } + if use, ok := columnMap[col.Name]; ok && !use { + continue + } if engine.dialect.DBType() == core.MSSQL && col.SQLType.Name == core.Text { continue @@ -414,13 +421,16 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if table, ok := engine.Tables[fieldValue.Type()]; ok { if len(table.PrimaryKeys) == 1 { pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) - if pkField.Int() != 0 { + // fix non-int pk issues + //if pkField.Int() != 0 { + if pkField.IsValid() && !isZero(pkField.Interface()) { val = pkField.Interface() } else { continue } } else { //TODO: how to handler? + panic("not supported") } } else { val = fieldValue.Interface() @@ -579,24 +589,29 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, t := int64(fieldValue.Uint()) val = reflect.ValueOf(&t).Interface() case reflect.Struct: - if fieldType == reflect.TypeOf(time.Now()) { - t := fieldValue.Interface().(time.Time) + if fieldType.ConvertibleTo(core.TimeType) { + t := fieldValue.Convert(core.TimeType).Interface().(time.Time) if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } val = engine.FormatTime(col.SQLType.Name, t) + } else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok { + continue } else { engine.autoMapType(fieldValue) if table, ok := engine.Tables[fieldValue.Type()]; ok { if len(table.PrimaryKeys) == 1 { pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) - if pkField.Int() != 0 { + // fix non-int pk issues + //if pkField.Int() != 0 { + if pkField.IsValid() && !isZero(pkField.Interface()) { val = pkField.Interface() } else { continue } } else { //TODO: how to handler? + panic("not supported") } } else { val = fieldValue.Interface() @@ -716,6 +731,13 @@ func (statement *Statement) Decr(column string, arg ...interface{}) *Statement { return statement } +// Generate "Update ... Set column = {expression}" statment +func (statement *Statement) SetExpr(column string, expression string) *Statement { + k := strings.ToLower(column) + statement.exprColumns[k] = exprParam{column, expression} + return statement +} + // Generate "Update ... Set column = column + arg" statment func (statement *Statement) getInc() map[string]incrParam { return statement.incrColumns @@ -726,6 +748,11 @@ func (statement *Statement) getDec() map[string]decrParam { return statement.decrColumns } +// Generate "Update ... Set column = {expression}" statment +func (statement *Statement) getExpr() map[string]exprParam { + return statement.exprColumns +} + // Generate "Where column IN (?) " statment func (statement *Statement) In(column string, args ...interface{}) *Statement { k := strings.ToLower(column) @@ -941,15 +968,9 @@ func (statement *Statement) Join(join_operator string, tablename interface{}, co l := len(t) if l > 1 { table := t[0] - if table[0] == '~' { - table = statement.Engine.TableMapper.TableName(table[1:]) - } joinTable = statement.Engine.Quote(table) + " AS " + statement.Engine.Quote(t[1]) } else if l == 1 { table := t[0] - if table[0] == '~' { - table = statement.Engine.TableMapper.TableName(table[1:]) - } joinTable = statement.Engine.Quote(table) } case []interface{}: @@ -962,9 +983,6 @@ func (statement *Statement) Join(join_operator string, tablename interface{}, co t := v.Type() if t.Kind() == reflect.String { table = f.(string) - if table[0] == '~' { - table = statement.Engine.TableMapper.TableName(table[1:]) - } } else if t.Kind() == reflect.Struct { r := statement.Engine.autoMapType(v) table = r.Name @@ -977,9 +995,6 @@ func (statement *Statement) Join(join_operator string, tablename interface{}, co } default: t := fmt.Sprintf("%v", tablename) - if t[0] == '~' { - t = statement.Engine.TableMapper.TableName(t[1:]) - } joinTable = statement.Engine.Quote(t) } if statement.JoinStr != "" { @@ -1105,9 +1120,10 @@ func (s *Statement) genDelIndexSQL() []string { return sqls } +/* func (s *Statement) genDropSQL() string { - return s.Engine.dialect.DropTableSql(s.TableName()) + ";" -} + return s.Engine.dialect.MustDropTa(s.TableName()) + ";" +}*/ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) { var table *core.Table @@ -1126,13 +1142,21 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) statement.BeanArgs = args var columnStr string = statement.ColumnStr - if statement.JoinStr == "" { - if columnStr == "" { - columnStr = statement.genColumnStr() + if len(statement.JoinStr) == 0 { + if len(columnStr) == 0 { + if statement.GroupByStr != "" { + columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1)) + } else { + columnStr = statement.genColumnStr() + } } } else { - if columnStr == "" { - columnStr = "*" + if len(columnStr) == 0 { + if statement.GroupByStr != "" { + columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1)) + } else { + columnStr = "*" + } } } @@ -1178,14 +1202,16 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{} id = "" } statement.attachInSql() - return statement.genSelectSql(fmt.Sprintf("count(%v) AS %v", id, statement.Engine.Quote("total"))), append(statement.Params, statement.BeanArgs...) + return statement.genSelectSql(fmt.Sprintf("count(%v)", id)), append(statement.Params, statement.BeanArgs...) } func (statement *Statement) genSelectSql(columnStr string) (a string) { - if statement.GroupByStr != "" { - columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1)) - statement.GroupByStr = columnStr - } + /*if statement.GroupByStr != "" { + if columnStr == "" { + columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1)) + } + //statement.GroupByStr = columnStr + }*/ var distinct string if statement.IsDistinct { distinct = "DISTINCT " @@ -1210,7 +1236,11 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) { } var fromStr string = " FROM " + statement.Engine.Quote(statement.TableName()) if statement.TableAlias != "" { - fromStr += " AS " + statement.Engine.Quote(statement.TableAlias) + if statement.Engine.dialect.DBType() == core.ORACLE { + fromStr += " " + statement.Engine.Quote(statement.TableAlias) + } else { + fromStr += " AS " + statement.Engine.Quote(statement.TableAlias) + } } if statement.JoinStr != "" { fromStr = fmt.Sprintf("%v %v", fromStr, statement.JoinStr) @@ -1233,8 +1263,16 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) { column = statement.RefTable.ColumnsSeq()[0] } } - mssqlCondi = fmt.Sprintf("(%s NOT IN (SELECT TOP %d %s%s%s))", - column, statement.Start, column, fromStr, whereStr) + var orderStr string + if len(statement.OrderStr) > 0 { + orderStr = " ORDER BY " + statement.OrderStr + } + var groupStr string + if len(statement.GroupByStr) > 0 { + groupStr = " GROUP BY " + statement.GroupByStr + } + mssqlCondi = fmt.Sprintf("(%s NOT IN (SELECT TOP %d %s%s%s%s%s))", + column, statement.Start, column, fromStr, whereStr, orderStr, groupStr) } } @@ -1258,12 +1296,16 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) { if statement.OrderStr != "" { a = fmt.Sprintf("%v ORDER BY %v", a, statement.OrderStr) } - if statement.Engine.dialect.DBType() != core.MSSQL { + if statement.Engine.dialect.DBType() != core.MSSQL && statement.Engine.dialect.DBType() != core.ORACLE { if statement.Start > 0 { a = fmt.Sprintf("%v LIMIT %v OFFSET %v", a, statement.LimitN, statement.Start) } else if statement.LimitN > 0 { a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN) } + } else if statement.Engine.dialect.DBType() == core.ORACLE { + if statement.Start != 0 || statement.LimitN != 0 { + a = fmt.Sprintf("SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d", columnStr, columnStr, a, statement.Start+statement.LimitN, statement.Start) + } } return diff --git a/Godeps/_workspace/src/github.com/go-xorm/xorm/xorm.go b/Godeps/_workspace/src/github.com/go-xorm/xorm/xorm.go index 76496ddd33a..71644e6c039 100644 --- a/Godeps/_workspace/src/github.com/go-xorm/xorm/xorm.go +++ b/Godeps/_workspace/src/github.com/go-xorm/xorm/xorm.go @@ -13,7 +13,7 @@ import ( ) const ( - Version string = "0.4.1" + Version string = "0.4.2.0225" ) func regDrvsNDialects() bool { @@ -84,17 +84,16 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { TZLocation: time.Local, } + engine.dialect.SetLogger(engine.Logger) + engine.SetMapper(core.NewCacheMapper(new(core.SnakeMapper))) - //engine.Filters = dialect.Filters() - //engine.Cacher = NewLRUCacher() - //err = engine.SetPool(NewSysConnectPool()) - runtime.SetFinalizer(engine, close) - return engine, err + + return engine, nil } // clone an engine func (engine *Engine) Clone() (*Engine, error) { - return NewEngine(engine.dialect.DriverName(), engine.dialect.DataSourceName()) + return NewEngine(engine.DriverName(), engine.DataSourceName()) } diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/README.md b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/README.md index 9d04745fa78..4383f0cd4ce 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/README.md +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/README.md @@ -41,12 +41,18 @@ FAQ > See: https://github.com/mattn/go-sqlite3/issues/106 > See also: http://www.limitlessfx.com/cross-compile-golang-app-for-windows-from-linux.html +* Want to get time.Time with current locale + + Use `loc=auto` in SQLite3 filename schema like `file:foo.db?loc=auto`. + License ------- MIT: http://mattn.mit-license.org/2012 -sqlite.c, sqlite3.h, sqlite3ext.h +sqlite3-binding.c, sqlite3-binding.h, sqlite3ext.h + +The -binding suffix was added to avoid build failures under gccgo. In this repository, those files are amalgamation code that copied from SQLite3. The license of those codes are depend on the license of SQLite3. diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/backup.go b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/backup.go index 270446aa724..3807c606b22 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/backup.go +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/backup.go @@ -6,7 +6,7 @@ package sqlite3 /* -#include +#include #include */ import "C" diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/error_test.go b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/error_test.go index a0061889465..1ccbe5bf858 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/error_test.go +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/error_test.go @@ -231,6 +231,12 @@ func TestExtendedErrorCodes_Unique(t *testing.T) { t.Errorf("Wrong extended error code: %d != %d", sqliteErr.ExtendedCode, ErrConstraintUnique) } + extended := sqliteErr.Code.Extend(3).Error() + expected := "constraint failed" + if extended != expected { + t.Errorf("Wrong basic error code: %q != %q", + extended, expected) + } } } diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.c b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3-binding.c similarity index 100% rename from Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.c rename to Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3-binding.c diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.h b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3-binding.h similarity index 100% rename from Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.h rename to Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3-binding.h diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.go b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.go index d446fb69f42..f4de3fd6f1c 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.go +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3.go @@ -6,7 +6,10 @@ package sqlite3 /* -#include +#cgo CFLAGS: -std=gnu99 +#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE +#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS +#include #include #include @@ -44,14 +47,23 @@ _sqlite3_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { #include #include -static long -_sqlite3_last_insert_rowid(sqlite3* db) { - return (long) sqlite3_last_insert_rowid(db); +static int +_sqlite3_exec(sqlite3* db, const char* pcmd, long* rowid, long* changes) +{ + int rv = sqlite3_exec(db, pcmd, 0, 0, 0); + *rowid = (long) sqlite3_last_insert_rowid(db); + *changes = (long) sqlite3_changes(db); + return rv; } -static long -_sqlite3_changes(sqlite3* db) { - return (long) sqlite3_changes(db); +static int +_sqlite3_step(sqlite3_stmt* stmt, long* rowid, long* changes) +{ + int rv = sqlite3_step(stmt); + sqlite3* db = sqlite3_db_handle(stmt); + *rowid = (long) sqlite3_last_insert_rowid(db); + *changes = (long) sqlite3_changes(db); + return rv; } */ @@ -60,8 +72,11 @@ import ( "database/sql" "database/sql/driver" "errors" + "fmt" "io" + "net/url" "runtime" + "strconv" "strings" "time" "unsafe" @@ -102,7 +117,8 @@ type SQLiteDriver struct { // Conn struct. type SQLiteConn struct { - db *C.sqlite3 + db *C.sqlite3 + loc *time.Location } // Tx struct. @@ -114,6 +130,8 @@ type SQLiteTx struct { type SQLiteStmt struct { c *SQLiteConn s *C.sqlite3_stmt + nv int + nn []string t string closed bool cls bool @@ -174,7 +192,7 @@ func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, err if s.(*SQLiteStmt).s != nil { na := s.NumInput() if len(args) < na { - return nil, errors.New("args is not enough to execute query") + return nil, fmt.Errorf("Not enough args to execute query. Expected %d, got %d.", na, len(args)) } res, err = s.Exec(args[:na]) if err != nil && err != driver.ErrSkip { @@ -201,6 +219,9 @@ func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, erro } s.(*SQLiteStmt).cls = true na := s.NumInput() + if len(args) < na { + return nil, fmt.Errorf("Not enough args to execute query. Expected %d, got %d.", na, len(args)) + } rows, err := s.Query(args[:na]) if err != nil && err != driver.ErrSkip { s.Close() @@ -220,14 +241,13 @@ func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, erro func (c *SQLiteConn) exec(cmd string) (driver.Result, error) { pcmd := C.CString(cmd) defer C.free(unsafe.Pointer(pcmd)) - rv := C.sqlite3_exec(c.db, pcmd, nil, nil, nil) + + var rowid, changes C.long + rv := C._sqlite3_exec(c.db, pcmd, &rowid, &changes) if rv != C.SQLITE_OK { return nil, c.lastError() } - return &SQLiteResult{ - int64(C._sqlite3_last_insert_rowid(c.db)), - int64(C._sqlite3_changes(c.db)), - }, nil + return &SQLiteResult{int64(rowid), int64(changes)}, nil } // Begin transaction. @@ -248,11 +268,51 @@ func errorString(err Error) string { // file:test.db?cache=shared&mode=memory // :memory: // file::memory: +// go-sqlite handle especially query parameters. +// _loc=XXX +// Specify location of time format. It's possible to specify "auto". +// _busy_timeout=XXX +// Specify value for sqlite3_busy_timeout. func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { if C.sqlite3_threadsafe() == 0 { return nil, errors.New("sqlite library was not compiled for thread-safe operation") } + var loc *time.Location + busy_timeout := 5000 + pos := strings.IndexRune(dsn, '?') + if pos >= 1 { + params, err := url.ParseQuery(dsn[pos+1:]) + if err != nil { + return nil, err + } + + // _loc + if val := params.Get("_loc"); val != "" { + if val == "auto" { + loc = time.Local + } else { + loc, err = time.LoadLocation(val) + if err != nil { + return nil, fmt.Errorf("Invalid _loc: %v: %v", val, err) + } + } + } + + // _busy_timeout + if val := params.Get("_busy_timeout"); val != "" { + iv, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return nil, fmt.Errorf("Invalid _busy_timeout: %v: %v", val, err) + } + busy_timeout = int(iv) + } + + if !strings.HasPrefix(dsn, "file:") { + dsn = dsn[:pos] + } + } + var db *C.sqlite3 name := C.CString(dsn) defer C.free(unsafe.Pointer(name)) @@ -268,12 +328,12 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil, errors.New("sqlite succeeded without returning a database") } - rv = C.sqlite3_busy_timeout(db, 5000) + rv = C.sqlite3_busy_timeout(db, C.int(busy_timeout)) if rv != C.SQLITE_OK { return nil, Error{Code: ErrNo(rv)} } - conn := &SQLiteConn{db} + conn := &SQLiteConn{db: db, loc: loc} if len(d.Extensions) > 0 { rv = C.sqlite3_enable_load_extension(db, 1) @@ -281,21 +341,15 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil, errors.New(C.GoString(C.sqlite3_errmsg(db))) } - stmt, err := conn.Prepare("SELECT load_extension(?);") - if err != nil { - return nil, err - } - for _, extension := range d.Extensions { - if _, err = stmt.Exec([]driver.Value{extension}); err != nil { - return nil, err + cext := C.CString(extension) + defer C.free(unsafe.Pointer(cext)) + rv = C.sqlite3_load_extension(db, cext, nil, nil) + if rv != C.SQLITE_OK { + return nil, errors.New(C.GoString(C.sqlite3_errmsg(db))) } } - if err = stmt.Close(); err != nil { - return nil, err - } - rv = C.sqlite3_enable_load_extension(db, 0) if rv != C.SQLITE_OK { return nil, errors.New(C.GoString(C.sqlite3_errmsg(db))) @@ -333,10 +387,18 @@ func (c *SQLiteConn) Prepare(query string) (driver.Stmt, error) { return nil, c.lastError() } var t string - if tail != nil && C.strlen(tail) > 0 { + if tail != nil && *tail != '\000' { t = strings.TrimSpace(C.GoString(tail)) } - ss := &SQLiteStmt{c: c, s: s, t: t} + nv := int(C.sqlite3_bind_parameter_count(s)) + var nn []string + for i := 0; i < nv; i++ { + pn := C.GoString(C.sqlite3_bind_parameter_name(s, C.int(i+1))) + if len(pn) > 1 && pn[0] == '$' && 48 <= pn[1] && pn[1] <= 57 { + nn = append(nn, C.GoString(C.sqlite3_bind_parameter_name(s, C.int(i+1)))) + } + } + ss := &SQLiteStmt{c: c, s: s, nv: nv, nn: nn, t: t} runtime.SetFinalizer(ss, (*SQLiteStmt).Close) return ss, nil } @@ -360,7 +422,12 @@ func (s *SQLiteStmt) Close() error { // Return a number of parameters. func (s *SQLiteStmt) NumInput() int { - return int(C.sqlite3_bind_parameter_count(s.s)) + return s.nv +} + +type bindArg struct { + n int + v driver.Value } func (s *SQLiteStmt) bind(args []driver.Value) error { @@ -369,8 +436,24 @@ func (s *SQLiteStmt) bind(args []driver.Value) error { return s.c.lastError() } - for i, v := range args { - n := C.int(i + 1) + var vargs []bindArg + narg := len(args) + vargs = make([]bindArg, narg) + if len(s.nn) > 0 { + for i, v := range s.nn { + if pi, err := strconv.Atoi(v[1:]); err == nil { + vargs[i] = bindArg{pi, args[i]} + } + } + } else { + for i, v := range args { + vargs[i] = bindArg{i + 1, v} + } + } + + for _, varg := range vargs { + n := C.int(varg.n) + v := varg.v switch v := v.(type) { case nil: rv = C.sqlite3_bind_null(s.s, n) @@ -431,19 +514,18 @@ func (r *SQLiteResult) RowsAffected() (int64, error) { func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) { if err := s.bind(args); err != nil { C.sqlite3_reset(s.s) + C.sqlite3_clear_bindings(s.s) return nil, err } - rv := C.sqlite3_step(s.s) + var rowid, changes C.long + rv := C._sqlite3_step(s.s, &rowid, &changes) if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { + err := s.c.lastError() C.sqlite3_reset(s.s) - return nil, s.c.lastError() + C.sqlite3_clear_bindings(s.s) + return nil, err } - - res := &SQLiteResult{ - int64(C._sqlite3_last_insert_rowid(s.c.db)), - int64(C._sqlite3_changes(s.c.db)), - } - return res, nil + return &SQLiteResult{int64(rowid), int64(changes)}, nil } // Close the rows. @@ -499,7 +581,22 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error { val := int64(C.sqlite3_column_int64(rc.s.s, C.int(i))) switch rc.decltype[i] { case "timestamp", "datetime", "date": - dest[i] = time.Unix(val, 0).Local() + unixTimestamp := strconv.FormatInt(val, 10) + var t time.Time + if len(unixTimestamp) == 13 { + duration, err := time.ParseDuration(unixTimestamp + "ms") + if err != nil { + return fmt.Errorf("error parsing %s value %d, %s", rc.decltype[i], val, err) + } + epoch := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) + t = epoch.Add(duration) + } else { + t = time.Unix(val, 0) + } + if rc.s.c.loc != nil { + t = t.In(rc.s.c.loc) + } + dest[i] = t case "boolean": dest[i] = val > 0 default: @@ -531,16 +628,21 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error { switch rc.decltype[i] { case "timestamp", "datetime", "date": + var t time.Time for _, format := range SQLiteTimestampFormats { if timeVal, err = time.ParseInLocation(format, s, time.UTC); err == nil { - dest[i] = timeVal.Local() + t = timeVal break } } if err != nil { // The column is a time value, so return the zero time on parse failure. - dest[i] = time.Time{} + t = time.Time{} } + if rc.s.c.loc != nil { + t = t.In(rc.s.c.loc) + } + dest[i] = t default: dest[i] = []byte(s) } diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_fts3_test.go b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_fts3_test.go new file mode 100644 index 00000000000..a1cd2172d79 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_fts3_test.go @@ -0,0 +1,83 @@ +// Copyright (C) 2015 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package sqlite3 + +import ( + "database/sql" + "os" + "testing" +) + +func TestFTS3(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec("DROP TABLE foo") + _, err = db.Exec("CREATE VIRTUAL TABLE foo USING fts3(id INTEGER PRIMARY KEY, value TEXT)") + if err != nil { + t.Fatal("Failed to create table:", err) + } + + _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 1, `今日の 晩御飯は 天麩羅よ`) + if err != nil { + t.Fatal("Failed to insert value:", err) + } + + _, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 2, `今日は いい 天気だ`) + if err != nil { + t.Fatal("Failed to insert value:", err) + } + + rows, err := db.Query("SELECT id, value FROM foo WHERE value MATCH '今日* 天*'") + if err != nil { + t.Fatal("Unable to query foo table:", err) + } + defer rows.Close() + + for rows.Next() { + var id int + var value string + + if err := rows.Scan(&id, &value); err != nil { + t.Error("Unable to scan results:", err) + continue + } + + if id == 1 && value != `今日の 晩御飯は 天麩羅よ` { + t.Error("Value for id 1 should be `今日の 晩御飯は 天麩羅よ`, but:", value) + } else if id == 2 && value != `今日は いい 天気だ` { + t.Error("Value for id 2 should be `今日は いい 天気だ`, but:", value) + } + } + + rows, err = db.Query("SELECT value FROM foo WHERE value MATCH '今日* 天麩羅*'") + if err != nil { + t.Fatal("Unable to query foo table:", err) + } + defer rows.Close() + + var value string + if !rows.Next() { + t.Fatal("Result should be only one") + } + + if err := rows.Scan(&value); err != nil { + t.Fatal("Unable to scan results:", err) + } + + if value != `今日の 晩御飯は 天麩羅よ` { + t.Fatal("Value should be `今日の 晩御飯は 天麩羅よ`, but:", value) + } + + if rows.Next() { + t.Fatal("Result should be only one") + } +} diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_other.go b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_other.go index 54b6c7a5e25..8d98b4a3a6c 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_other.go +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_other.go @@ -9,6 +9,6 @@ package sqlite3 /* #cgo CFLAGS: -I. #cgo linux LDFLAGS: -ldl -#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE +#cgo LDFLAGS: -lpthread */ import "C" diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_test.go b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_test.go index 9cc5a0ec5ed..aa8601181b5 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_test.go +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_test.go @@ -9,8 +9,10 @@ import ( "crypto/rand" "database/sql" "encoding/hex" + "net/url" "os" "path/filepath" + "strings" "testing" "time" @@ -309,6 +311,7 @@ func TestTimestamp(t *testing.T) { {"0000-00-00 00:00:00", time.Time{}}, {timestamp1, timestamp1}, {timestamp1.Unix(), timestamp1}, + {timestamp1.UnixNano() / int64(time.Millisecond), timestamp1}, {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1}, {timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1}, {timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1}, @@ -633,6 +636,102 @@ func TestWAL(t *testing.T) { } } +func TestTimezoneConversion(t *testing.T) { + zones := []string{"UTC", "US/Central", "US/Pacific", "Local"} + for _, tz := range zones { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename+"?_loc="+url.QueryEscape(tz)) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec("DROP TABLE foo") + _, err = db.Exec("CREATE TABLE foo(id INTEGER, ts TIMESTAMP, dt DATETIME)") + if err != nil { + t.Fatal("Failed to create table:", err) + } + + loc, err := time.LoadLocation(tz) + if err != nil { + t.Fatal("Failed to load location:", err) + } + + timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC) + timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC) + timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC) + tests := []struct { + value interface{} + expected time.Time + }{ + {"nonsense", time.Time{}.In(loc)}, + {"0000-00-00 00:00:00", time.Time{}.In(loc)}, + {timestamp1, timestamp1.In(loc)}, + {timestamp1.Unix(), timestamp1.In(loc)}, + {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02 15:04:05"), timestamp1.In(loc)}, + {timestamp1.Format("2006-01-02T15:04:05"), timestamp1.In(loc)}, + {timestamp2, timestamp2.In(loc)}, + {"2006-01-02 15:04:05.123456789", timestamp2.In(loc)}, + {"2006-01-02T15:04:05.123456789", timestamp2.In(loc)}, + {"2012-11-04", timestamp3.In(loc)}, + {"2012-11-04 00:00", timestamp3.In(loc)}, + {"2012-11-04 00:00:00", timestamp3.In(loc)}, + {"2012-11-04 00:00:00.000", timestamp3.In(loc)}, + {"2012-11-04T00:00", timestamp3.In(loc)}, + {"2012-11-04T00:00:00", timestamp3.In(loc)}, + {"2012-11-04T00:00:00.000", timestamp3.In(loc)}, + } + for i := range tests { + _, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value) + if err != nil { + t.Fatal("Failed to insert timestamp:", err) + } + } + + rows, err := db.Query("SELECT id, ts, dt FROM foo ORDER BY id ASC") + if err != nil { + t.Fatal("Unable to query foo table:", err) + } + defer rows.Close() + + seen := 0 + for rows.Next() { + var id int + var ts, dt time.Time + + if err := rows.Scan(&id, &ts, &dt); err != nil { + t.Error("Unable to scan results:", err) + continue + } + if id < 0 || id >= len(tests) { + t.Error("Bad row id: ", id) + continue + } + seen++ + if !tests[id].expected.Equal(ts) { + t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, ts) + } + if !tests[id].expected.Equal(dt) { + t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt) + } + if tests[id].expected.Location().String() != ts.Location().String() { + t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), ts.Location().String()) + } + if tests[id].expected.Location().String() != dt.Location().String() { + t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), dt.Location().String()) + } + } + + if seen != len(tests) { + t.Errorf("Expected to see %d rows", len(tests)) + } + } +} + func TestSuite(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { @@ -742,3 +841,107 @@ func TestStress(t *testing.T) { db.Close() } } + +func TestDateTimeLocal(t *testing.T) { + zone := "Asia/Tokyo" + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename+"?_loc="+zone) + if err != nil { + t.Fatal("Failed to open database:", err) + } + db.Exec("CREATE TABLE foo (dt datetime);") + db.Exec("INSERT INTO foo VALUES('2015-03-05 15:16:17');") + + row := db.QueryRow("select * from foo") + var d time.Time + err = row.Scan(&d) + if err != nil { + t.Fatal("Failed to scan datetime:", err) + } + if d.Hour() == 15 || !strings.Contains(d.String(), "JST") { + t.Fatal("Result should have timezone", d) + } + db.Close() + + db, err = sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + + row = db.QueryRow("select * from foo") + err = row.Scan(&d) + if err != nil { + t.Fatal("Failed to scan datetime:", err) + } + if d.UTC().Hour() != 15 || !strings.Contains(d.String(), "UTC") { + t.Fatalf("Result should not have timezone %v %v", zone, d.String()) + } + + _, err = db.Exec("DELETE FROM foo") + if err != nil { + t.Fatal("Failed to delete table:", err) + } + dt, err := time.Parse("2006/1/2 15/4/5 -0700 MST", "2015/3/5 15/16/17 +0900 JST") + if err != nil { + t.Fatal("Failed to parse datetime:", err) + } + db.Exec("INSERT INTO foo VALUES(?);", dt) + + db.Close() + db, err = sql.Open("sqlite3", tempFilename+"?_loc="+zone) + if err != nil { + t.Fatal("Failed to open database:", err) + } + + row = db.QueryRow("select * from foo") + err = row.Scan(&d) + if err != nil { + t.Fatal("Failed to scan datetime:", err) + } + if d.Hour() != 15 || !strings.Contains(d.String(), "JST") { + t.Fatalf("Result should have timezone %v %v", zone, d.String()) + } +} + +func TestVersion(t *testing.T) { + s, n, id := Version() + if s == "" || n == 0 || id == "" { + t.Errorf("Version failed %q, %d, %q\n", s, n, id) + } +} + +func TestNumberNamedParams(t *testing.T) { + tempFilename := TempFilename() + db, err := sql.Open("sqlite3", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer os.Remove(tempFilename) + defer db.Close() + + _, err = db.Exec(` + create table foo (id integer, name text, extra text); + `) + if err != nil { + t.Error("Failed to call db.Query:", err) + } + + _, err = db.Exec(`insert into foo(id, name, extra) values($1, $2, $2)`, 1, "foo") + if err != nil { + t.Error("Failed to call db.Exec:", err) + } + + row := db.QueryRow(`select id, extra from foo where id = $1 and extra = $2`, 1, "foo") + if row == nil { + t.Error("Failed to call db.QueryRow") + } + var id int + var extra string + err = row.Scan(&id, &extra) + if err != nil { + t.Error("Failed to db.Scan:", err) + } + if id != 1 || extra != "foo" { + t.Error("Failed to db.QueryRow: not matched results") + } +} diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_windows.go b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_windows.go index 84eb457f61b..abc8384e4b8 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_windows.go +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3_windows.go @@ -2,6 +2,7 @@ // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +// +build windows package sqlite3 @@ -9,6 +10,5 @@ package sqlite3 #cgo CFLAGS: -I. -fno-stack-check -fno-stack-protector -mno-stack-arg-probe #cgo windows,386 CFLAGS: -D_localtime32=localtime #cgo LDFLAGS: -lmingwex -lmingw32 -#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE */ import "C" diff --git a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3ext.h b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3ext.h index ecf93f62f6c..7cc58b6f86b 100644 --- a/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3ext.h +++ b/Godeps/_workspace/src/github.com/mattn/go-sqlite3/sqlite3ext.h @@ -17,7 +17,7 @@ */ #ifndef _SQLITE3EXT_H_ #define _SQLITE3EXT_H_ -#include "sqlite3.h" +#include "sqlite3-binding.h" typedef struct sqlite3_api_routines sqlite3_api_routines;