1 Star 0 Fork 8

jqmtony/toyorm

forked from bigpigeon/toyorm 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
basic.go 16.11 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
/*
* Copyright 2018. bigpigeon. All rights reserved.
* Use of this source code is governed by a MIT style
* license that can be found in the LICENSE file.
*/
package toyorm
import (
"bytes"
"database/sql"
"fmt"
"reflect"
"sort"
"strings"
"time"
)
/*
column convert rule:
User => user
UserName => user_name
UserID => user_id
OneToOne => one_to_one
*/
func SqlNameConvert(name string) string {
if len(name) == 0 {
panic("error length name string")
}
convert := []byte{}
var lowerCount, upperCount uint32
for i := 0; i < len(name); i++ {
a := name[i]
if a >= 'A' && a <= 'Z' {
if lowerCount >= 1 {
convert = append(convert, '_', a-'A'+'a')
} else {
convert = append(convert, a-'A'+'a')
}
upperCount++
lowerCount = 0
} else if a >= 'a' && a <= 'z' {
if upperCount > 1 {
convert = append(convert, '_', a)
} else {
convert = append(convert, a)
}
upperCount = 0
lowerCount++
} else if a == '.' {
lowerCount, upperCount = 0, 0
convert = append(convert, '_')
} else {
lowerCount, upperCount = 0, 0
convert = append(convert, a)
}
}
return string(convert)
}
func getStructFieldLen(vType reflect.Type) int {
sum := vType.NumField()
for i := 0; i < vType.NumField(); i++ {
field := vType.Field(i)
if field.Anonymous {
sum += getStructFieldLen(LoopTypeIndirect(field.Type))
}
}
return sum
}
func getStructValueFields(value reflect.Value, fieldList *[]reflect.Value) {
vtype := value.Type()
for i := 0; i < value.NumField(); i++ {
vfield := value.Field(i)
sfield := vtype.Field(i)
if sfield.Anonymous {
getStructValueFields(vfield, fieldList)
} else {
*fieldList = append(*fieldList, vfield)
}
}
}
// get all Value with struct field and its embed struct field
func GetStructValueFields(value reflect.Value) []reflect.Value {
vtype := value.Type()
// opt allocation
fieldList := make([]reflect.Value, 0, getStructFieldLen(vtype))
getStructValueFields(value, &fieldList)
return fieldList
}
// get all StructField with struct field and its embed field
func GetStructFields(_type reflect.Type) []reflect.StructField {
fieldList := []reflect.StructField{}
for i := 0; i < _type.NumField(); i++ {
sfield := _type.Field(i)
if sfield.Anonymous {
embedFieldList := GetStructFields(sfield.Type)
fieldList = append(fieldList, embedFieldList...)
} else {
fieldList = append(fieldList, sfield)
}
}
return fieldList
}
// loop to get ptr type elem when its type is not ptr/slice
func LoopTypeIndirect(_type reflect.Type) reflect.Type {
for _type.Kind() == reflect.Ptr {
_type = _type.Elem()
}
return _type
}
// loop to get ptr/slice type elem when its type is not ptr/slice
func LoopTypeIndirectSliceAndPtr(_type reflect.Type) reflect.Type {
for _type.Kind() == reflect.Ptr || _type.Kind() == reflect.Slice {
_type = _type.Elem()
}
return _type
}
// loop to get ptr value elem when its type is not ptr
func LoopIndirect(vValue reflect.Value) reflect.Value {
for vValue.Kind() == reflect.Ptr {
vValue = vValue.Elem()
}
return vValue
}
// loop to get ptr value elem when its type is not ptr and if value is zero, set a new one
func LoopIndirectAndNew(vValue reflect.Value) reflect.Value {
for vValue.Kind() == reflect.Ptr {
if vValue.IsNil() {
vValue.Set(reflect.New(vValue.Type().Elem()))
}
vValue = vValue.Elem()
}
return vValue
}
func LoopDivePtr(vValue reflect.Value) reflect.Value {
for vValue.Kind() == reflect.Ptr {
if vValue.IsNil() {
vValue = reflect.Zero(vValue.Type().Elem())
} else {
vValue = vValue.Elem()
}
}
return vValue
}
// loop to get ptr value elem
// if its type is ptr, get it's elem
// if it's type is slice get it's first elem or zero value's elem type
// loop get will not change the source object
func LoopDiveSliceAndPtr(vValue reflect.Value) reflect.Value {
for vValue.Kind() == reflect.Ptr || vValue.Kind() == reflect.Slice {
if vValue.Kind() == reflect.Ptr {
if vValue.IsNil() {
vValue = reflect.Zero(vValue.Type().Elem())
} else {
vValue = vValue.Elem()
}
} else {
if vValue.Len() > 0 {
vValue = vValue.Index(0)
} else {
vValue = reflect.Zero(vValue.Type().Elem())
}
}
}
return vValue
}
// to check value is zero
func IsZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Slice:
return v.IsNil()
case reflect.Map:
return v.IsNil()
default:
if v.Type().Comparable() == false {
return reflect.Zero(v.Type()).String() == v.String()
} else {
return v.Interface() == reflect.Zero(v.Type()).Interface()
}
}
}
// if value type is different with current value type ,try to convert it
func safeSet(v, x reflect.Value) {
vType, xType := v.Type(), x.Type()
if vType != xType {
// try to convert v
if vType.Kind() == reflect.Ptr && xType.Kind() != reflect.Ptr {
v = LoopIndirectAndNew(v)
vType = v.Type()
}
v.Set(x.Convert(vType))
} else {
v.Set(x)
}
}
// if value type is ptr to l elem type , try to get its elem and append to l
// if l elem type is ptr to x type , try to make x ptr and append to l
func SafeAppend(l reflect.Value, x ...reflect.Value) reflect.Value {
tPtrElem := l.Type().Elem()
canAppend := make([]reflect.Value, 0, len(x))
for _, e := range x {
if e.Type() == tPtrElem {
canAppend = append(canAppend, e)
} else {
tElem := tPtrElem
ptrDeep := 0
for tElem.Kind() == reflect.Ptr {
tElem = tElem.Elem()
ptrDeep++
}
e = LoopIndirect(e)
if tElem == e.Type() {
ePtr := e
for ptrDeep != 0 {
ptrDeep--
if e.CanAddr() {
ePtr = e.Addr()
} else {
ePtr = reflect.New(e.Type())
ePtr.Elem().Set(e)
}
}
canAppend = append(canAppend, ePtr)
} else {
panic(fmt.Sprintf("cannot assignable %s to %s", e.Type(), tElem))
}
}
}
return reflect.Append(l, canAppend...)
}
// generate a default field name with relation model
func GetRelationFieldName(subModel *Model) string {
return subModel.ReflectType.Name() + subModel.GetOnePrimary().Name()
}
func GetBelongsIDFieldName(subModel *Model, containerField Field) string {
return containerField.Name() + subModel.GetOnePrimary().Name()
}
func GetMiddleField(model, middleModel *Model, leftOrRight bool) Field {
// try to find field with name
if modelField := middleModel.GetFieldWithName(GetRelationFieldName(model)); modelField != nil {
return modelField
}
b2i := map[bool]int{false: 0, true: 1}
return middleModel.GetPosField(b2i[leftOrRight])
}
func makeRange(min, max int) (l []int) {
for min < max {
l = append(l, min)
min++
}
return
}
func getFieldsWithRecords(fields []Field, records ModelRecordFieldTypes) []Field {
var selectFields []Field
for _, field := range fields {
if _type := records.GetFieldType(field.Name()); _type != nil {
selectFields = append(selectFields, field)
}
}
return selectFields
}
func ToSqlType(_type reflect.Type) (sqlType string) {
switch _type.Kind() {
case reflect.Ptr:
sqlType = ToSqlType(_type.Elem())
case reflect.Bool:
sqlType = "BOOLEAN"
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
sqlType = "INTEGER"
case reflect.Int64, reflect.Uint64, reflect.Int, reflect.Uint:
sqlType = "BIGINT"
case reflect.Float32, reflect.Float64:
sqlType = "FLOAT"
case reflect.String:
sqlType = "VARCHAR(255)"
case reflect.Struct:
if _, ok := reflect.New(_type).Elem().Interface().(time.Time); ok {
sqlType = "TIMESTAMP"
} else if _, ok := reflect.New(_type).Elem().Interface().(sql.NullBool); ok {
sqlType = "BOOLEAN"
} else if _, ok := reflect.New(_type).Elem().Interface().(sql.NullInt64); ok {
sqlType = "BIGINT"
} else if _, ok := reflect.New(_type).Elem().Interface().(sql.NullString); ok {
sqlType = "VARCHAR(255)"
} else if _, ok := reflect.New(_type).Elem().Interface().(sql.NullFloat64); ok {
sqlType = "FLOAT"
} else if _, ok := reflect.New(_type).Elem().Interface().(sql.RawBytes); ok {
sqlType = "VARCHAR(255)"
}
default:
if _, ok := reflect.New(_type).Elem().Interface().([]byte); ok {
sqlType = "VARCHAR(255)"
}
}
return
}
// get model name with type
func ModelName(val reflect.Value) string {
var modelName string
if v, ok := val.Interface().(tabler); ok {
modelName = v.TableName()
} else {
canModelName := false
if val.CanAddr() {
if v, ok := val.Addr().Interface().(tabler); ok {
modelName = v.TableName()
canModelName = true
}
}
if canModelName == false {
if typeName := val.Type().Name(); typeName != "" {
modelName = SqlNameConvert(val.Type().Name())
}
}
}
if modelName == "" {
panic(ErrInvalidModelName{})
}
return modelName
}
func DefaultTemplateExec(brick *ToyBrick) map[string]BasicExec {
cExec := brick.ConditionExec()
result := map[string]BasicExec{
"ModelName": {brick.Model.Name, nil},
"Conditions": {cExec.Query(), cExec.Args()},
}
for _, field := range brick.Model.GetSqlFields() {
// add field name placeholder exec
result["FN-"+field.Name()] = BasicExec{field.Column(), nil}
// add field offset placeholder exec
result[fmt.Sprintf("0x%x", field.Offset())] = BasicExec{field.Column(), nil}
}
return result
}
func DefaultCollectionTemplateExec(brick *CollectionBrick) map[string]BasicExec {
cExec := brick.ConditionExec()
result := map[string]BasicExec{
"ModelName": {brick.Model.Name, nil},
"Conditions": {cExec.Query(), cExec.Args()},
"DBIndex": {fmt.Sprintf("%d", brick.dbIndex), nil},
}
for _, field := range brick.Model.GetSqlFields() {
// add field name placeholder exec
result["FN-"+field.Name()] = BasicExec{field.Column(), nil}
// add field offset placeholder exec
result[fmt.Sprintf("FO-%d", field.Offset())] = BasicExec{field.Column(), nil}
}
return result
}
// placeholder format:
// $Name ok
// $name ok
// $1Name ok
// $User-Name ok
// $user_name ok
// $User- no, the placeholder are interception as $User
// $user_name_ no, the placeholder are interception as $user_name
// $- error, placeholder is null
func getTemplateExec(exec BasicExec, execs map[string]BasicExec) (BasicExec, error) {
buff := bytes.Buffer{}
var pre, i int
var args []interface{}
isEscaping := false
qNum := 0
for i < len(exec.query) {
switch exec.query[i] {
case '$':
if isEscaping == false {
buff.WriteString(exec.query[pre:i])
i++
pre = i
end := i
for i < len(exec.query) {
c := exec.query[i]
i++
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
end = i
} else if c == '-' || c == '_' {
} else {
break
}
}
word := exec.query[pre:end]
if word == "" {
return BasicExec{}, ErrTemplateExecInvalidWord{"$"}
} else if match, ok := execs[word]; ok {
buff.WriteString(match.query)
args = append(args, match.args...)
} else {
return BasicExec{}, ErrTemplateExecInvalidWord{"$" + word}
}
pre, i = end, end
} else {
buff.WriteString(exec.query[pre : i-1])
buff.WriteByte(exec.query[i])
pre, i = i+1, i+1
}
case '?':
args = append(args, exec.args[qNum])
qNum++
i++
case '\\':
i++
isEscaping = true
continue
default:
i++
}
isEscaping = false
}
args = append(args, exec.args[qNum:]...)
buff.WriteString(exec.query[pre:i])
return BasicExec{buff.String(), args}, nil
}
func columnsValueToColumn(values []ColumnValue) []Column {
columns := make([]Column, len(values))
for i := range values {
columns[i] = values[i]
}
return columns
}
func getColumnExec(columns []Column) BasicExec {
var _list []string
for _, column := range columns {
_list = append(_list, column.Column())
}
return BasicExec{strings.Join(_list, ","), nil}
}
func getInsertColumnExecAndValue(values FieldValueList) (BasicExec, BasicExec) {
var _list []string
var args []interface{}
var qList []string
for _, val := range values {
if IsZero(val.Value()) {
if val.IsPrimary() || val.AutoIncrement() || val.Default() == "" {
continue
}
}
_list = append(_list, val.Column())
qList = append(qList, "?")
args = append(args, val.Value().Interface())
}
return BasicExec{strings.Join(_list, ","), nil},
BasicExec{strings.Join(qList, ","), args}
}
// e.g return BasicExec{query: "?,?,?" args:[1,2,3]}
func getValuesExec(values []ColumnValue) BasicExec {
var args []interface{}
var qList []string
for _, value := range values {
qList = append(qList, "?")
args = append(args, value.Value().Interface())
}
return BasicExec{strings.Join(qList, ","), args}
}
// e.g return BasicExec{query: "a = ?,b = ?,c = ?" args:[1,2,3]}
func getUpdateValuesExec(values []ColumnValue) BasicExec {
var args []interface{}
var _list []string
for _, value := range values {
_list = append(_list, value.Column()+" = ?")
args = append(args, value.Value().Interface())
}
return BasicExec{strings.Join(_list, ","), args}
}
func joinSwap(swap *JoinSwap, brick *ToyBrick) *JoinSwap {
currentJoinSwap := JoinSwap{
OwnOrderBy: brick.OwnOrderBy,
OwnGroupBy: brick.OwnGroupBy,
OwnSearch: brick.OwnSearch,
Alias: brick.alias,
FieldsSelector: brick.FieldsSelector,
SwapMap: brick.SwapMap,
JoinMap: brick.JoinMap,
MapPreloadBrick: brick.MapPreloadBrick,
BelongToPreload: brick.BelongToPreload,
OneToOnePreload: brick.OneToOnePreload,
OneToManyPreload: brick.OneToManyPreload,
ManyToManyPreload: brick.ManyToManyPreload,
}
if swap != nil {
brick.OwnOrderBy = swap.OwnOrderBy
brick.OwnGroupBy = swap.OwnGroupBy
brick.OwnSearch = swap.OwnSearch
brick.alias = swap.Alias
brick.FieldsSelector = swap.FieldsSelector
brick.SwapMap = swap.SwapMap
brick.JoinMap = swap.JoinMap
brick.MapPreloadBrick = swap.MapPreloadBrick
brick.BelongToPreload = swap.BelongToPreload
brick.OneToOnePreload = swap.OneToOnePreload
brick.OneToManyPreload = swap.OneToManyPreload
brick.ManyToManyPreload = swap.ManyToManyPreload
}
return &currentJoinSwap
}
// get columns and scanner generator
func FindColumnFactory(fieldTypes ModelRecordFieldTypes, brick *ToyBrick) ([]Column, func(ModelRecord) []interface{}) {
columns := brick.getSelectFields(fieldTypes).ToColumnList()
names := make([]string, 0, len(brick.JoinMap))
for name := range brick.JoinMap {
names = append(names, name)
}
sort.Strings(names)
nameFnMap := map[string]func(records ModelRecord) []interface{}{}
for _, name := range names {
joinBrick := brick.Join(name)
subRecord := MakeRecord(brick.JoinMap[name].SubModel, LoopTypeIndirect(fieldTypes.GetFieldType(name)))
var subColumns []Column
subColumns, nameFnMap[name] = FindColumnFactory(subRecord, joinBrick)
columns = append(columns, subColumns...)
}
var fn func(records ModelRecord) []interface{}
fn = func(record ModelRecord) []interface{} {
var scanners []interface{}
for _, field := range brick.getScanFields(record) {
value := record.FieldAddress(field.Name())
scanners = append(scanners, value.Interface())
}
for _, name := range names {
subRecord := NewRecord(brick.JoinMap[name].SubModel, LoopIndirectAndNew(record.Field(name)))
scanners = append(scanners, nameFnMap[name](subRecord)...)
}
return scanners
}
return columns, fn
}
func insertValuesFormat(model *Model, columnValues []ColumnNameValue) (string, string, []interface{}) {
valueMap := map[string]reflect.Value{}
for _, c := range columnValues {
valueMap[c.Name()] = c.Value()
}
fieldBuff := bytes.Buffer{}
qBytes := make([]byte, 0, len(model.GetSqlFields())*2)
args := make([]interface{}, 0, len(model.GetSqlFields()))
for _, r := range model.GetSqlFields() {
var val reflect.Value
if v, ok := valueMap[r.Name()]; ok {
val = v
} else if r.AutoIncrement() || r.IsPrimary() {
continue
} else if _default := r.Default(); _default != "" {
continue
} else {
val = reflect.Zero(r.StructField().Type)
}
if IsZero(val) {
if r.AutoIncrement() || r.IsPrimary() || r.Default() != "" {
continue
}
}
fieldBuff.WriteString(r.Column())
fieldBuff.WriteByte(',')
qBytes = append(qBytes, '?', ',')
args = append(args, val.Interface())
}
fieldBytes := fieldBuff.Bytes()
// last buff must be ,
fieldBytes = fieldBytes[:len(fieldBytes)-1]
qBytes = qBytes[:len(qBytes)-1]
return string(fieldBytes), string(qBytes), args
}
func IntKind(kind reflect.Kind) bool {
return kind >= reflect.Int && kind <= reflect.Uint64
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/jqmtony/toyorm.git
git@gitee.com:jqmtony/toyorm.git
jqmtony
toyorm
toyorm
master

搜索帮助

0d507c66 1850385 C8b1a773 1850385