From ff3a06b3dc619d096025235a3e49af40e1bd4e6a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Mar 2014 15:03:35 +0800 Subject: [PATCH] ql support --- engine.go | 1 + ql.go | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ql_test.go | 140 ++++++++++++++++++++++++++++++++ xorm.go | 4 + 4 files changed, 377 insertions(+) create mode 100644 ql.go create mode 100644 ql_test.go diff --git a/engine.go b/engine.go index 6e2cbad5..23038e1d 100644 --- a/engine.go +++ b/engine.go @@ -23,6 +23,7 @@ const ( MSSQL = "mssql" ORACLE_OCI = "oci8" + QL = "ql" ) // a dialect is a driver's wrapper diff --git a/ql.go b/ql.go new file mode 100644 index 00000000..8f26f3b5 --- /dev/null +++ b/ql.go @@ -0,0 +1,232 @@ +package xorm + +import ( + "database/sql" + "strings" +) + +type ql struct { + base +} + +type qlParser struct { +} + +func (p *qlParser) parse(driverName, dataSourceName string) (*uri, error) { + return &uri{dbType: QL, dbName: dataSourceName}, nil +} + +func (db *ql) Init(drivername, dataSourceName string) error { + return db.base.init(&qlParser{}, drivername, dataSourceName) +} + +func (db *ql) SqlType(c *Column) string { + switch t := c.SQLType.Name; t { + case Date, DateTime, TimeStamp, Time: + return Numeric + case TimeStampz: + return Text + case Char, Varchar, TinyText, Text, MediumText, LongText: + return Text + case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool: + return Integer + case Float, Double, Real: + return Real + case Decimal, Numeric: + return Numeric + case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary: + return Blob + case Serial, BigSerial: + c.IsPrimaryKey = true + c.IsAutoIncrement = true + c.Nullable = false + return Integer + default: + return t + } +} + +func (db *ql) SupportInsertMany() bool { + return true +} + +func (db *ql) QuoteStr() string { + return "" +} + +func (db *ql) AutoIncrStr() string { + return "AUTOINCREMENT" +} + +func (db *ql) SupportEngine() bool { + return false +} + +func (db *ql) SupportCharset() bool { + return false +} + +func (db *ql) IndexOnTable() bool { + return false +} + +func (db *ql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{idxName} + return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args +} + +func (db *ql) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{tableName} + return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +} + +func (db *ql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{tableName} + sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" + return sql, args +} + +func (db *ql) GetColumns(tableName string) ([]string, map[string]*Column, error) { + args := []interface{}{tableName} + s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, nil, err + } + + var sql string + for _, record := range res { + for name, content := range record { + if name == "sql" { + sql = string(content) + } + } + } + + nStart := strings.Index(sql, "(") + nEnd := strings.Index(sql, ")") + colCreates := strings.Split(sql[nStart+1:nEnd], ",") + cols := make(map[string]*Column) + colSeq := make([]string, 0) + for _, colStr := range colCreates { + fields := strings.Fields(strings.TrimSpace(colStr)) + col := new(Column) + col.Indexes = make(map[string]bool) + col.Nullable = true + for idx, field := range fields { + if idx == 0 { + col.Name = strings.Trim(field, "`[] ") + continue + } else if idx == 1 { + col.SQLType = SQLType{field, 0, 0} + } + switch field { + case "PRIMARY": + col.IsPrimaryKey = true + case "AUTOINCREMENT": + col.IsAutoIncrement = true + case "NULL": + if fields[idx-1] == "NOT" { + col.Nullable = false + } else { + col.Nullable = true + } + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + return colSeq, cols, nil +} + +func (db *ql) GetTables() ([]*Table, error) { + args := []interface{}{} + s := "SELECT name FROM sqlite_master WHERE type='table'" + + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, err + } + + tables := make([]*Table, 0) + for _, record := range res { + table := new(Table) + for name, content := range record { + switch name { + case "name": + table.Name = string(content) + } + } + if table.Name == "sqlite_sequence" { + continue + } + tables = append(tables, table) + } + return tables, nil +} + +func (db *ql) GetIndexes(tableName string) (map[string]*Index, error) { + args := []interface{}{tableName} + s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, err + } + + indexes := make(map[string]*Index, 0) + for _, record := range res { + index := new(Index) + sql := string(record["sql"]) + + if sql == "" { + continue + } + + nNStart := strings.Index(sql, "INDEX") + nNEnd := strings.Index(sql, "ON") + if nNStart == -1 || nNEnd == -1 { + continue + } + + 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 { + index.Name = indexName + } + + if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { + index.Type = UniqueType + } else { + index.Type = IndexType + } + + nStart := strings.Index(sql, "(") + nEnd := strings.Index(sql, ")") + colIndexes := strings.Split(sql[nStart+1:nEnd], ",") + + index.Cols = make([]string, 0) + for _, col := range colIndexes { + index.Cols = append(index.Cols, strings.Trim(col, "` []")) + } + indexes[index.Name] = index + } + + return indexes, nil +} diff --git a/ql_test.go b/ql_test.go new file mode 100644 index 00000000..46d0104d --- /dev/null +++ b/ql_test.go @@ -0,0 +1,140 @@ +package xorm + +import ( + "database/sql" + "os" + "testing" + + _ "github.com/mattn/ql-driver" +) + +func newQlEngine() (*Engine, error) { + os.Remove("./ql.db") + return NewEngine("ql", "./ql.db") +} + +func newQlDriverDB() (*sql.DB, error) { + os.Remove("./ql.db") + return sql.Open("ql", "./ql.db") +} + +func TestQl(t *testing.T) { + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.ShowSQL = showTestSql + engine.ShowErr = showTestSql + engine.ShowWarn = showTestSql + engine.ShowDebug = showTestSql + + testAll(engine, t) + testAll2(engine, t) + testAll3(engine, t) +} + +func TestQlWithCache(t *testing.T) { + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + engine.ShowSQL = showTestSql + engine.ShowErr = showTestSql + engine.ShowWarn = showTestSql + engine.ShowDebug = showTestSql + + testAll(engine, t) + testAll2(engine, t) +} + +const ( + createTableQl = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, `title` TEXT NULL, `age` TEXT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL);" + dropTableQl = "DROP TABLE IF EXISTS `big_struct`;" +) + +func BenchmarkQlDriverInsert(t *testing.B) { + doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, + doBenchDriverInsert, t) +} + +func BenchmarkQlDriverFind(t *testing.B) { + doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, + doBenchDriverFind, t) +} + +func BenchmarkQlNoCacheInsert(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchInsert(engine, t) +} + +func BenchmarkQlNoCacheFind(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchFind(engine, t) +} + +func BenchmarkQlNoCacheFindPtr(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchFindPtr(engine, t) +} + +func BenchmarkQlCacheInsert(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchInsert(engine, t) +} + +func BenchmarkQlCacheFind(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchFind(engine, t) +} + +func BenchmarkQlCacheFindPtr(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchFindPtr(engine, t) +} diff --git a/xorm.go b/xorm.go index 0a18d5e3..a4bded59 100644 --- a/xorm.go +++ b/xorm.go @@ -40,6 +40,10 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { } else if driverName == ORACLE_OCI { engine.dialect = &oracle{} engine.Filters = append(engine.Filters, &QuoteFilter{}) + } else if driverName == QL { + engine.dialect = &ql{} + engine.Filters = append(engine.Filters, &PgSeqFilter{}) + engine.Filters = append(engine.Filters, &QuoteFilter{}) } else { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) }