mirror of
https://github.com/geektutu/7days-golang.git
synced 2024-04-21 12:32:11 +00:00
geeorm: a gorm-like orm framework
This commit is contained in:
+5
-1
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user