- update dorm

master
李光春 2 years ago
parent 1ffb1ee1d6
commit 8b4b8d3b59

@ -14,6 +14,7 @@ require (
github.com/beego/beego/v2 v2.0.4
github.com/bmizerany/pq v0.0.0-20131128184720-da2b95e392c1
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d
github.com/daodao97/fly v0.0.0-20220716071342-fd98e4b05d96
github.com/dgraph-io/ristretto v0.1.0
github.com/gin-gonic/gin v1.8.1
github.com/glebarez/sqlite v1.4.6
@ -31,8 +32,9 @@ require (
github.com/ks3sdklib/aws-sdk-go v1.1.6
github.com/lesismal/sqlw v0.0.0-20220710073239-bd797c43fef9
github.com/lib/pq v1.10.5
github.com/lqs/sqlingo v0.11.1
github.com/mailru/go-clickhouse/v2 v2.0.0
github.com/mattn/go-sqlite3 v1.14.13
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mitchellh/mapstructure v1.5.0
github.com/mvdan/xurls v1.1.0
github.com/natefinch/lumberjack v2.0.0+incompatible
@ -80,6 +82,7 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.17.3 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
@ -111,6 +114,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.8 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -123,6 +127,7 @@ require (
github.com/saracen/solidblock v0.0.0-20190426153529-45df20abab6f // indirect
github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
@ -158,5 +163,6 @@ require (
modernc.org/sqlite v1.17.3 // indirect
modernc.org/strutil v1.1.1 // indirect
modernc.org/zappy v1.0.3 // indirect
muzzammil.xyz/jsonc v1.0.0 // indirect
xorm.io/builder v0.3.12 // indirect
)

@ -98,6 +98,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daodao97/fly v0.0.0-20220716071342-fd98e4b05d96 h1:PBSD5k0Geakg2WvRkE1JsNpUx34nSEUSbBbqCXw/c5M=
github.com/daodao97/fly v0.0.0-20220716071342-fd98e4b05d96/go.mod h1:y04a64MRtXHe/AwdIkk1CJL/bBlCy1NdZqzv8GKygB8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -132,8 +134,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -459,6 +464,8 @@ github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lqs/sqlingo v0.11.1 h1:omkxWseD3kwr2nOI5SqB4NjHMbijD43R6Eni4GX2ekA=
github.com/lqs/sqlingo v0.11.1/go.mod h1:dD8dcJH3syaTROM/PM64tWgGa3Yzo8hUHh22+pI5WoQ=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/go-clickhouse/v2 v2.0.0 h1:O+ZGJDwp/E5W19ooeouEqaOlg+qxA+4Zsfjt63QcnVU=
github.com/mailru/go-clickhouse/v2 v2.0.0/go.mod h1:TwxN829KnFZ7jAka9l9EoCV+U0CBFq83SFev4oLbnNU=
@ -468,6 +475,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@ -480,8 +489,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@ -652,6 +661,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -1188,6 +1199,8 @@ modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
modernc.org/zappy v1.0.3 h1:Tr+P3kclDSrvC6zYBW2hWmOmu5SjG6PtvCt3RCjRmss=
modernc.org/zappy v1.0.3/go.mod h1:w/Akq8ipfols/xZJdR5IYiQNOqC80qz2mVvsEwEbkiI=
muzzammil.xyz/jsonc v1.0.0 h1:B6kaT3wHueZ87mPz3q1nFuM1BlL32IG0wcq0/uOsQ18=
muzzammil.xyz/jsonc v1.0.0/go.mod h1:rFv8tUUKe+QLh7v02BhfxXEf4ZHhYD7unR93HL/1Uvo=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

@ -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,72 @@
package fly
import (
"database/sql"
"fmt"
"sync"
"github.com/pkg/errors"
)
type Config struct {
DSN string
Driver string
MaxOpenConn int
MaxIdleConn int
}
var pool = sync.Map{}
func Init(conns map[string]*Config) error {
for conn, conf := range conns {
db, err := newDb(conf)
if err != nil {
return err
}
pool.Store(conn, db)
}
return nil
}
func Close() {
pool.Range(func(key, value interface{}) bool {
db := value.(*sql.DB)
_ = db.Close()
return true
})
}
func db(conn string) (*sql.DB, error) {
if _db, ok := pool.Load(conn); ok {
return _db.(*sql.DB), nil
}
return nil, errors.New("connection not found : " + conn)
}
func newDb(conf *Config) (*sql.DB, error) {
driver := conf.Driver
if driver == "" {
driver = "mysql"
}
db, err := sql.Open(driver, conf.DSN)
if err != nil {
return nil, fmt.Errorf("failed Connection database: %s", err)
}
// 设置数据库连接池最大连接数
MaxOpen := 100
if conf.MaxOpenConn != 0 {
MaxOpen = conf.MaxOpenConn
}
db.SetMaxOpenConns(MaxOpen)
// 连接池最大允许的空闲连接数
// 如果没有sql任务需要执行的连接数大于20超过的连接会被连接池关闭
MaxIdle := 20
if conf.MaxIdleConn != 0 {
MaxIdle = conf.MaxIdleConn
}
db.SetMaxIdleConns(MaxIdle)
return db, nil
}

@ -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

Binary file not shown.

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"

@ -2,22 +2,20 @@ go-sqlite3
==========
[![GoDoc Reference](https://godoc.org/github.com/mattn/go-sqlite3?status.svg)](http://godoc.org/github.com/mattn/go-sqlite3)
[![GitHub Actions](https://github.com/mattn/go-sqlite3/workflows/Go/badge.svg)](https://github.com/mattn/go-sqlite3/actions?query=workflow%3AGo)
[![Build Status](https://travis-ci.org/mattn/go-sqlite3.svg?branch=master)](https://travis-ci.org/mattn/go-sqlite3)
[![Financial Contributors on Open Collective](https://opencollective.com/mattn-go-sqlite3/all/badge.svg?label=financial+contributors)](https://opencollective.com/mattn-go-sqlite3)
[![codecov](https://codecov.io/gh/mattn/go-sqlite3/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-sqlite3)
[![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3)
Latest stable version is v1.14 or later, not v2.
~~**NOTE:** The increase to v2 was an accident. There were no major changes or features.~~
NOTE: v2.0.1 or higher is unfortunatal release. So there are no big changes. And does not provide v2 feature.
# Description
A sqlite3 driver that conforms to the built-in database/sql interface.
sqlite3 driver conforming to the built-in database/sql interface
Supported Golang version: See [.github/workflows/go.yaml](./.github/workflows/go.yaml).
Supported Golang version: See .travis.yml
This package follows the official [Golang Release Policy](https://golang.org/doc/devel/release.html#policy).
[This package follows the official Golang Release Policy.](https://golang.org/doc/devel/release.html#policy)
### Overview
@ -64,7 +62,7 @@ This package follows the official [Golang Release Policy](https://golang.org/doc
# Installation
This package can be installed with the `go get` command:
This package can be installed with the go get command:
go get github.com/mattn/go-sqlite3
@ -72,28 +70,28 @@ _go-sqlite3_ is *cgo* package.
If you want to build your app using go-sqlite3, you need gcc.
However, after you have built and installed _go-sqlite3_ with `go install github.com/mattn/go-sqlite3` (which requires gcc), you can build your app without relying on gcc in future.
***Important: because this is a `CGO` enabled package, you are required to set the environment variable `CGO_ENABLED=1` and have a `gcc` compile present within your path.***
***Important: because this is a `CGO` enabled package you are required to set the environment variable `CGO_ENABLED=1` and have a `gcc` compile present within your path.***
# API Reference
API documentation can be found [here](http://godoc.org/github.com/mattn/go-sqlite3).
API documentation can be found here: http://godoc.org/github.com/mattn/go-sqlite3
Examples can be found under the [examples](./_example) directory.
Examples can be found under the [examples](./_example) directory
# Connection String
When creating a new SQLite database or connection to an existing one, with the file name additional options can be given.
This is also known as a DSN (Data Source Name) string.
This is also known as a DSN string. (Data Source Name).
Options are append after the filename of the SQLite database.
The database filename and options are separated by an `?` (Question Mark).
The database filename and options are seperated by an `?` (Question Mark).
Options should be URL-encoded (see [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)).
This also applies when using an in-memory database instead of a file.
Options can be given using the following format: `KEYWORD=VALUE` and multiple options can be combined with the `&` ampersand.
This library supports DSN options of SQLite itself and provides additional options.
This library supports dsn options of SQLite itself and provides additional options.
Boolean values can be one of:
* `0` `no` `false` `off`
@ -125,8 +123,6 @@ Boolean values can be one of:
| Time Zone Location | `_loc` | auto | Specify location of time format. |
| Transaction Lock | `_txlock` | <ul><li>immediate</li><li>deferred</li><li>exclusive</li></ul> | Specify locking behavior for transactions. |
| Writable Schema | `_writable_schema` | `Boolean` | When this pragma is on, the SQLITE_MASTER tables in which database can be changed using ordinary UPDATE, INSERT, and DELETE statements. Warning: misuse of this pragma can easily result in a corrupt database file. |
| Cache Size | `_cache_size` | `int` | Maximum cache size; default is 2000K (2M). See [PRAGMA cache_size](https://sqlite.org/pragma.html#pragma_cache_size) |
## DSN Examples
@ -138,18 +134,19 @@ file:test.db?cache=shared&mode=memory
This package allows additional configuration of features available within SQLite3 to be enabled or disabled by golang build constraints also known as build `tags`.
Click [here](https://golang.org/pkg/go/build/#hdr-Build_Constraints) for more information about build tags / constraints.
[Click here for more information about build tags / constraints.](https://golang.org/pkg/go/build/#hdr-Build_Constraints)
### Usage
If you wish to build this library with additional extensions / features, use the following command:
If you wish to build this library with additional extensions / features.
Use the following command.
```bash
go build --tags "<FEATURE>"
```
For available features, see the extension list.
When using multiple build tags, all the different tags should be space delimited.
For available features see the extension list.
When using multiple build tags, all the different tags should be space delimted.
Example:
@ -180,9 +177,9 @@ go build --tags "icu json1 fts5 secure_delete"
# Compilation
This package requires the `CGO_ENABLED=1` ennvironment variable if not set by default, and the presence of the `gcc` compiler.
This package requires `CGO_ENABLED=1` ennvironment variable if not set by default, and the presence of the `gcc` compiler.
If you need to add additional CFLAGS or LDFLAGS to the build command, and do not want to modify this package, then this can be achieved by using the `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables.
If you need to add additional CFLAGS or LDFLAGS to the build command, and do not want to modify this package. Then this can be achieved by using the `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables.
## Android
@ -197,7 +194,7 @@ For more information see [#201](https://github.com/mattn/go-sqlite3/issues/201)
# ARM
To compile for `ARM` use the following environment:
To compile for `ARM` use the following environment.
```bash
env CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ \
@ -215,15 +212,9 @@ This library can be cross-compiled.
In some cases you are required to the `CC` environment variable with the cross compiler.
## Cross Compiling from MAC OSX
The simplest way to cross compile from OSX is to use [xgo](https://github.com/karalabe/xgo).
Steps:
- Install [xgo](https://github.com/karalabe/xgo) (`go get github.com/karalabe/xgo`).
- Ensure that your project is within your `GOPATH`.
- Run `xgo local/path/to/project`.
Please refer to the project's [README](https://github.com/karalabe/xgo/blob/master/README.md) for further information.
Additional information:
- [#491](https://github.com/mattn/go-sqlite3/issues/491)
- [#560](https://github.com/mattn/go-sqlite3/issues/560)
# Google Cloud Platform
@ -233,7 +224,7 @@ Please work only with compiled final binaries.
## Linux
To compile this package on Linux, you must install the development tools for your linux distribution.
To compile this package on Linux you must install the development tools for your linux distribution.
To compile under linux use the build tag `linux`.
@ -249,7 +240,7 @@ go build --tags "libsqlite3 linux"
### Alpine
When building in an `alpine` container run the following command before building:
When building in an `alpine` container run the following command before building.
```
apk add --update gcc musl-dev
@ -269,29 +260,29 @@ sudo apt-get install build-essential
## Mac OSX
OSX should have all the tools present to compile this package. If not, install XCode to add all the developers tools.
OSX should have all the tools present to compile this package, if not install XCode this will add all the developers tools.
Required dependency:
Required dependency
```bash
brew install sqlite3
```
For OSX, there is an additional package to install which is required if you wish to build the `icu` extension.
For OSX there is an additional package install which is required if you wish to build the `icu` extension.
This additional package can be installed with `homebrew`:
This additional package can be installed with `homebrew`.
```bash
brew upgrade icu4c
```
To compile for Mac OSX:
To compile for Mac OSX.
```bash
go build --tags "darwin"
```
If you wish to link directly to libsqlite3, use the `libsqlite3` build tag:
If you wish to link directly to libsqlite3 then you can use the `libsqlite3` build tag.
```
go build --tags "libsqlite3 darwin"
@ -303,14 +294,14 @@ Additional information:
## Windows
To compile this package on Windows, you must have the `gcc` compiler installed.
To compile this package on Windows OS you must have the `gcc` compiler installed.
1) Install a Windows `gcc` toolchain.
2) Add the `bin` folder to the Windows path, if the installer did not do this by default.
3) Open a terminal for the TDM-GCC toolchain, which can be found in the Windows Start menu.
2) Add the `bin` folders to the Windows path if the installer did not do this by default.
3) Open a terminal for the TDM-GCC toolchain, can be found in the Windows Start menu.
4) Navigate to your project folder and run the `go build ...` command for this package.
For example the TDM-GCC Toolchain can be found [here](https://jmeubank.github.io/tdm-gcc/).
For example the TDM-GCC Toolchain can be found [here](https://sourceforge.net/projects/tdm-gcc/).
## Errors
@ -348,28 +339,28 @@ This package supports the SQLite User Authentication module.
## Compile
To use the User authentication module, the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features).
To use the User authentication module the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features).
## Usage
### Create protected database
To create a database protected by user authentication, provide the following argument to the connection string `_auth`.
To create a database protected by user authentication provide the following argument to the connection string `_auth`.
This will enable user authentication within the database. This option however requires two additional arguments:
- `_auth_user`
- `_auth_pass`
When `_auth` is present in the connection string user authentication will be enabled and the provided user will be created
When `_auth` is present on the connection string user authentication will be enabled and the provided user will be created
as an `admin` user. After initial creation, the parameter `_auth` has no effect anymore and can be omitted from the connection string.
Example connection strings:
Example connection string:
Create an user authentication database with user `admin` and password `admin`:
Create an user authentication database with user `admin` and password `admin`.
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin`
Create an user authentication database with user `admin` and password `admin` and use `SHA1` for the password encoding:
Create an user authentication database with user `admin` and password `admin` and use `SHA1` for the password encoding.
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin&_auth_crypt=sha1`
@ -395,11 +386,11 @@ salt this can be configured with `_auth_salt`.
### Restrictions
Operations on the database regarding user management can only be preformed by an administrator user.
Operations on the database regarding to user management can only be preformed by an administrator user.
### Support
The user authentication supports two kinds of users:
The user authentication supports two kinds of users
- administrators
- regular users
@ -410,7 +401,7 @@ User management can be done by directly using the `*SQLiteConn` or by SQL.
#### SQL
The following sql functions are available for user management:
The following sql functions are available for user management.
| Function | Arguments | Description |
|----------|-----------|-------------|
@ -419,7 +410,7 @@ The following sql functions are available for user management:
| `auth_user_change` | username `string`, password `string`, admin `int` | Function to modify an user. Users can change their own password, but only an administrator can change the administrator flag. |
| `authUserDelete` | username `string` | Delete an user from the database. Can only be used by an administrator. The current logged in administrator cannot be deleted. This is to make sure their is always an administrator remaining. |
These functions will return an integer:
These functions will return an integer.
- 0 (SQLITE_OK)
- 23 (SQLITE_AUTH) Failed to perform due to authentication or insufficient privileges
@ -440,7 +431,7 @@ SELECT user_delete('user');
#### *SQLiteConn
The following functions are available for User authentication from the `*SQLiteConn`:
The following functions are available for User authentication from the `*SQLiteConn`.
| Function | Description |
|----------|-------------|
@ -451,26 +442,16 @@ The following functions are available for User authentication from the `*SQLiteC
### Attached database
When using attached databases, SQLite will use the authentication from the `main` database for the attached database(s).
When using attached databases. SQLite will use the authentication from the `main` database for the attached database(s).
# Extensions
If you want your own extension to be listed here, or you want to add a reference to an extension; please submit an Issue for this.
If you want your own extension to be listed here or you want to add a reference to an extension; please submit an Issue for this.
## Spatialite
Spatialite is available as an extension to SQLite, and can be used in combination with this repository.
For an example, see [shaxbee/go-spatialite](https://github.com/shaxbee/go-spatialite).
## extension-functions.c from SQLite3 Contrib
extension-functions.c is available as an extension to SQLite, and provides the following functions:
- Math: acos, asin, atan, atn2, atan2, acosh, asinh, atanh, difference, degrees, radians, cos, sin, tan, cot, cosh, sinh, tanh, coth, exp, log, log10, power, sign, sqrt, square, ceil, floor, pi.
- String: replicate, charindex, leftstr, rightstr, ltrim, rtrim, trim, replace, reverse, proper, padl, padr, padc, strfilter.
- Aggregate: stdev, variance, mode, median, lower_quartile, upper_quartile
For an example, see [dinedal/go-sqlite3-extension-functions](https://github.com/dinedal/go-sqlite3-extension-functions).
For an example see [shaxbee/go-spatialite](https://github.com/shaxbee/go-spatialite).
# FAQ
@ -490,7 +471,7 @@ For an example, see [dinedal/go-sqlite3-extension-functions](https://github.com/
- Can I use this in multiple routines concurrently?
Yes for readonly. But not for writable. See [#50](https://github.com/mattn/go-sqlite3/issues/50), [#51](https://github.com/mattn/go-sqlite3/issues/51), [#209](https://github.com/mattn/go-sqlite3/issues/209), [#274](https://github.com/mattn/go-sqlite3/issues/274).
Yes for readonly. But, No for writable. See [#50](https://github.com/mattn/go-sqlite3/issues/50), [#51](https://github.com/mattn/go-sqlite3/issues/51), [#209](https://github.com/mattn/go-sqlite3/issues/209), [#274](https://github.com/mattn/go-sqlite3/issues/274).
- Why I'm getting `no such table` error?
@ -504,7 +485,7 @@ For an example, see [dinedal/go-sqlite3-extension-functions](https://github.com/
Note that if the last database connection in the pool closes, the in-memory database is deleted. Make sure the [max idle connection limit](https://golang.org/pkg/database/sql/#DB.SetMaxIdleConns) is > 0, and the [connection lifetime](https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime) is infinite.
For more information see:
For more information see
* [#204](https://github.com/mattn/go-sqlite3/issues/204)
* [#511](https://github.com/mattn/go-sqlite3/issues/511)
* https://www.sqlite.org/sharedcache.html#shared_cache_and_in_memory_databases
@ -514,20 +495,20 @@ For an example, see [dinedal/go-sqlite3-extension-functions](https://github.com/
OS X limits OS-wide to not have more than 1000 files open simultaneously by default.
For more information, see [#289](https://github.com/mattn/go-sqlite3/issues/289)
For more information see [#289](https://github.com/mattn/go-sqlite3/issues/289)
- Trying to execute a `.` (dot) command throws an error.
Error: `Error: near ".": syntax error`
Dot command are part of SQLite3 CLI, not of this library.
Dot command are part of SQLite3 CLI not of this library.
You need to implement the feature or call the sqlite3 cli.
More information see [#305](https://github.com/mattn/go-sqlite3/issues/305).
More information see [#305](https://github.com/mattn/go-sqlite3/issues/305)
- Error: `database is locked`
When you get a database is locked, please use the following options.
When you get a database is locked. Please use the following options.
Add to DSN: `cache=shared`
@ -536,24 +517,24 @@ For an example, see [dinedal/go-sqlite3-extension-functions](https://github.com/
db, err := sql.Open("sqlite3", "file:locked.sqlite?cache=shared")
```
Next, please set the database connections of the SQL package to 1:
Second please set the database connections of the SQL package to 1.
```go
db.SetMaxOpenConns(1)
```
For more information, see [#209](https://github.com/mattn/go-sqlite3/issues/209).
More information see [#209](https://github.com/mattn/go-sqlite3/issues/209)
## Contributors
### Code Contributors
This project exists thanks to all the people who [[contribute](CONTRIBUTING.md)].
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/mattn/go-sqlite3/graphs/contributors"><img src="https://opencollective.com/mattn-go-sqlite3/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute here](https://opencollective.com/mattn-go-sqlite3/contribute)].
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/mattn-go-sqlite3/contribute)]
#### Individuals

@ -7,7 +7,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif

@ -12,7 +12,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
@ -35,55 +35,56 @@ import (
//export callbackTrampoline
func callbackTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) {
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
fi := lookupHandle(C.sqlite3_user_data(ctx)).(*functionInfo)
fi := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*functionInfo)
fi.Call(ctx, args)
}
//export stepTrampoline
func stepTrampoline(ctx *C.sqlite3_context, argc C.int, argv **C.sqlite3_value) {
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:int(argc):int(argc)]
ai := lookupHandle(C.sqlite3_user_data(ctx)).(*aggInfo)
ai := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*aggInfo)
ai.Step(ctx, args)
}
//export doneTrampoline
func doneTrampoline(ctx *C.sqlite3_context) {
ai := lookupHandle(C.sqlite3_user_data(ctx)).(*aggInfo)
handle := uintptr(C.sqlite3_user_data(ctx))
ai := lookupHandle(handle).(*aggInfo)
ai.Done(ctx)
}
//export compareTrampoline
func compareTrampoline(handlePtr unsafe.Pointer, la C.int, a *C.char, lb C.int, b *C.char) C.int {
func compareTrampoline(handlePtr uintptr, la C.int, a *C.char, lb C.int, b *C.char) C.int {
cmp := lookupHandle(handlePtr).(func(string, string) int)
return C.int(cmp(C.GoStringN(a, la), C.GoStringN(b, lb)))
}
//export commitHookTrampoline
func commitHookTrampoline(handle unsafe.Pointer) int {
func commitHookTrampoline(handle uintptr) int {
callback := lookupHandle(handle).(func() int)
return callback()
}
//export rollbackHookTrampoline
func rollbackHookTrampoline(handle unsafe.Pointer) {
func rollbackHookTrampoline(handle uintptr) {
callback := lookupHandle(handle).(func())
callback()
}
//export updateHookTrampoline
func updateHookTrampoline(handle unsafe.Pointer, op int, db *C.char, table *C.char, rowid int64) {
func updateHookTrampoline(handle uintptr, op int, db *C.char, table *C.char, rowid int64) {
callback := lookupHandle(handle).(func(int, string, string, int64))
callback(op, C.GoString(db), C.GoString(table), rowid)
}
//export authorizerTrampoline
func authorizerTrampoline(handle unsafe.Pointer, op int, arg1 *C.char, arg2 *C.char, arg3 *C.char) int {
func authorizerTrampoline(handle uintptr, op int, arg1 *C.char, arg2 *C.char, arg3 *C.char) int {
callback := lookupHandle(handle).(func(int, string, string, string) int)
return callback(op, C.GoString(arg1), C.GoString(arg2), C.GoString(arg3))
}
//export preUpdateHookTrampoline
func preUpdateHookTrampoline(handle unsafe.Pointer, dbHandle uintptr, op int, db *C.char, table *C.char, oldrowid int64, newrowid int64) {
func preUpdateHookTrampoline(handle uintptr, dbHandle uintptr, op int, db *C.char, table *C.char, oldrowid int64, newrowid int64) {
hval := lookupHandleVal(handle)
data := SQLitePreUpdateData{
Conn: hval.db,
@ -104,27 +105,33 @@ type handleVal struct {
}
var handleLock sync.Mutex
var handleVals = make(map[unsafe.Pointer]handleVal)
var handleVals = make(map[uintptr]handleVal)
var handleIndex uintptr = 100
func newHandle(db *SQLiteConn, v interface{}) unsafe.Pointer {
func newHandle(db *SQLiteConn, v interface{}) uintptr {
handleLock.Lock()
defer handleLock.Unlock()
val := handleVal{db: db, val: v}
var p unsafe.Pointer = C.malloc(C.size_t(1))
if p == nil {
panic("can't allocate 'cgo-pointer hack index pointer': ptr == nil")
}
handleVals[p] = val
return p
i := handleIndex
handleIndex++
handleVals[i] = handleVal{db, v}
return i
}
func lookupHandleVal(handle unsafe.Pointer) handleVal {
func lookupHandleVal(handle uintptr) handleVal {
handleLock.Lock()
defer handleLock.Unlock()
return handleVals[handle]
r, ok := handleVals[handle]
if !ok {
if handle >= 100 && handle < handleIndex {
panic("deleted handle")
} else {
panic("invalid handle")
}
}
return r
}
func lookupHandle(handle unsafe.Pointer) interface{} {
func lookupHandle(handle uintptr) interface{} {
return lookupHandleVal(handle).val
}
@ -134,7 +141,6 @@ func deleteHandles(db *SQLiteConn) {
for handle, val := range handleVals {
if val.db == db {
delete(handleVals, handle)
C.free(handle)
}
}
}

@ -79,8 +79,9 @@ Then, you can use this extension.
Connection Hook
You can hook and inject your code when the connection is established by setting
ConnectHook to get the SQLiteConn.
You can hook and inject your code when the connection is established. database/sql
doesn't provide a way to get native go-sqlite3 interfaces. So if you want,
you need to set ConnectHook and get the SQLiteConn.
sql.Register("sqlite3_with_hook_example",
&sqlite3.SQLiteDriver{
@ -90,45 +91,21 @@ ConnectHook to get the SQLiteConn.
},
})
You can also use database/sql.Conn.Raw (Go >= 1.13):
conn, err := db.Conn(context.Background())
// if err != nil { ... }
defer conn.Close()
err = conn.Raw(func (driverConn interface{}) error {
sqliteConn := driverConn.(*sqlite3.SQLiteConn)
// ... use sqliteConn
})
// if err != nil { ... }
Go SQlite3 Extensions
If you want to register Go functions as SQLite extension functions
you can make a custom driver by calling RegisterFunction from
ConnectHook.
If you want to register Go functions as SQLite extension functions,
call RegisterFunction from ConnectHook.
regex = func(re, s string) (bool, error) {
return regexp.MatchString(re, s)
}
sql.Register("sqlite3_extended",
sql.Register("sqlite3_with_go_func",
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
return conn.RegisterFunc("regexp", regex, true)
},
})
You can then use the custom driver by passing its name to sql.Open.
var i int
conn, err := sql.Open("sqlite3_extended", "./foo.db")
if err != nil {
panic(err)
}
err = db.QueryRow(`SELECT regexp("foo.*", "seafood")`).Scan(&i)
if err != nil {
panic(err)
}
See the documentation of RegisterFunc for more details.
*/

@ -7,7 +7,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -4,7 +4,6 @@
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
//go:build cgo
// +build cgo
package sqlite3
@ -16,16 +15,16 @@ package sqlite3
#cgo CFLAGS: -DHAVE_USLEEP=1
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS
#cgo CFLAGS: -DSQLITE_ENABLE_FTS4_UNICODE61
#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
#cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED
#cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC
#cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1
#cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT
#cgo CFLAGS: -Wno-deprecated-declarations
#cgo linux,!android CFLAGS: -DHAVE_PREAD64=1 -DHAVE_PWRITE64=1
#cgo openbsd CFLAGS: -I/usr/local/include
#cgo openbsd LDFLAGS: -L/usr/local/lib
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
@ -234,14 +233,8 @@ const (
columnTimestamp string = "timestamp"
)
// This variable can be replaced with -ldflags like below:
// go build -ldflags="-X 'github.com/mattn/go-sqlite3.driverName=my-sqlite3'"
var driverName = "sqlite3"
func init() {
if driverName != "" {
sql.Register(driverName, &SQLiteDriver{})
}
sql.Register("sqlite3", &SQLiteDriver{})
}
// Version returns SQLite library version information.
@ -297,51 +290,6 @@ const (
/*SQLITE_RECURSIVE = C.SQLITE_RECURSIVE*/
)
// Standard File Control Opcodes
// See: https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
const (
SQLITE_FCNTL_LOCKSTATE = int(1)
SQLITE_FCNTL_GET_LOCKPROXYFILE = int(2)
SQLITE_FCNTL_SET_LOCKPROXYFILE = int(3)
SQLITE_FCNTL_LAST_ERRNO = int(4)
SQLITE_FCNTL_SIZE_HINT = int(5)
SQLITE_FCNTL_CHUNK_SIZE = int(6)
SQLITE_FCNTL_FILE_POINTER = int(7)
SQLITE_FCNTL_SYNC_OMITTED = int(8)
SQLITE_FCNTL_WIN32_AV_RETRY = int(9)
SQLITE_FCNTL_PERSIST_WAL = int(10)
SQLITE_FCNTL_OVERWRITE = int(11)
SQLITE_FCNTL_VFSNAME = int(12)
SQLITE_FCNTL_POWERSAFE_OVERWRITE = int(13)
SQLITE_FCNTL_PRAGMA = int(14)
SQLITE_FCNTL_BUSYHANDLER = int(15)
SQLITE_FCNTL_TEMPFILENAME = int(16)
SQLITE_FCNTL_MMAP_SIZE = int(18)
SQLITE_FCNTL_TRACE = int(19)
SQLITE_FCNTL_HAS_MOVED = int(20)
SQLITE_FCNTL_SYNC = int(21)
SQLITE_FCNTL_COMMIT_PHASETWO = int(22)
SQLITE_FCNTL_WIN32_SET_HANDLE = int(23)
SQLITE_FCNTL_WAL_BLOCK = int(24)
SQLITE_FCNTL_ZIPVFS = int(25)
SQLITE_FCNTL_RBU = int(26)
SQLITE_FCNTL_VFS_POINTER = int(27)
SQLITE_FCNTL_JOURNAL_POINTER = int(28)
SQLITE_FCNTL_WIN32_GET_HANDLE = int(29)
SQLITE_FCNTL_PDB = int(30)
SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = int(31)
SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = int(32)
SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = int(33)
SQLITE_FCNTL_LOCK_TIMEOUT = int(34)
SQLITE_FCNTL_DATA_VERSION = int(35)
SQLITE_FCNTL_SIZE_LIMIT = int(36)
SQLITE_FCNTL_CKPT_DONE = int(37)
SQLITE_FCNTL_RESERVE_BYTES = int(38)
SQLITE_FCNTL_CKPT_START = int(39)
SQLITE_FCNTL_EXTERNAL_READER = int(40)
SQLITE_FCNTL_CKSM_FILE = int(41)
)
// SQLiteDriver implements driver.Driver.
type SQLiteDriver struct {
Extensions []string
@ -525,7 +473,7 @@ func (c *SQLiteConn) RegisterCollation(name string, cmp func(string, string) int
handle := newHandle(c, cmp)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
rv := C.sqlite3_create_collation(c.db, cname, C.SQLITE_UTF8, handle, (*[0]byte)(unsafe.Pointer(C.compareTrampoline)))
rv := C.sqlite3_create_collation(c.db, cname, C.SQLITE_UTF8, unsafe.Pointer(handle), (*[0]byte)(unsafe.Pointer(C.compareTrampoline)))
if rv != C.SQLITE_OK {
return c.lastError()
}
@ -543,7 +491,7 @@ func (c *SQLiteConn) RegisterCommitHook(callback func() int) {
if callback == nil {
C.sqlite3_commit_hook(c.db, nil, nil)
} else {
C.sqlite3_commit_hook(c.db, (*[0]byte)(C.commitHookTrampoline), newHandle(c, callback))
C.sqlite3_commit_hook(c.db, (*[0]byte)(C.commitHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
}
}
@ -556,7 +504,7 @@ func (c *SQLiteConn) RegisterRollbackHook(callback func()) {
if callback == nil {
C.sqlite3_rollback_hook(c.db, nil, nil)
} else {
C.sqlite3_rollback_hook(c.db, (*[0]byte)(C.rollbackHookTrampoline), newHandle(c, callback))
C.sqlite3_rollback_hook(c.db, (*[0]byte)(C.rollbackHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
}
}
@ -573,7 +521,7 @@ func (c *SQLiteConn) RegisterUpdateHook(callback func(int, string, string, int64
if callback == nil {
C.sqlite3_update_hook(c.db, nil, nil)
} else {
C.sqlite3_update_hook(c.db, (*[0]byte)(C.updateHookTrampoline), newHandle(c, callback))
C.sqlite3_update_hook(c.db, (*[0]byte)(C.updateHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
}
}
@ -587,7 +535,7 @@ func (c *SQLiteConn) RegisterAuthorizer(callback func(int, string, string, strin
if callback == nil {
C.sqlite3_set_authorizer(c.db, nil, nil)
} else {
C.sqlite3_set_authorizer(c.db, (*[0]byte)(C.authorizerTrampoline), newHandle(c, callback))
C.sqlite3_set_authorizer(c.db, (*[0]byte)(C.authorizerTrampoline), unsafe.Pointer(newHandle(c, callback)))
}
}
@ -668,8 +616,8 @@ func (c *SQLiteConn) RegisterFunc(name string, impl interface{}, pure bool) erro
return nil
}
func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp unsafe.Pointer, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int {
return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(uintptr(pApp)), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal))
func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp uintptr, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int {
return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(pApp), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal))
}
// RegisterAggregator makes a Go type available as a SQLite aggregation function.
@ -854,38 +802,25 @@ func (c *SQLiteConn) exec(ctx context.Context, query string, args []namedValue)
}
var res driver.Result
if s.(*SQLiteStmt).s != nil {
stmtArgs := make([]namedValue, 0, len(args))
na := s.NumInput()
if len(args)-start < na {
if len(args) < na {
s.Close()
return nil, fmt.Errorf("not enough args to execute query: want %d got %d", na, len(args))
}
// consume the number of arguments used in the current
// statement and append all named arguments not
// contained therein
stmtArgs = append(stmtArgs, args[start:start+na]...)
for i := range args {
if (i < start || i >= na) && args[i].Name != "" {
stmtArgs = append(stmtArgs, args[i])
}
}
for i := range stmtArgs {
stmtArgs[i].Ordinal = i + 1
for i := 0; i < na; i++ {
args[i].Ordinal -= start
}
res, err = s.(*SQLiteStmt).exec(ctx, stmtArgs)
res, err = s.(*SQLiteStmt).exec(ctx, args[:na])
if err != nil && err != driver.ErrSkip {
s.Close()
return nil, err
}
args = args[na:]
start += na
}
tail := s.(*SQLiteStmt).t
s.Close()
if tail == "" {
if res == nil {
// https://github.com/mattn/go-sqlite3/issues/963
res = &SQLiteResult{0, 0}
}
return res, nil
}
query = tail
@ -913,33 +848,24 @@ func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, erro
func (c *SQLiteConn) query(ctx context.Context, query string, args []namedValue) (driver.Rows, error) {
start := 0
for {
stmtArgs := make([]namedValue, 0, len(args))
s, err := c.prepare(ctx, query)
if err != nil {
return nil, err
}
s.(*SQLiteStmt).cls = true
na := s.NumInput()
if len(args)-start < na {
return nil, fmt.Errorf("not enough args to execute query: want %d got %d", na, len(args)-start)
}
// consume the number of arguments used in the current
// statement and append all named arguments not contained
// therein
stmtArgs = append(stmtArgs, args[start:start+na]...)
for i := range args {
if (i < start || i >= na) && args[i].Name != "" {
stmtArgs = append(stmtArgs, args[i])
}
if len(args) < na {
return nil, fmt.Errorf("not enough args to execute query: want %d got %d", na, len(args))
}
for i := range stmtArgs {
stmtArgs[i].Ordinal = i + 1
for i := 0; i < na; i++ {
args[i].Ordinal -= start
}
rows, err := s.(*SQLiteStmt).query(ctx, stmtArgs)
rows, err := s.(*SQLiteStmt).query(ctx, args[:na])
if err != nil && err != driver.ErrSkip {
s.Close()
return rows, err
}
args = args[na:]
start += na
tail := s.(*SQLiteStmt).t
if tail == "" {
@ -1097,8 +1023,6 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
secureDelete := "DEFAULT"
synchronousMode := "NORMAL"
writableSchema := -1
vfsName := ""
var cacheSize *int64
pos := strings.IndexRune(dsn, '?')
if pos >= 1 {
@ -1422,22 +1346,6 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
}
}
// Cache size (_cache_size)
//
// https://sqlite.org/pragma.html#pragma_cache_size
//
if val := params.Get("_cache_size"); val != "" {
iv, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return nil, fmt.Errorf("Invalid _cache_size: %v: %v", val, err)
}
cacheSize = &iv
}
if val := params.Get("vfs"); val != "" {
vfsName = val
}
if !strings.HasPrefix(dsn, "file:") {
dsn = dsn[:pos]
}
@ -1446,14 +1354,9 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
var db *C.sqlite3
name := C.CString(dsn)
defer C.free(unsafe.Pointer(name))
var vfs *C.char
if vfsName != "" {
vfs = C.CString(vfsName)
defer C.free(unsafe.Pointer(vfs))
}
rv := C._sqlite3_open_v2(name, &db,
mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
vfs)
nil)
if rv != 0 {
// Save off the error _before_ closing the database.
// This is safe even if db is nil.
@ -1467,6 +1370,12 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
return nil, errors.New("sqlite succeeded without returning a database")
}
rv = C.sqlite3_busy_timeout(db, C.int(busyTimeout))
if rv != C.SQLITE_OK {
C.sqlite3_close_v2(db)
return nil, Error{Code: ErrNo(rv)}
}
exec := func(s string) error {
cs := C.CString(s)
rv := C.sqlite3_exec(db, cs, nil, nil, nil)
@ -1477,12 +1386,6 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
return nil
}
// Busy timeout
if err := exec(fmt.Sprintf("PRAGMA busy_timeout = %d;", busyTimeout)); err != nil {
C.sqlite3_close_v2(db)
return nil, err
}
// USER AUTHENTICATION
//
// User Authentication is always performed even when
@ -1734,7 +1637,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
//
// Because default is NORMAL this statement is always executed
if err := exec(fmt.Sprintf("PRAGMA synchronous = %s;", synchronousMode)); err != nil {
conn.Close()
C.sqlite3_close_v2(db)
return nil, err
}
@ -1746,14 +1649,6 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
}
}
// Cache Size
if cacheSize != nil {
if err := exec(fmt.Sprintf("PRAGMA cache_size = %d;", *cacheSize)); err != nil {
C.sqlite3_close_v2(db)
return nil, err
}
}
if len(d.Extensions) > 0 {
if err := conn.loadExtensions(d.Extensions); err != nil {
conn.Close()
@ -1858,31 +1753,6 @@ func (c *SQLiteConn) SetLimit(id int, newVal int) int {
return int(C._sqlite3_limit(c.db, C.int(id), C.int(newVal)))
}
// SetFileControlInt invokes the xFileControl method on a given database. The
// dbName is the name of the database. It will default to "main" if left blank.
// The op is one of the opcodes prefixed by "SQLITE_FCNTL_". The arg argument
// and return code are both opcode-specific. Please see the SQLite documentation.
//
// This method is not thread-safe as the returned error code can be changed by
// another call if invoked concurrently.
//
// See: sqlite3_file_control, https://www.sqlite.org/c3ref/file_control.html
func (c *SQLiteConn) SetFileControlInt(dbName string, op int, arg int) error {
if dbName == "" {
dbName = "main"
}
cDBName := C.CString(dbName)
defer C.free(unsafe.Pointer(cDBName))
cArg := C.int(arg)
rv := C.sqlite3_file_control(c.db, cDBName, C.int(op), unsafe.Pointer(&cArg))
if rv != C.SQLITE_OK {
return c.lastError()
}
return nil
}
// Close the statement.
func (s *SQLiteStmt) Close() error {
s.mu.Lock()
@ -1908,6 +1778,11 @@ func (s *SQLiteStmt) NumInput() int {
return int(C.sqlite3_bind_parameter_count(s.s))
}
type bindArg struct {
n int
v driver.Value
}
var placeHolder = []byte{0}
func (s *SQLiteStmt) bind(args []namedValue) error {
@ -1916,63 +1791,52 @@ func (s *SQLiteStmt) bind(args []namedValue) error {
return s.c.lastError()
}
bindIndices := make([][3]int, len(args))
prefixes := []string{":", "@", "$"}
for i, v := range args {
bindIndices[i][0] = args[i].Ordinal
if v.Name != "" {
for j := range prefixes {
cname := C.CString(prefixes[j] + v.Name)
bindIndices[i][j] = int(C.sqlite3_bind_parameter_index(s.s, cname))
C.free(unsafe.Pointer(cname))
cname := C.CString(":" + v.Name)
args[i].Ordinal = int(C.sqlite3_bind_parameter_index(s.s, cname))
C.free(unsafe.Pointer(cname))
}
}
for _, arg := range args {
n := C.int(arg.Ordinal)
switch v := arg.Value.(type) {
case nil:
rv = C.sqlite3_bind_null(s.s, n)
case string:
if len(v) == 0 {
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&placeHolder[0])), C.int(0))
} else {
b := []byte(v)
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
}
args[i].Ordinal = bindIndices[i][0]
}
}
for i, arg := range args {
for j := range bindIndices[i] {
if bindIndices[i][j] == 0 {
continue
case int64:
rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v))
case bool:
if v {
rv = C.sqlite3_bind_int(s.s, n, 1)
} else {
rv = C.sqlite3_bind_int(s.s, n, 0)
}
n := C.int(bindIndices[i][j])
switch v := arg.Value.(type) {
case nil:
case float64:
rv = C.sqlite3_bind_double(s.s, n, C.double(v))
case []byte:
if v == nil {
rv = C.sqlite3_bind_null(s.s, n)
case string:
if len(v) == 0 {
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&placeHolder[0])), C.int(0))
} else {
b := []byte(v)
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
} else {
ln := len(v)
if ln == 0 {
v = placeHolder
}
case int64:
rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v))
case bool:
if v {
rv = C.sqlite3_bind_int(s.s, n, 1)
} else {
rv = C.sqlite3_bind_int(s.s, n, 0)
}
case float64:
rv = C.sqlite3_bind_double(s.s, n, C.double(v))
case []byte:
if v == nil {
rv = C.sqlite3_bind_null(s.s, n)
} else {
ln := len(v)
if ln == 0 {
v = placeHolder
}
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln))
}
case time.Time:
b := []byte(v.Format(SQLiteTimestampFormats[0]))
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
}
if rv != C.SQLITE_OK {
return s.c.lastError()
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln))
}
case time.Time:
b := []byte(v.Format(SQLiteTimestampFormats[0]))
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
}
if rv != C.SQLITE_OK {
return s.c.lastError()
}
}
return nil
@ -2030,14 +1894,6 @@ func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
return s.exec(context.Background(), list)
}
func isInterruptErr(err error) bool {
sqliteErr, ok := err.(Error)
if ok {
return sqliteErr.Code == ErrInterrupt
}
return false
}
// exec executes a query that doesn't return rows. Attempts to honor context timeout.
func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) {
if ctx.Done() == nil {
@ -2053,22 +1909,19 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
r, err := s.execSync(args)
resultCh <- result{r, err}
}()
var rv result
select {
case rv = <-resultCh:
case rv := <-resultCh:
return rv.r, rv.err
case <-ctx.Done():
select {
case rv = <-resultCh: // no need to interrupt, operation completed in db
case <-resultCh: // no need to interrupt
default:
// this is still racy and can be no-op if executed between sqlite3_* calls in execSync.
C.sqlite3_interrupt(s.c.db)
rv = <-resultCh // wait for goroutine completed
if isInterruptErr(rv.err) {
return nil, ctx.Err()
}
<-resultCh // ensure goroutine completed
}
return nil, ctx.Err()
}
return rv.r, rv.err
}
func (s *SQLiteStmt) execSync(args []namedValue) (driver.Result, error) {
@ -2090,13 +1943,6 @@ func (s *SQLiteStmt) execSync(args []namedValue) (driver.Result, error) {
return &SQLiteResult{id: int64(rowid), changes: int64(changes)}, nil
}
// Readonly reports if this statement is considered readonly by SQLite.
//
// See: https://sqlite.org/c3ref/stmt_readonly.html
func (s *SQLiteStmt) Readonly() bool {
return C.sqlite3_stmt_readonly(s.s) == 1
}
// Close the rows.
func (rc *SQLiteRows) Close() error {
rc.s.mu.Lock()

@ -8,7 +8,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif

@ -14,6 +14,5 @@ package sqlite3
#cgo darwin CFLAGS: -I/usr/local/opt/sqlite/include
#cgo openbsd LDFLAGS: -lsqlite3
#cgo solaris LDFLAGS: -lsqlite3
#cgo windows LDFLAGS: -lsqlite3
*/
import "C"

@ -9,7 +9,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
@ -28,9 +28,12 @@ func (c *SQLiteConn) loadExtensions(extensions []string) error {
}
for _, extension := range extensions {
if err := c.loadExtension(extension, nil); err != nil {
cext := C.CString(extension)
defer C.free(unsafe.Pointer(cext))
rv = C.sqlite3_load_extension(c.db, cext, nil, nil)
if rv != C.SQLITE_OK {
C.sqlite3_enable_load_extension(c.db, 0)
return err
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
}
}
@ -38,7 +41,6 @@ func (c *SQLiteConn) loadExtensions(extensions []string) error {
if rv != C.SQLITE_OK {
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
}
return nil
}
@ -49,35 +51,19 @@ func (c *SQLiteConn) LoadExtension(lib string, entry string) error {
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
}
if err := c.loadExtension(lib, &entry); err != nil {
C.sqlite3_enable_load_extension(c.db, 0)
return err
}
rv = C.sqlite3_enable_load_extension(c.db, 0)
if rv != C.SQLITE_OK {
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
}
return nil
}
func (c *SQLiteConn) loadExtension(lib string, entry *string) error {
clib := C.CString(lib)
defer C.free(unsafe.Pointer(clib))
centry := C.CString(entry)
defer C.free(unsafe.Pointer(centry))
var centry *C.char
if entry != nil {
centry = C.CString(*entry)
defer C.free(unsafe.Pointer(centry))
rv = C.sqlite3_load_extension(c.db, clib, centry, nil)
if rv != C.SQLITE_OK {
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
}
var errMsg *C.char
defer C.sqlite3_free(unsafe.Pointer(errMsg))
rv := C.sqlite3_load_extension(c.db, clib, centry, &errMsg)
rv = C.sqlite3_enable_load_extension(c.db, 0)
if rv != C.SQLITE_OK {
return errors.New(C.GoString(errMsg))
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
}
return nil

@ -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"

@ -13,7 +13,7 @@ package sqlite3
#cgo LDFLAGS: -lm
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
@ -33,7 +33,7 @@ import (
// The callback is passed a SQLitePreUpdateData struct with the data for
// the update, as well as methods for fetching copies of impacted data.
//
// If there is an existing preupdate hook for this connection, it will be
// If there is an existing update hook for this connection, it will be
// removed. If callback is nil the existing hook (if any) will be removed
// without creating a new one.
func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) {

@ -13,7 +13,7 @@ package sqlite3
// The callback is passed a SQLitePreUpdateData struct with the data for
// the update, as well as methods for fetching copies of impacted data.
//
// If there is an existing preupdate hook for this connection, it will be
// If there is an existing update hook for this connection, it will be
// removed. If callback is nil the existing hook (if any) will be removed
// without creating a new one.
func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) {

@ -5,7 +5,7 @@
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
#include <stdio.h>
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
extern int unlock_notify_wait(sqlite3 *db);

@ -12,7 +12,7 @@ package sqlite3
#cgo CFLAGS: -DSQLITE_ENABLE_UNLOCK_NOTIFY
#include <stdlib.h>
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
extern void unlock_notify_callback(void *arg, int argc);
*/

@ -11,7 +11,7 @@ package sqlite3
#cgo CFLAGS: -DSQLITE_USER_AUTHENTICATION
#cgo LDFLAGS: -lm
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif

@ -19,7 +19,7 @@ package sqlite3
#cgo CFLAGS: -Wno-deprecated-declarations
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
@ -226,43 +226,11 @@ static sqlite3_module goModule = {
0 // xRollbackTo
};
// See https://sqlite.org/vtab.html#eponymous_only_virtual_tables
static sqlite3_module goModuleEponymousOnly = {
0, // iVersion
0, // xCreate - create a table, which here is null
cXConnect, // xConnect - connect to an existing table
cXBestIndex, // xBestIndex - Determine search strategy
cXDisconnect, // xDisconnect - Disconnect from a table
cXDestroy, // xDestroy - Drop a table
cXOpen, // xOpen - open a cursor
cXClose, // xClose - close a cursor
cXFilter, // xFilter - configure scan constraints
cXNext, // xNext - advance a cursor
cXEof, // xEof
cXColumn, // xColumn - read data
cXRowid, // xRowid - read data
cXUpdate, // xUpdate - write data
// Not implemented
0, // xBegin - begin transaction
0, // xSync - sync transaction
0, // xCommit - commit transaction
0, // xRollback - rollback transaction
0, // xFindFunction - function overloading
0, // xRename - rename the table
0, // xSavepoint
0, // xRelease
0 // xRollbackTo
};
void goMDestroy(void*);
static int _sqlite3_create_module(sqlite3 *db, const char *zName, uintptr_t pClientData) {
return sqlite3_create_module_v2(db, zName, &goModule, (void*) pClientData, goMDestroy);
}
static int _sqlite3_create_module_eponymous_only(sqlite3 *db, const char *zName, uintptr_t pClientData) {
return sqlite3_create_module_v2(db, zName, &goModuleEponymousOnly, (void*) pClientData, goMDestroy);
}
*/
import "C"
@ -320,13 +288,10 @@ type InfoOrderBy struct {
}
func constraints(info *C.sqlite3_index_info) []InfoConstraint {
slice := *(*[]C.struct_sqlite3_index_constraint)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(info.aConstraint)),
Len: int(info.nConstraint),
Cap: int(info.nConstraint),
}))
l := info.nConstraint
slice := (*[1 << 30]C.struct_sqlite3_index_constraint)(unsafe.Pointer(info.aConstraint))[:l:l]
cst := make([]InfoConstraint, 0, len(slice))
cst := make([]InfoConstraint, 0, l)
for _, c := range slice {
var usable bool
if c.usable > 0 {
@ -342,13 +307,10 @@ func constraints(info *C.sqlite3_index_info) []InfoConstraint {
}
func orderBys(info *C.sqlite3_index_info) []InfoOrderBy {
slice := *(*[]C.struct_sqlite3_index_orderby)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(info.aOrderBy)),
Len: int(info.nOrderBy),
Cap: int(info.nOrderBy),
}))
l := info.nOrderBy
slice := (*[1 << 30]C.struct_sqlite3_index_orderby)(unsafe.Pointer(info.aOrderBy))[:l:l]
ob := make([]InfoOrderBy, 0, len(slice))
ob := make([]InfoOrderBy, 0, l)
for _, c := range slice {
var desc bool
if c.desc > 0 {
@ -385,7 +347,7 @@ func mPrintf(format, arg string) *C.char {
//export goMInit
func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **C.char, isCreate C.int) C.uintptr_t {
m := lookupHandle(pClientData).(*sqliteModule)
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
if m.c.db != (*C.sqlite3)(db) {
*pzErr = mPrintf("%s", "Inconsistent db handles")
return 0
@ -411,12 +373,12 @@ func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **
}
vt := sqliteVTab{m, vTab}
*pzErr = nil
return C.uintptr_t(uintptr(newHandle(m.c, &vt)))
return C.uintptr_t(newHandle(m.c, &vt))
}
//export goVRelease
func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
vt := lookupHandle(pVTab).(*sqliteVTab)
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
var err error
if isDestroy == 1 {
err = vt.vTab.Destroy()
@ -431,7 +393,7 @@ func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
//export goVOpen
func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t {
vt := lookupHandle(pVTab).(*sqliteVTab)
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
vTabCursor, err := vt.vTab.Open()
if err != nil {
*pzErr = mPrintf("%s", err.Error())
@ -439,12 +401,12 @@ func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t {
}
vtc := sqliteVTabCursor{vt, vTabCursor}
*pzErr = nil
return C.uintptr_t(uintptr(newHandle(vt.module.c, &vtc)))
return C.uintptr_t(newHandle(vt.module.c, &vtc))
}
//export goVBestIndex
func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
vt := lookupHandle(pVTab).(*sqliteVTab)
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
info := (*C.sqlite3_index_info)(icp)
csts := constraints(info)
res, err := vt.vTab.BestIndex(csts, orderBys(info))
@ -456,37 +418,22 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
}
// Get a pointer to constraint_usage struct so we can update in place.
slice := *(*[]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(info.aConstraintUsage)),
Len: int(info.nConstraint),
Cap: int(info.nConstraint),
}))
l := info.nConstraint
s := (*[1 << 30]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(info.aConstraintUsage))[:l:l]
index := 1
for i := range slice {
for i := C.int(0); i < info.nConstraint; i++ {
if res.Used[i] {
slice[i].argvIndex = C.int(index)
slice[i].omit = C.uchar(1)
s[i].argvIndex = C.int(index)
s[i].omit = C.uchar(1)
index++
}
}
info.idxNum = C.int(res.IdxNum)
info.idxStr = (*C.char)(C.sqlite3_malloc(C.int(len(res.IdxStr) + 1)))
if info.idxStr == nil {
// C.malloc and C.CString ordinarily do this for you. See https://golang.org/cmd/cgo/
panic("out of memory")
}
info.needToFreeIdxStr = C.int(1)
idxStr := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(info.idxStr)),
Len: len(res.IdxStr) + 1,
Cap: len(res.IdxStr) + 1,
}))
copy(idxStr, res.IdxStr)
idxStr[len(idxStr)-1] = 0 // null-terminated string
idxStr := C.CString(res.IdxStr)
defer C.free(unsafe.Pointer(idxStr))
info.idxStr = idxStr
info.needToFreeIdxStr = C.int(0)
if res.AlreadyOrdered {
info.orderByConsumed = C.int(1)
}
@ -498,7 +445,7 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
//export goVClose
func goVClose(pCursor unsafe.Pointer) *C.char {
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
err := vtc.vTabCursor.Close()
if err != nil {
return mPrintf("%s", err.Error())
@ -508,13 +455,13 @@ func goVClose(pCursor unsafe.Pointer) *C.char {
//export goMDestroy
func goMDestroy(pClientData unsafe.Pointer) {
m := lookupHandle(pClientData).(*sqliteModule)
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
m.module.DestroyModule()
}
//export goVFilter
func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int, argv **C.sqlite3_value) *C.char {
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
vals := make([]interface{}, 0, argc)
for _, v := range args {
@ -533,7 +480,7 @@ func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int
//export goVNext
func goVNext(pCursor unsafe.Pointer) *C.char {
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
err := vtc.vTabCursor.Next()
if err != nil {
return mPrintf("%s", err.Error())
@ -543,7 +490,7 @@ func goVNext(pCursor unsafe.Pointer) *C.char {
//export goVEof
func goVEof(pCursor unsafe.Pointer) C.int {
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
err := vtc.vTabCursor.EOF()
if err {
return 1
@ -553,7 +500,7 @@ func goVEof(pCursor unsafe.Pointer) C.int {
//export goVColumn
func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char {
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
c := (*SQLiteContext)(cp)
err := vtc.vTabCursor.Column(c, int(col))
if err != nil {
@ -564,7 +511,7 @@ func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char {
//export goVRowid
func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char {
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
rowid, err := vtc.vTabCursor.Rowid()
if err != nil {
return mPrintf("%s", err.Error())
@ -575,7 +522,7 @@ func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char {
//export goVUpdate
func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid *C.sqlite3_int64) *C.char {
vt := lookupHandle(pVTab).(*sqliteVTab)
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
var tname string
if n, ok := vt.vTab.(interface {
@ -638,13 +585,6 @@ type Module interface {
DestroyModule()
}
// EponymousOnlyModule is a "virtual table module" (as above), but
// for defining "eponymous only" virtual tables See: https://sqlite.org/vtab.html#eponymous_only_virtual_tables
type EponymousOnlyModule interface {
Module
EponymousOnlyModule()
}
// VTab describes a particular instance of the virtual table.
// See: http://sqlite.org/c3ref/vtab.html
type VTab interface {
@ -702,19 +642,9 @@ func (c *SQLiteConn) CreateModule(moduleName string, module Module) error {
mname := C.CString(moduleName)
defer C.free(unsafe.Pointer(mname))
udm := sqliteModule{c, moduleName, module}
switch module.(type) {
case EponymousOnlyModule:
rv := C._sqlite3_create_module_eponymous_only(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
if rv != C.SQLITE_OK {
return c.lastError()
}
return nil
case Module:
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(uintptr(newHandle(c, &udm))))
if rv != C.SQLITE_OK {
return c.lastError()
}
return nil
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(newHandle(c, &udm)))
if rv != C.SQLITE_OK {
return c.lastError()
}
return nil
}

@ -9,7 +9,7 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
@ -215,6 +215,7 @@ func addTraceMapping(connHandle uintptr, traceConf TraceConfig) {
traceConf, connHandle, oldEntryCopy.config))
}
traceMap[connHandle] = traceMapEntry{config: traceConf}
fmt.Printf("Added trace config %v: handle 0x%x.\n", traceConf, connHandle)
}
func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) {
@ -233,6 +234,7 @@ func popTraceMapping(connHandle uintptr) (TraceConfig, bool) {
entryCopy, found := traceMap[connHandle]
if found {
delete(traceMap, connHandle)
fmt.Printf("Pop handle 0x%x: deleted trace config %v.\n", connHandle, entryCopy.config)
}
return entryCopy.config, found
}

@ -1,4 +1,5 @@
// 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.
@ -6,16 +7,15 @@ package sqlite3
/*
#ifndef USE_LIBSQLITE3
#include "sqlite3-binding.h"
#include <sqlite3-binding.h>
#else
#include <sqlite3.h>
#endif
*/
import "C"
import (
"database/sql"
"reflect"
"strings"
"time"
)
// ColumnTypeDatabaseTypeName implement RowsColumnTypeDatabaseTypeName.
@ -40,69 +40,23 @@ func (rc *SQLiteRows) ColumnTypeNullable(i int) (nullable, ok bool) {
// ColumnTypeScanType implement RowsColumnTypeScanType.
func (rc *SQLiteRows) ColumnTypeScanType(i int) reflect.Type {
//ct := C.sqlite3_column_type(rc.s.s, C.int(i)) // Always returns 5
return scanType(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i))))
}
const (
SQLITE_INTEGER = iota
SQLITE_TEXT
SQLITE_BLOB
SQLITE_REAL
SQLITE_NUMERIC
SQLITE_TIME
SQLITE_BOOL
SQLITE_NULL
)
func scanType(cdt string) reflect.Type {
t := strings.ToUpper(cdt)
i := databaseTypeConvSqlite(t)
switch i {
case SQLITE_INTEGER:
return reflect.TypeOf(sql.NullInt64{})
case SQLITE_TEXT:
return reflect.TypeOf(sql.NullString{})
case SQLITE_BLOB:
return reflect.TypeOf(sql.RawBytes{})
case SQLITE_REAL:
return reflect.TypeOf(sql.NullFloat64{})
case SQLITE_NUMERIC:
return reflect.TypeOf(sql.NullFloat64{})
case SQLITE_BOOL:
return reflect.TypeOf(sql.NullBool{})
case SQLITE_TIME:
return reflect.TypeOf(sql.NullTime{})
}
return reflect.TypeOf(new(interface{}))
}
func databaseTypeConvSqlite(t string) int {
if strings.Contains(t, "INT") {
return SQLITE_INTEGER
}
if t == "CLOB" || t == "TEXT" ||
strings.Contains(t, "CHAR") {
return SQLITE_TEXT
}
if t == "BLOB" {
return SQLITE_BLOB
}
if t == "REAL" || t == "FLOAT" ||
strings.Contains(t, "DOUBLE") {
return SQLITE_REAL
}
if t == "DATE" || t == "DATETIME" ||
t == "TIMESTAMP" {
return SQLITE_TIME
}
if t == "NUMERIC" ||
strings.Contains(t, "DECIMAL") {
return SQLITE_NUMERIC
}
if t == "BOOLEAN" {
return SQLITE_BOOL
}
return SQLITE_NULL
switch C.sqlite3_column_type(rc.s.s, C.int(i)) {
case C.SQLITE_INTEGER:
switch C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i))) {
case "timestamp", "datetime", "date":
return reflect.TypeOf(time.Time{})
case "boolean":
return reflect.TypeOf(false)
}
return reflect.TypeOf(int64(0))
case C.SQLITE_FLOAT:
return reflect.TypeOf(float64(0))
case C.SQLITE_BLOB:
return reflect.SliceOf(reflect.TypeOf(byte(0)))
case C.SQLITE_NULL:
return reflect.TypeOf(nil)
case C.SQLITE_TEXT:
return reflect.TypeOf("")
}
return reflect.SliceOf(reflect.TypeOf(byte(0)))
}

@ -15,9 +15,7 @@ package sqlite3
// This code should improve performance on windows because
// without the presence of usleep SQLite waits 1 second.
//
// Source: https://github.com/php/php-src/blob/PHP-5.0/win32/time.c
// License: https://github.com/php/php-src/blob/PHP-5.0/LICENSE
// Details: https://stackoverflow.com/questions/5801813/c-usleep-is-obsolete-workarounds-for-windows-mingw?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
// Source: https://stackoverflow.com/questions/5801813/c-usleep-is-obsolete-workarounds-for-windows-mingw?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
/*
#include <windows.h>

@ -19,10 +19,6 @@
#ifndef SQLITE3EXT_H
#define SQLITE3EXT_H
#include "sqlite3-binding.h"
#ifdef __clang__
#define assert(condition) ((void)0)
#endif
/*
** The following structure holds pointers to all of the SQLite API
@ -335,27 +331,6 @@ struct sqlite3_api_routines {
const char *(*filename_database)(const char*);
const char *(*filename_journal)(const char*);
const char *(*filename_wal)(const char*);
/* Version 3.32.0 and later */
char *(*create_filename)(const char*,const char*,const char*,
int,const char**);
void (*free_filename)(char*);
sqlite3_file *(*database_file_object)(const char*);
/* Version 3.34.0 and later */
int (*txn_state)(sqlite3*,const char*);
/* Version 3.36.1 and later */
sqlite3_int64 (*changes64)(sqlite3*);
sqlite3_int64 (*total_changes64)(sqlite3*);
/* Version 3.37.0 and later */
int (*autovacuum_pages)(sqlite3*,
unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int),
void*, void(*)(void*));
/* Version 3.38.0 and later */
int (*error_offset)(sqlite3*);
int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**);
int (*vtab_distinct)(sqlite3_index_info*);
int (*vtab_in)(sqlite3_index_info*,int,int);
int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
};
/*
@ -656,24 +631,6 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_filename_database sqlite3_api->filename_database
#define sqlite3_filename_journal sqlite3_api->filename_journal
#define sqlite3_filename_wal sqlite3_api->filename_wal
/* Version 3.32.0 and later */
#define sqlite3_create_filename sqlite3_api->create_filename
#define sqlite3_free_filename sqlite3_api->free_filename
#define sqlite3_database_file_object sqlite3_api->database_file_object
/* Version 3.34.0 and later */
#define sqlite3_txn_state sqlite3_api->txn_state
/* Version 3.36.1 and later */
#define sqlite3_changes64 sqlite3_api->changes64
#define sqlite3_total_changes64 sqlite3_api->total_changes64
/* Version 3.37.0 and later */
#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages
/* Version 3.38.0 and later */
#define sqlite3_error_offset sqlite3_api->error_offset
#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value
#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct
#define sqlite3_vtab_in sqlite3_api->vtab_in
#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first
#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

@ -13,25 +13,14 @@ import (
"errors"
)
var errorMsg = errors.New("Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub")
func init() {
sql.Register("sqlite3", &SQLiteDriver{})
sql.Register("sqlite3", &SQLiteDriverMock{})
}
type (
SQLiteDriver struct {
Extensions []string
ConnectHook func(*SQLiteConn) error
}
SQLiteConn struct{}
)
type SQLiteDriverMock struct{}
func (SQLiteDriver) Open(s string) (driver.Conn, error) { return nil, errorMsg }
func (c *SQLiteConn) RegisterAggregator(string, interface{}, bool) error { return errorMsg }
func (c *SQLiteConn) RegisterAuthorizer(func(int, string, string, string) int) {}
func (c *SQLiteConn) RegisterCollation(string, func(string, string) int) error { return errorMsg }
func (c *SQLiteConn) RegisterCommitHook(func() int) {}
func (c *SQLiteConn) RegisterFunc(string, interface{}, bool) error { return errorMsg }
func (c *SQLiteConn) RegisterRollbackHook(func()) {}
func (c *SQLiteConn) RegisterUpdateHook(func(int, string, string, int64)) {}
var errorMsg = errors.New("Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub")
func (SQLiteDriverMock) Open(s string) (driver.Conn, error) {
return nil, errorMsg
}

@ -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,75 @@
cast
====
[![GoDoc](https://godoc.org/github.com/spf13/cast?status.svg)](https://godoc.org/github.com/spf13/cast)
[![Build Status](https://github.com/spf13/cast/actions/workflows/go.yml/badge.svg)](https://github.com/spf13/cast/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cast)](https://goreportcard.com/report/github.com/spf13/cast)
Easy and safe casting from one type to another in Go
Dont Panic! ... Cast
## What is Cast?
Cast is a library to convert between different go types in a consistent and easy way.
Cast provides simple functions to easily convert a number to a string, an
interface into a bool, etc. Cast does this intelligently when an obvious
conversion is possible. It doesnt make any attempts to guess what you meant,
for example you can only convert a string to an int when it is a string
representation of an int such as “8”. Cast was developed for use in
[Hugo](http://hugo.spf13.com), a website engine which uses YAML, TOML or JSON
for meta data.
## Why use Cast?
When working with dynamic data in Go you often need to cast or convert the data
from one type into another. Cast goes beyond just using type assertion (though
it uses that when possible) to provide a very straightforward and convenient
library.
If you are working with interfaces to handle things like dynamic content
youll need an easy way to convert an interface into a given type. This
is the library for you.
If you are taking in data from YAML, TOML or JSON or other formats which lack
full types, then Cast is the library for you.
## Usage
Cast provides a handful of To_____ methods. These methods will always return
the desired type. **If input is provided that will not convert to that type, the
0 or nil value for that type will be returned**.
Cast also provides identical methods To_____E. These return the same result as
the To_____ methods, plus an additional error which tells you if it successfully
converted. Using these methods you can tell the difference between when the
input matched the zero value or when the conversion failed and the zero value
was returned.
The following examples are merely a sample of what is available. Please review
the code for a complete set.
### Example ToString:
cast.ToString("mayonegg") // "mayonegg"
cast.ToString(8) // "8"
cast.ToString(8.31) // "8.31"
cast.ToString([]byte("one time")) // "one time"
cast.ToString(nil) // ""
var foo interface{} = "one more time"
cast.ToString(foo) // "one more time"
### Example ToInt:
cast.ToInt(8) // 8
cast.ToInt(8.31) // 8
cast.ToInt("8") // 8
cast.ToInt(true) // 1
cast.ToInt(false) // 0
var eight interface{} = 8
cast.ToInt(eight) // 8
cast.ToInt(nil) // 0

@ -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]]
}

25
vendor/modules.txt vendored

@ -116,6 +116,12 @@ github.com/coreos/go-semver/semver
# github.com/coreos/go-systemd/v22 v22.3.2
## explicit; go 1.12
github.com/coreos/go-systemd/v22/journal
# github.com/daodao97/fly v0.0.0-20220716071342-fd98e4b05d96
## explicit; go 1.17
github.com/daodao97/fly
github.com/daodao97/fly/interval/hook
github.com/daodao97/fly/interval/util
github.com/daodao97/fly/interval/xtype
# github.com/davecgh/go-spew v1.1.1
## explicit
github.com/davecgh/go-spew/spew
@ -143,6 +149,9 @@ github.com/dustin/go-humanize
# github.com/edsrzf/mmap-go v1.0.0
## explicit
github.com/edsrzf/mmap-go
# github.com/fatih/color v1.13.0
## explicit; go 1.13
github.com/fatih/color
# github.com/gin-contrib/sse v0.1.0
## explicit; go 1.12
github.com/gin-contrib/sse
@ -350,14 +359,20 @@ github.com/lesismal/sqlw
github.com/lib/pq
github.com/lib/pq/oid
github.com/lib/pq/scram
# github.com/lqs/sqlingo v0.11.1
## explicit
github.com/lqs/sqlingo
# github.com/mailru/go-clickhouse/v2 v2.0.0
## explicit; go 1.11
github.com/mailru/go-clickhouse/v2
# github.com/mattn/go-colorable v0.1.9
## explicit; go 1.13
github.com/mattn/go-colorable
# github.com/mattn/go-isatty v0.0.14
## explicit; go 1.12
github.com/mattn/go-isatty
# github.com/mattn/go-sqlite3 v1.14.13
## explicit; go 1.12
# github.com/mattn/go-sqlite3 v2.0.3+incompatible
## explicit
github.com/mattn/go-sqlite3
# github.com/mitchellh/mapstructure v1.5.0
## explicit; go 1.14
@ -445,6 +460,9 @@ github.com/shopspring/decimal
# github.com/sirupsen/logrus v1.8.1
## explicit; go 1.13
github.com/sirupsen/logrus
# github.com/spf13/cast v1.5.0
## explicit; go 1.18
github.com/spf13/cast
# github.com/stretchr/testify v1.8.0
## explicit; go 1.13
github.com/stretchr/testify/assert
@ -861,6 +879,9 @@ modernc.org/strutil
# modernc.org/zappy v1.0.3
## explicit; go 1.15
modernc.org/zappy
# muzzammil.xyz/jsonc v1.0.0
## explicit
muzzammil.xyz/jsonc
# xorm.io/builder v0.3.12
## explicit; go 1.11
xorm.io/builder

@ -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…
Cancel
Save