Merge branch 'develop' of https://gitclone.com/github.com/beego/beego into frt/delete-txorm

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Anker Jam
2021-01-28 00:01:55 +08:00
98 changed files with 5278 additions and 647 deletions

View File

@@ -0,0 +1,131 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
import (
"github.com/beego/beego/v2/core/berror"
)
var InvalidUrl = berror.DefineCode(4001001, moduleName, "InvalidUrl", `
You pass a invalid url to httplib module. Please check your url, be careful about special character.
`)
var InvalidUrlProtocolVersion = berror.DefineCode(4001002, moduleName, "InvalidUrlProtocolVersion", `
You pass a invalid protocol version. In practice, we use HTTP/1.0, HTTP/1.1, HTTP/1.2
But something like HTTP/3.2 is valid for client, and the major version is 3, minor version is 2.
but you must confirm that server support those abnormal protocol version.
`)
var UnsupportedBodyType = berror.DefineCode(4001003, moduleName, "UnsupportedBodyType", `
You use a invalid data as request body.
For now, we only support type string and byte[].
`)
var InvalidXMLBody = berror.DefineCode(4001004, moduleName, "InvalidXMLBody", `
You pass invalid data which could not be converted to XML documents. In general, if you pass structure, it works well.
Sometimes you got XML document and you want to make it as request body. So you call XMLBody.
If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data.
`)
var InvalidYAMLBody = berror.DefineCode(4001005, moduleName, "InvalidYAMLBody", `
You pass invalid data which could not be converted to YAML documents. In general, if you pass structure, it works well.
Sometimes you got YAML document and you want to make it as request body. So you call YAMLBody.
If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data.
`)
var InvalidJSONBody = berror.DefineCode(4001006, moduleName, "InvalidJSONBody", `
You pass invalid data which could not be converted to JSON documents. In general, if you pass structure, it works well.
Sometimes you got JSON document and you want to make it as request body. So you call JSONBody.
If you do this, you got this code. Instead, you should call Header to set Content-type and call Body to set body data.
`)
// start with 5 --------------------------------------------------------------------------
var CreateFormFileFailed = berror.DefineCode(5001001, moduleName, "CreateFormFileFailed", `
In normal case than handling files with BeegoRequest, you should not see this error code.
Unexpected EOF, invalid characters, bad file descriptor may cause this error.
`)
var ReadFileFailed = berror.DefineCode(5001002, moduleName, "ReadFileFailed", `
There are several cases that cause this error:
1. file not found. Please check the file name;
2. file not found, but file name is correct. If you use relative file path, it's very possible for you to see this code.
make sure that this file is in correct directory which Beego looks for;
3. Beego don't have the privilege to read this file, please change file mode;
`)
var CopyFileFailed = berror.DefineCode(5001003, moduleName, "CopyFileFailed", `
When we try to read file content and then copy it to another writer, and failed.
1. Unexpected EOF;
2. Bad file descriptor;
3. Write conflict;
Please check your file content, and confirm that file is not processed by other process (or by user manually).
`)
var CloseFileFailed = berror.DefineCode(5001004, moduleName, "CloseFileFailed", `
After handling files, Beego try to close file but failed. Usually it was caused by bad file descriptor.
`)
var SendRequestFailed = berror.DefineCode(5001005, moduleName, "SendRequestRetryExhausted", `
Beego send HTTP request, but it failed.
If you config retry times, it means that Beego had retried and failed.
When you got this error, there are vary kind of reason:
1. Network unstable and timeout. In this case, sometimes server has received the request.
2. Server error. Make sure that server works well.
3. The request is invalid, which means that you pass some invalid parameter.
`)
var ReadGzipBodyFailed = berror.DefineCode(5001006, moduleName, "BuildGzipReaderFailed", `
Beego parse gzip-encode body failed. Usually Beego got invalid response.
Please confirm that server returns gzip data.
`)
var CreateFileIfNotExistFailed = berror.DefineCode(5001007, moduleName, "CreateFileIfNotExist", `
Beego want to create file if not exist and failed.
In most cases, it means that Beego doesn't have the privilege to create this file.
Please change file mode to ensure that Beego is able to create files on specific directory.
Or you can run Beego with higher authority.
In some cases, you pass invalid filename. Make sure that the file name is valid on your system.
`)
var UnmarshalJSONResponseToObjectFailed = berror.DefineCode(5001008, moduleName,
"UnmarshalResponseToObjectFailed", `
Beego trying to unmarshal response's body to structure but failed.
Make sure that:
1. You pass valid structure pointer to the function;
2. The body is valid json document
`)
var UnmarshalXMLResponseToObjectFailed = berror.DefineCode(5001009, moduleName,
"UnmarshalResponseToObjectFailed", `
Beego trying to unmarshal response's body to structure but failed.
Make sure that:
1. You pass valid structure pointer to the function;
2. The body is valid XML document
`)
var UnmarshalYAMLResponseToObjectFailed = berror.DefineCode(5001010, moduleName,
"UnmarshalResponseToObjectFailed", `
Beego trying to unmarshal response's body to structure but failed.
Make sure that:
1. You pass valid structure pointer to the function;
2. The body is valid YAML document
`)

View File

