parent
1ffb1ee1d6
commit
8b4b8d3b59
@ -0,0 +1,16 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
type ConfigFlyClient struct {
|
||||
Dns string // 地址
|
||||
}
|
||||
|
||||
// FlyClient
|
||||
// https://github.com/daodao97/fly
|
||||
type FlyClient struct {
|
||||
Db *orm.Ormer // 驱动
|
||||
config *ConfigFlyClient // 配置
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/daodao97/fly"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func NewFlyMysqlClient(config *ConfigFlyClient) (*FlyClient, error) {
|
||||
|
||||
var err error
|
||||
c := &FlyClient{config: config}
|
||||
|
||||
err = fly.Init(map[string]*fly.Config{
|
||||
"default": {DSN: c.config.Dns},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"github.com/lqs/sqlingo"
|
||||
)
|
||||
|
||||
type ConfigSqLiNgoClient struct {
|
||||
Dns string // 地址
|
||||
}
|
||||
|
||||
// SqLiNgoClient
|
||||
// https://github.com/lqs/sqlingo
|
||||
type SqLiNgoClient struct {
|
||||
Db sqlingo.Database // 驱动
|
||||
config *ConfigSqLiNgoClient // 配置
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/lqs/sqlingo"
|
||||
)
|
||||
|
||||
func NewSqLiNgoMysqlClient(config *ConfigSqLiNgoClient) (*SqLiNgoClient, error) {
|
||||
|
||||
var err error
|
||||
c := &SqLiNgoClient{config: config}
|
||||
|
||||
c.Db, err = sqlingo.Open("mysql", c.config.Dns)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1 @@
|
||||
.idea
|
@ -0,0 +1,86 @@
|
||||
# fly
|
||||
|
||||
A simple go db library
|
||||
|
||||
- data hook, Easy transition db data
|
||||
- sql builder, Not need handwritten SQL
|
||||
- hasOne/hasMany, Convenient access to associated data
|
||||
- validator, Flexible verification policies
|
||||
- extensible, Easy extend custom hook/sql/validator
|
||||
- cacheEnable, Support for custom cache implementations
|
||||
|
||||
# usages
|
||||
|
||||
more example please check out [model_test.go](./model_test.go)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/daodao97/fly"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
err := fly.Init(map[string]*fly.Config{
|
||||
"default": {DSN: "root@tcp(127.0.0.1:3306)/fly_test?&parseTime=true"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
m := fly.New(
|
||||
"user",
|
||||
fly.ColumnHook(fly.CommaInt("role_id"), fly.Json("profile")),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
_, err = m.Insert(map[string]interface{}{
|
||||
"name": "Seiya",
|
||||
"profile": map[string]interface{}{
|
||||
"hobby": "Pegasus Ryuseiken",
|
||||
},
|
||||
})
|
||||
|
||||
var result []*User
|
||||
err = m.Select(fly.WhereGt("id", 1)).Binding(&result)
|
||||
|
||||
var result1 *User
|
||||
err = m.SelectOne(fly.WhereEq("id", 1)).Binding(&result1)
|
||||
|
||||
count, err := m.Count()
|
||||
fmt.Println("count", count)
|
||||
|
||||
_, err = m.Update(User{
|
||||
ID: 1,
|
||||
Name: "星矢",
|
||||
Profile: &Profile{
|
||||
Hobby: "天马流行拳",
|
||||
},
|
||||
RoleIds: []int{2, 3},
|
||||
})
|
||||
|
||||
_, err = m.Delete(fly.WhereEq("id", 1))
|
||||
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status int64 `json:"status"`
|
||||
Profile *Profile `json:"profile"`
|
||||
IsDeleted int `json:"is_deleted"`
|
||||
RoleIds []int `json:"role_ids"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Hobby string `json:"hobby"`
|
||||
}
|
||||
```
|
@ -0,0 +1,31 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ExampleStruct struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
type ExampleStructSlice []ExampleStruct
|
||||
|
||||
func (l ExampleStructSlice) Value() (driver.Value, error) {
|
||||
bytes, err := json.Marshal(l)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func (l *ExampleStructSlice) Scan(input interface{}) error {
|
||||
switch value := input.(type) {
|
||||
case string:
|
||||
return json.Unmarshal([]byte(value), l)
|
||||
case []byte:
|
||||
return json.Unmarshal(value, l)
|
||||
default:
|
||||
return errors.New("not supported")
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 一般用Prepared Statements和Exec()完成INSERT, UPDATE, DELETE操作
|
||||
func exec(db *sql.DB, _sql string, args ...interface{}) (res sql.Result, err error) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
stmt, err := tx.Prepare(_sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
res, err = stmt.Exec(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func query(db *sql.DB, _sql string, args ...interface{}) (result []Row, err error) {
|
||||
stmt, err := db.Prepare(_sql)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fly.exec.Prepare err")
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
rows, err := stmt.Query(args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fly.exec.Query err")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return rows2SliceMap(rows)
|
||||
}
|
||||
|
||||
func destination(columnTypes []*sql.ColumnType) func() []interface{} {
|
||||
dest := make([]func() interface{}, 0, len(columnTypes))
|
||||
for _, v := range columnTypes {
|
||||
switch strings.ToUpper(v.DatabaseTypeName()) {
|
||||
case "VARCHAR", "CHAR", "TEXT", "NVARCHAR", "TIMESTAMP":
|
||||
if nullable, _ := v.Nullable(); nullable {
|
||||
dest = append(dest, func() interface{} {
|
||||
return new(sql.NullString)
|
||||
})
|
||||
} else {
|
||||
dest = append(dest, func() interface{} {
|
||||
return new(string)
|
||||
})
|
||||
}
|
||||
case "INT", "TINYINT", "INTEGER":
|
||||
dest = append(dest, func() interface{} {
|
||||
return new(int)
|
||||
})
|
||||
case "BIGINT":
|
||||
dest = append(dest, func() interface{} {
|
||||
return new(int64)
|
||||
})
|
||||
case "DATETIME":
|
||||
dest = append(dest, func() interface{} {
|
||||
return new(time.Time)
|
||||
})
|
||||
case "DOUBLE":
|
||||
dest = append(dest, func() interface{} {
|
||||
return new(float64)
|
||||
})
|
||||
default:
|
||||
dest = append(dest, func() interface{} {
|
||||
return new(string)
|
||||
})
|
||||
}
|
||||
}
|
||||
return func() []interface{} {
|
||||
tmp := make([]interface{}, 0, len(dest))
|
||||
for _, d := range dest {
|
||||
tmp = append(tmp, d())
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
}
|
||||
|
||||
func rows2SliceMap(rows *sql.Rows) (list []Row, err error) {
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fly.rows2SliceMap.columns err")
|
||||
}
|
||||
length := len(columns)
|
||||
|
||||
columnTypes, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fly.rows2SliceMap.ColumnTypes err")
|
||||
}
|
||||
|
||||
dest := destination(columnTypes)
|
||||
|
||||
for rows.Next() {
|
||||
tmp := dest()
|
||||
err = rows.Scan(tmp...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fly.rows2SliceMap.Scan err")
|
||||
}
|
||||
row := new(Row)
|
||||
row.Data = map[string]interface{}{}
|
||||
for i := 0; i < length; i++ {
|
||||
if val, ok := tmp[i].(*sql.NullString); ok {
|
||||
row.Data[columns[i]] = val.String
|
||||
} else {
|
||||
row.Data[columns[i]] = tmp[i]
|
||||
}
|
||||
}
|
||||
list = append(list, *row)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "fly.rows2SliceMap.rows.Err err")
|
||||
}
|
||||
return list, nil
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"github.com/daodao97/fly/interval/hook"
|
||||
)
|
||||
|
||||
type HookData interface {
|
||||
Input(row map[string]interface{}, fieldValue interface{}) (interface{}, error)
|
||||
Output(row map[string]interface{}, fieldValue interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
type Hook = func() (string, HookData)
|
||||
|
||||
func Json(field string) Hook {
|
||||
return func() (string, HookData) {
|
||||
return field, &hook.Json{}
|
||||
}
|
||||
}
|
||||
|
||||
func CommaInt(field string) Hook {
|
||||
return func() (string, HookData) {
|
||||
return field, &hook.CommaSeparatedInt{}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type CommaSeparatedInt struct{}
|
||||
|
||||
func (CommaSeparatedInt) Input(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
if fieldValue == nil {
|
||||
return "", nil
|
||||
}
|
||||
strSlice, err := cast.ToStringSliceE(fieldValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(strSlice, func(i, j int) bool {
|
||||
return strSlice[i] < strSlice[j]
|
||||
})
|
||||
return strings.Join(strSlice, ","), nil
|
||||
}
|
||||
|
||||
func (CommaSeparatedInt) Output(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
parts := strings.Split(cast.ToString(fieldValue), ",")
|
||||
var _parts []int
|
||||
for _, v := range parts {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
_parts = append(_parts, cast.ToInt(v))
|
||||
}
|
||||
|
||||
return _parts, nil
|
||||
}
|
||||
|
||||
type CommaSeparatedString struct {
|
||||
CommaSeparatedInt
|
||||
}
|
||||
|
||||
func (CommaSeparatedString) Input(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
tmp, err := cast.ToStringSliceE(fieldValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.Join(tmp, ","), nil
|
||||
}
|
||||
|
||||
func (CommaSeparatedString) Output(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
var tmp []string
|
||||
for _, i := range strings.Split(cast.ToString(fieldValue), ",") {
|
||||
if i == "" {
|
||||
continue
|
||||
}
|
||||
tmp = append(tmp, i)
|
||||
}
|
||||
return tmp, nil
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package hook
|
||||
|
||||
type Hook interface {
|
||||
Input(row map[string]interface{}, fieldValue interface{}) (interface{}, error)
|
||||
Output(row map[string]interface{}, fieldValue interface{}) (interface{}, error)
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/daodao97/fly/interval/util"
|
||||
)
|
||||
|
||||
type Json struct{}
|
||||
|
||||
func (Json) Input(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
if fieldValue == nil {
|
||||
return nil, nil
|
||||
}
|
||||
bt, err := json.Marshal(fieldValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(bt), err
|
||||
}
|
||||
|
||||
func (Json) Output(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
str := cast.ToString(fieldValue)
|
||||
if str == "" {
|
||||
return nil, nil
|
||||
}
|
||||
str, err := util.JsonStrRemoveComments(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmp1 := new([]interface{})
|
||||
err1 := json.Unmarshal([]byte(str), tmp1)
|
||||
if err1 == nil {
|
||||
return tmp1, nil
|
||||
}
|
||||
tmp2 := new(map[string]interface{})
|
||||
err2 := json.Unmarshal([]byte(str), tmp2)
|
||||
if err2 == nil {
|
||||
return tmp2, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Hook.Json.Output err %v %v", err1, err2)
|
||||
}
|
||||
|
||||
/** Array columnHook **/
|
||||
|
||||
type Array struct {
|
||||
Json
|
||||
}
|
||||
|
||||
func (Array) Output(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
str := cast.ToString(fieldValue)
|
||||
tmp1 := new([]interface{})
|
||||
if str == "" {
|
||||
return tmp1, nil
|
||||
}
|
||||
str, err := util.JsonStrRemoveComments(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err1 := json.Unmarshal([]byte(str), tmp1)
|
||||
if err1 == nil {
|
||||
return tmp1, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Hook.Array.Output err %v", err1)
|
||||
}
|
||||
|
||||
/** Object columnHook **/
|
||||
|
||||
type Object struct {
|
||||
Json
|
||||
}
|
||||
|
||||
func (Object) Output(row map[string]interface{}, fieldValue interface{}) (interface{}, error) {
|
||||
str := cast.ToString(fieldValue)
|
||||
tmp2 := new(map[string]interface{})
|
||||
if str == "" {
|
||||
return tmp2, nil
|
||||
}
|
||||
str, err := util.JsonStrRemoveComments(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err2 := json.Unmarshal([]byte(str), tmp2)
|
||||
if err2 == nil {
|
||||
return tmp2, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Hook.Object.Output err %v", err2)
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"muzzammil.xyz/jsonc"
|
||||
)
|
||||
|
||||
func JsonStrRemoveComments(str string) (string, error) {
|
||||
jc := jsonc.ToJSON([]byte(str))
|
||||
if jsonc.Valid(jc) {
|
||||
return string(jc), nil
|
||||
}
|
||||
return "", errors.New("Invalid JSON")
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrParamsType = errors.New("param record type must be map[string]interface, *map[string]interface, struct, *struct")
|
||||
|
||||
func DecodeToMap(s interface{}, saveZero bool) (map[string]interface{}, error) {
|
||||
tmp := map[string]interface{}{}
|
||||
t := reflect.TypeOf(s)
|
||||
if isMapStrInterface(t) {
|
||||
return s.(map[string]interface{}), nil
|
||||
}
|
||||
|
||||
if isPtrMapStrInterface(t) {
|
||||
return *s.(*map[string]interface{}), nil
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(s))
|
||||
if isStruct(t) || isPtrStruct(t) {
|
||||
t = Deref(t)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
name := f.Tag.Get("db")
|
||||
_v := v.Field(i)
|
||||
if !saveZero && _v.IsZero() {
|
||||
continue
|
||||
}
|
||||
tmp[name] = _v.Interface()
|
||||
}
|
||||
return tmp, nil
|
||||
}
|
||||
|
||||
return nil, ErrParamsType
|
||||
}
|
||||
|
||||
func Decoder(source, dest interface{}) error {
|
||||
_decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Result: dest,
|
||||
WeaklyTypedInput: true,
|
||||
TagName: "db",
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = _decoder.Decode(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package util
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Deref is Indirect for reflect.Types
|
||||
func Deref(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
var typeChecker = map[string]func(t reflect.Type) bool{
|
||||
"*[]struct": isPtrSliceStruct,
|
||||
"*[]*struct": isPtrSlicePtrStruct,
|
||||
"struct": isStruct,
|
||||
"*struct": isPtrStruct,
|
||||
"**struct": isPtrPtrStruct,
|
||||
"map[string]interface": isMapStrInterface,
|
||||
"*map[string]interface": isPtrMapStrInterface,
|
||||
}
|
||||
|
||||
func AllowType(v interface{}, types []string) (ok bool) {
|
||||
ty := reflect.TypeOf(v)
|
||||
for _, t := range types {
|
||||
if ok {
|
||||
return ok
|
||||
}
|
||||
|
||||
if checker, has := typeChecker[t]; has {
|
||||
ok = checker(ty)
|
||||
}
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func isMapStrInterface(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Map && t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface
|
||||
}
|
||||
|
||||
func isPtrMapStrInterface(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Map && t.Elem().Key().Kind() == reflect.String && t.Elem().Elem().Kind() == reflect.Interface
|
||||
}
|
||||
|
||||
func isStruct(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func isPtrStruct(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func isPtrPtrStruct(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr && t.Elem().Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func isPtrSliceStruct(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Slice && t.Elem().Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func isPtrSlicePtrStruct(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Slice && t.Elem().Elem().Kind() == reflect.Ptr && t.Elem().Elem().Elem().Kind() == reflect.Struct
|
||||
}
|
@ -0,0 +1,366 @@
|
||||
package xtype
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
STRING Type = iota
|
||||
NUMBER
|
||||
BOOL
|
||||
MAP
|
||||
ARRAY
|
||||
NULL
|
||||
UNKNOWN
|
||||
)
|
||||
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case STRING:
|
||||
return "STRING"
|
||||
case NUMBER:
|
||||
return "NUMBER"
|
||||
case BOOL:
|
||||
return "BOOL"
|
||||
case MAP:
|
||||
return "MAP"
|
||||
case ARRAY:
|
||||
return "ARRAY"
|
||||
case NULL:
|
||||
return "NULL"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
//Go's integer types are: uint8 , uint16 , uint32 , uint64 , int8 , int16 , int32 and int64. 8, 16, 32 and 64 tell us how many bits each of the types use. uint means “unsigned integer” while int means “signed integer”. Unsigned integers only contain positive numbers (or zero).
|
||||
var _numberTypes = map[string]bool{
|
||||
"uint8": true,
|
||||
"uint16": true,
|
||||
"uint32": true,
|
||||
"uint64": true,
|
||||
"int8": true,
|
||||
"int16": true,
|
||||
"int32": true,
|
||||
"int64": true,
|
||||
"uint": true,
|
||||
"int": true,
|
||||
"float32": true,
|
||||
"float64": true,
|
||||
}
|
||||
|
||||
// GetType get xtype.Type of given object
|
||||
func GetType(obj interface{}) Type {
|
||||
if obj == nil {
|
||||
return NULL
|
||||
}
|
||||
|
||||
typeName := reflect.TypeOf(obj).String()
|
||||
if strings.HasPrefix(typeName, "[") {
|
||||
return ARRAY
|
||||
}
|
||||
|
||||
if strings.HasPrefix(typeName, "map") {
|
||||
return MAP
|
||||
}
|
||||
|
||||
if _, ok := _numberTypes[typeName]; ok {
|
||||
return NUMBER
|
||||
}
|
||||
|
||||
if typeName == "string" {
|
||||
return STRING
|
||||
} else if typeName == "bool" {
|
||||
return BOOL
|
||||
}
|
||||
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
// Float try to get float value of given object.
|
||||
// number: type cast
|
||||
// bool: true => 1.0 false => 0.0
|
||||
// string: empty => 0, otherwise parse float
|
||||
// nil && otherwise: 0.0
|
||||
func Float(obj interface{}) float64 {
|
||||
if obj == nil {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
switch v := obj.(type) {
|
||||
case bool:
|
||||
if v {
|
||||
return 1.0
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
case float32:
|
||||
return float64(v)
|
||||
case float64:
|
||||
return v
|
||||
case int:
|
||||
return float64(v)
|
||||
case int8:
|
||||
return float64(v)
|
||||
case int16:
|
||||
return float64(v)
|
||||
case int32:
|
||||
return float64(v)
|
||||
case int64:
|
||||
return float64(v)
|
||||
case uint:
|
||||
return float64(v)
|
||||
case uint8:
|
||||
return float64(v)
|
||||
case uint16:
|
||||
return float64(v)
|
||||
case uint32:
|
||||
return float64(v)
|
||||
case uint64:
|
||||
return float64(v)
|
||||
case json.Number:
|
||||
vv, _ := json.Number(v).Float64()
|
||||
return vv
|
||||
case string:
|
||||
if v == "" {
|
||||
return 0.0
|
||||
}
|
||||
if n, err := strconv.ParseFloat(v, 64); err != nil {
|
||||
return 0.0
|
||||
} else {
|
||||
return n
|
||||
}
|
||||
default:
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// Int try to get int64 value of given object.
|
||||
// number : type cast
|
||||
// bool: true => 1, false => 0,
|
||||
// string: empty => 0, otherwise parse int
|
||||
// nil & otherwise : 0
|
||||
func Int(obj interface{}) int64 {
|
||||
if obj == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch v := obj.(type) {
|
||||
case bool:
|
||||
if v {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
case float32:
|
||||
return int64(v)
|
||||
case float64:
|
||||
return int64(v)
|
||||
case int:
|
||||
return int64(v)
|
||||
case int8:
|
||||
return int64(v)
|
||||
case int16:
|
||||
return int64(v)
|
||||
case int32:
|
||||
return int64(v)
|
||||
case int64:
|
||||
return v
|
||||
case uint:
|
||||
return int64(v)
|
||||
case uint8:
|
||||
return int64(v)
|
||||
case uint16:
|
||||
return int64(v)
|
||||
case uint32:
|
||||
return int64(v)
|
||||
case uint64:
|
||||
return int64(v)
|
||||
case json.Number:
|
||||
vv, _ := json.Number(v).Int64()
|
||||
return int64(vv)
|
||||
case string:
|
||||
if v == "" {
|
||||
return 0
|
||||
}
|
||||
if n, err := strconv.ParseInt(v, 10, 64); err != nil {
|
||||
return 0
|
||||
} else {
|
||||
return n
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Uint try to get uint64 value of given object.
|
||||
// number : type cast
|
||||
// bool: true => 1, false => 0,
|
||||
// string: empty => 0, otherwise parse int
|
||||
// nil & otherwise : 0
|
||||
func Uint(obj interface{}) uint64 {
|
||||
if obj == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch v := obj.(type) {
|
||||
case bool:
|
||||
if v {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
case float32:
|
||||
return uint64(v)
|
||||
case float64:
|
||||
return uint64(v)
|
||||
case int:
|
||||
return uint64(v)
|
||||
case int8:
|
||||
return uint64(v)
|
||||
case int16:
|
||||
return uint64(v)
|
||||
case int32:
|
||||
return uint64(v)
|
||||
case int64:
|
||||
return uint64(v)
|
||||
case uint64:
|
||||
return v
|
||||
case uint:
|
||||
return uint64(v)
|
||||
case uint8:
|
||||
return uint64(v)
|
||||
case uint16:
|
||||
return uint64(v)
|
||||
case uint32:
|
||||
return uint64(v)
|
||||
case json.Number:
|
||||
vv, _ := json.Number(v).Int64()
|
||||
return uint64(vv)
|
||||
case string:
|
||||
if v == "" {
|
||||
return 0
|
||||
}
|
||||
if n, err := strconv.ParseUint(v, 10, 64); err != nil {
|
||||
return 0
|
||||
} else {
|
||||
return n
|
||||
}
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// String try to get string value of given object.
|
||||
// string/[]byte : original string
|
||||
// bool : true => "1", false => ""
|
||||
// number : number format
|
||||
// nil & otherwise : ""
|
||||
func String(obj interface{}) string {
|
||||
if obj == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch v := obj.(type) {
|
||||
case bool:
|
||||
if v {
|
||||
return "1"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case int:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case uint:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case uint8:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case uint16:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case uint32:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case uint64:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case []byte:
|
||||
return string(v)
|
||||
case string:
|
||||
return v
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Bytes try to get []byte value of given object
|
||||
// see String()
|
||||
func Bytes(obj interface{}) []byte {
|
||||
return []byte(String(obj))
|
||||
}
|
||||
|
||||
const EPSILON float64 = 1e-9
|
||||
const FALSE_STRINGS = "no,false,off,0,"
|
||||
|
||||
// Bool try to get bool value of given object.
|
||||
// number: 0 => false, otherwise => true
|
||||
// string: ("", "false", "off", "no", "0") => false (case insensitive), otherwise => true
|
||||
// nil: false
|
||||
// otherwise: true
|
||||
func Bool(obj interface{}) bool {
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch v := obj.(type) {
|
||||
case bool:
|
||||
return v
|
||||
case float32:
|
||||
return math.Abs(float64(v)) > EPSILON
|
||||
case float64:
|
||||
return math.Abs(v) > EPSILON
|
||||
case int:
|
||||
return v != 0
|
||||
case int8:
|
||||
return v != 0
|
||||
case int16:
|
||||
return v != 0
|
||||
case int32:
|
||||
return v != 0
|
||||
case int64:
|
||||
return v != 0
|
||||
case uint:
|
||||
return v != 0
|
||||
case uint8:
|
||||
return v != 0
|
||||
case uint16:
|
||||
return v != 0
|
||||
case uint32:
|
||||
return v != 0
|
||||
case uint64:
|
||||
return v != 0
|
||||
case []byte:
|
||||
s := strings.ToLower(string(v))
|
||||
return s != "" && strings.Index(FALSE_STRINGS, s+",") == -1
|
||||
case string:
|
||||
s := strings.ToLower(v)
|
||||
return s != "" && strings.Index(FALSE_STRINGS, s+",") == -1
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var (
|
||||
red = color.New(color.BgRed).Sprint
|
||||
yellow = color.New(color.BgHiYellow).Sprint
|
||||
green = color.New(color.BgGreen).Sprint
|
||||
gray = color.New(color.BgCyan).Sprint
|
||||
prefix = color.New(color.FgGreen).Sprint
|
||||
)
|
||||
|
||||
type Level int
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case LevelDebug:
|
||||
return gray(" DEBUG ")
|
||||
case LevelInfo:
|
||||
return green(" INFO ")
|
||||
case LevelWarn:
|
||||
return yellow(" WARNING ")
|
||||
case LevelErr:
|
||||
return red(" ERROR ")
|
||||
}
|
||||
return gray(" DEBUG ")
|
||||
}
|
||||
|
||||
const LevelDebug = Level(0)
|
||||
const LevelInfo = Level(1)
|
||||
const LevelWarn = Level(2)
|
||||
const LevelErr = Level(3)
|
||||
|
||||
type Logger interface {
|
||||
Log(level Level, keyValues ...interface{}) error
|
||||
}
|
||||
|
||||
var (
|
||||
logger = newStdOutLogger()
|
||||
limitLevel = LevelDebug
|
||||
)
|
||||
|
||||
func SetLogger(customLogger Logger, customLimitLevel Level) {
|
||||
logger = customLogger
|
||||
limitLevel = customLimitLevel
|
||||
}
|
||||
|
||||
func newStdOutLogger() Logger {
|
||||
return &stdOutLogger{
|
||||
logger: log.New(os.Stdout, prefix("FLY LOG")+" ", log.Lmicroseconds),
|
||||
}
|
||||
}
|
||||
|
||||
type stdOutLogger struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func jsonEncode(v interface{}) string {
|
||||
bt, _ := json.Marshal(v)
|
||||
return string(bt)
|
||||
}
|
||||
|
||||
func (s stdOutLogger) Log(level Level, keyValues ...interface{}) error {
|
||||
if level < limitLevel {
|
||||
return nil
|
||||
}
|
||||
args := []interface{}{level.String()}
|
||||
args = append(args, keyValues...)
|
||||
|
||||
for i, v := range args {
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
args[i] = jsonEncode(t)
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Println(args...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func dbLog(prefix string, start time.Time, err *error, kv *[]interface{}) {
|
||||
tc := time.Since(start)
|
||||
_log := []interface{}{
|
||||
prefix,
|
||||
"ums:", tc.Milliseconds(),
|
||||
}
|
||||
_log = append(_log, *kv...)
|
||||
if *err != nil {
|
||||
_log = append(_log, "error:", *err)
|
||||
_ = logger.Log(LevelErr, _log...)
|
||||
return
|
||||
}
|
||||
_ = logger.Log(LevelDebug, _log...)
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/daodao97/fly/interval/util"
|
||||
)
|
||||
|
||||
type Model interface {
|
||||
PrimaryKey() string
|
||||
Select(opt ...Option) (rows *Rows)
|
||||
SelectOne(opt ...Option) *Row
|
||||
Count(opt ...Option) (count int64, err error)
|
||||
Insert(record interface{}) (lastId int64, err error)
|
||||
Update(record interface{}, opt ...Option) (ok bool, err error)
|
||||
Delete(opt ...Option) (ok bool, err error)
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
}
|
||||
|
||||
type model struct {
|
||||
connection string
|
||||
database string
|
||||
table string
|
||||
fakeDelKey string
|
||||
primaryKey string
|
||||
columnHook map[string]HookData
|
||||
columnValidator []Validator
|
||||
hasOne []HasOpts
|
||||
hasMany []HasOpts
|
||||
options *Options
|
||||
client *sql.DB
|
||||
saveZero bool
|
||||
err error
|
||||
}
|
||||
|
||||
func New(table string, baseOpt ...With) *model {
|
||||
m := &model{
|
||||
connection: "default",
|
||||
primaryKey: "id",
|
||||
table: table,
|
||||
}
|
||||
|
||||
if table == "" {
|
||||
m.err = errors.New("table name is empty")
|
||||
return m
|
||||
}
|
||||
|
||||
for _, v := range baseOpt {
|
||||
v(m)
|
||||
}
|
||||
|
||||
if m.client == nil {
|
||||
m.client, m.err = db(m.connection)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *model) PrimaryKey() string {
|
||||
return m.primaryKey
|
||||
}
|
||||
|
||||
func (m *model) Select(opt ...Option) (rows *Rows) {
|
||||
var kv []interface{}
|
||||
var err error
|
||||
defer dbLog("Select", time.Now(), &err, &kv)
|
||||
|
||||
if m.err != nil {
|
||||
err = m.err
|
||||
return &Rows{Err: m.err}
|
||||
}
|
||||
opts := new(Options)
|
||||
opt = append(opt, table(m.table), database(m.database))
|
||||
if m.fakeDelKey != "" {
|
||||
opt = append(opt, WhereEq(m.fakeDelKey, 0))
|
||||
}
|
||||
for _, o := range opt {
|
||||
o(opts)
|
||||
}
|
||||
|
||||
_sql, args := SelectBuilder(opt...)
|
||||
res, err := query(m.client, _sql, args...)
|
||||
kv = append(kv, "sql:", _sql, "args:", args)
|
||||
if err != nil {
|
||||
return &Rows{Err: err}
|
||||
}
|
||||
|
||||
for _, has := range m.hasOne {
|
||||
res, err = m.hasOneData(res, has)
|
||||
if err != nil {
|
||||
return &Rows{Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
for _, has := range m.hasMany {
|
||||
res, err = m.hasManyData(res, has)
|
||||
if err != nil {
|
||||
return &Rows{Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range m.columnHook {
|
||||
for i, r := range res {
|
||||
for field, val := range r.Data {
|
||||
if k == field {
|
||||
overVal, err1 := v.Output(res[i].Data, val)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
return &Rows{Err: err}
|
||||
}
|
||||
res[i].Data[field] = overVal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Rows{List: res, Err: err}
|
||||
}
|
||||
|
||||
func (m *model) SelectOne(opt ...Option) *Row {
|
||||
opt = append(opt, Limit(1))
|
||||
rows := m.Select(opt...)
|
||||
if rows.Err != nil {
|
||||
return &Row{Err: rows.Err}
|
||||
}
|
||||
if len(rows.List) == 0 {
|
||||
return &Row{}
|
||||
}
|
||||
return &rows.List[0]
|
||||
}
|
||||
|
||||
func (m *model) Count(opt ...Option) (count int64, err error) {
|
||||
opt = append(opt, table(m.table), AggregateCount("*"))
|
||||
var result struct {
|
||||
Count int64
|
||||
}
|
||||
err = m.SelectOne(opt...).Binding(&result)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.Count, nil
|
||||
}
|
||||
|
||||
func (m *model) Insert(record interface{}) (lastId int64, err error) {
|
||||
if m.err != nil {
|
||||
return 0, m.err
|
||||
}
|
||||
|
||||
var kv []interface{}
|
||||
defer dbLog("Insert", time.Now(), &err, &kv)
|
||||
|
||||
_record, err := util.DecodeToMap(record, m.saveZero)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_record, err = m.hookInput(_record)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, v := range m.columnValidator {
|
||||
for _, h := range v.Handle {
|
||||
ok, err := h(m, _record, _record[v.Field])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !ok {
|
||||
return 0, errors.New("ValidateHandle err " + v.Msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(_record, m.primaryKey)
|
||||
if len(_record) == 0 {
|
||||
return 0, errors.New("empty record to insert")
|
||||
}
|
||||
|
||||
ks, vs := m.recordToKV(_record)
|
||||
_sql, args := InsertBuilder(table(m.table), Field(ks...), Value(vs...))
|
||||
kv = append(kv, "sql:", _sql, "args:", vs)
|
||||
|
||||
result, err := exec(m.client, _sql, args...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func (m *model) Update(record interface{}, opt ...Option) (ok bool, err error) {
|
||||
if m.err != nil {
|
||||
return false, m.err
|
||||
}
|
||||
|
||||
var kv []interface{}
|
||||
defer dbLog("Update", time.Now(), &err, &kv)
|
||||
|
||||
_record, err := util.DecodeToMap(record, m.saveZero)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if id, ok := _record[m.primaryKey]; ok {
|
||||
kv = append(kv, "id:", id)
|
||||
opt = append(opt, WhereEq(m.primaryKey, id))
|
||||
}
|
||||
|
||||
_record, err = m.hookInput(_record)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
delete(_record, m.primaryKey)
|
||||
if len(_record) == 0 {
|
||||
return false, errors.New("empty record to update")
|
||||
}
|
||||
|
||||
for _, v := range m.columnValidator {
|
||||
for _, h := range v.Handle {
|
||||
ok, err := h(m, _record, _record[v.Field])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, errors.New("ValidateHandle err " + v.Msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ks, vs := m.recordToKV(_record)
|
||||
opt = append(opt, table(m.table), Field(ks...), Value(vs...))
|
||||
|
||||
_sql, args := UpdateBuilder(opt...)
|
||||
kv = append(kv, "sql:", _sql, "args:", vs)
|
||||
|
||||
result, err := exec(m.client, _sql, args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
effect, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return effect >= int64(0), nil
|
||||
}
|
||||
|
||||
func (m *model) Delete(opt ...Option) (ok bool, err error) {
|
||||
if len(opt) == 0 {
|
||||
return false, errors.New("danger, delete query must with some condition")
|
||||
}
|
||||
|
||||
if m.err != nil {
|
||||
return false, m.err
|
||||
}
|
||||
|
||||
opt = append(opt, table(m.table))
|
||||
if m.fakeDelKey != "" {
|
||||
return m.Update(map[string]interface{}{m.fakeDelKey: 1}, opt...)
|
||||
}
|
||||
|
||||
var kv []interface{}
|
||||
defer dbLog("Delete", time.Now(), &err, &kv)
|
||||
|
||||
_sql, args := DeleteBuilder(opt...)
|
||||
kv = append(kv, "slq:", _sql, "args:", args)
|
||||
|
||||
result, err := exec(m.client, _sql, args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
effect, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return effect > int64(0), nil
|
||||
}
|
||||
|
||||
func (m *model) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return m.client.Exec(query, args...)
|
||||
}
|
||||
|
||||
func (m *model) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
return m.client.Query(query, args...)
|
||||
}
|
||||
|
||||
func (m *model) hookInput(record map[string]interface{}) (map[string]interface{}, error) {
|
||||
for k, v := range m.columnHook {
|
||||
for field, val := range record {
|
||||
if k == field {
|
||||
overVal, err := v.Input(record, val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
record[field] = overVal
|
||||
}
|
||||
}
|
||||
}
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func (m *model) recordToKV(record map[string]interface{}) (ks []string, vs []interface{}) {
|
||||
for k, v := range record {
|
||||
ks = append(ks, k)
|
||||
vs = append(vs, v)
|
||||
}
|
||||
|
||||
return ks, vs
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type HasOpts struct {
|
||||
Conn string
|
||||
Database string
|
||||
Table string
|
||||
LocalKey string
|
||||
ForeignKey string
|
||||
OtherKeys []string
|
||||
}
|
||||
|
||||
var space = regexp.MustCompile(`\s+`)
|
||||
|
||||
func filterStr(arr []string) (real []string) {
|
||||
for _, v := range arr {
|
||||
if v != "" {
|
||||
real = append(real, v)
|
||||
}
|
||||
}
|
||||
return real
|
||||
}
|
||||
|
||||
func otherKeys(key []string) (rel []string, err error) {
|
||||
for _, v := range key {
|
||||
split := filterStr(space.Split(strings.Trim(v, " "), -1))
|
||||
l := len(split)
|
||||
if l == 1 || l == 3 {
|
||||
rel = append(rel, split[l-1])
|
||||
continue
|
||||
}
|
||||
return nil, errors.New("otherKey must be like a, a as b, a AS c")
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
func (m *model) hasOneData(rows []Row, opt HasOpts) ([]Row, error) {
|
||||
otherKeys, err := otherKeys(opt.OtherKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var localKeys []interface{}
|
||||
for _, v := range rows {
|
||||
if val, ok := v.Data[opt.LocalKey]; ok {
|
||||
localKeys = append(localKeys, val)
|
||||
}
|
||||
}
|
||||
|
||||
if len(localKeys) == 0 {
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
opt.OtherKeys = append(opt.OtherKeys, opt.ForeignKey)
|
||||
|
||||
_rows := New(opt.Table, WithConn(opt.Conn)).Select(Field(opt.OtherKeys...), WhereIn(opt.ForeignKey, localKeys))
|
||||
if _rows.Err != nil {
|
||||
return nil, errors.Wrap(_rows.Err, "hasOne err")
|
||||
}
|
||||
|
||||
for i, left := range rows {
|
||||
for _, right := range _rows.List {
|
||||
l, err := cast.ToStringE(left.Data[opt.LocalKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := cast.ToStringE(right.Data[opt.ForeignKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if l == r {
|
||||
for _, k := range otherKeys {
|
||||
rows[i].Data[k] = right.Data[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (m *model) hasManyData(rows []Row, opt HasOpts) ([]Row, error) {
|
||||
otherKeys, err := otherKeys(opt.OtherKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var localKeys []interface{}
|
||||
for _, v := range rows {
|
||||
if val, ok := v.Data[opt.LocalKey]; ok {
|
||||
localKeys = append(localKeys, val)
|
||||
}
|
||||
}
|
||||
|
||||
if len(localKeys) == 0 {
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
opt.OtherKeys = append(opt.OtherKeys, opt.ForeignKey)
|
||||
|
||||
_rows := New(opt.Table, WithConn(opt.Conn)).Select(Field(opt.OtherKeys...), WhereIn(opt.ForeignKey, localKeys))
|
||||
if _rows.Err != nil {
|
||||
return nil, errors.Wrap(_rows.Err, "hasOne err")
|
||||
}
|
||||
|
||||
for i, left := range rows {
|
||||
tmp := make(map[string][]interface{})
|
||||
for _, k := range otherKeys {
|
||||
tmp[k] = make([]interface{}, 0)
|
||||
}
|
||||
for _, right := range _rows.List {
|
||||
l, err := cast.ToStringE(left.Data[opt.LocalKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := cast.ToStringE(right.Data[opt.ForeignKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if l == r {
|
||||
for _, k := range otherKeys {
|
||||
tmp[k] = append(tmp[k], right.Data[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range tmp {
|
||||
rows[i].Data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type With = func(*model)
|
||||
|
||||
func WithDB(db *sql.DB) With {
|
||||
return func(b *model) {
|
||||
b.client = db
|
||||
}
|
||||
}
|
||||
|
||||
func WithConn(name string) With {
|
||||
return func(b *model) {
|
||||
b.connection = name
|
||||
}
|
||||
}
|
||||
|
||||
func WithFakeDelKey(name string) With {
|
||||
return func(b *model) {
|
||||
b.fakeDelKey = name
|
||||
}
|
||||
}
|
||||
|
||||
func WithPrimaryKey(name string) With {
|
||||
return func(b *model) {
|
||||
b.primaryKey = name
|
||||
}
|
||||
}
|
||||
|
||||
func ColumnHook(columnHook ...Hook) With {
|
||||
return func(b *model) {
|
||||
if b.columnHook == nil {
|
||||
b.columnHook = make(map[string]HookData)
|
||||
}
|
||||
for _, v := range columnHook {
|
||||
f, h := v()
|
||||
b.columnHook[f] = h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnValidator while validate data by validator when create or update event
|
||||
func ColumnValidator(validator ...Validator) With {
|
||||
return func(b *model) {
|
||||
if b.columnValidator == nil {
|
||||
b.columnValidator = make([]Validator, 0, len(validator))
|
||||
}
|
||||
b.columnValidator = append(b.columnValidator, validator...)
|
||||
}
|
||||
}
|
||||
|
||||
func Validate(field, msg string, handle ...ValidateHandleMaker) (v Validator) {
|
||||
v.Field = field
|
||||
v.Msg = msg
|
||||
v.Handle = make([]ValidateHandle, 0, len(handle))
|
||||
for _, h := range handle {
|
||||
v.Handle = append(v.Handle, h(field))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func WithSaveZero() With {
|
||||
return func(b *model) {
|
||||
b.saveZero = true
|
||||
}
|
||||
}
|
||||
|
||||
func HasOne(opts ...HasOpts) With {
|
||||
return func(b *model) {
|
||||
if b.hasOne == nil {
|
||||
b.hasOne = make([]HasOpts, 0)
|
||||
}
|
||||
for i, v := range opts {
|
||||
if v.Conn == "" {
|
||||
opts[i].Conn = "default"
|
||||
}
|
||||
if v.LocalKey == "" {
|
||||
opts[i].LocalKey = "id"
|
||||
}
|
||||
if v.ForeignKey == "" {
|
||||
opts[i].ForeignKey = "id"
|
||||
}
|
||||
}
|
||||
b.hasOne = append(b.hasOne, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
func HasMany(opts ...HasOpts) With {
|
||||
return func(b *model) {
|
||||
if b.hasMany == nil {
|
||||
b.hasMany = make([]HasOpts, 0)
|
||||
}
|
||||
for i, v := range opts {
|
||||
if v.Conn == "" {
|
||||
opts[i].Conn = "default"
|
||||
}
|
||||
if v.LocalKey == "" {
|
||||
opts[i].LocalKey = "id"
|
||||
}
|
||||
if v.ForeignKey == "" {
|
||||
opts[i].ForeignKey = "id"
|
||||
}
|
||||
}
|
||||
b.hasMany = append(b.hasMany, opts...)
|
||||
}
|
||||
}
|
@ -0,0 +1,586 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/daodao97/fly/interval/util"
|
||||
)
|
||||
|
||||
var ErrRowBindingType = errors.New("binding dest type must be *struct **struct")
|
||||
var ErrRowsBindingType = errors.New("binding dest type must be *[]struct, *[]*struct")
|
||||
|
||||
const selectMod = "select %s from `%s`"
|
||||
const insertMod = "insert into %s (%s) values (%s)"
|
||||
const updateMod = "update %s set %s"
|
||||
const deleteMod = "delete from %s"
|
||||
|
||||
type Row struct {
|
||||
Data map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
func (r *Row) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(r.Data)
|
||||
}
|
||||
|
||||
func (r *Row) Binding(dest interface{}) error {
|
||||
if !util.AllowType(dest, []string{"*struct", "**struct"}) {
|
||||
return ErrRowBindingType
|
||||
}
|
||||
|
||||
return util.Decoder(r.Data, dest)
|
||||
}
|
||||
|
||||
func (r Row) Get(key string) (interface{}, bool) {
|
||||
v, ok := r.Data[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
type Rows struct {
|
||||
List []Row
|
||||
Err error
|
||||
}
|
||||
|
||||
func (r *Rows) Binding(dest interface{}) error {
|
||||
if !util.AllowType(dest, []string{"*[]struct", "*[]*struct"}) {
|
||||
return ErrRowsBindingType
|
||||
}
|
||||
|
||||
var source []map[string]interface{}
|
||||
for _, v := range r.List {
|
||||
source = append(source, v.Data)
|
||||
}
|
||||
|
||||
return util.Decoder(source, dest)
|
||||
}
|
||||
|
||||
type Option = func(opts *Options)
|
||||
|
||||
type Options struct {
|
||||
database string
|
||||
table string
|
||||
field []string
|
||||
where []where
|
||||
orderBy string
|
||||
groupBy string
|
||||
limit int
|
||||
offset int
|
||||
value []interface{}
|
||||
}
|
||||
|
||||
func table(table string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.table = table
|
||||
}
|
||||
}
|
||||
|
||||
func database(database string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.database = database
|
||||
}
|
||||
}
|
||||
|
||||
func Offset(offset int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
func Limit(offset int) Option {
|
||||
return func(opts *Options) {
|
||||
opts.limit = offset
|
||||
}
|
||||
}
|
||||
|
||||
func Pagination(pageNumber, pageSize int) []Option {
|
||||
return []Option{
|
||||
Limit(pageSize),
|
||||
Offset((pageNumber - 1) * pageSize),
|
||||
}
|
||||
}
|
||||
|
||||
func Field(name ...string) Option {
|
||||
var _name []string
|
||||
for _, v := range name {
|
||||
if strings.Contains(v, " as ") {
|
||||
tmp := strings.Split(v, " as ")
|
||||
_name = append(_name, "`"+strings.Trim(tmp[0], " ")+"` as `"+strings.Trim(tmp[1], " ")+"`")
|
||||
} else if strings.Contains(v, " AS ") {
|
||||
tmp := strings.Split(v, " AS ")
|
||||
_name = append(_name, "`"+strings.Trim(tmp[0], " ")+"` as `"+strings.Trim(tmp[1], " ")+"`")
|
||||
_name = append(_name, "`"+v+"`")
|
||||
} else {
|
||||
_name = append(_name, "`"+v+"`")
|
||||
}
|
||||
}
|
||||
return func(opts *Options) {
|
||||
opts.field = _name
|
||||
}
|
||||
}
|
||||
|
||||
func FieldRaw(name string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.field = append(opts.field, name)
|
||||
}
|
||||
}
|
||||
|
||||
func AggregateSum(name string) Option {
|
||||
return Field("sum(" + name + ") as aggregate")
|
||||
}
|
||||
|
||||
func AggregateCount(name string) Option {
|
||||
return FieldRaw("count(" + name + ") as count")
|
||||
}
|
||||
|
||||
func AggregateMax(name string) Option {
|
||||
return Field("max(" + name + ") as aggregate")
|
||||
}
|
||||
|
||||
func Value(val ...interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.value = val
|
||||
}
|
||||
}
|
||||
|
||||
type where struct {
|
||||
field string
|
||||
operator string
|
||||
value interface{}
|
||||
logic string
|
||||
sub []where
|
||||
}
|
||||
|
||||
func Where(field, operator string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: operator,
|
||||
value: value,
|
||||
logic: "and",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereEq(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "=",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereNotEq(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "!=",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereGt(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: ">",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereGe(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: ">=",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereLt(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "<",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereLe(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "<=",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereIn(field string, value []interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "in",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereNotIn(field string, value []interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "not in",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOr(field, operator string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: operator,
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrEq(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "=",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrNotEq(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "!=",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrGt(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: ">",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrGe(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: ">=",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrLt(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "<",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrLe(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "<=",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrIn(field string, value []interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "in",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrNotIn(field string, value []interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "not in",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereGroup(opts ...Option) Option {
|
||||
opt := &Options{}
|
||||
for _, v := range opts {
|
||||
v(opt)
|
||||
}
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
logic: "and",
|
||||
sub: opt.where,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrGroup(opts ...Option) Option {
|
||||
opt := &Options{}
|
||||
for _, v := range opts {
|
||||
v(opt)
|
||||
}
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
logic: "or",
|
||||
sub: opt.where,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereLike(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "like",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrLike(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "like",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrNotLike(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "not like",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereBetween(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "between",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereFindInSet(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "find_in_set",
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func WhereOrFindInSet(field string, value interface{}) Option {
|
||||
return func(opts *Options) {
|
||||
opts.where = append(opts.where, where{
|
||||
field: field,
|
||||
operator: "find_in_set",
|
||||
value: value,
|
||||
logic: "or",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func OrderByDesc(field string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.orderBy = "`" + field + "` " + "desc"
|
||||
}
|
||||
}
|
||||
|
||||
func OrderByAsc(field string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.orderBy = "`" + field + "` " + "asc"
|
||||
}
|
||||
}
|
||||
|
||||
func GroupBy(field string) Option {
|
||||
return func(opts *Options) {
|
||||
opts.groupBy = field
|
||||
}
|
||||
}
|
||||
|
||||
func whereBuilder(condition []where) (sql string, args []interface{}) {
|
||||
if len(condition) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
var tokens []string
|
||||
for i, v := range condition {
|
||||
if i != 0 {
|
||||
if v.logic != "" {
|
||||
tokens = append(tokens, v.logic)
|
||||
} else {
|
||||
tokens = append(tokens, "and")
|
||||
}
|
||||
}
|
||||
|
||||
if v.field != "" {
|
||||
switch v.operator {
|
||||
case "in", "not in":
|
||||
val := v.value.([]interface{})
|
||||
var placeholder []string
|
||||
for range val {
|
||||
placeholder = append(placeholder, "?")
|
||||
}
|
||||
tokens = append(tokens, fmt.Sprintf("`%s` %s (%s)", v.field, v.operator, strings.Join(placeholder, ",")))
|
||||
args = append(args, val...)
|
||||
case "between":
|
||||
val := v.value.([]interface{})
|
||||
tokens = append(tokens, fmt.Sprintf("`%s` %s ? and ?", v.field, v.operator))
|
||||
args = append(args, val...)
|
||||
case "find_in_set":
|
||||
tokens = append(tokens, fmt.Sprintf("find_in_set(?, %s)", v.field))
|
||||
args = append(args, v.value)
|
||||
default:
|
||||
tokens = append(tokens, fmt.Sprintf("`%s` %s ?", v.field, v.operator))
|
||||
args = append(args, v.value)
|
||||
}
|
||||
}
|
||||
|
||||
if v.sub != nil {
|
||||
_sql, _args := whereBuilder(v.sub)
|
||||
tokens = append(tokens, "("+_sql+")")
|
||||
args = append(args, _args...)
|
||||
}
|
||||
}
|
||||
return strings.Join(tokens, " "), args
|
||||
}
|
||||
|
||||
func SelectBuilder(opts ...Option) (sql string, args []interface{}) {
|
||||
_opts := &Options{}
|
||||
for _, v := range opts {
|
||||
v(_opts)
|
||||
}
|
||||
|
||||
_where, args := whereBuilder(_opts.where)
|
||||
_field := "*"
|
||||
|
||||
if len(_opts.field) > 0 {
|
||||
_field = strings.Join(_opts.field, ", ")
|
||||
}
|
||||
|
||||
sql = fmt.Sprintf(selectMod, _field, _opts.table)
|
||||
|
||||
if _where != "" {
|
||||
sql = sql + " where " + _where
|
||||
}
|
||||
|
||||
if _opts.orderBy != "" {
|
||||
sql = sql + " order by " + _opts.orderBy
|
||||
}
|
||||
|
||||
if _opts.groupBy != "" {
|
||||
sql = sql + " group by " + _opts.groupBy
|
||||
}
|
||||
|
||||
if _opts.limit != 0 {
|
||||
sql = sql + " limit ? offset ? "
|
||||
args = append(args, _opts.limit, _opts.offset)
|
||||
}
|
||||
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func getTable(opt *Options) string {
|
||||
if opt.database == "" {
|
||||
return opt.table
|
||||
}
|
||||
return opt.database + "." + opt.table
|
||||
}
|
||||
|
||||
func InsertBuilder(opts ...Option) (sql string, args []interface{}) {
|
||||
_opts := &Options{}
|
||||
for _, v := range opts {
|
||||
v(_opts)
|
||||
}
|
||||
var _val []string
|
||||
for range _opts.field {
|
||||
_val = append(_val, "?")
|
||||
}
|
||||
sql = fmt.Sprintf(insertMod, getTable(_opts), strings.Join(_opts.field, ", "), strings.Join(_val, ","))
|
||||
args = _opts.value
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func UpdateBuilder(opts ...Option) (sql string, args []interface{}) {
|
||||
_opts := &Options{}
|
||||
for _, v := range opts {
|
||||
v(_opts)
|
||||
}
|
||||
var _val []string
|
||||
for _, v := range _opts.field {
|
||||
_val = append(_val, v+" = ?")
|
||||
}
|
||||
sql = fmt.Sprintf(updateMod, getTable(_opts), strings.Join(_val, ","))
|
||||
args = _opts.value
|
||||
if len(_opts.where) > 0 {
|
||||
_where, _args := whereBuilder(_opts.where)
|
||||
sql = sql + " where " + _where
|
||||
args = append(args, _args...)
|
||||
}
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func DeleteBuilder(opts ...Option) (sql string, args []interface{}) {
|
||||
_opts := &Options{}
|
||||
for _, v := range opts {
|
||||
v(_opts)
|
||||
}
|
||||
sql = fmt.Sprintf(deleteMod, _opts.table)
|
||||
if len(_opts.where) > 0 {
|
||||
_where, _args := whereBuilder(_opts.where)
|
||||
sql = sql + " where " + _where
|
||||
args = append(args, _args...)
|
||||
}
|
||||
return sql, args
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package fly
|
||||
|
||||
import (
|
||||
"github.com/daodao97/fly/interval/xtype"
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
Field string
|
||||
Msg string
|
||||
Handle []ValidateHandle
|
||||
}
|
||||
|
||||
type ValidateHandle = func(m Model, row map[string]interface{}, val interface{}) (ok bool, err error)
|
||||
|
||||
type ValidateHandleMaker = func(field string) ValidateHandle
|
||||
|
||||
// Required field value must exist and not zero
|
||||
func Required(field string) ValidateHandle {
|
||||
return func(m Model, row map[string]interface{}, val interface{}) (ok bool, err error) {
|
||||
v, ok := row[field]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
return xtype.Bool(v), nil
|
||||
}
|
||||
}
|
||||
|
||||
// IfRequired if field1 is existed, then field2 must exist and not zero
|
||||
func IfRequired(field1 string) ValidateHandleMaker {
|
||||
return func(field string) ValidateHandle {
|
||||
return func(m Model, row map[string]interface{}, val interface{}) (ok bool, err error) {
|
||||
h := Required(field1)
|
||||
ok, err = h(m, row, row[field1])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return true, nil
|
||||
}
|
||||
h = Required(field)
|
||||
return h(m, row, row[field])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unique field value must unique in current table
|
||||
func Unique(field string) ValidateHandle {
|
||||
return func(m Model, row map[string]interface{}, val interface{}) (ok bool, err error) {
|
||||
opts := []Option{WhereEq(field, val)}
|
||||
if id, ok := row[m.PrimaryKey()]; ok {
|
||||
opts = append(opts, WhereNotEq(m.PrimaryKey(), id))
|
||||
}
|
||||
count, err := m.Count(opts...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count == 0, nil
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Fatih Arslan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,178 @@
|
||||
# color [![](https://github.com/fatih/color/workflows/build/badge.svg)](https://github.com/fatih/color/actions) [![PkgGoDev](https://pkg.go.dev/badge/github.com/fatih/color)](https://pkg.go.dev/github.com/fatih/color)
|
||||
|
||||
Color lets you use colorized outputs in terms of [ANSI Escape
|
||||
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
|
||||
has support for Windows too! The API can be used in several ways, pick one that
|
||||
suits you.
|
||||
|
||||
![Color](https://user-images.githubusercontent.com/438920/96832689-03b3e000-13f4-11eb-9803-46f4c4de3406.jpg)
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/fatih/color
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Standard colors
|
||||
|
||||
```go
|
||||
// Print with default helper functions
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// A newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// These are using the default foreground colors
|
||||
color.Red("We have red")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
```
|
||||
|
||||
### Mix and reuse colors
|
||||
|
||||
```go
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with white background.")
|
||||
```
|
||||
|
||||
### Use your own output (io.Writer)
|
||||
|
||||
```go
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(writer, "This will print text in blue.")
|
||||
```
|
||||
|
||||
### Custom print functions (PrintFunc)
|
||||
|
||||
```go
|
||||
// Create a custom print function for convenience
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("Warning")
|
||||
red("Error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("Don't forget this...")
|
||||
```
|
||||
|
||||
### Custom fprint functions (FprintFunc)
|
||||
|
||||
```go
|
||||
blue := color.New(color.FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, "Don't forget this...")
|
||||
```
|
||||
|
||||
### Insert into noncolor strings (SprintFunc)
|
||||
|
||||
```go
|
||||
// Create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
yellow := color.New(color.FgYellow).SprintFunc()
|
||||
red := color.New(color.FgRed).SprintFunc()
|
||||
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
|
||||
fmt.Printf("This %s rocks!\n", info("package"))
|
||||
|
||||
// Use helper functions
|
||||
fmt.Println("This", color.RedString("warning"), "should be not neglected.")
|
||||
fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
|
||||
|
||||
// Windows supported too! Just don't forget to change the output to color.Output
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
```
|
||||
|
||||
### Plug into existing code
|
||||
|
||||
```go
|
||||
// Use handy standard colors
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will now be in yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // Don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // Use it in your function
|
||||
|
||||
fmt.Println("All text will now be bold magenta.")
|
||||
```
|
||||
|
||||
### Disable/Enable color
|
||||
|
||||
There might be a case where you want to explicitly disable/enable color output. the
|
||||
`go-isatty` package will automatically disable color output for non-tty output streams
|
||||
(for example if the output were piped directly to `less`).
|
||||
|
||||
The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment
|
||||
variable is set (regardless of its value).
|
||||
|
||||
`Color` has support to disable/enable colors programatically both globally and
|
||||
for single color definitions. For example suppose you have a CLI app and a
|
||||
`--no-color` bool flag. You can easily disable the color output with:
|
||||
|
||||
```go
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
```
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
```go
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
```
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams.
|
||||
|
||||
## Todo
|
||||
|
||||
* Save/Return previous values
|
||||
* Evaluate fmt.Formatter interface
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* [Fatih Arslan](https://github.com/fatih)
|
||||
* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
|
@ -0,0 +1,618 @@
|
||||
package color
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
// false or true based on the stdout's file descriptor referring to a terminal
|
||||
// or not. It's also set to true if the NO_COLOR environment variable is
|
||||
// set (regardless of its value). This is a global option and affects all
|
||||
// colors. For more control over each color block use the methods
|
||||
// DisableColor() individually.
|
||||
NoColor = noColorExists() || os.Getenv("TERM") == "dumb" ||
|
||||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
||||
|
||||
// Output defines the standard output of the print functions. By default
|
||||
// os.Stdout is used.
|
||||
Output = colorable.NewColorableStdout()
|
||||
|
||||
// Error defines a color supporting writer for os.Stderr.
|
||||
Error = colorable.NewColorableStderr()
|
||||
|
||||
// colorsCache is used to reduce the count of created Color objects and
|
||||
// allows to reuse already created objects with required Attribute.
|
||||
colorsCache = make(map[Attribute]*Color)
|
||||
colorsCacheMu sync.Mutex // protects colorsCache
|
||||
)
|
||||
|
||||
// noColorExists returns true if the environment variable NO_COLOR exists.
|
||||
func noColorExists() bool {
|
||||
_, exists := os.LookupEnv("NO_COLOR")
|
||||
return exists
|
||||
}
|
||||
|
||||
// Color defines a custom color object which is defined by SGR parameters.
|
||||
type Color struct {
|
||||
params []Attribute
|
||||
noColor *bool
|
||||
}
|
||||
|
||||
// Attribute defines a single SGR Code
|
||||
type Attribute int
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
// Base attributes
|
||||
const (
|
||||
Reset Attribute = iota
|
||||
Bold
|
||||
Faint
|
||||
Italic
|
||||
Underline
|
||||
BlinkSlow
|
||||
BlinkRapid
|
||||
ReverseVideo
|
||||
Concealed
|
||||
CrossedOut
|
||||
)
|
||||
|
||||
// Foreground text colors
|
||||
const (
|
||||
FgBlack Attribute = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta
|
||||
FgCyan
|
||||
FgWhite
|
||||
)
|
||||
|
||||
// Foreground Hi-Intensity text colors
|
||||
const (
|
||||
FgHiBlack Attribute = iota + 90
|
||||
FgHiRed
|
||||
FgHiGreen
|
||||
FgHiYellow
|
||||
FgHiBlue
|
||||
FgHiMagenta
|
||||
FgHiCyan
|
||||
FgHiWhite
|
||||
)
|
||||
|
||||
// Background text colors
|
||||
const (
|
||||
BgBlack Attribute = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
)
|
||||
|
||||
// Background Hi-Intensity text colors
|
||||
const (
|
||||
BgHiBlack Attribute = iota + 100
|
||||
BgHiRed
|
||||
BgHiGreen
|
||||
BgHiYellow
|
||||
BgHiBlue
|
||||
BgHiMagenta
|
||||
BgHiCyan
|
||||
BgHiWhite
|
||||
)
|
||||
|
||||
// New returns a newly created color object.
|
||||
func New(value ...Attribute) *Color {
|
||||
c := &Color{
|
||||
params: make([]Attribute, 0),
|
||||
}
|
||||
|
||||
if noColorExists() {
|
||||
c.noColor = boolPtr(true)
|
||||
}
|
||||
|
||||
c.Add(value...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Set sets the given parameters immediately. It will change the color of
|
||||
// output with the given SGR parameters until color.Unset() is called.
|
||||
func Set(p ...Attribute) *Color {
|
||||
c := New(p...)
|
||||
c.Set()
|
||||
return c
|
||||
}
|
||||
|
||||
// Unset resets all escape attributes and clears the output. Usually should
|
||||
// be called after Set().
|
||||
func Unset() {
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Set sets the SGR sequence.
|
||||
func (c *Color) Set() *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unset() {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
Unset()
|
||||
}
|
||||
|
||||
func (c *Color) setWriter(w io.Writer) *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unsetWriter(w io.Writer) {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
||||
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
||||
func (c *Color) Add(value ...Attribute) *Color {
|
||||
c.params = append(c.params, value...)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) prepend(value Attribute) {
|
||||
c.params = append(c.params, 0)
|
||||
copy(c.params[1:], c.params[0:])
|
||||
c.params[0] = value
|
||||
}
|
||||
|
||||
// Fprint formats using the default formats for its operands and writes to w.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprint(w, a...)
|
||||
}
|
||||
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are added between operands when neither is a
|
||||
// string. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprint(Output, a...)
|
||||
}
|
||||
|
||||
// Fprintf formats according to a format specifier and writes to w.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// This is the standard fmt.Printf() method wrapped with the given color.
|
||||
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintf(Output, format, a...)
|
||||
}
|
||||
|
||||
// Fprintln formats using the default formats for its operands and writes to w.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintln(w, a...)
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are always added between operands and a newline is
|
||||
// appended. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintln(Output, a...)
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
func (c *Color) Sprint(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// FprintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprint().
|
||||
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprint(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Print().
|
||||
func (c *Color) PrintFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Print(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintf().
|
||||
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
||||
return func(w io.Writer, format string, a ...interface{}) {
|
||||
c.Fprintf(w, format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Printf().
|
||||
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
||||
return func(format string, a ...interface{}) {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintln().
|
||||
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprintln(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Println().
|
||||
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Println(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// SprintFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output, example:
|
||||
//
|
||||
// put := New(FgYellow).SprintFunc()
|
||||
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
||||
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintfFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
return func(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintlnFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
|
||||
// an example output might be: "1;36" -> bold cyan
|
||||
func (c *Color) sequence() string {
|
||||
format := make([]string, len(c.params))
|
||||
for i, v := range c.params {
|
||||
format[i] = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
return strings.Join(format, ";")
|
||||
}
|
||||
|
||||
// wrap wraps the s string with the colors attributes. The string is ready to
|
||||
// be printed.
|
||||
func (c *Color) wrap(s string) string {
|
||||
if c.isNoColorSet() {
|
||||
return s
|
||||
}
|
||||
|
||||
return c.format() + s + c.unformat()
|
||||
}
|
||||
|
||||
func (c *Color) format() string {
|
||||
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
||||
}
|
||||
|
||||
func (c *Color) unformat() string {
|
||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// DisableColor disables the color output. Useful to not change any existing
|
||||
// code and still being able to output. Can be used for flags like
|
||||
// "--no-color". To enable back use EnableColor() method.
|
||||
func (c *Color) DisableColor() {
|
||||
c.noColor = boolPtr(true)
|
||||
}
|
||||
|
||||
// EnableColor enables the color output. Use it in conjunction with
|
||||
// DisableColor(). Otherwise this method has no side effects.
|
||||
func (c *Color) EnableColor() {
|
||||
c.noColor = boolPtr(false)
|
||||
}
|
||||
|
||||
func (c *Color) isNoColorSet() bool {
|
||||
// check first if we have user set action
|
||||
if c.noColor != nil {
|
||||
return *c.noColor
|
||||
}
|
||||
|
||||
// if not return the global option, which is disabled by default
|
||||
return NoColor
|
||||
}
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attr := range c.params {
|
||||
if !c2.attrExists(attr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Color) attrExists(a Attribute) bool {
|
||||
for _, attr := range c.params {
|
||||
if attr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func getCachedColor(p Attribute) *Color {
|
||||
colorsCacheMu.Lock()
|
||||
defer colorsCacheMu.Unlock()
|
||||
|
||||
c, ok := colorsCache[p]
|
||||
if !ok {
|
||||
c = New(p)
|
||||
colorsCache[p] = c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func colorPrint(format string, p Attribute, a ...interface{}) {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
c.Print(format)
|
||||
} else {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func colorString(format string, p Attribute, a ...interface{}) string {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if len(a) == 0 {
|
||||
return c.SprintFunc()(format)
|
||||
}
|
||||
|
||||
return c.SprintfFunc()(format, a...)
|
||||
}
|
||||
|
||||
// Black is a convenient helper function to print with black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
||||
|
||||
// Red is a convenient helper function to print with red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
||||
|
||||
// Green is a convenient helper function to print with green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
||||
|
||||
// Yellow is a convenient helper function to print with yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
||||
|
||||
// Blue is a convenient helper function to print with blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
||||
|
||||
// Magenta is a convenient helper function to print with magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
||||
|
||||
// Cyan is a convenient helper function to print with cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
||||
|
||||
// White is a convenient helper function to print with white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
||||
|
||||
// BlackString is a convenient helper function to return a string with black
|
||||
// foreground.
|
||||
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
||||
|
||||
// RedString is a convenient helper function to return a string with red
|
||||
// foreground.
|
||||
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
||||
|
||||
// GreenString is a convenient helper function to return a string with green
|
||||
// foreground.
|
||||
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
||||
|
||||
// YellowString is a convenient helper function to return a string with yellow
|
||||
// foreground.
|
||||
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
||||
|
||||
// BlueString is a convenient helper function to return a string with blue
|
||||
// foreground.
|
||||
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
||||
|
||||
// MagentaString is a convenient helper function to return a string with magenta
|
||||
// foreground.
|
||||
func MagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgMagenta, a...)
|
||||
}
|
||||
|
||||
// CyanString is a convenient helper function to return a string with cyan
|
||||
// foreground.
|
||||
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
||||
|
||||
// WhiteString is a convenient helper function to return a string with white
|
||||
// foreground.
|
||||
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
||||
|
||||
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
||||
|
||||
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
||||
|
||||
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
||||
|
||||
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
||||
|
||||
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
||||
|
||||
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
||||
// foreground.
|
||||
func HiBlackString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiBlack, a...)
|
||||
}
|
||||
|
||||
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
||||
// foreground.
|
||||
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
||||
// foreground.
|
||||
func HiGreenString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiGreen, a...)
|
||||
}
|
||||
|
||||
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
||||
// foreground.
|
||||
func HiYellowString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiYellow, a...)
|
||||
}
|
||||
|
||||
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
||||
// foreground.
|
||||
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
||||
// foreground.
|
||||
func HiMagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiMagenta, a...)
|
||||
}
|
||||
|
||||
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
||||
// foreground.
|
||||
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
||||
// foreground.
|
||||
func HiWhiteString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiWhite, a...)
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
Package color is an ANSI color package to output colorized or SGR defined
|
||||
output to the standard output. The API can be used in several way, pick one
|
||||
that suits you.
|
||||
|
||||
Use simple and default helper functions with predefined foreground colors:
|
||||
|
||||
color.Cyan("Prints text in cyan.")
|
||||
|
||||
// a newline will be appended automatically
|
||||
color.Blue("Prints %s in blue.", "text")
|
||||
|
||||
// More default foreground colors..
|
||||
color.Red("We have red")
|
||||
color.Yellow("Yellow color too!")
|
||||
color.Magenta("And many others ..")
|
||||
|
||||
// Hi-intensity colors
|
||||
color.HiGreen("Bright green color.")
|
||||
color.HiBlack("Bright black means gray..")
|
||||
color.HiWhite("Shiny white color!")
|
||||
|
||||
However there are times where custom color mixes are required. Below are some
|
||||
examples to create custom color objects and use the print functions of each
|
||||
separate color object.
|
||||
|
||||
// Create a new color object
|
||||
c := color.New(color.FgCyan).Add(color.Underline)
|
||||
c.Println("Prints cyan text with an underline.")
|
||||
|
||||
// Or just add them to New()
|
||||
d := color.New(color.FgCyan, color.Bold)
|
||||
d.Printf("This prints bold cyan %s\n", "too!.")
|
||||
|
||||
|
||||
// Mix up foreground and background colors, create new mixes!
|
||||
red := color.New(color.FgRed)
|
||||
|
||||
boldRed := red.Add(color.Bold)
|
||||
boldRed.Println("This will print text in bold red.")
|
||||
|
||||
whiteBackground := red.Add(color.BgWhite)
|
||||
whiteBackground.Println("Red text with White background.")
|
||||
|
||||
// Use your own io.Writer output
|
||||
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
|
||||
|
||||
blue := color.New(color.FgBlue)
|
||||
blue.Fprint(myWriter, "This will print text in blue.")
|
||||
|
||||
You can create PrintXxx functions to simplify even more:
|
||||
|
||||
// Create a custom print function for convenient
|
||||
red := color.New(color.FgRed).PrintfFunc()
|
||||
red("warning")
|
||||
red("error: %s", err)
|
||||
|
||||
// Mix up multiple attributes
|
||||
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
|
||||
notice("don't forget this...")
|
||||
|
||||
You can also FprintXxx functions to pass your own io.Writer:
|
||||
|
||||
blue := color.New(FgBlue).FprintfFunc()
|
||||
blue(myWriter, "important notice: %s", stars)
|
||||
|
||||
// Mix up with multiple attributes
|
||||
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
|
||||
success(myWriter, don't forget this...")
|
||||
|
||||
|
||||
Or create SprintXxx functions to mix strings with other non-colorized strings:
|
||||
|
||||
yellow := New(FgYellow).SprintFunc()
|
||||
red := New(FgRed).SprintFunc()
|
||||
|
||||
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Printf("this %s rocks!\n", info("package"))
|
||||
|
||||
Windows support is enabled by default. All Print functions work as intended.
|
||||
However only for color.SprintXXX functions, user should use fmt.FprintXXX and
|
||||
set the output to color.Output:
|
||||
|
||||
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
|
||||
|
||||
info := New(FgWhite, BgGreen).SprintFunc()
|
||||
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
|
||||
|
||||
Using with existing code is possible. Just use the Set() method to set the
|
||||
standard output to the given parameters. That way a rewrite of an existing
|
||||
code is not required.
|
||||
|
||||
// Use handy standard colors.
|
||||
color.Set(color.FgYellow)
|
||||
|
||||
fmt.Println("Existing text will be now in Yellow")
|
||||
fmt.Printf("This one %s\n", "too")
|
||||
|
||||
color.Unset() // don't forget to unset
|
||||
|
||||
// You can mix up parameters
|
||||
color.Set(color.FgMagenta, color.Bold)
|
||||
defer color.Unset() // use it in your function
|
||||
|
||||
fmt.Println("All text will be now bold magenta.")
|
||||
|
||||
There might be a case where you want to disable color output (for example to
|
||||
pipe the standard output of your app to somewhere else). `Color` has support to
|
||||
disable colors both globally and for single color definition. For example
|
||||
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
|
||||
the color output with:
|
||||
|
||||
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
|
||||
|
||||
if *flagNoColor {
|
||||
color.NoColor = true // disables colorized output
|
||||
}
|
||||
|
||||
You can also disable the color by setting the NO_COLOR environment variable to any value.
|
||||
|
||||
It also has support for single color definitions (local). You can
|
||||
disable/enable color output on the fly:
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
c.Println("Prints cyan text")
|
||||
|
||||
c.DisableColor()
|
||||
c.Println("This is printed without any color")
|
||||
|
||||
c.EnableColor()
|
||||
c.Println("This prints again cyan...")
|
||||
*/
|
||||
package color
|
@ -0,0 +1,15 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- "1.15.x"
|
||||
- "1.16.x"
|
||||
- "1.17.x"
|
||||
- "1.18.x"
|
||||
|
||||
script:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Liu Qishuai
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,106 @@
|
||||
<img src="https://raw.githubusercontent.com/lqs/sqlingo/master/logo.png" width="236" height="106">
|
||||
|
||||
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)
|
||||
[![go.dev](https://img.shields.io/badge/go.dev-reference-007d9c)](https://pkg.go.dev/github.com/lqs/sqlingo?tab=doc)
|
||||
[![Travis CI](https://api.travis-ci.com/lqs/sqlingo.svg?branch=master)](https://app.travis-ci.com/github/lqs/sqlingo)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/lqs/sqlingo)](https://goreportcard.com/report/github.com/lqs/sqlingo)
|
||||
[![codecov](https://codecov.io/gh/lqs/sqlingo/branch/master/graph/badge.svg)](https://codecov.io/gh/lqs/sqlingo)
|
||||
[![MIT license](http://img.shields.io/badge/license-MIT-9d1f14)](http://opensource.org/licenses/MIT)
|
||||
[![last commit](https://img.shields.io/github/last-commit/lqs/sqlingo.svg)](https://github.com/lqs/sqlingo/commits)
|
||||
|
||||
**sqlingo** is a SQL DSL (a.k.a. SQL Builder or ORM) library in Go. It generates code from the database and lets you write SQL queries in an elegant way.
|
||||
|
||||
<img src="https://lqs-public-us-west.oss-us-west-1.aliyuncs.com/sqlingo/demo2.gif" width="443" height="297">
|
||||
|
||||
## Features
|
||||
* Auto-generating DSL objects and model structs from the database so you don't need to manually keep things in sync
|
||||
* SQL DML (SELECT / INSERT / UPDATE / DELETE) with some advanced SQL query syntaxes
|
||||
* Many common errors could be detected at compile time
|
||||
* Your can use the features in your editor / IDE, such as autocompleting the fields and queries, or finding the usage of a field or a table
|
||||
* Context support
|
||||
* Transaction support
|
||||
* Interceptor support
|
||||
|
||||
## Database Support Status
|
||||
| Database | Status |
|
||||
------------- | --------------
|
||||
| MySQL | stable |
|
||||
| PostgreSQL | experimental |
|
||||
| SQLite | experimental |
|
||||
|
||||
## Tutorial
|
||||
|
||||
### Install and use sqlingo code generator
|
||||
The first step is to generate code from the database. In order to generate code, sqlingo requires your tables are already created in the database.
|
||||
|
||||
```
|
||||
$ go get -u github.com/lqs/sqlingo/sqlingo-gen-mysql
|
||||
$ mkdir -p generated/sqlingo
|
||||
$ sqlingo-gen-mysql root:123456@/database_name >generated/sqlingo/database_name.dsl.go
|
||||
```
|
||||
|
||||
|
||||
### Write your application
|
||||
Here's a demonstration of some simple & advanced usage of sqlingo.
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/lqs/sqlingo"
|
||||
. "./generated/sqlingo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := sqlingo.Open("mysql", "root:123456@/database_name")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// a simple query
|
||||
var customers []*CustomerModel
|
||||
db.SelectFrom(Customer).
|
||||
Where(Customer.Id.In(1, 2)).
|
||||
OrderBy(Customer.Name.Desc()).
|
||||
FetchAll(&customers)
|
||||
|
||||
// query from multiple tables
|
||||
var customerId int64
|
||||
var orderId int64
|
||||
err = db.Select(Customer.Id, Order.Id).
|
||||
From(Customer, Order).
|
||||
Where(Customer.Id.Equals(Order.CustomerId), Order.Id.Equals(1)).
|
||||
FetchFirst(&customerId, &orderId)
|
||||
|
||||
// subquery and count
|
||||
count, err := db.SelectFrom(Order)
|
||||
Where(Order.CustomerId.In(db.Select(Customer.Id).
|
||||
From(Customer).
|
||||
Where(Customer.Name.Equals("Customer One")))).
|
||||
Count()
|
||||
|
||||
// group-by with auto conversion to map
|
||||
var customerIdToOrderCount map[int64]int64
|
||||
err = db.Select(Order.CustomerId, f.Count(1)).
|
||||
From(Order).
|
||||
GroupBy(Order.CustomerId).
|
||||
FetchAll(&customerIdToOrderCount)
|
||||
if err != nil {
|
||||
println(err)
|
||||
}
|
||||
|
||||
// insert some rows
|
||||
customer1 := &CustomerModel{name: "Customer One"}
|
||||
customer2 := &CustomerModel{name: "Customer Two"}
|
||||
_, err = db.InsertInto(Customer).
|
||||
Models(customer1, customer2).
|
||||
Execute()
|
||||
|
||||
// insert with on-duplicate-key-update
|
||||
_, err = db.InsertInto(Customer).
|
||||
Fields(Customer.Id, Customer.Name).
|
||||
Values(42, "Universe").
|
||||
OnDuplicateKeyUpdate().
|
||||
Set(Customer.Name, Customer.Name.Concat(" 2")).
|
||||
Execute()
|
||||
}
|
||||
```
|
@ -0,0 +1,87 @@
|
||||
package sqlingo
|
||||
|
||||
import "strings"
|
||||
|
||||
// CaseExpression indicates the status in a CASE statement
|
||||
type CaseExpression interface {
|
||||
WhenThen(when BooleanExpression, then interface{}) CaseExpression
|
||||
Else(value interface{}) CaseExpressionWithElse
|
||||
End() Expression
|
||||
}
|
||||
|
||||
// CaseExpressionWithElse indicates the status in CASE ... ELSE ... statement
|
||||
type CaseExpressionWithElse interface {
|
||||
End() Expression
|
||||
}
|
||||
|
||||
type caseStatus struct {
|
||||
head, tail *whenThen
|
||||
elseValue interface{}
|
||||
}
|
||||
|
||||
type whenThen struct {
|
||||
next *whenThen
|
||||
when BooleanExpression
|
||||
then interface{}
|
||||
}
|
||||
|
||||
// Case initiates a CASE statement
|
||||
func Case() CaseExpression {
|
||||
return caseStatus{}
|
||||
}
|
||||
|
||||
func (s caseStatus) WhenThen(when BooleanExpression, then interface{}) CaseExpression {
|
||||
whenThen := &whenThen{when: when, then: then}
|
||||
if s.head == nil {
|
||||
s.head = whenThen
|
||||
}
|
||||
if s.tail != nil {
|
||||
s.tail.next = whenThen
|
||||
}
|
||||
s.tail = whenThen
|
||||
return s
|
||||
}
|
||||
|
||||
func (s caseStatus) Else(value interface{}) CaseExpressionWithElse {
|
||||
s.elseValue = value
|
||||
return s
|
||||
}
|
||||
|
||||
func (s caseStatus) End() Expression {
|
||||
if s.head == nil {
|
||||
return expression{
|
||||
builder: func(scope scope) (string, error) {
|
||||
elseSql, _, err := getSQL(scope, s.elseValue)
|
||||
return elseSql, err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return expression{
|
||||
builder: func(scope scope) (string, error) {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("CASE ")
|
||||
|
||||
for whenThen := s.head; whenThen != nil; whenThen = whenThen.next {
|
||||
whenSql, err := whenThen.when.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
thenSql, _, err := getSQL(scope, whenThen.then)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString("WHEN " + whenSql + " THEN " + thenSql + " ")
|
||||
}
|
||||
if s.elseValue != nil {
|
||||
elseSql, _, err := getSQL(scope, s.elseValue)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString("ELSE " + elseSql + " ")
|
||||
}
|
||||
sb.WriteString("END")
|
||||
return sb.String(), nil
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// SqlingoRuntimeVersion is the the runtime version of sqlingo
|
||||
SqlingoRuntimeVersion = 2
|
||||
)
|
||||
|
||||
// Model is the interface of generated model struct
|
||||
type Model interface {
|
||||
GetTable() Table
|
||||
GetValues() []interface{}
|
||||
}
|
||||
|
||||
// Assignment is an assignment statement
|
||||
type Assignment interface {
|
||||
GetSQL(scope scope) (string, error)
|
||||
}
|
||||
|
||||
type assignment struct {
|
||||
Assignment
|
||||
field Field
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (a assignment) GetSQL(scope scope) (string, error) {
|
||||
value, _, err := getSQL(scope, a.value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fieldSql, err := a.field.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fieldSql + " = " + value, nil
|
||||
}
|
||||
|
||||
func command(name string, arg interface{}) expression {
|
||||
return expression{builder: func(scope scope) (string, error) {
|
||||
sql, _, err := getSQL(scope, arg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name + " " + sql, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func commaFields(scope scope, fields []Field) (string, error) {
|
||||
var sqlBuilder strings.Builder
|
||||
sqlBuilder.Grow(128)
|
||||
for i, item := range fields {
|
||||
if i > 0 {
|
||||
sqlBuilder.WriteString(", ")
|
||||
}
|
||||
itemSql, err := item.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sqlBuilder.WriteString(itemSql)
|
||||
}
|
||||
return sqlBuilder.String(), nil
|
||||
}
|
||||
|
||||
func commaExpressions(scope scope, expressions []Expression) (string, error) {
|
||||
var sqlBuilder strings.Builder
|
||||
sqlBuilder.Grow(128)
|
||||
for i, item := range expressions {
|
||||
if i > 0 {
|
||||
sqlBuilder.WriteString(", ")
|
||||
}
|
||||
itemSql, err := item.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sqlBuilder.WriteString(itemSql)
|
||||
}
|
||||
return sqlBuilder.String(), nil
|
||||
}
|
||||
|
||||
func commaTables(scope scope, tables []Table) string {
|
||||
var sqlBuilder strings.Builder
|
||||
sqlBuilder.Grow(32)
|
||||
for i, table := range tables {
|
||||
if i > 0 {
|
||||
sqlBuilder.WriteString(", ")
|
||||
}
|
||||
sqlBuilder.WriteString(table.GetSQL(scope))
|
||||
}
|
||||
return sqlBuilder.String()
|
||||
}
|
||||
|
||||
func commaValues(scope scope, values []interface{}) (string, error) {
|
||||
var sqlBuilder strings.Builder
|
||||
for i, item := range values {
|
||||
if i > 0 {
|
||||
sqlBuilder.WriteString(", ")
|
||||
}
|
||||
itemSql, _, err := getSQL(scope, item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sqlBuilder.WriteString(itemSql)
|
||||
}
|
||||
return sqlBuilder.String(), nil
|
||||
}
|
||||
|
||||
func commaAssignments(scope scope, assignments []assignment) (string, error) {
|
||||
var sqlBuilder strings.Builder
|
||||
for i, item := range assignments {
|
||||
if i > 0 {
|
||||
sqlBuilder.WriteString(", ")
|
||||
}
|
||||
itemSql, err := item.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sqlBuilder.WriteString(itemSql)
|
||||
}
|
||||
return sqlBuilder.String(), nil
|
||||
}
|
||||
|
||||
func commaOrderBys(scope scope, orderBys []OrderBy) (string, error) {
|
||||
var sqlBuilder strings.Builder
|
||||
for i, item := range orderBys {
|
||||
if i > 0 {
|
||||
sqlBuilder.WriteString(", ")
|
||||
}
|
||||
itemSql, err := item.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sqlBuilder.WriteString(itemSql)
|
||||
}
|
||||
return sqlBuilder.String(), nil
|
||||
}
|
||||
|
||||
func getCallerInfo(db database, retry bool) string {
|
||||
if !db.enableCallerInfo {
|
||||
return ""
|
||||
}
|
||||
extraInfo := ""
|
||||
if db.tx != nil {
|
||||
extraInfo += " (tx)"
|
||||
}
|
||||
if retry {
|
||||
extraInfo += " (retry)"
|
||||
}
|
||||
for i := 0; true; i++ {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if file == "" || strings.Contains(file, "/sqlingo@v") {
|
||||
continue
|
||||
}
|
||||
segs := strings.Split(file, "/")
|
||||
name := segs[len(segs)-1]
|
||||
return fmt.Sprintf("/* %s:%d%s */ ", name, line, extraInfo)
|
||||
}
|
||||
return ""
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Cursor is the interface of a row cursor.
|
||||
type Cursor interface {
|
||||
Next() bool
|
||||
Scan(dest ...interface{}) error
|
||||
GetMap() (map[string]value, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
rows *sql.Rows
|
||||
}
|
||||
|
||||
func (c cursor) Next() bool {
|
||||
return c.rows.Next()
|
||||
}
|
||||
|
||||
func preparePointers(val reflect.Value, scans *[]interface{}) error {
|
||||
kind := val.Kind()
|
||||
switch kind {
|
||||
case reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.String:
|
||||
if addr := val.Addr(); addr.CanInterface() {
|
||||
*scans = append(*scans, addr.Interface())
|
||||
}
|
||||
case reflect.Struct:
|
||||
for j := 0; j < val.NumField(); j++ {
|
||||
field := val.Field(j)
|
||||
if field.Kind() == reflect.Interface {
|
||||
continue
|
||||
}
|
||||
if err := preparePointers(field, scans); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
toType := val.Type().Elem()
|
||||
switch toType.Kind() {
|
||||
case reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.String:
|
||||
*scans = append(*scans, val.Addr().Interface())
|
||||
default:
|
||||
to := reflect.New(toType).Elem()
|
||||
val.Set(to.Addr())
|
||||
err := preparePointers(to, scans)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
if _, ok := (val.Interface()).([]byte); ok {
|
||||
*scans = append(*scans, val.Addr().Interface())
|
||||
} else {
|
||||
return fmt.Errorf("unknown type []%s", val.Type().Elem().Kind().String())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown type %s", kind.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBool(s []byte) (bool, error) {
|
||||
if len(s) == 1 {
|
||||
if s[0] == 0 {
|
||||
return false, nil
|
||||
} else if s[0] == 1 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return strconv.ParseBool(string(s))
|
||||
}
|
||||
|
||||
func (c cursor) Scan(dest ...interface{}) error {
|
||||
if len(dest) == 0 {
|
||||
// dry run
|
||||
return nil
|
||||
}
|
||||
|
||||
var scans []interface{}
|
||||
for i, item := range dest {
|
||||
if reflect.ValueOf(item).Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("argument %d is not pointer", i)
|
||||
}
|
||||
|
||||
val := reflect.Indirect(reflect.ValueOf(item))
|
||||
|
||||
err := preparePointers(val, &scans)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pbs := make(map[int]*bool)
|
||||
ppbs := make(map[int]**bool)
|
||||
|
||||
for i, scan := range scans {
|
||||
if pb, ok := scan.(*bool); ok {
|
||||
var s []uint8
|
||||
scans[i] = &s
|
||||
pbs[i] = pb
|
||||
} else if ppb, ok := scan.(**bool); ok {
|
||||
var s *[]uint8
|
||||
scans[i] = &s
|
||||
ppbs[i] = ppb
|
||||
}
|
||||
}
|
||||
|
||||
err := c.rows.Scan(scans...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, pb := range pbs {
|
||||
if *(scans[i].(*[]byte)) == nil {
|
||||
return fmt.Errorf("field %d is null", i)
|
||||
}
|
||||
b, err := parseBool(*(scans[i].(*[]byte)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*pb = b
|
||||
}
|
||||
for i, ppb := range ppbs {
|
||||
if *(scans[i].(**[]uint8)) == nil {
|
||||
*ppb = nil
|
||||
} else {
|
||||
b, err := parseBool(**(scans[i].(**[]byte)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*ppb = &b
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c cursor) GetMap() (result map[string]value, err error) {
|
||||
columns, err := c.rows.Columns()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
columnCount := len(columns)
|
||||
values := make([]interface{}, columnCount)
|
||||
for i := 0; i < columnCount; i++ {
|
||||
var value *string
|
||||
values[i] = &value
|
||||
}
|
||||
if err = c.rows.Scan(values...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result = make(map[string]value, columnCount)
|
||||
for i, column := range columns {
|
||||
result[column] = value{stringValue: *values[i].(**string)}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c cursor) Close() error {
|
||||
return c.rows.Close()
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Database is the interface of a database with underlying sql.DB object.
|
||||
type Database interface {
|
||||
// Get the underlying sql.DB object of the database
|
||||
GetDB() *sql.DB
|
||||
BeginTx(ctx context.Context, opts *sql.TxOptions, f func(tx Transaction) error) error
|
||||
// Executes a query and return the cursor
|
||||
Query(sql string) (Cursor, error)
|
||||
// Executes a query with context and return the cursor
|
||||
QueryContext(ctx context.Context, sqlString string) (Cursor, error)
|
||||
// Executes a statement
|
||||
Execute(sql string) (sql.Result, error)
|
||||
// Executes a statement with context
|
||||
ExecuteContext(ctx context.Context, sql string) (sql.Result, error)
|
||||
// Set the logger function
|
||||
SetLogger(logger func(sql string, durationNano int64))
|
||||
// Set the retry policy function.
|
||||
// The retry policy function returns true if needs retry.
|
||||
SetRetryPolicy(retryPolicy func(err error) bool)
|
||||
// enable or disable caller info
|
||||
EnableCallerInfo(enableCallerInfo bool)
|
||||
// Set a interceptor function
|
||||
SetInterceptor(interceptor InterceptorFunc)
|
||||
|
||||
// Initiate a SELECT statement
|
||||
Select(fields ...interface{}) selectWithFields
|
||||
// Initiate a SELECT DISTINCT statement
|
||||
SelectDistinct(fields ...interface{}) selectWithFields
|
||||
// Initiate a SELECT * FROM statement
|
||||
SelectFrom(tables ...Table) selectWithTables
|
||||
// Initiate a INSERT INTO statement
|
||||
InsertInto(table Table) insertWithTable
|
||||
// Initiate a REPLACE INTO statement
|
||||
ReplaceInto(table Table) insertWithTable
|
||||
// Initiate a UPDATE statement
|
||||
Update(table Table) updateWithSet
|
||||
// Initiate a DELETE FROM statement
|
||||
DeleteFrom(table Table) deleteWithTable
|
||||
}
|
||||
|
||||
type txOrDB interface {
|
||||
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
type database struct {
|
||||
db *sql.DB
|
||||
tx *sql.Tx
|
||||
logger func(sql string, durationNano int64)
|
||||
dialect dialect
|
||||
retryPolicy func(error) bool
|
||||
enableCallerInfo bool
|
||||
interceptor InterceptorFunc
|
||||
}
|
||||
|
||||
func (d *database) SetLogger(logger func(sql string, durationNano int64)) {
|
||||
d.logger = logger
|
||||
}
|
||||
|
||||
func (d *database) SetRetryPolicy(retryPolicy func(err error) bool) {
|
||||
d.retryPolicy = retryPolicy
|
||||
}
|
||||
|
||||
func (d *database) EnableCallerInfo(enableCallerInfo bool) {
|
||||
d.enableCallerInfo = enableCallerInfo
|
||||
}
|
||||
|
||||
func (d *database) SetInterceptor(interceptor InterceptorFunc) {
|
||||
d.interceptor = interceptor
|
||||
}
|
||||
|
||||
// Open a database, similar to sql.Open
|
||||
func Open(driverName string, dataSourceName string) (db Database, err error) {
|
||||
var sqlDB *sql.DB
|
||||
if dataSourceName != "" {
|
||||
sqlDB, err = sql.Open(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
db = &database{
|
||||
dialect: getDialectFromDriverName(driverName),
|
||||
db: sqlDB,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d database) GetDB() *sql.DB {
|
||||
return d.db
|
||||
}
|
||||
|
||||
func (d database) getTxOrDB() txOrDB {
|
||||
if d.tx != nil {
|
||||
return d.tx
|
||||
}
|
||||
return d.db
|
||||
}
|
||||
|
||||
func (d database) Query(sqlString string) (Cursor, error) {
|
||||
return d.QueryContext(context.Background(), sqlString)
|
||||
}
|
||||
|
||||
func (d database) QueryContext(ctx context.Context, sqlString string) (Cursor, error) {
|
||||
isRetry := false
|
||||
for {
|
||||
sqlStringWithCallerInfo := getCallerInfo(d, isRetry) + sqlString
|
||||
|
||||
rows, err := d.queryContextOnce(ctx, sqlStringWithCallerInfo)
|
||||
if err != nil {
|
||||
isRetry = d.tx == nil && d.retryPolicy != nil && d.retryPolicy(err)
|
||||
if isRetry {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return cursor{rows: rows}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d database) queryContextOnce(ctx context.Context, sqlStringWithCallerInfo string) (*sql.Rows, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
startTime := time.Now().UnixNano()
|
||||
defer func() {
|
||||
endTime := time.Now().UnixNano()
|
||||
if d.logger != nil {
|
||||
d.logger(sqlStringWithCallerInfo, endTime-startTime)
|
||||
}
|
||||
}()
|
||||
|
||||
interceptor := d.interceptor
|
||||
var rows *sql.Rows
|
||||
invoker := func(ctx context.Context, sql string) (err error) {
|
||||
rows, err = d.getTxOrDB().QueryContext(ctx, sql)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if interceptor == nil {
|
||||
err = invoker(ctx, sqlStringWithCallerInfo)
|
||||
} else {
|
||||
err = interceptor(ctx, sqlStringWithCallerInfo, invoker)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (d database) Execute(sqlString string) (sql.Result, error) {
|
||||
return d.ExecuteContext(context.Background(), sqlString)
|
||||
}
|
||||
|
||||
func (d database) ExecuteContext(ctx context.Context, sqlString string) (sql.Result, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
sqlStringWithCallerInfo := getCallerInfo(d, false) + sqlString
|
||||
startTime := time.Now().UnixNano()
|
||||
defer func() {
|
||||
endTime := time.Now().UnixNano()
|
||||
if d.logger != nil {
|
||||
d.logger(sqlStringWithCallerInfo, endTime-startTime)
|
||||
}
|
||||
}()
|
||||
|
||||
var result sql.Result
|
||||
invoker := func(ctx context.Context, sql string) (err error) {
|
||||
result, err = d.getTxOrDB().ExecContext(ctx, sql)
|
||||
return
|
||||
}
|
||||
var err error
|
||||
if d.interceptor == nil {
|
||||
err = invoker(ctx, sqlStringWithCallerInfo)
|
||||
} else {
|
||||
err = d.interceptor(ctx, sqlStringWithCallerInfo, invoker)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type deleteStatus struct {
|
||||
scope scope
|
||||
where BooleanExpression
|
||||
orderBys []OrderBy
|
||||
limit *int
|
||||
}
|
||||
|
||||
type deleteWithTable interface {
|
||||
Where(conditions ...BooleanExpression) deleteWithWhere
|
||||
}
|
||||
|
||||
type deleteWithWhere interface {
|
||||
toDeleteFinal
|
||||
OrderBy(orderBys ...OrderBy) deleteWithOrder
|
||||
Limit(limit int) deleteWithLimit
|
||||
}
|
||||
|
||||
type deleteWithOrder interface {
|
||||
toDeleteFinal
|
||||
Limit(limit int) deleteWithLimit
|
||||
}
|
||||
|
||||
type deleteWithLimit interface {
|
||||
toDeleteFinal
|
||||
}
|
||||
|
||||
type toDeleteFinal interface {
|
||||
GetSQL() (string, error)
|
||||
Execute() (result sql.Result, err error)
|
||||
}
|
||||
|
||||
func (d *database) DeleteFrom(table Table) deleteWithTable {
|
||||
return deleteStatus{scope: scope{Database: d, Tables: []Table{table}}}
|
||||
}
|
||||
|
||||
func (s deleteStatus) Where(conditions ...BooleanExpression) deleteWithWhere {
|
||||
s.where = And(conditions...)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s deleteStatus) OrderBy(orderBys ...OrderBy) deleteWithOrder {
|
||||
s.orderBys = orderBys
|
||||
return s
|
||||
}
|
||||
|
||||
func (s deleteStatus) Limit(limit int) deleteWithLimit {
|
||||
s.limit = &limit
|
||||
return s
|
||||
}
|
||||
|
||||
func (s deleteStatus) GetSQL() (string, error) {
|
||||
var sb strings.Builder
|
||||
sb.Grow(128)
|
||||
|
||||
sb.WriteString("DELETE FROM ")
|
||||
sb.WriteString(s.scope.Tables[0].GetSQL(s.scope))
|
||||
sb.WriteString(" WHERE ")
|
||||
whereSql, err := s.where.GetSQL(s.scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString(whereSql)
|
||||
|
||||
if len(s.orderBys) > 0 {
|
||||
orderBySql, err := commaOrderBys(s.scope, s.orderBys)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString(" ORDER BY ")
|
||||
sb.WriteString(orderBySql)
|
||||
}
|
||||
|
||||
if s.limit != nil {
|
||||
sb.WriteString(" LIMIT ")
|
||||
sb.WriteString(strconv.Itoa(*s.limit))
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func (s deleteStatus) Execute() (sql.Result, error) {
|
||||
sqlString, err := s.GetSQL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.scope.Database.Execute(sqlString)
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package sqlingo
|
||||
|
||||
type dialect int
|
||||
|
||||
const (
|
||||
dialectUnknown dialect = iota
|
||||
dialectMySQL
|
||||
dialectSqlite3
|
||||
dialectPostgres
|
||||
dialectMSSQL
|
||||
|
||||
dialectCount
|
||||
)
|
||||
|
||||
type dialectArray [dialectCount]string
|
||||
|
||||
func getDialectFromDriverName(driverName string) dialect {
|
||||
switch driverName {
|
||||
case "mysql":
|
||||
return dialectMySQL
|
||||
case "sqlite3":
|
||||
return dialectSqlite3
|
||||
case "postgres":
|
||||
return dialectPostgres
|
||||
case "sqlserver", "mssql":
|
||||
return dialectMSSQL
|
||||
default:
|
||||
return dialectUnknown
|
||||
}
|
||||
}
|
@ -0,0 +1,646 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type priority uint8
|
||||
|
||||
// Expression is the interface of an SQL expression.
|
||||
type Expression interface {
|
||||
// get the SQL string
|
||||
GetSQL(scope scope) (string, error)
|
||||
getOperatorPriority() priority
|
||||
|
||||
// <> operator
|
||||
NotEquals(other interface{}) BooleanExpression
|
||||
// == operator
|
||||
Equals(other interface{}) BooleanExpression
|
||||
// < operator
|
||||
LessThan(other interface{}) BooleanExpression
|
||||
// <= operator
|
||||
LessThanOrEquals(other interface{}) BooleanExpression
|
||||
// > operator
|
||||
GreaterThan(other interface{}) BooleanExpression
|
||||
// >= operator
|
||||
GreaterThanOrEquals(other interface{}) BooleanExpression
|
||||
|
||||
IsNull() BooleanExpression
|
||||
IsNotNull() BooleanExpression
|
||||
In(values ...interface{}) BooleanExpression
|
||||
NotIn(values ...interface{}) BooleanExpression
|
||||
Between(min interface{}, max interface{}) BooleanExpression
|
||||
NotBetween(min interface{}, max interface{}) BooleanExpression
|
||||
Desc() OrderBy
|
||||
|
||||
As(alias string) Alias
|
||||
|
||||
IfNull(altValue interface{}) Expression
|
||||
}
|
||||
|
||||
// Alias is the interface of an table/column alias.
|
||||
type Alias interface {
|
||||
GetSQL(scope scope) (string, error)
|
||||
}
|
||||
|
||||
// BooleanExpression is the interface of an SQL expression with boolean value.
|
||||
type BooleanExpression interface {
|
||||
Expression
|
||||
And(other interface{}) BooleanExpression
|
||||
Or(other interface{}) BooleanExpression
|
||||
Xor(other interface{}) BooleanExpression
|
||||
Not() BooleanExpression
|
||||
}
|
||||
|
||||
// NumberExpression is the interface of an SQL expression with number value.
|
||||
type NumberExpression interface {
|
||||
Expression
|
||||
Add(other interface{}) NumberExpression
|
||||
Sub(other interface{}) NumberExpression
|
||||
Mul(other interface{}) NumberExpression
|
||||
Div(other interface{}) NumberExpression
|
||||
IntDiv(other interface{}) NumberExpression
|
||||
Mod(other interface{}) NumberExpression
|
||||
|
||||
Sum() NumberExpression
|
||||
Avg() NumberExpression
|
||||
Min() UnknownExpression
|
||||
Max() UnknownExpression
|
||||
}
|
||||
|
||||
// StringExpression is the interface of an SQL expression with string value.
|
||||
type StringExpression interface {
|
||||
Expression
|
||||
Min() UnknownExpression
|
||||
Max() UnknownExpression
|
||||
Like(other interface{}) BooleanExpression
|
||||
Contains(substring string) BooleanExpression
|
||||
Concat(other interface{}) StringExpression
|
||||
IfEmpty(altValue interface{}) StringExpression
|
||||
IsEmpty() BooleanExpression
|
||||
}
|
||||
|
||||
// UnknownExpression is the interface of an SQL expression with unknown value.
|
||||
type UnknownExpression interface {
|
||||
Expression
|
||||
And(other interface{}) BooleanExpression
|
||||
Or(other interface{}) BooleanExpression
|
||||
Xor(other interface{}) BooleanExpression
|
||||
Not() BooleanExpression
|
||||
Add(other interface{}) NumberExpression
|
||||
Sub(other interface{}) NumberExpression
|
||||
Mul(other interface{}) NumberExpression
|
||||
Div(other interface{}) NumberExpression
|
||||
IntDiv(other interface{}) NumberExpression
|
||||
Mod(other interface{}) NumberExpression
|
||||
|
||||
Sum() NumberExpression
|
||||
Avg() NumberExpression
|
||||
Min() UnknownExpression
|
||||
Max() UnknownExpression
|
||||
|
||||
Like(other interface{}) BooleanExpression
|
||||
Contains(substring string) BooleanExpression
|
||||
Concat(other interface{}) StringExpression
|
||||
IfEmpty(altValue interface{}) StringExpression
|
||||
IsEmpty() BooleanExpression
|
||||
}
|
||||
|
||||
type expression struct {
|
||||
sql string
|
||||
builder func(scope scope) (string, error)
|
||||
priority priority
|
||||
isTrue bool
|
||||
isFalse bool
|
||||
}
|
||||
|
||||
func (e expression) GetTable() Table {
|
||||
return nil
|
||||
}
|
||||
|
||||
type scope struct {
|
||||
Database *database
|
||||
Tables []Table
|
||||
lastJoin *join
|
||||
}
|
||||
|
||||
func staticExpression(sql string, priority priority) expression {
|
||||
return expression{
|
||||
sql: sql,
|
||||
priority: priority,
|
||||
}
|
||||
}
|
||||
|
||||
func trueExpression() expression {
|
||||
return expression{
|
||||
sql: "1",
|
||||
isTrue: true,
|
||||
}
|
||||
}
|
||||
|
||||
func falseExpression() expression {
|
||||
return expression{
|
||||
sql: "0",
|
||||
isFalse: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Raw create a raw SQL statement
|
||||
func Raw(sql string) UnknownExpression {
|
||||
return expression{
|
||||
sql: sql,
|
||||
priority: 99,
|
||||
}
|
||||
}
|
||||
|
||||
// And creates an expression with AND operator.
|
||||
func And(expressions ...BooleanExpression) (result BooleanExpression) {
|
||||
if len(expressions) == 0 {
|
||||
result = trueExpression()
|
||||
return
|
||||
}
|
||||
for _, condition := range expressions {
|
||||
if result == nil {
|
||||
result = condition
|
||||
} else {
|
||||
result = result.And(condition)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Or creates an expression with OR operator.
|
||||
func Or(expressions ...BooleanExpression) (result BooleanExpression) {
|
||||
if len(expressions) == 0 {
|
||||
result = falseExpression()
|
||||
return
|
||||
}
|
||||
for _, condition := range expressions {
|
||||
if result == nil {
|
||||
result = condition
|
||||
} else {
|
||||
result = result.Or(condition)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e expression) As(name string) Alias {
|
||||
return expression{builder: func(scope scope) (string, error) {
|
||||
expressionSql, err := e.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return expressionSql + " AS " + name, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func (e expression) IfNull(altValue interface{}) Expression {
|
||||
return Function("IFNULL", e, altValue)
|
||||
}
|
||||
|
||||
func (e expression) IfEmpty(altValue interface{}) StringExpression {
|
||||
return If(e.NotEquals(""), e, altValue)
|
||||
}
|
||||
|
||||
func (e expression) IsEmpty() BooleanExpression {
|
||||
return e.Equals("")
|
||||
}
|
||||
|
||||
func (e expression) GetSQL(scope scope) (string, error) {
|
||||
if e.sql != "" {
|
||||
return e.sql, nil
|
||||
}
|
||||
return e.builder(scope)
|
||||
}
|
||||
|
||||
func quoteIdentifier(identifier string) (result dialectArray) {
|
||||
for dialect := dialect(0); dialect < dialectCount; dialect++ {
|
||||
switch dialect {
|
||||
case dialectMySQL:
|
||||
result[dialect] = "`" + identifier + "`"
|
||||
case dialectMSSQL:
|
||||
result[dialect] = "[" + identifier + "]"
|
||||
default:
|
||||
result[dialect] = "\"" + identifier + "\""
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func quoteString(s string) string {
|
||||
bytes := []byte(s)
|
||||
buf := make([]byte, len(s)*2+2)
|
||||
buf[0] = '\''
|
||||
n := 1
|
||||
|
||||
for _, b := range bytes {
|
||||
switch b {
|
||||
case 0, '\n', '\r', '\\', '\'', '"', 0x1a:
|
||||
buf[n] = '\\'
|
||||
n++
|
||||
}
|
||||
buf[n] = b
|
||||
n++
|
||||
}
|
||||
buf[n] = '\''
|
||||
n++
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
func getSQL(scope scope, value interface{}) (sql string, priority priority, err error) {
|
||||
if value == nil {
|
||||
sql = "NULL"
|
||||
return
|
||||
}
|
||||
switch value.(type) {
|
||||
case int:
|
||||
sql = strconv.Itoa(value.(int))
|
||||
case string:
|
||||
sql = quoteString(value.(string))
|
||||
case Expression:
|
||||
sql, err = value.(Expression).GetSQL(scope)
|
||||
priority = value.(Expression).getOperatorPriority()
|
||||
case Assignment:
|
||||
sql, err = value.(Assignment).GetSQL(scope)
|
||||
case toSelectFinal:
|
||||
sql, err = value.(toSelectFinal).GetSQL()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sql = "(" + sql + ")"
|
||||
case toUpdateFinal:
|
||||
sql, err = value.(toUpdateFinal).GetSQL()
|
||||
case Table:
|
||||
sql = value.(Table).GetSQL(scope)
|
||||
case CaseExpression:
|
||||
sql, err = value.(CaseExpression).End().GetSQL(scope)
|
||||
default:
|
||||
v := reflect.ValueOf(value)
|
||||
sql, priority, err = getSQLFromReflectValue(scope, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getSQLFromReflectValue(scope scope, v reflect.Value) (sql string, priority priority, err error) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
// dereference pointers
|
||||
for {
|
||||
if v.IsNil() {
|
||||
sql = "NULL"
|
||||
return
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Ptr {
|
||||
break
|
||||
}
|
||||
}
|
||||
sql, priority, err = getSQL(scope, v.Interface())
|
||||
return
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
sql = "1"
|
||||
} else {
|
||||
sql = "0"
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
sql = strconv.FormatInt(v.Int(), 10)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
sql = strconv.FormatUint(v.Uint(), 10)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sql = strconv.FormatFloat(v.Float(), 'g', -1, 64)
|
||||
case reflect.String:
|
||||
sql = quoteString(v.String())
|
||||
case reflect.Array, reflect.Slice:
|
||||
length := v.Len()
|
||||
values := make([]interface{}, length)
|
||||
for i := 0; i < length; i++ {
|
||||
values[i] = v.Index(i).Interface()
|
||||
}
|
||||
sql, err = commaValues(scope, values)
|
||||
if err == nil {
|
||||
sql = "(" + sql + ")"
|
||||
}
|
||||
default:
|
||||
if vs, ok := v.Interface().(interface{ String() string }); ok {
|
||||
sql = quoteString(vs.String())
|
||||
} else {
|
||||
err = fmt.Errorf("invalid type %s", v.Kind().String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
1 INTERVAL
|
||||
2 BINARY, COLLATE
|
||||
3 !
|
||||
4 - (unary minus), ~ (unary bit inversion)
|
||||
5 ^
|
||||
6 *, /, DIV, %, MOD
|
||||
7 -, +
|
||||
8 <<, >>
|
||||
9 &
|
||||
10 |
|
||||
11 = (comparison), <=>, >=, >, <=, <, <>, !=, IS, LIKE, REGEXP, IN
|
||||
12 BETWEEN, CASE, WHEN, THEN, ELSE
|
||||
13 NOT
|
||||
14 AND, &&
|
||||
15 XOR
|
||||
16 OR, ||
|
||||
17 = (assignment), :=
|
||||
*/
|
||||
func (e expression) NotEquals(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation("<>", other, 11)
|
||||
}
|
||||
|
||||
func (e expression) Equals(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation("=", other, 11)
|
||||
}
|
||||
|
||||
func (e expression) LessThan(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation("<", other, 11)
|
||||
}
|
||||
|
||||
func (e expression) LessThanOrEquals(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation("<=", other, 11)
|
||||
}
|
||||
|
||||
func (e expression) GreaterThan(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation(">", other, 11)
|
||||
}
|
||||
|
||||
func (e expression) GreaterThanOrEquals(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation(">=", other, 11)
|
||||
}
|
||||
|
||||
func toBooleanExpression(value interface{}) BooleanExpression {
|
||||
e, ok := value.(expression)
|
||||
switch {
|
||||
case !ok:
|
||||
return nil
|
||||
case e.isTrue:
|
||||
return trueExpression()
|
||||
case e.isFalse:
|
||||
return falseExpression()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e expression) And(other interface{}) BooleanExpression {
|
||||
switch {
|
||||
case e.isFalse:
|
||||
return e
|
||||
case e.isTrue:
|
||||
if exp := toBooleanExpression(other); exp != nil {
|
||||
return exp
|
||||
}
|
||||
}
|
||||
return e.binaryOperation("AND", other, 14)
|
||||
}
|
||||
|
||||
func (e expression) Or(other interface{}) BooleanExpression {
|
||||
switch {
|
||||
case e.isTrue:
|
||||
return e
|
||||
case e.isFalse:
|
||||
if exp := toBooleanExpression(other); exp != nil {
|
||||
return exp
|
||||
}
|
||||
}
|
||||
return e.binaryOperation("OR", other, 16)
|
||||
}
|
||||
|
||||
func (e expression) Xor(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation("XOR", other, 15)
|
||||
}
|
||||
|
||||
func (e expression) Add(other interface{}) NumberExpression {
|
||||
return e.binaryOperation("+", other, 7)
|
||||
}
|
||||
|
||||
func (e expression) Sub(other interface{}) NumberExpression {
|
||||
return e.binaryOperation("-", other, 7)
|
||||
}
|
||||
|
||||
func (e expression) Mul(other interface{}) NumberExpression {
|
||||
return e.binaryOperation("*", other, 6)
|
||||
}
|
||||
|
||||
func (e expression) Div(other interface{}) NumberExpression {
|
||||
return e.binaryOperation("/", other, 6)
|
||||
}
|
||||
|
||||
func (e expression) IntDiv(other interface{}) NumberExpression {
|
||||
return e.binaryOperation("DIV", other, 6)
|
||||
}
|
||||
|
||||
func (e expression) Mod(other interface{}) NumberExpression {
|
||||
return e.binaryOperation("%", other, 6)
|
||||
}
|
||||
|
||||
func (e expression) Sum() NumberExpression {
|
||||
return function("SUM", e)
|
||||
}
|
||||
|
||||
func (e expression) Avg() NumberExpression {
|
||||
return function("AVG", e)
|
||||
}
|
||||
|
||||
func (e expression) Min() UnknownExpression {
|
||||
return function("MIN", e)
|
||||
}
|
||||
|
||||
func (e expression) Max() UnknownExpression {
|
||||
return function("MAX", e)
|
||||
}
|
||||
|
||||
func (e expression) Like(other interface{}) BooleanExpression {
|
||||
return e.binaryOperation("LIKE", other, 11)
|
||||
}
|
||||
|
||||
func (e expression) Concat(other interface{}) StringExpression {
|
||||
return Concat(e, other)
|
||||
}
|
||||
|
||||
func (e expression) Contains(substring string) BooleanExpression {
|
||||
return function("LOCATE", substring, e).GreaterThan(0)
|
||||
}
|
||||
|
||||
func (e expression) binaryOperation(operator string, value interface{}, priority priority) expression {
|
||||
return expression{builder: func(scope scope) (string, error) {
|
||||
leftSql, err := e.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
leftPriority := e.priority
|
||||
rightSql, rightPriority, err := getSQL(scope, value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if leftPriority > priority {
|
||||
leftSql = "(" + leftSql + ")"
|
||||
}
|
||||
if rightPriority >= priority {
|
||||
rightSql = "(" + rightSql + ")"
|
||||
}
|
||||
return leftSql + " " + operator + " " + rightSql, err
|
||||
}, priority: priority}
|
||||
}
|
||||
|
||||
func (e expression) prefixSuffixExpression(prefix string, suffix string, priority priority) expression {
|
||||
if e.sql != "" {
|
||||
return expression{
|
||||
sql: prefix + e.sql + suffix,
|
||||
priority: priority,
|
||||
}
|
||||
}
|
||||
return expression{builder: func(scope scope) (string, error) {
|
||||
exprSql, err := e.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return prefix + exprSql + suffix, nil
|
||||
}, priority: priority}
|
||||
}
|
||||
|
||||
func (e expression) IsNull() BooleanExpression {
|
||||
return e.prefixSuffixExpression("", " IS NULL", 11)
|
||||
}
|
||||
|
||||
func (e expression) Not() BooleanExpression {
|
||||
if e.isTrue {
|
||||
return falseExpression()
|
||||
}
|
||||
if e.isFalse {
|
||||
return trueExpression()
|
||||
}
|
||||
return e.prefixSuffixExpression("NOT ", "", 13)
|
||||
}
|
||||
|
||||
func (e expression) IsNotNull() BooleanExpression {
|
||||
return e.prefixSuffixExpression("", " IS NOT NULL", 11)
|
||||
}
|
||||
|
||||
func expandSliceValue(value reflect.Value) (result []interface{}) {
|
||||
result = make([]interface{}, 0, 16)
|
||||
kind := value.Kind()
|
||||
switch kind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
length := value.Len()
|
||||
for i := 0; i < length; i++ {
|
||||
result = append(result, expandSliceValue(value.Index(i))...)
|
||||
}
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
result = append(result, expandSliceValue(value.Elem())...)
|
||||
default:
|
||||
result = append(result, value.Interface())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func expandSliceValues(values []interface{}) (result []interface{}) {
|
||||
result = make([]interface{}, 0, 16)
|
||||
for _, v := range values {
|
||||
value := reflect.ValueOf(v)
|
||||
result = append(result, expandSliceValue(value)...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e expression) In(values ...interface{}) BooleanExpression {
|
||||
values = expandSliceValues(values)
|
||||
if len(values) == 0 {
|
||||
return falseExpression()
|
||||
}
|
||||
joiner := func(exprSql, valuesSql string) string { return exprSql + " IN (" + valuesSql + ")" }
|
||||
builder := e.getBuilder(e.Equals, joiner, values...)
|
||||
return expression{builder: builder, priority: 11}
|
||||
}
|
||||
|
||||
func (e expression) NotIn(values ...interface{}) BooleanExpression {
|
||||
values = expandSliceValues(values)
|
||||
if len(values) == 0 {
|
||||
return trueExpression()
|
||||
}
|
||||
joiner := func(exprSql, valuesSql string) string { return exprSql + " NOT IN (" + valuesSql + ")" }
|
||||
builder := e.getBuilder(e.NotEquals, joiner, values...)
|
||||
return expression{builder: builder, priority: 11}
|
||||
}
|
||||
|
||||
type joinerFunc = func(exprSql, valuesSql string) string
|
||||
type booleanFunc = func(other interface{}) BooleanExpression
|
||||
type builderFunc = func(scope scope) (string, error)
|
||||
|
||||
func (e expression) getBuilder(single booleanFunc, joiner joinerFunc, values ...interface{}) builderFunc {
|
||||
return func(scope scope) (string, error) {
|
||||
var valuesSql string
|
||||
var err error
|
||||
|
||||
if len(values) == 1 {
|
||||
value := values[0]
|
||||
if selectStatus, ok := value.(toSelectFinal); ok {
|
||||
// IN subquery
|
||||
valuesSql, err = selectStatus.GetSQL()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
// IN a single value
|
||||
return single(value).GetSQL(scope)
|
||||
}
|
||||
} else {
|
||||
// IN a list
|
||||
valuesSql, err = commaValues(scope, values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
exprSql, err := e.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return joiner(exprSql, valuesSql), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e expression) Between(min interface{}, max interface{}) BooleanExpression {
|
||||
return e.buildBetween(" BETWEEN ", min, max)
|
||||
}
|
||||
|
||||
func (e expression) NotBetween(min interface{}, max interface{}) BooleanExpression {
|
||||
return e.buildBetween(" NOT BETWEEN ", min, max)
|
||||
}
|
||||
|
||||
func (e expression) buildBetween(operator string, min interface{}, max interface{}) BooleanExpression {
|
||||
return expression{builder: func(scope scope) (string, error) {
|
||||
exprSql, err := e.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
minSql, _, err := getSQL(scope, min)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
maxSql, _, err := getSQL(scope, max)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return exprSql + operator + minSql + " AND " + maxSql, nil
|
||||
}, priority: 12}
|
||||
}
|
||||
|
||||
func (e expression) getOperatorPriority() priority {
|
||||
return e.priority
|
||||
}
|
||||
|
||||
func (e expression) Desc() OrderBy {
|
||||
return orderBy{by: e, desc: true}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package sqlingo
|
||||
|
||||
import "strings"
|
||||
|
||||
// Field is the interface of a generated field.
|
||||
type Field interface {
|
||||
Expression
|
||||
GetTable() Table
|
||||
}
|
||||
|
||||
// NumberField is the interface of a generated field of number type.
|
||||
type NumberField interface {
|
||||
NumberExpression
|
||||
GetTable() Table
|
||||
}
|
||||
|
||||
// BooleanField is the interface of a generated field of boolean type.
|
||||
type BooleanField interface {
|
||||
BooleanExpression
|
||||
GetTable() Table
|
||||
}
|
||||
|
||||
// StringField is the interface of a generated field of string type.
|
||||
type StringField interface {
|
||||
StringExpression
|
||||
GetTable() Table
|
||||
}
|
||||
|
||||
type actualField struct {
|
||||
expression
|
||||
table Table
|
||||
}
|
||||
|
||||
func (f actualField) GetTable() Table {
|
||||
return f.table
|
||||
}
|
||||
|
||||
func newField(table Table, fieldName string) actualField {
|
||||
tableName := table.GetName()
|
||||
tableNameSqlArray := quoteIdentifier(tableName)
|
||||
fieldNameSqlArray := quoteIdentifier(fieldName)
|
||||
|
||||
var fullFieldNameSqlArray dialectArray
|
||||
for dialect := dialect(0); dialect < dialectCount; dialect++ {
|
||||
fullFieldNameSqlArray[dialect] = tableNameSqlArray[dialect] + "." + fieldNameSqlArray[dialect]
|
||||
}
|
||||
|
||||
return actualField{
|
||||
expression: expression{
|
||||
builder: func(scope scope) (string, error) {
|
||||
dialect := dialectUnknown
|
||||
if scope.Database != nil {
|
||||
dialect = scope.Database.dialect
|
||||
}
|
||||
if len(scope.Tables) != 1 || scope.lastJoin != nil || scope.Tables[0].GetName() != tableName {
|
||||
return fullFieldNameSqlArray[dialect], nil
|
||||
}
|
||||
return fieldNameSqlArray[dialect], nil
|
||||
},
|
||||
},
|
||||
table: table,
|
||||
}
|
||||
}
|
||||
|
||||
// NewNumberField creates a reference to a number field. It should only be called from generated code.
|
||||
func NewNumberField(table Table, fieldName string) NumberField {
|
||||
return newField(table, fieldName)
|
||||
}
|
||||
|
||||
// NewBooleanField creates a reference to a boolean field. It should only be called from generated code.
|
||||
func NewBooleanField(table Table, fieldName string) BooleanField {
|
||||
return newField(table, fieldName)
|
||||
}
|
||||
|
||||
// NewStringField creates a reference to a string field. It should only be called from generated code.
|
||||
func NewStringField(table Table, fieldName string) StringField {
|
||||
return newField(table, fieldName)
|
||||
}
|
||||
|
||||
type fieldList []Field
|
||||
|
||||
func (fields fieldList) GetSQL(scope scope) (string, error) {
|
||||
isSingleTable := len(scope.Tables) == 1 && scope.lastJoin == nil
|
||||
var sb strings.Builder
|
||||
if len(fields) == 0 {
|
||||
for i, table := range scope.Tables {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
actualTable, ok := table.(actualTable)
|
||||
if ok {
|
||||
if isSingleTable {
|
||||
sb.WriteString(actualTable.GetFieldsSQL())
|
||||
} else {
|
||||
sb.WriteString(actualTable.GetFullFieldsSQL())
|
||||
}
|
||||
} else {
|
||||
sb.WriteByte('*')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fieldsSql, err := commaFields(scope, fields)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString(fieldsSql)
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package sqlingo
|
||||
|
||||
func function(name string, args ...interface{}) expression {
|
||||
return expression{builder: func(scope scope) (string, error) {
|
||||
valuesSql, err := commaValues(scope, args)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name + "(" + valuesSql + ")", nil
|
||||
}}
|
||||
}
|
||||
|
||||
// Function creates an expression of the call to specified function.
|
||||
func Function(name string, args ...interface{}) Expression {
|
||||
return function(name, args...)
|
||||
}
|
||||
|
||||
// Concat creates an expression of CONCAT function.
|
||||
func Concat(args ...interface{}) StringExpression {
|
||||
return function("CONCAT", args...)
|
||||
}
|
||||
|
||||
// Count creates an expression of COUNT aggregator.
|
||||
func Count(arg interface{}) NumberExpression {
|
||||
return function("COUNT", arg)
|
||||
}
|
||||
|
||||
// If creates an expression of IF function.
|
||||
func If(predicate Expression, trueValue interface{}, falseValue interface{}) (result UnknownExpression) {
|
||||
return function("IF", predicate, trueValue, falseValue)
|
||||
}
|
||||
|
||||
// Length creates an expression of LENGTH function.
|
||||
func Length(arg interface{}) NumberExpression {
|
||||
return function("LENGTH", arg)
|
||||
}
|
||||
|
||||
// Sum creates an expression of SUM aggregator.
|
||||
func Sum(arg interface{}) NumberExpression {
|
||||
return function("SUM", arg)
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package sqlingo
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WellKnownBinaryField is the interface of a generated field of binary geometry (WKB) type.
|
||||
type WellKnownBinaryField interface {
|
||||
WellKnownBinaryExpression
|
||||
GetTable() Table
|
||||
}
|
||||
|
||||
// WellKnownBinaryExpression is the interface of an SQL expression with binary geometry (WKB) value.
|
||||
type WellKnownBinaryExpression interface {
|
||||
Expression
|
||||
STAsText() StringExpression
|
||||
}
|
||||
|
||||
// WellKnownBinary is the type of geometry well-known binary (WKB) field.
|
||||
type WellKnownBinary []byte
|
||||
|
||||
// NewWellKnownBinaryField creates a reference to a geometry WKB field. It should only be called from generated code.
|
||||
func NewWellKnownBinaryField(table Table, fieldName string) WellKnownBinaryField {
|
||||
return newField(table, fieldName)
|
||||
}
|
||||
|
||||
func (e expression) STAsText() StringExpression {
|
||||
return function("ST_AsText", e)
|
||||
}
|
||||
|
||||
func STGeomFromText(text interface{}) WellKnownBinaryExpression {
|
||||
return function("ST_GeomFromText", text)
|
||||
}
|
||||
|
||||
func STGeomFromTextf(format string, a ...interface{}) WellKnownBinaryExpression {
|
||||
text := fmt.Sprintf(format, a...)
|
||||
return STGeomFromText(text)
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type insertStatus struct {
|
||||
method string
|
||||
scope scope
|
||||
fields []Field
|
||||
values []interface{}
|
||||
models []interface{}
|
||||
onDuplicateKeyUpdateAssignments []assignment
|
||||
}
|
||||
|
||||
type insertWithTable interface {
|
||||
Fields(fields ...Field) insertWithValues
|
||||
Values(values ...interface{}) insertWithValues
|
||||
Models(models ...interface{}) insertWithModels
|
||||
}
|
||||
|
||||
type insertWithValues interface {
|
||||
toInsertFinal
|
||||
Values(values ...interface{}) insertWithValues
|
||||
OnDuplicateKeyIgnore() toInsertFinal
|
||||
OnDuplicateKeyUpdate() insertWithOnDuplicateKeyUpdateBegin
|
||||
}
|
||||
|
||||
type insertWithModels interface {
|
||||
toInsertFinal
|
||||
Models(models ...interface{}) insertWithModels
|
||||
OnDuplicateKeyIgnore() toInsertFinal
|
||||
OnDuplicateKeyUpdate() insertWithOnDuplicateKeyUpdateBegin
|
||||
}
|
||||
|
||||
type insertWithOnDuplicateKeyUpdateBegin interface {
|
||||
Set(Field Field, value interface{}) insertWithOnDuplicateKeyUpdate
|
||||
SetIf(condition bool, Field Field, value interface{}) insertWithOnDuplicateKeyUpdate
|
||||
}
|
||||
|
||||
type insertWithOnDuplicateKeyUpdate interface {
|
||||
toInsertFinal
|
||||
Set(Field Field, value interface{}) insertWithOnDuplicateKeyUpdate
|
||||
SetIf(condition bool, Field Field, value interface{}) insertWithOnDuplicateKeyUpdate
|
||||
}
|
||||
|
||||
type toInsertFinal interface {
|
||||
GetSQL() (string, error)
|
||||
Execute() (result sql.Result, err error)
|
||||
}
|
||||
|
||||
func (d *database) InsertInto(table Table) insertWithTable {
|
||||
return insertStatus{method: "INSERT", scope: scope{Database: d, Tables: []Table{table}}}
|
||||
}
|
||||
|
||||
func (d *database) ReplaceInto(table Table) insertWithTable {
|
||||
return insertStatus{method: "REPLACE", scope: scope{Database: d, Tables: []Table{table}}}
|
||||
}
|
||||
|
||||
func (s insertStatus) Fields(fields ...Field) insertWithValues {
|
||||
s.fields = fields
|
||||
return s
|
||||
}
|
||||
|
||||
func (s insertStatus) Values(values ...interface{}) insertWithValues {
|
||||
s.values = append([]interface{}{}, s.values...)
|
||||
s.values = append(s.values, values)
|
||||
return s
|
||||
}
|
||||
|
||||
func addModel(models *[]Model, model interface{}) error {
|
||||
if model, ok := model.(Model); ok {
|
||||
*models = append(*models, model)
|
||||
return nil
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(model)
|
||||
switch value.Kind() {
|
||||
case reflect.Ptr:
|
||||
value = reflect.Indirect(value)
|
||||
return addModel(models, value.Interface())
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
elem := value.Index(i)
|
||||
addr := elem.Addr()
|
||||
inter := addr.Interface()
|
||||
if err := addModel(models, inter); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown model type (kind = %d)", value.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
func (s insertStatus) Models(models ...interface{}) insertWithModels {
|
||||
s.models = models
|
||||
return s
|
||||
}
|
||||
|
||||
func (s insertStatus) OnDuplicateKeyUpdate() insertWithOnDuplicateKeyUpdateBegin {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s insertStatus) SetIf(condition bool, field Field, value interface{}) insertWithOnDuplicateKeyUpdate {
|
||||
if condition {
|
||||
return s.Set(field, value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s insertStatus) Set(field Field, value interface{}) insertWithOnDuplicateKeyUpdate {
|
||||
s.onDuplicateKeyUpdateAssignments = append([]assignment{}, s.onDuplicateKeyUpdateAssignments...)
|
||||
s.onDuplicateKeyUpdateAssignments = append(s.onDuplicateKeyUpdateAssignments, assignment{
|
||||
field: field,
|
||||
value: value,
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s insertStatus) OnDuplicateKeyIgnore() toInsertFinal {
|
||||
firstField := s.scope.Tables[0].GetFields()[0]
|
||||
return s.OnDuplicateKeyUpdate().Set(firstField, firstField)
|
||||
}
|
||||
|
||||
func (s insertStatus) GetSQL() (string, error) {
|
||||
var fields []Field
|
||||
var values []interface{}
|
||||
if len(s.models) > 0 {
|
||||
models := make([]Model, 0, len(s.models))
|
||||
for _, model := range s.models {
|
||||
if err := addModel(&models, model); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
fields = models[0].GetTable().GetFields()
|
||||
for _, model := range models {
|
||||
if model.GetTable().GetName() != s.scope.Tables[0].GetName() {
|
||||
return "", errors.New("invalid table from model")
|
||||
}
|
||||
values = append(values, model.GetValues())
|
||||
}
|
||||
} else {
|
||||
if len(s.fields) == 0 {
|
||||
fields = s.scope.Tables[0].GetFields()
|
||||
} else {
|
||||
fields = s.fields
|
||||
}
|
||||
values = s.values
|
||||
}
|
||||
|
||||
tableSql := s.scope.Tables[0].GetSQL(s.scope)
|
||||
fieldsSql, err := commaFields(s.scope, fields)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
valuesSql, err := commaValues(s.scope, values)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sqlString := s.method + " INTO " + tableSql + " (" + fieldsSql + ") VALUES " + valuesSql
|
||||
if len(s.onDuplicateKeyUpdateAssignments) > 0 {
|
||||
assignmentsSql, err := commaAssignments(s.scope, s.onDuplicateKeyUpdateAssignments)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sqlString += " ON DUPLICATE KEY UPDATE " + assignmentsSql
|
||||
}
|
||||
|
||||
return sqlString, nil
|
||||
}
|
||||
|
||||
func (s insertStatus) Execute() (result sql.Result, err error) {
|
||||
sqlString, err := s.GetSQL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.scope.Database.Execute(sqlString)
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// InvokerFunc is the function type of the actual invoker. It should be called in an interceptor.
|
||||
type InvokerFunc = func(ctx context.Context, sql string) error
|
||||
|
||||
// InterceptorFunc is the function type of an interceptor. An interceptor should implement this function to fulfill it's purpose.
|
||||
type InterceptorFunc = func(ctx context.Context, sql string, invoker InvokerFunc) error
|
||||
|
||||
// TODO: add some common interceptors
|
After Width: | Height: | Size: 99 KiB |
@ -0,0 +1,22 @@
|
||||
package sqlingo
|
||||
|
||||
// OrderBy indicates the ORDER BY column and the status of descending order.
|
||||
type OrderBy interface {
|
||||
GetSQL(scope scope) (string, error)
|
||||
}
|
||||
|
||||
type orderBy struct {
|
||||
by Expression
|
||||
desc bool
|
||||
}
|
||||
|
||||
func (o orderBy) GetSQL(scope scope) (string, error) {
|
||||
sql, err := o.by.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if o.desc {
|
||||
sql += " DESC"
|
||||
}
|
||||
return sql, nil
|
||||
}
|
@ -0,0 +1,651 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type selectWithFields interface {
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
From(tables ...Table) selectWithTables
|
||||
}
|
||||
|
||||
type selectWithTables interface {
|
||||
toSelectJoin
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
toUnionSelect
|
||||
Where(conditions ...BooleanExpression) selectWithWhere
|
||||
GroupBy(expressions ...Expression) selectWithGroupBy
|
||||
OrderBy(orderBys ...OrderBy) selectWithOrder
|
||||
Limit(limit int) selectWithLimit
|
||||
}
|
||||
|
||||
type toSelectJoin interface {
|
||||
Join(table Table) selectWithJoin
|
||||
LeftJoin(table Table) selectWithJoin
|
||||
RightJoin(table Table) selectWithJoin
|
||||
}
|
||||
|
||||
type selectWithJoin interface {
|
||||
On(condition BooleanExpression) selectWithJoinOn
|
||||
}
|
||||
|
||||
type selectWithJoinOn interface {
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
toUnionSelect
|
||||
Where(conditions ...BooleanExpression) selectWithWhere
|
||||
GroupBy(expressions ...Expression) selectWithGroupBy
|
||||
OrderBy(orderBys ...OrderBy) selectWithOrder
|
||||
Limit(limit int) selectWithLimit
|
||||
}
|
||||
|
||||
type selectWithWhere interface {
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
toUnionSelect
|
||||
GroupBy(expressions ...Expression) selectWithGroupBy
|
||||
OrderBy(orderBys ...OrderBy) selectWithOrder
|
||||
Limit(limit int) selectWithLimit
|
||||
}
|
||||
|
||||
type selectWithGroupBy interface {
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
toUnionSelect
|
||||
Having(conditions ...BooleanExpression) selectWithGroupByHaving
|
||||
OrderBy(orderBys ...OrderBy) selectWithOrder
|
||||
Limit(limit int) selectWithLimit
|
||||
}
|
||||
|
||||
type selectWithGroupByHaving interface {
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
toUnionSelect
|
||||
OrderBy(orderBys ...OrderBy) selectWithOrder
|
||||
}
|
||||
|
||||
type selectWithOrder interface {
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
Limit(limit int) selectWithLimit
|
||||
}
|
||||
|
||||
type selectWithLimit interface {
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
Offset(offset int) selectWithOffset
|
||||
}
|
||||
|
||||
type selectWithOffset interface {
|
||||
toSelectWithLock
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
}
|
||||
|
||||
type toSelectWithLock interface {
|
||||
LockInShareMode() selectWithLock
|
||||
ForUpdate() selectWithLock
|
||||
}
|
||||
|
||||
type selectWithLock interface {
|
||||
toSelectWithContext
|
||||
toSelectFinal
|
||||
}
|
||||
|
||||
type toSelectWithContext interface {
|
||||
WithContext(ctx context.Context) toSelectFinal
|
||||
}
|
||||
|
||||
type toUnionSelect interface {
|
||||
UnionSelect(fields ...interface{}) selectWithFields
|
||||
UnionSelectFrom(tables ...Table) selectWithTables
|
||||
UnionSelectDistinct(fields ...interface{}) selectWithFields
|
||||
UnionAllSelect(fields ...interface{}) selectWithFields
|
||||
UnionAllSelectFrom(tables ...Table) selectWithTables
|
||||
UnionAllSelectDistinct(fields ...interface{}) selectWithFields
|
||||
}
|
||||
|
||||
type toSelectFinal interface {
|
||||
Exists() (bool, error)
|
||||
Count() (int, error)
|
||||
GetSQL() (string, error)
|
||||
FetchFirst(out ...interface{}) (bool, error)
|
||||
FetchExactlyOne(out ...interface{}) error
|
||||
FetchAll(dest ...interface{}) (rows int, err error)
|
||||
FetchCursor() (Cursor, error)
|
||||
}
|
||||
|
||||
type join struct {
|
||||
previous *join
|
||||
prefix string
|
||||
table Table
|
||||
on BooleanExpression
|
||||
}
|
||||
|
||||
type selectBase struct {
|
||||
scope scope
|
||||
distinct bool
|
||||
fields fieldList
|
||||
where BooleanExpression
|
||||
groupBys []Expression
|
||||
having BooleanExpression
|
||||
}
|
||||
|
||||
type selectStatus struct {
|
||||
base selectBase
|
||||
orderBys []OrderBy
|
||||
lastUnion *unionSelectStatus
|
||||
limit *int
|
||||
offset int
|
||||
ctx context.Context
|
||||
lock string
|
||||
}
|
||||
|
||||
type unionSelectStatus struct {
|
||||
base selectBase
|
||||
all bool
|
||||
previous *unionSelectStatus
|
||||
}
|
||||
|
||||
func (s *selectStatus) activeSelectBase() *selectBase {
|
||||
if s.lastUnion != nil {
|
||||
return &s.lastUnion.base
|
||||
}
|
||||
return &s.base
|
||||
}
|
||||
|
||||
func (s selectStatus) Join(table Table) selectWithJoin {
|
||||
return s.join("", table)
|
||||
}
|
||||
|
||||
func (s selectStatus) LeftJoin(table Table) selectWithJoin {
|
||||
return s.join("LEFT ", table)
|
||||
}
|
||||
|
||||
func (s selectStatus) RightJoin(table Table) selectWithJoin {
|
||||
return s.join("RIGHT ", table)
|
||||
}
|
||||
|
||||
func (s selectStatus) join(prefix string, table Table) selectWithJoin {
|
||||
base := s.activeSelectBase()
|
||||
base.scope.lastJoin = &join{
|
||||
previous: base.scope.lastJoin,
|
||||
prefix: prefix,
|
||||
table: table,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) On(condition BooleanExpression) selectWithJoinOn {
|
||||
base := s.activeSelectBase()
|
||||
join := *base.scope.lastJoin
|
||||
join.on = condition
|
||||
base.scope.lastJoin = &join
|
||||
return s
|
||||
}
|
||||
|
||||
func getFields(fields []interface{}) (result []Field) {
|
||||
fields = expandSliceValues(fields)
|
||||
result = make([]Field, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
switch field.(type) {
|
||||
case Field:
|
||||
result = append(result, field.(Field))
|
||||
case Table:
|
||||
result = append(result, field.(Table).GetFields()...)
|
||||
default:
|
||||
fieldCopy := field
|
||||
fieldExpression := expression{builder: func(scope scope) (string, error) {
|
||||
sql, _, err := getSQL(scope, fieldCopy)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sql, nil
|
||||
}}
|
||||
result = append(result, fieldExpression)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *database) Select(fields ...interface{}) selectWithFields {
|
||||
return selectStatus{
|
||||
base: selectBase{
|
||||
scope: scope{
|
||||
Database: d,
|
||||
},
|
||||
fields: getFields(fields),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s selectStatus) From(tables ...Table) selectWithTables {
|
||||
s.activeSelectBase().scope.Tables = tables
|
||||
return s
|
||||
}
|
||||
|
||||
func (d *database) SelectFrom(tables ...Table) selectWithTables {
|
||||
return selectStatus{
|
||||
base: selectBase{
|
||||
scope: scope{
|
||||
Database: d,
|
||||
Tables: tables,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *database) SelectDistinct(fields ...interface{}) selectWithFields {
|
||||
return selectStatus{
|
||||
base: selectBase{
|
||||
scope: scope{
|
||||
Database: d,
|
||||
},
|
||||
fields: getFields(fields),
|
||||
distinct: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s selectStatus) Where(conditions ...BooleanExpression) selectWithWhere {
|
||||
s.activeSelectBase().where = And(conditions...)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) GroupBy(expressions ...Expression) selectWithGroupBy {
|
||||
s.activeSelectBase().groupBys = expressions
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) Having(conditions ...BooleanExpression) selectWithGroupByHaving {
|
||||
s.activeSelectBase().having = And(conditions...)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) UnionSelect(fields ...interface{}) selectWithFields {
|
||||
return s.withUnionSelect(false, false, fields, nil)
|
||||
}
|
||||
|
||||
func (s selectStatus) UnionSelectFrom(tables ...Table) selectWithTables {
|
||||
return s.withUnionSelect(false, false, nil, tables)
|
||||
}
|
||||
|
||||
func (s selectStatus) UnionSelectDistinct(fields ...interface{}) selectWithFields {
|
||||
return s.withUnionSelect(false, true, fields, nil)
|
||||
}
|
||||
|
||||
func (s selectStatus) UnionAllSelect(fields ...interface{}) selectWithFields {
|
||||
return s.withUnionSelect(true, false, fields, nil)
|
||||
}
|
||||
|
||||
func (s selectStatus) UnionAllSelectFrom(tables ...Table) selectWithTables {
|
||||
return s.withUnionSelect(true, false, nil, tables)
|
||||
}
|
||||
|
||||
func (s selectStatus) UnionAllSelectDistinct(fields ...interface{}) selectWithFields {
|
||||
return s.withUnionSelect(true, true, fields, nil)
|
||||
}
|
||||
|
||||
func (s selectStatus) withUnionSelect(all bool, distinct bool, fields []interface{}, tables []Table) selectStatus {
|
||||
s.lastUnion = &unionSelectStatus{
|
||||
base: selectBase{
|
||||
scope: scope{
|
||||
Database: s.base.scope.Database,
|
||||
Tables: tables,
|
||||
},
|
||||
distinct: distinct,
|
||||
fields: getFields(fields),
|
||||
},
|
||||
all: all,
|
||||
previous: s.lastUnion,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) OrderBy(orderBys ...OrderBy) selectWithOrder {
|
||||
s.orderBys = orderBys
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) Limit(limit int) selectWithLimit {
|
||||
s.limit = &limit
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) Offset(offset int) selectWithOffset {
|
||||
s.offset = offset
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) Count() (count int, err error) {
|
||||
if s.lastUnion == nil && len(s.base.groupBys) == 0 && s.limit == nil {
|
||||
if s.base.distinct {
|
||||
fields := s.base.fields
|
||||
s.base.distinct = false
|
||||
s.base.fields = []Field{expression{builder: func(scope scope) (string, error) {
|
||||
fieldsSql, err := fields.GetSQL(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "COUNT(DISTINCT " + fieldsSql + ")", nil
|
||||
}}}
|
||||
_, err = s.FetchFirst(&count)
|
||||
} else {
|
||||
s.base.fields = []Field{staticExpression("COUNT(1)", 0)}
|
||||
_, err = s.FetchFirst(&count)
|
||||
}
|
||||
} else {
|
||||
if !s.base.distinct {
|
||||
s.base.fields = []Field{staticExpression("1", 0)}
|
||||
}
|
||||
_, err = s.base.scope.Database.Select(Function("COUNT", 1)).
|
||||
From(s.asDerivedTable("t")).
|
||||
FetchFirst(&count)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s selectStatus) LockInShareMode() selectWithLock {
|
||||
s.lock = " LOCK IN SHARE MODE"
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) ForUpdate() selectWithLock {
|
||||
s.lock = " FOR UPDATE"
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) asDerivedTable(name string) Table {
|
||||
return derivedTable{
|
||||
name: name,
|
||||
selectStatus: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s selectStatus) Exists() (exists bool, err error) {
|
||||
_, err = s.base.scope.Database.Select(command("EXISTS", s)).FetchFirst(&exists)
|
||||
return
|
||||
}
|
||||
|
||||
func (s selectBase) buildSelectBase(sb *strings.Builder) error {
|
||||
sb.WriteString("SELECT ")
|
||||
if s.distinct {
|
||||
sb.WriteString("DISTINCT ")
|
||||
}
|
||||
|
||||
// find tables from fields if "From" is not specified
|
||||
if len(s.scope.Tables) == 0 && len(s.fields) > 0 {
|
||||
tableNames := make([]string, 0, len(s.fields))
|
||||
tableMap := make(map[string]Table)
|
||||
for _, field := range s.fields {
|
||||
table := field.GetTable()
|
||||
if table == nil {
|
||||
continue
|
||||
}
|
||||
tableName := table.GetName()
|
||||
if _, ok := tableMap[tableName]; !ok {
|
||||
tableMap[tableName] = table
|
||||
tableNames = append(tableNames, tableName)
|
||||
}
|
||||
}
|
||||
for _, tableName := range tableNames {
|
||||
table := tableMap[tableName]
|
||||
s.scope.Tables = append(s.scope.Tables, table)
|
||||
}
|
||||
}
|
||||
|
||||
fieldsSql, err := s.fields.GetSQL(s.scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.WriteString(fieldsSql)
|
||||
|
||||
if len(s.scope.Tables) > 0 {
|
||||
fromSql := commaTables(s.scope, s.scope.Tables)
|
||||
sb.WriteString(" FROM ")
|
||||
sb.WriteString(fromSql)
|
||||
}
|
||||
|
||||
if s.scope.lastJoin != nil {
|
||||
var joins []*join
|
||||
for j := s.scope.lastJoin; j != nil; j = j.previous {
|
||||
joins = append(joins, j)
|
||||
}
|
||||
for i := len(joins) - 1; i >= 0; i-- {
|
||||
join := joins[i]
|
||||
onSql, err := join.on.GetSQL(s.scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.WriteString(join.prefix)
|
||||
sb.WriteString(" JOIN ")
|
||||
sb.WriteString(join.table.GetSQL(s.scope))
|
||||
sb.WriteString(" ON ")
|
||||
sb.WriteString(onSql)
|
||||
}
|
||||
}
|
||||
|
||||
if s.where != nil {
|
||||
whereSql, err := s.where.GetSQL(s.scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.WriteString(" WHERE ")
|
||||
sb.WriteString(whereSql)
|
||||
}
|
||||
|
||||
if len(s.groupBys) != 0 {
|
||||
groupBySql, err := commaExpressions(s.scope, s.groupBys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.WriteString(" GROUP BY ")
|
||||
sb.WriteString(groupBySql)
|
||||
|
||||
if s.having != nil {
|
||||
havingSql, err := s.having.GetSQL(s.scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.WriteString(" HAVING ")
|
||||
sb.WriteString(havingSql)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s selectStatus) GetSQL() (string, error) {
|
||||
var sb strings.Builder
|
||||
sb.Grow(128)
|
||||
|
||||
if err := s.base.buildSelectBase(&sb); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var unions []*unionSelectStatus
|
||||
for union := s.lastUnion; union != nil; union = union.previous {
|
||||
unions = append(unions, union)
|
||||
}
|
||||
for i := len(unions) - 1; i >= 0; i-- {
|
||||
union := unions[i]
|
||||
if union.all {
|
||||
sb.WriteString(" UNION ALL ")
|
||||
} else {
|
||||
sb.WriteString(" UNION ")
|
||||
}
|
||||
if err := union.base.buildSelectBase(&sb); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.orderBys) > 0 {
|
||||
orderBySql, err := commaOrderBys(s.base.scope, s.orderBys)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString(" ORDER BY ")
|
||||
sb.WriteString(orderBySql)
|
||||
}
|
||||
|
||||
if s.limit != nil {
|
||||
sb.WriteString(" LIMIT ")
|
||||
sb.WriteString(strconv.Itoa(*s.limit))
|
||||
}
|
||||
|
||||
if s.offset != 0 {
|
||||
sb.WriteString(" OFFSET ")
|
||||
sb.WriteString(strconv.Itoa(s.offset))
|
||||
}
|
||||
|
||||
sb.WriteString(s.lock)
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func (s selectStatus) WithContext(ctx context.Context) toSelectFinal {
|
||||
s.ctx = ctx
|
||||
return s
|
||||
}
|
||||
|
||||
func (s selectStatus) FetchCursor() (Cursor, error) {
|
||||
sqlString, err := s.GetSQL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cursor, err := s.base.scope.Database.QueryContext(s.ctx, sqlString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cursor, nil
|
||||
}
|
||||
|
||||
func (s selectStatus) FetchFirst(dest ...interface{}) (ok bool, err error) {
|
||||
cursor, err := s.FetchCursor()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close()
|
||||
|
||||
for cursor.Next() {
|
||||
err = cursor.Scan(dest...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s selectStatus) FetchExactlyOne(dest ...interface{}) (err error) {
|
||||
cursor, err := s.FetchCursor()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close()
|
||||
|
||||
hasResult := false
|
||||
for cursor.Next() {
|
||||
if hasResult {
|
||||
return errors.New("more than one rows")
|
||||
}
|
||||
err = cursor.Scan(dest...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hasResult = true
|
||||
}
|
||||
if !hasResult {
|
||||
err = errors.New("no rows")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s selectStatus) fetchAllAsMap(cursor Cursor, mapType reflect.Type) (mapValue reflect.Value, err error) {
|
||||
mapValue = reflect.MakeMap(mapType)
|
||||
key := reflect.New(mapType.Key())
|
||||
elem := reflect.New(mapType.Elem())
|
||||
|
||||
for cursor.Next() {
|
||||
err = cursor.Scan(key.Interface(), elem.Interface())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mapValue.SetMapIndex(reflect.Indirect(key), reflect.Indirect(elem))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s selectStatus) FetchAll(dest ...interface{}) (rows int, err error) {
|
||||
cursor, err := s.FetchCursor()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer cursor.Close()
|
||||
|
||||
count := len(dest)
|
||||
values := make([]reflect.Value, count)
|
||||
for i, item := range dest {
|
||||
if reflect.ValueOf(item).Kind() != reflect.Ptr {
|
||||
err = errors.New("dest should be a pointer")
|
||||
return
|
||||
}
|
||||
val := reflect.Indirect(reflect.ValueOf(item))
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Slice:
|
||||
values[i] = val
|
||||
case reflect.Map:
|
||||
if len(dest) != 1 {
|
||||
err = errors.New("dest map should be 1 element")
|
||||
return
|
||||
}
|
||||
var mapValue reflect.Value
|
||||
mapValue, err = s.fetchAllAsMap(cursor, val.Type())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
reflect.ValueOf(item).Elem().Set(mapValue)
|
||||
return
|
||||
default:
|
||||
err = errors.New("dest should be pointed to a slice")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
elements := make([]reflect.Value, count)
|
||||
pointers := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
elements[i] = reflect.New(values[i].Type().Elem())
|
||||
pointers[i] = elements[i].Interface()
|
||||
}
|
||||
for cursor.Next() {
|
||||
err = cursor.Scan(pointers...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
values[i].Set(reflect.Append(values[i], reflect.Indirect(elements[i])))
|
||||
}
|
||||
rows++
|
||||
}
|
||||
return
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package sqlingo
|
||||
|
||||
// Table is the interface of a generated table.
|
||||
type Table interface {
|
||||
GetName() string
|
||||
GetSQL(scope scope) string
|
||||
GetFields() []Field
|
||||
}
|
||||
|
||||
type actualTable interface {
|
||||
Table
|
||||
GetFieldsSQL() string
|
||||
GetFullFieldsSQL() string
|
||||
}
|
||||
|
||||
type table struct {
|
||||
Table
|
||||
name string
|
||||
sqlDialects dialectArray
|
||||
}
|
||||
|
||||
func (t table) GetName() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t table) GetSQL(scope scope) string {
|
||||
return t.sqlDialects[scope.Database.dialect]
|
||||
}
|
||||
|
||||
func (t table) getOperatorPriority() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// NewTable creates a reference to a table. It should only be called from generated code.
|
||||
func NewTable(name string) Table {
|
||||
return table{name: name, sqlDialects: quoteIdentifier(name)}
|
||||
}
|
||||
|
||||
type derivedTable struct {
|
||||
name string
|
||||
selectStatus selectStatus
|
||||
}
|
||||
|
||||
func (t derivedTable) GetName() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t derivedTable) GetSQL(scope scope) string {
|
||||
sql, _ := t.selectStatus.GetSQL()
|
||||
return "(" + sql + ") AS " + t.name
|
||||
}
|
||||
|
||||
func (t derivedTable) GetFields() []Field {
|
||||
return t.selectStatus.activeSelectBase().fields
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Transaction is the interface of a transaction with underlying sql.Tx object.
|
||||
type Transaction interface {
|
||||
GetDB() *sql.DB
|
||||
GetTx() *sql.Tx
|
||||
Query(sql string) (Cursor, error)
|
||||
Execute(sql string) (sql.Result, error)
|
||||
|
||||
Select(fields ...interface{}) selectWithFields
|
||||
SelectDistinct(fields ...interface{}) selectWithFields
|
||||
SelectFrom(tables ...Table) selectWithTables
|
||||
InsertInto(table Table) insertWithTable
|
||||
Update(table Table) updateWithSet
|
||||
DeleteFrom(table Table) deleteWithTable
|
||||
}
|
||||
|
||||
func (d *database) GetTx() *sql.Tx {
|
||||
return d.tx
|
||||
}
|
||||
|
||||
func (d *database) BeginTx(ctx context.Context, opts *sql.TxOptions, f func(tx Transaction) error) error {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
tx, err := d.db.BeginTx(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isCommitted := false
|
||||
defer func() {
|
||||
if !isCommitted {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
if f != nil {
|
||||
db := *d
|
||||
db.tx = tx
|
||||
err = f(&db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isCommitted = true
|
||||
return nil
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type updateStatus struct {
|
||||
scope scope
|
||||
assignments []assignment
|
||||
where BooleanExpression
|
||||
orderBys []OrderBy
|
||||
limit *int
|
||||
}
|
||||
|
||||
func (d *database) Update(table Table) updateWithSet {
|
||||
return updateStatus{scope: scope{Database: d, Tables: []Table{table}}}
|
||||
}
|
||||
|
||||
type updateWithSet interface {
|
||||
Set(Field Field, value interface{}) updateWithSet
|
||||
SetIf(prerequisite bool, Field Field, value interface{}) updateWithSet
|
||||
Where(conditions ...BooleanExpression) updateWithWhere
|
||||
OrderBy(orderBys ...OrderBy) updateWithOrder
|
||||
Limit(limit int) updateWithLimit
|
||||
}
|
||||
|
||||
type updateWithWhere interface {
|
||||
toUpdateFinal
|
||||
OrderBy(orderBys ...OrderBy) updateWithOrder
|
||||
Limit(limit int) updateWithLimit
|
||||
}
|
||||
|
||||
type updateWithOrder interface {
|
||||
toUpdateFinal
|
||||
Limit(limit int) updateWithLimit
|
||||
}
|
||||
|
||||
type updateWithLimit interface {
|
||||
toUpdateFinal
|
||||
}
|
||||
|
||||
type toUpdateFinal interface {
|
||||
GetSQL() (string, error)
|
||||
Execute() (sql.Result, error)
|
||||
}
|
||||
|
||||
func (s updateStatus) Set(field Field, value interface{}) updateWithSet {
|
||||
s.assignments = append([]assignment{}, s.assignments...)
|
||||
s.assignments = append(s.assignments, assignment{
|
||||
field: field,
|
||||
value: value,
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s updateStatus) SetIf(prerequisite bool, field Field, value interface{}) updateWithSet {
|
||||
if prerequisite {
|
||||
return s.Set(field, value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s updateStatus) Where(conditions ...BooleanExpression) updateWithWhere {
|
||||
s.where = And(conditions...)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s updateStatus) OrderBy(orderBys ...OrderBy) updateWithOrder {
|
||||
s.orderBys = orderBys
|
||||
return s
|
||||
}
|
||||
|
||||
func (s updateStatus) Limit(limit int) updateWithLimit {
|
||||
s.limit = &limit
|
||||
return s
|
||||
}
|
||||
|
||||
func (s updateStatus) GetSQL() (string, error) {
|
||||
if len(s.assignments) == 0 {
|
||||
return "/* UPDATE without SET clause */ DO 0", nil
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.Grow(128)
|
||||
|
||||
sb.WriteString("UPDATE ")
|
||||
sb.WriteString(s.scope.Tables[0].GetSQL(s.scope))
|
||||
|
||||
assignmentsSql, err := commaAssignments(s.scope, s.assignments)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString(" SET ")
|
||||
sb.WriteString(assignmentsSql)
|
||||
|
||||
if s.where != nil {
|
||||
whereSql, err := s.where.GetSQL(s.scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString(" WHERE ")
|
||||
sb.WriteString(whereSql)
|
||||
}
|
||||
|
||||
if len(s.orderBys) > 0 {
|
||||
orderBySql, err := commaOrderBys(s.scope, s.orderBys)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sb.WriteString(" ORDER BY ")
|
||||
sb.WriteString(orderBySql)
|
||||
}
|
||||
|
||||
if s.limit != nil {
|
||||
sb.WriteString(" LIMIT ")
|
||||
sb.WriteString(strconv.Itoa(*s.limit))
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func (s updateStatus) Execute() (sql.Result, error) {
|
||||
sqlString, err := s.GetSQL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.scope.Database.Execute(sqlString)
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package sqlingo
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
maxInt = 1<<(strconv.IntSize-1) - 1
|
||||
minInt = -1 << (strconv.IntSize - 1)
|
||||
maxUint = 1<<strconv.IntSize - 1
|
||||
)
|
||||
|
||||
type value struct {
|
||||
stringValue *string
|
||||
}
|
||||
|
||||
func (v value) Int64() int64 {
|
||||
if v.stringValue == nil {
|
||||
return 0
|
||||
}
|
||||
// TODO: check BIT(1)
|
||||
if result, err := strconv.ParseInt(*v.stringValue, 10, 64); err == nil {
|
||||
return result
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Uint64() uint64 {
|
||||
if v.stringValue == nil {
|
||||
return 0
|
||||
}
|
||||
// TODO: check BIT(1)
|
||||
if result, err := strconv.ParseUint(*v.stringValue, 10, 64); err == nil {
|
||||
return result
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Int() int {
|
||||
if r := v.Int64(); r >= minInt && r <= maxInt {
|
||||
return int(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Int8() int8 {
|
||||
if r := v.Int64(); r >= math.MinInt8 && r <= math.MaxInt8 {
|
||||
return int8(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Int16() int16 {
|
||||
if r := v.Int64(); r >= math.MinInt16 && r <= math.MaxInt16 {
|
||||
return int16(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Int32() int32 {
|
||||
if r := v.Int64(); r >= math.MinInt32 && r <= math.MaxInt32 {
|
||||
return int32(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Uint() uint {
|
||||
if r := v.Uint64(); r <= maxUint {
|
||||
return uint(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Uint8() uint8 {
|
||||
if r := v.Uint64(); r <= math.MaxUint8 {
|
||||
return uint8(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Uint16() uint16 {
|
||||
if r := v.Uint64(); r <= math.MaxUint16 {
|
||||
return uint16(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Uint32() uint32 {
|
||||
if r := v.Uint64(); r <= math.MaxUint32 {
|
||||
return uint32(r)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v value) Bool() bool {
|
||||
if v.stringValue == nil {
|
||||
return false
|
||||
}
|
||||
switch *v.stringValue {
|
||||
case "", "0", "\x00":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (v value) String() string {
|
||||
if v.stringValue == nil {
|
||||
return ""
|
||||
}
|
||||
return *v.stringValue
|
||||
}
|
||||
|
||||
func (v value) IsNull() bool {
|
||||
return v.stringValue == nil
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- ./go.test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,48 @@
|
||||
# go-colorable
|
||||
|
||||
[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable)
|
||||
[![Codecov](https://codecov.io/gh/mattn/go-colorable/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-colorable)
|
||||
[![GoDoc](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
|
||||
[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)
|
||||
|
||||
Colorable writer for windows.
|
||||
|
||||
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
|
||||
This package is possible to handle escape sequence for ansi color on windows.
|
||||
|
||||
## Too Bad!
|
||||
|
||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
|
||||
|
||||
|
||||
## So Good!
|
||||
|
||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
|
||||
logrus.SetOutput(colorable.NewColorableStdout())
|
||||
|
||||
logrus.Info("succeeded")
|
||||
logrus.Warn("not correct")
|
||||
logrus.Error("something error")
|
||||
logrus.Fatal("panic")
|
||||
```
|
||||
|
||||
You can compile above code on non-windows OSs.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-colorable
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
@ -0,0 +1,37 @@
|
||||
// +build appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable returns new instance of Writer which handles escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
|
||||
// EnableColorsStdout enable colors if possible.
|
||||
func EnableColorsStdout(enabled *bool) func() {
|
||||
if enabled != nil {
|
||||
*enabled = true
|
||||
}
|
||||
return func() {}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// +build !windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable returns new instance of Writer which handles escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
|
||||
// EnableColorsStdout enable colors if possible.
|
||||
func EnableColorsStdout(enabled *bool) func() {
|
||||
if enabled != nil {
|
||||
*enabled = true
|
||||
}
|
||||
return func() {}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,58 @@
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// NonColorable holds writer but removes escape sequence.
|
||||
type NonColorable struct {
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// NewNonColorable returns new instance of Writer which removes escape sequence from Writer.
|
||||
func NewNonColorable(w io.Writer) io.Writer {
|
||||
return &NonColorable{out: w}
|
||||
}
|
||||
|
||||
// Write writes data on console
|
||||
func (w *NonColorable) Write(data []byte) (n int, err error) {
|
||||
er := bytes.NewReader(data)
|
||||
var bw [1]byte
|
||||
loop:
|
||||
for {
|
||||
c1, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c1 != 0x1b {
|
||||
bw[0] = c1
|
||||
_, err = w.out.Write(bw[:])
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
continue
|
||||
}
|
||||
c2, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c2 != 0x5b {
|
||||
continue
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
c, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
|
||||
break
|
||||
}
|
||||
buf.Write([]byte(string(c)))
|
||||
}
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
coverage:
|
||||
status:
|
||||
project: off
|
||||
patch: off
|
@ -0,0 +1,33 @@
|
||||
language: go
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
|
||||
go:
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
brew update
|
||||
fi
|
||||
- go get github.com/smartystreets/goconvey
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -repotoken 3qJVUE0iQwqnCbmNcDsjYu1nh4J4KIFXx
|
||||
- go test -race -v . -tags ""
|
||||
- go test -race -v . -tags "libsqlite3"
|
||||
- go test -race -v . -tags "sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_preupdate_hook sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable sqlite_unlock_notify"
|
||||
- go test -race -v . -tags "sqlite_vacuum_full"
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@
|
||||
// +build sqlite_column_metadata
|
||||
|
||||
package sqlite3
|
||||
|
||||
/*
|
||||
#ifndef USE_LIBSQLITE3
|
||||
#cgo CFLAGS: -DSQLITE_ENABLE_COLUMN_METADATA
|
||||
#include <sqlite3-binding.h>
|
||||
#else
|
||||
#include <sqlite3.h>
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// ColumnTableName returns the table that is the origin of a particular result
|
||||
// column in a SELECT statement.
|
||||
//
|
||||
// See https://www.sqlite.org/c3ref/column_database_name.html
|
||||
func (s *SQLiteStmt) ColumnTableName(n int) string {
|
||||
return C.GoString(C.sqlite3_column_table_name(s.s, C.int(n)))
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build sqlite_json sqlite_json1 json1
|
||||
|
||||
package sqlite3
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DSQLITE_ENABLE_JSON1
|
||||
*/
|
||||
import "C"
|
@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
||||
*.bench
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Steve Francia
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,40 @@
|
||||
GOVERSION := $(shell go version | cut -d ' ' -f 3 | cut -d '.' -f 2)
|
||||
|
||||
.PHONY: check fmt lint test test-race vet test-cover-html help
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
check: test-race fmt vet lint ## Run tests and linters
|
||||
|
||||
test: ## Run tests
|
||||
go test ./...
|
||||
|
||||
test-race: ## Run tests with race detector
|
||||
go test -race ./...
|
||||
|
||||
fmt: ## Run gofmt linter
|
||||
ifeq "$(GOVERSION)" "12"
|
||||
@for d in `go list` ; do \
|
||||
if [ "`gofmt -l -s $$GOPATH/src/$$d | tee /dev/stderr`" ]; then \
|
||||
echo "^ improperly formatted go files" && echo && exit 1; \
|
||||
fi \
|
||||
done
|
||||
endif
|
||||
|
||||
lint: ## Run golint linter
|
||||
@for d in `go list` ; do \
|
||||
if [ "`golint $$d | tee /dev/stderr`" ]; then \
|
||||
echo "^ golint errors!" && echo && exit 1; \
|
||||
fi \
|
||||
done
|
||||
|
||||
vet: ## Run go vet linter
|
||||
@if [ "`go vet | tee /dev/stderr`" ]; then \
|
||||
echo "^ go vet errors!" && echo && exit 1; \
|
||||
fi
|
||||
|
||||
test-cover-html: ## Generate test coverage report
|
||||
go test -coverprofile=coverage.out -covermode=count
|
||||
go tool cover -func=coverage.out
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@ -0,0 +1,176 @@
|
||||
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package cast provides easy and safe casting in Go.
|
||||
package cast
|
||||
|
||||
import "time"
|
||||
|
||||
// ToBool casts an interface to a bool type.
|
||||
func ToBool(i interface{}) bool {
|
||||
v, _ := ToBoolE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToTime casts an interface to a time.Time type.
|
||||
func ToTime(i interface{}) time.Time {
|
||||
v, _ := ToTimeE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
func ToTimeInDefaultLocation(i interface{}, location *time.Location) time.Time {
|
||||
v, _ := ToTimeInDefaultLocationE(i, location)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToDuration casts an interface to a time.Duration type.
|
||||
func ToDuration(i interface{}) time.Duration {
|
||||
v, _ := ToDurationE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToFloat64 casts an interface to a float64 type.
|
||||
func ToFloat64(i interface{}) float64 {
|
||||
v, _ := ToFloat64E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToFloat32 casts an interface to a float32 type.
|
||||
func ToFloat32(i interface{}) float32 {
|
||||
v, _ := ToFloat32E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToInt64 casts an interface to an int64 type.
|
||||
func ToInt64(i interface{}) int64 {
|
||||
v, _ := ToInt64E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToInt32 casts an interface to an int32 type.
|
||||
func ToInt32(i interface{}) int32 {
|
||||
v, _ := ToInt32E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToInt16 casts an interface to an int16 type.
|
||||
func ToInt16(i interface{}) int16 {
|
||||
v, _ := ToInt16E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToInt8 casts an interface to an int8 type.
|
||||
func ToInt8(i interface{}) int8 {
|
||||
v, _ := ToInt8E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToInt casts an interface to an int type.
|
||||
func ToInt(i interface{}) int {
|
||||
v, _ := ToIntE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToUint casts an interface to a uint type.
|
||||
func ToUint(i interface{}) uint {
|
||||
v, _ := ToUintE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToUint64 casts an interface to a uint64 type.
|
||||
func ToUint64(i interface{}) uint64 {
|
||||
v, _ := ToUint64E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToUint32 casts an interface to a uint32 type.
|
||||
func ToUint32(i interface{}) uint32 {
|
||||
v, _ := ToUint32E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToUint16 casts an interface to a uint16 type.
|
||||
func ToUint16(i interface{}) uint16 {
|
||||
v, _ := ToUint16E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToUint8 casts an interface to a uint8 type.
|
||||
func ToUint8(i interface{}) uint8 {
|
||||
v, _ := ToUint8E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToString casts an interface to a string type.
|
||||
func ToString(i interface{}) string {
|
||||
v, _ := ToStringE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToStringMapString casts an interface to a map[string]string type.
|
||||
func ToStringMapString(i interface{}) map[string]string {
|
||||
v, _ := ToStringMapStringE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToStringMapStringSlice casts an interface to a map[string][]string type.
|
||||
func ToStringMapStringSlice(i interface{}) map[string][]string {
|
||||
v, _ := ToStringMapStringSliceE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToStringMapBool casts an interface to a map[string]bool type.
|
||||
func ToStringMapBool(i interface{}) map[string]bool {
|
||||
v, _ := ToStringMapBoolE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToStringMapInt casts an interface to a map[string]int type.
|
||||
func ToStringMapInt(i interface{}) map[string]int {
|
||||
v, _ := ToStringMapIntE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToStringMapInt64 casts an interface to a map[string]int64 type.
|
||||
func ToStringMapInt64(i interface{}) map[string]int64 {
|
||||
v, _ := ToStringMapInt64E(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToStringMap casts an interface to a map[string]interface{} type.
|
||||
func ToStringMap(i interface{}) map[string]interface{} {
|
||||
v, _ := ToStringMapE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToSlice casts an interface to a []interface{} type.
|
||||
func ToSlice(i interface{}) []interface{} {
|
||||
v, _ := ToSliceE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToBoolSlice casts an interface to a []bool type.
|
||||
func ToBoolSlice(i interface{}) []bool {
|
||||
v, _ := ToBoolSliceE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToStringSlice casts an interface to a []string type.
|
||||
func ToStringSlice(i interface{}) []string {
|
||||
v, _ := ToStringSliceE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToIntSlice casts an interface to a []int type.
|
||||
func ToIntSlice(i interface{}) []int {
|
||||
v, _ := ToIntSliceE(i)
|
||||
return v
|
||||
}
|
||||
|
||||
// ToDurationSlice casts an interface to a []time.Duration type.
|
||||
func ToDurationSlice(i interface{}) []time.Duration {
|
||||
v, _ := ToDurationSliceE(i)
|
||||
return v
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
||||
// Code generated by "stringer -type timeFormatType"; DO NOT EDIT.
|
||||
|
||||
package cast
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[timeFormatNoTimezone-0]
|
||||
_ = x[timeFormatNamedTimezone-1]
|
||||
_ = x[timeFormatNumericTimezone-2]
|
||||
_ = x[timeFormatNumericAndNamedTimezone-3]
|
||||
_ = x[timeFormatTimeOnly-4]
|
||||
}
|
||||
|
||||
const _timeFormatType_name = "timeFormatNoTimezonetimeFormatNamedTimezonetimeFormatNumericTimezonetimeFormatNumericAndNamedTimezonetimeFormatTimeOnly"
|
||||
|
||||
var _timeFormatType_index = [...]uint8{0, 20, 43, 68, 101, 119}
|
||||
|
||||
func (i timeFormatType) String() string {
|
||||
if i < 0 || i >= timeFormatType(len(_timeFormatType_index)-1) {
|
||||
return "timeFormatType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _timeFormatType_name[_timeFormatType_index[i]:_timeFormatType_index[i+1]]
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Muhammad Muzzammil
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,71 @@
|
||||
#
|
||||
|
||||
![jsonc](.github/images/jsonc.png)
|
||||
|
||||
<p align="center">
|
||||
<i>JSON with comments for Go!</i> <br>
|
||||
<i><a href="https://github.com/muhammadmuzzammil1998/jsonc/actions/workflows/go.yml" target="_blank"><img src="https://github.com/muhammadmuzzammil1998/jsonc/actions/workflows/go.yml/badge.svg" alt="GitHub Actions"></a></i>
|
||||
</p>
|
||||
|
||||
JSONC is a superset of JSON which supports comments. JSON formatted files are readable to humans but the lack of comments decreases readability. With JSONC, you can use block (`/* */`) and single line (`//` of `#`) comments to describe the functionality. Microsoft VS Code also uses this format in their configuration files like `settings.json`, `keybindings.json`, `launch.json`, etc.
|
||||
|
||||
![jsonc](.github/images/carbon.png)
|
||||
|
||||
## What this package offers
|
||||
|
||||
**JSONC for Go** offers ability to convert and unmarshal JSONC to pure JSON. It also provides functionality to read JSONC file from disk and return JSONC and corresponding JSON encoding to operate on. However, it only provides a one way conversion. That is, you can not generate JSONC from JSON. Read [documentation](.github/DOCUMENTATION.md) for detailed examples.
|
||||
|
||||
## Usage
|
||||
|
||||
### `go get` it
|
||||
|
||||
Run `go get` command to install the package.
|
||||
|
||||
```sh
|
||||
$ go get muzzammil.xyz/jsonc
|
||||
```
|
||||
|
||||
### Import jsonc
|
||||
|
||||
Import `muzzammil.xyz/jsonc` to your source file.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"muzzammil.xyz/jsonc"
|
||||
)
|
||||
```
|
||||
|
||||
### Test it
|
||||
|
||||
Now test it!
|
||||
|
||||
```go
|
||||
func main() {
|
||||
j := []byte(`{"foo": /*comment*/ "bar"}`)
|
||||
jc := jsonc.ToJSON(j) // Calling jsonc.ToJSON() to convert JSONC to JSON
|
||||
if jsonc.Valid(jc) {
|
||||
fmt.Println(string(jc))
|
||||
} else {
|
||||
fmt.Println("Invalid JSONC")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
$ go run app.go
|
||||
{"foo":"bar"}
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome but kindly follow the Code of Conduct and guidelines. Please don't make Pull Requests for typographical errors, grammatical mistakes, "sane way" of doing it, etc. Open an issue for it. Thanks!
|
||||
|
||||
### Contributors
|
||||
|
||||
<a href="https://github.com/muhammadmuzzammil1998/jsonc/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=muhammadmuzzammil1998/jsonc" />
|
||||
</a>
|
@ -0,0 +1,57 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Muhammad Muzzammil
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package jsonc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// ToJSON returns JSON equivalent of JSON with comments
|
||||
func ToJSON(b []byte) []byte {
|
||||
return translate(b)
|
||||
}
|
||||
|
||||
// ReadFromFile reads jsonc file and returns JSONC and JSON encodings
|
||||
func ReadFromFile(filename string) ([]byte, []byte, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
jc := data
|
||||
j := translate(jc)
|
||||
return jc, j, nil
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSONC-encoded data and stores the result in the value pointed to by v.
|
||||
// Equivalent of calling `json.Unmarshal(jsonc.ToJSON(data), v)`
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
j := translate(data)
|
||||
return json.Unmarshal(j, v)
|
||||
}
|
||||
|
||||
// Valid reports whether data is a valid JSONC encoding or not
|
||||
func Valid(data []byte) bool {
|
||||
j := translate(data)
|
||||
return json.Valid(j)
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Muhammad Muzzammil
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package jsonc
|
||||
|
||||
const (
|
||||
ESCAPE = 92
|
||||
QUOTE = 34
|
||||
SPACE = 32
|
||||
TAB = 9
|
||||
NEWLINE = 10
|
||||
ASTERISK = 42
|
||||
SLASH = 47
|
||||
HASH = 35
|
||||
)
|
||||
|
||||
func translate(s []byte) []byte {
|
||||
var (
|
||||
i int
|
||||
quote bool
|
||||
escaped bool
|
||||
)
|
||||
j := make([]byte, len(s))
|
||||
comment := &commentData{}
|
||||
for _, ch := range s {
|
||||
if ch == ESCAPE || escaped {
|
||||
if !comment.startted {
|
||||
j[i] = ch
|
||||
i++
|
||||
}
|
||||
escaped = !escaped
|
||||
continue
|
||||
}
|
||||
if ch == QUOTE {
|
||||
quote = !quote
|
||||
}
|
||||
if (ch == SPACE || ch == TAB) && !quote {
|
||||
continue
|
||||
}
|
||||
if ch == NEWLINE {
|
||||
if comment.isSingleLined {
|
||||
comment.stop()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if quote && !comment.startted {
|
||||
j[i] = ch
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if comment.startted {
|
||||
if ch == ASTERISK && !comment.isSingleLined {
|
||||
comment.canEnd = true
|
||||
continue
|
||||
}
|
||||
if comment.canEnd && ch == SLASH && !comment.isSingleLined {
|
||||
comment.stop()
|
||||
continue
|
||||
}
|
||||
comment.canEnd = false
|
||||
continue
|
||||
}
|
||||
if comment.canStart && (ch == ASTERISK || ch == SLASH) {
|
||||
comment.start(ch)
|
||||
continue
|
||||
}
|
||||
if ch == SLASH {
|
||||
comment.canStart = true
|
||||
continue
|
||||
}
|
||||
if ch == HASH {
|
||||
comment.start(ch)
|
||||
continue
|
||||
}
|
||||
j[i] = ch
|
||||
i++
|
||||
}
|
||||
return j[:i]
|
||||
}
|
||||
|
||||
type commentData struct {
|
||||
canStart bool
|
||||
canEnd bool
|
||||
startted bool
|
||||
isSingleLined bool
|
||||
endLine int
|
||||
}
|
||||
|
||||
func (c *commentData) stop() {
|
||||
c.startted = false
|
||||
c.canStart = false
|
||||
}
|
||||
|
||||
func (c *commentData) start(ch byte) {
|
||||
c.startted = true
|
||||
c.isSingleLined = ch == SLASH || ch == HASH
|
||||
}
|
Loading…
Reference in new issue