mirror of
https://github.com/hanzoai/mock.git
synced 2026-09-30 22:56:01 +00:00
850 lines
21 KiB
Go
850 lines
21 KiB
Go
// 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"
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/hanzo-ds/go"
|
|
"github.com/hanzo-ds/go/lib/column"
|
|
"github.com/hanzo-ds/go/lib/driver"
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
)
|
|
|
|
type OpError struct { // taken from https://github.com/ClickHouse/datastore-go/blob/bfd9f33931482ddacadee5a899b760455b9268e6/datastore.go#L51
|
|
Op string
|
|
ColumnName string
|
|
Err error
|
|
}
|
|
|
|
func (e *OpError) Error() string {
|
|
switch err := e.Err.(type) {
|
|
case *column.Error:
|
|
return fmt.Sprintf("datastore [%s]: (%s %s) %s", e.Op, e.ColumnName, err.ColumnType, err.Err)
|
|
case *column.ColumnConverterError:
|
|
var hint string
|
|
if len(err.Hint) != 0 {
|
|
hint += ". " + err.Hint
|
|
}
|
|
return fmt.Sprintf("datastore [%s]: (%s) converting %s to %s is unsupported%s",
|
|
err.Op, e.ColumnName,
|
|
err.From, err.To,
|
|
hint,
|
|
)
|
|
}
|
|
return fmt.Sprintf("datastore [%s]: %s", e.Op, e.Err)
|
|
}
|
|
|
|
// ClickConnMockCommon interface serves to create expectations
|
|
// for any kind of database action in order to mock
|
|
// and test real database behavior.
|
|
type ClickConnMockCommon interface {
|
|
// ExpectClose queues an expectation for this database
|
|
// action to be triggered. the *ExpectedClose allows
|
|
// to mock database response
|
|
ExpectClose() *ExpectedClose
|
|
|
|
// ExpectStats queues an expectation for this database
|
|
// action to be triggered. the *ExpectedStats allows
|
|
// to mock database response
|
|
ExpectStats() *ExpectedStats
|
|
|
|
// ExpectPing expects *driver.Conn.Ping to be called.
|
|
// the *ExpectedPing allows to mock database response
|
|
ExpectPing() *ExpectedPing
|
|
|
|
// ExpectAsyncInsert expects *driver.Conn.AsyncInsert to be called.
|
|
// the *ExpectedAsyncInsert allows to mock database response
|
|
ExpectAsyncInsert(expectedSQL string, expectedWait bool) *ExpectedAsyncInsert
|
|
|
|
// ExpectExec expects Exec() to be called with expectedSQL query.
|
|
// the *ExpectedExec allows to mock database response
|
|
ExpectExec(expectedSQL string) *ExpectedExec
|
|
|
|
// ExpectPrepareBatch expects PrepareBatch() to be called with expectedSQL query.
|
|
// the *ExpectedPrepareBatch allows to mock database response.
|
|
// Note that you may expect Append() or AppendStruct() on the *ExpectedPrepareBatch
|
|
// statement to prevent repeating expectedSQL
|
|
ExpectPrepareBatch(expectedSQL string) *ExpectedPrepareBatch
|
|
|
|
// ExpectQueryRow expects QueryRow() to be called with expectedSQL query.
|
|
// the *ExpectedQuery allows to mock database response.
|
|
ExpectQueryRow(expectedSQL string) *ExpectedQueryRow
|
|
|
|
// ExpectQuery expects Query() to be called with expectedSQL query.
|
|
// the *ExpectedQuery allows to mock database response.
|
|
ExpectQuery(expectedSQL string) *ExpectedQuery
|
|
|
|
// ExpectSelect expects Select() to be called with expectedSQL query.
|
|
// the *ExpectedSelect allows to mock database response.
|
|
ExpectSelect(expectedSQL string) *ExpectedSelect
|
|
|
|
// ExpectServerVersion queues an expectation for this database
|
|
// action to be triggered. the *ExpectedServerVersion allows
|
|
// to mock database response
|
|
ExpectServerVersion() *ExpectedServerVersion
|
|
|
|
// ExpectContributors queues an expectation for this database
|
|
// action to be triggered. the *ExpectedContributors allows
|
|
// to mock database response
|
|
ExpectContributors() *ExpectedContributors
|
|
|
|
// ExpectationsWereMet checks whether all queued expectations
|
|
// were met in order. If any of them was not met - an error is returned.
|
|
ExpectationsWereMet() error
|
|
|
|
// MatchExpectationsInOrder gives an option whether to match all
|
|
// expectations in the order they were set or not.
|
|
//
|
|
// By default it is set to true. But if you use goroutines
|
|
// to parallelize your query executation, that option may
|
|
// be handy.
|
|
//
|
|
// This option may be turned on anytime during tests. As soon
|
|
// as it is switched to false, expectations will be matched
|
|
// in any order. Or otherwise if switched to true, any unmatched
|
|
// expectations will be expected in order
|
|
MatchExpectationsInOrder(bool)
|
|
}
|
|
|
|
type datastoremock struct {
|
|
ordered bool
|
|
dsn string
|
|
opened int
|
|
drv *mockDatastoreDriver
|
|
queryMatcher sqlmock.QueryMatcher
|
|
monitorPings bool
|
|
|
|
expected []expectation
|
|
}
|
|
|
|
var _ ClickConnMockCommon = (*datastoremock)(nil)
|
|
var _ driver.Conn = (*datastoremock)(nil)
|
|
|
|
func (c *datastoremock) open(options *datastore.Options) (*datastoremock, error) {
|
|
if c.queryMatcher == nil {
|
|
c.queryMatcher = sqlmock.QueryMatcherRegexp
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *datastoremock) queryMatcherFunc() sqlmock.QueryMatcher {
|
|
return c.queryMatcher
|
|
}
|
|
|
|
func (c *datastoremock) MatchExpectationsInOrder(b bool) {
|
|
c.ordered = b
|
|
}
|
|
|
|
func (c *datastoremock) ExpectClose() *ExpectedClose {
|
|
e := &ExpectedClose{}
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// Close a mock database driver connection. It may or may not
|
|
// be called depending on the circumstances, but if it is called
|
|
// there must be an *ExpectedClose expectation satisfied.
|
|
// meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) Close() error {
|
|
c.drv.Lock()
|
|
defer c.drv.Unlock()
|
|
|
|
c.opened--
|
|
if c.opened == 0 {
|
|
delete(c.drv.conns, c.dsn)
|
|
}
|
|
|
|
var expected *ExpectedClose
|
|
var fulfilled int
|
|
var ok bool
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if expected, ok = next.(*ExpectedClose); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
if c.ordered {
|
|
return fmt.Errorf("call to database Close, was not expected, next expectation is: %s", next)
|
|
}
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to database Close was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return fmt.Errorf("%s", msg)
|
|
}
|
|
|
|
expected.triggered = true
|
|
expected.Unlock()
|
|
return expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectStats() *ExpectedStats {
|
|
e := &ExpectedStats{}
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// Stats meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) Stats() driver.Stats {
|
|
c.drv.Lock()
|
|
defer c.drv.Unlock()
|
|
|
|
var expected *ExpectedStats
|
|
var fulfilled int
|
|
var ok bool
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if expected, ok = next.(*ExpectedStats); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
if c.ordered {
|
|
panic(fmt.Errorf("call to database Stats, was not expected, next expectation is: %s", next))
|
|
}
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to database Stats was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
panic((msg))
|
|
}
|
|
|
|
expected.triggered = true
|
|
expected.Unlock()
|
|
return expected.stats
|
|
}
|
|
|
|
// Ping meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) Ping(ctx context.Context) error {
|
|
if !c.monitorPings {
|
|
return nil
|
|
}
|
|
|
|
c.drv.Lock()
|
|
defer c.drv.Unlock()
|
|
|
|
var expected *ExpectedPing
|
|
var fulfilled int
|
|
var ok bool
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if expected, ok = next.(*ExpectedPing); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
if c.ordered {
|
|
panic(fmt.Errorf("call to database Ping, was not expected, next expectation is: %s", next))
|
|
}
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to database Ping was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
panic(fmt.Errorf("%s", msg))
|
|
}
|
|
|
|
expected.triggered = true
|
|
expected.Unlock()
|
|
return expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectPing() *ExpectedPing {
|
|
e := &ExpectedPing{}
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
func (c *datastoremock) ExpectAsyncInsert(expectedSQL string, expectedWait bool) *ExpectedAsyncInsert {
|
|
e := &ExpectedAsyncInsert{}
|
|
e.expectSQL = expectedSQL
|
|
e.expectWait = expectedWait
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// AsyncInsert meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) AsyncInsert(ctx context.Context, query string, wait bool, args ...any) error {
|
|
ex, err := c.asyncInsert(ctx, query, wait)
|
|
if ex != nil {
|
|
select {
|
|
case <-time.After(ex.delay):
|
|
return err
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *datastoremock) asyncInsert(ctx context.Context, query string, wait bool) (*ExpectedAsyncInsert, error) {
|
|
var expected *ExpectedAsyncInsert
|
|
var fulfilled int
|
|
var ok bool
|
|
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if c.ordered {
|
|
if expected, ok = next.(*ExpectedAsyncInsert); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
return nil, fmt.Errorf("call to AsyncInsert statement with query '%s', was not expected, next expectation is: %s", query, next)
|
|
}
|
|
|
|
if pr, ok := next.(*ExpectedAsyncInsert); ok {
|
|
if err := c.queryMatcher.Match(pr.expectSQL, query); err == nil {
|
|
expected = pr
|
|
break
|
|
}
|
|
}
|
|
next.Unlock()
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to AsyncInsert '%s' query was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return nil, fmt.Errorf(msg, query)
|
|
}
|
|
defer expected.Unlock()
|
|
if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil {
|
|
return nil, fmt.Errorf("AsyncInsert: %v", err)
|
|
}
|
|
|
|
expected.triggered = true
|
|
return expected, expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectExec(expectedSQL string) *ExpectedExec {
|
|
e := &ExpectedExec{}
|
|
e.expectSQL = expectedSQL
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// Exec meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) Exec(ctx context.Context, query string, args ...any) error {
|
|
c.drv.Lock()
|
|
defer c.drv.Unlock()
|
|
|
|
var expected *ExpectedExec
|
|
var fulfilled int
|
|
var ok bool
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if expected, ok = next.(*ExpectedExec); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
if c.ordered {
|
|
return fmt.Errorf("call to database Exec, was not expected, next expectation is: %s", next)
|
|
}
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to database Exec was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return fmt.Errorf("%s", msg)
|
|
}
|
|
|
|
if err := c.queryMatcherFunc().Match(expected.expectSQL, query); err != nil {
|
|
expected.Unlock()
|
|
return fmt.Errorf("call to database Exec with unexpected query: %s", query)
|
|
}
|
|
|
|
expected.triggered = true
|
|
expected.Unlock()
|
|
return expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectPrepareBatch(expectedSQL string) *ExpectedPrepareBatch {
|
|
e := &ExpectedPrepareBatch{expectSQL: expectedSQL}
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// PrepareBatch meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) PrepareBatch(ctx context.Context, query string, opts ...driver.PrepareBatchOption) (driver.Batch, error) {
|
|
ex, err := c.prepareBatch(ctx, query)
|
|
if ex != nil {
|
|
select {
|
|
case <-time.After(ex.delay):
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &batch{c, ex, query}, nil
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
func (c *datastoremock) prepareBatch(ctx context.Context, query string) (*ExpectedPrepareBatch, error) {
|
|
var expected *ExpectedPrepareBatch
|
|
var fulfilled int
|
|
var ok bool
|
|
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if c.ordered {
|
|
if expected, ok = next.(*ExpectedPrepareBatch); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
return nil, fmt.Errorf("call to Prepare statement with query '%s', was not expected, next expectation is: %s", query, next)
|
|
}
|
|
|
|
if pr, ok := next.(*ExpectedPrepareBatch); ok {
|
|
if err := c.queryMatcher.Match(pr.expectSQL, query); err == nil {
|
|
expected = pr
|
|
break
|
|
}
|
|
}
|
|
next.Unlock()
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to Prepare '%s' query was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return nil, fmt.Errorf(msg, query)
|
|
}
|
|
defer expected.Unlock()
|
|
if err := c.queryMatcherFunc().Match(expected.expectSQL, query); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
expected.triggered = true
|
|
return expected, expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectQueryRow(expectedSQL string) *ExpectedQueryRow {
|
|
e := &ExpectedQueryRow{}
|
|
e.expectSQL = expectedSQL
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// QueryRow meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) QueryRow(ctx context.Context, query string, args ...any) driver.Row {
|
|
ex, err := c.queryRow(ctx, query, args...)
|
|
if ex != nil {
|
|
ex.row.err = err
|
|
}
|
|
if ex != nil {
|
|
select {
|
|
case <-time.After(ex.delay):
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
return ex.row
|
|
}
|
|
|
|
func (c *datastoremock) queryRow(ctx context.Context, query string, args ...any) (*ExpectedQueryRow, error) {
|
|
var expected *ExpectedQueryRow
|
|
var fulfilled int
|
|
var ok bool
|
|
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if c.ordered {
|
|
if expected, ok = next.(*ExpectedQueryRow); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
return nil, fmt.Errorf("call to QueryRow statement with query '%s', was not expected, next expectation is: %s", query, next)
|
|
}
|
|
|
|
if pr, ok := next.(*ExpectedQueryRow); ok {
|
|
if err := c.queryMatcher.Match(pr.expectSQL, query); err == nil {
|
|
expected = pr
|
|
break
|
|
}
|
|
}
|
|
next.Unlock()
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to QueryRow '%s' query was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return nil, fmt.Errorf(msg, query)
|
|
}
|
|
defer expected.Unlock()
|
|
if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil {
|
|
return nil, fmt.Errorf("QueryRow: %v", err)
|
|
}
|
|
|
|
expected.triggered = true
|
|
return expected, expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectQuery(expectedSQL string) *ExpectedQuery {
|
|
e := &ExpectedQuery{}
|
|
e.expectSQL = expectedSQL
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// Query meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) Query(ctx context.Context, query string, args ...any) (driver.Rows, error) {
|
|
ex, err := c.query(ctx, query, args...)
|
|
if ex != nil {
|
|
select {
|
|
case <-time.After(ex.delay):
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ex.rows, nil
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
func (c *datastoremock) query(ctx context.Context, query string, args ...any) (*ExpectedQuery, error) {
|
|
var expected *ExpectedQuery
|
|
var fulfilled int
|
|
var ok bool
|
|
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if c.ordered {
|
|
if expected, ok = next.(*ExpectedQuery); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
return nil, fmt.Errorf("call to Query statement with query '%s', was not expected, next expectation is: %s", query, next)
|
|
}
|
|
|
|
if pr, ok := next.(*ExpectedQuery); ok {
|
|
if err := c.queryMatcher.Match(pr.expectSQL, query); err == nil {
|
|
expected = pr
|
|
break
|
|
}
|
|
}
|
|
next.Unlock()
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to Query '%s' query was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return nil, fmt.Errorf(msg, query)
|
|
}
|
|
defer expected.Unlock()
|
|
if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil {
|
|
return nil, fmt.Errorf("Query: %v", err)
|
|
}
|
|
|
|
if err := expected.matchArgs(args); err != nil {
|
|
return nil, fmt.Errorf("Query: '%s' arguments do not match: %s", query, err)
|
|
}
|
|
|
|
expected.triggered = true
|
|
return expected, expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectSelect(expectedSQL string) *ExpectedSelect {
|
|
e := &ExpectedSelect{}
|
|
e.expectSQL = expectedSQL
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// Select meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) Select(ctx context.Context, dest any, query string, args ...any) error {
|
|
// Implementation based on that of Select in datastore-go https://github.com/ClickHouse/datastore-go/blob/main/scan.go#L29
|
|
dstSlicePtr := reflect.ValueOf(dest)
|
|
if dstSlicePtr.Kind() != reflect.Ptr {
|
|
return &OpError{
|
|
Op: "Select",
|
|
Err: fmt.Errorf("must pass a pointer, not a value, to Select destination"),
|
|
}
|
|
}
|
|
if dstSlicePtr.IsNil() {
|
|
return &OpError{
|
|
Op: "Select",
|
|
Err: fmt.Errorf("nil pointer passed to Select destination"),
|
|
}
|
|
}
|
|
|
|
dstSlice := reflect.Indirect(dstSlicePtr)
|
|
if dstSlice.Kind() != reflect.Slice {
|
|
return fmt.Errorf("must pass a slice to Select destination")
|
|
}
|
|
if dstSlice.Len() != 0 {
|
|
// dest should point to empty slice
|
|
// to make select result correct
|
|
dstSlice.Set(reflect.MakeSlice(dstSlice.Type(), 0, dstSlice.Cap()))
|
|
}
|
|
dstSliceElType := dstSlice.Type().Elem()
|
|
|
|
ex, err := c.selectQuery(ctx, query)
|
|
if ex != nil {
|
|
select {
|
|
case <-time.After(ex.delay):
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer ex.rows.Close()
|
|
for ex.rows.Next() {
|
|
elem := reflect.New(dstSliceElType)
|
|
if err := ex.rows.ScanStruct(elem.Interface()); err != nil {
|
|
return err
|
|
}
|
|
dstSlice.Set(reflect.Append(dstSlice, elem.Elem()))
|
|
}
|
|
if err := ex.rows.Close(); err != nil {
|
|
return err
|
|
}
|
|
return ex.rows.Err()
|
|
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *datastoremock) selectQuery(ctx context.Context, query string) (*ExpectedSelect, error) {
|
|
var expected *ExpectedSelect
|
|
var fulfilled int
|
|
var ok bool
|
|
|
|
for _, next := range c.expected {
|
|
next.Lock()
|
|
if next.fulfilled() {
|
|
next.Unlock()
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if c.ordered {
|
|
if expected, ok = next.(*ExpectedSelect); ok {
|
|
break
|
|
}
|
|
|
|
next.Unlock()
|
|
return nil, fmt.Errorf("call to Select statement with query '%s', was not expected, next expectation is: %s", query, next)
|
|
}
|
|
|
|
if pr, ok := next.(*ExpectedSelect); ok {
|
|
if err := c.queryMatcher.Match(pr.expectSQL, query); err == nil {
|
|
expected = pr
|
|
break
|
|
}
|
|
}
|
|
next.Unlock()
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to Select '%s' query was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return nil, fmt.Errorf(msg, query)
|
|
}
|
|
defer expected.Unlock()
|
|
if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil {
|
|
return nil, fmt.Errorf("Select: %v", err)
|
|
}
|
|
|
|
expected.triggered = true
|
|
return expected, expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectServerVersion() *ExpectedServerVersion {
|
|
e := &ExpectedServerVersion{}
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// ServerVersion returns the version of the database.
|
|
// meets http://golang.org/pkg/database/sql/driver/#Conn interface
|
|
func (c *datastoremock) ServerVersion() (*driver.ServerVersion, error) {
|
|
c.drv.Lock()
|
|
defer c.drv.Unlock()
|
|
|
|
var expected *ExpectedServerVersion
|
|
var fulfilled int
|
|
var ok bool
|
|
for _, next := range c.expected {
|
|
|
|
if next.fulfilled() {
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if expected, ok = next.(*ExpectedServerVersion); ok {
|
|
break
|
|
}
|
|
|
|
if c.ordered {
|
|
return &driver.ServerVersion{}, fmt.Errorf("call to database ServerVersion, was not expected, next expectation is: %s", next)
|
|
}
|
|
}
|
|
|
|
if expected == nil {
|
|
msg := "call to database ServerVersion was not expected"
|
|
if fulfilled == len(c.expected) {
|
|
msg = "all expectations were already fulfilled, " + msg
|
|
}
|
|
return &driver.ServerVersion{}, fmt.Errorf("%s", msg)
|
|
}
|
|
|
|
expected.triggered = true
|
|
return &expected.version, expected.err
|
|
}
|
|
|
|
func (c *datastoremock) ExpectContributors() *ExpectedContributors {
|
|
e := &ExpectedContributors{}
|
|
c.expected = append(c.expected, e)
|
|
return e
|
|
}
|
|
|
|
// Contributors meets https://pkg.go.dev/github.com/hanzo-ds/go/lib/driver#Conn interface
|
|
func (c *datastoremock) Contributors() []string {
|
|
c.drv.Lock()
|
|
defer c.drv.Unlock()
|
|
|
|
var expected *ExpectedContributors
|
|
var fulfilled int
|
|
var ok bool
|
|
for _, next := range c.expected {
|
|
|
|
if next.fulfilled() {
|
|
fulfilled++
|
|
continue
|
|
}
|
|
|
|
if expected, ok = next.(*ExpectedContributors); ok {
|
|
break
|
|
}
|
|
|
|
if c.ordered {
|
|
return []string{}
|
|
}
|
|
}
|
|
|
|
if expected == nil {
|
|
return []string{}
|
|
}
|
|
|
|
expected.triggered = true
|
|
return expected.contributors
|
|
}
|
|
|
|
func (c *datastoremock) ExpectationsWereMet() error {
|
|
for _, e := range c.expected {
|
|
e.Lock()
|
|
fulfilled := e.fulfilled()
|
|
e.Unlock()
|
|
|
|
if !fulfilled {
|
|
return fmt.Errorf("there is a remaining expectation which was not matched: %s", e)
|
|
}
|
|
|
|
// for expected prepared statement check whether it was closed if expected
|
|
if prep, ok := e.(*ExpectedPrepareBatch); ok {
|
|
if prep.mustBeSent && !prep.wasClosed {
|
|
return fmt.Errorf("expected prepared statement to be closed, but it was not: %s", prep)
|
|
}
|
|
}
|
|
|
|
// must check whether all expected queried rows are closed
|
|
if query, ok := e.(*ExpectedQuery); ok {
|
|
if query.rowsMustBeClosed && !query.rowsWereClosed {
|
|
return fmt.Errorf("expected query rows to be closed, but it was not: %s", query)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|