diff --git a/base_test.go b/base_test.go index 2c12c6d3..c17ed772 100644 --- a/base_test.go +++ b/base_test.go @@ -42,7 +42,7 @@ type Userdetail struct { } func directCreateTable(engine *Engine, t *testing.T) { - err := engine.CreateTables(&Userinfo{}) + err := engine.Sync(&Userinfo{}) if err != nil { t.Error(err) panic(err) diff --git a/benchmark_base_test.go b/benchmark_base_test.go new file mode 100644 index 00000000..89c2a80e --- /dev/null +++ b/benchmark_base_test.go @@ -0,0 +1,50 @@ +package xorm + +import ( + "testing" +) + +type BigStruct struct { + Id int64 + Name string + Title string + Age string + Alias string + NickName string +} + +func doBenchCacheFind(engine *Engine, b *testing.B) { + b.StopTimer() + bs := &BigStruct{0, "fafdasf", "fadfa", "afadfsaf", "fadfafdsafd", "fadfafdsaf"} + err := engine.CreateTables(bs) + if err != nil { + b.Error(err) + return + } + + for i := 0; i < 100; i++ { + bs.Id = 0 + _, err = engine.Insert(bs) + if err != nil { + b.Error(err) + return + } + } + + b.StartTimer() + for i := 0; i < b.N; i++ { + bss := new([]BigStruct) + err = engine.Limit(50).Find(bss) + if err != nil { + b.Error(err) + return + } + } + b.StopTimer() + err = engine.DropTables(bs) + if err != nil { + b.Error(err) + return + } + +} diff --git a/engine.go b/engine.go index 9fea8bcd..c006b33c 100644 --- a/engine.go +++ b/engine.go @@ -2,6 +2,7 @@ package xorm import ( "database/sql" + "errors" "fmt" "io" "reflect" @@ -18,6 +19,7 @@ const ( ) type dialect interface { + Init(uri string) error SqlType(t *Column) string SupportInsertMany() bool QuoteStr() string @@ -25,6 +27,9 @@ type dialect interface { SupportEngine() bool SupportCharset() bool IndexOnTable() bool + IndexCheckSql(tableName, idxName string) (string, []interface{}) + TableCheckSql(tableName string) (string, []interface{}) + ColumnCheckSql(tableName, colName string) (string, []interface{}) } type Engine struct { @@ -70,6 +75,7 @@ func (engine *Engine) SetPool(pool IConnectPool) error { return engine.Pool.Init(engine) } +// only for go 1.2+ func (engine *Engine) SetMaxConns(conns int) { engine.Pool.SetMaxConns(conns) } @@ -465,6 +471,132 @@ func (engine *Engine) Map(beans ...interface{}) (e error) { return } +// is a table has +func (engine *Engine) IsEmptyTable(bean interface{}) (bool, error) { + t := Type(bean) + if t.Kind() != reflect.Struct { + return false, errors.New("bean should be a struct or struct's point") + } + engine.AutoMapType(t) + session := engine.NewSession() + defer session.Close() + has, err := session.Get(bean) + return !has, err +} + +func (engine *Engine) isTableExist(bean interface{}) (bool, error) { + t := Type(bean) + if t.Kind() != reflect.Struct { + return false, errors.New("bean should be a struct or struct's point") + } + table := engine.AutoMapType(t) + session := engine.NewSession() + defer session.Close() + has, err := session.isTableExist(table.Name) + return has, err +} + +func (engine *Engine) ClearCache(beans ...interface{}) { + for _, bean := range beans { + table := engine.AutoMap(bean) + table.Cacher.ClearIds(table.Name) + } +} + +// sync the new struct to database, this method will auto add column, index, unique +// but will not delete or change anything. +func (engine *Engine) Sync(beans ...interface{}) error { + for _, bean := range beans { + table := engine.AutoMap(bean) + + s := engine.NewSession() + defer s.Close() + isExist, err := s.Table(bean).isTableExist(table.Name) + if err != nil { + return err + } + if !isExist { + err = engine.CreateTables(bean) + if err != nil { + return err + } + } else { + isEmpty, err := engine.IsEmptyTable(bean) + if err != nil { + return err + } + if isEmpty { + err = engine.DropTables(bean) + if err != nil { + return err + } + err = engine.CreateTables(bean) + if err != nil { + return err + } + } else { + for _, col := range table.Columns { + session := engine.NewSession() + session.Statement.RefTable = table + defer session.Close() + isExist, err := session.isColumnExist(table.Name, col.Name) + if err != nil { + return err + } + if !isExist { + session := engine.NewSession() + session.Statement.RefTable = table + defer session.Close() + err = session.addColumn(col.Name) + if err != nil { + return err + } + } + } + + for idx, _ := range table.Indexes { + session := engine.NewSession() + session.Statement.RefTable = table + defer session.Close() + isExist, err := session.isIndexExist(table.Name, idx, false) + if err != nil { + return err + } + if !isExist { + session := engine.NewSession() + session.Statement.RefTable = table + defer session.Close() + err = session.addIndex(table.Name, idx) + if err != nil { + return err + } + } + } + + for uqe, _ := range table.Uniques { + session := engine.NewSession() + session.Statement.RefTable = table + defer session.Close() + isExist, err := session.isIndexExist(table.Name, uqe, true) + if err != nil { + return err + } + if !isExist { + session := engine.NewSession() + session.Statement.RefTable = table + defer session.Close() + err = session.addUnique(table.Name, uqe) + if err != nil { + return err + } + } + } + } + } + } + return nil +} + func (engine *Engine) UnMap(beans ...interface{}) (e error) { engine.mutex.Lock() defer engine.mutex.Unlock() diff --git a/examples/sync.go b/examples/sync.go new file mode 100644 index 00000000..aad010d9 --- /dev/null +++ b/examples/sync.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + _ "github.com/bylevel/pq" + _ "github.com/go-sql-driver/mysql" + _ "github.com/mattn/go-sqlite3" + "xorm" +) + +type SyncUser struct { + Id int64 + Name string `xorm:"unique"` + Age int `xorm:"index"` +} + +type SyncLoginInfo struct { + Id int64 + IP string `xorm:"index"` + UserId int64 + AddedCol int + // timestamp should be updated by database, so only allow get from db + TimeStamp string + // assume + Nonuse int +} + +func sync(engine *xorm.Engine) error { + return engine.Sync(&SyncLoginInfo{}, &SyncUser{}) +} + +func sqliteEngine() (*xorm.Engine, error) { + f := "sync.db" + //os.Remove(f) + + return xorm.NewEngine("sqlite3", f) +} + +func mysqlEngine() (*xorm.Engine, error) { + return xorm.NewEngine("mysql", "root:@/test?charset=utf8") +} + +func postgresEngine() (*xorm.Engine, error) { + return xorm.NewEngine("postgres", "dbname=xorm_test sslmode=disable") +} + +type engineFunc func() (*xorm.Engine, error) + +func main() { + engines := []engineFunc{sqliteEngine, mysqlEngine, postgresEngine} + for _, enginefunc := range engines { + Orm, err := enginefunc() + fmt.Println("--------", Orm.DriverName, "----------") + if err != nil { + fmt.Println(err) + return + } + Orm.ShowSQL = true + err = sync(Orm) + if err != nil { + fmt.Println(err) + } + } +} diff --git a/filter.go b/filter.go index ec46aa8b..dcf1aac9 100644 --- a/filter.go +++ b/filter.go @@ -25,10 +25,10 @@ func (s *PgSeqFilter) Do(sql string, session *Session) string { return res } -type PgQuoteFilter struct { +type QuoteFilter struct { } -func (s *PgQuoteFilter) Do(sql string, session *Session) string { +func (s *QuoteFilter) Do(sql string, session *Session) string { return strings.Replace(sql, "`", session.Engine.QuoteStr(), -1) } diff --git a/mymysql.go b/mymysql.go new file mode 100644 index 00000000..d9317c2f --- /dev/null +++ b/mymysql.go @@ -0,0 +1,65 @@ +package xorm + +import ( + "errors" + "strings" + "time" +) + +type mymysql struct { + mysql + proto string + raddr string + laddr string + timeout time.Duration + db string + user string + passwd string +} + +func (db *mymysql) Init(uri string) error { + pd := strings.SplitN(uri, "*", 2) + if len(pd) == 2 { + // Parse protocol part of URI + p := strings.SplitN(pd[0], ":", 2) + if len(p) != 2 { + return errors.New("Wrong protocol part of URI") + } + db.proto = p[0] + options := strings.Split(p[1], ",") + db.raddr = options[0] + for _, o := range options[1:] { + kv := strings.SplitN(o, "=", 2) + var k, v string + if len(kv) == 2 { + k, v = kv[0], kv[1] + } else { + k, v = o, "true" + } + switch k { + case "laddr": + db.laddr = v + case "timeout": + to, err := time.ParseDuration(v) + if err != nil { + return err + } + db.timeout = to + default: + return errors.New("Unknown option: " + k) + } + } + // Remove protocol part + pd = pd[1:] + } + // Parse database part of URI + dup := strings.SplitN(pd[0], "/", 3) + if len(dup) != 3 { + return errors.New("Wrong database part of URI") + } + db.dbname = dup[0] + db.user = dup[1] + db.passwd = dup[2] + + return nil +} diff --git a/mymysql_test.go b/mymysql_test.go index 17e5bc70..cbc951bd 100644 --- a/mymysql_test.go +++ b/mymysql_test.go @@ -10,6 +10,8 @@ CREATE DATABASE IF NOT EXISTS xorm_test CHARACTER SET utf8 COLLATE utf8_general_ci; */ +var showTestSql bool = false + func TestMyMysql(t *testing.T) { engine, err := NewEngine("mymysql", "xorm_test2/root/") defer engine.Close() @@ -17,8 +19,30 @@ func TestMyMysql(t *testing.T) { t.Error(err) return } - engine.ShowSQL = true + engine.ShowSQL = showTestSql testAll(engine, t) testAll2(engine, t) } + +func BenchmarkMyMysqlNoCache(t *testing.B) { + engine, err := NewEngine("mymysql", "xorm_test2/root/") + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchCacheFind(engine, t) +} + +func BenchmarkMyMysqlCache(t *testing.B) { + engine, err := NewEngine("mymysql", "xorm_test2/root/") + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchCacheFind(engine, t) +} diff --git a/mysql.go b/mysql.go index fdddfd2d..080920cc 100644 --- a/mysql.go +++ b/mysql.go @@ -1,10 +1,63 @@ package xorm import ( + "crypto/tls" + //"fmt" + "regexp" "strconv" + //"strings" + "time" ) type mysql struct { + user string + passwd string + net string + addr string + dbname string + params map[string]string + loc *time.Location + timeout time.Duration + tls *tls.Config + allowAllFiles bool + allowOldPasswords bool + clientFoundRows bool +} + +/*func readBool(input string) (value bool, valid bool) { + switch input { + case "1", "true", "TRUE", "True": + return true, true + case "0", "false", "FALSE", "False": + return false, true + } + + // Not a valid bool value + return +}*/ + +func (cfg *mysql) parseDSN(dsn string) (err error) { + //cfg.params = make(map[string]string) + dsnPattern := regexp.MustCompile( + `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] + `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] + `\/(?P.*?)` + // /dbname + `(?:\?(?P[^\?]*))?$`) // [?param1=value1¶mN=valueN] + matches := dsnPattern.FindStringSubmatch(dsn) + //tlsConfigRegister := make(map[string]*tls.Config) + names := dsnPattern.SubexpNames() + + for i, match := range matches { + switch names[i] { + case "dbname": + cfg.dbname = match + } + } + return +} + +func (db *mysql) Init(uri string) error { + return db.parseDSN(uri) } func (db *mysql) SqlType(c *Column) string { @@ -46,14 +99,14 @@ func (db *mysql) QuoteStr() string { return "`" } -func (db *mysql) AutoIncrStr() string { - return "AUTO_INCREMENT" -} - func (db *mysql) SupportEngine() bool { return true } +func (db *mysql) AutoIncrStr() string { + return "AUTO_INCREMENT" +} + func (db *mysql) SupportCharset() bool { return true } @@ -61,3 +114,22 @@ func (db *mysql) SupportCharset() bool { func (db *mysql) IndexOnTable() bool { return true } + +func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{db.dbname, tableName, idxName} + sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`" + sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?" + return sql, args +} + +func (db *mysql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{db.dbname, tableName, colName} + sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" + return sql, args +} + +func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{db.dbname, tableName} + sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" + return sql, args +} diff --git a/mysql_test.go b/mysql_test.go index 085144ac..adb04f1c 100644 --- a/mysql_test.go +++ b/mysql_test.go @@ -17,8 +17,30 @@ func TestMysql(t *testing.T) { t.Error(err) return } - engine.ShowSQL = true + engine.ShowSQL = showTestSql testAll(engine, t) testAll2(engine, t) } + +func BenchmarkMysqlNoCache(t *testing.B) { + engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8") + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchCacheFind(engine, t) +} + +func BenchmarkMysqlCache(t *testing.B) { + engine, err := NewEngine("mysql", "root:@/xorm_test?charset=utf8") + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchCacheFind(engine, t) +} diff --git a/postgres.go b/postgres.go index 1287dfc6..88b6831e 100644 --- a/postgres.go +++ b/postgres.go @@ -1,8 +1,58 @@ package xorm -import "strconv" +import ( + "errors" + "fmt" + "strconv" + "strings" +) type postgres struct { + dbname string +} + +type Values map[string]string + +func (vs Values) Set(k, v string) { + vs[k] = v +} + +func (vs Values) Get(k string) (v string) { + return vs[k] +} + +type Error error + +func errorf(s string, args ...interface{}) { + panic(Error(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))) +} + +func parseOpts(name string, o Values) { + if len(name) == 0 { + return + } + + name = strings.TrimSpace(name) + + ps := strings.Split(name, " ") + for _, p := range ps { + kv := strings.Split(p, "=") + if len(kv) < 2 { + errorf("invalid option: %q", p) + } + o.Set(kv[0], kv[1]) + } +} + +func (db *postgres) Init(uri string) error { + o := make(Values) + parseOpts(uri, o) + + db.dbname = o.Get("dbname") + if db.dbname == "" { + return errors.New("dbname is empty") + } + return nil } func (db *postgres) SqlType(c *Column) string { @@ -68,3 +118,20 @@ func (db *postgres) SupportCharset() bool { func (db *postgres) IndexOnTable() bool { return false } + +func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{tableName, idxName} + return `SELECT indexname FROM pg_indexes ` + + `WHERE tablename = ? AND indexname = ?`, args +} + +func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{tableName} + return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args +} + +func (db *postgres) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{tableName, colName} + return "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = ?" + + " AND column_name = ?", args +} diff --git a/postgres_test.go b/postgres_test.go index 7aa72d69..cac0719f 100644 --- a/postgres_test.go +++ b/postgres_test.go @@ -13,7 +13,7 @@ func TestPostgres(t *testing.T) { return } defer engine.Close() - engine.ShowSQL = true + engine.ShowSQL = showTestSql testAll(engine, t) testAll2(engine, t) @@ -26,7 +26,7 @@ func TestPostgres2(t *testing.T) { return } defer engine.Close() - engine.ShowSQL = true + engine.ShowSQL = showTestSql engine.Mapper = SameMapper{} fmt.Println("-------------- directCreateTable --------------") @@ -98,3 +98,27 @@ func TestPostgres2(t *testing.T) { fmt.Println("-------------- testIndexAndUnique --------------") testIndexAndUnique(engine, t) } + +func BenchmarkPostgresNoCache(t *testing.B) { + engine, err := NewEngine("postgres", "dbname=xorm_test sslmode=disable") + + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchCacheFind(engine, t) +} + +func BenchmarkPostgresCache(t *testing.B) { + engine, err := NewEngine("postgres", "dbname=xorm_test sslmode=disable") + + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchCacheFind(engine, t) +} diff --git a/session.go b/session.go index 3128d4fa..f44f0fc6 100644 --- a/session.go +++ b/session.go @@ -293,27 +293,53 @@ func (session *Session) CreateTable(bean interface{}) error { return session.createOneTable() } +func (session *Session) CreateIndexes(bean interface{}) error { + session.Statement.RefTable = session.Engine.AutoMap(bean) + + err := session.newDb() + if err != nil { + return err + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + + sqls := session.Statement.genIndexSQL() + for _, sql := range sqls { + _, err = session.exec(sql) + if err != nil { + return err + } + } + return nil +} + +func (session *Session) CreateUniques(bean interface{}) error { + session.Statement.RefTable = session.Engine.AutoMap(bean) + + err := session.newDb() + if err != nil { + return err + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + + sqls := session.Statement.genUniqueSQL() + for _, sql := range sqls { + _, err = session.exec(sql) + if err != nil { + return err + } + } + return nil +} + func (session *Session) createOneTable() error { sql := session.Statement.genCreateSQL() _, err := session.exec(sql) - if err == nil { - sqls := session.Statement.genIndexSQL() - for _, sql := range sqls { - _, err = session.exec(sql) - if err != nil { - return err - } - } - } - if err == nil { - sqls := session.Statement.genUniqueSQL() - for _, sql := range sqls { - _, err = session.exec(sql) - if err != nil { - return err - } - } - } return err } @@ -337,6 +363,26 @@ func (session *Session) CreateAll() error { return nil } +func (session *Session) DropIndexes(bean interface{}) error { + err := session.newDb() + if err != nil { + return err + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + + sqls := session.Statement.genDelIndexSQL() + for _, sql := range sqls { + _, err = session.exec(sql) + if err != nil { + return err + } + } + return nil +} + // DropTable drop a table and all indexes of the table func (session *Session) DropTable(bean interface{}) error { err := session.newDb() @@ -355,14 +401,6 @@ func (session *Session) DropTable(bean interface{}) error { session.Statement.AltTableName = bean.(string) } else if t.Kind() == reflect.Struct { session.Statement.RefTable = session.Engine.AutoMap(bean) - - sqls := session.Statement.genDelIndexSQL() - for _, sql := range sqls { - _, err = session.exec(sql) - if err != nil { - return err - } - } } else { return errors.New("Unsupported type") } @@ -727,6 +765,7 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) return nil } +// test if database is ok func (session *Session) Ping() error { err := session.newDb() if err != nil { @@ -740,6 +779,102 @@ func (session *Session) Ping() error { return session.Db.Ping() } +func (session *Session) isColumnExist(tableName, colName string) (bool, error) { + err := session.newDb() + if err != nil { + return false, err + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + sql, args := session.Engine.Dialect.ColumnCheckSql(tableName, colName) + results, err := session.query(sql, args...) + return len(results) > 0, err +} + +func (session *Session) isTableExist(tableName string) (bool, error) { + err := session.newDb() + if err != nil { + return false, err + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + sql, args := session.Engine.Dialect.TableCheckSql(tableName) + results, err := session.query(sql, args...) + 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 + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + var idx string + if unique { + idx = uniqueName(tableName, idxName) + } else { + idx = indexName(tableName, idxName) + } + sql, args := session.Engine.Dialect.IndexCheckSql(tableName, idx) + results, err := session.query(sql, args...) + return len(results) > 0, err +} + +func (session *Session) addColumn(colName string) error { + err := session.newDb() + if err != nil { + return err + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + //fmt.Println(session.Statement.RefTable) + col := session.Statement.RefTable.Columns[colName] + sql, args := session.Statement.genAddColumnStr(col) + _, 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.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + //fmt.Println(idxName) + cols := session.Statement.RefTable.Indexes[idxName] + sql, args := session.Statement.genAddIndexStr(indexName(tableName, idxName), cols) + _, err = session.exec(sql, args...) + return err +} + +func (session *Session) addUnique(tableName, uqeName string) error { + err := session.newDb() + if err != nil { + return err + } + defer session.Statement.Init() + if session.IsAutoClose { + defer session.Close() + } + //fmt.Println(uqeName, session.Statement.RefTable.Uniques) + cols := session.Statement.RefTable.Uniques[uqeName] + sql, args := session.Statement.genAddUniqueStr(uniqueName(tableName, uqeName), cols) + _, err = session.exec(sql, args...) + return err +} + func (session *Session) DropAll() error { err := session.newDb() if err != nil { diff --git a/sqlite3.go b/sqlite3.go index 525e8ebc..1d464524 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -3,6 +3,10 @@ package xorm type sqlite3 struct { } +func (db *sqlite3) Init(uri string) error { + return nil +} + func (db *sqlite3) SqlType(c *Column) string { switch t := c.SQLType.Name; t { case Date, DateTime, TimeStamp, Time: @@ -50,3 +54,18 @@ func (db *sqlite3) SupportCharset() bool { func (db *sqlite3) IndexOnTable() bool { return false } + +func (db *sqlite3) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{idxName} + return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args +} + +func (db *sqlite3) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{tableName} + return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +} + +func (db *sqlite3) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{tableName} + return "SELECT name FROM sqlite_master WHERE type='table' and name = ? and sql like '%`" + colName + "`%'", args +} diff --git a/sqlite3_test.go b/sqlite3_test.go index 5c7f3de9..0a1c33a8 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -14,8 +14,32 @@ func TestSqlite3(t *testing.T) { t.Error(err) return } - engine.ShowSQL = true + engine.ShowSQL = showTestSql testAll(engine, t) testAll2(engine, t) } + +func BenchmarkSqlite3NoCache(t *testing.B) { + os.Remove("./test.db") + engine, err := NewEngine("sqlite3", "./test.db") + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchCacheFind(engine, t) +} + +func BenchmarkSqlite3Cache(t *testing.B) { + os.Remove("./test.db") + engine, err := NewEngine("sqlite3", "./test.db") + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchCacheFind(engine, t) +} diff --git a/statement.go b/statement.go index d23b5757..e21467ca 100644 --- a/statement.go +++ b/statement.go @@ -279,47 +279,57 @@ func (statement *Statement) genCreateSQL() string { return sql } -func (statement *Statement) genIndexSQL() []string { +func indexName(tableName, idxName string) string { + return fmt.Sprintf("IDX_%v_%v", tableName, idxName) +} + +func (s *Statement) genIndexSQL() []string { var sqls []string = make([]string, 0) - for indexName, cols := range statement.RefTable.Indexes { - sql := fmt.Sprintf("CREATE INDEX IDX_%v_%v ON %v (%v);", statement.TableName(), indexName, - statement.Engine.Quote(statement.TableName()), statement.Engine.Quote(strings.Join(cols, statement.Engine.Quote(",")))) + tbName := s.TableName() + quote := s.Engine.Quote + for idxName, cols := range s.RefTable.Indexes { + sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)), + quote(tbName), quote(strings.Join(cols, quote(",")))) sqls = append(sqls, sql) } return sqls } +func uniqueName(tableName, uqeName string) string { + return fmt.Sprintf("UQE_%v_%v", tableName, uqeName) +} + func (statement *Statement) genUniqueSQL() []string { var sqls []string = make([]string, 0) for indexName, cols := range statement.RefTable.Uniques { - sql := fmt.Sprintf("CREATE UNIQUE INDEX `UQE_%v_%v` ON %v (%v);", statement.TableName(), indexName, + sql := fmt.Sprintf("CREATE UNIQUE INDEX `%v` ON %v (%v);", uniqueName(statement.TableName(), indexName), statement.Engine.Quote(statement.TableName()), statement.Engine.Quote(strings.Join(cols, statement.Engine.Quote(",")))) sqls = append(sqls, sql) } return sqls } -func (statement *Statement) genDelIndexSQL() []string { +func (s *Statement) genDelIndexSQL() []string { var sqls []string = make([]string, 0) - for indexName, _ := range statement.RefTable.Uniques { - sql := fmt.Sprintf("DROP INDEX `UQE_%v_%v`", statement.TableName(), indexName) - if statement.Engine.Dialect.IndexOnTable() { - sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(statement.TableName())) + for indexName, _ := range s.RefTable.Uniques { + sql := fmt.Sprintf("DROP INDEX %v", s.Engine.Quote(uniqueName(s.TableName(), indexName))) + if s.Engine.Dialect.IndexOnTable() { + sql += fmt.Sprintf(" ON %v", s.Engine.Quote(s.TableName())) } sqls = append(sqls, sql) } - for indexName, _ := range statement.RefTable.Indexes { - sql := fmt.Sprintf("DROP INDEX IDX_%v_%v", statement.TableName(), indexName) - if statement.Engine.Dialect.IndexOnTable() { - sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(statement.TableName())) + for indexName, _ := range s.RefTable.Indexes { + sql := fmt.Sprintf("DROP INDEX %v", s.Engine.Quote(uniqueName(s.TableName(), indexName))) + if s.Engine.Dialect.IndexOnTable() { + sql += fmt.Sprintf(" ON %v", s.Engine.Quote(s.TableName())) } sqls = append(sqls, sql) } return sqls } -func (statement *Statement) genDropSQL() string { - sql := "DROP TABLE IF EXISTS " + statement.Engine.Quote(statement.TableName()) + ";" +func (s *Statement) genDropSQL() string { + sql := "DROP TABLE IF EXISTS " + s.Engine.Quote(s.TableName()) + ";" return sql } @@ -339,6 +349,27 @@ func (statement Statement) genGetSql(bean interface{}) (string, []interface{}) { return statement.genSelectSql(columnStr), append(statement.Params, statement.BeanArgs...) } +func (s *Statement) genAddColumnStr(col *Column) (string, []interface{}) { + quote := s.Engine.Quote + sql := fmt.Sprintf("ALTER TABLE %v ADD COLUMN %v;", quote(s.TableName()), + col.String(s.Engine)) + return sql, []interface{}{} +} + +func (s *Statement) genAddIndexStr(idxName string, cols []string) (string, []interface{}) { + quote := s.Engine.Quote + colstr := quote(strings.Join(cols, quote(", "))) + sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(idxName), quote(s.TableName()), colstr) + return sql, []interface{}{} +} + +func (s *Statement) genAddUniqueStr(uqeName string, cols []string) (string, []interface{}) { + quote := s.Engine.Quote + colstr := quote(strings.Join(cols, quote(", "))) + sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uqeName), quote(s.TableName()), colstr) + return sql, []interface{}{} +} + func (statement Statement) genCountSql(bean interface{}) (string, []interface{}) { table := statement.Engine.AutoMap(bean) statement.RefTable = table diff --git a/table.go b/table.go index de4807af..26c31e0e 100644 --- a/table.go +++ b/table.go @@ -161,6 +161,16 @@ const ( UNIONUNIQUE ) +type Index struct { + Name string + IsUnique bool + Cols []*Column +} + +func NewIndex(name string, isUnique bool) *Index { + return &Index{name, isUnique, make([]*Column, 0)} +} + type Column struct { Name string FieldName string diff --git a/xorm.go b/xorm.go index c85d8f55..d2dc91eb 100644 --- a/xorm.go +++ b/xorm.go @@ -17,16 +17,12 @@ func close(engine *Engine) { engine.Close() } -// new a db manager according to the parameter. Currently support three -// driver +// new a db manager according to the parameter. Currently support four +// drivers func NewEngine(driverName string, dataSourceName string) (*Engine, error) { - engine := &Engine{ShowSQL: false, DriverName: driverName, Mapper: SnakeMapper{}, - DataSourceName: dataSourceName} + engine := &Engine{DriverName: driverName, Mapper: SnakeMapper{}, + DataSourceName: dataSourceName, Filters: make([]Filter, 0)} - engine.Tables = make(map[reflect.Type]*Table) - engine.mutex = &sync.Mutex{} - engine.TagIdentifier = "xorm" - engine.Filters = make([]Filter, 0) if driverName == SQLITE { engine.Dialect = &sqlite3{} } else if driverName == MYSQL { @@ -34,19 +30,28 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { } else if driverName == POSTGRES { engine.Dialect = &postgres{} engine.Filters = append(engine.Filters, &PgSeqFilter{}) - engine.Filters = append(engine.Filters, &PgQuoteFilter{}) + engine.Filters = append(engine.Filters, &QuoteFilter{}) } else if driverName == MYMYSQL { - engine.Dialect = &mysql{} + engine.Dialect = &mymysql{} } else { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) } + err := engine.Dialect.Init(dataSourceName) + if err != nil { + return nil, err + } + + engine.Tables = make(map[reflect.Type]*Table) + engine.mutex = &sync.Mutex{} + engine.TagIdentifier = "xorm" + engine.Filters = append(engine.Filters, &IdFilter{}) engine.Logger = os.Stdout //engine.Pool = NewSimpleConnectPool() //engine.Pool = NewNoneConnectPool() //engine.Cacher = NewLRUCacher() - err := engine.SetPool(NewSysConnectPool()) + err = engine.SetPool(NewSysConnectPool()) runtime.SetFinalizer(engine, close) return engine, err }