From c73e0395edeeac9f26c811a60f575a7ea2e4eb9b Mon Sep 17 00:00:00 2001 From: ngaut Date: Thu, 10 Sep 2015 16:31:53 +0800 Subject: [PATCH 1/3] Orm: Support TiDB --- orm/db_alias.go | 3 + orm/db_tidb.go | 63 +++++++++++++++++++ orm/models_test.go | 10 +++ orm/orm_test.go | 10 ++- orm/qb.go | 2 + orm/qb_tidb.go | 151 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 orm/db_tidb.go create mode 100644 orm/qb_tidb.go diff --git a/orm/db_alias.go b/orm/db_alias.go index 38302bc2..7918300e 100644 --- a/orm/db_alias.go +++ b/orm/db_alias.go @@ -32,6 +32,7 @@ const ( DRSqlite // sqlite DROracle // oracle DRPostgres // pgsql + DRTiDB // TiDB ) // database driver string. @@ -57,12 +58,14 @@ var ( "mysql": DRMySQL, "postgres": DRPostgres, "sqlite3": DRSqlite, + "tidb": DRTiDB, } dbBasers = map[DriverType]dbBaser{ DRMySQL: newdbBaseMysql(), DRSqlite: newdbBaseSqlite(), DROracle: newdbBaseMysql(), DRPostgres: newdbBasePostgres(), + DRTiDB: newdbBaseTidb(), } ) diff --git a/orm/db_tidb.go b/orm/db_tidb.go new file mode 100644 index 00000000..6020a488 --- /dev/null +++ b/orm/db_tidb.go @@ -0,0 +1,63 @@ +// Copyright 2015 TiDB Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package orm + +import ( + "fmt" +) + +// mysql dbBaser implementation. +type dbBaseTidb struct { + dbBase +} + +var _ dbBaser = new(dbBaseTidb) + +// get mysql operator. +func (d *dbBaseTidb) OperatorSQL(operator string) string { + return mysqlOperators[operator] +} + +// get mysql table field types. +func (d *dbBaseTidb) DbTypes() map[string]string { + return mysqlTypes +} + +// show table sql for mysql. +func (d *dbBaseTidb) ShowTablesQuery() string { + return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()" +} + +// show columns sql of table for mysql. +func (d *dbBaseTidb) ShowColumnsQuery(table string) string { + return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+ + "WHERE table_schema = DATABASE() AND table_name = '%s'", table) +} + +// execute sql to check index exist. +func (d *dbBaseTidb) IndexExists(db dbQuerier, table string, name string) bool { + row := db.QueryRow("SELECT count(*) FROM information_schema.statistics "+ + "WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name) + var cnt int + row.Scan(&cnt) + return cnt > 0 +} + +// create new mysql dbBaser. +func newdbBaseTidb() dbBaser { + b := new(dbBaseTidb) + b.ins = b + return b +} diff --git a/orm/models_test.go b/orm/models_test.go index 9dfa183d..23e2b9e3 100644 --- a/orm/models_test.go +++ b/orm/models_test.go @@ -25,6 +25,7 @@ import ( _ "github.com/go-sql-driver/mysql" _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" + _ "github.com/pingcap/tidb" ) // A slice string field. @@ -345,6 +346,7 @@ var ( IsMysql = DBARGS.Driver == "mysql" IsSqlite = DBARGS.Driver == "sqlite3" IsPostgres = DBARGS.Driver == "postgres" + IsTidb = DBARGS.Driver == "tidb" ) var ( @@ -364,6 +366,7 @@ Default DB Drivers. mysql: https://github.com/go-sql-driver/mysql sqlite3: https://github.com/mattn/go-sqlite3 postgres: https://github.com/lib/pq +tidb: https://github.com/pingcap/tidb usage: @@ -371,6 +374,7 @@ go get -u github.com/astaxie/beego/orm go get -u github.com/go-sql-driver/mysql go get -u github.com/mattn/go-sqlite3 go get -u github.com/lib/pq +go get -u github.com/pingcap/tidb #### MySQL mysql -u root -e 'create database orm_test;' @@ -390,6 +394,12 @@ psql -c 'create database orm_test;' -U postgres export ORM_DRIVER=postgres export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" go test -v github.com/astaxie/beego/orm + +#### TiDB +export ORM_DRIVER=tidb +export ORM_SOURCE='memory://test' +go test -v github.com/astaxie/beego/orm + `) os.Exit(2) } diff --git a/orm/orm_test.go b/orm/orm_test.go index 4342afa7..1d174fa9 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -702,7 +702,7 @@ func TestOperators(t *testing.T) { var shouldNum int - if IsSqlite { + if IsSqlite || IsTidb { shouldNum = 2 } else { shouldNum = 0 @@ -740,7 +740,7 @@ func TestOperators(t *testing.T) { throwFail(t, err) throwFail(t, AssertIs(num, 1)) - if IsSqlite { + if IsSqlite || IsTidb { shouldNum = 1 } else { shouldNum = 0 @@ -758,7 +758,7 @@ func TestOperators(t *testing.T) { throwFail(t, err) throwFail(t, AssertIs(num, 2)) - if IsSqlite { + if IsSqlite || IsTidb { shouldNum = 2 } else { shouldNum = 0 @@ -986,6 +986,10 @@ func TestValuesFlat(t *testing.T) { } func TestRelatedSel(t *testing.T) { + if IsTidb { + // Skip it. TiDB does not support relation now. + return + } qs := dORM.QueryTable("user") num, err := qs.Filter("profile__age", 28).Count() throwFail(t, err) diff --git a/orm/qb.go b/orm/qb.go index d7efae81..f5f10ebf 100644 --- a/orm/qb.go +++ b/orm/qb.go @@ -48,6 +48,8 @@ type QueryBuilder interface { func NewQueryBuilder(driver string) (qb QueryBuilder, err error) { if driver == "mysql" { qb = new(MySQLQueryBuilder) + } else if driver == "mysql" { + qb = new(MySQLQueryBuilder) } else if driver == "postgres" { err = errors.New("postgres query builder is not supported yet") } else if driver == "sqlite" { diff --git a/orm/qb_tidb.go b/orm/qb_tidb.go new file mode 100644 index 00000000..348002e1 --- /dev/null +++ b/orm/qb_tidb.go @@ -0,0 +1,151 @@ +// Copyright 2015 TiDB Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package orm + +import ( + "fmt" + "strconv" + "strings" +) + +type TiDBQueryBuilder struct { + Tokens []string +} + +func (qb *TiDBQueryBuilder) Select(fields ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "SELECT", strings.Join(fields, CommaSpace)) + return qb +} + +func (qb *TiDBQueryBuilder) From(tables ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace)) + return qb +} + +func (qb *TiDBQueryBuilder) InnerJoin(table string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "INNER JOIN", table) + return qb +} + +func (qb *TiDBQueryBuilder) LeftJoin(table string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "LEFT JOIN", table) + return qb +} + +func (qb *TiDBQueryBuilder) RightJoin(table string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "RIGHT JOIN", table) + return qb +} + +func (qb *TiDBQueryBuilder) On(cond string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "ON", cond) + return qb +} + +func (qb *TiDBQueryBuilder) Where(cond string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "WHERE", cond) + return qb +} + +func (qb *TiDBQueryBuilder) And(cond string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "AND", cond) + return qb +} + +func (qb *TiDBQueryBuilder) Or(cond string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "OR", cond) + return qb +} + +func (qb *TiDBQueryBuilder) In(vals ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "IN", "(", strings.Join(vals, CommaSpace), ")") + return qb +} + +func (qb *TiDBQueryBuilder) OrderBy(fields ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "ORDER BY", strings.Join(fields, CommaSpace)) + return qb +} + +func (qb *TiDBQueryBuilder) Asc() QueryBuilder { + qb.Tokens = append(qb.Tokens, "ASC") + return qb +} + +func (qb *TiDBQueryBuilder) Desc() QueryBuilder { + qb.Tokens = append(qb.Tokens, "DESC") + return qb +} + +func (qb *TiDBQueryBuilder) Limit(limit int) QueryBuilder { + qb.Tokens = append(qb.Tokens, "LIMIT", strconv.Itoa(limit)) + return qb +} + +func (qb *TiDBQueryBuilder) Offset(offset int) QueryBuilder { + qb.Tokens = append(qb.Tokens, "OFFSET", strconv.Itoa(offset)) + return qb +} + +func (qb *TiDBQueryBuilder) GroupBy(fields ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "GROUP BY", strings.Join(fields, CommaSpace)) + return qb +} + +func (qb *TiDBQueryBuilder) Having(cond string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "HAVING", cond) + return qb +} + +func (qb *TiDBQueryBuilder) Update(tables ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "UPDATE", strings.Join(tables, CommaSpace)) + return qb +} + +func (qb *TiDBQueryBuilder) Set(kv ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "SET", strings.Join(kv, CommaSpace)) + return qb +} + +func (qb *TiDBQueryBuilder) Delete(tables ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "DELETE") + if len(tables) != 0 { + qb.Tokens = append(qb.Tokens, strings.Join(tables, CommaSpace)) + } + return qb +} + +func (qb *TiDBQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder { + qb.Tokens = append(qb.Tokens, "INSERT INTO", table) + if len(fields) != 0 { + fieldsStr := strings.Join(fields, CommaSpace) + qb.Tokens = append(qb.Tokens, "(", fieldsStr, ")") + } + return qb +} + +func (qb *TiDBQueryBuilder) Values(vals ...string) QueryBuilder { + valsStr := strings.Join(vals, CommaSpace) + qb.Tokens = append(qb.Tokens, "VALUES", "(", valsStr, ")") + return qb +} + +func (qb *TiDBQueryBuilder) Subquery(sub string, alias string) string { + return fmt.Sprintf("(%s) AS %s", sub, alias) +} + +func (qb *TiDBQueryBuilder) String() string { + return strings.Join(qb.Tokens, " ") +} From c841a77ad64823b653b6d07ab23571e7aaea2989 Mon Sep 17 00:00:00 2001 From: ngaut Date: Fri, 11 Sep 2015 11:24:58 +0800 Subject: [PATCH 2/3] Orm: Add tidb for query builder --- orm/models_test.go | 2 +- orm/orm_test.go | 5 ++++- orm/qb.go | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/orm/models_test.go b/orm/models_test.go index 23e2b9e3..2a9962a1 100644 --- a/orm/models_test.go +++ b/orm/models_test.go @@ -397,7 +397,7 @@ go test -v github.com/astaxie/beego/orm #### TiDB export ORM_DRIVER=tidb -export ORM_SOURCE='memory://test' +export ORM_SOURCE='memory://test/test' go test -v github.com/astaxie/beego/orm `) diff --git a/orm/orm_test.go b/orm/orm_test.go index 1d174fa9..dc68da1a 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -26,6 +26,8 @@ import ( "strings" "testing" "time" + + "github.com/juju/errors" ) var _ = os.PathSeparator @@ -133,6 +135,7 @@ func throwFail(t *testing.T, err error, args ...interface{}) { } con += " " + strings.Join(parts, ", ") } + t.Error(errors.ErrorStack(err)) t.Error(con) t.Fail() } @@ -172,7 +175,7 @@ func TestSyncDb(t *testing.T) { RegisterModel(new(UserBig)) RegisterModel(new(PostTags)) - err := RunSyncdb("default", true, false) + err := RunSyncdb("default", true, true) throwFail(t, err) modelCache.clean() diff --git a/orm/qb.go b/orm/qb.go index f5f10ebf..9f778916 100644 --- a/orm/qb.go +++ b/orm/qb.go @@ -48,8 +48,8 @@ type QueryBuilder interface { func NewQueryBuilder(driver string) (qb QueryBuilder, err error) { if driver == "mysql" { qb = new(MySQLQueryBuilder) - } else if driver == "mysql" { - qb = new(MySQLQueryBuilder) + } else if driver == "tidb" { + qb = new(TiDBQueryBuilder) } else if driver == "postgres" { err = errors.New("postgres query builder is not supported yet") } else if driver == "sqlite" { From 09b7457ac656aa33be0714112139fc65c1033b0e Mon Sep 17 00:00:00 2001 From: ngaut Date: Thu, 17 Sep 2015 15:41:39 +0800 Subject: [PATCH 3/3] orm_test: Skip relation test --- orm/orm_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index dc68da1a..1d174fa9 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -26,8 +26,6 @@ import ( "strings" "testing" "time" - - "github.com/juju/errors" ) var _ = os.PathSeparator @@ -135,7 +133,6 @@ func throwFail(t *testing.T, err error, args ...interface{}) { } con += " " + strings.Join(parts, ", ") } - t.Error(errors.ErrorStack(err)) t.Error(con) t.Fail() } @@ -175,7 +172,7 @@ func TestSyncDb(t *testing.T) { RegisterModel(new(UserBig)) RegisterModel(new(PostTags)) - err := RunSyncdb("default", true, true) + err := RunSyncdb("default", true, false) throwFail(t, err) modelCache.clean()