@@ -18,6 +18,7 @@ import (
"context"
"net/http"
"strconv"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
@@ -26,24 +27,30 @@ import (
)
type FilterChainBuilder struct {
summaryVec prometheus.ObserverVec
AppName string
ServerName string
RunMode string
}
var summaryVec prometheus.ObserverVec
var initSummaryVec sync.Once
func (builder *FilterChainBuilder) FilterChain(next httplib.Filter) httplib.Filter {
builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{
Name: "beego",
Subsystem: "remote_http_request",
ConstLabels: map[string]string{
"server": builder.ServerName,
"env": builder.RunMode,
"appname": builder.AppName,
},
Help: "The statics info for remote http requests",
}, []string{"proto", "scheme", "method", "host", "path", "status", "isError"})
initSummaryVec.Do(func() {
summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{
Name: "beego",
Subsystem: "remote_http_request",
ConstLabels: map[string]string{
"server": builder.ServerName,
"env": builder.RunMode,
"appname": builder.AppName,
},
Help: "The statics info for remote http requests",
}, []string{"proto", "scheme", "method", "host", "path", "status", "isError"})
prometheus.MustRegister(summaryVec)
})
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
startTime := time.Now()
@@ -72,6 +79,6 @@ func (builder *FilterChainBuilder) report(startTime time.Time, endTime time.Time
dur := int(endTime.Sub(startTime) / time.Millisecond)
builder.summaryVec.WithLabelValues(proto, scheme, method, host, path,
summaryVec.WithLabelValues(proto, scheme, method, host, path,
strconv.Itoa(status), strconv.FormatBool(err != nil)).Observe(float64(dur))
}

View File

@@ -0,0 +1,36 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewHttpResponseWithJsonBody(t *testing.T) {
// string
resp := NewHttpResponseWithJsonBody("{}")
assert.Equal(t, int64(2), resp.ContentLength)
resp = NewHttpResponseWithJsonBody([]byte("{}"))
assert.Equal(t, int64(2), resp.ContentLength)
resp = NewHttpResponseWithJsonBody(&user{
Name: "Tom",
})
assert.True(t, resp.ContentLength > 0)
}

View File

@@ -40,59 +40,36 @@ import (
"encoding/xml"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net"
"net/http"
"net/http/cookiejar"
"net/http/httputil"
"net/url"
"os"
"path"
"strings"
"sync"
"time"
"gopkg.in/yaml.v2"
"github.com/beego/beego/v2/core/berror"
"github.com/beego/beego/v2/core/logs"
)
var defaultSetting = BeegoHTTPSettings{
UserAgent: "beegoServer",
ConnectTimeout: 60 * time.Second,
ReadWriteTimeout: 60 * time.Second,
Gzip: true,
DumpBody: true,
FilterChains: []FilterChain{mockFilter.FilterChain},
}
var defaultCookieJar http.CookieJar
var settingMutex sync.Mutex
// it will be the last filter and execute request.Do
var doRequestFilter = func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
return req.doRequest(ctx)
}
// createDefaultCookie creates a global cookiejar to store cookies.
func createDefaultCookie() {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultCookieJar, _ = cookiejar.New(nil)
}
// SetDefaultSetting overwrites default settings
func SetDefaultSetting(setting BeegoHTTPSettings) {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultSetting = setting
}
// NewBeegoRequest returns *BeegoHttpRequest with specific method
// TODO add error as return value
// I think if we don't return error
// users are hard to check whether we create Beego request successfully
func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest {
var resp http.Response
u, err := url.Parse(rawurl)
if err != nil {
log.Println("Httplib:", err)
logs.Error("%+v", berror.Wrapf(err, InvalidUrl, "invalid raw url: %s", rawurl))
}
req := http.Request{
URL: u,
@@ -137,24 +114,6 @@ func Head(url string) *BeegoHTTPRequest {
return NewBeegoRequest(url, "HEAD")
}
// BeegoHTTPSettings is the http.Client setting
type BeegoHTTPSettings struct {
ShowDebug bool
UserAgent string
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
TLSClientConfig *tls.Config
Proxy func(*http.Request) (*url.URL, error)
Transport http.RoundTripper
CheckRedirect func(req *http.Request, via []*http.Request) error
EnableCookie bool
Gzip bool
DumpBody bool
Retries int // if set to -1 means will retry forever
RetryDelay time.Duration
FilterChains []FilterChain
}
// BeegoHTTPRequest provides more useful methods than http.Request for requesting a url.
type BeegoHTTPRequest struct {
url string
@@ -254,7 +213,7 @@ func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest {
}
// SetProtocolVersion sets the protocol version for incoming requests.
// Client requests always use HTTP/1.1.
// Client requests always use HTTP/1.1
func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
if len(vers) == 0 {
vers = "HTTP/1.1"
@@ -265,8 +224,9 @@ func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
b.req.Proto = vers
b.req.ProtoMajor = major
b.req.ProtoMinor = minor
return b
}
logs.Error("%+v", berror.Errorf(InvalidUrlProtocolVersion, "invalid protocol: %s", vers))
return b
}
@@ -334,6 +294,7 @@ func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest
// Body adds request raw body.
// Supports string and []byte.
// TODO return error if data is invalid
func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
switch t := data.(type) {
case string:
@@ -350,6 +311,8 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
return ioutil.NopCloser(bf), nil
}
b.req.ContentLength = int64(len(t))
default:
logs.Error("%+v", berror.Errorf(UnsupportedBodyType, "unsupported body data type: %s", t))
}
return b
}
@@ -359,9 +322,12 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil {
byts, err := xml.Marshal(obj)
if err != nil {
return b, err
return b, berror.Wrap(err, InvalidXMLBody, "obj could not be converted to XML data")
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.GetBody = func() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(byts)), nil
}
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/xml")
}
@@ -373,7 +339,7 @@ func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error)
if b.req.Body == nil && obj != nil {
byts, err := yaml.Marshal(obj)
if err != nil {
return b, err
return b, berror.Wrap(err, InvalidYAMLBody, "obj could not be converted to YAML data")
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
@@ -387,7 +353,7 @@ func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error)
if b.req.Body == nil && obj != nil {
byts, err := json.Marshal(obj)
if err != nil {
return b, err
return b, berror.Wrap(err, InvalidJSONBody, "obj could not be converted to JSON body")
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
@@ -415,28 +381,15 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) {
bodyWriter := multipart.NewWriter(pw)
go func() {
for formname, filename := range b.files {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
if err != nil {
log.Println("Httplib:", err)
}
fh, err := os.Open(filename)
if err != nil {
log.Println("Httplib:", err)
}
// iocopy
_, err = io.Copy(fileWriter, fh)
fh.Close()
if err != nil {
log.Println("Httplib:", err)
}
b.handleFileToBody(bodyWriter, formname, filename)
}
for k, v := range b.params {
for _, vv := range v {
bodyWriter.WriteField(k, vv)
_ = bodyWriter.WriteField(k, vv)
}
}
bodyWriter.Close()
pw.Close()
_ = bodyWriter.Close()
_ = pw.Close()
}()
b.Header("Content-Type", bodyWriter.FormDataContentType())
b.req.Body = ioutil.NopCloser(pr)
@@ -452,6 +405,29 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) {
}
}
func (b *BeegoHTTPRequest) handleFileToBody(bodyWriter *multipart.Writer, formname string, filename string) {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
const errFmt = "Httplib: %+v"
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, CreateFormFileFailed,
"could not create form file, formname: %s, filename: %s", formname, filename))
}
fh, err := os.Open(filename)
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, ReadFileFailed, "could not open this file %s", filename))
}
// iocopy
_, err = io.Copy(fileWriter, fh)
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, CopyFileFailed, "could not copy this file %s", filename))
}
err = fh.Close()
if err != nil {
logs.Error(errFmt, berror.Wrapf(err, CloseFileFailed, "could not close this file %s", filename))
}
}
func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) {
if b.resp.StatusCode != 0 {
return b.resp, nil
@@ -480,62 +456,20 @@ func (b *BeegoHTTPRequest) DoRequestWithCtx(ctx context.Context) (resp *http.Res
return root(ctx, b)
}
func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response, err error) {
var paramBody string
if len(b.params) > 0 {
var buf bytes.Buffer
for k, v := range b.params {
for _, vv := range v {
buf.WriteString(url.QueryEscape(k))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(vv))
buf.WriteByte('&')
}
}
paramBody = buf.String()
paramBody = paramBody[0 : len(paramBody)-1]
}
func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (*http.Response, error) {
paramBody := b.buildParamBody()
b.buildURL(paramBody)
urlParsed, err := url.Parse(b.url)
if err != nil {
return nil, err
return nil, berror.Wrapf(err, InvalidUrl, "parse url failed, the url is %s", b.url)
}
b.req.URL = urlParsed
trans := b.setting.Transport
trans := b.buildTrans()
if trans == nil {
// create default transport
trans = &http.Transport{
TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: 100,
}
} else {
// if b.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = b.setting.TLSClientConfig
}
if t.Proxy == nil {
t.Proxy = b.setting.Proxy
}
if t.Dial == nil {
t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
}
}
}
var jar http.CookieJar
if b.setting.EnableCookie {
if defaultCookieJar == nil {
createDefaultCookie()
}
jar = defaultCookieJar
}
jar := b.buildCookieJar()
client := &http.Client{
Transport: trans,
@@ -551,12 +485,16 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response,
}
if b.setting.ShowDebug {
dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
if err != nil {
log.Println(err.Error())
dump, e := httputil.DumpRequest(b.req, b.setting.DumpBody)
if e != nil {
logs.Error("%+v", e)
}
b.dump = dump
}
return b.sendRequest(client)
}
func (b *BeegoHTTPRequest) sendRequest(client *http.Client) (resp *http.Response, err error) {
// retries default value is 0, it will run once.
// retries equal to -1, it will run forever until success
// retries is setted, it will retries fixed times.
@@ -564,11 +502,68 @@ func (b *BeegoHTTPRequest) doRequest(ctx context.Context) (resp *http.Response,
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
resp, err = client.Do(b.req)
if err == nil {
break
return
}
time.Sleep(b.setting.RetryDelay)
}
return resp, err
return nil, berror.Wrap(err, SendRequestFailed, "sending request fail")
}
func (b *BeegoHTTPRequest) buildCookieJar() http.CookieJar {
var jar http.CookieJar
if b.setting.EnableCookie {
if defaultCookieJar == nil {
createDefaultCookie()
}
jar = defaultCookieJar
}
return jar
}
func (b *BeegoHTTPRequest) buildTrans() http.RoundTripper {
trans := b.setting.Transport
if trans == nil {
// create default transport
trans = &http.Transport{
TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy,
DialContext: TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: 100,
}
} else {
// if b.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = b.setting.TLSClientConfig
}
if t.Proxy == nil {
t.Proxy = b.setting.Proxy
}
if t.DialContext == nil {
t.DialContext = TimeoutDialerCtx(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
}
}
}
return trans
}
func (b *BeegoHTTPRequest) buildParamBody() string {
var paramBody string
if len(b.params) > 0 {
var buf bytes.Buffer
for k, v := range b.params {
for _, vv := range v {
buf.WriteString(url.QueryEscape(k))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(vv))
buf.WriteByte('&')
}
}
paramBody = buf.String()
paramBody = paramBody[0 : len(paramBody)-1]
}
return paramBody
}
// String returns the body string in response.
@@ -599,10 +594,10 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" {
reader, err := gzip.NewReader(resp.Body)
if err != nil {
return nil, err
return nil, berror.Wrap(err, ReadGzipBodyFailed, "building gzip reader failed")
}
b.body, err = ioutil.ReadAll(reader)
return b.body, err
return b.body, berror.Wrap(err, ReadGzipBodyFailed, "reading gzip data failed")
}
b.body, err = ioutil.ReadAll(resp.Body)
return b.body, err
@@ -645,7 +640,7 @@ func pathExistAndMkdir(filename string) (err error) {
return nil
}
}
return err
return berror.Wrapf(err, CreateFileIfNotExistFailed, "try to create(if not exist) failed: %s", filename)
}
// ToJSON returns the map that marshals from the body bytes as json in response.
@@ -655,7 +650,8 @@ func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {
if err != nil {
return err
}
return json.Unmarshal(data, v)
return berror.Wrap(json.Unmarshal(data, v),
UnmarshalJSONResponseToObjectFailed, "unmarshal json body to object failed.")
}
// ToXML returns the map that marshals from the body bytes as xml in response .
@@ -665,7 +661,8 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
if err != nil {
return err
}
return xml.Unmarshal(data, v)
return berror.Wrap(xml.Unmarshal(data, v),
UnmarshalXMLResponseToObjectFailed, "unmarshal xml body to object failed.")
}
// ToYAML returns the map that marshals from the body bytes as yaml in response .
@@ -675,7 +672,8 @@ func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
if err != nil {
return err
}
return yaml.Unmarshal(data, v)
return berror.Wrap(yaml.Unmarshal(data, v),
UnmarshalYAMLResponseToObjectFailed, "unmarshal yaml body to object failed.")
}
// Response executes request client gets response manually.
@@ -684,8 +682,18 @@ func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
}
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
// Deprecated
// we will move this at the end of 2021
// please use TimeoutDialerCtx
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
return func(netw, addr string) (net.Conn, error) {
return TimeoutDialerCtx(cTimeout, rwTimeout)(context.Background(), netw, addr)
}
}
func TimeoutDialerCtx(cTimeout time.Duration,
rwTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) {
return func(ctx context.Context, netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, cTimeout)
if err != nil {
return nil, err

View File

@@ -300,3 +300,136 @@ func TestAddFilter(t *testing.T) {
r := Get("http://beego.me")
assert.Equal(t, 1, len(req.setting.FilterChains)-len(r.setting.FilterChains))
}
func TestFilterChainOrder(t *testing.T) {
req := Get("http://beego.me")
req.AddFilters(func(next Filter) Filter {
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
return NewHttpResponseWithJsonBody("first"), nil
}
})
req.AddFilters(func(next Filter) Filter {
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
return NewHttpResponseWithJsonBody("second"), nil
}
})
resp, err := req.DoRequestWithCtx(context.Background())
assert.Nil(t, err)
data := make([]byte, 5)
_, _ = resp.Body.Read(data)
assert.Equal(t, "first", string(data))
}
func TestHead(t *testing.T) {
req := Head("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "HEAD", req.req.Method)
}
func TestDelete(t *testing.T) {
req := Delete("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "DELETE", req.req.Method)
}
func TestPost(t *testing.T) {
req := Post("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "POST", req.req.Method)
}
func TestNewBeegoRequest(t *testing.T) {
req := NewBeegoRequest("http://beego.me", "GET")
assert.NotNil(t, req)
assert.Equal(t, "GET", req.req.Method)
// invalid case but still go request
req = NewBeegoRequest("httpa\ta://beego.me", "GET")
assert.NotNil(t, req)
}
func TestBeegoHTTPRequest_SetProtocolVersion(t *testing.T) {
req := NewBeegoRequest("http://beego.me", "GET")
req.SetProtocolVersion("HTTP/3.10")
assert.Equal(t, "HTTP/3.10", req.req.Proto)
assert.Equal(t, 3, req.req.ProtoMajor)
assert.Equal(t, 10, req.req.ProtoMinor)
req.SetProtocolVersion("")
assert.Equal(t, "HTTP/1.1", req.req.Proto)
assert.Equal(t, 1, req.req.ProtoMajor)
assert.Equal(t, 1, req.req.ProtoMinor)
// invalid case
req.SetProtocolVersion("HTTP/aaa1.1")
assert.Equal(t, "HTTP/1.1", req.req.Proto)
assert.Equal(t, 1, req.req.ProtoMajor)
assert.Equal(t, 1, req.req.ProtoMinor)
}
func TestPut(t *testing.T) {
req := Put("http://beego.me")
assert.NotNil(t, req)
assert.Equal(t, "PUT", req.req.Method)
}
func TestBeegoHTTPRequest_Header(t *testing.T) {
req := Post("http://beego.me")
key, value := "test-header", "test-header-value"
req.Header(key, value)
assert.Equal(t, value, req.req.Header.Get(key))
}
func TestBeegoHTTPRequest_SetHost(t *testing.T) {
req := Post("http://beego.me")
host := "test-hose"
req.SetHost(host)
assert.Equal(t, host, req.req.Host)
}
func TestBeegoHTTPRequest_Param(t *testing.T) {
req := Post("http://beego.me")
key, value := "test-param", "test-param-value"
req.Param(key, value)
assert.Equal(t, value, req.params[key][0])
value1 := "test-param-value-1"
req.Param(key, value1)
assert.Equal(t, value1, req.params[key][1])
}
func TestBeegoHTTPRequest_Body(t *testing.T) {
req := Post("http://beego.me")
body := `hello, world`
req.Body([]byte(body))
assert.Equal(t, int64(len(body)), req.req.ContentLength)
assert.NotNil(t, req.req.GetBody)
assert.NotNil(t, req.req.Body)
body = "hhhh, i am test"
req.Body(body)
assert.Equal(t, int64(len(body)), req.req.ContentLength)
assert.NotNil(t, req.req.GetBody)
assert.NotNil(t, req.req.Body)
// invalid case
req.Body(13)
}
type user struct {
Name string `xml:"name"`
}
func TestBeegoHTTPRequest_XMLBody(t *testing.T) {
req := Post("http://beego.me")
body := &user{
Name: "Tom",
}
_, err := req.XMLBody(body)
assert.True(t, req.req.ContentLength > 0)
assert.Nil(t, err)
assert.NotNil(t, req.req.GetBody)
}

View File

@@ -12,17 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
package mock
import (
"context"
"net/http"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/core/logs"
)
const mockCtxKey = "beego-httplib-mock"
func init() {
InitMockSetting()
}
type Stub interface {
Mock(cond RequestCondition, resp *http.Response, err error)
Clear()
@@ -31,6 +36,10 @@ type Stub interface {
var mockFilter = &MockResponseFilter{}
func InitMockSetting() {
httplib.AddDefaultFilter(mockFilter.FilterChain)
}
func StartMock() Stub {
return mockFilter
}

View File

@@ -12,17 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
package mock
import (
"context"
"encoding/json"
"net/textproto"
"regexp"
"github.com/beego/beego/v2/client/httplib"
)
type RequestCondition interface {
Match(ctx context.Context, req *BeegoHTTPRequest) bool
Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool
}
// reqCondition create condition
@@ -54,7 +56,7 @@ func NewSimpleCondition(path string, opts ...simpleConditionOption) *SimpleCondi
return sc
}
func (sc *SimpleCondition) Match(ctx context.Context, req *BeegoHTTPRequest) bool {
func (sc *SimpleCondition) Match(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
res := true
if len(sc.path) > 0 {
res = sc.matchPath(ctx, req)
@@ -70,12 +72,12 @@ func (sc *SimpleCondition) Match(ctx context.Context, req *BeegoHTTPRequest) boo
sc.matchBodyFields(ctx, req)
}
func (sc *SimpleCondition) matchPath(ctx context.Context, req *BeegoHTTPRequest) bool {
func (sc *SimpleCondition) matchPath(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
path := req.GetRequest().URL.Path
return path == sc.path
}
func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *BeegoHTTPRequest) bool {
func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
path := req.GetRequest().URL.Path
if b, err := regexp.Match(sc.pathReg, []byte(path)); err == nil {
return b
@@ -83,7 +85,7 @@ func (sc *SimpleCondition) matchPathReg(ctx context.Context, req *BeegoHTTPReque
return false
}
func (sc *SimpleCondition) matchQuery(ctx context.Context, req *BeegoHTTPRequest) bool {
func (sc *SimpleCondition) matchQuery(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
qs := req.GetRequest().URL.Query()
for k, v := range sc.query {
if uv, ok := qs[k]; !ok || uv[0] != v {
@@ -93,7 +95,7 @@ func (sc *SimpleCondition) matchQuery(ctx context.Context, req *BeegoHTTPRequest
return true
}
func (sc *SimpleCondition) matchHeader(ctx context.Context, req *BeegoHTTPRequest) bool {
func (sc *SimpleCondition) matchHeader(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
headers := req.GetRequest().Header
for k, v := range sc.header {
if uv, ok := headers[k]; !ok || uv[0] != v {
@@ -103,7 +105,7 @@ func (sc *SimpleCondition) matchHeader(ctx context.Context, req *BeegoHTTPReques
return true
}
func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *BeegoHTTPRequest) bool {
func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
if len(sc.body) == 0 {
return true
}
@@ -135,7 +137,7 @@ func (sc *SimpleCondition) matchBodyFields(ctx context.Context, req *BeegoHTTPRe
return true
}
func (sc *SimpleCondition) matchMethod(ctx context.Context, req *BeegoHTTPRequest) bool {
func (sc *SimpleCondition) matchMethod(ctx context.Context, req *httplib.BeegoHTTPRequest) bool {
if len(sc.method) > 0 {
return sc.method == req.GetRequest().Method
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
package mock
import (
"context"
@@ -20,6 +20,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/httplib"
)
func init() {
@@ -28,37 +29,37 @@ func init() {
func TestSimpleCondition_MatchPath(t *testing.T) {
sc := NewSimpleCondition("/abc/s")
res := sc.Match(context.Background(), Get("http://localhost:8080/abc/s"))
res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s"))
assert.True(t, res)
}
func TestSimpleCondition_MatchQuery(t *testing.T) {
k, v := "my-key", "my-value"
sc := NewSimpleCondition("/abc/s")
res := sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-value"))
res := sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value"))
assert.True(t, res)
sc = NewSimpleCondition("/abc/s", WithQuery(k, v))
res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-value"))
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value"))
assert.True(t, res)
res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-valuesss"))
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-valuesss"))
assert.False(t, res)
res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key-a=my-value"))
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key-a=my-value"))
assert.False(t, res)
res = sc.Match(context.Background(), Get("http://localhost:8080/abc/s?my-key=my-value&abc=hello"))
res = sc.Match(context.Background(), httplib.Get("http://localhost:8080/abc/s?my-key=my-value&abc=hello"))
assert.True(t, res)
}
func TestSimpleCondition_MatchHeader(t *testing.T) {
k, v := "my-header", "my-header-value"
sc := NewSimpleCondition("/abc/s")
req := Get("http://localhost:8080/abc/s")
req := httplib.Get("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
req = Get("http://localhost:8080/abc/s")
req = httplib.Get("http://localhost:8080/abc/s")
req.Header(k, v)
assert.True(t, sc.Match(context.Background(), req))
@@ -73,7 +74,7 @@ func TestSimpleCondition_MatchHeader(t *testing.T) {
func TestSimpleCondition_MatchBodyField(t *testing.T) {
sc := NewSimpleCondition("/abc/s")
req := Post("http://localhost:8080/abc/s")
req := httplib.Post("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
@@ -102,7 +103,7 @@ func TestSimpleCondition_MatchBodyField(t *testing.T) {
func TestSimpleCondition_Match(t *testing.T) {
sc := NewSimpleCondition("/abc/s")
req := Post("http://localhost:8080/abc/s")
req := httplib.Post("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
@@ -115,9 +116,9 @@ func TestSimpleCondition_Match(t *testing.T) {
func TestSimpleCondition_MatchPathReg(t *testing.T) {
sc := NewSimpleCondition("", WithPathReg(`\/abc\/.*`))
req := Post("http://localhost:8080/abc/s")
req := httplib.Post("http://localhost:8080/abc/s")
assert.True(t, sc.Match(context.Background(), req))
req = Post("http://localhost:8080/abcd/s")
req = httplib.Post("http://localhost:8080/abcd/s")
assert.False(t, sc.Match(context.Background(), req))
}

View File

@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
package mock
import (
"context"
"fmt"
"net/http"
"github.com/beego/beego/v2/client/httplib"
)
// MockResponse will return mock response if find any suitable mock data
@@ -32,13 +33,10 @@ func NewMockResponseFilter() *MockResponseFilter {
}
}
func (m *MockResponseFilter) FilterChain(next Filter) Filter {
return func(ctx context.Context, req *BeegoHTTPRequest) (*http.Response, error) {
func (m *MockResponseFilter) FilterChain(next httplib.Filter) httplib.Filter {
return func(ctx context.Context, req *httplib.BeegoHTTPRequest) (*http.Response, error) {
ms := mockFromCtx(ctx)
ms = append(ms, m.ms...)
fmt.Printf("url: %s, mock: %d \n", req.url, len(ms))
for _, mock := range ms {
if mock.cond.Match(ctx, req) {
return mock.resp, mock.err

View File

@@ -12,20 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
package mock
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/httplib"
)
func TestMockResponseFilter_FilterChain(t *testing.T) {
req := Get("http://localhost:8080/abc/s")
req := httplib.Get("http://localhost:8080/abc/s")
ft := NewMockResponseFilter()
expectedResp := NewHttpResponseWithJsonBody(`{}`)
expectedResp := httplib.NewHttpResponseWithJsonBody(`{}`)
expectedErr := errors.New("expected error")
ft.Mock(NewSimpleCondition("/abc/s"), expectedResp, expectedErr)
@@ -35,16 +37,16 @@ func TestMockResponseFilter_FilterChain(t *testing.T) {
assert.Equal(t, expectedErr, err)
assert.Equal(t, expectedResp, resp)
req = Get("http://localhost:8080/abcd/s")
req = httplib.Get("http://localhost:8080/abcd/s")
req.AddFilters(ft.FilterChain)
resp, err = req.DoRequest()
assert.NotEqual(t, expectedErr, err)
assert.NotEqual(t, expectedResp, resp)
req = Get("http://localhost:8080/abc/s")
req = httplib.Get("http://localhost:8080/abc/s")
req.AddFilters(ft.FilterChain)
expectedResp1 := NewHttpResponseWithJsonBody(map[string]string{})
expectedResp1 := httplib.NewHttpResponseWithJsonBody(map[string]string{})
expectedErr1 := errors.New("expected error")
ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1)
@@ -52,7 +54,7 @@ func TestMockResponseFilter_FilterChain(t *testing.T) {
assert.Equal(t, expectedErr, err)
assert.Equal(t, expectedResp, resp)
req = Get("http://localhost:8080/abc/abs/bbc")
req = httplib.Get("http://localhost:8080/abc/abs/bbc")
req.AddFilters(ft.FilterChain)
ft.Mock(NewSimpleCondition("/abc/abs/bbc"), expectedResp1, expectedErr1)
resp, err = req.DoRequest()

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
package mock
import (
"context"
@@ -21,16 +21,18 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/httplib"
)
func TestStartMock(t *testing.T) {
defaultSetting.FilterChains = []FilterChain{mockFilter.FilterChain}
// httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain}
stub := StartMock()
// defer stub.Clear()
expectedResp := NewHttpResponseWithJsonBody([]byte(`{}`))
expectedResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`))
expectedErr := errors.New("expected err")
stub.Mock(NewSimpleCondition("/abc"), expectedResp, expectedErr)
@@ -45,14 +47,14 @@ func TestStartMock(t *testing.T) {
// TestStartMock_Isolation Test StartMock that
// mock only work for this request
func TestStartMock_Isolation(t *testing.T) {
defaultSetting.FilterChains = []FilterChain{mockFilter.FilterChain}
// httplib.defaultSetting.FilterChains = []httplib.FilterChain{mockFilter.FilterChain}
// setup global stub
stub := StartMock()
globalMockResp := NewHttpResponseWithJsonBody([]byte(`{}`))
globalMockResp := httplib.NewHttpResponseWithJsonBody([]byte(`{}`))
globalMockErr := errors.New("expected err")
stub.Mock(NewSimpleCondition("/abc"), globalMockResp, globalMockErr)
expectedResp := NewHttpResponseWithJsonBody(struct {
expectedResp := httplib.NewHttpResponseWithJsonBody(struct {
A string `json:"a"`
}{
A: "aaa",
@@ -67,9 +69,9 @@ func TestStartMock_Isolation(t *testing.T) {
}
func OriginnalCodeUsingHttplibPassCtx(ctx context.Context) (*http.Response, error) {
return Get("http://localhost:7777/abc").DoRequestWithCtx(ctx)
return httplib.Get("http://localhost:7777/abc").DoRequestWithCtx(ctx)
}
func OriginalCodeUsingHttplib() (*http.Response, error){
return Get("http://localhost:7777/abc").DoRequest()
return httplib.Get("http://localhost:7777/abc").DoRequest()
}

17
client/httplib/module.go Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2021 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
const moduleName = "httplib"

81
client/httplib/setting.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httplib
import (
"crypto/tls"
"net/http"
"net/http/cookiejar"
"net/url"
"sync"
"time"
)
// BeegoHTTPSettings is the http.Client setting
type BeegoHTTPSettings struct {
ShowDebug bool
UserAgent string
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
TLSClientConfig *tls.Config
Proxy func(*http.Request) (*url.URL, error)
Transport http.RoundTripper
CheckRedirect func(req *http.Request, via []*http.Request) error
EnableCookie bool
Gzip bool
DumpBody bool
Retries int // if set to -1 means will retry forever
RetryDelay time.Duration
FilterChains []FilterChain
}
// createDefaultCookie creates a global cookiejar to store cookies.
func createDefaultCookie() {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultCookieJar, _ = cookiejar.New(nil)
}
// SetDefaultSetting overwrites default settings
// Keep in mind that when you invoke the SetDefaultSetting
// some methods invoked before SetDefaultSetting
func SetDefaultSetting(setting BeegoHTTPSettings) {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultSetting = setting
}
var defaultSetting = BeegoHTTPSettings{
UserAgent: "beegoServer",
ConnectTimeout: 60 * time.Second,
ReadWriteTimeout: 60 * time.Second,
Gzip: true,
DumpBody: true,
FilterChains: make([]FilterChain, 0, 4),
}
var defaultCookieJar http.CookieJar
var settingMutex sync.Mutex
// AddDefaultFilter add a new filter into defaultSetting
// Be careful about using this method if you invoke SetDefaultSetting somewhere
func AddDefaultFilter(fc FilterChain) {
settingMutex.Lock()
defer settingMutex.Unlock()
if defaultSetting.FilterChains == nil {
defaultSetting.FilterChains = make([]FilterChain, 0, 4)
}
defaultSetting.FilterChains = append(defaultSetting.FilterChains, fc)
}

View File

@@ -0,0 +1,6 @@
package clauses
const (
ExprSep = "__"
ExprDot = "."
)

View File

@@ -0,0 +1,103 @@
package order_clause
import (
"github.com/beego/beego/v2/client/orm/clauses"
"strings"
)
type Sort int8
const (
None Sort = 0
Ascending Sort = 1
Descending Sort = 2
)
type Option func(order *Order)
type Order struct {
column string
sort Sort
isRaw bool
}
func Clause(options ...Option) *Order {
o := &Order{}
for _, option := range options {
option(o)
}
return o
}
func (o *Order) GetColumn() string {
return o.column
}
func (o *Order) GetSort() Sort {
return o.sort
}
func (o *Order) SortString() string {
switch o.GetSort() {
case Ascending:
return "ASC"
case Descending:
return "DESC"
}
return ``
}
func (o *Order) IsRaw() bool {
return o.isRaw
}
func ParseOrder(expressions ...string) []*Order {
var orders []*Order
for _, expression := range expressions {
sort := Ascending
column := strings.ReplaceAll(expression, clauses.ExprSep, clauses.ExprDot)
if column[0] == '-' {
sort = Descending
column = column[1:]
}
orders = append(orders, &Order{
column: column,
sort: sort,
})
}
return orders
}
func Column(column string) Option {
return func(order *Order) {
order.column = strings.ReplaceAll(column, clauses.ExprSep, clauses.ExprDot)
}
}
func sort(sort Sort) Option {
return func(order *Order) {
order.sort = sort
}
}
func SortAscending() Option {
return sort(Ascending)
}
func SortDescending() Option {
return sort(Descending)
}
func SortNone() Option {
return sort(None)
}
func Raw() Option {
return func(order *Order) {
order.isRaw = true
}
}

View File

@@ -0,0 +1,144 @@
package order_clause
import (
"testing"
)
func TestClause(t *testing.T) {
var (
column = `a`
)
o := Clause(
Column(column),
)
if o.GetColumn() != column {
t.Error()
}
}
func TestSortAscending(t *testing.T) {
o := Clause(
SortAscending(),
)
if o.GetSort() != Ascending {
t.Error()
}
}
func TestSortDescending(t *testing.T) {
o := Clause(
SortDescending(),
)
if o.GetSort() != Descending {
t.Error()
}
}
func TestSortNone(t *testing.T) {
o1 := Clause(
SortNone(),
)
if o1.GetSort() != None {
t.Error()
}
o2 := Clause()
if o2.GetSort() != None {
t.Error()
}
}
func TestRaw(t *testing.T) {
o1 := Clause()
if o1.IsRaw() {
t.Error()
}
o2 := Clause(
Raw(),
)
if !o2.IsRaw() {
t.Error()
}
}
func TestColumn(t *testing.T) {
o1 := Clause(
Column(`aaa`),
)
if o1.GetColumn() != `aaa` {
t.Error()
}
}
func TestParseOrder(t *testing.T) {
orders := ParseOrder(
`-user__status`,
`status`,
`user__status`,
)
t.Log(orders)
if orders[0].GetSort() != Descending {
t.Error()
}
if orders[0].GetColumn() != `user.status` {
t.Error()
}
if orders[1].GetColumn() != `status` {
t.Error()
}
if orders[1].GetSort() != Ascending {
t.Error()
}
if orders[2].GetColumn() != `user.status` {
t.Error()
}
}
func TestOrder_GetColumn(t *testing.T) {
o := Clause(
Column(`user__id`),
)
if o.GetColumn() != `user.id` {
t.Error()
}
}
func TestOrder_GetSort(t *testing.T) {
o := Clause(
SortDescending(),
)
if o.GetSort() != Descending {
t.Error()
}
}
func TestOrder_IsRaw(t *testing.T) {
o1 := Clause()
if o1.IsRaw() {
t.Error()
}
o2 := Clause(
Raw(),
)
if !o2.IsRaw() {
t.Error()
}
}

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"flag"
"fmt"
"os"
@@ -141,6 +142,7 @@ func (d *commandSyncDb) Run() error {
fmt.Printf(" %s\n", err.Error())
}
ctx := context.Background()
for i, mi := range modelCache.allOrdered() {
if !isApplicableTableForDB(mi.addrField, d.al.Name) {
@@ -154,7 +156,7 @@ func (d *commandSyncDb) Run() error {
}
var fields []*fieldInfo
columns, err := d.al.DbBaser.GetColumns(db, mi.table)
columns, err := d.al.DbBaser.GetColumns(ctx, db, mi.table)
if err != nil {
if d.rtOnError {
return err
@@ -188,7 +190,7 @@ func (d *commandSyncDb) Run() error {
}
for _, idx := range indexes[mi.table] {
if !d.al.DbBaser.IndexExists(db, idx.Table, idx.Name) {
if !d.al.DbBaser.IndexExists(ctx, db, idx.Table, idx.Name) {
if !d.noInfo {
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
}

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"database/sql"
"errors"
"fmt"
@@ -268,7 +269,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
}
// create insert sql preparation statement object.
func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string, error) {
func (d *dbBase) PrepareInsert(ctx context.Context, q dbQuerier, mi *modelInfo) (stmtQuerier, string, error) {
Q := d.ins.TableQuote()
dbcols := make([]string, 0, len(mi.fields.dbcols))
@@ -289,12 +290,12 @@ func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string,
d.ins.HasReturningID(mi, &query)
stmt, err := q.Prepare(query)
stmt, err := q.PrepareContext(ctx, query)
return stmt, query, err
}
// insert struct with prepared statement and given struct reflect value.
func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
func (d *dbBase) InsertStmt(ctx context.Context, stmt stmtQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
if err != nil {
return 0, err
@@ -306,7 +307,7 @@ func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value,
err := row.Scan(&id)
return id, err
}
res, err := stmt.Exec(values...)
res, err := stmt.ExecContext(ctx, values...)
if err == nil {
return res.LastInsertId()
}
@@ -314,7 +315,7 @@ func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value,
}
// query sql ,read records and persist in dbBaser.
func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
func (d *dbBase) Read(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
var whereCols []string
var args []interface{}
@@ -360,7 +361,7 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
d.ins.ReplaceMarks(&query)
row := q.QueryRow(query, args...)
row := q.QueryRowContext(ctx, query, args...)
if err := row.Scan(refs...); err != nil {
if err == sql.ErrNoRows {
return ErrNoRows
@@ -375,26 +376,26 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
}
// execute insert sql dbQuerier with given struct reflect.Value.
func (d *dbBase) Insert(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
func (d *dbBase) Insert(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
names := make([]string, 0, len(mi.fields.dbcols))
values, autoFields, err := d.collectValues(mi, ind, mi.fields.dbcols, false, true, &names, tz)
if err != nil {
return 0, err
}
id, err := d.InsertValue(q, mi, false, names, values)
id, err := d.InsertValue(ctx, q, mi, false, names, values)
if err != nil {
return 0, err
}
if len(autoFields) > 0 {
err = d.ins.setval(q, mi, autoFields)
err = d.ins.setval(ctx, q, mi, autoFields)
}
return id, err
}
// multi-insert sql with given slice struct reflect.Value.
func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bulk int, tz *time.Location) (int64, error) {
func (d *dbBase) InsertMulti(ctx context.Context, q dbQuerier, mi *modelInfo, sind reflect.Value, bulk int, tz *time.Location) (int64, error) {
var (
cnt int64
nums int
@@ -440,7 +441,7 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
}
if i > 1 && i%bulk == 0 || length == i {
num, err := d.InsertValue(q, mi, true, names, values[:nums])
num, err := d.InsertValue(ctx, q, mi, true, names, values[:nums])
if err != nil {
return cnt, err
}
@@ -451,7 +452,7 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
var err error
if len(autoFields) > 0 {
err = d.ins.setval(q, mi, autoFields)
err = d.ins.setval(ctx, q, mi, autoFields)
}
return cnt, err
@@ -459,7 +460,7 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
// execute insert sql with given struct and given values.
// insert the given values, not the field values in struct.
func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
func (d *dbBase) InsertValue(ctx context.Context, q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
Q := d.ins.TableQuote()
marks := make([]string, len(names))
@@ -482,7 +483,7 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s
d.ins.ReplaceMarks(&query)
if isMulti || !d.ins.HasReturningID(mi, &query) {
res, err := q.Exec(query, values...)
res, err := q.ExecContext(ctx, query, values...)
if err == nil {
if isMulti {
return res.RowsAffected()
@@ -498,7 +499,7 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s
}
return 0, err
}
row := q.QueryRow(query, values...)
row := q.QueryRowContext(ctx, query, values...)
var id int64
err := row.Scan(&id)
return id, err
@@ -507,7 +508,7 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s
// InsertOrUpdate a row
// If your primary key or unique column conflict will update
// If no will insert
func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
func (d *dbBase) InsertOrUpdate(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
args0 := ""
iouStr := ""
argsMap := map[string]string{}
@@ -590,7 +591,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
d.ins.ReplaceMarks(&query)
if isMulti || !d.ins.HasReturningID(mi, &query) {
res, err := q.Exec(query, values...)
res, err := q.ExecContext(ctx, query, values...)
if err == nil {
if isMulti {
return res.RowsAffected()
@@ -607,7 +608,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
return 0, err
}
row := q.QueryRow(query, values...)
row := q.QueryRowContext(ctx, query, values...)
var id int64
err = row.Scan(&id)
if err != nil && err.Error() == `pq: syntax error at or near "ON"` {
@@ -617,7 +618,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
}
// execute update sql dbQuerier with given struct reflect.Value.
func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
func (d *dbBase) Update(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
pkName, pkValue, ok := getExistPk(mi, ind)
if !ok {
return 0, ErrMissPK
@@ -674,7 +675,7 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
d.ins.ReplaceMarks(&query)
res, err := q.Exec(query, setValues...)
res, err := q.ExecContext(ctx, query, setValues...)
if err == nil {
return res.RowsAffected()
}
@@ -683,7 +684,7 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
// execute delete sql dbQuerier with given struct reflect.Value.
// delete index is pk.
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
func (d *dbBase) Delete(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
var whereCols []string
var args []interface{}
// if specify cols length > 0, then use it for where condition.
@@ -712,7 +713,7 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
query := fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s = ?", Q, mi.table, Q, Q, wheres, Q)
d.ins.ReplaceMarks(&query)
res, err := q.Exec(query, args...)
res, err := q.ExecContext(ctx, query, args...)
if err == nil {
num, err := res.RowsAffected()
if err != nil {
@@ -726,7 +727,7 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
}
}
err := d.deleteRels(q, mi, args, tz)
err := d.deleteRels(ctx, q, mi, args, tz)
if err != nil {
return num, err
}
@@ -738,7 +739,7 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
// update table-related record by querySet.
// need querySet not struct reflect.Value to update related records.
func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, params Params, tz *time.Location) (int64, error) {
func (d *dbBase) UpdateBatch(ctx context.Context, q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, params Params, tz *time.Location) (int64, error) {
columns := make([]string, 0, len(params))
values := make([]interface{}, 0, len(params))
for col, val := range params {
@@ -819,13 +820,7 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
}
d.ins.ReplaceMarks(&query)
var err error
var res sql.Result
if qs != nil && qs.forContext {
res, err = q.ExecContext(qs.ctx, query, values...)
} else {
res, err = q.Exec(query, values...)
}
res, err := q.ExecContext(ctx, query, values...)
if err == nil {
return res.RowsAffected()
}
@@ -834,13 +829,13 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
// delete related records.
// do UpdateBanch or DeleteBanch by condition of tables' relationship.
func (d *dbBase) deleteRels(q dbQuerier, mi *modelInfo, args []interface{}, tz *time.Location) error {
func (d *dbBase) deleteRels(ctx context.Context, q dbQuerier, mi *modelInfo, args []interface{}, tz *time.Location) error {
for _, fi := range mi.fields.fieldsReverse {
fi = fi.reverseFieldInfo
switch fi.onDelete {
case odCascade:
cond := NewCondition().And(fmt.Sprintf("%s__in", fi.name), args...)
_, err := d.DeleteBatch(q, nil, fi.mi, cond, tz)
_, err := d.DeleteBatch(ctx, q, nil, fi.mi, cond, tz)
if err != nil {
return err
}
@@ -850,7 +845,7 @@ func (d *dbBase) deleteRels(q dbQuerier, mi *modelInfo, args []interface{}, tz *
if fi.onDelete == odSetDefault {
params[fi.column] = fi.initial.String()
}
_, err := d.UpdateBatch(q, nil, fi.mi, cond, params, tz)
_, err := d.UpdateBatch(ctx, q, nil, fi.mi, cond, params, tz)
if err != nil {
return err
}
@@ -861,7 +856,7 @@ func (d *dbBase) deleteRels(q dbQuerier, mi *modelInfo, args []interface{}, tz *
}
// delete table-related records.
func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, tz *time.Location) (int64, error) {
func (d *dbBase) DeleteBatch(ctx context.Context, q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, tz *time.Location) (int64, error) {
tables := newDbTables(mi, d.ins)
tables.skipEnd = true
@@ -886,7 +881,7 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
d.ins.ReplaceMarks(&query)
var rs *sql.Rows
r, err := q.Query(query, args...)
r, err := q.QueryContext(ctx, query, args...)
if err != nil {
return 0, err
}
@@ -920,19 +915,14 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
query = fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s %s", Q, mi.table, Q, Q, mi.fields.pk.column, Q, sqlIn)
d.ins.ReplaceMarks(&query)
var res sql.Result
if qs != nil && qs.forContext {
res, err = q.ExecContext(qs.ctx, query, args...)
} else {
res, err = q.Exec(query, args...)
}
res, err := q.ExecContext(ctx, query, args...)
if err == nil {
num, err := res.RowsAffected()
if err != nil {
return 0, err
}
if num > 0 {
err := d.deleteRels(q, mi, args, tz)
err := d.deleteRels(ctx, q, mi, args, tz)
if err != nil {
return num, err
}
@@ -943,14 +933,15 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
}
// read related records.
func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, container interface{}, tz *time.Location, cols []string) (int64, error) {
func (d *dbBase) ReadBatch(ctx context.Context, q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, container interface{}, tz *time.Location, cols []string) (int64, error) {
val := reflect.ValueOf(container)
ind := reflect.Indirect(val)
errTyp := true
unregister := true
one := true
isPtr := true
name := ""
if val.Kind() == reflect.Ptr {
fn := ""
@@ -963,19 +954,17 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
case reflect.Struct:
isPtr = false
fn = getFullName(typ)
name = getTableName(reflect.New(typ))
}
} else {
fn = getFullName(ind.Type())
name = getTableName(ind)
}
errTyp = fn != mi.fullName
unregister = fn != mi.fullName
}
if errTyp {
if one {
panic(fmt.Errorf("wrong object type `%s` for rows scan, need *%s", val.Type(), mi.fullName))
} else {
panic(fmt.Errorf("wrong object type `%s` for rows scan, need *[]*%s or *[]%s", val.Type(), mi.fullName, mi.fullName))
}
if unregister {
RegisterModel(container)
}
rlimit := qs.limit
@@ -1040,6 +1029,9 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
if qs.distinct {
sqlSelect += " DISTINCT"
}
if qs.aggregate != "" {
sels = qs.aggregate
}
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s%s",
sqlSelect, sels, Q, mi.table, Q,
specifyIndexes, join, where, groupBy, orderBy, limit)
@@ -1050,18 +1042,18 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
d.ins.ReplaceMarks(&query)
var rs *sql.Rows
var err error
if qs != nil && qs.forContext {
rs, err = q.QueryContext(qs.ctx, query, args...)
if err != nil {
return 0, err
}
} else {
rs, err = q.Query(query, args...)
if err != nil {
return 0, err
}
rs, err := q.QueryContext(ctx, query, args...)
if err != nil {
return 0, err
}
defer rs.Close()
slice := ind
if unregister {
mi, _ = modelCache.get(name)
tCols = mi.fields.dbcols
colsNum = len(tCols)
}
refs := make([]interface{}, colsNum)
@@ -1069,11 +1061,6 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
var ref interface{}
refs[i] = &ref
}
defer rs.Close()
slice := ind
var cnt int64
for rs.Next() {
if one && cnt == 0 || !one {
@@ -1172,7 +1159,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
}
// excute count sql and return count result int64.
func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, tz *time.Location) (cnt int64, err error) {
func (d *dbBase) Count(ctx context.Context, q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, tz *time.Location) (cnt int64, err error) {
tables := newDbTables(mi, d.ins)
tables.parseRelated(qs.related, qs.relDepth)
@@ -1194,12 +1181,7 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
d.ins.ReplaceMarks(&query)
var row *sql.Row
if qs != nil && qs.forContext {
row = q.QueryRowContext(qs.ctx, query, args...)
} else {
row = q.QueryRow(query, args...)
}
row := q.QueryRowContext(ctx, query, args...)
err = row.Scan(&cnt)
return
}
@@ -1649,7 +1631,7 @@ setValue:
}
// query sql, read values , save to *[]ParamList.
func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, exprs []string, container interface{}, tz *time.Location) (int64, error) {
func (d *dbBase) ReadValues(ctx context.Context, q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, exprs []string, container interface{}, tz *time.Location) (int64, error) {
var (
maps []Params
@@ -1732,7 +1714,7 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
d.ins.ReplaceMarks(&query)
rs, err := q.Query(query, args...)
rs, err := q.QueryContext(ctx, query, args...)
if err != nil {
return 0, err
}
@@ -1847,7 +1829,7 @@ func (d *dbBase) HasReturningID(*modelInfo, *string) bool {
}
// sync auto key
func (d *dbBase) setval(db dbQuerier, mi *modelInfo, autoFields []string) error {
func (d *dbBase) setval(ctx context.Context, db dbQuerier, mi *modelInfo, autoFields []string) error {
return nil
}
@@ -1892,10 +1874,10 @@ func (d *dbBase) GetTables(db dbQuerier) (map[string]bool, error) {
}
// get all cloumns in table.
func (d *dbBase) GetColumns(db dbQuerier, table string) (map[string][3]string, error) {
func (d *dbBase) GetColumns(ctx context.Context, db dbQuerier, table string) (map[string][3]string, error) {
columns := make(map[string][3]string)
query := d.ins.ShowColumnsQuery(table)
rows, err := db.Query(query)
rows, err := db.QueryContext(ctx, query)
if err != nil {
return columns, err
}
@@ -1934,7 +1916,7 @@ func (d *dbBase) ShowColumnsQuery(table string) string {
}
// not implement.
func (d *dbBase) IndexExists(dbQuerier, string, string) bool {
func (d *dbBase) IndexExists(context.Context, dbQuerier, string, string) bool {
panic(ErrNotImplement)
}

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"fmt"
"reflect"
"strings"
@@ -93,8 +94,8 @@ func (d *dbBaseMysql) ShowColumnsQuery(table string) string {
}
// execute sql to check index exist.
func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool {
row := db.QueryRow("SELECT count(*) FROM information_schema.statistics "+
func (d *dbBaseMysql) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
var cnt int
row.Scan(&cnt)
@@ -105,7 +106,7 @@ func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool
// If your primary key or unique column conflict will update
// If no will insert
// Add "`" for mysql sql building
func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
func (d *dbBaseMysql) InsertOrUpdate(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
var iouStr string
argsMap := map[string]string{}
@@ -161,7 +162,7 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val
d.ins.ReplaceMarks(&query)
if isMulti || !d.ins.HasReturningID(mi, &query) {
res, err := q.Exec(query, values...)
res, err := q.ExecContext(ctx, query, values...)
if err == nil {
if isMulti {
return res.RowsAffected()
@@ -178,7 +179,7 @@ func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Val
return 0, err
}
row := q.QueryRow(query, values...)
row := q.QueryRowContext(ctx, query, values...)
var id int64
err = row.Scan(&id)
return id, err

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"fmt"
"strings"
@@ -89,8 +90,8 @@ func (d *dbBaseOracle) ShowColumnsQuery(table string) string {
}
// check index is exist
func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool {
row := db.QueryRow("SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+
func (d *dbBaseOracle) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
row := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+
"WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+
"AND USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name))
@@ -124,7 +125,7 @@ func (d *dbBaseOracle) GenerateSpecifyIndex(tableName string, useIndex int, inde
// execute insert sql with given struct and given values.
// insert the given values, not the field values in struct.
func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
func (d *dbBaseOracle) InsertValue(ctx context.Context, q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
Q := d.ins.TableQuote()
marks := make([]string, len(names))
@@ -147,7 +148,7 @@ func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, nam
d.ins.ReplaceMarks(&query)
if isMulti || !d.ins.HasReturningID(mi, &query) {
res, err := q.Exec(query, values...)
res, err := q.ExecContext(ctx, query, values...)
if err == nil {
if isMulti {
return res.RowsAffected()
@@ -163,7 +164,7 @@ func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, nam
}
return 0, err
}
row := q.QueryRow(query, values...)
row := q.QueryRowContext(ctx, query, values...)
var id int64
err := row.Scan(&id)
return id, err

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"fmt"
"strconv"
)
@@ -140,7 +141,7 @@ func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) bool {
}
// sync auto key
func (d *dbBasePostgres) setval(db dbQuerier, mi *modelInfo, autoFields []string) error {
func (d *dbBasePostgres) setval(ctx context.Context, db dbQuerier, mi *modelInfo, autoFields []string) error {
if len(autoFields) == 0 {
return nil
}
@@ -151,7 +152,7 @@ func (d *dbBasePostgres) setval(db dbQuerier, mi *modelInfo, autoFields []string
mi.table, name,
Q, name, Q,
Q, mi.table, Q)
if _, err := db.Exec(query); err != nil {
if _, err := db.ExecContext(ctx, query); err != nil {
return err
}
}
@@ -174,9 +175,9 @@ func (d *dbBasePostgres) DbTypes() map[string]string {
}
// check index exist in postgresql.
func (d *dbBasePostgres) IndexExists(db dbQuerier, table string, name string) bool {
func (d *dbBasePostgres) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
query := fmt.Sprintf("SELECT COUNT(*) FROM pg_indexes WHERE tablename = '%s' AND indexname = '%s'", table, name)
row := db.QueryRow(query)
row := db.QueryRowContext(ctx, query)
var cnt int
row.Scan(&cnt)
return cnt > 0

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"database/sql"
"fmt"
"reflect"
@@ -73,11 +74,11 @@ type dbBaseSqlite struct {
var _ dbBaser = new(dbBaseSqlite)
// override base db read for update behavior as SQlite does not support syntax
func (d *dbBaseSqlite) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
func (d *dbBaseSqlite) Read(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
if isForUpdate {
DebugLog.Println("[WARN] SQLite does not support SELECT FOR UPDATE query, isForUpdate param is ignored and always as false to do the work")
}
return d.dbBase.Read(q, mi, ind, tz, cols, false)
return d.dbBase.Read(ctx, q, mi, ind, tz, cols, false)
}
// get sqlite operator.
@@ -114,9 +115,9 @@ func (d *dbBaseSqlite) ShowTablesQuery() string {
}
// get columns in sqlite.
func (d *dbBaseSqlite) GetColumns(db dbQuerier, table string) (map[string][3]string, error) {
func (d *dbBaseSqlite) GetColumns(ctx context.Context, db dbQuerier, table string) (map[string][3]string, error) {
query := d.ins.ShowColumnsQuery(table)
rows, err := db.Query(query)
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, err
}
@@ -140,9 +141,9 @@ func (d *dbBaseSqlite) ShowColumnsQuery(table string) string {
}
// check index exist in sqlite.
func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool {
func (d *dbBaseSqlite) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
query := fmt.Sprintf("PRAGMA index_list('%s')", table)
rows, err := db.Query(query)
rows, err := db.QueryContext(ctx, query)
if err != nil {
panic(err)
}

View File

@@ -16,6 +16,8 @@ package orm
import (
"fmt"
"github.com/beego/beego/v2/client/orm/clauses"
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
"strings"
"time"
)
@@ -421,7 +423,7 @@ func (t *dbTables) getGroupSQL(groups []string) (groupSQL string) {
}
// generate order sql.
func (t *dbTables) getOrderSQL(orders []string) (orderSQL string) {
func (t *dbTables) getOrderSQL(orders []*order_clause.Order) (orderSQL string) {
if len(orders) == 0 {
return
}
@@ -430,19 +432,25 @@ func (t *dbTables) getOrderSQL(orders []string) (orderSQL string) {
orderSqls := make([]string, 0, len(orders))
for _, order := range orders {
asc := "ASC"
if order[0] == '-' {
asc = "DESC"
order = order[1:]
}
exprs := strings.Split(order, ExprSep)
column := order.GetColumn()
clause := strings.Split(column, clauses.ExprDot)
index, _, fi, suc := t.parseExprs(t.mi, exprs)
if !suc {
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
}
if order.IsRaw() {
if len(clause) == 2 {
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", clause[0], Q, clause[1], Q, order.SortString()))
} else if len(clause) == 1 {
orderSqls = append(orderSqls, fmt.Sprintf("%s%s%s %s", Q, clause[0], Q, order.SortString()))
} else {
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
}
} else {
index, _, fi, suc := t.parseExprs(t.mi, clause)
if !suc {
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
}
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, asc))
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, order.SortString()))
}
}
orderSQL = fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"fmt"
)
@@ -47,8 +48,8 @@ func (d *dbBaseTidb) ShowColumnsQuery(table string) string {
}
// execute sql to check index exist.
func (d *dbBaseTidb) IndexExists(db dbQuerier, table string, name string) bool {
row := db.QueryRow("SELECT count(*) FROM information_schema.statistics "+
func (d *dbBaseTidb) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
var cnt int
row.Scan(&cnt)

View File

@@ -66,6 +66,7 @@ func (d *DoNothingOrm) QueryM2M(md interface{}, name string) QueryM2Mer {
return nil
}
// NOTE: this method is deprecated, context parameter will not take effect.
func (d *DoNothingOrm) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer {
return nil
}
@@ -74,6 +75,7 @@ func (d *DoNothingOrm) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
return nil
}
// NOTE: this method is deprecated, context parameter will not take effect.
func (d *DoNothingOrm) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter {
return nil
}

View File

@@ -36,7 +36,6 @@ func TestDoNothingOrm(t *testing.T) {
assert.Nil(t, o.Driver())
assert.Nil(t, o.QueryM2MWithCtx(nil, nil, ""))
assert.Nil(t, o.QueryM2M(nil, ""))
assert.Nil(t, o.ReadWithCtx(nil, nil))
assert.Nil(t, o.Read(nil))
@@ -92,7 +91,6 @@ func TestDoNothingOrm(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, int64(0), i)
assert.Nil(t, o.QueryTableWithCtx(nil, nil))
assert.Nil(t, o.QueryTable(nil))
assert.Nil(t, o.Read(nil))

View File

@@ -27,7 +27,7 @@ import (
// this Filter's behavior looks a little bit strange
// for example:
// if we want to trace QuerySetter
// actually we trace invoking "QueryTable" and "QueryTableWithCtx"
// actually we trace invoking "QueryTable"
// the method Begin*, Commit and Rollback are ignored.
// When use using those methods, it means that they want to manager their transaction manually, so we won't handle them.
type FilterChainBuilder struct {

View File

@@ -18,6 +18,7 @@ import (
"context"
"strconv"
"strings"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
@@ -31,26 +32,31 @@ import (
// this Filter's behavior looks a little bit strange
// for example:
// if we want to records the metrics of QuerySetter
// actually we only records metrics of invoking "QueryTable" and "QueryTableWithCtx"
// actually we only records metrics of invoking "QueryTable"
type FilterChainBuilder struct {
summaryVec prometheus.ObserverVec
AppName string
ServerName string
RunMode string
}
var summaryVec prometheus.ObserverVec
var initSummaryVec sync.Once
func (builder *FilterChainBuilder) FilterChain(next orm.Filter) orm.Filter {
builder.summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{
Name: "beego",
Subsystem: "orm_operation",
ConstLabels: map[string]string{
"server": builder.ServerName,
"env": builder.RunMode,
"appname": builder.AppName,
},
Help: "The statics info for orm operation",
}, []string{"method", "name", "insideTx", "txName"})
initSummaryVec.Do(func() {
summaryVec = prometheus.NewSummaryVec(prometheus.SummaryOpts{
Name: "beego",
Subsystem: "orm_operation",
ConstLabels: map[string]string{
"server": builder.ServerName,
"env": builder.RunMode,
"appname": builder.AppName,
},
Help: "The statics info for orm operation",
}, []string{"method", "name", "insideTx", "txName"})
prometheus.MustRegister(summaryVec)
})
return func(ctx context.Context, inv *orm.Invocation) []interface{} {
startTime := time.Now()
@@ -74,12 +80,12 @@ func (builder *FilterChainBuilder) report(ctx context.Context, inv *orm.Invocati
builder.reportTxn(ctx, inv)
return
}
builder.summaryVec.WithLabelValues(inv.Method, inv.GetTableName(),
summaryVec.WithLabelValues(inv.Method, inv.GetTableName(),
strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur))
}
func (builder *FilterChainBuilder) reportTxn(ctx context.Context, inv *orm.Invocation) {
dur := time.Now().Sub(inv.TxStartTime) / time.Millisecond
builder.summaryVec.WithLabelValues(inv.Method, inv.TxName,
summaryVec.WithLabelValues(inv.Method, inv.TxName,
strconv.FormatBool(inv.InsideTx), inv.TxName).Observe(float64(dur))
}

View File

@@ -32,7 +32,7 @@ func TestFilterChainBuilder_FilterChain1(t *testing.T) {
builder := &FilterChainBuilder{}
filter := builder.FilterChain(next)
assert.NotNil(t, builder.summaryVec)
assert.NotNil(t, summaryVec)
assert.NotNil(t, filter)
inv := &orm.Invocation{}

View File

@@ -20,6 +20,7 @@ import (
"reflect"
"time"
"github.com/beego/beego/v2/core/logs"
"github.com/beego/beego/v2/core/utils"
)
@@ -161,36 +162,34 @@ func (f *filterOrmDecorator) LoadRelatedWithCtx(ctx context.Context, md interfac
}
func (f *filterOrmDecorator) QueryM2M(md interface{}, name string) QueryM2Mer {
return f.QueryM2MWithCtx(context.Background(), md, name)
}
func (f *filterOrmDecorator) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer {
mi, _ := modelCache.getByMd(md)
inv := &Invocation{
Method: "QueryM2MWithCtx",
Method: "QueryM2M",
Args: []interface{}{md, name},
Md: md,
mi: mi,
InsideTx: f.insideTx,
TxStartTime: f.txStartTime,
f: func(c context.Context) []interface{} {
res := f.ormer.QueryM2MWithCtx(c, md, name)
res := f.ormer.QueryM2M(md, name)
return []interface{}{res}
},
}
res := f.root(ctx, inv)
res := f.root(context.Background(), inv)
if res[0] == nil {
return nil
}
return res[0].(QueryM2Mer)
}
func (f *filterOrmDecorator) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
return f.QueryTableWithCtx(context.Background(), ptrStructOrTableName)
// NOTE: this method is deprecated, context parameter will not take effect.
func (f *filterOrmDecorator) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` on QueryM2Mer suffix as replacement.")
return f.QueryM2M(md, name)
}
func (f *filterOrmDecorator) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter {
func (f *filterOrmDecorator) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
var (
name string
md interface{}
@@ -209,18 +208,18 @@ func (f *filterOrmDecorator) QueryTableWithCtx(ctx context.Context, ptrStructOrT
}
inv := &Invocation{
Method: "QueryTableWithCtx",
Method: "QueryTable",
Args: []interface{}{ptrStructOrTableName},
InsideTx: f.insideTx,
TxStartTime: f.txStartTime,
Md: md,
mi: mi,
f: func(c context.Context) []interface{} {
res := f.ormer.QueryTableWithCtx(c, ptrStructOrTableName)
res := f.ormer.QueryTable(ptrStructOrTableName)
return []interface{}{res}
},
}
res := f.root(ctx, inv)
res := f.root(context.Background(), inv)
if res[0] == nil {
return nil
@@ -228,6 +227,12 @@ func (f *filterOrmDecorator) QueryTableWithCtx(ctx context.Context, ptrStructOrT
return res[0].(QuerySeter)
}
// NOTE: this method is deprecated, context parameter will not take effect.
func (f *filterOrmDecorator) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) QuerySeter {
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx`on QuerySeter suffix as replacement.")
return f.QueryTable(ptrStructOrTableName)
}
func (f *filterOrmDecorator) DBStats() *sql.DBStats {
inv := &Invocation{
Method: "DBStats",

View File

@@ -268,7 +268,7 @@ func TestFilterOrmDecorator_QueryM2M(t *testing.T) {
o := &filterMockOrm{}
od := NewFilterOrmDecorator(o, func(next Filter) Filter {
return func(ctx context.Context, inv *Invocation) []interface{} {
assert.Equal(t, "QueryM2MWithCtx", inv.Method)
assert.Equal(t, "QueryM2M", inv.Method)
assert.Equal(t, 2, len(inv.Args))
assert.Equal(t, "FILTER_TEST", inv.GetTableName())
assert.False(t, inv.InsideTx)
@@ -284,7 +284,7 @@ func TestFilterOrmDecorator_QueryTable(t *testing.T) {
o := &filterMockOrm{}
od := NewFilterOrmDecorator(o, func(next Filter) Filter {
return func(ctx context.Context, inv *Invocation) []interface{} {
assert.Equal(t, "QueryTableWithCtx", inv.Method)
assert.Equal(t, "QueryTable", inv.Method)
assert.Equal(t, 1, len(inv.Args))
assert.Equal(t, "FILTER_TEST", inv.GetTableName())
assert.False(t, inv.InsideTx)

View File

@@ -0,0 +1,63 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"github.com/beego/beego/v2/client/orm"
)
type Mock struct {
cond Condition
resp []interface{}
cb func(inv *orm.Invocation)
}
func NewMock(cond Condition, resp []interface{}, cb func(inv *orm.Invocation)) *Mock {
return &Mock{
cond: cond,
resp: resp,
cb: cb,
}
}
type Condition interface {
Match(ctx context.Context, inv *orm.Invocation) bool
}
type SimpleCondition struct {
tableName string
method string
}
func NewSimpleCondition(tableName string, methodName string) Condition {
return &SimpleCondition{
tableName: tableName,
method: methodName,
}
}
func (s *SimpleCondition) Match(ctx context.Context, inv *orm.Invocation) bool {
res := true
if len(s.tableName) != 0 {
res = res && (s.tableName == inv.GetTableName())
}
if len(s.method) != 0 {
res = res && (s.method == inv.Method)
}
return res
}

View File

@@ -0,0 +1,41 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/orm"
)
func TestSimpleCondition_Match(t *testing.T) {
cond := NewSimpleCondition("", "")
res := cond.Match(context.Background(), &orm.Invocation{})
assert.True(t, res)
cond = NewSimpleCondition("hello", "")
assert.False(t, cond.Match(context.Background(), &orm.Invocation{}))
cond = NewSimpleCondition("", "A")
assert.False(t, cond.Match(context.Background(), &orm.Invocation{
Method: "B",
}))
assert.True(t, cond.Match(context.Background(), &orm.Invocation{
Method: "A",
}))
}

View File

@@ -0,0 +1,40 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"github.com/beego/beego/v2/core/logs"
)
type mockCtxKeyType string
const mockCtxKey = mockCtxKeyType("beego-orm-mock")
func CtxWithMock(ctx context.Context, mock ...*Mock) context.Context {
return context.WithValue(ctx, mockCtxKey, mock)
}
func mockFromCtx(ctx context.Context) []*Mock {
ms := ctx.Value(mockCtxKey)
if ms != nil {
if res, ok := ms.([]*Mock); ok {
return res
}
logs.Error("mockCtxKey found in context, but value is not type []*Mock")
}
return nil
}

View File

@@ -0,0 +1,29 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCtx(t *testing.T) {
ms := make([]*Mock, 0, 4)
ctx := CtxWithMock(context.Background(), ms...)
res := mockFromCtx(ctx)
assert.Equal(t, ms, res)
}

72
client/orm/mock/mock.go Normal file
View File

@@ -0,0 +1,72 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"github.com/beego/beego/v2/client/orm"
)
var stub = newOrmStub()
func init() {
orm.AddGlobalFilterChain(stub.FilterChain)
}
type Stub interface {
Mock(m *Mock)
Clear()
}
type OrmStub struct {
ms []*Mock
}
func StartMock() Stub {
return stub
}
func newOrmStub() *OrmStub {
return &OrmStub{
ms: make([]*Mock, 0, 4),
}
}
func (o *OrmStub) Mock(m *Mock) {
o.ms = append(o.ms, m)
}
func (o *OrmStub) Clear() {
o.ms = make([]*Mock, 0, 4)
}
func (o *OrmStub) FilterChain(next orm.Filter) orm.Filter {
return func(ctx context.Context, inv *orm.Invocation) []interface{} {
ms := mockFromCtx(ctx)
ms = append(ms, o.ms...)
for _, mock := range ms {
if mock.cond.Match(ctx, inv) {
if mock.cb != nil {
mock.cb(inv)
}
return mock.resp
}
}
return next(ctx, inv)
}
}

162
client/orm/mock/mock_orm.go Normal file
View File

@@ -0,0 +1,162 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"database/sql"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
"github.com/beego/beego/v2/client/orm"
)
func init() {
RegisterMockDB("default")
}
// RegisterMockDB create an "virtual DB" by using sqllite
// you should not
func RegisterMockDB(name string) {
source := filepath.Join(os.TempDir(), name+".db")
_ = orm.RegisterDataBase(name, "sqlite3", source)
}
// MockTable only check table name
func MockTable(tableName string, resp ...interface{}) *Mock {
return NewMock(NewSimpleCondition(tableName, ""), resp, nil)
}
// MockMethod only check method name
func MockMethod(method string, resp ...interface{}) *Mock {
return NewMock(NewSimpleCondition("", method), resp, nil)
}
// MockOrmRead support orm.Read and orm.ReadWithCtx
// cb is used to mock read data from DB
func MockRead(tableName string, cb func(data interface{}), err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "ReadWithCtx"), []interface{}{err}, func(inv *orm.Invocation) {
if cb != nil {
cb(inv.Args[0])
}
})
}
// MockReadForUpdateWithCtx support ReadForUpdate and ReadForUpdateWithCtx
// cb is used to mock read data from DB
func MockReadForUpdateWithCtx(tableName string, cb func(data interface{}), err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "ReadForUpdateWithCtx"),
[]interface{}{err},
func(inv *orm.Invocation) {
cb(inv.Args[0])
})
}
// MockReadOrCreateWithCtx support ReadOrCreate and ReadOrCreateWithCtx
// cb is used to mock read data from DB
func MockReadOrCreateWithCtx(tableName string,
cb func(data interface{}),
insert bool, id int64, err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "ReadOrCreateWithCtx"),
[]interface{}{insert, id, err},
func(inv *orm.Invocation) {
cb(inv.Args[0])
})
}
// MockInsertWithCtx support Insert and InsertWithCtx
func MockInsertWithCtx(tableName string, id int64, err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "InsertWithCtx"), []interface{}{id, err}, nil)
}
// MockInsertMultiWithCtx support InsertMulti and InsertMultiWithCtx
func MockInsertMultiWithCtx(tableName string, cnt int64, err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "InsertMultiWithCtx"), []interface{}{cnt, err}, nil)
}
// MockInsertOrUpdateWithCtx support InsertOrUpdate and InsertOrUpdateWithCtx
func MockInsertOrUpdateWithCtx(tableName string, id int64, err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "InsertOrUpdateWithCtx"), []interface{}{id, err}, nil)
}
// MockUpdateWithCtx support UpdateWithCtx and Update
func MockUpdateWithCtx(tableName string, affectedRow int64, err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "UpdateWithCtx"), []interface{}{affectedRow, err}, nil)
}
// MockDeleteWithCtx support Delete and DeleteWithCtx
func MockDeleteWithCtx(tableName string, affectedRow int64, err error) *Mock {
return NewMock(NewSimpleCondition(tableName, "DeleteWithCtx"), []interface{}{affectedRow, err}, nil)
}
// MockQueryM2MWithCtx support QueryM2MWithCtx and QueryM2M
// Now you may be need to use golang/mock to generate QueryM2M mock instance
// Or use DoNothingQueryM2Mer
// for example:
// post := Post{Id: 4}
// m2m := Ormer.QueryM2M(&post, "Tags")
// when you write test code:
// MockQueryM2MWithCtx("post", "Tags", mockM2Mer)
// "post" is the table name of model Post structure
// TODO provide orm.QueryM2Mer
func MockQueryM2MWithCtx(tableName string, name string, res orm.QueryM2Mer) *Mock {
return NewMock(NewQueryM2MerCondition(tableName, name), []interface{}{res}, nil)
}
// MockLoadRelatedWithCtx support LoadRelatedWithCtx and LoadRelated
func MockLoadRelatedWithCtx(tableName string, name string, rows int64, err error) *Mock {
return NewMock(NewQueryM2MerCondition(tableName, name), []interface{}{rows, err}, nil)
}
// MockQueryTableWithCtx support QueryTableWithCtx and QueryTable
func MockQueryTableWithCtx(tableName string, qs orm.QuerySeter) *Mock {
return NewMock(NewSimpleCondition(tableName, "QueryTable"), []interface{}{qs}, nil)
}
// MockRawWithCtx support RawWithCtx and Raw
func MockRawWithCtx(rs orm.RawSeter) *Mock {
return NewMock(NewSimpleCondition("", "RawWithCtx"), []interface{}{rs}, nil)
}
// MockDriver support Driver
// func MockDriver(driver orm.Driver) *Mock {
// return NewMock(NewSimpleCondition("", "Driver"), []interface{}{driver})
// }
// MockDBStats support DBStats
func MockDBStats(stats *sql.DBStats) *Mock {
return NewMock(NewSimpleCondition("", "DBStats"), []interface{}{stats}, nil)
}
// MockBeginWithCtxAndOpts support Begin, BeginWithCtx, BeginWithOpts, BeginWithCtxAndOpts
// func MockBeginWithCtxAndOpts(txOrm *orm.TxOrmer, err error) *Mock {
// return NewMock(NewSimpleCondition("", "BeginWithCtxAndOpts"), []interface{}{txOrm, err})
// }
// MockDoTxWithCtxAndOpts support DoTx, DoTxWithCtx, DoTxWithOpts, DoTxWithCtxAndOpts
// func MockDoTxWithCtxAndOpts(txOrm *orm.TxOrmer, err error) *Mock {
// return MockBeginWithCtxAndOpts(txOrm, err)
// }
// MockCommit support Commit
func MockCommit(err error) *Mock {
return NewMock(NewSimpleCondition("", "Commit"), []interface{}{err}, nil)
}
// MockRollback support Rollback
func MockRollback(err error) *Mock {
return NewMock(NewSimpleCondition("", "Rollback"), []interface{}{err}, nil)
}

View File

@@ -0,0 +1,297 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"database/sql"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/orm"
)
const mockErrorMsg = "mock error"
func init() {
orm.RegisterModel(&User{})
}
func TestMockDBStats(t *testing.T) {
s := StartMock()
defer s.Clear()
stats := &sql.DBStats{}
s.Mock(MockDBStats(stats))
o := orm.NewOrm()
res := o.DBStats()
assert.Equal(t, stats, res)
}
func TestMockDeleteWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
s.Mock(MockDeleteWithCtx((&User{}).TableName(), 12, nil))
o := orm.NewOrm()
rows, err := o.Delete(&User{})
assert.Equal(t, int64(12), rows)
assert.Nil(t, err)
}
func TestMockInsertOrUpdateWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
s.Mock(MockInsertOrUpdateWithCtx((&User{}).TableName(), 12, nil))
o := orm.NewOrm()
id, err := o.InsertOrUpdate(&User{})
assert.Equal(t, int64(12), id)
assert.Nil(t, err)
}
func TestMockRead(t *testing.T) {
s := StartMock()
defer s.Clear()
err := errors.New(mockErrorMsg)
s.Mock(MockRead((&User{}).TableName(), func(data interface{}) {
u := data.(*User)
u.Name = "Tom"
}, err))
o := orm.NewOrm()
u := &User{}
e := o.Read(u)
assert.Equal(t, err, e)
assert.Equal(t, "Tom", u.Name)
}
func TestMockQueryM2MWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := &DoNothingQueryM2Mer{}
s.Mock(MockQueryM2MWithCtx((&User{}).TableName(), "Tags", mock))
o := orm.NewOrm()
res := o.QueryM2M(&User{}, "Tags")
assert.Equal(t, mock, res)
}
func TestMockQueryTableWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := &DoNothingQuerySetter{}
s.Mock(MockQueryTableWithCtx((&User{}).TableName(), mock))
o := orm.NewOrm()
res := o.QueryTable(&User{})
assert.Equal(t, mock, res)
}
func TestMockTable(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockTable((&User{}).TableName(), mock))
o := orm.NewOrm()
res := o.Read(&User{})
assert.Equal(t, mock, res)
}
func TestMockInsertMultiWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockInsertMultiWithCtx((&User{}).TableName(), 12, mock))
o := orm.NewOrm()
res, err := o.InsertMulti(11, []interface{}{&User{}})
assert.Equal(t, int64(12), res)
assert.Equal(t, mock, err)
}
func TestMockInsertWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockInsertWithCtx((&User{}).TableName(), 13, mock))
o := orm.NewOrm()
res, err := o.Insert(&User{})
assert.Equal(t, int64(13), res)
assert.Equal(t, mock, err)
}
func TestMockUpdateWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockUpdateWithCtx((&User{}).TableName(), 12, mock))
o := orm.NewOrm()
res, err := o.Update(&User{})
assert.Equal(t, int64(12), res)
assert.Equal(t, mock, err)
}
func TestMockLoadRelatedWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockLoadRelatedWithCtx((&User{}).TableName(), "T", 12, mock))
o := orm.NewOrm()
res, err := o.LoadRelated(&User{}, "T")
assert.Equal(t, int64(12), res)
assert.Equal(t, mock, err)
}
func TestMockMethod(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockMethod("ReadWithCtx", mock))
o := orm.NewOrm()
err := o.Read(&User{})
assert.Equal(t, mock, err)
}
func TestMockReadForUpdateWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockReadForUpdateWithCtx((&User{}).TableName(), func(data interface{}) {
u := data.(*User)
u.Name = "Tom"
}, mock))
o := orm.NewOrm()
u := &User{}
err := o.ReadForUpdate(u)
assert.Equal(t, mock, err)
assert.Equal(t, "Tom", u.Name)
}
func TestMockRawWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := &DoNothingRawSetter{}
s.Mock(MockRawWithCtx(mock))
o := orm.NewOrm()
res := o.Raw("")
assert.Equal(t, mock, res)
}
func TestMockReadOrCreateWithCtx(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockReadOrCreateWithCtx((&User{}).TableName(), func(data interface{}) {
u := data.(*User)
u.Name = "Tom"
}, false, 12, mock))
o := orm.NewOrm()
u := &User{}
inserted, id, err := o.ReadOrCreate(u, "")
assert.Equal(t, mock, err)
assert.Equal(t, int64(12), id)
assert.False(t, inserted)
assert.Equal(t, "Tom", u.Name)
}
func TestTransactionClosure(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockRead((&User{}).TableName(), func(data interface{}) {
u := data.(*User)
u.Name = "Tom"
}, mock))
u, err := originalTxUsingClosure()
assert.Equal(t, mock, err)
assert.Equal(t, "Tom", u.Name)
}
func TestTransactionManually(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockRead((&User{}).TableName(), func(data interface{}) {
u := data.(*User)
u.Name = "Tom"
}, mock))
u, err := originalTxManually()
assert.Equal(t, mock, err)
assert.Equal(t, "Tom", u.Name)
}
func TestTransactionRollback(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockRead((&User{}).TableName(), nil, errors.New("read error")))
s.Mock(MockRollback(mock))
_, err := originalTx()
assert.Equal(t, mock, err)
}
func TestTransactionCommit(t *testing.T) {
s := StartMock()
defer s.Clear()
mock := errors.New(mockErrorMsg)
s.Mock(MockRead((&User{}).TableName(), func(data interface{}) {
u := data.(*User)
u.Name = "Tom"
}, nil))
s.Mock(MockCommit(mock))
u, err := originalTx()
assert.Equal(t, mock, err)
assert.Equal(t, "Tom", u.Name)
}
func originalTx() (*User, error) {
u := &User{}
o := orm.NewOrm()
txOrm, _ := o.Begin()
err := txOrm.Read(u)
if err == nil {
err = txOrm.Commit()
return u, err
} else {
err = txOrm.Rollback()
return nil, err
}
}
func originalTxManually() (*User, error) {
u := &User{}
o := orm.NewOrm()
txOrm, _ := o.Begin()
err := txOrm.Read(u)
_ = txOrm.Commit()
return u, err
}
func originalTxUsingClosure() (*User, error) {
u := &User{}
var err error
o := orm.NewOrm()
_ = o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
err = txOrm.Read(u)
return nil
})
return u, err
}
type User struct {
Id int
Name string
}
func (u *User) TableName() string {
return "user"
}

View File

@@ -0,0 +1,92 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"github.com/beego/beego/v2/client/orm"
)
// DoNothingQueryM2Mer do nothing
// use it to build mock orm.QueryM2Mer
type DoNothingQueryM2Mer struct {
}
func (d *DoNothingQueryM2Mer) AddWithCtx(ctx context.Context, i ...interface{}) (int64, error) {
return 0, nil
}
func (d *DoNothingQueryM2Mer) RemoveWithCtx(ctx context.Context, i ...interface{}) (int64, error) {
return 0, nil
}
func (d *DoNothingQueryM2Mer) ExistWithCtx(ctx context.Context, i interface{}) bool {
return true
}
func (d *DoNothingQueryM2Mer) ClearWithCtx(ctx context.Context) (int64, error) {
return 0, nil
}
func (d *DoNothingQueryM2Mer) CountWithCtx(ctx context.Context) (int64, error) {
return 0, nil
}
func (d *DoNothingQueryM2Mer) Add(i ...interface{}) (int64, error) {
return 0, nil
}
func (d *DoNothingQueryM2Mer) Remove(i ...interface{}) (int64, error) {
return 0, nil
}
func (d *DoNothingQueryM2Mer) Exist(i interface{}) bool {
return true
}
func (d *DoNothingQueryM2Mer) Clear() (int64, error) {
return 0, nil
}
func (d *DoNothingQueryM2Mer) Count() (int64, error) {
return 0, nil
}
type QueryM2MerCondition struct {
tableName string
name string
}
func NewQueryM2MerCondition(tableName string, name string) *QueryM2MerCondition {
return &QueryM2MerCondition{
tableName: tableName,
name: name,
}
}
func (q *QueryM2MerCondition) Match(ctx context.Context, inv *orm.Invocation) bool {
res := true
if len(q.tableName) > 0 {
res = res && (q.tableName == inv.GetTableName())
}
if len(q.name) > 0 {
res = res && (len(inv.Args) > 1) && (q.name == inv.Args[1].(string))
}
return res
}

View File

@@ -0,0 +1,63 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/orm"
)
func TestDoNothingQueryM2Mer(t *testing.T) {
m2m := &DoNothingQueryM2Mer{}
i, err := m2m.Clear()
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = m2m.Count()
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = m2m.Add()
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = m2m.Remove()
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
assert.True(t, m2m.Exist(nil))
}
func TestNewQueryM2MerCondition(t *testing.T) {
cond := NewQueryM2MerCondition("", "")
res := cond.Match(context.Background(), &orm.Invocation{})
assert.True(t, res)
cond = NewQueryM2MerCondition("hello", "")
assert.False(t, cond.Match(context.Background(), &orm.Invocation{}))
cond = NewQueryM2MerCondition("", "A")
assert.False(t, cond.Match(context.Background(), &orm.Invocation{
Args: []interface{}{0, "B"},
}))
assert.True(t, cond.Match(context.Background(), &orm.Invocation{
Args: []interface{}{0, "A"},
}))
}

View File

@@ -0,0 +1,183 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"github.com/beego/beego/v2/client/orm"
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
)
// DoNothingQuerySetter do nothing
// usually you use this to build your mock QuerySetter
type DoNothingQuerySetter struct {
}
func (d *DoNothingQuerySetter) OrderClauses(orders ...*order_clause.Order) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) CountWithCtx(ctx context.Context) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) ExistWithCtx(ctx context.Context) bool {
return true
}
func (d *DoNothingQuerySetter) UpdateWithCtx(ctx context.Context, values orm.Params) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) DeleteWithCtx(ctx context.Context) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) PrepareInsertWithCtx(ctx context.Context) (orm.Inserter, error) {
return nil, nil
}
func (d *DoNothingQuerySetter) AllWithCtx(ctx context.Context, container interface{}, cols ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) OneWithCtx(ctx context.Context, container interface{}, cols ...string) error {
return nil
}
func (d *DoNothingQuerySetter) ValuesWithCtx(ctx context.Context, results *[]orm.Params, exprs ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) ValuesListWithCtx(ctx context.Context, results *[]orm.ParamsList, exprs ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) ValuesFlatWithCtx(ctx context.Context, result *orm.ParamsList, expr string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) Aggregate(s string) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) Filter(s string, i ...interface{}) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) FilterRaw(s string, s2 string) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) Exclude(s string, i ...interface{}) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) SetCond(condition *orm.Condition) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) GetCond() *orm.Condition {
return orm.NewCondition()
}
func (d *DoNothingQuerySetter) Limit(limit interface{}, args ...interface{}) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) Offset(offset interface{}) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) GroupBy(exprs ...string) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) OrderBy(exprs ...string) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) ForceIndex(indexes ...string) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) UseIndex(indexes ...string) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) IgnoreIndex(indexes ...string) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) RelatedSel(params ...interface{}) orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) Distinct() orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) ForUpdate() orm.QuerySeter {
return d
}
func (d *DoNothingQuerySetter) Count() (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) Exist() bool {
return true
}
func (d *DoNothingQuerySetter) Update(values orm.Params) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) Delete() (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) PrepareInsert() (orm.Inserter, error) {
return nil, nil
}
func (d *DoNothingQuerySetter) All(container interface{}, cols ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) One(container interface{}, cols ...string) error {
return nil
}
func (d *DoNothingQuerySetter) Values(results *[]orm.Params, exprs ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) ValuesList(results *[]orm.ParamsList, exprs ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) ValuesFlat(result *orm.ParamsList, expr string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) RowsToMap(result *orm.Params, keyCol, valueCol string) (int64, error) {
return 0, nil
}
func (d *DoNothingQuerySetter) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) {
return 0, nil
}

