add day6 transaction

This commit is contained in:
gzdaijie
2020-02-26 22:16:13 +08:00
parent 1629faa6a9
commit a28737e42f
20 changed files with 1112 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
package clause
import (
"strings"
)
// Clause contains SQL conditions
type Clause struct {
sql map[Type]string
sqlVars map[Type][]interface{}
}
// Type is the type of Clause
type Type int
// Support types for Clause
const (
INSERT Type = iota
VALUES
SELECT
LIMIT
WHERE
ORDERBY
UPDATE
SET
DELETE
COUNT
)
// Set adds a sub clause of specific type
func (c *Clause) Set(name Type, vars ...interface{}) {
if c.sql == nil {
c.sql = make(map[Type]string)
c.sqlVars = make(map[Type][]interface{})
}
sql, vars := generators[name](vars...)
c.sql[name] = sql
c.sqlVars[name] = vars
}
// Build generate the final SQL and SQLVars
func (c *Clause) Build(orders ...Type) (string, []interface{}) {
var sqls []string
var vars []interface{}
for _, order := range orders {
if sql, ok := c.sql[order]; ok {
sqls = append(sqls, sql)
vars = append(vars, c.sqlVars[order]...)
}
}
return strings.Join(sqls, " "), vars
}
@@ -0,0 +1,76 @@
package clause
import (
"reflect"
"testing"
)
func TestClause_Set(t *testing.T) {
var clause Clause
clause.Set(INSERT, "User", []string{"Name", "Age"})
sql := clause.sql[INSERT]
vars := clause.sqlVars[INSERT]
t.Log(sql, vars)
if sql != "INSERT INTO User (Name,Age)" || len(vars) != 0 {
t.Fatal("failed to get clause")
}
}
func testSelect(t *testing.T) {
var clause Clause
clause.Set(LIMIT, 3)
clause.Set(SELECT, "User", []string{"*"})
clause.Set(WHERE, "Name = ?", "Tom")
clause.Set(ORDERBY, "Age ASC")
sql, vars := clause.Build(SELECT, WHERE, ORDERBY, LIMIT)
t.Log(sql, vars)
if sql != "SELECT * FROM User WHERE Name = ? ORDER BY Age ASC LIMIT ?" {
t.Fatal("failed to build SQL")
}
if !reflect.DeepEqual(vars, []interface{}{"Tom", 3}) {
t.Fatal("failed to build SQLVars")
}
}
func testUpdate(t *testing.T) {
var clause Clause
clause.Set(UPDATE, "User")
clause.Set(WHERE, "Name = ?", "Tom")
clause.Set(SET, map[string]interface{}{"Age": 30, "Name": "Tommy"})
sql, vars := clause.Build(UPDATE, SET, WHERE)
t.Log(sql, vars)
if sql != "UPDATE User SET Age = ?, Name = ? WHERE Name = ?" {
t.Fatal("failed to build SQL")
}
if !reflect.DeepEqual(vars, []interface{}{30, "Tommy", "Tom"}) {
t.Fatal("failed to build SQLVars")
}
}
func testDelete(t *testing.T) {
var clause Clause
clause.Set(DELETE, "User")
clause.Set(WHERE, "Name = ?", "Tom")
sql, vars := clause.Build(DELETE, WHERE)
t.Log(sql, vars)
if sql != "DELETE FROM User WHERE Name = ?" {
t.Fatal("failed to build SQL")
}
if !reflect.DeepEqual(vars, []interface{}{"Tom"}) {
t.Fatal("failed to build SQLVars")
}
}
func TestClause_Build(t *testing.T) {
t.Run("select", func(t *testing.T) {
testSelect(t)
})
t.Run("update", func(t *testing.T) {
testUpdate(t)
})
t.Run("delete", func(t *testing.T) {
testDelete(t)
})
}
@@ -0,0 +1,106 @@
package clause
import (
"fmt"
"strings"
)
type generator func(values ...interface{}) (string, []interface{})
var generators map[Type]generator
func init() {
generators = make(map[Type]generator)
generators[INSERT] = _insert
generators[VALUES] = _values
generators[SELECT] = _select
generators[LIMIT] = _limit
generators[WHERE] = _where
generators[ORDERBY] = _orderby
generators[UPDATE] = _update
generators[SET] = _set
generators[DELETE] = _delete
generators[COUNT] = _count
}
func genBindVars(num int) string {
var vars []string
for i := 0; i < num; i++ {
vars = append(vars, "?")
}
return strings.Join(vars, ", ")
}
func _insert(values ...interface{}) (string, []interface{}) {
// INSERT INTO $tableName ($fields)
tableName := values[0]
fields := strings.Join(values[1].([]string), ",")
return fmt.Sprintf("INSERT INTO %s (%v)", tableName, fields), []interface{}{}
}
func _values(values ...interface{}) (string, []interface{}) {
// VALUES ($v1), (&v2), ...
var bindStr string
var sql strings.Builder
var vars []interface{}
sql.WriteString("VALUES ")
for i, value := range values {
v := value.([]interface{})
if bindStr == "" {
bindStr = genBindVars(len(v))
}
sql.WriteString(fmt.Sprintf("(%v)", bindStr))
if i+1 != len(values) {
sql.WriteString(", ")
}
vars = append(vars, v...)
}
return sql.String(), vars
}
func _select(values ...interface{}) (string, []interface{}) {
// SELECT $fields FROM $tableName
tableName := values[0]
fields := strings.Join(values[1].([]string), ",")
return fmt.Sprintf("SELECT %v FROM %s", fields, tableName), []interface{}{}
}
func _limit(values ...interface{}) (string, []interface{}) {
// LIMIT $num
return "LIMIT ?", values
}
func _where(values ...interface{}) (string, []interface{}) {
// WHERE $desc
desc, vars := values[0], values[1:]
return fmt.Sprintf("WHERE %s", desc), vars
}
func _orderby(values ...interface{}) (string, []interface{}) {
return fmt.Sprintf("ORDER BY %s", values[0]), []interface{}{}
}
func _update(values ...interface{}) (string, []interface{}) {
return fmt.Sprintf("UPDATE %s", values[0]), []interface{}{}
}
func _set(values ...interface{}) (string, []interface{}) {
m := values[0].(map[string]interface{})
var keys []string
var vars []interface{}
for k, v := range m {
keys = append(keys, k+" = ?")
vars = append(vars, v)
}
return fmt.Sprintf("SET %s", strings.Join(keys, ", ")), vars
}
func _delete(values ...interface{}) (string, []interface{}) {
return fmt.Sprintf("DELETE FROM %s", values[0]), []interface{}{}
}
func _count(values ...interface{}) (string, []interface{}) {
return _select(values[0], []string{"count(*)"})
}
@@ -0,0 +1,22 @@
package dialect
import "reflect"
var dialectsMap = map[string]Dialect{}
// Dialect is an interface contains methods that a dialect has to implement
type Dialect interface {
DataTypeOf(typ reflect.Value) string
TableExistSQL(tableName string) (string, []interface{})
}
// RegisterDialect register a dialect to the global variable
func RegisterDialect(name string, dialect Dialect) {
dialectsMap[name] = dialect
}
// Get the dialect from global variable if it exists
func GetDialect(name string) (dialect Dialect, ok bool) {
dialect, ok = dialectsMap[name]
return
}
@@ -0,0 +1,45 @@
package dialect
import (
"fmt"
"reflect"
"time"
)
type sqlite3 struct{}
var _ Dialect = (*sqlite3)(nil)
func init() {
RegisterDialect("sqlite3", &sqlite3{})
}
// Get Data Type for sqlite3 Dialect
func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
switch typ.Kind() {
case reflect.Bool:
return "bool"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
return "integer"
case reflect.Int64, reflect.Uint64:
return "bigint"
case reflect.Float32, reflect.Float64:
return "real"
case reflect.String:
return "text"
case reflect.Array, reflect.Slice:
return "blob"
case reflect.Struct:
if _, ok := typ.Interface().(time.Time); ok {
return "datetime"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind()))
}
// TableExistSQL returns SQL that judge whether the table exists in database
func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) {
args := []interface{}{tableName}
return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
}
@@ -0,0 +1,25 @@
package dialect
import (
"reflect"
"testing"
)
func TestDataTypeOf(t *testing.T) {
dial := &sqlite3{}
cases := []struct {
Value interface{}
Type string
}{
{"Tom", "text"},
{123, "integer"},
{1.2, "real"},
{[]int{1, 2, 3}, "blob"},
}
for _, c := range cases {
if typ := dial.DataTypeOf(reflect.ValueOf(c.Value)); typ != c.Type {
t.Fatalf("expect %s, but got %s", c.Type, typ)
}
}
}
+75
View File
@@ -0,0 +1,75 @@
package geeorm
import (
"database/sql"
"geeorm/dialect"
"geeorm/log"
"geeorm/session"
)
// Engine is the main struct of geeorm, manages all db sessions and transactions.
type Engine struct {
db *sql.DB
dialect dialect.Dialect
}
// NewEngine create a instance of Engine
// connect database and ping it to test whether it's alive
func NewEngine(driver, source string) (e *Engine, err error) {
db, err := sql.Open(driver, source)
if err != nil {
log.Error(err)
return
}
// Send a ping to make sure the database connection is alive.
if err = db.Ping(); err != nil {
log.Error(err)
return
}
// make sure the specific dialect exists
dial, ok := dialect.GetDialect(driver)
if !ok {
log.Errorf("dialect %s Not Found", driver)
return
}
e = &Engine{db: db, dialect: dial}
log.Info("Connect database success")
return
}
// Close database connection
func (e *Engine) Close() (err error) {
if err = e.db.Close(); err == nil {
log.Info("Close database success")
}
return
}
// NewSession creates a new session for next operations
func (e *Engine) NewSession() *session.Session {
return session.New(e.db, e.dialect)
}
// TxFunc will be called between tx.Begin() and tx.Commit()
// https://stackoverflow.com/questions/16184238/database-sql-tx-detecting-commit-or-rollback
type TxFunc func(*session.Session) (interface{}, error)
// Transaction executes sql wrapped in a transaction, then automatically commit if no error occurs
func (e *Engine) Transaction(f TxFunc) (result interface{}, err error) {
s := e.NewSession()
if err := s.Begin(); err != nil {
return nil, err
}
defer func() {
if p := recover(); p != nil {
_ = s.Rollback()
panic(p) // re-throw panic after Rollback
} else if err != nil {
_ = s.Rollback() // err is non-nil; don't change it
} else {
err = s.Commit() // err is nil; if Commit returns error update err
}
}()
return f(s)
}
+71
View File
@@ -0,0 +1,71 @@
package geeorm
import (
"errors"
"geeorm/session"
"testing"
_ "github.com/mattn/go-sqlite3"
)
func OpenDB(t *testing.T) *Engine {
t.Helper()
engine, err := NewEngine("sqlite3", "gee.db")
if err != nil {
t.Fatal("failed to connect", err)
}
return engine
}
func CloseDB(engine *Engine) {
_ = engine.Close()
}
func TestNewEngine(t *testing.T) {
engine := OpenDB(t)
_ = engine.Close()
}
type User struct {
Name string `geeorm:"primary_key"`
Age int
}
func transactionRollback(t *testing.T) {
engine := OpenDB(t)
defer CloseDB(engine)
_ = engine.NewSession().DropTable(&User{})
_, err := engine.Transaction(func(s *session.Session) (result interface{}, err error) {
_ = s.CreateTable(&User{})
_, err = s.Create(&User{"Tom", 18})
return nil, errors.New("Error")
})
if err == nil || engine.NewSession().HasTable("User") {
t.Fatal("failed to rollback")
}
}
func transactionCommit(t *testing.T) {
engine := OpenDB(t)
defer CloseDB(engine)
_ = engine.NewSession().DropTable(&User{})
_, err := engine.Transaction(func(s *session.Session) (result interface{}, err error) {
err = s.CreateTable(&User{})
_, err = s.Create(&User{"Tom", 18})
return
})
u := &User{}
_ = engine.NewSession().First(u)
if err != nil || u.Name != "Tom" {
t.Fatal("failed to commit")
}
}
func TestEngine_Transaction(t *testing.T) {
t.Run("rollback", func(t *testing.T) {
transactionRollback(t)
})
t.Run("commit", func(t *testing.T) {
transactionCommit(t)
})
}
+5
View File
@@ -0,0 +1,5 @@
module geeorm
go 1.13
require github.com/mattn/go-sqlite3 v2.0.3+incompatible
+47
View File
@@ -0,0 +1,47 @@
package log
import (
"io/ioutil"
"log"
"os"
"sync"
)
var (
errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile)
infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile)
loggers = []*log.Logger{errorLog, infoLog}
mu sync.Mutex
)
// log methods
var (
Error = errorLog.Println
Errorf = errorLog.Printf
Info = infoLog.Println
Infof = infoLog.Printf
)
// log levels
const (
InfoLevel = iota
ErrorLevel
Disabled
)
// SetLevel controls log level
func SetLevel(level int) {
mu.Lock()
defer mu.Unlock()
for _, logger := range loggers {
logger.SetOutput(os.Stdout)
}
if ErrorLevel < level {
errorLog.SetOutput(ioutil.Discard)
}
if InfoLevel < level {
infoLog.SetOutput(ioutil.Discard)
}
}
+17
View File
@@ -0,0 +1,17 @@
package log
import (
"os"
"testing"
)
func TestSetLevel(t *testing.T) {
SetLevel(ErrorLevel)
if infoLog.Writer() == os.Stdout || errorLog.Writer() != os.Stdout {
t.Fatal("failed to set log level")
}
SetLevel(Disabled)
if infoLog.Writer() == os.Stdout || errorLog.Writer() == os.Stdout {
t.Fatal("failed to set log level")
}
}
+67
View File
@@ -0,0 +1,67 @@
package schema
import (
"fmt"
"geeorm/dialect"
"go/ast"
"reflect"
)
// Field represents a column of database
type Field struct {
Name string
Tag string
}
// Schema represents a table of database
type Schema struct {
TableName string
PrimaryField *Field
Fields []*Field
FieldNames []string
}
// Values return the values of dest's member variables
func (schema *Schema) Values(dest interface{}) []interface{} {
destValue := reflect.Indirect(reflect.ValueOf(dest))
var fieldValues []interface{}
for _, field := range schema.Fields {
fieldValues = append(fieldValues, destValue.FieldByName(field.Name).Interface())
}
return fieldValues
}
// Parse a struct to a Schema instance
func Parse(dest interface{}, d dialect.Dialect) *Schema {
modelType := reflect.Indirect(reflect.ValueOf(dest)).Type()
schema := &Schema{
TableName: modelType.Name(),
PrimaryField: &Field{Name: "ID", Tag: ""},
}
for i := 0; i < modelType.NumField(); i++ {
p := modelType.Field(i)
if !p.Anonymous && ast.IsExported(p.Name) {
field := &Field{
Name: p.Name,
Tag: d.DataTypeOf(reflect.Indirect(reflect.New(p.Type))),
}
if v, ok := p.Tag.Lookup("geeorm"); ok && v == "primary_key" {
schema.PrimaryField = field
}
schema.Fields = append(schema.Fields, field)
schema.FieldNames = append(schema.FieldNames, p.Name)
}
}
return schema
}
// String returns readable string
func (field *Field) String() string {
return fmt.Sprintf("(%s %s)", field.Name, field.Tag)
}
// String returns readable string
func (schema *Schema) String() string {
return fmt.Sprintf("TABLE %s %v", schema.TableName, schema.Fields)
}
@@ -0,0 +1,36 @@
package schema
import (
"geeorm/dialect"
"testing"
)
type User struct {
Name string `geeorm:"primary_key"`
Age int
}
var TestDial, _ = dialect.GetDialect("sqlite3")
func TestParse(t *testing.T) {
schema := Parse(&User{}, TestDial)
if schema.TableName != "User" || len(schema.Fields) != 2 {
t.Fatal("failed to parse User struct")
}
if schema.PrimaryField.Name != "Name" {
t.Fatal("failed to parse primary key")
}
t.Log(schema)
}
func TestSchema_Values(t *testing.T) {
schema := Parse(&User{}, TestDial)
values := schema.Values(&User{"Tom", 18})
name := values[0].(string)
age := values[1].(int)
if name != "Tom" || age != 18 {
t.Fatal("failed to get values")
}
}
+88
View File
@@ -0,0 +1,88 @@
package session
import (
"database/sql"
"geeorm/clause"
"geeorm/dialect"
"geeorm/log"
"geeorm/schema"
)
// Session keep a pointer to sql.DB and provides all execution of all
// kind of database operations.
type Session struct {
db *sql.DB
dialect dialect.Dialect
tx *sql.Tx
refTable *schema.Schema
clause clause.Clause
sql string
sqlVars []interface{}
}
// New creates a instance of Session
func New(db *sql.DB, dialect dialect.Dialect) *Session {
return &Session{
db: db,
dialect: dialect,
}
}
// Clear initialize the state of a session, except for isAutoCommit
func (s *Session) Clear() {
s.refTable, s.sqlVars = nil, nil
s.clause = clause.Clause{}
s.sql = ""
}
// CommonDB is a minimal function set of db
type CommonDB interface {
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
Exec(query string, args ...interface{}) (sql.Result, error)
}
var _ CommonDB = (*sql.DB)(nil)
var _ CommonDB = (*sql.Tx)(nil)
// DB returns tx if a tx begins. otherwise return *sql.DB
func (s *Session) DB() CommonDB {
if s.tx != nil {
return s.tx
}
return s.db
}
// Exec raw sql with sqlVars
func (s *Session) Exec() (result sql.Result, err error) {
defer s.Clear()
log.Info(s.sql, s.sqlVars)
if result, err = s.DB().Exec(s.sql, s.sqlVars...); err != nil {
log.Error(err)
}
return
}
// QueryRow gets a record from db
func (s *Session) QueryRow() *sql.Row {
defer s.Clear()
log.Info(s.sql, s.sqlVars)
return s.DB().QueryRow(s.sql, s.sqlVars...)
}
// QueryRows gets a list of records from db
func (s *Session) QueryRows() (rows *sql.Rows, err error) {
defer s.Clear()
log.Info(s.sql, s.sqlVars)
if rows, err = s.DB().Query(s.sql, s.sqlVars...); err != nil {
log.Error(err)
}
return
}
// Raw appends sql and sqlVars
func (s *Session) Raw(sql string, values ...interface{}) *Session {
s.sql += sql
s.sqlVars = append(s.sqlVars, values...)
return s
}
@@ -0,0 +1,46 @@
package session
import (
"database/sql"
"os"
"testing"
"geeorm/dialect"
_ "github.com/mattn/go-sqlite3"
)
var (
TestDB *sql.DB
TestDial, _ = dialect.GetDialect("sqlite3")
)
func TestMain(m *testing.M) {
TestDB, _ = sql.Open("sqlite3", "gee.db")
code := m.Run()
_ = TestDB.Close()
os.Exit(code)
}
func NewSession() *Session {
return &Session{db: TestDB, dialect: TestDial}
}
func TestSession_Exec(t *testing.T) {
_, _ = NewSession().Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = NewSession().Raw("CREATE TABLE User(name text);").Exec()
result, _ := NewSession().Raw("INSERT INTO User(`name`) values (?), (?)", "Tom", "Sam").Exec()
if count, err := result.RowsAffected(); err != nil || count != 2 {
t.Fatal("expect 2, but got", count)
}
}
func TestSession_QueryRows(t *testing.T) {
_, _ = NewSession().Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = NewSession().Raw("CREATE TABLE User(name text);").Exec()
row := NewSession().Raw("SELECT count(*) FROM User").QueryRow()
var count int
if err := row.Scan(&count); err != nil || count != 0 {
t.Fatal("failed to query db", err)
}
}
+130
View File
@@ -0,0 +1,130 @@
package session
import (
"geeorm/clause"
"reflect"
)
// Create one or more records in database
func (s *Session) Create(values ...interface{}) (int64, error) {
recordValues := make([]interface{}, 0)
for _, value := range values {
table := s.RefTable(value)
s.clause.Set(clause.INSERT, table.TableName, table.FieldNames)
recordValues = append(recordValues, table.Values(value))
}
s.clause.Set(clause.VALUES, recordValues...)
sql, vars := s.clause.Build(clause.INSERT, clause.VALUES)
result, err := s.Raw(sql, vars...).Exec()
if err != nil {
return 0, err
}
return result.RowsAffected()
}
// Find gets all eligible records
func (s *Session) Find(values interface{}) error {
destSlice := reflect.Indirect(reflect.ValueOf(values))
destType := destSlice.Type().Elem()
table := s.RefTable(reflect.New(destType).Elem().Interface())
s.clause.Set(clause.SELECT, table.TableName, table.FieldNames)
sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT)
rows, err := s.Raw(sql, vars...).QueryRows()
if err != nil {
return err
}
for rows.Next() {
dest := reflect.New(destType).Elem()
var values []interface{}
for _, name := range table.FieldNames {
values = append(values, dest.FieldByName(name).Addr().Interface())
}
if err := rows.Scan(values...); err != nil {
return err
}
destSlice.Set(reflect.Append(destSlice, dest))
}
return rows.Close()
}
// First gets the 1st row
func (s *Session) First(value interface{}) error {
dest := reflect.Indirect(reflect.ValueOf(value))
destSlice := reflect.New(reflect.SliceOf(dest.Type())).Elem()
err := s.Limit(1).Find(destSlice.Addr().Interface())
dest.Set(destSlice.Index(0))
return err
}
// Limit adds limit condition to clause
func (s *Session) Limit(num int) *Session {
s.clause.Set(clause.LIMIT, num)
return s
}
// Where adds limit condition to clause
func (s *Session) Where(desc string, args ...interface{}) *Session {
var vars []interface{}
s.clause.Set(clause.WHERE, append(append(vars, desc), args...)...)
return s
}
// OrderBy adds order by condition to clause
func (s *Session) OrderBy(desc string) *Session {
s.clause.Set(clause.ORDERBY, desc)
return s
}
// Set adds Assignment by condition to clause
// support map[string]interface{}
// also support "Name", "Tom", "Age", 18, etc
func (s *Session) Set(values ...interface{}) *Session {
m, ok := values[0].(map[string]interface{})
if !ok {
m = make(map[string]interface{})
for i := 0; i < len(values); i += 2 {
m[values[i].(string)] = values[i+1]
}
}
s.clause.Set(clause.SET, m)
return s
}
// Update records with where clause
func (s *Session) Update(value interface{}) (int64, error) {
s.clause.Set(clause.UPDATE, s.guessTableName(value))
sql, vars := s.clause.Build(clause.UPDATE, clause.SET, clause.WHERE)
result, err := s.Raw(sql, vars...).Exec()
if err != nil {
return 0, err
}
return result.RowsAffected()
}
// Delete records with where clause
func (s *Session) Delete(value interface{}) (int64, error) {
s.clause.Set(clause.DELETE, s.guessTableName(value))
sql, vars := s.clause.Build(clause.DELETE, clause.WHERE)
result, err := s.Raw(sql, vars...).Exec()
if err != nil {
return 0, err
}
return result.RowsAffected()
}
// Count records with where clause
func (s *Session) Count(value interface{}) (int64, error) {
s.clause.Set(clause.COUNT, s.guessTableName(value))
sql, vars := s.clause.Build(clause.COUNT, clause.WHERE)
row := s.Raw(sql, vars...).QueryRow()
var tmp int64
if err := row.Scan(&tmp); err != nil {
return 0, err
}
return tmp, nil
}
@@ -0,0 +1,96 @@
package session
import "testing"
var (
user1 = &User{"Tom", 18}
user2 = &User{"Sam", 25}
user3 = &User{"Jack", 25}
)
func testRecordInit(t *testing.T) {
t.Helper()
err1 := NewSession().DropTable(&User{})
err2 := NewSession().CreateTable(&User{})
_, err3 := NewSession().Create(user1, user2)
if err1 != nil || err2 != nil || err3 != nil {
t.Fatal("failed init test records")
}
}
func TestSession_Create(t *testing.T) {
testRecordInit(t)
affected, err := NewSession().Create(user3)
if err != nil || affected != 1 {
t.Fatal("failed to create record")
}
}
func TestSession_Find(t *testing.T) {
testRecordInit(t)
users := []User{}
if err := NewSession().Find(&users); err != nil || len(users) != 2 {
t.Fatal("failed to query all")
}
}
func TestSession_First(t *testing.T) {
testRecordInit(t)
u := &User{}
err := NewSession().First(u)
if err != nil || u.Name != "Tom" || u.Age != 18 {
t.Fatal("failed to query first")
}
}
func TestSession_Limit(t *testing.T) {
testRecordInit(t)
var users []User
err := NewSession().Limit(1).Find(&users)
if err != nil || len(users) != 1 {
t.Fatal("failed to query with limit condition")
}
}
func TestSession_Where(t *testing.T) {
testRecordInit(t)
var users []User
_, err1 := NewSession().Create(user3)
err2 := NewSession().Where("Age = ?", 25).Find(&users)
if err1 != nil || err2 != nil || len(users) != 2 {
t.Fatal("failed to query with where condition")
}
}
func TestSession_OrderBy(t *testing.T) {
testRecordInit(t)
u := &User{}
err := NewSession().OrderBy("Age DESC").First(u)
if err != nil || u.Age != 25 {
t.Fatal("failed to query with order by condition")
}
}
func TestSession_Update(t *testing.T) {
testRecordInit(t)
affected, _ := NewSession().Where("Name = ?", "Tom").Set("Age", 30).Update(&User{})
u := &User{}
_ = NewSession().OrderBy("Age DESC").First(u)
if affected != 1 || u.Age != 30 {
t.Fatal("failed to update")
}
}
func TestSession_DeleteAndCount(t *testing.T) {
testRecordInit(t)
affected, _ := NewSession().Where("Name = ?", "Tom").Delete("User")
count, _ := NewSession().Count("User")
if affected != 1 || count != 1 {
t.Fatal("failed to delete or count")
}
}
+59
View File
@@ -0,0 +1,59 @@
package session
import (
"fmt"
"strings"
"geeorm/schema"
)
// RefTable returns a Schema instance that contains all parsed fields
func (s *Session) RefTable(value interface{}) *schema.Schema {
if value == nil {
panic("value is nil")
}
if s.refTable == nil {
s.refTable = schema.Parse(value, s.dialect)
}
return s.refTable
}
// CreateTable create a table in database with a model
func (s *Session) CreateTable(value interface{}) error {
table := s.RefTable(value)
var columns []string
for _, field := range table.Fields {
tag := field.Tag
if field.Name == table.PrimaryField.Name {
tag += " PRIMARY KEY"
}
columns = append(columns, fmt.Sprintf("%s %s", field.Name, tag))
}
desc := strings.Join(columns, ",")
_, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.TableName, desc)).Exec()
return err
}
// DropTable drops a table with the name of model
func (s *Session) DropTable(value interface{}) error {
table := s.RefTable(value)
_, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", table.TableName)).Exec()
return err
}
func (s *Session) guessTableName(value interface{}) string {
if tableName, ok := value.(string); ok {
return tableName
}
return s.RefTable(value).TableName
}
// HasTable returns true of the table exists
func (s *Session) HasTable(value interface{}) bool {
tableName := s.guessTableName(value)
sql, values := s.dialect.TableExistSQL(tableName)
row := s.Raw(sql, values...).QueryRow()
var tmp string
_ = row.Scan(&tmp)
return tmp == tableName
}
@@ -0,0 +1,18 @@
package session
import (
"testing"
)
type User struct {
Name string `geeorm:"primary_key"`
Age int
}
func TestSession_CreateTable(t *testing.T) {
_ = NewSession().DropTable(&User{})
_ = NewSession().CreateTable(&User{})
if !NewSession().HasTable("User") {
t.Fatal("failed to create table User")
}
}
@@ -0,0 +1,31 @@
package session
import "geeorm/log"
// Begin a transaction
func (s *Session) Begin() (err error) {
log.Info("transaction begin")
if s.tx, err = s.db.Begin(); err != nil {
log.Error(err)
return
}
return
}
// Commit a transaction
func (s *Session) Commit() (err error) {
log.Info("transaction commit")
if err = s.tx.Commit(); err != nil {
log.Error(err)
}
return
}
// Rollback a transaction
func (s *Session) Rollback() (err error) {
log.Info("transaction rollback")
if err = s.tx.Rollback(); err != nil {
log.Error(err)
}
return
}