2. 对象表结构映射
...大约 4 分钟
1. Dialect
不同的数据库对于SQL语句支持可能不同,ORM框架需要兼容多种数据库,将各数据不同的地方提取出来单独实现,这种方式被称为Dialect
。
var dialectsMap = map[string]Dialect{}
type Dialect interface {
DataTypeOf(typ reflect.Value) string
TableExistSQL(tableName string) (string, []any)
}
func RegisterDialect(name string, dialect Dialect) {
dialectsMap[name] = dialect
}
func GetDialect(name string) (Dialect, bool) {
dialect, ok := dialectsMap[name]
return dialect, ok
}
Dialect
接口定义了两个方法:
DataTpyeOf
:将 Golang 的数据类型转换为数据库的数据类型TableExistSQL
:返回某个表是否存在的 SQL 语句
全局变量dialectsMap
存储不同的数据库对应的Dialect
。
1.1 SQLite3
package dialect
import (
"fmt"
"reflect"
"time"
)
type sqlite3 struct{}
var _ Dialect = (*sqlite3)(nil)
func init() {
RegisterDialect("sqlite3", &sqlite3{})
}
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) TableExistSQL(tableName string) (string, []any) {
args := []any{tableName}
return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
}
init
:函数在包第一次加载时,会将sqlite3
的dialect
进行注册
2. Schema
ORM 框架最为核心的功能就是对象(Object)和数据库表(Table)的转换,表结构和结构体可以形成映射关系:
- 表名(Table Name):结构体名(Struct Name)
- 字段名/类型:结构体字段名/类型
- 约束条件:结构体字段的标签(tag)
例如:
type User struct {
Name string `geeorm:"PRIMARY KEY"`
Age int
}
对应:
CREATE TABLE `User` (`Name` text PRIMARY KEY, `Age` integer);
package schema
// Field represents a column of database
type Field struct {
Name string
Type string
Tag string
}
// Schema represents a table of database
type Schema struct {
Model any
Name string
Fields []*Field
FieldNames []string
fieldMap map[string]*Field
}
func (s *Schema) GetField(name string) *Field {
return s.fieldMap[name]
}
Field
:表示数据表字段Name
:字段名Type
:数据类型Tag
:约束条件
Schema
:表示数据表Model
:映射的对象Name
:表名Fields
:表字段列表FieldNames
:表字段名列表fieldMap
:字段名/字段映射表,用于通过字段名快速获取字段
func Parse(dst any, d dialect.Dialect) *Schema {
modelType := reflect.Indirect(reflect.ValueOf(dst)).Type()
schema := &Schema{
Model: dst,
Name: modelType.Name(),
fieldMap: make(map[string]*Field),
}
for i := 0; i < modelType.NumField(); i++ {
f := modelType.Field(i)
if !f.Anonymous && ast.IsExported(f.Name) {
field := &Field{
Name: f.Name,
Type: d.DataTypeOf(reflect.Indirect(reflect.New(f.Type))),
}
if v, ok := f.Tag.Lookup("geeorm"); ok {
field.Tag = v
}
schema.Fields = append(schema.Fields, field)
schema.FieldNames = append(schema.FieldNames, f.Name)
schema.fieldMap[f.Name] = field
}
}
return schema
}
获取对象的数据类型,设计的入参类型为指针,使用
reflect.Indirect
获取指针指向的对象通过
modleType.Name
获取结构体名称遍历结构体字段,若字段不是匿名字段并且是导出的:
- 获取字段名
- 将字段类型转换成数据库数据类型
- 获取字段标签
- 构建
Field
实例 - 添加至
Schema.Fields
,Schema.FieldNames
,Schema.fileMap
中
返回
Schema
实例
2.1 单元测试
type User struct {
Name string `geeorm:"PRIMARY KEY"`
Age int
}
var testDialect, _ = dialect.GetDialect("sqlite3")
func TestParse(t *testing.T) {
schema := Parse(&User{}, testDialect)
if schema.Name != "User" || len(schema.Fields) != 2 {
t.Fatal("failed to parse User struct")
}
if schema.GetField("Name").Tag != "PRIMARY KEY" {
t.Fatal("failed to parse primary key")
}
}
3. Session
修改Session
数据结构。
type Session struct {
db *sql.DB
dialect dialect.Dialect
refTable *schema.Schema
sql strings.Builder
sqlVars []any
}
func New(db *sql.DB, dialect dialect.Dialect) *Session {
return &Session{
db: db,
dialect: dialect,
}
}
新建文件session/table.go
,用于放置数据库表相关的代码
package session
import (
"geeorm/log"
"geeorm/schema"
"reflect"
)
func (s *Session) Model(val any) *Session {
// nil or different model, update refTable
if s.refTable == nil || reflect.TypeOf(val) != reflect.TypeOf(s.refTable.Model) {
s.refTable = schema.Parse(val, s.dialect)
}
return s
}
func (s *Session) RefTable() *schema.Schema {
if s.refTable == nil {
log.Error("Model is not set")
}
return s.refTable
}
Model
用于给Session.refTable
赋值,因为解析操作开销较大,所以结构体不发生变化就不会重复进行解析RefTable
获取Session.refTable
,若为空则打印日志
实现数据表的创建、删除和判断是否存在:
func (s *Session) CreateTable() error {
table := s.RefTable()
var columns []string
for _, field := range table.Fields {
columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag))
}
desc := strings.Join(columns, ",")
_, err := s.Raw(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc)).Exec()
return err
}
func (s *Session) DropTable() error {
_, err := s.Raw(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name)).Exec()
return err
}
func (s *Session) HasTable() bool {
sql, vals := s.dialect.TableExistSQL(s.RefTable().Name)
row := s.Raw(sql, vals...).QueryRow()
var tmp string
_ = row.Scan(&tmp)
return tmp == s.RefTable().Name
}
3.1 单元测试
type User struct {
Name string `geeorm:"PRIMARY KEY"`
Age int
}
func TestSession_CreateTable(t *testing.T) {
s := newSession().Model(&User{})
_ = s.DropTable()
_ = s.CreateTable()
if !s.HasTable() {
t.Fatal("failed to create table User")
}
}
4. Engine
修改Engine
的数据结构。
type Engine struct {
db *sql.DB
dialect dialect.Dialect
}
func NewEngine(driver, source string) (*Engine, error) {
db, err := sql.Open(driver, source)
if err != nil {
log.Error(err)
return nil, err
}
// Send ping to make sure the database connection is alive
if err = db.Ping(); err != nil {
log.Error(err)
return nil, err
}
// make sure the specific dialect exists
d, ok := dialect.GetDialect(driver)
if !ok {
log.Errorf("dialect %s Not Found", driver)
return nil, DialectNotFoundErr
}
e := &Engine{db: db, dialect: d}
log.Info("Connect database success")
return e, nil
}
func (engine *Engine) NewSession() *session.Session {
return session.New(engine.db, engine.dialect)
}
Reference
Powered by Waline v2.15.2