View File

@@ -0,0 +1,74 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDoNothingQuerySetter(t *testing.T) {
setter := &DoNothingQuerySetter{}
setter.GroupBy().Filter("").Limit(10).
Distinct().Exclude("a").FilterRaw("", "").
ForceIndex().ForUpdate().IgnoreIndex().
Offset(11).OrderBy().RelatedSel().SetCond(nil).UseIndex()
assert.True(t, setter.Exist())
err := setter.One(nil)
assert.Nil(t, err)
i, err := setter.Count()
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.Delete()
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.All(nil)
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.Update(nil)
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.RowsToMap(nil, "", "")
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.RowsToStruct(nil, "", "")
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.Values(nil)
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.ValuesFlat(nil, "")
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = setter.ValuesList(nil)
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
ins, err := setter.PrepareInsert()
assert.Nil(t, err)
assert.Nil(t, ins)
assert.NotNil(t, setter.GetCond())
}

View File

@@ -0,0 +1,64 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"database/sql"
"github.com/beego/beego/v2/client/orm"
)
type DoNothingRawSetter struct {
}
func (d *DoNothingRawSetter) Exec() (sql.Result, error) {
return nil, nil
}
func (d *DoNothingRawSetter) QueryRow(containers ...interface{}) error {
return nil
}
func (d *DoNothingRawSetter) QueryRows(containers ...interface{}) (int64, error) {
return 0, nil
}
func (d *DoNothingRawSetter) SetArgs(i ...interface{}) orm.RawSeter {
return d
}
func (d *DoNothingRawSetter) Values(container *[]orm.Params, cols ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingRawSetter) ValuesList(container *[]orm.ParamsList, cols ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingRawSetter) ValuesFlat(container *orm.ParamsList, cols ...string) (int64, error) {
return 0, nil
}
func (d *DoNothingRawSetter) RowsToMap(result *orm.Params, keyCol, valueCol string) (int64, error) {
return 0, nil
}
func (d *DoNothingRawSetter) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) {
return 0, nil
}
func (d *DoNothingRawSetter) Prepare() (orm.RawPreparer, error) {
return nil, nil
}

