Merge branch 'develop' of https://gitclone.com/github.com/beego/beego into frt/delete-txorm
# Conflicts: # CHANGELOG.md
This commit is contained in:
131
client/httplib/error_code.go
Normal file
131
client/httplib/error_code.go
Normal 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
|
||||
`)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
36
client/httplib/http_response_test.go
Normal file
36
client/httplib/http_response_test.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
17
client/httplib/module.go
Normal 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
81
client/httplib/setting.go
Normal 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)
|
||||
}
|
||||
6
client/orm/clauses/const.go
Normal file
6
client/orm/clauses/const.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package clauses
|
||||
|
||||
const (
|
||||
ExprSep = "__"
|
||||
ExprDot = "."
|
||||
)
|
||||
103
client/orm/clauses/order_clause/order.go
Normal file
103
client/orm/clauses/order_clause/order.go
Normal 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
|
||||
}
|
||||
}
|
||||
144
client/orm/clauses/order_clause/order_test.go
Normal file
144
client/orm/clauses/order_clause/order_test.go
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
146
client/orm/db.go
146
client/orm/db.go
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, ", "))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
63
client/orm/mock/condition.go
Normal file
63
client/orm/mock/condition.go
Normal 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
|
||||
}
|
||||
41
client/orm/mock/condition_test.go
Normal file
41
client/orm/mock/condition_test.go
Normal 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",
|
||||
}))
|
||||
}
|
||||
40
client/orm/mock/context.go
Normal file
40
client/orm/mock/context.go
Normal 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
|
||||
}
|
||||
29
client/orm/mock/context_test.go
Normal file
29
client/orm/mock/context_test.go
Normal 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
72
client/orm/mock/mock.go
Normal 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
162
client/orm/mock/mock_orm.go
Normal 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)
|
||||
}
|
||||
297
client/orm/mock/mock_orm_test.go
Normal file
297
client/orm/mock/mock_orm_test.go
Normal 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"
|
||||
}
|
||||
92
client/orm/mock/mock_queryM2Mer.go
Normal file
92
client/orm/mock/mock_queryM2Mer.go
Normal 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
|
||||
}
|
||||
|
||||
|
||||
63
client/orm/mock/mock_queryM2Mer_test.go
Normal file
63
client/orm/mock/mock_queryM2Mer_test.go
Normal 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"},
|
||||
}))
|
||||
}
|
||||
183
client/orm/mock/mock_querySetter.go
Normal file
183
client/orm/mock/mock_querySetter.go
Normal 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
|
||||
}
|
||||
74
client/orm/mock/mock_querySetter_test.go
Normal file
74
client/orm/mock/mock_querySetter_test.go
Normal 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())
|
||||
}
|
||||
64
client/orm/mock/mock_rawSetter.go
Normal file
64
client/orm/mock/mock_rawSetter.go
Normal 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
|
||||
}
|
||||
63
client/orm/mock/mock_rawSetter_test.go
Normal file
63
client/orm/mock/mock_rawSetter_test.go
Normal 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)
|
||||
}
|
||||
58
client/orm/mock/mock_test.go
Normal file
58
client/orm/mock/mock_test.go
Normal 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])
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -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)})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user