geeorm: a gorm-like orm framework

This commit is contained in:
gzdaijie
2020-02-22 23:32:48 +08:00
parent 4e03cef414
commit b5a1d75ef8
12 changed files with 339 additions and 1 deletions
+5 -1
View File
@@ -1,2 +1,6 @@
.DS_Store
tmp
.idea
.vscode
tmp
*.db
*.sum
@@ -0,0 +1,19 @@
package dialect
import "reflect"
var dialectsMap = map[string]Dialect{}
type Dialect interface {
DataTypeOf(typ reflect.Value) string
PrimaryKeyTag(key string) string
}
func RegisterDialect(name string, dialect Dialect) {
dialectsMap[name] = dialect
}
func GetDialect(name string) (dialect Dialect, ok bool) {
dialect, ok = dialectsMap[name]
return
}
@@ -0,0 +1,43 @@
package dialect
import (
"fmt"
"reflect"
"time"
)
type sqlite3 struct{}
var _ Dialect = (*sqlite3)(nil)
func init() {
RegisterDialect("sqlite3", &sqlite3{})
}
// Get Data Type for Sqlite 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()))
}
func (s *sqlite3) PrimaryKeyTag(key string) string {
return "INTEGER PRIMARY KEY AUTOINCREMENT"
}
@@ -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)
}
}
}
@@ -0,0 +1,67 @@
package geeorm
import (
"database/sql"
"fmt"
"log"
"os"
"geeorm/dialect"
"geeorm/schema"
)
var (
ErrorLog = log.New(os.Stdout, "[error] ", log.LstdFlags)
InfoLog = log.New(os.Stdout, "[info ] ", log.LstdFlags)
)
type Engine struct {
db *sql.DB
dialect dialect.Dialect
}
func NewEngine(driver, source string) (e *Engine, err error) {
db, err := sql.Open(driver, source)
if err != nil {
ErrorLog.Println(err)
return
}
// Send a ping to make sure the database connection is alive.
if err = db.Ping(); err != nil {
ErrorLog.Println(err)
return
}
// make sure the specific dialect exists
dial, ok := dialect.GetDialect(driver)
if !ok {
err = fmt.Errorf("dialect %s Not Found", driver)
ErrorLog.Println(err)
return
}
e = &Engine{db: db, dialect: dial}
InfoLog.Println("Connect database success")
return
}
func (e *Engine) Close() (err error) {
if err = e.db.Close(); err == nil {
InfoLog.Println("Close database success")
}
return
}
func (e *Engine) CreateTable(value interface{}) error {
_, err := e.NewSession(value).CreateTable().Exec()
return err
}
func (e *Engine) NewSession(value interface{}) *Session {
var refTable *schema.Schema
if value != nil {
refTable = schema.Parse(value, e.dialect)
}
return &Session{
refTable: refTable,
engine: e,
}
}
@@ -0,0 +1,20 @@
package geeorm
import (
_ "github.com/mattn/go-sqlite3"
"testing"
)
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 TestNewEngine(t *testing.T) {
engine := OpenDB(t)
defer engine.Close()
}
+5
View File
@@ -0,0 +1,5 @@
module geeorm
go 1.13
require github.com/mattn/go-sqlite3 v2.0.3+incompatible
@@ -0,0 +1,13 @@
package schema
import "fmt"
type Field struct {
Name string
Value interface{}
Tag string
}
func (f *Field) String() string {
return fmt.Sprintf("%s %s", f.Name, f.Tag)
}
@@ -0,0 +1,45 @@
package schema
import (
"fmt"
"go/ast"
"reflect"
"strings"
"geeorm/dialect"
)
type Schema struct {
Table string
PrimaryField *Field
Fields []*Field
}
func Parse(dest interface{}, d dialect.Dialect) *Schema {
modelType := reflect.Indirect(reflect.ValueOf(dest)).Type()
schema := &Schema{
Table: modelType.Name(),
PrimaryField: &Field{Name: "ID", Value: 0},
}
for i := 0; i < modelType.NumField(); i++ {
p := modelType.Field(i)
if !p.Anonymous && ast.IsExported(p.Name) {
schema.Fields = append(schema.Fields, &Field{
Name: p.Name,
Tag: d.DataTypeOf(reflect.Indirect(reflect.New(p.Type))),
})
}
}
return schema
}
func (s *Schema) String() string {
var fieldStr []string
for _, field := range s.Fields {
fieldStr = append(fieldStr, field.String())
}
return fmt.Sprintf("TABLE %s(%s)", s.Table, strings.Join(fieldStr, ", "))
}
@@ -0,0 +1,20 @@
package schema
import (
"geeorm/dialect"
"testing"
)
type User struct {
Name string
Age int
}
func TestParse(t *testing.T) {
dial, _ := dialect.GetDialect("sqlite3")
schema := Parse(&User{"Tom", 18}, dial)
if schema.Table != "User" || len(schema.Fields) != 2 {
t.Fatal("failed to parse User struct")
}
}
@@ -0,0 +1,48 @@
package geeorm
import (
"database/sql"
"fmt"
"strings"
"geeorm/schema"
)
type Session struct {
engine *Engine
refTable *schema.Schema
Value interface{}
SQL strings.Builder
SQLVars []interface{}
}
func (s *Session) Exec() (result sql.Result, err error) {
if result, err = s.engine.db.Exec(s.SQL.String(), s.SQLVars...); err != nil {
ErrorLog.Println(err)
}
return
}
func (s *Session) QueryRows() (rows *sql.Rows, err error) {
if rows, err = s.engine.db.Query(s.SQL.String(), s.SQLVars...); err != nil {
ErrorLog.Println(err)
}
return
}
func (s *Session) Raw(sql string, values ...interface{}) *Session {
s.SQL.WriteString(sql)
s.SQLVars = values
return s
}
func (s *Session) CreateTable() *Session {
var columns []string
for _, field := range s.refTable.Fields {
columns = append(columns, fmt.Sprintf("%s %s", field.Name, field.Tag))
}
desc := strings.Join(columns, ",")
s.SQL.WriteString(fmt.Sprintf("CREATE TABLE %s (%s);", s.refTable.Table, desc))
return s
}
@@ -0,0 +1,29 @@
package geeorm
import "testing"
func TestExec(t *testing.T) {
engine := OpenDB(t)
defer engine.Close()
engine.NewSession(nil).Raw("DROP TABLE USER;").Exec()
engine.NewSession(nil).Raw("CREATE TABLE USER(name text);").Exec()
result, _ := engine.NewSession(nil).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 TestQuery(t *testing.T) {
engine := OpenDB(t)
defer engine.Close()
engine.NewSession(nil).Raw("DROP TABLE USER;").Exec()
engine.NewSession(nil).Raw("CREATE TABLE USER(name text);").Exec()
rows, _ := engine.NewSession(nil).Raw("SELECT count(*) FROM USER").QueryRows()
defer rows.Close()
var count int
for rows.Next() {
if err := rows.Scan(&count); err != nil || count != 0 {
t.Fatal("failed to query db", err)
}
}
}