View File

@@ -0,0 +1,63 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDoNothingRawSetter(t *testing.T) {
rs := &DoNothingRawSetter{}
i, err := rs.ValuesList(nil)
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = rs.Values(nil)
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = rs.ValuesFlat(nil)
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = rs.RowsToStruct(nil, "", "")
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = rs.RowsToMap(nil, "", "")
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
i, err = rs.QueryRows()
assert.Equal(t, int64(0), i)
assert.Nil(t, err)
err = rs.QueryRow()
// assert.Equal(t, int64(0), i)
assert.Nil(t, err)
s, err := rs.Exec()
assert.Nil(t, err)
assert.Nil(t, s)
p, err := rs.Prepare()
assert.Nil(t, err)
assert.Nil(t, p)
rrs := rs.SetArgs()
assert.Equal(t, rrs, rs)
}

View File

@@ -0,0 +1,58 @@
// Copyright 2020 beego
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/beego/beego/v2/client/orm"
)
func TestOrmStub_FilterChain(t *testing.T) {
os := newOrmStub()
inv := &orm.Invocation{
Args: []interface{}{10},
}
i := 1
os.FilterChain(func(ctx context.Context, inv *orm.Invocation) []interface{} {
i++
return nil
})(context.Background(), inv)
assert.Equal(t, 2, i)
m := NewMock(NewSimpleCondition("", ""), nil, func(inv *orm.Invocation) {
arg := inv.Args[0]
j := arg.(int)
inv.Args[0] = j + 1
})
os.Mock(m)
os.FilterChain(nil)(context.Background(), inv)
assert.Equal(t, 11, inv.Args[0])
inv.Args[0] = 10
ctxMock := NewMock(NewSimpleCondition("", ""), nil, func(inv *orm.Invocation) {
arg := inv.Args[0]
j := arg.(int)
inv.Args[0] = j + 3
})
os.FilterChain(nil)(CtxWithMock(context.Background(), ctxMock), inv)
assert.Equal(t, 13, inv.Args[0])
}

