Compare commits

..

30 Commits

Author SHA1 Message Date
Kevin Wan
e31128650e Revert "🐞 fix(gen): pg gen of insert (#1591)" (#1598)
This reverts commit cc4c4928e0.
2022-03-01 20:27:59 +08:00
Kevin Wan
168740b64d chore: upgrade etcd (#1597) 2022-03-01 20:16:44 +08:00
toutou_o
cc4c4928e0 🐞 fix(gen): pg gen of insert (#1591)
Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-03-01 19:53:23 +08:00
Fyn
fba6543b23 fix: goctl api dart support form tag (#1596) 2022-03-01 16:17:37 +08:00
Kevin Wan
877eb6ac56 Update readme.md
add producthunt.
2022-03-01 16:11:17 +08:00
Kevin Wan
259a5a13e7 chore: fix data race (#1593) 2022-02-28 23:17:51 +08:00
Fyn
cf7c7cb392 build: update goctl dependency ddl-parser to v1.0.3 (#1586)
* build: update goctl dependency ddl-parser to v1.0.3

* fix: race condition when testing logx

Resolves: #1587
2022-02-28 17:31:59 +08:00
ccx
86d01e2e99 test: add testcase for FIFO Queue in collection module (#1589)
cover the case of non-zero value for q.Header when q.Elements expands
2022-02-28 17:15:11 +08:00
Kevin Wan
7a28e19a27 Update readme-cn.md 2022-02-27 23:30:14 +08:00
Kevin Wan
900ea63d68 Update readme-cn.md
add migration notice.
2022-02-27 23:29:45 +08:00
Kevin Wan
87ab86cdd0 Update readme.md 2022-02-27 23:26:03 +08:00
Kevin Wan
0697494ffd Update readme.md
Add migrate steps.
2022-02-27 23:25:22 +08:00
anqiansong
ffd69a2f5e Fix bug int overflow while build goctl on arch 386 (#1582)
Co-authored-by: anqiansong <anqiansong@bytedance.com>
2022-02-27 10:51:57 +08:00
Kevin Wan
66f10bb5e6 chore: add goctl command help (#1578) 2022-02-26 17:02:04 +08:00
Kevin Wan
8131a0e777 Update readme.md
update chat link.
2022-02-25 23:02:09 +08:00
Kevin Wan
32a557dff6 Update readme.md
add discord.
2022-02-25 23:01:15 +08:00
Fyn
db949e40f1 feat: supports importValue for more path formats (#1569)
`importValueRegex` now can match more path formats

Resolves: #1568
2022-02-25 11:16:57 +08:00
Kevin Wan
e0454138e0 update goctl to go 1.16 for io/fs usage (#1571)
* update goctl to go 1.16 for io/fs usage

* feat: support pg serial type for auto_increment (#1563)

* add correct example for pg's url

* 🐞 fix: merge

* 🐞 fix: pg default port

*  feat: support serial type

Co-authored-by: kurimi1 <d0n41df@gmail.com>

* chore: format code

Co-authored-by: toutou_o <33993460+kurimi1@users.noreply.github.com>
Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-02-24 13:58:53 +08:00
toutou_o
3b07ed1b97 feat: support pg serial type for auto_increment (#1563)
* add correct example for pg's url

* 🐞 fix: merge

* 🐞 fix: pg default port

*  feat: support serial type

Co-authored-by: kurimi1 <d0n41df@gmail.com>
2022-02-24 13:39:31 +08:00
anqiansong
daa98f5a27 Feature: Add goctl env (#1557) 2022-02-21 10:19:33 +08:00
Kevin Wan
842656aa90 feat: log 404 requests with traceid (#1554) 2022-02-19 20:50:33 +08:00
Kevin Wan
aa29036cb3 feat: support ctx in sql model generation (#1551) 2022-02-17 10:28:55 +08:00
Kevin Wan
607bae27fa feat: support ctx in sqlx/sqlc, listed in ROADMAP (#1535)
* feat: support ctx in sqlx/sqlc

* chore: update roadmap

* fix: context.Canceled should be acceptable

* use %w to wrap errors

* chore: remove unused vars
2022-02-16 19:31:43 +08:00
Kevin Wan
7c63676be4 docs: add go-zero users (#1546) 2022-02-16 12:02:52 +08:00
Kevin Wan
9e113909b3 ignore context.Canceled for redis breaker (#1545) 2022-02-15 21:31:30 +08:00
Kevin Wan
bd105474ca chore: update help message (#1544) 2022-02-15 21:19:40 +08:00
Mikael
a078f5d764 add the serviceAccount of deployment (#1543)
Co-authored-by: 977231903@qq.com <>
2022-02-15 20:57:14 +08:00
Kevin Wan
b215fa3ee6 fix #1541 (#1542) 2022-02-15 18:40:26 +08:00
mlr3000
50b1928502 chore:use struct pointer (#1538) 2022-02-15 11:34:48 +08:00
Kevin Wan
493e3bcf4b docs: update roadmap (#1537) 2022-02-15 08:37:03 +08:00
88 changed files with 2246 additions and 529 deletions

View File

@@ -20,9 +20,9 @@ We hope that the items listed below will inspire further engagement from the com
- [x] Support `goctl bug` to report bugs conveniently
## 2022
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
- [x] Support `context` in redis related methods for timeout and tracing
- [x] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing
- [ ] Add `httpx.Client` with governance, like circuit breaker etc.
- [ ] Support `goctl doctor` command to report potential issues for given service
- [ ] Support `context` in redis related methods for timeout and tracing
- [ ] Support `context` in sql related methods for timeout and tracing
- [ ] Support `context` in mongodb related methods for timeout and tracing
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file

View File

@@ -61,3 +61,41 @@ func TestPutMore(t *testing.T) {
assert.Equal(t, string(element), string(body.([]byte)))
}
}
func TestPutMoreWithHeaderNotZero(t *testing.T) {
elements := [][]byte{
[]byte("hello"),
[]byte("world"),
[]byte("again"),
}
queue := NewQueue(4)
for i := range elements {
queue.Put(elements[i])
}
// take 1
body, ok := queue.Take()
assert.True(t, ok)
element, ok := body.([]byte)
assert.True(t, ok)
assert.Equal(t, element, []byte("hello"))
// put more
queue.Put([]byte("b4"))
queue.Put([]byte("b5")) // will store in elements[0]
queue.Put([]byte("b6")) // cause expansion
results := [][]byte{
[]byte("world"),
[]byte("again"),
[]byte("b4"),
[]byte("b5"),
[]byte("b6"),
}
for _, element := range results {
body, ok := queue.Take()
assert.True(t, ok)
assert.Equal(t, string(element), string(body.([]byte)))
}
}

View File

@@ -11,10 +11,12 @@ type (
errorArray []error
)
// Add adds err to be.
func (be *BatchError) Add(err error) {
if err != nil {
be.errs = append(be.errs, err)
// Add adds errs to be, nil errors are ignored.
func (be *BatchError) Add(errs ...error) {
for _, err := range errs {
if err != nil {
be.errs = append(be.errs, err)
}
}
}

View File

@@ -3,6 +3,7 @@ package logx
import (
"fmt"
"io"
"sync/atomic"
"time"
"github.com/zeromicro/go-zero/core/timex"
@@ -79,7 +80,7 @@ func (l *durationLogger) WithDuration(duration time.Duration) Logger {
}
func (l *durationLogger) write(writer io.Writer, level string, val interface{}) {
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val, l.Duration)
default:

View File

@@ -3,6 +3,7 @@ package logx
import (
"log"
"strings"
"sync/atomic"
"testing"
"time"
@@ -38,10 +39,10 @@ func TestWithDurationInfo(t *testing.T) {
}
func TestWithDurationInfoConsole(t *testing.T) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
var builder strings.Builder

View File

@@ -75,7 +75,7 @@ var (
timeFormat = "2006-01-02T15:04:05.000Z07:00"
writeConsole bool
logLevel uint32
encoding = jsonEncodingType
encoding uint32 = jsonEncodingType
// use uint32 for atomic operations
disableStat uint32
infoLog io.WriteCloser
@@ -137,9 +137,9 @@ func SetUp(c LogConf) error {
}
switch c.Encoding {
case plainEncoding:
encoding = plainEncodingType
atomic.StoreUint32(&encoding, plainEncodingType)
default:
encoding = jsonEncodingType
atomic.StoreUint32(&encoding, jsonEncodingType)
}
switch c.Mode {
@@ -424,7 +424,7 @@ func infoTextSync(msg string) {
}
func outputAny(writer io.Writer, level string, val interface{}) {
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val)
default:
@@ -438,7 +438,7 @@ func outputAny(writer io.Writer, level string, val interface{}) {
}
func outputText(writer io.Writer, level, msg string) {
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainText(writer, level, msg)
default:

View File

@@ -145,10 +145,10 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(v)
@@ -159,10 +159,10 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(fmt.Sprint(v...))
@@ -173,10 +173,10 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(errors.New(fmt.Sprint(v...)))
@@ -187,10 +187,10 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Infov(ValStringer{
@@ -203,10 +203,10 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
doTestStructedLogConsole(t, func(writer io.WriteCloser) {
infoLog = writer
}, func(v ...interface{}) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, plainEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
Info(fmt.Sprint(v...))

View File

@@ -29,9 +29,9 @@ func TestRedirector(t *testing.T) {
}
func captureOutput(f func()) string {
atomic.StoreUint32(&initialized, 1)
writer := new(mockWriter)
infoLog = writer
atomic.StoreUint32(&initialized, 1)
prevLevel := atomic.LoadUint32(&logLevel)
SetLevel(InfoLevel)
@@ -44,5 +44,9 @@ func captureOutput(f func()) string {
func getContent(jsonStr string) string {
var entry logEntry
json.Unmarshal([]byte(jsonStr), &entry)
return entry.Content.(string)
val, ok := entry.Content.(string)
if ok {
return val
}
return ""
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"sync/atomic"
"time"
"github.com/zeromicro/go-zero/core/timex"
@@ -80,7 +81,7 @@ func (l *traceLogger) write(writer io.Writer, level string, val interface{}) {
traceID := traceIdFromContext(l.ctx)
spanID := spanIdFromContext(l.ctx)
switch encoding {
switch atomic.LoadUint32(&encoding) {
case plainEncodingType:
writePlainAny(writer, level, val, l.Duration, traceID, spanID)
default:

View File

@@ -83,10 +83,10 @@ func TestTraceInfo(t *testing.T) {
}
func TestTraceInfoConsole(t *testing.T) {
old := encoding
encoding = plainEncodingType
old := atomic.LoadUint32(&encoding)
atomic.StoreUint32(&encoding, jsonEncodingType)
defer func() {
encoding = old
atomic.StoreUint32(&encoding, old)
}()
var buf mockWriter

View File

@@ -937,7 +937,6 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
reader = strings.NewReader("chenquan")
err = UnmarshalYamlReader(reader, &v)
assert.ErrorIs(t, err, ErrUnsupportedType)
}
func TestUnmarshalYamlBadReader(t *testing.T) {

View File

@@ -2358,7 +2358,7 @@ func WithTLS() Option {
}
func acceptable(err error) bool {
return err == nil || err == red.Nil
return err == nil || err == red.Nil || err == context.Canceled
}
func getRedis(r *Redis) (RedisNode, error) {

View File

@@ -1,6 +1,7 @@
package sqlc
import (
"context"
"database/sql"
"time"
@@ -18,19 +19,27 @@ var (
ErrNotFound = sqlx.ErrNotFound
// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
exclusiveCalls = syncx.NewSingleFlight()
stats = cache.NewStat("sqlc")
singleFlights = syncx.NewSingleFlight()
stats = cache.NewStat("sqlc")
)
type (
// ExecFn defines the sql exec method.
ExecFn func(conn sqlx.SqlConn) (sql.Result, error)
// ExecCtxFn defines the sql exec method.
ExecCtxFn func(ctx context.Context, conn sqlx.SqlConn) (sql.Result, error)
// IndexQueryFn defines the query method that based on unique indexes.
IndexQueryFn func(conn sqlx.SqlConn, v interface{}) (interface{}, error)
// IndexQueryCtxFn defines the query method that based on unique indexes.
IndexQueryCtxFn func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (interface{}, error)
// PrimaryQueryFn defines the query method that based on primary keys.
PrimaryQueryFn func(conn sqlx.SqlConn, v, primary interface{}) error
// PrimaryQueryCtxFn defines the query method that based on primary keys.
PrimaryQueryCtxFn func(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error
// QueryFn defines the query method.
QueryFn func(conn sqlx.SqlConn, v interface{}) error
// QueryCtxFn defines the query method.
QueryCtxFn func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error
// A CachedConn is a DB connection with cache capability.
CachedConn struct {
@@ -41,7 +50,7 @@ type (
// NewConn returns a CachedConn with a redis cluster cache.
func NewConn(db sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) CachedConn {
cc := cache.New(c, exclusiveCalls, stats, sql.ErrNoRows, opts...)
cc := cache.New(c, singleFlights, stats, sql.ErrNoRows, opts...)
return NewConnWithCache(db, cc)
}
@@ -55,28 +64,46 @@ func NewConnWithCache(db sqlx.SqlConn, c cache.Cache) CachedConn {
// NewNodeConn returns a CachedConn with a redis node cache.
func NewNodeConn(db sqlx.SqlConn, rds *redis.Redis, opts ...cache.Option) CachedConn {
c := cache.NewNode(rds, exclusiveCalls, stats, sql.ErrNoRows, opts...)
c := cache.NewNode(rds, singleFlights, stats, sql.ErrNoRows, opts...)
return NewConnWithCache(db, c)
}
// DelCache deletes cache with keys.
func (cc CachedConn) DelCache(keys ...string) error {
return cc.cache.Del(keys...)
return cc.DelCacheCtx(context.Background(), keys...)
}
// DelCacheCtx deletes cache with keys.
func (cc CachedConn) DelCacheCtx(ctx context.Context, keys ...string) error {
return cc.cache.DelCtx(ctx, keys...)
}
// GetCache unmarshals cache with given key into v.
func (cc CachedConn) GetCache(key string, v interface{}) error {
return cc.cache.Get(key, v)
return cc.GetCacheCtx(context.Background(), key, v)
}
// GetCacheCtx unmarshals cache with given key into v.
func (cc CachedConn) GetCacheCtx(ctx context.Context, key string, v interface{}) error {
return cc.cache.GetCtx(ctx, key, v)
}
// Exec runs given exec on given keys, and returns execution result.
func (cc CachedConn) Exec(exec ExecFn, keys ...string) (sql.Result, error) {
res, err := exec(cc.db)
execCtx := func(_ context.Context, conn sqlx.SqlConn) (sql.Result, error) {
return exec(conn)
}
return cc.ExecCtx(context.Background(), execCtx, keys...)
}
// ExecCtx runs given exec on given keys, and returns execution result.
func (cc CachedConn) ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string) (sql.Result, error) {
res, err := exec(ctx, cc.db)
if err != nil {
return nil, err
}
if err := cc.DelCache(keys...); err != nil {
if err := cc.DelCacheCtx(ctx, keys...); err != nil {
return nil, err
}
@@ -85,31 +112,61 @@ func (cc CachedConn) Exec(exec ExecFn, keys ...string) (sql.Result, error) {
// ExecNoCache runs exec with given sql statement, without affecting cache.
func (cc CachedConn) ExecNoCache(q string, args ...interface{}) (sql.Result, error) {
return cc.db.Exec(q, args...)
return cc.ExecNoCacheCtx(context.Background(), q, args...)
}
// ExecNoCacheCtx runs exec with given sql statement, without affecting cache.
func (cc CachedConn) ExecNoCacheCtx(ctx context.Context, q string, args ...interface{}) (
sql.Result, error) {
return cc.db.ExecCtx(ctx, q, args...)
}
// QueryRow unmarshals into v with given key and query func.
func (cc CachedConn) QueryRow(v interface{}, key string, query QueryFn) error {
return cc.cache.Take(v, key, func(v interface{}) error {
return query(cc.db, v)
queryCtx := func(_ context.Context, conn sqlx.SqlConn, v interface{}) error {
return query(conn, v)
}
return cc.QueryRowCtx(context.Background(), v, key, queryCtx)
}
// QueryRowCtx unmarshals into v with given key and query func.
func (cc CachedConn) QueryRowCtx(ctx context.Context, v interface{}, key string, query QueryCtxFn) error {
return cc.cache.TakeCtx(ctx, v, key, func(v interface{}) error {
return query(ctx, cc.db, v)
})
}
// QueryRowIndex unmarshals into v with given key.
func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary interface{}) string,
indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {
indexQueryCtx := func(_ context.Context, conn sqlx.SqlConn, v interface{}) (interface{}, error) {
return indexQuery(conn, v)
}
primaryQueryCtx := func(_ context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
return primaryQuery(conn, v, primary)
}
return cc.QueryRowIndexCtx(context.Background(), v, key, keyer, indexQueryCtx, primaryQueryCtx)
}
// QueryRowIndexCtx unmarshals into v with given key.
func (cc CachedConn) QueryRowIndexCtx(ctx context.Context, v interface{}, key string,
keyer func(primary interface{}) string, indexQuery IndexQueryCtxFn,
primaryQuery PrimaryQueryCtxFn) error {
var primaryKey interface{}
var found bool
if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) {
primaryKey, err = indexQuery(cc.db, v)
if err != nil {
return
}
if err := cc.cache.TakeWithExpireCtx(ctx, &primaryKey, key,
func(val interface{}, expire time.Duration) (err error) {
primaryKey, err = indexQuery(ctx, cc.db, v)
if err != nil {
return
}
found = true
return cc.cache.SetWithExpire(keyer(primaryKey), v, expire+cacheSafeGapBetweenIndexAndPrimary)
}); err != nil {
found = true
return cc.cache.SetWithExpireCtx(ctx, keyer(primaryKey), v,
expire+cacheSafeGapBetweenIndexAndPrimary)
}); err != nil {
return err
}
@@ -117,28 +174,54 @@ func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary
return nil
}
return cc.cache.Take(v, keyer(primaryKey), func(v interface{}) error {
return primaryQuery(cc.db, v, primaryKey)
return cc.cache.TakeCtx(ctx, v, keyer(primaryKey), func(v interface{}) error {
return primaryQuery(ctx, cc.db, v, primaryKey)
})
}
// QueryRowNoCache unmarshals into v with given statement.
func (cc CachedConn) QueryRowNoCache(v interface{}, q string, args ...interface{}) error {
return cc.db.QueryRow(v, q, args...)
return cc.QueryRowNoCacheCtx(context.Background(), v, q, args...)
}
// QueryRowNoCacheCtx unmarshals into v with given statement.
func (cc CachedConn) QueryRowNoCacheCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return cc.db.QueryRowCtx(ctx, v, q, args...)
}
// QueryRowsNoCache unmarshals into v with given statement.
// It doesn't use cache, because it might cause consistency problem.
func (cc CachedConn) QueryRowsNoCache(v interface{}, q string, args ...interface{}) error {
return cc.db.QueryRows(v, q, args...)
return cc.QueryRowsNoCacheCtx(context.Background(), v, q, args...)
}
// QueryRowsNoCacheCtx unmarshals into v with given statement.
// It doesn't use cache, because it might cause consistency problem.
func (cc CachedConn) QueryRowsNoCacheCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return cc.db.QueryRowsCtx(ctx, v, q, args...)
}
// SetCache sets v into cache with given key.
func (cc CachedConn) SetCache(key string, v interface{}) error {
return cc.cache.Set(key, v)
func (cc CachedConn) SetCache(key string, val interface{}) error {
return cc.SetCacheCtx(context.Background(), key, val)
}
// SetCacheCtx sets v into cache with given key.
func (cc CachedConn) SetCacheCtx(ctx context.Context, key string, val interface{}) error {
return cc.cache.SetCtx(ctx, key, val)
}
// Transact runs given fn in transaction mode.
func (cc CachedConn) Transact(fn func(sqlx.Session) error) error {
return cc.db.Transact(fn)
fnCtx := func(_ context.Context, session sqlx.Session) error {
return fn(session)
}
return cc.TransactCtx(context.Background(), fnCtx)
}
// TransactCtx runs given fn in transaction mode.
func (cc CachedConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
return cc.db.TransactCtx(ctx, fn)
}

View File

@@ -1,6 +1,7 @@
package sqlc
import (
"context"
"database/sql"
"encoding/json"
"errors"
@@ -568,7 +569,7 @@ func TestNewConnWithCache(t *testing.T) {
defer clean()
var conn trackedConn
c := NewConnWithCache(&conn, cache.NewNode(r, exclusiveCalls, stats, sql.ErrNoRows))
c := NewConnWithCache(&conn, cache.NewNode(r, singleFlights, stats, sql.ErrNoRows))
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
assert.Nil(t, err)
assert.True(t, conn.execValue)
@@ -585,6 +586,30 @@ type dummySqlConn struct {
queryRow func(interface{}, string, ...interface{}) error
}
func (d dummySqlConn) ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
func (d dummySqlConn) PrepareCtx(ctx context.Context, query string) (sqlx.StmtSession, error) {
return nil, nil
}
func (d dummySqlConn) QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (d dummySqlConn) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (d dummySqlConn) QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (d dummySqlConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
return nil
}
func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
@@ -594,6 +619,10 @@ func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) {
}
func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error {
return d.QueryRowCtx(context.Background(), v, query, args...)
}
func (d dummySqlConn) QueryRowCtx(_ context.Context, v interface{}, query string, args ...interface{}) error {
if d.queryRow != nil {
return d.queryRow(v, query, args...)
}
@@ -628,13 +657,21 @@ type trackedConn struct {
}
func (c *trackedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return c.ExecCtx(context.Background(), query, args...)
}
func (c *trackedConn) ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
c.execValue = true
return c.dummySqlConn.Exec(query, args...)
return c.dummySqlConn.ExecCtx(ctx, query, args...)
}
func (c *trackedConn) QueryRows(v interface{}, query string, args ...interface{}) error {
return c.QueryRowsCtx(context.Background(), v, query, args...)
}
func (c *trackedConn) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
c.queryRowsValue = true
return c.dummySqlConn.QueryRows(v, query, args...)
return c.dummySqlConn.QueryRowsCtx(ctx, v, query, args...)
}
func (c *trackedConn) RawDB() (*sql.DB, error) {
@@ -642,6 +679,12 @@ func (c *trackedConn) RawDB() (*sql.DB, error) {
}
func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error {
c.transactValue = true
return c.dummySqlConn.Transact(fn)
return c.TransactCtx(context.Background(), func(_ context.Context, session sqlx.Session) error {
return fn(session)
})
}
func (c *trackedConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
c.transactValue = true
return c.dummySqlConn.TransactCtx(ctx, fn)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"strconv"
@@ -17,12 +18,40 @@ type mockedConn struct {
execErr error
}
func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
func (c *mockedConn) ExecCtx(_ context.Context, query string, args ...interface{}) (sql.Result, error) {
c.query = query
c.args = args
return nil, c.execErr
}
func (c *mockedConn) PrepareCtx(ctx context.Context, query string) (StmtSession, error) {
panic("implement me")
}
func (c *mockedConn) QueryRowCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
panic("implement me")
}
func (c *mockedConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error {
panic("should not called")
}
func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return c.ExecCtx(context.Background(), query, args...)
}
func (c *mockedConn) Prepare(query string) (StmtSession, error) {
panic("should not called")
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"testing"
@@ -16,7 +17,7 @@ func TestUnmarshalRowBool(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value bool
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.True(t, value)
@@ -29,7 +30,7 @@ func TestUnmarshalRowBoolNotSettable(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value bool
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
assert.NotNil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select value from users where user=?", "anyone"))
})
@@ -41,7 +42,7 @@ func TestUnmarshalRowInt(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, 2, value)
@@ -54,7 +55,7 @@ func TestUnmarshalRowInt8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, int8(3), value)
@@ -67,7 +68,7 @@ func TestUnmarshalRowInt16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.Equal(t, int16(4), value)
@@ -80,7 +81,7 @@ func TestUnmarshalRowInt32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.Equal(t, int32(5), value)
@@ -93,7 +94,7 @@ func TestUnmarshalRowInt64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value int64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, int64(6), value)
@@ -106,7 +107,7 @@ func TestUnmarshalRowUint(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint(2), value)
@@ -119,7 +120,7 @@ func TestUnmarshalRowUint8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint8(3), value)
@@ -132,7 +133,7 @@ func TestUnmarshalRowUint16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint16(4), value)
@@ -145,7 +146,7 @@ func TestUnmarshalRowUint32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint32(5), value)
@@ -158,7 +159,7 @@ func TestUnmarshalRowUint64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value uint64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, uint16(6), value)
@@ -171,7 +172,7 @@ func TestUnmarshalRowFloat32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value float32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, float32(7), value)
@@ -184,7 +185,7 @@ func TestUnmarshalRowFloat64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value float64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, float64(8), value)
@@ -198,7 +199,7 @@ func TestUnmarshalRowString(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value string
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -215,7 +216,7 @@ func TestUnmarshalRowStruct(t *testing.T) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select name, age from users where user=?", "anyone"))
assert.Equal(t, "liao", value.Name)
@@ -233,7 +234,7 @@ func TestUnmarshalRowStructWithTags(t *testing.T) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select name, age from users where user=?", "anyone"))
assert.Equal(t, "liao", value.Name)
@@ -251,7 +252,7 @@ func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
rs := sqlmock.NewRows([]string{"name"}).FromCSVString("liao")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
assert.NotNil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select name, age from users where user=?", "anyone"))
})
@@ -264,7 +265,7 @@ func TestUnmarshalRowsBool(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []bool
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -278,7 +279,7 @@ func TestUnmarshalRowsInt(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -292,7 +293,7 @@ func TestUnmarshalRowsInt8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -306,7 +307,7 @@ func TestUnmarshalRowsInt16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -320,7 +321,7 @@ func TestUnmarshalRowsInt32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -334,7 +335,7 @@ func TestUnmarshalRowsInt64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []int64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -348,7 +349,7 @@ func TestUnmarshalRowsUint(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -362,7 +363,7 @@ func TestUnmarshalRowsUint8(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -376,7 +377,7 @@ func TestUnmarshalRowsUint16(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -390,7 +391,7 @@ func TestUnmarshalRowsUint32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -404,7 +405,7 @@ func TestUnmarshalRowsUint64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []uint64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -418,7 +419,7 @@ func TestUnmarshalRowsFloat32(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []float32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -432,7 +433,7 @@ func TestUnmarshalRowsFloat64(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []float64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -446,7 +447,7 @@ func TestUnmarshalRowsString(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []string
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -462,7 +463,7 @@ func TestUnmarshalRowsBoolPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*bool
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -478,7 +479,7 @@ func TestUnmarshalRowsIntPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -494,7 +495,7 @@ func TestUnmarshalRowsInt8Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -510,7 +511,7 @@ func TestUnmarshalRowsInt16Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -526,7 +527,7 @@ func TestUnmarshalRowsInt32Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -542,7 +543,7 @@ func TestUnmarshalRowsInt64Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*int64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -558,7 +559,7 @@ func TestUnmarshalRowsUintPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -574,7 +575,7 @@ func TestUnmarshalRowsUint8Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint8
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -590,7 +591,7 @@ func TestUnmarshalRowsUint16Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint16
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -606,7 +607,7 @@ func TestUnmarshalRowsUint32Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -622,7 +623,7 @@ func TestUnmarshalRowsUint64Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*uint64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -638,7 +639,7 @@ func TestUnmarshalRowsFloat32Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*float32
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -654,7 +655,7 @@ func TestUnmarshalRowsFloat64Ptr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*float64
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -670,7 +671,7 @@ func TestUnmarshalRowsStringPtr(t *testing.T) {
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
var value []*string
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select value from users where user=?", "anyone"))
assert.EqualValues(t, expect, value)
@@ -699,7 +700,7 @@ func TestUnmarshalRowsStruct(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -739,7 +740,7 @@ func TestUnmarshalRowsStructWithNullStringType(t *testing.T) {
rs := sqlmock.NewRows([]string{"name", "value"}).AddRow(
"first", "firstnullstring").AddRow("second", nil)
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -773,7 +774,7 @@ func TestUnmarshalRowsStructWithTags(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -814,7 +815,7 @@ func TestUnmarshalRowsStructAndEmbeddedAnonymousStructWithTags(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age", "value"}).FromCSVString("first,2,3\nsecond,3,4")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age, value from users where user=?", "anyone"))
@@ -856,7 +857,7 @@ func TestUnmarshalRowsStructAndEmbeddedStructPtrAnonymousWithTags(t *testing.T)
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age", "value"}).FromCSVString("first,2,3\nsecond,3,4")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age, value from users where user=?", "anyone"))
@@ -890,7 +891,7 @@ func TestUnmarshalRowsStructPtr(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -923,7 +924,7 @@ func TestUnmarshalRowsStructWithTagsPtr(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -956,7 +957,7 @@ func TestUnmarshalRowsStructWithTagsPtrWithInnerPtr(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRows(&value, rows, true)
}, "select name, age from users where user=?", "anyone"))
@@ -976,7 +977,7 @@ func TestCommonSqlConn_QueryRowOptional(t *testing.T) {
User string `db:"user"`
Age int `db:"age"`
}
assert.Nil(t, query(db, func(rows *sql.Rows) error {
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
return unmarshalRow(&r, rows, false)
}, "select age from users where user=?", "anyone"))
assert.Empty(t, r.User)
@@ -1027,7 +1028,7 @@ func TestUnmarshalRowError(t *testing.T) {
User string `db:"user"`
Age int `db:"age"`
}
test.validate(query(db, func(rows *sql.Rows) error {
test.validate(query(context.Background(), db, func(rows *sql.Rows) error {
scanner := mockedScanner{
colErr: test.colErr,
scanErr: test.scanErr,

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"github.com/zeromicro/go-zero/core/breaker"
@@ -14,11 +15,17 @@ type (
// Session stands for raw connections or transaction sessions
Session interface {
Exec(query string, args ...interface{}) (sql.Result, error)
ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
Prepare(query string) (StmtSession, error)
PrepareCtx(ctx context.Context, query string) (StmtSession, error)
QueryRow(v interface{}, query string, args ...interface{}) error
QueryRowCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
QueryRowPartial(v interface{}, query string, args ...interface{}) error
QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
QueryRows(v interface{}, query string, args ...interface{}) error
QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
QueryRowsPartial(v interface{}, query string, args ...interface{}) error
QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error
}
// SqlConn only stands for raw connections, so Transact method can be called.
@@ -27,7 +34,8 @@ type (
// RawDB is for other ORM to operate with, use it with caution.
// Notice: don't close it.
RawDB() (*sql.DB, error)
Transact(func(session Session) error) error
Transact(fn func(Session) error) error
TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error
}
// SqlOption defines the method to customize a sql connection.
@@ -37,10 +45,15 @@ type (
StmtSession interface {
Close() error
Exec(args ...interface{}) (sql.Result, error)
ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error)
QueryRow(v interface{}, args ...interface{}) error
QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRowPartial(v interface{}, args ...interface{}) error
QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRows(v interface{}, args ...interface{}) error
QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRowsPartial(v interface{}, args ...interface{}) error
QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error
}
// thread-safe
@@ -58,7 +71,9 @@ type (
sessionConn interface {
Exec(query string, args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
}
statement struct {
@@ -68,7 +83,9 @@ type (
stmtConn interface {
Exec(args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
Query(args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error)
}
)
@@ -112,6 +129,11 @@ func NewSqlConnFromDB(db *sql.DB, opts ...SqlOption) SqlConn {
}
func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result, err error) {
return db.ExecCtx(context.Background(), q, args...)
}
func (db *commonSqlConn) ExecCtx(ctx context.Context, q string, args ...interface{}) (
result sql.Result, err error) {
err = db.brk.DoWithAcceptable(func() error {
var conn *sql.DB
conn, err = db.connProv()
@@ -120,7 +142,7 @@ func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result,
return err
}
result, err = exec(conn, q, args...)
result, err = exec(ctx, conn, q, args...)
return err
}, db.acceptable)
@@ -128,6 +150,10 @@ func (db *commonSqlConn) Exec(q string, args ...interface{}) (result sql.Result,
}
func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
return db.PrepareCtx(context.Background(), query)
}
func (db *commonSqlConn) PrepareCtx(ctx context.Context, query string) (stmt StmtSession, err error) {
err = db.brk.DoWithAcceptable(func() error {
var conn *sql.DB
conn, err = db.connProv()
@@ -136,7 +162,7 @@ func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
return err
}
st, err := conn.Prepare(query)
st, err := conn.PrepareContext(ctx, query)
if err != nil {
return err
}
@@ -152,25 +178,45 @@ func (db *commonSqlConn) Prepare(query string) (stmt StmtSession, err error) {
}
func (db *commonSqlConn) QueryRow(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true)
}, q, args...)
}
func (db *commonSqlConn) QueryRowPartial(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowPartialCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowPartialCtx(ctx context.Context, v interface{},
q string, args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false)
}, q, args...)
}
func (db *commonSqlConn) QueryRows(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowsCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowsCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true)
}, q, args...)
}
func (db *commonSqlConn) QueryRowsPartial(v interface{}, q string, args ...interface{}) error {
return db.queryRows(func(rows *sql.Rows) error {
return db.QueryRowsPartialCtx(context.Background(), v, q, args...)
}
func (db *commonSqlConn) QueryRowsPartialCtx(ctx context.Context, v interface{},
q string, args ...interface{}) error {
return db.queryRows(ctx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false)
}, q, args...)
}
@@ -180,13 +226,19 @@ func (db *commonSqlConn) RawDB() (*sql.DB, error) {
}
func (db *commonSqlConn) Transact(fn func(Session) error) error {
return db.TransactCtx(context.Background(), func(_ context.Context, session Session) error {
return fn(session)
})
}
func (db *commonSqlConn) TransactCtx(ctx context.Context, fn func(context.Context, Session) error) error {
return db.brk.DoWithAcceptable(func() error {
return transact(db, db.beginTx, fn)
return transact(ctx, db, db.beginTx, fn)
}, db.acceptable)
}
func (db *commonSqlConn) acceptable(err error) bool {
ok := err == nil || err == sql.ErrNoRows || err == sql.ErrTxDone
ok := err == nil || err == sql.ErrNoRows || err == sql.ErrTxDone || err == context.Canceled
if db.accept == nil {
return ok
}
@@ -194,7 +246,8 @@ func (db *commonSqlConn) acceptable(err error) bool {
return ok || db.accept(err)
}
func (db *commonSqlConn) queryRows(scanner func(*sql.Rows) error, q string, args ...interface{}) error {
func (db *commonSqlConn) queryRows(ctx context.Context, scanner func(*sql.Rows) error,
q string, args ...interface{}) error {
var qerr error
return db.brk.DoWithAcceptable(func() error {
conn, err := db.connProv()
@@ -203,7 +256,7 @@ func (db *commonSqlConn) queryRows(scanner func(*sql.Rows) error, q string, args
return err
}
return query(conn, func(rows *sql.Rows) error {
return query(ctx, conn, func(rows *sql.Rows) error {
qerr = scanner(rows)
return qerr
}, q, args...)
@@ -217,29 +270,49 @@ func (s statement) Close() error {
}
func (s statement) Exec(args ...interface{}) (sql.Result, error) {
return execStmt(s.stmt, s.query, args...)
return s.ExecCtx(context.Background(), args...)
}
func (s statement) ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error) {
return execStmt(ctx, s.stmt, s.query, args...)
}
func (s statement) QueryRow(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowCtx(context.Background(), v, args...)
}
func (s statement) QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true)
}, s.query, args...)
}
func (s statement) QueryRowPartial(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowPartialCtx(context.Background(), v, args...)
}
func (s statement) QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false)
}, s.query, args...)
}
func (s statement) QueryRows(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowsCtx(context.Background(), v, args...)
}
func (s statement) QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true)
}, s.query, args...)
}
func (s statement) QueryRowsPartial(v interface{}, args ...interface{}) error {
return queryStmt(s.stmt, func(rows *sql.Rows) error {
return s.QueryRowsPartialCtx(context.Background(), v, args...)
}
func (s statement) QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error {
return queryStmt(ctx, s.stmt, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false)
}, s.query, args...)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"time"
@@ -18,64 +19,65 @@ func SetSlowThreshold(threshold time.Duration) {
slowThreshold.Set(threshold)
}
func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
func exec(ctx context.Context, conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
stmt, err := format(q, args...)
if err != nil {
return nil, err
}
startTime := timex.Now()
result, err := conn.Exec(q, args...)
result, err := conn.ExecContext(ctx, q, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql exec: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql exec: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
}
return result, err
}
func execStmt(conn stmtConn, q string, args ...interface{}) (sql.Result, error) {
func execStmt(ctx context.Context, conn stmtConn, q string, args ...interface{}) (sql.Result, error) {
stmt, err := format(q, args...)
if err != nil {
return nil, err
}
startTime := timex.Now()
result, err := conn.Exec(args...)
result, err := conn.ExecContext(ctx, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql execStmt: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql execStmt: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
}
return result, err
}
func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...interface{}) error {
func query(ctx context.Context, conn sessionConn, scanner func(*sql.Rows) error,
q string, args ...interface{}) error {
stmt, err := format(q, args...)
if err != nil {
return err
}
startTime := timex.Now()
rows, err := conn.Query(q, args...)
rows, err := conn.QueryContext(ctx, q, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql query: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql query: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
return err
}
defer rows.Close()
@@ -83,22 +85,23 @@ func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...in
return scanner(rows)
}
func queryStmt(conn stmtConn, scanner func(*sql.Rows) error, q string, args ...interface{}) error {
func queryStmt(ctx context.Context, conn stmtConn, scanner func(*sql.Rows) error,
q string, args ...interface{}) error {
stmt, err := format(q, args...)
if err != nil {
return err
}
startTime := timex.Now()
rows, err := conn.Query(args...)
rows, err := conn.QueryContext(ctx, args...)
duration := timex.Since(startTime)
if duration > slowThreshold.Load() {
logx.WithDuration(duration).Slowf("[SQL] queryStmt: slowcall - %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Slowf("[SQL] queryStmt: slowcall - %s", stmt)
} else {
logx.WithDuration(duration).Infof("sql queryStmt: %s", stmt)
logx.WithContext(ctx).WithDuration(duration).Infof("sql queryStmt: %s", stmt)
}
if err != nil {
logSqlError(stmt, err)
logSqlError(ctx, stmt, err)
return err
}
defer rows.Close()

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"testing"
@@ -57,7 +58,7 @@ func TestStmt_exec(t *testing.T) {
test := test
fns := []func(args ...interface{}) (sql.Result, error){
func(args ...interface{}) (sql.Result, error) {
return exec(&mockedSessionConn{
return exec(context.Background(), &mockedSessionConn{
lastInsertId: test.lastInsertId,
rowsAffected: test.rowsAffected,
err: test.err,
@@ -65,7 +66,7 @@ func TestStmt_exec(t *testing.T) {
}, test.query, args...)
},
func(args ...interface{}) (sql.Result, error) {
return execStmt(&mockedStmtConn{
return execStmt(context.Background(), &mockedStmtConn{
lastInsertId: test.lastInsertId,
rowsAffected: test.rowsAffected,
err: test.err,
@@ -137,7 +138,7 @@ func TestStmt_query(t *testing.T) {
test := test
fns := []func(args ...interface{}) error{
func(args ...interface{}) error {
return query(&mockedSessionConn{
return query(context.Background(), &mockedSessionConn{
err: test.err,
delay: test.delay,
}, func(rows *sql.Rows) error {
@@ -145,7 +146,7 @@ func TestStmt_query(t *testing.T) {
}, test.query, args...)
},
func(args ...interface{}) error {
return queryStmt(&mockedStmtConn{
return queryStmt(context.Background(), &mockedStmtConn{
err: test.err,
delay: test.delay,
}, func(rows *sql.Rows) error {
@@ -185,6 +186,10 @@ type mockedSessionConn struct {
}
func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result, error) {
return m.ExecContext(context.Background(), query, args...)
}
func (m *mockedSessionConn) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
@@ -195,6 +200,10 @@ func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result,
}
func (m *mockedSessionConn) Query(query string, args ...interface{}) (*sql.Rows, error) {
return m.QueryContext(context.Background(), query, args...)
}
func (m *mockedSessionConn) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
@@ -214,6 +223,10 @@ type mockedStmtConn struct {
}
func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
return m.ExecContext(context.Background(), args...)
}
func (m *mockedStmtConn) ExecContext(_ context.Context, _ ...interface{}) (sql.Result, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}
@@ -224,6 +237,10 @@ func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
}
func (m *mockedStmtConn) Query(args ...interface{}) (*sql.Rows, error) {
return m.QueryContext(context.Background(), args...)
}
func (m *mockedStmtConn) QueryContext(_ context.Context, _ ...interface{}) (*sql.Rows, error) {
if m.delay {
time.Sleep(defaultSlowThreshold + time.Millisecond)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"fmt"
)
@@ -26,11 +27,19 @@ func NewSessionFromTx(tx *sql.Tx) Session {
}
func (t txSession) Exec(q string, args ...interface{}) (sql.Result, error) {
return exec(t.Tx, q, args...)
return t.ExecCtx(context.Background(), q, args...)
}
func (t txSession) ExecCtx(ctx context.Context, q string, args ...interface{}) (sql.Result, error) {
return exec(ctx, t.Tx, q, args...)
}
func (t txSession) Prepare(q string) (StmtSession, error) {
stmt, err := t.Tx.Prepare(q)
return t.PrepareCtx(context.Background(), q)
}
func (t txSession) PrepareCtx(ctx context.Context, q string) (StmtSession, error) {
stmt, err := t.Tx.PrepareContext(ctx, q)
if err != nil {
return nil, err
}
@@ -42,25 +51,43 @@ func (t txSession) Prepare(q string) (StmtSession, error) {
}
func (t txSession) QueryRow(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, true)
}, q, args...)
}
func (t txSession) QueryRowPartial(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowPartialCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowPartialCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRow(v, rows, false)
}, q, args...)
}
func (t txSession) QueryRows(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowsCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowsCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, true)
}, q, args...)
}
func (t txSession) QueryRowsPartial(v interface{}, q string, args ...interface{}) error {
return query(t.Tx, func(rows *sql.Rows) error {
return t.QueryRowsPartialCtx(context.Background(), v, q, args...)
}
func (t txSession) QueryRowsPartialCtx(ctx context.Context, v interface{}, q string,
args ...interface{}) error {
return query(ctx, t.Tx, func(rows *sql.Rows) error {
return unmarshalRows(v, rows, false)
}, q, args...)
}
@@ -76,17 +103,19 @@ func begin(db *sql.DB) (trans, error) {
}, nil
}
func transact(db *commonSqlConn, b beginnable, fn func(Session) error) (err error) {
func transact(ctx context.Context, db *commonSqlConn, b beginnable,
fn func(context.Context, Session) error) (err error) {
conn, err := db.connProv()
if err != nil {
db.onError(err)
return err
}
return transactOnConn(conn, b, fn)
return transactOnConn(ctx, conn, b, fn)
}
func transactOnConn(conn *sql.DB, b beginnable, fn func(Session) error) (err error) {
func transactOnConn(ctx context.Context, conn *sql.DB, b beginnable,
fn func(context.Context, Session) error) (err error) {
var tx trans
tx, err = b(conn)
if err != nil {
@@ -96,18 +125,18 @@ func transactOnConn(conn *sql.DB, b beginnable, fn func(Session) error) (err err
defer func() {
if p := recover(); p != nil {
if e := tx.Rollback(); e != nil {
err = fmt.Errorf("recover from %#v, rollback failed: %s", p, e)
err = fmt.Errorf("recover from %#v, rollback failed: %w", p, e)
} else {
err = fmt.Errorf("recoveer from %#v", p)
}
} else if err != nil {
if e := tx.Rollback(); e != nil {
err = fmt.Errorf("transaction failed: %s, rollback failed: %s", err, e)
err = fmt.Errorf("transaction failed: %s, rollback failed: %w", err, e)
}
} else {
err = tx.Commit()
}
}()
return fn(tx)
return fn(ctx, tx)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"database/sql"
"errors"
"testing"
@@ -26,26 +27,50 @@ func (mt *mockTx) Exec(q string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
func (mt *mockTx) ExecCtx(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return nil, nil
}
func (mt *mockTx) Prepare(query string) (StmtSession, error) {
return nil, nil
}
func (mt *mockTx) PrepareCtx(ctx context.Context, query string) (StmtSession, error) {
return nil, nil
}
func (mt *mockTx) QueryRow(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowPartial(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRows(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowsCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowsPartial(v interface{}, q string, args ...interface{}) error {
return nil
}
func (mt *mockTx) QueryRowsPartialCtx(ctx context.Context, v interface{}, query string, args ...interface{}) error {
return nil
}
func (mt *mockTx) Rollback() error {
mt.status |= mockRollback
return nil
@@ -59,18 +84,20 @@ func beginMock(mock *mockTx) beginnable {
func TestTransactCommit(t *testing.T) {
mock := &mockTx{}
err := transactOnConn(nil, beginMock(mock), func(Session) error {
return nil
})
err := transactOnConn(context.Background(), nil, beginMock(mock),
func(context.Context, Session) error {
return nil
})
assert.Equal(t, mockCommit, mock.status)
assert.Nil(t, err)
}
func TestTransactRollback(t *testing.T) {
mock := &mockTx{}
err := transactOnConn(nil, beginMock(mock), func(Session) error {
return errors.New("rollback")
})
err := transactOnConn(context.Background(), nil, beginMock(mock),
func(context.Context, Session) error {
return errors.New("rollback")
})
assert.Equal(t, mockRollback, mock.status)
assert.NotNil(t, err)
}

View File

@@ -1,6 +1,7 @@
package sqlx
import (
"context"
"fmt"
"strconv"
"strings"
@@ -109,9 +110,9 @@ func logInstanceError(datasource string, err error) {
logx.Errorf("Error on getting sql instance of %s: %v", datasource, err)
}
func logSqlError(stmt string, err error) {
func logSqlError(ctx context.Context, stmt string, err error) {
if err != nil && err != ErrNotFound {
logx.Errorf("stmt: %s, error: %s", stmt, err.Error())
logx.WithContext(ctx).Errorf("stmt: %s, error: %s", stmt, err.Error())
}
}

13
go.mod
View File

@@ -17,8 +17,8 @@ require (
github.com/prometheus/client_golang v1.11.0
github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.7.0
go.etcd.io/etcd/api/v3 v3.5.1
go.etcd.io/etcd/client/v3 v3.5.1
go.etcd.io/etcd/api/v3 v3.5.2
go.etcd.io/etcd/client/v3 v3.5.2
go.opentelemetry.io/otel v1.3.0
go.opentelemetry.io/otel/exporters/jaeger v1.3.0
go.opentelemetry.io/otel/exporters/zipkin v1.3.0
@@ -44,8 +44,11 @@ require (
github.com/go-redis/redis/v8 v8.11.4
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/openzipkin/zipkin-go v0.4.0 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
google.golang.org/genproto v0.0.0-20220211171837-173942840c17 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 // indirect
k8s.io/klog/v2 v2.40.1 // indirect
)

20
go.sum
View File

@@ -52,6 +52,7 @@ github.com/alicebob/miniredis/v2 v2.17.0 h1:EwLdrIS50uczw71Jc7iVSxZluTKj5nfSP8n7
github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -378,10 +379,16 @@ github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5u
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI=
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE=
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.1 h1:oImGuV5LGKjCqXdjkMHCyWa5OO1gYKCnC/1sgdfj1Uk=
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -399,14 +406,21 @@ go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKu
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -485,6 +499,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -548,6 +564,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -648,6 +666,8 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220211171837-173942840c17 h1:2X+CNIheCutWRyKRte8szGxrE5ggtV4U+NKAbh/oLhg=
google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View File

@@ -13,7 +13,15 @@
> ***缩短从需求到上线的距离***
**注意:为了满足开源基金会要求go-zero 从好未来tal-tech组织下迁移至中立的 GitHub 组织zeromicro。**
**为了满足开源基金会要求go-zero 从好未来tal-tech组织下迁移至中立的 GitHub 组织zeromicro。**
> ***注意:***
>
> 从 v1.3.0 之前版本升级请执行以下命令:
>
> GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
>
> goctl migrate —verbose —version v1.3.0
## 0. go-zero 介绍
@@ -242,6 +250,8 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>54. 一犀科技成都有限公司
>55. 北京术杰科技有限公司
>56. 时代脉搏网络科技(云浮市)有限公司
>57. 店有帮
>58. 七牛云
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。

View File

@@ -9,9 +9,19 @@ English | [简体中文](readme-cn.md)
[![Go Report Card](https://goreportcard.com/badge/github.com/zeromicro/go-zero)](https://goreportcard.com/report/github.com/zeromicro/go-zero)
[![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe)
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
**Note: To meet the requirements of Open Source Foundation, we moved go-zero from tal-tech to zeromicro (a neutral GitHub organization).**
> ***Important!***
>
> To upgrade from previous versions, run the following commands.
>
> `go install github.com/zeromicro/go-zero/tools/goctl@latest`
>
> `goctl migrate —verbose —version v1.3.0`
## 0. what is go-zero
go-zero (listed in CNCF Landscape: [https://landscape.cncf.io/?selected=go-zero](https://landscape.cncf.io/?selected=go-zero)) is a web and rpc framework with lots of builtin engineering practices. Its born to ensure the stability of the busy services with resilience design and has been serving sites with tens of millions of users for years.
@@ -221,7 +231,7 @@ go get -u github.com/zeromicro/go-zero
## 9. Chat group
Join the chat via https://join.slack.com/t/go-zero/shared_invite/zt-10ruju779-BE4y6lQNB_R21samtyKTgA
Join the chat via https://discord.gg/4JQvC5A4Fe
## 10. Cloud Native Landscape

View File

@@ -14,6 +14,7 @@ import (
"github.com/zeromicro/go-zero/rest/handler"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal"
"github.com/zeromicro/go-zero/rest/internal/response"
)
// use 1000m to represent 100%
@@ -154,6 +155,27 @@ func (ng *engine) getShedder(priority bool) load.Shedder {
return ng.shedder
}
// notFoundHandler returns a middleware that handles 404 not found requests.
func (ng *engine) notFoundHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
chain := alice.New(
handler.TracingHandler(ng.conf.Name, ""),
ng.getLogHandler(),
)
var h http.Handler
if next != nil {
h = chain.Then(next)
} else {
h = chain.Then(http.NotFoundHandler())
}
cw := response.NewHeaderOnceResponseWriter(w)
h.ServeHTTP(cw, r)
cw.WriteHeader(http.StatusNotFound)
})
}
func (ng *engine) setTlsConfig(cfg *tls.Config) {
ng.tlsConfig = cfg
}

View File

@@ -1,13 +1,17 @@
package rest
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
)
func TestNewEngine(t *testing.T) {
@@ -190,6 +194,75 @@ func TestEngine_checkedTimeout(t *testing.T) {
}
}
func TestEngine_notFoundHandler(t *testing.T) {
logx.Disable()
ng := newEngine(RestConf{})
ts := httptest.NewServer(ng.notFoundHandler(nil))
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
assert.Nil(t, err)
res, err := client.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, res.StatusCode)
return res.Body.Close()
}(context.Background())
assert.Nil(t, err)
}
func TestEngine_notFoundHandlerNotNil(t *testing.T) {
logx.Disable()
ng := newEngine(RestConf{})
var called int32
ts := httptest.NewServer(ng.notFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&called, 1)
})))
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
assert.Nil(t, err)
res, err := client.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, res.StatusCode)
return res.Body.Close()
}(context.Background())
assert.Nil(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&called))
}
func TestEngine_notFoundHandlerNotNilWriteHeader(t *testing.T) {
logx.Disable()
ng := newEngine(RestConf{})
var called int32
ts := httptest.NewServer(ng.notFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&called, 1)
w.WriteHeader(http.StatusExpectationFailed)
})))
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
assert.Nil(t, err)
res, err := client.Do(req)
assert.Nil(t, err)
assert.Equal(t, http.StatusExpectationFailed, res.StatusCode)
return res.Body.Close()
}(context.Background())
assert.Nil(t, err)
assert.Equal(t, int32(1), atomic.LoadInt32(&called))
}
type mockedRouter struct{}
func (m mockedRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) {

View File

@@ -1,15 +1,14 @@
package handler
import (
"bufio"
"context"
"errors"
"net"
"net/http"
"net/http/httputil"
"github.com/golang-jwt/jwt/v4"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/internal/response"
"github.com/zeromicro/go-zero/rest/token"
)
@@ -105,7 +104,7 @@ func detailAuthLog(r *http.Request, reason string) {
}
func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) {
writer := newGuardedResponseWriter(w)
writer := response.NewHeaderOnceResponseWriter(w)
if err != nil {
detailAuthLog(r, err.Error())
@@ -121,47 +120,3 @@ func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback Un
// if user not setting HTTP header, we set header with 401
writer.WriteHeader(http.StatusUnauthorized)
}
type guardedResponseWriter struct {
writer http.ResponseWriter
wroteHeader bool
}
func newGuardedResponseWriter(w http.ResponseWriter) *guardedResponseWriter {
return &guardedResponseWriter{
writer: w,
}
}
func (grw *guardedResponseWriter) Flush() {
if flusher, ok := grw.writer.(http.Flusher); ok {
flusher.Flush()
}
}
func (grw *guardedResponseWriter) Header() http.Header {
return grw.writer.Header()
}
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (grw *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := grw.writer.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
func (grw *guardedResponseWriter) Write(body []byte) (int, error) {
return grw.writer.Write(body)
}
func (grw *guardedResponseWriter) WriteHeader(statusCode int) {
if grw.wroteHeader {
return
}
grw.wroteHeader = true
grw.writer.WriteHeader(statusCode)
}

View File

@@ -90,26 +90,6 @@ func TestAuthHandler_NilError(t *testing.T) {
})
}
func TestAuthHandler_Flush(t *testing.T) {
resp := httptest.NewRecorder()
handler := newGuardedResponseWriter(resp)
handler.Flush()
assert.True(t, resp.Flushed)
}
func TestAuthHandler_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := newGuardedResponseWriter(resp)
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = newGuardedResponseWriter(mockedHijackable{resp})
assert.NotPanics(t, func() {
writer.Hijack()
})
}
func buildToken(secretKey string, payloads map[string]interface{}, seconds int64) (string, error) {
now := time.Now().Unix()
claims := make(jwt.MapClaims)

View File

@@ -9,7 +9,7 @@ import (
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal/security"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const breakerSeparator = "://"
@@ -28,7 +28,7 @@ func BreakerHandler(method, path string, metrics *stat.Metrics) func(http.Handle
return
}
cw := &security.WithCodeResponseWriter{Writer: w}
cw := &response.WithCodeResponseWriter{Writer: w}
defer func() {
if cw.Code < http.StatusInternalServerError {
promise.Accept()

View File

@@ -8,7 +8,7 @@ import (
"github.com/zeromicro/go-zero/core/metric"
"github.com/zeromicro/go-zero/core/prometheus"
"github.com/zeromicro/go-zero/core/timex"
"github.com/zeromicro/go-zero/rest/internal/security"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const serverNamespace = "http_server"
@@ -41,7 +41,7 @@ func PrometheusHandler(path string) func(http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
startTime := timex.Now()
cw := &security.WithCodeResponseWriter{Writer: w}
cw := &response.WithCodeResponseWriter{Writer: w}
defer func() {
metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path)
metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code))

View File

@@ -8,7 +8,7 @@ import (
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stat"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal/security"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const serviceType = "api"
@@ -41,7 +41,7 @@ func SheddingHandler(shedder load.Shedder, metrics *stat.Metrics) func(http.Hand
return
}
cw := &security.WithCodeResponseWriter{Writer: w}
cw := &response.WithCodeResponseWriter{Writer: w}
defer func() {
if cw.Code == http.StatusServiceUnavailable {
promise.Fail()

View File

@@ -18,12 +18,16 @@ func TracingHandler(serviceName, path string) func(http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
spanName := path
if len(spanName) == 0 {
spanName = r.URL.Path
}
spanCtx, span := tracer.Start(
ctx,
path,
spanName,
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(
serviceName, path, r)...),
serviceName, spanName, r)...),
)
defer span.End()

View File

@@ -6,6 +6,7 @@ import (
"net/http/httptest"
"testing"
"github.com/justinas/alice"
"github.com/stretchr/testify/assert"
ztrace "github.com/zeromicro/go-zero/core/trace"
"go.opentelemetry.io/otel"
@@ -21,28 +22,31 @@ func TestOtelHandler(t *testing.T) {
Sampler: 1.0,
})
ts := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
spanCtx := trace.SpanContextFromContext(ctx)
assert.Equal(t, true, spanCtx.IsValid())
}),
)
defer ts.Close()
for _, test := range []string{"", "bar"} {
t.Run(test, func(t *testing.T) {
h := alice.New(TracingHandler("foo", test)).Then(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
spanCtx := trace.SpanContextFromContext(ctx)
assert.True(t, spanCtx.IsValid())
}))
ts := httptest.NewServer(h)
defer ts.Close()
client := ts.Client()
err := func(ctx context.Context) error {
ctx, span := otel.Tracer("httptrace/client").Start(ctx, "test")
defer span.End()
client := ts.Client()
err := func(ctx context.Context) error {
ctx, span := otel.Tracer("httptrace/client").Start(ctx, "test")
defer span.End()
req, _ := http.NewRequest("GET", ts.URL, nil)
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
req, _ := http.NewRequest("GET", ts.URL, nil)
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
res, err := client.Do(req)
assert.Equal(t, err, nil)
_ = res.Body.Close()
return nil
}(context.Background())
res, err := client.Do(req)
assert.Nil(t, err)
return res.Body.Close()
}(context.Background())
assert.Equal(t, err, nil)
assert.Nil(t, err)
})
}
}

View File

@@ -1,10 +1,9 @@
package cors
import (
"bufio"
"errors"
"net"
"net/http"
"github.com/zeromicro/go-zero/rest/internal/response"
)
const (
@@ -30,7 +29,7 @@ const (
// At most one origin can be specified, other origins are ignored if given, default to be *.
func NotAllowedHandler(fn func(w http.ResponseWriter), origins ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gw := &guardedResponseWriter{w: w}
gw := response.NewHeaderOnceResponseWriter(w)
checkAndSetHeaders(gw, r, origins)
if fn != nil {
fn(gw)
@@ -62,44 +61,6 @@ func Middleware(fn func(w http.Header), origins ...string) func(http.HandlerFunc
}
}
type guardedResponseWriter struct {
w http.ResponseWriter
wroteHeader bool
}
func (w *guardedResponseWriter) Flush() {
if flusher, ok := w.w.(http.Flusher); ok {
flusher.Flush()
}
}
func (w *guardedResponseWriter) Header() http.Header {
return w.w.Header()
}
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (w *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := w.w.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
func (w *guardedResponseWriter) Write(bytes []byte) (int, error) {
return w.w.Write(bytes)
}
func (w *guardedResponseWriter) WriteHeader(code int) {
if w.wroteHeader {
return
}
w.w.WriteHeader(code)
w.wroteHeader = true
}
func checkAndSetHeaders(w http.ResponseWriter, r *http.Request, origins []string) {
setVaryHeaders(w, r)

View File

@@ -1,8 +1,6 @@
package cors
import (
"bufio"
"net"
"net/http"
"net/http/httptest"
"testing"
@@ -131,48 +129,3 @@ func TestCorsHandlerWithOrigins(t *testing.T) {
}
}
}
func TestGuardedResponseWriter_Flush(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
handler := NotAllowedHandler(func(w http.ResponseWriter) {
w.Header().Set("X-Test", "test")
w.WriteHeader(http.StatusServiceUnavailable)
_, err := w.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := w.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
}, "foo.com")
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}
func TestGuardedResponseWriter_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := &guardedResponseWriter{
w: resp,
}
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = &guardedResponseWriter{
w: mockedHijackable{resp},
}
assert.NotPanics(t, func() {
writer.Hijack()
})
}
type mockedHijackable struct {
*httptest.ResponseRecorder
}
func (m mockedHijackable) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return nil, nil, nil
}

View File

@@ -0,0 +1,57 @@
package response
import (
"bufio"
"errors"
"net"
"net/http"
)
// HeaderOnceResponseWriter is a http.ResponseWriter implementation
// that only the first WriterHeader takes effect.
type HeaderOnceResponseWriter struct {
w http.ResponseWriter
wroteHeader bool
}
// NewHeaderOnceResponseWriter returns a HeaderOnceResponseWriter.
func NewHeaderOnceResponseWriter(w http.ResponseWriter) http.ResponseWriter {
return &HeaderOnceResponseWriter{w: w}
}
// Flush flushes the response writer.
func (w *HeaderOnceResponseWriter) Flush() {
if flusher, ok := w.w.(http.Flusher); ok {
flusher.Flush()
}
}
// Header returns the http header.
func (w *HeaderOnceResponseWriter) Header() http.Header {
return w.w.Header()
}
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (w *HeaderOnceResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := w.w.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
// Write writes bytes into w.
func (w *HeaderOnceResponseWriter) Write(bytes []byte) (int, error) {
return w.w.Write(bytes)
}
// WriteHeader writes code into w, and not sealing the writer.
func (w *HeaderOnceResponseWriter) WriteHeader(code int) {
if w.wroteHeader {
return
}
w.w.WriteHeader(code)
w.wroteHeader = true
}

View File

@@ -0,0 +1,58 @@
package response
import (
"bufio"
"net"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHeaderOnceResponseWriter_Flush(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cw := NewHeaderOnceResponseWriter(w)
cw.Header().Set("X-Test", "test")
cw.WriteHeader(http.StatusServiceUnavailable)
cw.WriteHeader(http.StatusExpectationFailed)
_, err := cw.Write([]byte("content"))
assert.Nil(t, err)
flusher, ok := cw.(http.Flusher)
assert.True(t, ok)
flusher.Flush()
})
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}
func TestHeaderOnceResponseWriter_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := &HeaderOnceResponseWriter{
w: resp,
}
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = &HeaderOnceResponseWriter{
w: mockedHijackable{resp},
}
assert.NotPanics(t, func() {
writer.Hijack()
})
}
type mockedHijackable struct {
*httptest.ResponseRecorder
}
func (m mockedHijackable) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return nil, nil, nil
}

View File

@@ -1,7 +1,8 @@
package security
package response
import (
"bufio"
"errors"
"net"
"net/http"
)
@@ -27,7 +28,11 @@ func (w *WithCodeResponseWriter) Header() http.Header {
// Hijack implements the http.Hijacker interface.
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
func (w *WithCodeResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.Writer.(http.Hijacker).Hijack()
if hijacked, ok := w.Writer.(http.Hijacker); ok {
return hijacked.Hijack()
}
return nil, nil, errors.New("server doesn't support hijacking")
}
// Write writes bytes into w.

View File

@@ -1,4 +1,4 @@
package security
package response
import (
"net/http"
@@ -31,3 +31,20 @@ func TestWithCodeResponseWriter(t *testing.T) {
assert.Equal(t, "test", resp.Header().Get("X-Test"))
assert.Equal(t, "content", resp.Body.String())
}
func TestWithCodeResponseWriter_Hijack(t *testing.T) {
resp := httptest.NewRecorder()
writer := &WithCodeResponseWriter{
Writer: resp,
}
assert.NotPanics(t, func() {
writer.Hijack()
})
writer = &WithCodeResponseWriter{
Writer: mockedHijackable{resp},
}
assert.NotPanics(t, func() {
writer.Hijack()
})
}

View File

@@ -49,6 +49,7 @@ func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
router: router.NewRouter(),
}
opts = append([]RunOption{WithNotFoundHandler(nil)}, opts...)
for _, opt := range opts {
opt(server)
}
@@ -163,7 +164,8 @@ func WithMiddleware(middleware Middleware, rs ...Route) []Route {
// WithNotFoundHandler returns a RunOption with not found handler set to given handler.
func WithNotFoundHandler(handler http.Handler) RunOption {
return func(server *Server) {
server.router.SetNotFoundHandler(handler)
notFoundHandler := server.ngin.notFoundHandler(handler)
server.router.SetNotFoundHandler(notFoundHandler)
}
}

View File

@@ -19,12 +19,12 @@ class {{.Name}}{
});
factory {{.Name}}.fromJson(Map<String,dynamic> m) {
return {{.Name}}({{range .Members}}
{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{tagGet .Tag "json"}}']{{else if isClassListType .Type.Name}}(m['{{tagGet .Tag "json"}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{tagGet .Tag "json"}}']){{end}},{{end}}
{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{getPropertyFromMember .}}']{{else if isClassListType .Type.Name}}(m['{{getPropertyFromMember .}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{getPropertyFromMember .}}']){{end}},{{end}}
);
}
Map<String,dynamic> toJson() {
return { {{range .Members}}
'{{tagGet .Tag "json"}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
'{{getPropertyFromMember .}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
};
}
}

View File

@@ -34,18 +34,12 @@ func pathToFuncName(path string) string {
return util.ToLower(camel[:1]) + camel[1:]
}
func tagGet(tag, k string) string {
tags, err := spec.Parse(tag)
func getPropertyFromMember(member spec.Member) string {
name, err := member.GetPropertyName()
if err != nil {
panic(k + " not exist")
panic(fmt.Sprintf("cannot get property name of %q", member.Name))
}
v, err := tags.Get(k)
if err != nil {
panic(k + " value not exist")
}
return v.Name
return name
}
func isDirectType(s string) bool {

View File

@@ -0,0 +1,39 @@
package dartgen
import (
"testing"
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
)
func Test_getPropertyFromMember(t *testing.T) {
tests := []struct {
name string
member spec.Member
want string
}{
{
name: "json tag should be ok",
member: spec.Member{
Tag: "`json:\"foo\"`",
Name: "Foo",
},
want: "foo",
},
{
name: "form tag should be ok",
member: spec.Member{
Tag: "`form:\"bar\"`",
Name: "Bar",
},
want: "bar",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getPropertyFromMember(tt.member); got != tt.want {
t.Errorf("getPropertyFromMember() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -3,12 +3,12 @@ package dartgen
import "text/template"
var funcMap = template.FuncMap{
"tagGet": tagGet,
"isDirectType": isDirectType,
"isClassListType": isClassListType,
"getCoreType": getCoreType,
"pathToFuncName": pathToFuncName,
"lowCamelCase": lowCamelCase,
"getPropertyFromMember": getPropertyFromMember,
"isDirectType": isDirectType,
"isClassListType": isClassListType,
"getCoreType": getCoreType,
"pathToFuncName": pathToFuncName,
"lowCamelCase": lowCamelCase,
}
const (

View File

@@ -12,7 +12,7 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
// DocCommand generate markdown doc file
// DocCommand generate Markdown doc file
func DocCommand(c *cli.Context) error {
dir := c.String("dir")
if len(dir) == 0 {
@@ -45,7 +45,7 @@ func DocCommand(c *cli.Context) error {
for _, p := range files {
api, err := parser.Parse(p)
if err != nil {
return fmt.Errorf("parse file: %s, err: %s", p, err.Error())
return fmt.Errorf("parse file: %s, err: %w", p, err)
}
api.Service = api.Service.JoinPrefix()

View File

@@ -34,7 +34,7 @@ func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
{{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx)
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}req{{end}})
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
if err != nil {
httpx.Error(w, err)
} else {

View File

@@ -26,8 +26,8 @@ type {{.logic}} struct {
svcCtx *svc.ServiceContext
}
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) {{.logic}} {
return {{.logic}}{
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} {
return &{{.logic}}{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
@@ -73,7 +73,7 @@ func genLogicByRoute(dir, rootPkg string, cfg *config.Config, group spec.Group,
returnString = "return nil"
}
if len(route.RequestTypeName()) > 0 {
requestString = "req " + requestGoTypeName(route, typesPacket)
requestString = "req *" + requestGoTypeName(route, typesPacket)
}
subDir := getLogicFolderPath(group, route)

View File

@@ -8,8 +8,10 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/api/parser/g4/gen/api"
)
const prefixKey = "prefix"
const groupKey = "group"
const (
prefixKey = "prefix"
groupKey = "group"
)
// Api describes syntax for api
type Api struct {

View File

@@ -114,13 +114,16 @@ func (p *Parser) parse(filename, content string) (*Api, error) {
apiAstList = append(apiAstList, root)
for _, imp := range root.Import {
dir := filepath.Dir(p.src)
imp := filepath.Join(dir, imp.Value.Text())
data, err := p.readContent(imp)
impPath := strings.ReplaceAll(imp.Value.Text(), "\"", "")
if !filepath.IsAbs(impPath) {
impPath = filepath.Join(dir, impPath)
}
data, err := p.readContent(impPath)
if err != nil {
return nil, err
}
nestedApi, err := p.invoke(imp, data)
nestedApi, err := p.invoke(impPath, data)
if err != nil {
return nil, err
}

View File

@@ -12,7 +12,7 @@ import (
const (
versionRegex = `(?m)"v[1-9][0-9]*"`
importValueRegex = `(?m)"(/?[a-zA-Z0-9_#-])+\.api"`
importValueRegex = `(?m)"\/?(([a-zA-Z0-9.]+)+(\/?){1})+([a-zA-Z0-9]+)+\.api"`
tagRegex = `(?m)\x60[a-z]+:".+"\x60`
)

View File

@@ -9,3 +9,24 @@ import (
func TestMatch(t *testing.T) {
assert.False(t, matchRegex("v1ddd", versionRegex))
}
func TestImportRegex(t *testing.T) {
tests := []struct {
value string
matched bool
}{
{`"bar.api"`, true},
{`"foo/bar.api"`, true},
{`"/foo/bar.api"`, true},
{`"../foo/bar.api"`, true},
{`"../../foo/bar.api"`, true},
{`"bar..api"`, false},
{`"//bar.api"`, false},
}
for _, tt := range tests {
t.Run(tt.value, func(t *testing.T) {
assert.Equal(t, tt.matched, matchRegex(tt.value, importValueRegex))
})
}
}

View File

@@ -1,7 +1,10 @@
package completion
const BashCompletionFlag = `generate-goctl-completion`
const defaultCompletionFilename = "goctl_autocomplete"
const (
BashCompletionFlag = `generate-goctl-completion`
defaultCompletionFilename = "goctl_autocomplete"
)
const (
magic = 1 << iota
flagZsh

112
tools/goctl/env/check.go vendored Normal file
View File

@@ -0,0 +1,112 @@
package env
import (
"fmt"
"strings"
"time"
"github.com/urfave/cli"
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
"github.com/zeromicro/go-zero/tools/goctl/pkg/protoc"
"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengo"
"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengogrpc"
"github.com/zeromicro/go-zero/tools/goctl/util/console"
)
type bin struct {
name string
exists bool
get func(cacheDir string) (string, error)
}
var bins = []bin{
{
name: "protoc",
exists: protoc.Exists(),
get: protoc.Install,
},
{
name: "protoc-gen-go",
exists: protocgengo.Exists(),
get: protocgengo.Install,
},
{
name: "protoc-gen-go-grpc",
exists: protocgengogrpc.Exists(),
get: protocgengogrpc.Install,
},
}
func Check(ctx *cli.Context) error {
install := ctx.Bool("install")
force := ctx.Bool("force")
return check(install, force)
}
func check(install, force bool) error {
pending := true
console.Info("[goctl-env]: preparing to check env")
defer func() {
if p := recover(); p != nil {
console.Error("%+v", p)
return
}
if pending {
console.Success("\n[goctl-env]: congratulations! your goctl environment is ready!")
} else {
console.Error(`
[goctl-env]: check env finish, some dependencies is not found in PATH, you can execute
command 'goctl env check --install' or 'goctl env install' to install it, for details,
please see 'goctl env check --help' or 'goctl env install --help'`)
}
}()
for _, e := range bins {
time.Sleep(200 * time.Millisecond)
console.Info("")
console.Info("[goctl-env]: looking up %q", e.name)
if e.exists {
console.Success("[goctl-env]: %q is installed", e.name)
continue
}
console.Warning("[goctl-env]: %q is not found in PATH", e.name)
if install {
install := func() {
console.Info("[goctl-env]: preparing to install %q", e.name)
path, err := e.get(env.Get(env.GoctlCache))
if err != nil {
console.Error("[goctl-env]: an error interrupted the installation: %+v", err)
pending = false
} else {
console.Success("[goctl-env]: %q is already installed in %q", e.name, path)
}
}
if force {
install()
continue
}
console.Info("[goctl-env]: do you want to install %q [y: YES, n: No]", e.name)
for {
var in string
fmt.Scanln(&in)
var brk bool
switch {
case strings.EqualFold(in, "y"):
install()
brk = true
case strings.EqualFold(in, "n"):
pending = false
console.Info("[goctl-env]: %q installation is ignored", e.name)
brk = true
default:
console.Error("[goctl-env]: invalid input, input 'y' for yes, 'n' for no")
}
if brk {
break
}
}
} else {
pending = false
}
}
return nil
}

17
tools/goctl/env/env.go vendored Normal file
View File

@@ -0,0 +1,17 @@
package env
import (
"fmt"
"github.com/urfave/cli"
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
)
func Action(c *cli.Context) error {
write := c.StringSlice("write")
if len(write) > 0 {
return env.WriteEnv(write)
}
fmt.Println(env.Print())
return nil
}

View File

@@ -1,6 +1,6 @@
module github.com/zeromicro/go-zero/tools/goctl
go 1.15
go 1.16
require (
github.com/DATA-DOG/go-sqlmock v1.5.0
@@ -12,6 +12,6 @@ require (
github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.22.5
github.com/zeromicro/antlr v0.0.1
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348
github.com/zeromicro/ddl-parser v1.0.3
github.com/zeromicro/go-zero v1.3.0
)

View File

@@ -173,7 +173,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -272,7 +271,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -358,8 +356,8 @@ github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5u
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348 h1:OhxL9tn28gDeJVzreIUiE5oVxZCjL3tBJ0XBNw8p5R8=
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
github.com/zeromicro/ddl-parser v1.0.3 h1:hFecpbt0oPQMhHAbqG1tz78MUepHUnOkFJp1dvRBFyc=
github.com/zeromicro/ddl-parser v1.0.3/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
github.com/zeromicro/go-zero v1.3.0 h1:Eyn36yBtR043sm4YKmxR6eS3UA/GtZDktQ+UqIJ3Lm0=
github.com/zeromicro/go-zero v1.3.0/go.mod h1:Hy4o1VFAt32lXaQMbaBhoFeZjA/rJqJ4PTGNdGsURcc=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
@@ -381,7 +379,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
@@ -417,7 +414,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -427,7 +423,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -574,12 +569,10 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -643,7 +636,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -22,6 +22,7 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/bug"
"github.com/zeromicro/go-zero/tools/goctl/completion"
"github.com/zeromicro/go-zero/tools/goctl/docker"
"github.com/zeromicro/go-zero/tools/goctl/env"
"github.com/zeromicro/go-zero/tools/goctl/internal/errorx"
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
"github.com/zeromicro/go-zero/tools/goctl/kube"
@@ -47,6 +48,34 @@ var commands = []cli.Command{
Usage: "upgrade goctl to latest version",
Action: upgrade.Upgrade,
},
{
Name: "env",
Usage: "check or edit goctl environment",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "write, w",
Usage: "edit goctl environment",
},
},
Subcommands: []cli.Command{
{
Name: "check",
Usage: "detect goctl env and dependency tools",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "install, i",
Usage: "install dependencies if not found",
},
cli.BoolFlag{
Name: "force, f",
Usage: "silent installation of non-existent dependencies",
},
},
Action: env.Check,
},
},
Action: env.Action,
},
{
Name: "migrate",
Usage: "migrate from tal-tech to zeromicro",
@@ -408,6 +437,10 @@ var commands = []cli.Command{
"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
"https://github.com/zeromicro/go-zero-template directory structure",
},
cli.StringFlag{
Name: "serviceAccount",
Usage: "the ServiceAccount for the deployment",
},
},
Action: kube.DeploymentCommand,
},
@@ -658,7 +691,7 @@ var commands = []cli.Command{
Flags: []cli.Flag{
cli.StringFlag{
Name: "url",
Usage: `the data source of database,like "postgres://root:password@127.0.0.1:54332/database?sslmode=disable"`,
Usage: `the data source of database,like "postgres://root:password@127.0.0.1:5432/database?sslmode=disable"`,
},
cli.StringFlag{
Name: "table, t",
@@ -825,7 +858,7 @@ func main() {
app.Version = fmt.Sprintf("%s %s/%s", version.BuildVersion, runtime.GOOS, runtime.GOARCH)
app.Commands = commands
// cli already print error messages
// cli already print error messages.
if err := app.Run(os.Args); err != nil {
fmt.Println(aurora.Red(errorx.Wrap(err).Error()))
os.Exit(codeFailure)

View File

@@ -2,14 +2,14 @@ package errorx
import (
"fmt"
"runtime"
"strings"
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
)
var errorFormat = `goctl: generation error: %+v
goctl version: %s
var errorFormat = `goctl error: %+v
goctl env:
%s
%s`
// GoctlError represents a goctl error.
@@ -20,8 +20,7 @@ type GoctlError struct {
func (e *GoctlError) Error() string {
detail := wrapMessage(e.message...)
v := fmt.Sprintf("%s %s/%s", version.BuildVersion, runtime.GOOS, runtime.GOARCH)
return fmt.Sprintf(errorFormat, e.err, v, detail)
return fmt.Sprintf(errorFormat, e.err, env.Print(), detail)
}
// Wrap wraps an error with goctl version and message.

View File

@@ -17,7 +17,8 @@ spec:
metadata:
labels:
app: {{.Name}}
spec:
spec:{{if .ServiceAccount}}
serviceAccountName: {{.ServiceAccount}}{{end}}
containers:
- name: {{.Name}}
image: {{.Image}}

View File

@@ -11,8 +11,9 @@ spec:
jobTemplate:
spec:
template:
spec:
containers:
spec:{{if .ServiceAccount}}
serviceAccountName: {{.ServiceAccount}}{{end}}
{{end}}containers:
- name: {{.Name}}
image: # todo image url
resources:

View File

@@ -21,21 +21,22 @@ const (
// Deployment describes the k8s deployment yaml
type Deployment struct {
Name string
Namespace string
Image string
Secret string
Replicas int
Revisions int
Port int
NodePort int
UseNodePort bool
RequestCpu int
RequestMem int
LimitCpu int
LimitMem int
MinReplicas int
MaxReplicas int
Name string
Namespace string
Image string
Secret string
Replicas int
Revisions int
Port int
NodePort int
UseNodePort bool
RequestCpu int
RequestMem int
LimitCpu int
LimitMem int
MinReplicas int
MaxReplicas int
ServiceAccount string
}
// DeploymentCommand is used to generate the kubernetes deployment yaml files.
@@ -72,21 +73,22 @@ func DeploymentCommand(c *cli.Context) error {
t := template.Must(template.New("deploymentTemplate").Parse(text))
err = t.Execute(out, Deployment{
Name: c.String("name"),
Namespace: c.String("namespace"),
Image: c.String("image"),
Secret: c.String("secret"),
Replicas: c.Int("replicas"),
Revisions: c.Int("revisions"),
Port: c.Int("port"),
NodePort: nodePort,
UseNodePort: nodePort > 0,
RequestCpu: c.Int("requestCpu"),
RequestMem: c.Int("requestMem"),
LimitCpu: c.Int("limitCpu"),
LimitMem: c.Int("limitMem"),
MinReplicas: c.Int("minReplicas"),
MaxReplicas: c.Int("maxReplicas"),
Name: c.String("name"),
Namespace: c.String("namespace"),
Image: c.String("image"),
Secret: c.String("secret"),
Replicas: c.Int("replicas"),
Revisions: c.Int("revisions"),
Port: c.Int("port"),
NodePort: nodePort,
UseNodePort: nodePort > 0,
RequestCpu: c.Int("requestCpu"),
RequestMem: c.Int("requestMem"),
LimitCpu: c.Int("limitCpu"),
LimitMem: c.Int("limitMem"),
MinReplicas: c.Int("minReplicas"),
MaxReplicas: c.Int("maxReplicas"),
ServiceAccount: c.String("serviceAccount"),
})
if err != nil {
return err

View File

@@ -164,12 +164,12 @@ func writeFile(pkgs []*ast.Package, verbose bool) error {
w := bytes.NewBuffer(nil)
err := format.Node(w, fset, file)
if err != nil {
return fmt.Errorf("[rewriteImport] format file %s error: %+v", filename, err)
return fmt.Errorf("[rewriteImport] format file %s error: %w", filename, err)
}
err = ioutil.WriteFile(filename, w.Bytes(), os.ModePerm)
if err != nil {
return fmt.Errorf("[rewriteImport] write file %s error: %+v", filename, err)
return fmt.Errorf("[rewriteImport] write file %s error: %w", filename, err)
}
if verbose {
console.Success("[OK] migrated %q successfully", filepath.Base(filename))

View File

@@ -9,8 +9,10 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
)
var defaultProxy = "https://goproxy.cn"
var defaultProxies = []string{defaultProxy}
var (
defaultProxy = "https://goproxy.cn"
defaultProxies = []string{defaultProxy}
)
func goProxy() []string {
wd, err := os.Getwd()

View File

@@ -108,6 +108,7 @@ func (m *PostgreSqlModel) getColumns(schema, table string, in []*PostgreColumn)
if err != nil {
return nil, err
}
var list []*Column
for _, e := range in {
var dft interface{}
@@ -120,9 +121,15 @@ func (m *PostgreSqlModel) getColumns(schema, table string, in []*PostgreColumn)
isNullAble = "NO"
}
extra := "auto_increment"
if e.IdentityIncrement.Int32 != 1 {
extra = ""
var extra string
// when identity is true, the column is auto increment
if e.IdentityIncrement.Int32 == 1 {
extra = "auto_increment"
}
// when type is serial, it's auto_increment. and the default value is tablename_columnname_seq
if strings.Contains(e.ColumnDefault.String, table+"_"+e.Field.String+"_seq") {
extra = "auto_increment"
}
if len(index[e.Field.String]) > 0 {
@@ -172,6 +179,7 @@ func (m *PostgreSqlModel) getIndex(schema, table string) (map[string][]*DbIndex,
if err != nil {
return nil, err
}
index := make(map[string][]*DbIndex)
for _, e := range indexes {
if e.IsPrimary.Bool {
@@ -193,6 +201,7 @@ func (m *PostgreSqlModel) getIndex(schema, table string) (map[string][]*DbIndex,
SeqInIndex: int(e.IndexSort.Int32),
})
}
return index, nil
}

View File

@@ -2,21 +2,21 @@ package template
// Delete defines a delete template
var Delete = `
func (m *default{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne({{.lowerStartCamelPrimaryKey}})
func (m *default{{.upperStartCamelObject}}Model) Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOneCtx(ctx, {{.lowerStartCamelPrimaryKey}})
if err!=nil{
return err
}{{end}}
}
{{.keys}}
_, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
{{end}} {{.keys}}
_, err {{if .containsIndexCache}}={{else}}:={{end}} m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table)
return conn.Exec(query, {{.lowerStartCamelPrimaryKey}})
return conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}})
}, {{.keyValues}}){{else}}query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table)
_,err:=m.conn.Exec(query, {{.lowerStartCamelPrimaryKey}}){{end}}
_,err:=m.conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}}){{end}}
return err
}
`
// DeleteMethod defines a delete template for interface method
var DeleteMethod = `Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error`
var DeleteMethod = `Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error`

View File

@@ -2,12 +2,12 @@ package template
// FindOne defines find row by id.
var FindOne = `
func (m *default{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) {
func (m *default{{.upperStartCamelObject}}Model) FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) {
{{if .withCache}}{{.cacheKey}}
var resp {{.upperStartCamelObject}}
err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error {
err := m.QueryRowCtx(ctx, &resp, {{.cacheKeyVariable}}, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} limit 1", {{.lowerStartCamelObject}}Rows, m.table)
return conn.QueryRow(v, query, {{.lowerStartCamelPrimaryKey}})
return conn.QueryRowCtx(ctx, v, query, {{.lowerStartCamelPrimaryKey}})
})
switch err {
case nil:
@@ -18,7 +18,7 @@ func (m *default{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrima
return nil, err
}{{else}}query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} limit 1", {{.lowerStartCamelObject}}Rows, m.table)
var resp {{.upperStartCamelObject}}
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelPrimaryKey}})
err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelPrimaryKey}})
switch err {
case nil:
return &resp, nil
@@ -35,9 +35,9 @@ var FindOneByField = `
func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) {
{{if .withCache}}{{.cacheKey}}
var resp {{.upperStartCamelObject}}
err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
err := m.QueryRowIndexCtx(ctx, &resp, {{.cacheKeyVariable}}, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where {{.originalField}} limit 1", {{.lowerStartCamelObject}}Rows, m.table)
if err := conn.QueryRow(&resp, query, {{.lowerStartCamelField}}); err != nil {
if err := conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}}); err != nil {
return nil, err
}
return resp.{{.upperStartCamelPrimaryKey}}, nil
@@ -52,7 +52,7 @@ func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}
}
}{{else}}var resp {{.upperStartCamelObject}}
query := fmt.Sprintf("select %s from %s where {{.originalField}} limit 1", {{.lowerStartCamelObject}}Rows, m.table )
err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelField}})
err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}})
switch err {
case nil:
return &resp, nil
@@ -70,14 +70,14 @@ func (m *default{{.upperStartCamelObject}}Model) formatPrimary(primary interface
return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary)
}
func (m *default{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
func (m *default{{.upperStartCamelObject}}Model) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = {{if .postgreSql}}$1{{else}}?{{end}} limit 1", {{.lowerStartCamelObject}}Rows, m.table )
return conn.QueryRow(v, query, primary)
return conn.QueryRowCtx(ctx, v, query, primary)
}
`
// FindOneMethod defines find row method.
var FindOneMethod = `FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error)`
var FindOneMethod = `FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error)`
// FindOneByFieldMethod defines find row by field method.
var FindOneByFieldMethod = `FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) `
var FindOneByFieldMethod = `FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) `

View File

@@ -3,6 +3,7 @@ package template
var (
// Imports defines a import template for model in cache case
Imports = `import (
"context"
"database/sql"
"fmt"
"strings"
@@ -17,6 +18,7 @@ var (
`
// ImportsNoCache defines a import template for model in normal case
ImportsNoCache = `import (
"context"
"database/sql"
"fmt"
"strings"

View File

@@ -2,18 +2,18 @@ package template
// Insert defines a template for insert code in model
var Insert = `
func (m *default{{.upperStartCamelObject}}Model) Insert(data *{{.upperStartCamelObject}}) (sql.Result,error) {
func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error) {
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
return conn.Exec(query, {{.expressionValues}})
return conn.ExecCtx(ctx, query, {{.expressionValues}})
}, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
ret,err:=m.ExecNoCache(query, {{.expressionValues}})
ret,err:=m.ExecNoCacheCtx(ctx, query, {{.expressionValues}})
{{end}}{{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
ret,err:=m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}}
return ret,err
}
`
// InsertMethod defines an interface method template for insert code in model
var InsertMethod = `Insert(data *{{.upperStartCamelObject}}) (sql.Result,error)`
var InsertMethod = `Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error)`

View File

@@ -2,16 +2,16 @@ package template
// Update defines a template for generating update codes
var Update = `
func (m *default{{.upperStartCamelObject}}Model) Update(data *{{.upperStartCamelObject}}) error {
func (m *default{{.upperStartCamelObject}}Model) Update(ctx context.Context, data *{{.upperStartCamelObject}}) error {
{{if .withCache}}{{.keys}}
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
return conn.Exec(query, {{.expressionValues}})
return conn.ExecCtx(ctx, query, {{.expressionValues}})
}, {{.keyValues}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
_,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
_,err:=m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}}
return err
}
`
// UpdateMethod defines an interface method template for generating update codes
var UpdateMethod = `Update(data *{{.upperStartCamelObject}}) error`
var UpdateMethod = `Update(ctx context.Context, data *{{.upperStartCamelObject}}) error`

View File

@@ -0,0 +1,210 @@
package sortedmap
import (
"container/list"
"errors"
"fmt"
"strings"
"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
)
var (
ErrInvalidKVExpression = errors.New(`invalid key-value expression`)
ErrInvalidKVS = errors.New("the length of kv must be a even number")
)
type KV []interface{}
type SortedMap struct {
kv *list.List
keys map[interface{}]*list.Element
}
func New() *SortedMap {
return &SortedMap{
kv: list.New(),
keys: make(map[interface{}]*list.Element),
}
}
func (m *SortedMap) SetExpression(expression string) (key interface{}, value interface{}, err error) {
idx := strings.Index(expression, "=")
if idx == -1 {
return "", "", ErrInvalidKVExpression
}
key = expression[:idx]
if len(expression) == idx {
value = ""
} else {
value = expression[idx+1:]
}
if keys, ok := key.(string); ok && stringx.ContainsWhiteSpace(keys) {
return "", "", ErrInvalidKVExpression
}
if values, ok := value.(string); ok && stringx.ContainsWhiteSpace(values) {
return "", "", ErrInvalidKVExpression
}
if len(key.(string)) == 0 {
return "", "", ErrInvalidKVExpression
}
m.SetKV(key, value)
return
}
func (m *SortedMap) SetKV(key, value interface{}) {
e, ok := m.keys[key]
if !ok {
e = m.kv.PushBack(KV{
key, value,
})
} else {
e.Value.(KV)[1] = value
}
m.keys[key] = e
}
func (m *SortedMap) Set(kv KV) error {
if len(kv) == 0 {
return nil
}
if len(kv)%2 != 0 {
return ErrInvalidKVS
}
for idx := 0; idx < len(kv); idx += 2 {
m.SetKV(kv[idx], kv[idx+1])
}
return nil
}
func (m *SortedMap) Get(key interface{}) (interface{}, bool) {
e, ok := m.keys[key]
if !ok {
return nil, false
}
return e.Value.(KV)[1], true
}
func (m *SortedMap) GetOr(key interface{}, dft interface{}) interface{} {
e, ok := m.keys[key]
if !ok {
return dft
}
return e.Value.(KV)[1]
}
func (m *SortedMap) GetString(key interface{}) (string, bool) {
value, ok := m.Get(key)
if !ok {
return "", false
}
vs, ok := value.(string)
return vs, ok
}
func (m *SortedMap) GetStringOr(key interface{}, dft string) string {
value, ok := m.GetString(key)
if !ok {
return dft
}
return value
}
func (m *SortedMap) HasKey(key interface{}) bool {
_, ok := m.keys[key]
return ok
}
func (m *SortedMap) HasValue(value interface{}) bool {
var contains bool
m.RangeIf(func(key, v interface{}) bool {
if value == v {
contains = true
return false
}
return true
})
return contains
}
func (m *SortedMap) Keys() []interface{} {
keys := make([]interface{}, 0)
next := m.kv.Front()
for next != nil {
keys = append(keys, next.Value.(KV)[0])
next = next.Next()
}
return keys
}
func (m *SortedMap) Values() []interface{} {
keys := m.Keys()
values := make([]interface{}, len(keys))
for idx, key := range keys {
values[idx] = m.keys[key].Value.(KV)[1]
}
return values
}
func (m *SortedMap) Range(iterator func(key, value interface{})) {
next := m.kv.Front()
for next != nil {
value := next.Value.(KV)
iterator(value[0], value[1])
next = next.Next()
}
}
func (m *SortedMap) RangeIf(iterator func(key, value interface{}) bool) {
next := m.kv.Front()
for next != nil {
value := next.Value.(KV)
loop := iterator(value[0], value[1])
if !loop {
return
}
next = next.Next()
}
}
func (m *SortedMap) Remove(key interface{}) (value interface{}, ok bool) {
v, ok := m.keys[key]
if !ok {
return nil, false
}
value = v.Value.(KV)[1]
ok = true
m.kv.Remove(v)
delete(m.keys, key)
return
}
func (m *SortedMap) Insert(sm *SortedMap) {
sm.Range(func(key, value interface{}) {
m.SetKV(key, value)
})
}
func (m *SortedMap) Copy() *SortedMap {
sm := New()
m.Range(func(key, value interface{}) {
sm.SetKV(key, value)
})
return sm
}
func (m *SortedMap) Format() []string {
format := make([]string, 0)
m.Range(func(key, value interface{}) {
format = append(format, fmt.Sprintf("%s=%s", key, value))
})
return format
}
func (m *SortedMap) Reset() {
m.kv.Init()
for key := range m.keys {
delete(m.keys, key)
}
}

View File

@@ -0,0 +1,235 @@
package sortedmap
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_SortedMap(t *testing.T) {
sm := New()
t.Run("SetExpression", func(t *testing.T) {
_, _, err := sm.SetExpression("")
assert.ErrorIs(t, err, ErrInvalidKVExpression)
_, _, err = sm.SetExpression("foo")
assert.ErrorIs(t, err, ErrInvalidKVExpression)
_, _, err = sm.SetExpression("foo= ")
assert.ErrorIs(t, err, ErrInvalidKVExpression)
_, _, err = sm.SetExpression(" foo=")
assert.ErrorIs(t, err, ErrInvalidKVExpression)
_, _, err = sm.SetExpression("foo =")
assert.ErrorIs(t, err, ErrInvalidKVExpression)
_, _, err = sm.SetExpression("=")
assert.ErrorIs(t, err, ErrInvalidKVExpression)
_, _, err = sm.SetExpression("=bar")
assert.ErrorIs(t, err, ErrInvalidKVExpression)
key, value, err := sm.SetExpression("foo=bar")
assert.Nil(t, err)
assert.Equal(t, "foo", key)
assert.Equal(t, "bar", value)
key, value, err = sm.SetExpression("foo=")
assert.Nil(t, err)
assert.Equal(t, value, sm.GetOr(key, ""))
sm.Reset()
})
t.Run("SetKV", func(t *testing.T) {
sm.SetKV("foo", "bar")
assert.Equal(t, "bar", sm.GetOr("foo", ""))
sm.SetKV("foo", "bar-changed")
assert.Equal(t, "bar-changed", sm.GetOr("foo", ""))
sm.Reset()
})
t.Run("Set", func(t *testing.T) {
err := sm.Set(KV{})
assert.Nil(t, err)
err = sm.Set(KV{"foo"})
assert.ErrorIs(t, ErrInvalidKVS, err)
err = sm.Set(KV{"foo", "bar", "bar", "foo"})
assert.Nil(t, err)
assert.Equal(t, "bar", sm.GetOr("foo", ""))
assert.Equal(t, "foo", sm.GetOr("bar", ""))
sm.Reset()
})
t.Run("Get", func(t *testing.T) {
_, ok := sm.Get("foo")
assert.False(t, ok)
sm.SetKV("foo", "bar")
value, ok := sm.Get("foo")
assert.True(t, ok)
assert.Equal(t, "bar", value)
sm.Reset()
})
t.Run("GetString", func(t *testing.T) {
_, ok := sm.GetString("foo")
assert.False(t, ok)
sm.SetKV("foo", "bar")
value, ok := sm.GetString("foo")
assert.True(t, ok)
assert.Equal(t, "bar", value)
sm.Reset()
})
t.Run("GetStringOr", func(t *testing.T) {
value := sm.GetStringOr("foo", "bar")
assert.Equal(t, "bar", value)
sm.SetKV("foo", "foo")
value = sm.GetStringOr("foo", "bar")
assert.Equal(t, "foo", value)
sm.Reset()
})
t.Run("GetOr", func(t *testing.T) {
value := sm.GetOr("foo", "bar")
assert.Equal(t, "bar", value)
sm.SetKV("foo", "foo")
value = sm.GetOr("foo", "bar")
assert.Equal(t, "foo", value)
sm.Reset()
})
t.Run("HasKey", func(t *testing.T) {
ok := sm.HasKey("foo")
assert.False(t, ok)
sm.SetKV("foo", "")
assert.True(t, sm.HasKey("foo"))
sm.Reset()
})
t.Run("HasValue", func(t *testing.T) {
assert.False(t, sm.HasValue("bar"))
sm.SetKV("foo", "bar")
assert.True(t, sm.HasValue("bar"))
sm.Reset()
})
t.Run("Keys", func(t *testing.T) {
keys := sm.Keys()
assert.Equal(t, 0, len(keys))
expected := []string{"foo1", "foo2", "foo3"}
for _, key := range expected {
sm.SetKV(key, "")
}
keys = sm.Keys()
var actual []string
for _, key := range keys {
actual = append(actual, key.(string))
}
assert.Equal(t, expected, actual)
sm.Reset()
})
t.Run("Values", func(t *testing.T) {
values := sm.Values()
assert.Equal(t, 0, len(values))
expected := []string{"foo1", "foo2", "foo3"}
for _, key := range expected {
sm.SetKV(key, key)
}
values = sm.Values()
var actual []string
for _, value := range values {
actual = append(actual, value.(string))
}
assert.Equal(t, expected, actual)
sm.Reset()
})
t.Run("Range", func(t *testing.T) {
var keys, values []string
sm.Range(func(key, value interface{}) {
keys = append(keys, key.(string))
values = append(values, value.(string))
})
assert.Len(t, keys, 0)
assert.Len(t, values, 0)
expected := []string{"foo1", "foo2", "foo3"}
for _, key := range expected {
sm.SetKV(key, key)
}
sm.Range(func(key, value interface{}) {
keys = append(keys, key.(string))
values = append(values, value.(string))
})
assert.Equal(t, expected, keys)
assert.Equal(t, expected, values)
sm.Reset()
})
t.Run("RangeIf", func(t *testing.T) {
var keys, values []string
sm.RangeIf(func(key, value interface{}) bool {
keys = append(keys, key.(string))
values = append(values, value.(string))
return true
})
assert.Len(t, keys, 0)
assert.Len(t, values, 0)
expected := []string{"foo1", "foo2", "foo3"}
for _, key := range expected {
sm.SetKV(key, key)
}
sm.RangeIf(func(key, value interface{}) bool {
keys = append(keys, key.(string))
values = append(values, value.(string))
if key.(string) == "foo1" {
return false
}
return true
})
assert.Equal(t, []string{"foo1"}, keys)
assert.Equal(t, []string{"foo1"}, values)
sm.Reset()
})
t.Run("Remove", func(t *testing.T) {
_, ok := sm.Remove("foo")
assert.False(t, ok)
sm.SetKV("foo", "bar")
value, ok := sm.Remove("foo")
assert.True(t, ok)
assert.Equal(t, "bar", value)
assert.False(t, sm.HasKey("foo"))
assert.False(t, sm.HasValue("bar"))
sm.Reset()
})
t.Run("Insert", func(t *testing.T) {
data := New()
data.SetKV("foo", "bar")
sm.SetKV("foo1", "bar1")
sm.Insert(data)
assert.True(t, sm.HasKey("foo"))
assert.True(t, sm.HasValue("bar"))
sm.Reset()
})
t.Run("Copy", func(t *testing.T) {
sm.SetKV("foo", "bar")
data := sm.Copy()
assert.True(t, data.HasKey("foo"))
assert.True(t, data.HasValue("bar"))
sm.SetKV("foo", "bar1")
assert.True(t, data.HasKey("foo"))
assert.True(t, data.HasValue("bar"))
sm.Reset()
})
t.Run("Format", func(t *testing.T) {
format := sm.Format()
assert.Equal(t, []string{}, format)
sm.SetKV("foo1", "bar1")
sm.SetKV("foo2", "bar2")
sm.SetKV("foo3", "")
format = sm.Format()
assert.Equal(t, []string{"foo1=bar1", "foo2=bar2", "foo3="}, format)
sm.Reset()
})
}

View File

@@ -0,0 +1,23 @@
package downloader
import (
"io"
"net/http"
"os"
)
func Download(url string, filename string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}

147
tools/goctl/pkg/env/env.go vendored Normal file
View File

@@ -0,0 +1,147 @@
package env
import (
"fmt"
"io/ioutil"
"log"
"path/filepath"
"runtime"
"strings"
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
sortedmap "github.com/zeromicro/go-zero/tools/goctl/pkg/collection"
"github.com/zeromicro/go-zero/tools/goctl/pkg/protoc"
"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengo"
"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengogrpc"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
var goctlEnv *sortedmap.SortedMap
const (
GoctlOS = "GOCTL_OS"
GoctlArch = "GOCTL_ARCH"
GoctlHome = "GOCTL_HOME"
GoctlDebug = "GOCTL_DEBUG"
GoctlCache = "GOCTL_CACHE"
GoctlVersion = "GOCTL_VERSION"
ProtocVersion = "PROTOC_VERSION"
ProtocGenGoVersion = "PROTOC_GEN_GO_VERSION"
ProtocGenGoGRPCVersion = "PROTO_GEN_GO_GRPC_VERSION"
envFileDir = "env"
)
// init initializes the goctl environment variables, the environment variables of the function are set in order,
// please do not change the logic order of the code.
func init() {
defaultGoctlHome, err := pathx.GetDefaultGoctlHome()
if err != nil {
log.Fatalln(err)
}
goctlEnv = sortedmap.New()
goctlEnv.SetKV(GoctlOS, runtime.GOOS)
goctlEnv.SetKV(GoctlArch, runtime.GOARCH)
existsEnv := readEnv(defaultGoctlHome)
if existsEnv != nil {
goctlHome, ok := existsEnv.GetString(GoctlHome)
if ok && len(goctlHome) > 0 {
goctlEnv.SetKV(GoctlHome, goctlHome)
}
if debug := existsEnv.GetOr(GoctlDebug, "").(string); debug != "" {
if strings.EqualFold(debug, "true") || strings.EqualFold(debug, "false") {
goctlEnv.SetKV(GoctlDebug, debug)
}
}
if value := existsEnv.GetStringOr(GoctlCache, ""); value != "" {
goctlEnv.SetKV(GoctlCache, value)
}
}
if !goctlEnv.HasKey(GoctlHome) {
goctlEnv.SetKV(GoctlHome, defaultGoctlHome)
}
if !goctlEnv.HasKey(GoctlDebug) {
goctlEnv.SetKV(GoctlDebug, "False")
}
if !goctlEnv.HasKey(GoctlCache) {
cacheDir, _ := pathx.GetCacheDir()
goctlEnv.SetKV(GoctlCache, cacheDir)
}
goctlEnv.SetKV(GoctlVersion, version.BuildVersion)
protocVer, _ := protoc.Version()
goctlEnv.SetKV(ProtocVersion, protocVer)
protocGenGoVer, _ := protocgengo.Version()
goctlEnv.SetKV(ProtocGenGoVersion, protocGenGoVer)
protocGenGoGrpcVer, _ := protocgengogrpc.Version()
goctlEnv.SetKV(ProtocGenGoGRPCVersion, protocGenGoGrpcVer)
}
func Print() string {
return strings.Join(goctlEnv.Format(), "\n")
}
func Get(key string) string {
return GetOr(key, "")
}
func GetOr(key string, def string) string {
return goctlEnv.GetStringOr(key, def)
}
func readEnv(goctlHome string) *sortedmap.SortedMap {
envFile := filepath.Join(goctlHome, envFileDir)
data, err := ioutil.ReadFile(envFile)
if err != nil {
return nil
}
dataStr := string(data)
lines := strings.Split(dataStr, "\n")
sm := sortedmap.New()
for _, line := range lines {
_, _, err = sm.SetExpression(line)
if err != nil {
continue
}
}
return sm
}
func WriteEnv(kv []string) error {
defaultGoctlHome, err := pathx.GetDefaultGoctlHome()
if err != nil {
log.Fatalln(err)
}
data := sortedmap.New()
for _, e := range kv {
_, _, err := data.SetExpression(e)
if err != nil {
return err
}
}
data.RangeIf(func(key, value interface{}) bool {
switch key.(string) {
case GoctlHome, GoctlCache:
path := value.(string)
if !pathx.FileExists(path) {
err = fmt.Errorf("[writeEnv]: path %q is not exists", path)
return false
}
}
if goctlEnv.HasKey(key) {
goctlEnv.SetKV(key, value)
return true
} else {
err = fmt.Errorf("[writeEnv]: invalid key: %v", key)
return false
}
})
if err != nil {
return err
}
envFile := filepath.Join(defaultGoctlHome, envFileDir)
return ioutil.WriteFile(envFile, []byte(strings.Join(goctlEnv.Format(), "\n")), 0o777)
}

View File

@@ -0,0 +1,41 @@
package goctl
import (
"path/filepath"
"runtime"
"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
"github.com/zeromicro/go-zero/tools/goctl/util/console"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
"github.com/zeromicro/go-zero/tools/goctl/vars"
)
func Install(cacheDir, name string, installFn func(dest string) (string, error)) (string, error) {
goBin := golang.GoBin()
cacheFile := filepath.Join(cacheDir, name)
binFile := filepath.Join(goBin, name)
goos := runtime.GOOS
if goos == vars.OsWindows {
cacheFile = cacheFile + ".exe"
binFile = binFile + ".exe"
}
// read cache.
err := pathx.Copy(cacheFile, binFile)
if err == nil {
console.Info("%q installed from cache", name)
return binFile, nil
}
binFile, err = installFn(binFile)
if err != nil {
return "", err
}
// write cache.
err = pathx.Copy(binFile, cacheFile)
if err != nil {
console.Warning("write cache error: %+v", err)
}
return binFile, nil
}

View File

@@ -0,0 +1,27 @@
package golang
import (
"go/build"
"os"
"path/filepath"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
// GoBin returns a path of GOBIN.
func GoBin() string {
def := build.Default
goroot := os.Getenv("GOROOT")
bin := filepath.Join(goroot, "bin")
if !pathx.FileExists(bin) {
gopath := os.Getenv("GOPATH")
bin = filepath.Join(gopath, "bin")
}
if !pathx.FileExists(bin) {
bin = os.Getenv("GOBIN")
}
if !pathx.FileExists(bin) {
bin = filepath.Join(def.GOPATH, "bin")
}
return bin
}

View File

@@ -0,0 +1,17 @@
package golang
import (
"os"
"os/exec"
)
func Install(git string) error {
cmd := exec.Command("go", "install", git)
env := os.Environ()
env = append(env, "GO111MODULE=on", "GOPROXY=https://goproxy.cn,direct")
cmd.Env = env
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
return err
}

View File

@@ -0,0 +1,79 @@
package protoc
import (
"archive/zip"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/zeromicro/go-zero/tools/goctl/pkg/downloader"
"github.com/zeromicro/go-zero/tools/goctl/pkg/goctl"
"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
"github.com/zeromicro/go-zero/tools/goctl/util/env"
"github.com/zeromicro/go-zero/tools/goctl/util/zipx"
"github.com/zeromicro/go-zero/tools/goctl/vars"
)
var url = map[string]string{
"linux_32": "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_32.zip",
"linux_64": "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip",
"darwin": "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-osx-x86_64.zip",
"windows_32": "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win32.zip",
"windows_64": "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip",
}
const (
Name = "protoc"
ZipFileName = Name + ".zip"
)
func Install(cacheDir string) (string, error) {
return goctl.Install(cacheDir, Name, func(dest string) (string, error) {
goos := runtime.GOOS
tempFile := filepath.Join(os.TempDir(), ZipFileName)
bit := 32 << (^uint(0) >> 63)
var downloadUrl string
switch goos {
case vars.OsMac:
downloadUrl = url[vars.OsMac]
case vars.OsWindows:
downloadUrl = url[fmt.Sprintf("%s_%d", vars.OsWindows, bit)]
case vars.OsLinux:
downloadUrl = url[fmt.Sprintf("%s_%d", vars.OsLinux, bit)]
default:
return "", fmt.Errorf("unsupport OS: %q", goos)
}
err := downloader.Download(downloadUrl, tempFile)
if err != nil {
return "", err
}
return dest, zipx.Unpacking(tempFile, filepath.Dir(dest), func(f *zip.File) bool {
return filepath.Base(f.Name) == filepath.Base(dest)
})
})
}
func Exists() bool {
_, err := env.LookUpProtoc()
return err == nil
}
func Version() (string, error) {
path, err := env.LookUpProtoc()
if err != nil {
return "", err
}
version, err := execx.Run(path+" --version", "")
if err != nil {
return "", err
}
fields := strings.Fields(version)
if len(fields) > 1 {
return fields[1], nil
}
return "", nil
}

View File

@@ -0,0 +1,53 @@
package protocgengo
import (
"strings"
"time"
"github.com/zeromicro/go-zero/tools/goctl/pkg/goctl"
"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
"github.com/zeromicro/go-zero/tools/goctl/util/env"
)
const (
Name = "protoc-gen-go"
url = "google.golang.org/protobuf/cmd/protoc-gen-go@latest"
)
func Install(cacheDir string) (string, error) {
return goctl.Install(cacheDir, Name, func(dest string) (string, error) {
err := golang.Install(url)
return dest, err
})
}
func Exists() bool {
_, err := env.LookUpProtocGenGo()
return err == nil
}
// Version is used to get the version of the protoc-gen-go plugin. For older versions, protoc-gen-go does not support
// version fetching, so if protoc-gen-go --version is executed, it will cause the process to block, so it is controlled
// by a timer to prevent the older version process from blocking.
func Version() (string, error) {
path, err := env.LookUpProtocGenGo()
if err != nil {
return "", err
}
versionC := make(chan string)
go func(c chan string) {
version, _ := execx.Run(path+" --version", "")
fields := strings.Fields(version)
if len(fields) > 1 {
c <- fields[1]
}
}(versionC)
t := time.NewTimer(time.Second)
select {
case <-t.C:
return "", nil
case version := <-versionC:
return version, nil
}
}

View File

@@ -0,0 +1,44 @@
package protocgengogrpc
import (
"strings"
"github.com/zeromicro/go-zero/tools/goctl/pkg/goctl"
"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
"github.com/zeromicro/go-zero/tools/goctl/util/env"
)
const (
Name = "protoc-gen-go-grpc"
url = "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest"
)
func Install(cacheDir string) (string, error) {
return goctl.Install(cacheDir, Name, func(dest string) (string, error) {
err := golang.Install(url)
return dest, err
})
}
func Exists() bool {
_, err := env.LookUpProtocGenGoGrpc()
return err == nil
}
// Version is used to get the version of the protoc-gen-go-grpc plugin.
func Version() (string, error) {
path, err := env.LookUpProtocGenGoGrpc()
if err != nil {
return "", err
}
version, err := execx.Run(path+" --version", "")
if err != nil {
return "", err
}
fields := strings.Fields(version)
if len(fields) > 1 {
return fields[1], nil
}
return "", nil
}

View File

@@ -80,7 +80,7 @@ func ZRPC(c *cli.Context) error {
return err
}
var isGooglePlugin = len(grpcOut) > 0
isGooglePlugin := len(grpcOut) > 0
// If grpcOut is not empty means that user generates grpc code by
// https://google.golang.org/protobuf/cmd/protoc-gen-go and
// https://google.golang.org/grpc/cmd/protoc-gen-go-grpc,

View File

@@ -23,7 +23,7 @@ func Test_GetSourceProto(t *testing.T) {
return
}
var testData = []test{
testData := []test{
{
source: []string{"a.proto"},
expected: filepath.Join(pwd, "a.proto"),
@@ -54,7 +54,7 @@ func Test_GetSourceProto(t *testing.T) {
}
func Test_RemoveGoctlFlag(t *testing.T) {
var testData = []test{
testData := []test{
{
source: strings.Fields("protoc foo.proto --go_out=. --go_opt=bar --zrpc_out=. --style go-zero --home=foo"),
expected: "protoc foo.proto --go_out=. --go_opt=bar",
@@ -87,7 +87,7 @@ func Test_RemoveGoctlFlag(t *testing.T) {
}
func Test_RemovePluginFlag(t *testing.T) {
var testData = []test{
testData := []test{
{
source: strings.Fields("plugins=grpc:."),
expected: ".",

View File

@@ -11,10 +11,11 @@ import (
)
const (
bin = "bin"
binGo = "go"
binProtoc = "protoc"
binProtocGenGo = "protoc-gen-go"
bin = "bin"
binGo = "go"
binProtoc = "protoc"
binProtocGenGo = "protoc-gen-go"
binProtocGenGrpcGo = "protoc-gen-go-grpc"
)
// LookUpGo searches an executable go in the directories
@@ -46,6 +47,14 @@ func LookUpProtocGenGo() (string, error) {
return LookPath(xProtocGenGo)
}
// LookUpProtocGenGoGrpc searches an executable protoc-gen-go-grpc in the directories
// named by the PATH environment variable.
func LookUpProtocGenGoGrpc() (string, error) {
suffix := getExeSuffix()
xProtocGenGoGrpc := binProtocGenGrpcGo + suffix
return LookPath(xProtocGenGoGrpc)
}
// LookPath searches for an executable named file in the
// directories named by the PATH environment variable,
// for the os windows, the named file will be spliced with the

View File

@@ -3,6 +3,7 @@ package pathx
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"os"
@@ -13,22 +14,23 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
)
// NL defines a new line
// NL defines a new line.
const (
NL = "\n"
goctlDir = ".goctl"
gitDir = ".git"
autoCompleteDir = ".auto_complete"
cacheDir = "cache"
)
var goctlHome string
// RegisterGoctlHome register goctl home path
// RegisterGoctlHome register goctl home path.
func RegisterGoctlHome(home string) {
goctlHome = home
}
// CreateIfNotExist creates a file if it is not exists
// CreateIfNotExist creates a file if it is not exists.
func CreateIfNotExist(file string) (*os.File, error) {
_, err := os.Stat(file)
if !os.IsNotExist(err) {
@@ -38,7 +40,7 @@ func CreateIfNotExist(file string) (*os.File, error) {
return os.Create(file)
}
// RemoveIfExist deletes the specified file if it is exists
// RemoveIfExist deletes the specified file if it is exists.
func RemoveIfExist(filename string) error {
if !FileExists(filename) {
return nil
@@ -47,7 +49,7 @@ func RemoveIfExist(filename string) error {
return os.Remove(filename)
}
// RemoveOrQuit deletes the specified file if read a permit command from stdin
// RemoveOrQuit deletes the specified file if read a permit command from stdin.
func RemoveOrQuit(filename string) error {
if !FileExists(filename) {
return nil
@@ -60,23 +62,29 @@ func RemoveOrQuit(filename string) error {
return os.Remove(filename)
}
// FileExists returns true if the specified file is exists
// FileExists returns true if the specified file is exists.
func FileExists(file string) bool {
_, err := os.Stat(file)
return err == nil
}
// FileNameWithoutExt returns a file name without suffix
// FileNameWithoutExt returns a file name without suffix.
func FileNameWithoutExt(file string) string {
return strings.TrimSuffix(file, filepath.Ext(file))
}
// GetGoctlHome returns the path value of the goctl home where Join $HOME with .goctl
// GetGoctlHome returns the path value of the goctl, the default path is ~/.goctl, if the path has
// been set by calling the RegisterGoctlHome method, the user-defined path refers to.
func GetGoctlHome() (string, error) {
if len(goctlHome) != 0 {
return goctlHome, nil
}
return GetDefaultGoctlHome()
}
// GetDefaultGoctlHome returns the path value of the goctl home where Join $HOME with .goctl.
func GetDefaultGoctlHome() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
@@ -104,7 +112,17 @@ func GetAutoCompleteHome() (string, error) {
return filepath.Join(goctlH, autoCompleteDir), nil
}
// GetTemplateDir returns the category path value in GoctlHome where could get it by GetGoctlHome
// GetCacheDir returns the cache dit of goctl.
func GetCacheDir() (string, error) {
goctlH, err := GetGoctlHome()
if err != nil {
return "", err
}
return filepath.Join(goctlH, cacheDir), nil
}
// GetTemplateDir returns the category path value in GoctlHome where could get it by GetGoctlHome.
func GetTemplateDir(category string) (string, error) {
home, err := GetGoctlHome()
if err != nil {
@@ -112,7 +130,7 @@ func GetTemplateDir(category string) (string, error) {
}
if home == goctlHome {
// backward compatible, it will be removed in the feature
// backward compatible start
// backward compatible start.
beforeTemplateDir := filepath.Join(home, version.GetGoctlVersion(), category)
fs, _ := ioutil.ReadDir(beforeTemplateDir)
var hasContent bool
@@ -124,7 +142,7 @@ func GetTemplateDir(category string) (string, error) {
if hasContent {
return beforeTemplateDir, nil
}
// backward compatible end
// backward compatible end.
return filepath.Join(home, category), nil
}
@@ -132,7 +150,7 @@ func GetTemplateDir(category string) (string, error) {
return filepath.Join(home, version.GetGoctlVersion(), category), nil
}
// InitTemplates creates template files GoctlHome where could get it by GetGoctlHome
// InitTemplates creates template files GoctlHome where could get it by GetGoctlHome.
func InitTemplates(category string, templates map[string]string) error {
dir, err := GetTemplateDir(category)
if err != nil {
@@ -152,7 +170,7 @@ func InitTemplates(category string, templates map[string]string) error {
return nil
}
// CreateTemplate writes template into file even it is exists
// CreateTemplate writes template into file even it is exists.
func CreateTemplate(category, name, content string) error {
dir, err := GetTemplateDir(category)
if err != nil {
@@ -161,7 +179,7 @@ func CreateTemplate(category, name, content string) error {
return createTemplate(filepath.Join(dir, name), content, true)
}
// Clean deletes all templates and removes the parent directory
// Clean deletes all templates and removes the parent directory.
func Clean(category string) error {
dir, err := GetTemplateDir(category)
if err != nil {
@@ -170,7 +188,7 @@ func Clean(category string) error {
return os.RemoveAll(dir)
}
// LoadTemplate gets template content by the specified file
// LoadTemplate gets template content by the specified file.
func LoadTemplate(category, file, builtin string) (string, error) {
dir, err := GetTemplateDir(category)
if err != nil {
@@ -223,7 +241,7 @@ func createTemplate(file, content string, force bool) error {
return err
}
// MustTempDir creates a temporary directory
// MustTempDir creates a temporary directory.
func MustTempDir() string {
dir, err := ioutil.TempDir("", "")
if err != nil {
@@ -232,3 +250,25 @@ func MustTempDir() string {
return dir
}
func Copy(src, dest string) error {
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
dir := filepath.Dir(dest)
err = MkdirIfNotExist(dir)
if err != nil {
return err
}
w, err := os.Create(dest)
if err != nil {
return err
}
w.Chmod(os.ModePerm)
defer w.Close()
_, err = io.Copy(w, f)
return err
}

View File

@@ -6,6 +6,8 @@ import (
"unicode"
)
var WhiteSpace = []rune{'\n', '\t', '\f', '\v', ' '}
// String provides for converting the source text into other spell case,like lower,snake,camel
type String struct {
source string
@@ -114,3 +116,24 @@ func (s String) splitBy(fn func(r rune) bool, remove bool) []string {
}
return list
}
func ContainsAny(s string, runes ...rune) bool {
if len(runes) == 0 {
return true
}
tmp := make(map[rune]struct{}, len(runes))
for _, r := range runes {
tmp[r] = struct{}{}
}
for _, r := range s {
if _, ok := tmp[r]; ok {
return true
}
}
return false
}
func ContainsWhiteSpace(s string) bool {
return ContainsAny(s, WhiteSpace...)
}

View File

@@ -0,0 +1,51 @@
package zipx
import (
"archive/zip"
"io"
"os"
"path/filepath"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
func Unpacking(name, destPath string, mapper func(f *zip.File) bool) error {
r, err := zip.OpenReader(name)
if err != nil {
return err
}
defer r.Close()
for _, file := range r.File {
ok := mapper(file)
if ok {
err = fileCopy(file, destPath)
if err != nil {
return err
}
}
}
return nil
}
func fileCopy(file *zip.File, destPath string) error {
rc, err := file.Open()
if err != nil {
return err
}
defer rc.Close()
filename := filepath.Join(destPath, filepath.Base(file.Name))
dir := filepath.Dir(filename)
err = pathx.MkdirIfNotExist(dir)
if err != nil {
return err
}
w, err := os.Create(filename)
if err != nil {
return err
}
defer w.Close()
_, err = io.Copy(w, rc)
return err
}