View File

@@ -332,10 +332,6 @@ end:
// register register models to model cache
func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, models ...interface{}) (err error) {
if mc.done {
err = fmt.Errorf("register must be run before BootStrap")
return
}
for _, model := range models {
val := reflect.ValueOf(model)
@@ -352,7 +348,9 @@ func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, m
err = fmt.Errorf("<orm.RegisterModel> only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)
return
}
if val.Elem().Kind() == reflect.Slice {
val = reflect.New(val.Elem().Type().Elem())
}
table := getTableName(val)
if prefixOrSuffixStr != "" {
@@ -371,8 +369,7 @@ func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, m
}
if _, ok := mc.get(table); ok {
err = fmt.Errorf("<orm.RegisterModel> table name `%s` repeat register, must be unique\n", table)
return
return nil
}
mi := newModelInfo(val)
@@ -389,12 +386,6 @@ func (mc *_modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, m
}
}
}
if mi.fields.pk == nil {
err = fmt.Errorf("<orm.RegisterModel> `%s` needs a primary key field, default is to use 'id' if not set\n", name)
return
}
}
mi.table = table

View File

@@ -255,6 +255,22 @@ func NewTM() *TM {
return obj
}
type DeptInfo struct {
ID int `orm:"column(id)"`
Created time.Time `orm:"auto_now_add"`
DeptName string
EmployeeName string
Salary int
}
type UnregisterModel struct {
ID int `orm:"column(id)"`
Created time.Time `orm:"auto_now_add"`
DeptName string
EmployeeName string
Salary int
}
type User struct {
ID int `orm:"column(id)"`
UserName string `orm:"size(30);unique"`
@@ -476,45 +492,45 @@ var (
helpinfo = `need driver and source!
Default DB Drivers.
driver: url
mysql: https://github.com/go-sql-driver/mysql
sqlite3: https://github.com/mattn/go-sqlite3
postgres: https://github.com/lib/pq
tidb: https://github.com/pingcap/tidb
usage:
go get -u github.com/beego/beego/v2/client/orm
go get -u github.com/go-sql-driver/mysql
go get -u github.com/mattn/go-sqlite3
go get -u github.com/lib/pq
go get -u github.com/pingcap/tidb
#### MySQL
mysql -u root -e 'create database orm_test;'
export ORM_DRIVER=mysql
export ORM_SOURCE="root:@/orm_test?charset=utf8"
go test -v github.com/beego/beego/v2/client/orm
#### Sqlite3
export ORM_DRIVER=sqlite3
export ORM_SOURCE='file:memory_test?mode=memory'
go test -v github.com/beego/beego/v2/client/orm
#### PostgreSQL
psql -c 'create database orm_test;' -U postgres
export ORM_DRIVER=postgres
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
go test -v github.com/beego/beego/v2/client/orm
#### TiDB
export ORM_DRIVER=tidb
export ORM_SOURCE='memory://test/test'
go test -v github.com/beego/beego/v2/pgk/orm
`
)

View File

@@ -109,6 +109,9 @@ func getTableUnique(val reflect.Value) [][]string {
// get whether the table needs to be created for the database alias
func isApplicableTableForDB(val reflect.Value, db string) bool {
if !val.IsValid() {
return true
}
fun := val.MethodByName("IsApplicableTableForDB")
if fun.IsValid() {
vals := fun.Call([]reflect.Value{reflect.ValueOf(db)})

View File

@@ -58,6 +58,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
"os"
"reflect"
"time"
@@ -135,7 +136,7 @@ func (o *ormBase) Read(md interface{}, cols ...string) error {
}
func (o *ormBase) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true)
return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false)
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
}
// read data to model, like Read(), but use "SELECT FOR UPDATE" form
@@ -144,7 +145,7 @@ func (o *ormBase) ReadForUpdate(md interface{}, cols ...string) error {
}
func (o *ormBase) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true)
return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true)
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, true)
}
// Try to read a row from the database, or insert one if it doesn't exist
@@ -154,7 +155,7 @@ func (o *ormBase) ReadOrCreate(md interface{}, col1 string, cols ...string) (boo
func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
cols = append([]string{col1}, cols...)
mi, ind := o.getMiInd(md, true)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false)
err := o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
if err == ErrNoRows {
// Create
id, err := o.InsertWithCtx(ctx, md)
@@ -179,7 +180,7 @@ func (o *ormBase) Insert(md interface{}) (int64, error) {
}
func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
mi, ind := o.getMiInd(md, true)
id, err := o.alias.DbBaser.Insert(o.db, mi, ind, o.alias.TZ)
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
if err != nil {
return id, err
}
@@ -222,7 +223,7 @@ func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interfac
for i := 0; i < sind.Len(); i++ {
ind := reflect.Indirect(sind.Index(i))
mi, _ := o.getMiInd(ind.Interface(), false)
id, err := o.alias.DbBaser.Insert(o.db, mi, ind, o.alias.TZ)
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
if err != nil {
return cnt, err
}
@@ -233,7 +234,7 @@ func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interfac
}
} else {
mi, _ := o.getMiInd(sind.Index(0).Interface(), false)
return o.alias.DbBaser.InsertMulti(o.db, mi, sind, bulk, o.alias.TZ)
return o.alias.DbBaser.InsertMulti(ctx, o.db, mi, sind, bulk, o.alias.TZ)
}
return cnt, nil
}
@@ -244,7 +245,7 @@ func (o *ormBase) InsertOrUpdate(md interface{}, colConflictAndArgs ...string) (
}
func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias, colConflitAndArgs...)
id, err := o.alias.DbBaser.InsertOrUpdate(ctx, o.db, mi, ind, o.alias, colConflitAndArgs...)
if err != nil {
return id, err
}
@@ -261,7 +262,7 @@ func (o *ormBase) Update(md interface{}, cols ...string) (int64, error) {
}
func (o *ormBase) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
return o.alias.DbBaser.Update(o.db, mi, ind, o.alias.TZ, cols)
return o.alias.DbBaser.Update(ctx, o.db, mi, ind, o.alias.TZ, cols)
}
// delete model in database
@@ -271,7 +272,7 @@ func (o *ormBase) Delete(md interface{}, cols ...string) (int64, error) {
}
func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols)
num, err := o.alias.DbBaser.Delete(ctx, o.db, mi, ind, o.alias.TZ, cols)
if err != nil {
return num, err
}
@@ -283,9 +284,6 @@ func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...str
// create a models to models queryer
func (o *ormBase) QueryM2M(md interface{}, name string) QueryM2Mer {
return o.QueryM2MWithCtx(context.Background(), md, name)
}
func (o *ormBase) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer {
mi, ind := o.getMiInd(md, true)
fi := o.getFieldInfo(mi, name)
@@ -299,6 +297,12 @@ func (o *ormBase) QueryM2MWithCtx(ctx context.Context, md interface{}, name stri
return newQueryM2M(md, o, mi, fi, ind)
}
// NOTE: this method is deprecated, context parameter will not take effect.
func (o *ormBase) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QueryM2M as replacement please.")
return o.QueryM2M(md, name)
}
// load related models to md model.
// args are limit, offset int and order string.
//
@@ -351,7 +355,7 @@ func (o *ormBase) LoadRelatedWithCtx(ctx context.Context, md interface{}, name s
qs.relDepth = relDepth
if len(order) > 0 {
qs.orders = []string{order}
qs.orders = order_clause.ParseOrder(order)
}
find := ind.FieldByIndex(fi.fieldIndex)
@@ -451,9 +455,6 @@ func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *queryS
// table name can be string or struct.
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
func (o *ormBase) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
return o.QueryTableWithCtx(context.Background(), ptrStructOrTableName)
}
func (o *ormBase) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) {
var name string
if table, ok := ptrStructOrTableName.(string); ok {
name = nameStrategyMap[defaultNameStrategy](table)
@@ -469,7 +470,13 @@ func (o *ormBase) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName in
if qs == nil {
panic(fmt.Errorf("<Ormer.QueryTable> table name: `%s` not exists", name))
}
return
return qs
}
// NOTE: this method is deprecated, context parameter will not take effect.
func (o *ormBase) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) {
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QuerySeter as replacement please.")
return o.QueryTable(ptrStructOrTableName)
}
// return a raw query seter for raw sql string.
@@ -595,9 +602,8 @@ func NewOrm() Ormer {
func NewOrmUsingDB(aliasName string) Ormer {
if al, ok := dataBaseCache.get(aliasName); ok {
return newDBWithAlias(al)
} else {
panic(fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", aliasName))
}
panic(fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", aliasName))
}
// NewOrmWithDB create a new ormer object with specify *sql.DB for query

View File

@@ -16,12 +16,13 @@ package orm
import (
"fmt"
"github.com/beego/beego/v2/client/orm/clauses"
"strings"
)
// ExprSep define the expression separation
const (
ExprSep = "__"
ExprSep = clauses.ExprSep
)
type condValue struct {

View File

@@ -85,20 +85,31 @@ func (d *stmtQueryLog) Close() error {
}
func (d *stmtQueryLog) Exec(args ...interface{}) (sql.Result, error) {
return d.ExecContext(context.Background(), args...)
}
func (d *stmtQueryLog) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) {
a := time.Now()
res, err := d.stmt.Exec(args...)
res, err := d.stmt.ExecContext(ctx, args...)
debugLogQueies(d.alias, "st.Exec", d.query, a, err, args...)
return res, err
}
func (d *stmtQueryLog) Query(args ...interface{}) (*sql.Rows, error) {
return d.QueryContext(context.Background(), args...)
}
func (d *stmtQueryLog) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) {
a := time.Now()
res, err := d.stmt.Query(args...)
res, err := d.stmt.QueryContext(ctx, args...)
debugLogQueies(d.alias, "st.Query", d.query, a, err, args...)
return res, err
}
func (d *stmtQueryLog) QueryRow(args ...interface{}) *sql.Row {
return d.QueryRowContext(context.Background(), args...)
}
func (d *stmtQueryLog) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row {
a := time.Now()
res := d.stmt.QueryRow(args...)
debugLogQueies(d.alias, "st.QueryRow", d.query, a, nil, args...)

View File

@@ -15,6 +15,7 @@
package orm
import (
"context"
"fmt"
"reflect"
)
@@ -31,6 +32,10 @@ var _ Inserter = new(insertSet)
// insert model ignore it's registered or not.
func (o *insertSet) Insert(md interface{}) (int64, error) {
return o.InsertWithCtx(context.Background(), md)
}
func (o *insertSet) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
if o.closed {
return 0, ErrStmtClosed
}
@@ -44,7 +49,7 @@ func (o *insertSet) Insert(md interface{}) (int64, error) {
if name != o.mi.fullName {
panic(fmt.Errorf("<Inserter.Insert> need model `%s` but found `%s`", o.mi.fullName, name))
}
id, err := o.orm.alias.DbBaser.InsertStmt(o.stmt, o.mi, ind, o.orm.alias.TZ)
id, err := o.orm.alias.DbBaser.InsertStmt(ctx, o.stmt, o.mi, ind, o.orm.alias.TZ)
if err != nil {
return id, err
}
@@ -70,11 +75,11 @@ func (o *insertSet) Close() error {
}
// create new insert queryer.
func newInsertSet(orm *ormBase, mi *modelInfo) (Inserter, error) {
func newInsertSet(ctx context.Context, orm *ormBase, mi *modelInfo) (Inserter, error) {
bi := new(insertSet)
bi.orm = orm
bi.mi = mi
st, query, err := orm.alias.DbBaser.PrepareInsert(orm.db, mi)
st, query, err := orm.alias.DbBaser.PrepareInsert(ctx, orm.db, mi)
if err != nil {
return nil, err
}

View File

@@ -14,7 +14,10 @@
package orm
import "reflect"
import (
"context"
"reflect"
)
// model to model struct
type queryM2M struct {
@@ -33,6 +36,10 @@ type queryM2M struct {
//
// make sure the relation is defined in post model struct tag.
func (o *queryM2M) Add(mds ...interface{}) (int64, error) {
return o.AddWithCtx(context.Background(), mds...)
}
func (o *queryM2M) AddWithCtx(ctx context.Context, mds ...interface{}) (int64, error) {
fi := o.fi
mi := fi.relThroughModelInfo
mfi := fi.reverseFieldInfo
@@ -96,11 +103,15 @@ func (o *queryM2M) Add(mds ...interface{}) (int64, error) {
}
names = append(names, otherNames...)
values = append(values, otherValues...)
return dbase.InsertValue(orm.db, mi, true, names, values)
return dbase.InsertValue(ctx, orm.db, mi, true, names, values)
}
// remove models following the origin model relationship
func (o *queryM2M) Remove(mds ...interface{}) (int64, error) {
return o.RemoveWithCtx(context.Background(), mds...)
}
func (o *queryM2M) RemoveWithCtx(ctx context.Context, mds ...interface{}) (int64, error) {
fi := o.fi
qs := o.qs.Filter(fi.reverseFieldInfo.name, o.md)
@@ -109,21 +120,33 @@ func (o *queryM2M) Remove(mds ...interface{}) (int64, error) {
// check model is existed in relationship of origin model
func (o *queryM2M) Exist(md interface{}) bool {
return o.ExistWithCtx(context.Background(), md)
}
func (o *queryM2M) ExistWithCtx(ctx context.Context, md interface{}) bool {
fi := o.fi
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).
Filter(fi.reverseFieldInfoTwo.name, md).Exist()
Filter(fi.reverseFieldInfoTwo.name, md).ExistWithCtx(ctx)
}
// clean all models in related of origin model
func (o *queryM2M) Clear() (int64, error) {
return o.ClearWithCtx(context.Background())
}
func (o *queryM2M) ClearWithCtx(ctx context.Context) (int64, error) {
fi := o.fi
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).Delete()
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).DeleteWithCtx(ctx)
}
// count all related models of origin model
func (o *queryM2M) Count() (int64, error) {
return o.CountWithCtx(context.Background())
}
func (o *queryM2M) CountWithCtx(ctx context.Context) (int64, error) {
fi := o.fi
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).Count()
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).CountWithCtx(ctx)
}
var _ QueryM2Mer = new(queryM2M)

View File

@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
"github.com/beego/beego/v2/client/orm/hints"
)
@@ -64,21 +65,20 @@ func ColValue(opt operator, value interface{}) interface{} {
// real query struct
type querySet struct {
mi *modelInfo
cond *Condition
related []string
relDepth int
limit int64
offset int64
groups []string
orders []string
distinct bool
forUpdate bool
useIndex int
indexes []string
orm *ormBase
ctx context.Context
forContext bool
mi *modelInfo
cond *Condition
related []string
relDepth int
limit int64
offset int64
groups []string
orders []*order_clause.Order
distinct bool
forUpdate bool
useIndex int
indexes []string
orm *ormBase
aggregate string
}
var _ QuerySeter = new(querySet)
@@ -139,8 +139,20 @@ func (o querySet) GroupBy(exprs ...string) QuerySeter {
// add ORDER expression.
// "column" means ASC, "-column" means DESC.
func (o querySet) OrderBy(exprs ...string) QuerySeter {
o.orders = exprs
func (o querySet) OrderBy(expressions ...string) QuerySeter {
if len(expressions) <= 0 {
return &o
}
o.orders = order_clause.ParseOrder(expressions...)
return &o
}
// add ORDER expression.
func (o querySet) OrderClauses(orders ...*order_clause.Order) QuerySeter {
if len(orders) <= 0 {
return &o
}
o.orders = orders
return &o
}
@@ -210,23 +222,39 @@ func (o querySet) GetCond() *Condition {
// return QuerySeter execution result number
func (o *querySet) Count() (int64, error) {
return o.orm.alias.DbBaser.Count(o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
return o.CountWithCtx(context.Background())
}
func (o *querySet) CountWithCtx(ctx context.Context) (int64, error) {
return o.orm.alias.DbBaser.Count(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
}
// check result empty or not after QuerySeter executed
func (o *querySet) Exist() bool {
cnt, _ := o.orm.alias.DbBaser.Count(o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
return o.ExistWithCtx(context.Background())
}
func (o *querySet) ExistWithCtx(ctx context.Context) bool {
cnt, _ := o.orm.alias.DbBaser.Count(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
return cnt > 0
}
// execute update with parameters
func (o *querySet) Update(values Params) (int64, error) {
return o.orm.alias.DbBaser.UpdateBatch(o.orm.db, o, o.mi, o.cond, values, o.orm.alias.TZ)
return o.UpdateWithCtx(context.Background(), values)
}
func (o *querySet) UpdateWithCtx(ctx context.Context, values Params) (int64, error) {
return o.orm.alias.DbBaser.UpdateBatch(ctx, o.orm.db, o, o.mi, o.cond, values, o.orm.alias.TZ)
}
// execute delete
func (o *querySet) Delete() (int64, error) {
return o.orm.alias.DbBaser.DeleteBatch(o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
return o.DeleteWithCtx(context.Background())
}
func (o *querySet) DeleteWithCtx(ctx context.Context) (int64, error) {
return o.orm.alias.DbBaser.DeleteBatch(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
}
// return a insert queryer.
@@ -235,20 +263,32 @@ func (o *querySet) Delete() (int64, error) {
// i,err := sq.PrepareInsert()
// i.Add(&user1{},&user2{})
func (o *querySet) PrepareInsert() (Inserter, error) {
return newInsertSet(o.orm, o.mi)
return o.PrepareInsertWithCtx(context.Background())
}
func (o *querySet) PrepareInsertWithCtx(ctx context.Context) (Inserter, error) {
return newInsertSet(ctx, o.orm, o.mi)
}
// query all data and map to containers.
// cols means the columns when querying.
func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
return o.AllWithCtx(context.Background(), container, cols...)
}
func (o *querySet) AllWithCtx(ctx context.Context, container interface{}, cols ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadBatch(ctx, o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
}
// query one row data and map to containers.
// cols means the columns when querying.
func (o *querySet) One(container interface{}, cols ...string) error {
return o.OneWithCtx(context.Background(), container, cols...)
}
func (o *querySet) OneWithCtx(ctx context.Context, container interface{}, cols ...string) error {
o.limit = 1
num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
num, err := o.orm.alias.DbBaser.ReadBatch(ctx, o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
if err != nil {
return err
}
@@ -266,19 +306,31 @@ func (o *querySet) One(container interface{}, cols ...string) error {
// expres means condition expression.
// it converts data to []map[column]value.
func (o *querySet) Values(results *[]Params, exprs ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadValues(o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
return o.ValuesWithCtx(context.Background(), results, exprs...)
}
func (o *querySet) ValuesWithCtx(ctx context.Context, results *[]Params, exprs ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
}
// query all data and map to [][]interface
// it converts data to [][column_index]value
func (o *querySet) ValuesList(results *[]ParamsList, exprs ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadValues(o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
return o.ValuesListWithCtx(context.Background(), results, exprs...)
}
func (o *querySet) ValuesListWithCtx(ctx context.Context, results *[]ParamsList, exprs ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
}
// query all data and map to []interface.
// it's designed for one row record set, auto change to []value, not [][column]value.
func (o *querySet) ValuesFlat(result *ParamsList, expr string) (int64, error) {
return o.orm.alias.DbBaser.ReadValues(o.orm.db, o, o.mi, o.cond, []string{expr}, result, o.orm.alias.TZ)
return o.ValuesFlatWithCtx(context.Background(), result, expr)
}
func (o *querySet) ValuesFlatWithCtx(ctx context.Context, result *ParamsList, expr string) (int64, error) {
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, []string{expr}, result, o.orm.alias.TZ)
}
// query all rows into map[string]interface with specify key and value column name.
@@ -309,13 +361,6 @@ func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string)
panic(ErrNotImplement)
}
// set context to QuerySeter.
func (o querySet) WithContext(ctx context.Context) QuerySeter {
o.ctx = ctx
o.forContext = true
return &o
}
// create new QuerySeter.
func newQuerySet(orm *ormBase, mi *modelInfo) QuerySeter {
o := new(querySet)
@@ -323,3 +368,9 @@ func newQuerySet(orm *ormBase, mi *modelInfo) QuerySeter {
o.orm = orm
return o
}
// aggregate func
func (o querySet) Aggregate(s string) QuerySeter {
o.aggregate = s
return &o
}

View File

@@ -21,6 +21,7 @@ import (
"context"
"database/sql"
"fmt"
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
"io/ioutil"
"math"
"os"
@@ -205,6 +206,7 @@ func TestSyncDb(t *testing.T) {
RegisterModel(new(Index))
RegisterModel(new(StrPk))
RegisterModel(new(TM))
RegisterModel(new(DeptInfo))
err := RunSyncdb("default", true, Debug)
throwFail(t, err)
@@ -232,6 +234,7 @@ func TestRegisterModels(t *testing.T) {
RegisterModel(new(Index))
RegisterModel(new(StrPk))
RegisterModel(new(TM))
RegisterModel(new(DeptInfo))
BootStrap()
@@ -333,6 +336,73 @@ func TestTM(t *testing.T) {
throwFail(t, AssertIs(recTM.TMPrecision2.String(), "2020-08-07 02:07:04.1235 +0000 UTC"))
}
func TestUnregisterModel(t *testing.T) {
data := []*DeptInfo{
{
DeptName: "A",
EmployeeName: "A1",
Salary: 1000,
},
{
DeptName: "A",
EmployeeName: "A2",
Salary: 2000,
},
{
DeptName: "B",
EmployeeName: "B1",
Salary: 2000,
},
{
DeptName: "B",
EmployeeName: "B2",
Salary: 4000,
},
{
DeptName: "B",
EmployeeName: "B3",
Salary: 3000,
},
}
qs := dORM.QueryTable("dept_info")
i, _ := qs.PrepareInsert()
for _, d := range data {
_, err := i.Insert(d)
if err != nil {
throwFail(t, err)
}
}
f := func() {
var res []UnregisterModel
n, err := dORM.QueryTable("dept_info").All(&res)
throwFail(t, err)
throwFail(t, AssertIs(n, 5))
throwFail(t, AssertIs(res[0].EmployeeName, "A1"))
type Sum struct {
DeptName string
Total int
}
var sun []Sum
qs.Aggregate("dept_name,sum(salary) as total").GroupBy("dept_name").OrderBy("dept_name").All(&sun)
throwFail(t, AssertIs(sun[0].DeptName, "A"))
throwFail(t, AssertIs(sun[0].Total, 3000))
type Max struct {
DeptName string
Max float64
}
var max []Max
qs.Aggregate("dept_name,max(salary) as max").GroupBy("dept_name").OrderBy("dept_name").All(&max)
throwFail(t, AssertIs(max[1].DeptName, "B"))
throwFail(t, AssertIs(max[1].Max, 4000))
}
for i := 0; i < 5; i++ {
f()
}
}
func TestNullDataTypes(t *testing.T) {
d := DataNull{}
@@ -1077,6 +1147,26 @@ func TestOrderBy(t *testing.T) {
num, err = qs.OrderBy("-profile__age").Filter("user_name", "astaxie").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
num, err = qs.OrderClauses(
order_clause.Clause(
order_clause.Column(`profile__age`),
order_clause.SortDescending(),
),
).Filter("user_name", "astaxie").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
if IsMysql {
num, err = qs.OrderClauses(
order_clause.Clause(
order_clause.Column(`rand()`),
order_clause.Raw(),
),
).Filter("user_name", "astaxie").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
}
}
func TestAll(t *testing.T) {
@@ -1163,6 +1253,19 @@ func TestValues(t *testing.T) {
throwFail(t, AssertIs(maps[2]["Profile"], nil))
}
num, err = qs.OrderClauses(
order_clause.Clause(
order_clause.Column("Id"),
order_clause.SortAscending(),
),
).Values(&maps)
throwFail(t, err)
throwFail(t, AssertIs(num, 3))
if num == 3 {
throwFail(t, AssertIs(maps[0]["UserName"], "slene"))
throwFail(t, AssertIs(maps[2]["Profile"], nil))
}
num, err = qs.OrderBy("Id").Values(&maps, "UserName", "Profile__Age")
throwFail(t, err)
throwFail(t, AssertIs(num, 3))
@@ -2717,3 +2820,23 @@ func TestCondition(t *testing.T) {
throwFail(t, AssertIs(!cycleFlag, true))
return
}
func TestContextCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
user := User{UserName: "slene"}
err := dORM.ReadWithCtx(ctx, &user, "UserName")
throwFail(t, err)
cancel()
err = dORM.ReadWithCtx(ctx, &user, "UserName")
throwFail(t, AssertIs(err, context.Canceled))
ctx, cancel = context.WithCancel(context.Background())
cancel()
qs := dORM.QueryTable(user)
_, err = qs.Filter("UserName", "slene").CountWithCtx(ctx)
throwFail(t, AssertIs(err, context.Canceled))
}

View File

@@ -20,6 +20,7 @@ import (
"reflect"
"time"
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
"github.com/beego/beego/v2/core/utils"
)
@@ -196,12 +197,16 @@ type DQL interface {
// post := Post{Id: 4}
// m2m := Ormer.QueryM2M(&post, "Tags")
QueryM2M(md interface{}, name string) QueryM2Mer
// NOTE: this method is deprecated, context parameter will not take effect.
// Use context.Context directly on methods with `WithCtx` suffix such as InsertWithCtx/UpdateWithCtx
QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer
// return a QuerySeter for table operations.
// table name can be string or struct.
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
QueryTable(ptrStructOrTableName interface{}) QuerySeter
// NOTE: this method is deprecated, context parameter will not take effect.
// Use context.Context directly on methods with `WithCtx` suffix such as InsertWithCtx/UpdateWithCtx
QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter
DBStats() *sql.DBStats
@@ -236,6 +241,7 @@ type TxOrmer interface {
// Inserter insert prepared statement
type Inserter interface {
Insert(interface{}) (int64, error)
InsertWithCtx(context.Context, interface{}) (int64, error)
Close() error
}
@@ -295,6 +301,28 @@ type QuerySeter interface {
// for example:
// qs.OrderBy("-status")
OrderBy(exprs ...string) QuerySeter
// add ORDER expression by order clauses
// for example:
// OrderClauses(
// order_clause.Clause(
// order.Column("Id"),
// order.SortAscending(),
// ),
// order_clause.Clause(
// order.Column("status"),
// order.SortDescending(),
// ),
// )
// OrderClauses(order_clause.Clause(
// order_clause.Column(`user__status`),
// order_clause.SortDescending(),//default None
// ))
// OrderClauses(order_clause.Clause(
// order_clause.Column(`random()`),
// order_clause.SortNone(),//default None
// order_clause.Raw(),//default false.if true, do not check field is valid or not
// ))
OrderClauses(orders ...*order_clause.Order) QuerySeter
// add FORCE INDEX expression.
// for example:
// qs.ForceIndex(`idx_name1`,`idx_name2`)
@@ -333,9 +361,11 @@ type QuerySeter interface {
// for example:
// num, err = qs.Filter("profile__age__gt", 28).Count()
Count() (int64, error)
CountWithCtx(context.Context) (int64, error)
// check result empty or not after QuerySeter executed
// the same as QuerySeter.Count > 0
Exist() bool
ExistWithCtx(context.Context) bool
// execute update with parameters
// for example:
// num, err = qs.Filter("user_name", "slene").Update(Params{
@@ -345,11 +375,13 @@ type QuerySeter interface {
// "user_name": "slene2"
// }) // user slene's name will change to slene2
Update(values Params) (int64, error)
UpdateWithCtx(ctx context.Context, values Params) (int64, error)
// delete from table
// for example:
// num ,err = qs.Filter("user_name__in", "testing1", "testing2").Delete()
// //delete two user who's name is testing1 or testing2
Delete() (int64, error)
DeleteWithCtx(context.Context) (int64, error)
// return a insert queryer.
// it can be used in times.
// example:
@@ -358,18 +390,21 @@ type QuerySeter interface {
// num, err = i.Insert(&user2) // user table will add one record user2 at once
// err = i.Close() //don't forget call Close
PrepareInsert() (Inserter, error)
PrepareInsertWithCtx(context.Context) (Inserter, error)
// query all data and map to containers.
// cols means the columns when querying.
// for example:
// var users []*User
// qs.All(&users) // users[0],users[1],users[2] ...
All(container interface{}, cols ...string) (int64, error)
AllWithCtx(ctx context.Context, container interface{}, cols ...string) (int64, error)
// query one row data and map to containers.
// cols means the columns when querying.
// for example:
// var user User
// qs.One(&user) //user.UserName == "slene"
One(container interface{}, cols ...string) error
OneWithCtx(ctx context.Context, container interface{}, cols ...string) error
// query all data and map to []map[string]interface.
// expres means condition expression.
// it converts data to []map[column]value.
@@ -377,18 +412,21 @@ type QuerySeter interface {
// var maps []Params
// qs.Values(&maps) //maps[0]["UserName"]=="slene"
Values(results *[]Params, exprs ...string) (int64, error)
ValuesWithCtx(ctx context.Context, results *[]Params, exprs ...string) (int64, error)
// query all data and map to [][]interface
// it converts data to [][column_index]value
// for example:
// var list []ParamsList
// qs.ValuesList(&list) // list[0][1] == "slene"
ValuesList(results *[]ParamsList, exprs ...string) (int64, error)
ValuesListWithCtx(ctx context.Context, results *[]ParamsList, exprs ...string) (int64, error)
// query all data and map to []interface.
// it's designed for one column record set, auto change to []value, not [][column]value.
// for example:
// var list ParamsList
// qs.ValuesFlat(&list, "UserName") // list[0] == "slene"
ValuesFlat(result *ParamsList, expr string) (int64, error)
ValuesFlatWithCtx(ctx context.Context, result *ParamsList, expr string) (int64, error)
// query all rows into map[string]interface with specify key and value column name.
// keyCol = "name", valueCol = "value"
// table data
@@ -411,6 +449,15 @@ type QuerySeter interface {
// Found int
// }
RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error)
// aggregate func.
// for example:
// type result struct {
// DeptName string
// Total int
// }
// var res []result
// o.QueryTable("dept_info").Aggregate("dept_name,sum(salary) as total").GroupBy("dept_name").All(&res)
Aggregate(s string) QuerySeter
}
// QueryM2Mer model to model query struct
@@ -428,18 +475,23 @@ type QueryM2Mer interface {
// insert one or more rows to m2m table
// make sure the relation is defined in post model struct tag.
Add(...interface{}) (int64, error)
AddWithCtx(context.Context, ...interface{}) (int64, error)
// remove models following the origin model relationship
// only delete rows from m2m table
// for example:
// tag3 := &Tag{Id:5,Name: "TestTag3"}
// num, err = m2m.Remove(tag3)
Remove(...interface{}) (int64, error)
RemoveWithCtx(context.Context, ...interface{}) (int64, error)
// check model is existed in relationship of origin model
Exist(interface{}) bool
ExistWithCtx(context.Context, interface{}) bool
// clean all models in related of origin model
Clear() (int64, error)
ClearWithCtx(context.Context) (int64, error)
// count all related models of origin model
Count() (int64, error)
CountWithCtx(context.Context) (int64, error)
}
// RawPreparer raw query statement
@@ -513,11 +565,11 @@ type RawSeter interface {
type stmtQuerier interface {
Close() error
Exec(args ...interface{}) (sql.Result, error)
// ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
Query(args ...interface{}) (*sql.Rows, error)
// QueryContext(args ...interface{}) (*sql.Rows, error)
QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error)
QueryRow(args ...interface{}) *sql.Row
// QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
}
// db querier
@@ -554,28 +606,28 @@ type txEnder interface {
// base database struct
type dbBaser interface {
Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error
ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error)
Count(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
ReadValues(dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error)
Read(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error
ReadBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error)
Count(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
ReadValues(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error)
Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
Insert(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
InsertOrUpdate(context.Context, dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
InsertMulti(context.Context, dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
InsertValue(context.Context, dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
InsertStmt(context.Context, stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
Update(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
Update(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
UpdateBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
DeleteBatch(dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
Delete(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
DeleteBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
SupportUpdateJoin() bool
OperatorSQL(string) string
GenerateOperatorSQL(*modelInfo, *fieldInfo, string, []interface{}, *time.Location) (string, []interface{})
GenerateOperatorLeftCol(*fieldInfo, string, *string)
PrepareInsert(dbQuerier, *modelInfo) (stmtQuerier, string, error)
PrepareInsert(context.Context, dbQuerier, *modelInfo) (stmtQuerier, string, error)
MaxLimit() uint64
TableQuote() string
ReplaceMarks(*string)
@@ -584,12 +636,12 @@ type dbBaser interface {
TimeToDB(*time.Time, *time.Location)
DbTypes() map[string]string
GetTables(dbQuerier) (map[string]bool, error)
GetColumns(dbQuerier, string) (map[string][3]string, error)
GetColumns(context.Context, dbQuerier, string) (map[string][3]string, error)
ShowTablesQuery() string
ShowColumnsQuery(string) string
IndexExists(dbQuerier, string, string) bool
IndexExists(context.Context, dbQuerier, string, string) bool
collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error)
setval(dbQuerier, *modelInfo, []string) error
setval(context.Context, dbQuerier, *modelInfo, []string) error
GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string
}