squash all ydb-support commit history
(temp fix) ignore secure connection test - use `ydb` as hostname fix go vet fix CI update ydb-go-sdk to v3.52.1 fix `GetTables`
This commit is contained in:
parent
3eda0f7805
commit
8c5508fa79
|
|
@ -0,0 +1,61 @@
|
|||
name: test ydb
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
GOPROXY: https://goproxy.io,direct
|
||||
GOPATH: /go_path
|
||||
GOCACHE: /go_cache
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: test ydb
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# - name: cache go path
|
||||
# id: cache-go-path
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: /go_path
|
||||
# key: go_path-${{ github.repository }}-${{ github.ref_name }}
|
||||
# restore-keys: |
|
||||
# go_path-${{ github.repository }}-
|
||||
# go_path-
|
||||
# - name: cache go cache
|
||||
# id: cache-go-cache
|
||||
# uses: https://github.com/actions/cache@v3
|
||||
# with:
|
||||
# path: /go_cache
|
||||
# key: go_cache-${{ github.repository }}-${{ github.ref_name }}
|
||||
# restore-keys: |
|
||||
# go_cache-${{ github.repository }}-
|
||||
# go_cache-
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.20
|
||||
- uses: actions/checkout@v3
|
||||
#- name: test ydb (secure connection)
|
||||
# run: TEST_YDB_SCHEME=grpcs TEST_YDB_HOST=ydb:2135 TEST_YDB_DBNAME=local make test-ydb
|
||||
- name: test ydb (insecure connection)
|
||||
run: TEST_YDB_SCHEME=grpc TEST_YDB_HOST=ydb:2136 TEST_YDB_DBNAME=local make test-ydb
|
||||
|
||||
services:
|
||||
ydb:
|
||||
image: cr.yandex/yc/yandex-docker-local-ydb:23.2
|
||||
ports:
|
||||
- 2135:2135
|
||||
- 2136:2136
|
||||
- 8765:8765
|
||||
#volumes:
|
||||
# - /tmp/ydb_certs:/ydb_certs
|
||||
env:
|
||||
YDB_LOCAL_SURVIVE_RESTART: true
|
||||
YDB_USE_IN_MEMORY_PDISKS: true
|
||||
|
||||
env:
|
||||
#YDB_SSL_ROOT_CERTIFICATES_FILE: /tmp/ydb_certs/ca.pem
|
||||
YDB_SESSIONS_SHUTDOWN_URLS: http://ydb:8765/actors/kqp_proxy?force_shutdown=all
|
||||
HIDE_APPLICATION_OUTPUT: 1
|
||||
18
Makefile
18
Makefile
|
|
@ -10,7 +10,7 @@ GO_DIRS := caches contexts integrations core dialects internal log migrate names
|
|||
GOFILES := $(wildcard *.go)
|
||||
GOFILES += $(shell find $(GO_DIRS) -name "*.go" -type f)
|
||||
INTEGRATION_PACKAGES := xorm.io/xorm/tests
|
||||
PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...))
|
||||
PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES) $(INTEGRATION_PACKAGES)/ydbtest,$(shell $(GO) list ./...))
|
||||
|
||||
TEST_COCKROACH_HOST ?= cockroach:26257
|
||||
TEST_COCKROACH_SCHEMA ?=
|
||||
|
|
@ -47,6 +47,15 @@ TEST_DAMENG_HOST ?= dameng:5236
|
|||
TEST_DAMENG_USERNAME ?= SYSDBA
|
||||
TEST_DAMENG_PASSWORD ?= SYSDBA
|
||||
|
||||
TEST_YDB_SCHEME ?= grpc
|
||||
TEST_YDB_HOST ?= ydb:2136
|
||||
TEST_YDB_DBNAME ?= local
|
||||
TEST_YDB_TABLE_PATH_PREFIX ?= /local/xorm/test
|
||||
TEST_YDB_QUERY_BIND ?= table_path_prefix($(TEST_YDB_TABLE_PATH_PREFIX)),declare,numeric
|
||||
TEST_YDB_FAKE_TX ?= scan,scheme,scripting
|
||||
TEST_YDB_USERNAME ?=
|
||||
TEST_YDB_PASSWORD ?=
|
||||
|
||||
TEST_CACHE_ENABLE ?= false
|
||||
TEST_QUOTE_POLICY ?= always
|
||||
|
||||
|
|
@ -107,6 +116,7 @@ help:
|
|||
@echo " - test-sqlite3 run integration tests for sqlite"
|
||||
@echo " - test-sqlite run integration tests for pure go sqlite"
|
||||
@echo " - test-tidb run integration tests for tidb"
|
||||
@echo " - test-ydb run integration tests for ydb"
|
||||
@echo " - vet examines Go source code and reports suspicious constructs"
|
||||
|
||||
.PHONY: lint
|
||||
|
|
@ -277,6 +287,12 @@ test-dameng\#%: go-check
|
|||
-conn_str="dm://$(TEST_DAMENG_USERNAME):$(TEST_DAMENG_PASSWORD)@$(TEST_DAMENG_HOST)" \
|
||||
-coverprofile=dameng.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
|
||||
|
||||
.PHONY: test-ydb
|
||||
test-ydb: go-check
|
||||
$(GO) test $(INTEGRATION_PACKAGES)/ydbtest -v -race -db=ydb -cache=$(TEST_CACHE_ENABLE) \
|
||||
-conn_str="$(TEST_YDB_SCHEME)://$(TEST_YDB_HOST)/$(TEST_YDB_DBNAME)?go_query_bind=$(TEST_YDB_QUERY_BIND)&go_fake_tx=$(TEST_YDB_FAKE_TX)" \
|
||||
-quote=$(TEST_QUOTE_POLICY) -coverprofile=ydb.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
$(GO) vet $(shell $(GO) list ./...)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ Drivers for Go's sql package which currently support database/sql includes:
|
|||
- [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment)
|
||||
- [github.com/sijms/go-ora](https://github.com/sijms/go-ora) (experiment)
|
||||
|
||||
* [YDB](https://github.com/ydb-platform/ydb)
|
||||
- [github.com/ydb-platform/ydb-go-sdk](https://github.com/ydb-platform/ydb-go-sdk)
|
||||
|
||||
## Installation
|
||||
|
||||
go get xorm.io/xorm
|
||||
|
|
|
|||
|
|
@ -46,6 +46,16 @@ func Interface2Interface(userLocation *time.Location, v interface{}) (interface{
|
|||
return vv.Time.In(userLocation).Format("2006-01-02 15:04:05"), nil
|
||||
}
|
||||
return "", nil
|
||||
case *NullUint32:
|
||||
if vv.Valid {
|
||||
return vv.Uint32, nil
|
||||
}
|
||||
return nil, nil
|
||||
case *NullUint64:
|
||||
if vv.Valid {
|
||||
return vv.Uint64, nil
|
||||
}
|
||||
return nil, nil
|
||||
default:
|
||||
return "", fmt.Errorf("convert assign string unsupported type: %#v", vv)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package convert
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -180,3 +181,58 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
|
|||
}
|
||||
return nil, fmt.Errorf("unsupported value %#v as time", src)
|
||||
}
|
||||
|
||||
func AsDuration(src interface{}) (*time.Duration, error) {
|
||||
switch t := src.(type) {
|
||||
case string:
|
||||
d, err := time.ParseDuration(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
case *sql.NullString:
|
||||
if !t.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
d, err := time.ParseDuration(t.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
case int64:
|
||||
d := time.Duration(t)
|
||||
return &d, nil
|
||||
case *int64:
|
||||
d := time.Duration(*t)
|
||||
return &d, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported value %#v as duration", src)
|
||||
}
|
||||
|
||||
var _ sql.Scanner = &NullDuration{}
|
||||
|
||||
type NullDuration struct {
|
||||
Duration time.Duration
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (n *NullDuration) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
n.Duration, n.Valid = time.Duration(0), false
|
||||
return nil
|
||||
}
|
||||
n.Valid = true
|
||||
d, err := AsDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.Duration = *d
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NullDuration) Value() (driver.Value, error) {
|
||||
if !n.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return n.Duration, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ type Dialect interface {
|
|||
|
||||
Filters() []Filter
|
||||
SetParams(params map[string]string)
|
||||
|
||||
IsRetryable(err error) (canRetry bool)
|
||||
}
|
||||
|
||||
// Base represents a basic dialect and all real dialects could embed this struct
|
||||
|
|
@ -247,6 +249,11 @@ func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string {
|
|||
func (db *Base) SetParams(params map[string]string) {
|
||||
}
|
||||
|
||||
// check if an error is retryable
|
||||
func (db *Base) IsRetryable(err error) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var dialects = map[string]func() Dialect{}
|
||||
|
||||
// RegisterDialect register database dialect
|
||||
|
|
@ -281,6 +288,7 @@ func regDrvsNDialects() bool {
|
|||
"sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }},
|
||||
"oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }},
|
||||
"godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }},
|
||||
"ydb": {"ydb", func() Driver { return &ydbDriver{} }, func() Dialect { return &ydb{} }},
|
||||
"oracle": {"oracle", func() Driver { return &oracleDriver{} }, func() Dialect { return &oracle{} }},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -143,3 +143,65 @@ func oracleSeqFilterConvertQuestionMark(sql, prefix string, start int) string {
|
|||
func (s *oracleSeqFilter) Do(ctx context.Context, sql string) string {
|
||||
return oracleSeqFilterConvertQuestionMark(sql, s.Prefix, s.Start)
|
||||
}
|
||||
|
||||
// ydbSeqFilter filter SQL replace ?, ? ... to $1, $2 ...
|
||||
type ydbSeqFilter struct {
|
||||
Prefix string
|
||||
Start int
|
||||
}
|
||||
|
||||
func ydbSeqFilterConvertQuestionMark(sql, prefix string, start int) string {
|
||||
var buf strings.Builder
|
||||
var beginSingleQuote bool
|
||||
var isLineComment bool
|
||||
var isComment bool
|
||||
var isMaybeLineComment bool
|
||||
var isMaybeComment bool
|
||||
var isMaybeCommentEnd bool
|
||||
index := start
|
||||
for _, c := range sql {
|
||||
if !beginSingleQuote && !isLineComment && !isComment && c == '?' {
|
||||
buf.WriteString(prefix)
|
||||
buf.WriteString(strconv.Itoa(index))
|
||||
index++
|
||||
} else {
|
||||
if isMaybeLineComment {
|
||||
if c == '-' {
|
||||
isLineComment = true
|
||||
}
|
||||
isMaybeLineComment = false
|
||||
} else if isMaybeComment {
|
||||
if c == '*' {
|
||||
isComment = true
|
||||
}
|
||||
isMaybeComment = false
|
||||
} else if isMaybeCommentEnd {
|
||||
if c == '/' {
|
||||
isComment = false
|
||||
}
|
||||
isMaybeCommentEnd = false
|
||||
} else if isLineComment {
|
||||
if c == '\n' {
|
||||
isLineComment = false
|
||||
}
|
||||
} else if isComment {
|
||||
if c == '*' {
|
||||
isMaybeCommentEnd = true
|
||||
}
|
||||
} else if !beginSingleQuote && c == '-' {
|
||||
isMaybeLineComment = true
|
||||
} else if !beginSingleQuote && c == '/' {
|
||||
isMaybeComment = true
|
||||
} else if c == '\'' {
|
||||
beginSingleQuote = !beginSingleQuote
|
||||
}
|
||||
buf.WriteRune(c)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Do implements Filter
|
||||
func (s *ydbSeqFilter) Do(ctx context.Context, sql string) string {
|
||||
return ydbSeqFilterConvertQuestionMark(sql, s.Prefix, s.Start)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import (
|
|||
|
||||
// FormatColumnTime format column time
|
||||
func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.Column, t time.Time) (interface{}, error) {
|
||||
if dialect != nil && dialect.URI().DBType == schemas.YDB && t.IsZero() {
|
||||
return (*time.Time)(nil), nil
|
||||
}
|
||||
|
||||
if t.IsZero() {
|
||||
if col.Nullable {
|
||||
return nil, nil
|
||||
|
|
@ -41,6 +45,9 @@ func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.C
|
|||
}
|
||||
return t.Format(layout), nil
|
||||
case schemas.DateTime, schemas.TimeStamp:
|
||||
if dialect != nil && dialect.URI().DBType == schemas.YDB {
|
||||
return t, nil
|
||||
}
|
||||
layout := "2006-01-02 15:04:05"
|
||||
if col.Length > 0 {
|
||||
// we can use int(...) casting here as it's very unlikely to a huge sized field
|
||||
|
|
@ -55,7 +62,12 @@ func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.C
|
|||
} else {
|
||||
return t.Format(time.RFC3339Nano), nil
|
||||
}
|
||||
case schemas.Interval:
|
||||
return time.Since(t), nil
|
||||
case schemas.BigInt, schemas.Int:
|
||||
if dialect != nil && dialect.URI().DBType == schemas.YDB {
|
||||
return t.UnixMicro(), nil
|
||||
}
|
||||
return t.Unix(), nil
|
||||
default:
|
||||
return t, nil
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,438 @@
|
|||
package dialects
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseYDBConnString(t *testing.T) {
|
||||
type result struct {
|
||||
dbType string
|
||||
host string
|
||||
port string
|
||||
dbName string
|
||||
userName string
|
||||
password string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
connString string
|
||||
expected result
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
connString: "grpc://localhost:2136/local",
|
||||
expected: result{
|
||||
dbType: "ydb",
|
||||
host: "localhost",
|
||||
port: "2136",
|
||||
dbName: "/local",
|
||||
userName: "",
|
||||
password: "",
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
connString: "grpcs://localhost:2135/local",
|
||||
expected: result{
|
||||
dbType: "ydb",
|
||||
host: "localhost",
|
||||
port: "2135",
|
||||
dbName: "/local",
|
||||
userName: "",
|
||||
password: "",
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
connString: "grpcs://ydb.serverless.yandexcloud.net:2135/ru-central1/b1g8skpblkos03malf3s/etn01q5ko6sh271beftr",
|
||||
expected: result{
|
||||
dbType: "ydb",
|
||||
host: "ydb.serverless.yandexcloud.net",
|
||||
port: "2135",
|
||||
dbName: "/ru-central1/b1g8skpblkos03malf3s/etn01q5ko6sh271beftr",
|
||||
userName: "",
|
||||
password: "",
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
connString: "https://localhost:2135/local",
|
||||
expected: result{},
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
connString: "grpcs://localhost:2135/local?query_mode=data&go_query_bind=table_path_prefix(/local/test),numeric,declare",
|
||||
expected: result{
|
||||
dbType: "ydb",
|
||||
host: "localhost",
|
||||
port: "2135",
|
||||
dbName: "/local",
|
||||
userName: "",
|
||||
password: "",
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
connString: "grpcs://user:password@localhost:2135/local",
|
||||
expected: result{
|
||||
dbType: "ydb",
|
||||
host: "localhost",
|
||||
port: "2135",
|
||||
dbName: "/local",
|
||||
userName: "user",
|
||||
password: "password",
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
connString: "grpcs://lb.etn03r9df42nb631unbv.ydb.mdb.yandexcloud.net:2135/ru-central1/b1g8skpblkos03malf3s/etn03r9df42nb631unbv",
|
||||
expected: result{
|
||||
dbType: "ydb",
|
||||
host: "lb.etn03r9df42nb631unbv.ydb.mdb.yandexcloud.net",
|
||||
port: "2135",
|
||||
dbName: "/ru-central1/b1g8skpblkos03malf3s/etn03r9df42nb631unbv",
|
||||
userName: "",
|
||||
password: "",
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
driver := QueryDriver("ydb")
|
||||
for _, test := range tests {
|
||||
t.Run(test.connString, func(t *testing.T) {
|
||||
info, err := driver.Parse("ydb", test.connString)
|
||||
|
||||
if err != nil && test.valid {
|
||||
t.Errorf("%q got unexpected error: %s", test.connString, err)
|
||||
} else if err == nil {
|
||||
expected := test.expected
|
||||
actual := result{}
|
||||
if test.valid {
|
||||
actual = result{
|
||||
dbType: string(info.DBType),
|
||||
host: info.Host,
|
||||
port: info.Port,
|
||||
dbName: info.DBName,
|
||||
userName: info.User,
|
||||
password: info.Passwd,
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("%q got: %+v want: %+v", test.connString, actual, expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// error object for testing `IsRetryable()` method of YDB.
|
||||
type mockError struct {
|
||||
code int32
|
||||
name string
|
||||
}
|
||||
|
||||
func (merr mockError) Error() string {
|
||||
return fmt.Sprintf("%d/%s", merr.code, merr.name)
|
||||
}
|
||||
|
||||
func (merr mockError) Code() int32 {
|
||||
return merr.code
|
||||
}
|
||||
|
||||
func (merr mockError) Name() string {
|
||||
return merr.name
|
||||
}
|
||||
|
||||
func TestIsRetryableYDB(t *testing.T) {
|
||||
ydbDialect := QueryDialect("ydb") // get ydb dialect
|
||||
|
||||
for _, curErr := range []struct {
|
||||
retryable bool
|
||||
err error
|
||||
}{
|
||||
{
|
||||
retryable: false,
|
||||
err: fmt.Errorf("unknown error"),
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: fmt.Errorf("errors.As() failed"),
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: context.DeadlineExceeded,
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: context.Canceled,
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_Unknown),
|
||||
name: "grpc unknown",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_InvalidArgument),
|
||||
name: "grpc invalid argument",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_DeadlineExceeded),
|
||||
name: "grpc deadline exceeded",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_NotFound),
|
||||
name: "grpc not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_AlreadyExists),
|
||||
name: "grpc already exists",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_PermissionDenied),
|
||||
name: "grpc permission denied",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_FailedPrecondition),
|
||||
name: "grpc failed precondition",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_OutOfRange),
|
||||
name: "grpc out of range",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_Unimplemented),
|
||||
name: "grpc unimplemented",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_DataLoss),
|
||||
name: "grpc data loss",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_Unauthenticated),
|
||||
name: "grpc unauthenticated",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_Canceled),
|
||||
name: "grpc canceled",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_ResourceExhausted),
|
||||
name: "grpc resource exhauseed",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_Aborted),
|
||||
name: "grpc aborted",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_Internal),
|
||||
name: "grpc internal",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: int32(ydb_grpc_Unavailable),
|
||||
name: "grpc unavailable",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_STATUS_CODE_UNSPECIFIED,
|
||||
name: "ydb status code unspecified",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_BAD_REQUEST,
|
||||
name: "ydb bad request",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_UNAUTHORIZED,
|
||||
name: "ydb unauthorized",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_INTERNAL_ERROR,
|
||||
name: "ydb internal error",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_SCHEME_ERROR,
|
||||
name: "ydb scheme error",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_GENERIC_ERROR,
|
||||
name: "ydb generic error",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_TIMEOUT,
|
||||
name: "ydb timeout",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_PRECONDITION_FAILED,
|
||||
name: "ydb precondition failed",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_ALREADY_EXISTS,
|
||||
name: "ydb already exists",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_NOT_FOUND,
|
||||
name: "ydb not found",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_SESSION_EXPIRED,
|
||||
name: "ydb session expired",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_CANCELLED,
|
||||
name: "ydb cancelled",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: mockError{
|
||||
code: ydb_UNSUPPORTED,
|
||||
name: "ydb unsupported",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: ydb_ABORTED,
|
||||
name: "ydb aborted",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: ydb_UNAVAILABLE,
|
||||
name: "ydb unavailable",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: ydb_OVERLOADED,
|
||||
name: "ydb overloaded",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: ydb_BAD_SESSION,
|
||||
name: "ydb bad session",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: ydb_UNDETERMINED,
|
||||
name: "ydb undetermined",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: mockError{
|
||||
code: ydb_SESSION_BUSY,
|
||||
name: "ydb session busy",
|
||||
},
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: fmt.Errorf("wrap error: %w", mockError{code: int32(ydb_grpc_Unknown), name: "wrap grpc unknown"}),
|
||||
},
|
||||
{
|
||||
retryable: true,
|
||||
err: fmt.Errorf("wrap error: %w", mockError{code: int32(ydb_UNAVAILABLE), name: "wrap ydb unavailable"}),
|
||||
},
|
||||
{
|
||||
retryable: false,
|
||||
err: fmt.Errorf("wrap error: %w", mockError{code: -1, name: "unknown error"}),
|
||||
},
|
||||
} {
|
||||
t.Run(curErr.err.Error(), func(t *testing.T) {
|
||||
retryable := ydbDialect.IsRetryable(curErr.err)
|
||||
assert.EqualValues(t, curErr.retryable, retryable)
|
||||
})
|
||||
}
|
||||
}
|
||||
111
engine.go
111
engine.go
|
|
@ -19,11 +19,13 @@ import (
|
|||
|
||||
"xorm.io/xorm/caches"
|
||||
"xorm.io/xorm/contexts"
|
||||
"xorm.io/xorm/convert"
|
||||
"xorm.io/xorm/core"
|
||||
"xorm.io/xorm/dialects"
|
||||
"xorm.io/xorm/internal/utils"
|
||||
"xorm.io/xorm/log"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/retry"
|
||||
"xorm.io/xorm/schemas"
|
||||
"xorm.io/xorm/tags"
|
||||
)
|
||||
|
|
@ -550,6 +552,10 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w
|
|||
}
|
||||
|
||||
for _, index := range dstTable.Indexes {
|
||||
// !datbeohbbh! with YDB, if there are indexes in table, these indexes have already been created in CREATE TABLE script.
|
||||
if dstDialect.URI().DBType == schemas.YDB {
|
||||
continue
|
||||
}
|
||||
_, err = io.WriteString(w, dstDialect.CreateIndexSQL(dstTable.Name, index)+";\n")
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -792,6 +798,56 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w
|
|||
return err
|
||||
}
|
||||
}
|
||||
} else if dstDialect.URI().DBType == schemas.YDB {
|
||||
castTmpl := "CAST(%v AS Optional<%v>)"
|
||||
yqlType := dstDialect.SQLType(dstTable.Columns()[i])
|
||||
if dstTable.Columns()[i].IsPrimaryKey {
|
||||
if strings.HasPrefix(yqlType, "UINT") || strings.HasPrefix(yqlType, "INT") {
|
||||
if _, err = io.WriteString(w, s.String); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if yqlType == "TIMESTAMP" {
|
||||
fromLoc := engine.GetTZLocation()
|
||||
toLoc := engine.GetTZDatabase()
|
||||
t, err := convert.String2Time(s.String, fromLoc, toLoc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.WriteString(w, fmt.Sprintf("%v", t.UnixMicro())); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "\\'")+"'"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if yqlType == "TIMESTAMP" {
|
||||
fromLoc := engine.GetTZLocation()
|
||||
toLoc := engine.GetTZDatabase()
|
||||
t, err := convert.String2Time(s.String, fromLoc, toLoc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.WriteString(w, fmt.Sprintf(castTmpl, t.UnixMicro(), yqlType)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if yqlType == "INTERVAL" {
|
||||
// !datbeohbbh! TODO: only work if database represent interval time in microsecond.
|
||||
d, err := strconv.ParseInt(s.String, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sec := float64(d) / float64(time.Microsecond)
|
||||
if _, err = io.WriteString(w, fmt.Sprintf(castTmpl, sec, yqlType)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err = io.WriteString(w, fmt.Sprintf(castTmpl, "'"+strings.ReplaceAll(s.String, "'", "\\'")+"'", yqlType)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil {
|
||||
return err
|
||||
|
|
@ -1433,3 +1489,58 @@ func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interf
|
|||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Do is a retryer of session
|
||||
func (engine *Engine) Do(ctx context.Context, f func(context.Context, *Session) error, opts ...retry.RetryOption) error {
|
||||
var (
|
||||
dialect = engine.Dialect()
|
||||
attempts = 0
|
||||
)
|
||||
err := retry.Retry(ctx, dialect.IsRetryable, func(ctx context.Context) (err error) {
|
||||
attempts++
|
||||
session := engine.NewSession().Context(ctx)
|
||||
defer func() {
|
||||
_ = session.Close()
|
||||
}()
|
||||
if err = f(ctx, session); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("operation failed after %d attempts: %w", attempts, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoTx is a retryer of session transactions
|
||||
func (engine *Engine) DoTx(ctx context.Context, f func(context.Context, *Session) error, opts ...retry.RetryOption) error {
|
||||
var (
|
||||
dialect = engine.Dialect()
|
||||
attempts = 0
|
||||
)
|
||||
err := retry.Retry(ctx, dialect.IsRetryable, func(ctx context.Context) (err error) {
|
||||
attempts++
|
||||
session := engine.NewSession().Context(ctx)
|
||||
defer func() {
|
||||
_ = session.Close()
|
||||
}()
|
||||
if err = session.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = session.Rollback()
|
||||
}()
|
||||
if err = f(ctx, session); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = session.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tx failed after %d attempts: %w", attempts, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -7,7 +7,9 @@ require (
|
|||
github.com/denisenkom/go-mssqldb v0.12.3
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/goccy/go-json v0.8.1
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jackc/pgx/v4 v4.18.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/lib/pq v1.10.7
|
||||
|
|
@ -15,6 +17,7 @@ require (
|
|||
github.com/shopspring/decimal v1.3.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.52.1
|
||||
github.com/ziutek/mymysql v1.5.4
|
||||
modernc.org/sqlite v1.20.4
|
||||
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978
|
||||
|
|
|
|||
|
|
@ -640,9 +640,9 @@ func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string,
|
|||
if len(sqlOrArgs) > 1 {
|
||||
newArgs := make([]interface{}, 0, len(sqlOrArgs)-1)
|
||||
for _, arg := range sqlOrArgs[1:] {
|
||||
if v, ok := arg.(time.Time); ok {
|
||||
if v, ok := arg.(time.Time); ok && statement.dialect.URI().DBType != schemas.YDB {
|
||||
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
|
||||
} else if v, ok := arg.(*time.Time); ok && v != nil {
|
||||
} else if v, ok := arg.(*time.Time); ok && v != nil && statement.dialect.URI().DBType != schemas.YDB {
|
||||
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
|
||||
} else if v, ok := arg.(convert.ConversionTo); ok {
|
||||
r, err := v.ToDB()
|
||||
|
|
|
|||
|
|
@ -92,12 +92,57 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
|
|||
} else if fieldType.ConvertibleTo(nullFloatType) {
|
||||
t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64)
|
||||
if !t.Valid {
|
||||
return nil, nil
|
||||
return (*float64)(nil), nil
|
||||
}
|
||||
return t.Float64, nil
|
||||
} else if fieldType.ConvertibleTo(bigFloatType) {
|
||||
t := fieldValue.Convert(bigFloatType).Interface().(big.Float)
|
||||
return t.String(), nil
|
||||
} else if fieldType.ConvertibleTo(schemas.IntervalType) {
|
||||
t := fieldValue.Convert(schemas.IntervalType).Interface().(time.Duration)
|
||||
return t, nil
|
||||
} else if fieldType.ConvertibleTo(schemas.NullBoolType) {
|
||||
t := fieldValue.Convert(schemas.NullBoolType).Interface().(sql.NullBool)
|
||||
if !t.Valid {
|
||||
return (*bool)(nil), nil
|
||||
}
|
||||
return t.Bool, nil
|
||||
} else if fieldType.ConvertibleTo(schemas.NullFloat64Type) {
|
||||
t := fieldValue.Convert(schemas.NullFloat64Type).Interface().(sql.NullFloat64)
|
||||
if !t.Valid {
|
||||
return (*float64)(nil), nil
|
||||
}
|
||||
return t.Float64, nil
|
||||
} else if fieldType.ConvertibleTo(schemas.NullInt16Type) {
|
||||
t := fieldValue.Convert(schemas.NullInt16Type).Interface().(sql.NullInt16)
|
||||
if !t.Valid {
|
||||
return (*int16)(nil), nil
|
||||
}
|
||||
return t.Int16, nil
|
||||
} else if fieldType.ConvertibleTo(schemas.NullInt32Type) {
|
||||
t := fieldValue.Convert(schemas.NullInt32Type).Interface().(sql.NullInt32)
|
||||
if !t.Valid {
|
||||
return (*int32)(nil), nil
|
||||
}
|
||||
return t.Int32, nil
|
||||
} else if fieldType.ConvertibleTo(schemas.NullInt64Type) {
|
||||
t := fieldValue.Convert(schemas.NullInt64Type).Interface().(sql.NullInt64)
|
||||
if !t.Valid {
|
||||
return (*int64)(nil), nil
|
||||
}
|
||||
return t.Int64, nil
|
||||
} else if fieldType.ConvertibleTo(schemas.NullStringType) {
|
||||
t := fieldValue.Convert(schemas.NullStringType).Interface().(sql.NullString)
|
||||
if !t.Valid {
|
||||
return (*string)(nil), nil
|
||||
}
|
||||
return t.String, nil
|
||||
} else if fieldType.ConvertibleTo(schemas.NullTimeType) {
|
||||
t := fieldValue.Convert(schemas.NullTimeType).Interface().(sql.NullTime)
|
||||
if !t.Valid {
|
||||
return (*time.Time)(nil), nil
|
||||
}
|
||||
return t.Time, nil
|
||||
}
|
||||
|
||||
if !col.IsJSON {
|
||||
|
|
@ -164,8 +209,37 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
|
|||
}
|
||||
return nil, ErrUnSupportedType
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return fieldValue.Uint(), nil
|
||||
val := fieldValue.Uint()
|
||||
switch t := fieldValue.Kind(); t {
|
||||
case reflect.Uint8:
|
||||
return uint8(val), nil
|
||||
case reflect.Uint16:
|
||||
return uint16(val), nil
|
||||
case reflect.Uint32:
|
||||
return uint32(val), nil
|
||||
case reflect.Uint64:
|
||||
return uint64(val), nil
|
||||
default:
|
||||
return val, nil
|
||||
}
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
val := fieldValue.Int()
|
||||
switch t := fieldValue.Kind(); t {
|
||||
case reflect.Int8:
|
||||
return int8(val), nil
|
||||
case reflect.Int16:
|
||||
return int16(val), nil
|
||||
case reflect.Int32:
|
||||
return int32(val), nil
|
||||
case reflect.Int64:
|
||||
return int64(val), nil
|
||||
default:
|
||||
return val, nil
|
||||
}
|
||||
default:
|
||||
if fieldValue.Interface() == nil && statement.dialect.URI().DBType == schemas.YDB {
|
||||
return (*string)(nil), nil
|
||||
}
|
||||
return fieldValue.Interface(), nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
// reference: https://aws.amazon.com/vi/blogs/architecture/exponential-backoff-and-jitter/
|
||||
package retry
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BackoffInterface interface {
|
||||
Wait(n int) <-chan time.Time
|
||||
|
||||
Delay(i int) time.Duration
|
||||
}
|
||||
|
||||
type Backoff struct {
|
||||
min time.Duration // default 5ms
|
||||
max time.Duration // default 5s
|
||||
jitter bool // default true
|
||||
}
|
||||
|
||||
func DefaultBackoff() *Backoff {
|
||||
return &Backoff{
|
||||
min: 5 * time.Millisecond,
|
||||
max: 5 * time.Second,
|
||||
jitter: true,
|
||||
}
|
||||
}
|
||||
|
||||
func NewBackoff(min, max time.Duration, jitter bool) *Backoff {
|
||||
return &Backoff{
|
||||
min: min,
|
||||
max: max,
|
||||
jitter: jitter,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Backoff) Wait(n int) <-chan time.Time {
|
||||
return time.After(b.Delay(n))
|
||||
}
|
||||
|
||||
// Decorrelated Jitter
|
||||
func (b *Backoff) Delay(i int) time.Duration {
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
base := int64(b.min)
|
||||
cap := int64(b.max)
|
||||
|
||||
if base >= cap {
|
||||
return time.Duration(cap)
|
||||
}
|
||||
|
||||
t := int(math.Log2(float64(cap)/float64(base))) + 1
|
||||
if i > t {
|
||||
i = t
|
||||
}
|
||||
|
||||
bf := base * int64(1<<i)
|
||||
if bf > cap {
|
||||
bf = cap
|
||||
}
|
||||
|
||||
if !b.jitter {
|
||||
return time.Duration(bf)
|
||||
}
|
||||
|
||||
w := (bf >> 1) + rand.Int63n((bf>>1)+1)
|
||||
w = base + rand.Int63n(w*3-base+1)
|
||||
if w > cap {
|
||||
w = cap
|
||||
}
|
||||
|
||||
return time.Duration(w)
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package retry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDefaultBackoff(t *testing.T) {
|
||||
bf := DefaultBackoff()
|
||||
for i := 0; i < 64; i++ {
|
||||
d := bf.Delay(i)
|
||||
n := time.Now()
|
||||
start := n.Add(bf.min)
|
||||
end := n.Add(bf.max)
|
||||
cur := n.Add(d)
|
||||
assert.WithinRange(t, cur, start, end)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackoff(t *testing.T) {
|
||||
for _, v := range []struct {
|
||||
min time.Duration
|
||||
max time.Duration
|
||||
jitter bool
|
||||
attempts int
|
||||
}{
|
||||
{
|
||||
min: 5 * time.Microsecond,
|
||||
max: 10 * time.Microsecond,
|
||||
jitter: true,
|
||||
attempts: 0,
|
||||
},
|
||||
{
|
||||
min: 10 * time.Millisecond,
|
||||
max: 20 * time.Millisecond,
|
||||
jitter: false,
|
||||
attempts: 1,
|
||||
},
|
||||
{
|
||||
min: 20 * time.Microsecond,
|
||||
max: 30 * time.Millisecond,
|
||||
jitter: false,
|
||||
attempts: 2,
|
||||
},
|
||||
{
|
||||
min: 30 * time.Second,
|
||||
max: 40 * time.Second,
|
||||
jitter: true,
|
||||
attempts: 70,
|
||||
},
|
||||
{
|
||||
min: 10 * time.Millisecond,
|
||||
max: 20 * time.Second,
|
||||
jitter: true,
|
||||
attempts: 10,
|
||||
},
|
||||
{
|
||||
min: 1 * time.Second,
|
||||
max: 2 * time.Second,
|
||||
jitter: false,
|
||||
attempts: 30,
|
||||
},
|
||||
} {
|
||||
bf := NewBackoff(v.min, v.max, v.jitter)
|
||||
d := bf.Delay(v.attempts)
|
||||
n := time.Now()
|
||||
start := n.Add(bf.min)
|
||||
end := n.Add(bf.max)
|
||||
cur := n.Add(d)
|
||||
assert.WithinRange(t, cur, start, end)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
// reference: https://github.com/ydb-platform/ydb-go-sdk/blob/master/retry/retry.go
|
||||
package retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type retryOptions struct {
|
||||
id string
|
||||
idempotent bool
|
||||
backoff BackoffInterface // default implement 'Decorrelated Jitter' algorithm
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNonRetryable = errors.New("retry error: non-retryable operation")
|
||||
ErrNonIdempotent = errors.New("retry error: non-idempotent operation")
|
||||
ErrMaxRetriesLimitExceed = errors.New("retry error: max retries limit exceeded")
|
||||
)
|
||||
|
||||
// !datbeohbbh! This function can be dialect.IsRetryable(err)
|
||||
// or your custom function that check if an error can be retried
|
||||
type checkRetryable func(error) bool
|
||||
|
||||
type retryOperation func(context.Context) error
|
||||
|
||||
type RetryOption func(*retryOptions)
|
||||
|
||||
type maxRetriesKey struct{}
|
||||
|
||||
func WithMaxRetries(maxRetriesValue int) RetryOption {
|
||||
return func(o *retryOptions) {
|
||||
o.ctx = context.WithValue(o.ctx, maxRetriesKey{}, maxRetriesValue)
|
||||
}
|
||||
}
|
||||
|
||||
func WithID(id string) RetryOption {
|
||||
return func(o *retryOptions) {
|
||||
o.id = id
|
||||
}
|
||||
}
|
||||
|
||||
func WithIdempotent(idempotent bool) RetryOption {
|
||||
return func(o *retryOptions) {
|
||||
o.idempotent = idempotent
|
||||
}
|
||||
}
|
||||
|
||||
func WithBackoff(backoff BackoffInterface) RetryOption {
|
||||
return func(o *retryOptions) {
|
||||
o.backoff = backoff
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *retryOptions) reachMaxRetries(attempts int) bool {
|
||||
if mx, has := opts.ctx.Value(maxRetriesKey{}).(int); !has {
|
||||
return false
|
||||
} else {
|
||||
return attempts > mx
|
||||
}
|
||||
}
|
||||
|
||||
// !datbeohbbh! Retry provide the best effort fo retrying operation
|
||||
//
|
||||
// Retry implements internal busy loop until one of the following conditions is met:
|
||||
// - context was canceled or deadlined
|
||||
// - retry operation returned nil as error
|
||||
//
|
||||
// Warning: if deadline without deadline or cancellation func Retry will be worked infinite
|
||||
func Retry(ctx context.Context, check checkRetryable, f retryOperation, opts ...RetryOption) error {
|
||||
options := &retryOptions{
|
||||
ctx: ctx,
|
||||
backoff: DefaultBackoff(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
if o != nil {
|
||||
o(options)
|
||||
}
|
||||
}
|
||||
|
||||
attempts := 0
|
||||
for !options.reachMaxRetries(attempts) {
|
||||
attempts++
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
err := f(ctx)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
canRetry := check(err)
|
||||
if !canRetry {
|
||||
return fmt.Errorf("Retry process with id '%s': %w",
|
||||
options.id, fmt.Errorf("%v: %w", err, ErrNonRetryable))
|
||||
}
|
||||
if !options.idempotent {
|
||||
return fmt.Errorf("Retry process with id '%s': %w",
|
||||
options.id, fmt.Errorf("%v: %w", err, ErrNonIdempotent))
|
||||
}
|
||||
if err = wait(ctx, options.backoff, attempts); err != nil {
|
||||
return fmt.Errorf("Retry process with id '%s': %w", options.id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Retry process with id '%s': %w",
|
||||
options.id,
|
||||
fmt.Errorf("%v: %w",
|
||||
fmt.Errorf("max retries: %v", options.ctx.Value(maxRetriesKey{})),
|
||||
ErrMaxRetriesLimitExceed,
|
||||
))
|
||||
}
|
||||
|
||||
func wait(ctx context.Context, backoff BackoffInterface, attempts int) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-backoff.Wait(attempts):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetRetryOptions(t *testing.T) {
|
||||
opts := []RetryOption{
|
||||
WithMaxRetries(10),
|
||||
WithID("ut-test-retry"),
|
||||
WithIdempotent(true),
|
||||
WithBackoff(DefaultBackoff()),
|
||||
}
|
||||
|
||||
rt := &retryOptions{
|
||||
ctx: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
if o != nil {
|
||||
o(rt)
|
||||
}
|
||||
}
|
||||
|
||||
val, ok := rt.ctx.Value(maxRetriesKey{}).(int)
|
||||
assert.True(t, ok)
|
||||
assert.EqualValues(t, 10, val)
|
||||
|
||||
assert.Equal(t, "ut-test-retry", rt.id)
|
||||
|
||||
assert.True(t, rt.idempotent)
|
||||
|
||||
assert.EqualValues(t, DefaultBackoff(), rt.backoff)
|
||||
}
|
||||
|
||||
func TestMaxRetries(t *testing.T) {
|
||||
const mxRetries int = 10
|
||||
|
||||
opts := []RetryOption{
|
||||
WithMaxRetries(mxRetries),
|
||||
}
|
||||
|
||||
rt := &retryOptions{
|
||||
ctx: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
if o != nil {
|
||||
o(rt)
|
||||
}
|
||||
}
|
||||
|
||||
val, ok := rt.ctx.Value(maxRetriesKey{}).(int)
|
||||
assert.True(t, ok)
|
||||
assert.EqualValues(t, mxRetries, val)
|
||||
|
||||
for i := 0; i < mxRetries; i++ {
|
||||
assert.False(t, rt.reachMaxRetries(i))
|
||||
}
|
||||
|
||||
assert.True(t, rt.reachMaxRetries(mxRetries+1))
|
||||
}
|
||||
|
||||
func TestRetryTimeOut(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := Retry(ctx, func(err error) bool {
|
||||
return true
|
||||
}, func(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(3 * time.Millisecond):
|
||||
return nil
|
||||
}
|
||||
}, WithIdempotent(true))
|
||||
|
||||
assert.True(t, errors.Is(err, context.DeadlineExceeded))
|
||||
}
|
||||
|
||||
func TestRetryMaxRetriesExceeded(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
utErr := errors.New("ut-error")
|
||||
wrapErr := fmt.Errorf("[error-ut-test]: %w", utErr)
|
||||
|
||||
err := Retry(ctx, func(err error) bool {
|
||||
return errors.Is(err, utErr)
|
||||
}, func(ctx context.Context) error {
|
||||
return wrapErr
|
||||
},
|
||||
WithMaxRetries(10),
|
||||
WithIdempotent(true),
|
||||
WithBackoff(NewBackoff(1*time.Millisecond, 2*time.Millisecond, true)))
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, ErrMaxRetriesLimitExceed))
|
||||
}
|
||||
|
||||
func TestRetryCanRetry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
utErr := errors.New("ut-error-can-not-retry")
|
||||
canRetryErr := errors.New("ut-error-retryable")
|
||||
wrapErr := fmt.Errorf("[error-ut-test]: %w", utErr)
|
||||
|
||||
err := Retry(ctx, func(err error) bool {
|
||||
return errors.Is(err, canRetryErr)
|
||||
}, func(ctx context.Context) error {
|
||||
return wrapErr
|
||||
},
|
||||
WithBackoff(NewBackoff(1*time.Millisecond, 2*time.Millisecond, true)))
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, ErrNonRetryable))
|
||||
}
|
||||
|
||||
func TestRetryIdempotent(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
utErr := errors.New("ut-error-non-idempotent")
|
||||
wrapErr := fmt.Errorf("[error-ut-test]: %w", utErr)
|
||||
|
||||
err := Retry(ctx, func(err error) bool {
|
||||
return errors.Is(err, utErr)
|
||||
}, func(ctx context.Context) error {
|
||||
return wrapErr
|
||||
},
|
||||
WithIdempotent(false),
|
||||
WithBackoff(NewBackoff(1*time.Millisecond, 2*time.Millisecond, true)))
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, ErrNonIdempotent))
|
||||
}
|
||||
|
||||
func TestRetry(t *testing.T) {
|
||||
const mxRetries int = 10
|
||||
ctx := context.Background()
|
||||
|
||||
utErr := errors.New("ut-retryable-error")
|
||||
wrapErr := fmt.Errorf("[error-ut-test]: %w", utErr)
|
||||
|
||||
var c int = 0
|
||||
|
||||
err := Retry(ctx, func(err error) bool {
|
||||
return errors.Is(err, utErr)
|
||||
}, func(ctx context.Context) error {
|
||||
defer func() {
|
||||
c += 1
|
||||
}()
|
||||
if c == mxRetries {
|
||||
return nil
|
||||
}
|
||||
return wrapErr
|
||||
},
|
||||
WithMaxRetries(mxRetries),
|
||||
WithIdempotent(true),
|
||||
WithBackoff(NewBackoff(1*time.Millisecond, 2*time.Millisecond, true)))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, c, mxRetries)
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ const (
|
|||
MSSQL DBType = "mssql"
|
||||
ORACLE DBType = "oracle"
|
||||
DAMENG DBType = "dameng"
|
||||
YDB DBType = "ydb"
|
||||
)
|
||||
|
||||
// SQLType represents SQL types
|
||||
|
|
@ -131,6 +132,7 @@ var (
|
|||
SmallDateTime = "SMALLDATETIME"
|
||||
Time = "TIME"
|
||||
TimeStamp = "TIMESTAMP"
|
||||
Interval = "INTERVAL"
|
||||
TimeStampz = "TIMESTAMPZ"
|
||||
Year = "YEAR"
|
||||
|
||||
|
|
@ -266,12 +268,15 @@ var (
|
|||
BytesType = reflect.SliceOf(ByteType)
|
||||
|
||||
TimeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
IntervalType = reflect.TypeOf((*time.Duration)(nil)).Elem()
|
||||
BigFloatType = reflect.TypeOf((*big.Float)(nil)).Elem()
|
||||
NullFloat64Type = reflect.TypeOf((*sql.NullFloat64)(nil)).Elem()
|
||||
NullStringType = reflect.TypeOf((*sql.NullString)(nil)).Elem()
|
||||
NullInt16Type = reflect.TypeOf((*sql.NullInt16)(nil)).Elem()
|
||||
NullInt32Type = reflect.TypeOf((*sql.NullInt32)(nil)).Elem()
|
||||
NullInt64Type = reflect.TypeOf((*sql.NullInt64)(nil)).Elem()
|
||||
NullBoolType = reflect.TypeOf((*sql.NullBool)(nil)).Elem()
|
||||
NullTimeType = reflect.TypeOf((*sql.NullTime)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Type2SQLType generate SQLType acorrding Go's type
|
||||
|
|
@ -314,6 +319,10 @@ func Type2SQLType(t reflect.Type) (st SQLType) {
|
|||
st = SQLType{BigInt, 0, 0}
|
||||
} else if t.ConvertibleTo(NullBoolType) {
|
||||
st = SQLType{Boolean, 0, 0}
|
||||
} else if t.ConvertibleTo(NullTimeType) {
|
||||
st = SQLType{TimeStamp, 0, 0}
|
||||
} else if t.ConvertibleTo(IntervalType) {
|
||||
st = SQLType{Interval, 0, 0}
|
||||
} else {
|
||||
// TODO need to handle association struct
|
||||
st = SQLType{Text, 0, 0}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,9 @@ func (session *Session) Tx() *core.Tx {
|
|||
}
|
||||
|
||||
func (session *Session) getQueryer() core.Queryer {
|
||||
if session.engine.dialect.URI().DBType == schemas.YDB {
|
||||
return session.db()
|
||||
}
|
||||
if session.tx != nil {
|
||||
return session.tx
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
|
|
@ -197,5 +198,9 @@ func (session *Session) delete(beans []interface{}, mustHaveConditions bool) (in
|
|||
cleanupProcessorsClosures(&session.afterClosures)
|
||||
// --
|
||||
|
||||
return res.RowsAffected()
|
||||
affected, err := res.RowsAffected()
|
||||
if errors.Is(err, driver.ErrSkip) {
|
||||
err = nil
|
||||
}
|
||||
return affected, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
|
@ -57,6 +58,11 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) {
|
|||
cnt, err = session.insertStruct(bean)
|
||||
}
|
||||
}
|
||||
// !datbeohbbh! YDB does not support (sql.Result).LastInsertId() and (sql.Result) RowsAffected().
|
||||
// YDB returns `0, driver.ErrSkip` instead.
|
||||
if errors.Is(err, driver.ErrSkip) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return affected, err
|
||||
}
|
||||
|
|
@ -307,7 +313,7 @@ func (session *Session) insertStruct(bean interface{}) (int64, error) {
|
|||
}
|
||||
|
||||
// if there is auto increment column and driver don't support return it
|
||||
if len(table.AutoIncrement) > 0 && !session.engine.driver.Features().SupportReturnInsertedID {
|
||||
if len(table.AutoIncrement) > 0 && !session.engine.driver.Features().SupportReturnInsertedID && session.engine.dialect.URI().DBType != schemas.YDB {
|
||||
var sql string
|
||||
var newArgs []interface{}
|
||||
var needCommit bool
|
||||
|
|
@ -430,7 +436,11 @@ func (session *Session) InsertOne(bean interface{}) (int64, error) {
|
|||
defer session.Close()
|
||||
}
|
||||
|
||||
return session.insertStruct(bean)
|
||||
affected, err := session.insertStruct(bean)
|
||||
if errors.Is(err, driver.ErrSkip) {
|
||||
err = nil
|
||||
}
|
||||
return affected, err
|
||||
}
|
||||
|
||||
func (session *Session) cacheInsert(table string) error {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"xorm.io/xorm/core"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) {
|
||||
|
|
@ -16,6 +17,12 @@ func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{})
|
|||
*sqlStr = filter.Do(session.ctx, *sqlStr)
|
||||
}
|
||||
|
||||
if session.engine.dialect.URI().DBType == schemas.YDB {
|
||||
if preCast, ok := session.engine.driver.(interface{ Cast(...interface{}) }); ok {
|
||||
preCast.Cast(paramStr...)
|
||||
}
|
||||
}
|
||||
|
||||
session.lastSQL = *sqlStr
|
||||
session.lastSQLArgs = paramStr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
package xorm
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
|
@ -237,7 +239,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
|
|||
cleanupProcessorsClosures(&session.afterClosures) // cleanup after used
|
||||
// --
|
||||
|
||||
return res.RowsAffected()
|
||||
affected, err := res.RowsAffected()
|
||||
if errors.Is(err, driver.ErrSkip) {
|
||||
err = nil
|
||||
}
|
||||
return affected, err
|
||||
}
|
||||
|
||||
func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interface{}, error) {
|
||||
|
|
|
|||
10
sync.go
10
sync.go
|
|
@ -109,6 +109,11 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// !datbeohbbh! skip with YDB. All indexes are created when create table.
|
||||
if engine.dialect.URI().DBType == schemas.YDB {
|
||||
continue
|
||||
}
|
||||
|
||||
if !opts.IgnoreConstrains {
|
||||
err = session.createUniques(bean)
|
||||
if err != nil {
|
||||
|
|
@ -155,6 +160,11 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{})
|
|||
expectedType := engine.dialect.SQLType(col)
|
||||
curType := engine.dialect.SQLType(oriCol)
|
||||
if expectedType != curType {
|
||||
if engine.dialect.URI().DBType == schemas.YDB {
|
||||
engine.logger.Warnf("YDB does not support modify column type")
|
||||
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s",
|
||||
tbNameWithSchema, col.Name, curType, expectedType)
|
||||
}
|
||||
if expectedType == schemas.Text &&
|
||||
strings.HasPrefix(curType, schemas.Varchar) {
|
||||
// currently only support mysql & postgres
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func TestDump(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NoError(t, sess.Commit())
|
||||
|
||||
for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL} {
|
||||
for _, tp := range []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL, schemas.YDB} {
|
||||
name := fmt.Sprintf("dump_%v.sql", tp)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.NoError(t, testEngine.DumpAllToFile(name, tp))
|
||||
|
|
@ -136,7 +136,7 @@ func TestDump(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var dbtypes = []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL}
|
||||
var dbtypes = []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL, schemas.YDB}
|
||||
|
||||
func TestDumpTables(t *testing.T) {
|
||||
assert.NoError(t, PrepareEngine())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
// !datbeohbbh! this test is copied from original xorm tests.
|
||||
package ydb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm/caches"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCacheFind(t *testing.T) {
|
||||
type MailBox struct {
|
||||
Id int64 `xorm:"pk"`
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldCacher := engine.GetDefaultCacher()
|
||||
cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
|
||||
assert.NoError(t, PrepareScheme(new(MailBox)))
|
||||
defer func() {
|
||||
assert.NoError(t, engine.DropTables(new(MailBox)))
|
||||
}()
|
||||
|
||||
var inserts = []*MailBox{
|
||||
{
|
||||
Id: 0,
|
||||
Username: "user1",
|
||||
Password: "pass1",
|
||||
},
|
||||
{
|
||||
Id: 1,
|
||||
Username: "user2",
|
||||
Password: "pass2",
|
||||
},
|
||||
}
|
||||
_, err = engine.Insert(inserts[0], inserts[1])
|
||||
assert.NoError(t, err)
|
||||
|
||||
var boxes []MailBox
|
||||
assert.NoError(t, engine.Find(&boxes))
|
||||
assert.EqualValues(t, 2, len(boxes))
|
||||
for i, box := range boxes {
|
||||
assert.Equal(t, inserts[i].Id, box.Id)
|
||||
assert.Equal(t, inserts[i].Username, box.Username)
|
||||
assert.Equal(t, inserts[i].Password, box.Password)
|
||||
}
|
||||
|
||||
boxes = make([]MailBox, 0, 2)
|
||||
assert.NoError(t, engine.Find(&boxes))
|
||||
assert.EqualValues(t, 2, len(boxes))
|
||||
for i, box := range boxes {
|
||||
assert.Equal(t, inserts[i].Id, box.Id)
|
||||
assert.Equal(t, inserts[i].Username, box.Username)
|
||||
assert.Equal(t, inserts[i].Password, box.Password)
|
||||
}
|
||||
|
||||
boxes = make([]MailBox, 0, 2)
|
||||
assert.NoError(t, engine.Alias("a").Where("`a`.`id`> -1").
|
||||
Asc("`a`.`id`").Find(&boxes))
|
||||
assert.EqualValues(t, 2, len(boxes))
|
||||
for i, box := range boxes {
|
||||
assert.Equal(t, inserts[i].Id, box.Id)
|
||||
assert.Equal(t, inserts[i].Username, box.Username)
|
||||
assert.Equal(t, inserts[i].Password, box.Password)
|
||||
}
|
||||
|
||||
type MailBox4 struct {
|
||||
Id int64
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
boxes2 := make([]MailBox4, 0, 2)
|
||||
assert.NoError(t, engine.Table("mail_box").Where("`mail_box`.`id` > -1").
|
||||
Asc("mail_box.id").Find(&boxes2))
|
||||
assert.EqualValues(t, 2, len(boxes2))
|
||||
for i, box := range boxes2 {
|
||||
assert.Equal(t, inserts[i].Id, box.Id)
|
||||
assert.Equal(t, inserts[i].Username, box.Username)
|
||||
assert.Equal(t, inserts[i].Password, box.Password)
|
||||
}
|
||||
|
||||
engine.SetDefaultCacher(oldCacher)
|
||||
}
|
||||
|
||||
func TestCacheFind2(t *testing.T) {
|
||||
type MailBox2 struct {
|
||||
Id uint64 `xorm:"pk"`
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldCacher := engine.GetDefaultCacher()
|
||||
cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
|
||||
assert.NoError(t, PrepareScheme(new(MailBox2)))
|
||||
defer func() {
|
||||
assert.NoError(t, engine.DropTables(new(MailBox2)))
|
||||
}()
|
||||
|
||||
var inserts = []*MailBox2{
|
||||
{
|
||||
Id: 0,
|
||||
Username: "user1",
|
||||
Password: "pass1",
|
||||
},
|
||||
{
|
||||
Id: 1,
|
||||
Username: "user2",
|
||||
Password: "pass2",
|
||||
},
|
||||
}
|
||||
_, err = engine.Insert(inserts[0], inserts[1])
|
||||
assert.NoError(t, err)
|
||||
|
||||
var boxes []MailBox2
|
||||
assert.NoError(t, engine.Find(&boxes))
|
||||
assert.EqualValues(t, 2, len(boxes))
|
||||
for i, box := range boxes {
|
||||
assert.Equal(t, inserts[i].Id, box.Id)
|
||||
assert.Equal(t, inserts[i].Username, box.Username)
|
||||
assert.Equal(t, inserts[i].Password, box.Password)
|
||||
}
|
||||
|
||||
boxes = make([]MailBox2, 0, 2)
|
||||
assert.NoError(t, engine.Find(&boxes))
|
||||
assert.EqualValues(t, 2, len(boxes))
|
||||
for i, box := range boxes {
|
||||
assert.Equal(t, inserts[i].Id, box.Id)
|
||||
assert.Equal(t, inserts[i].Username, box.Username)
|
||||
assert.Equal(t, inserts[i].Password, box.Password)
|
||||
}
|
||||
|
||||
engine.SetDefaultCacher(oldCacher)
|
||||
}
|
||||
|
||||
func TestCacheGet(t *testing.T) {
|
||||
type MailBox3 struct {
|
||||
Id uint64 `xorm:"pk"`
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldCacher := engine.GetDefaultCacher()
|
||||
cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)
|
||||
engine.SetDefaultCacher(cacher)
|
||||
|
||||
assert.NoError(t, PrepareScheme(new(MailBox3)))
|
||||
defer func() {
|
||||
assert.NoError(t, engine.DropTables(new(MailBox3)))
|
||||
}()
|
||||
|
||||
var inserts = []*MailBox3{
|
||||
{
|
||||
Id: 0,
|
||||
Username: "user1",
|
||||
Password: "pass1",
|
||||
},
|
||||
}
|
||||
_, err = engine.Insert(inserts[0])
|
||||
assert.NoError(t, err)
|
||||
|
||||
var box1 MailBox3
|
||||
has, err := engine.Where("`id` = ?", inserts[0].Id).Get(&box1)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, "user1", box1.Username)
|
||||
assert.EqualValues(t, "pass1", box1.Password)
|
||||
|
||||
var box2 MailBox3
|
||||
has, err = engine.Where("`id` = ?", inserts[0].Id).Get(&box2)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, "user1", box2.Username)
|
||||
assert.EqualValues(t, "pass1", box2.Password)
|
||||
|
||||
engine.SetDefaultCacher(oldCacher)
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/retry"
|
||||
|
||||
_ "github.com/ydb-platform/ydb-go-sdk/v3"
|
||||
)
|
||||
|
||||
type e2e struct {
|
||||
ctx context.Context
|
||||
engines *EngineWithMode
|
||||
}
|
||||
|
||||
func TestE2E(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
scope := &e2e{
|
||||
ctx: ctx,
|
||||
engines: NewEngineWithMode(ctx, connString),
|
||||
}
|
||||
|
||||
t.Run("create-engine", func(t *testing.T) {
|
||||
engine, err := scope.engines.GetDefaultEngine()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = engine.PingContext(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = engine.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("e2e", func(t *testing.T) {
|
||||
t.Run("prepare-stage", func(t *testing.T) {
|
||||
t.Run("scheme", func(t *testing.T) {
|
||||
err := scope.prepareScheme()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("fill-stage", func(t *testing.T) {
|
||||
err := scope.fill()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("query-stage", func(t *testing.T) {
|
||||
t.Run("explain", func(t *testing.T) {
|
||||
engine, err := scope.engines.GetExplainQueryEngine()
|
||||
require.NoError(t, err)
|
||||
|
||||
var (
|
||||
ast string
|
||||
plan string
|
||||
)
|
||||
|
||||
r, err := engine.
|
||||
Table(&Episodes{}).
|
||||
Cols("views").
|
||||
Where("series_id = ?", uuid.New()).
|
||||
And("season_id = ?", uuid.New()).
|
||||
And("episode_id = ?", uuid.New()).
|
||||
Rows(&struct {
|
||||
Ast string
|
||||
Plan string
|
||||
}{})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, r.Next())
|
||||
|
||||
err = r.Scan(&ast, &plan)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("ast = %v\n", ast)
|
||||
t.Logf("plan = %v\n", plan)
|
||||
})
|
||||
|
||||
t.Run("increment", func(t *testing.T) {
|
||||
t.Run("views", func(t *testing.T) {
|
||||
engine, err := scope.engines.GetDataQueryEngine()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = engine.DoTx(scope.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
var epData Episodes
|
||||
_, err = session.Get(&epData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session.
|
||||
Table(Episodes{}).
|
||||
Cols("views").
|
||||
Where("series_id = ?", epData.SeriesID).
|
||||
And("season_id = ?", epData.SeasonID).
|
||||
And("episode_id = ?", epData.EpisodeID).
|
||||
Prepare()
|
||||
|
||||
rows, err := session.Rows(&Episodes{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
var ep Episodes
|
||||
if err = rows.Scan(&ep); err != nil {
|
||||
return fmt.Errorf("cannot scan views: %w", err)
|
||||
}
|
||||
t.Logf("got views: %+v\n", ep.Views)
|
||||
|
||||
// increase views by 1
|
||||
_, err = session.
|
||||
Table(Episodes{}).
|
||||
Where("series_id = ?", epData.SeriesID).
|
||||
And("season_id = ?", epData.SeasonID).
|
||||
And("episode_id = ?", epData.EpisodeID).
|
||||
Incr("views", uint64(1)).
|
||||
Update(&Episodes{})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot increase views by 1 %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}, retry.WithID("e2e-test-query-increment"),
|
||||
retry.WithIdempotent(true))
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("select", func(t *testing.T) {
|
||||
engine, err := scope.engines.GetScanQueryEngine()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = engine.DoTx(scope.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
has, err := session.
|
||||
Table(Episodes{}).
|
||||
Where("views = ?", uint64(1)).
|
||||
Exist()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return fmt.Errorf("expected record exists")
|
||||
}
|
||||
|
||||
rows, err := session.
|
||||
Table(Episodes{}).
|
||||
Cols("title", "air_date", "views").
|
||||
Where("views = ?", uint64(1)).
|
||||
Rows(&Episodes{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
title string
|
||||
air_date time.Time
|
||||
views uint64
|
||||
)
|
||||
if err := rows.Scan(&title, &air_date, &views); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Logf("> %v %v %v\n", title, views, air_date.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}, retry.WithID("e2e-test-query-select"),
|
||||
retry.WithIdempotent(true))
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("close-engines", func(t *testing.T) {
|
||||
err := scope.engines.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (scope *e2e) fill() error {
|
||||
engine, err := scope.engines.GetDataQueryEngine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
series, seasons, episodes := getData()
|
||||
err = engine.DoTx(scope.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
_, err = session.Insert(series, seasons, episodes)
|
||||
return err
|
||||
},
|
||||
retry.WithID("e2e-test-fill-stage"),
|
||||
retry.WithIdempotent(true))
|
||||
return err
|
||||
}
|
||||
|
||||
func (scope *e2e) prepareScheme() error {
|
||||
engine, err := scope.engines.GetSchemeQueryEngine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = engine.DropTables(&Series{}, &Seasons{}, &Episodes{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = engine.CreateTables(&Series{}, &Seasons{}, &Episodes{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/retry"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, engine.Ping())
|
||||
}
|
||||
|
||||
func TestPingContext(t *testing.T) {
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancelFunc()
|
||||
|
||||
time.Sleep(time.Nanosecond)
|
||||
|
||||
err = engine.PingContext(ctx)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, context.DeadlineExceeded))
|
||||
}
|
||||
|
||||
var dbtypes = []schemas.DBType{schemas.SQLITE, schemas.MYSQL, schemas.POSTGRES, schemas.MSSQL, schemas.ORACLE, schemas.YDB}
|
||||
|
||||
func TestDump(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, tp := range dbtypes {
|
||||
name := fmt.Sprintf("%s/dump_%v-all.sql", t.TempDir(), tp)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.NoError(t, engine.DumpAllToFile(name, tp))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpTables(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tb, err := engine.TableInfo(new(Users))
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, tp := range dbtypes {
|
||||
name := fmt.Sprintf("%s/dump_%v-table.sql", t.TempDir(), tp)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.NoError(t, engine.DumpTablesToFile([]*schemas.Table{tb}, name, tp))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportFromDumpFile(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
assert.NoError(t, PrepareScheme(&Series{}))
|
||||
assert.NoError(t, PrepareScheme(&Episodes{}))
|
||||
assert.NoError(t, PrepareScheme(&Seasons{}))
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
t.Run("dump-and-import", func(t *testing.T) {
|
||||
name := fmt.Sprintf("%s/dump_%v-all-tables.yql", t.TempDir(), schemas.YDB)
|
||||
assert.NoError(t, engine.DumpAllToFile(name, schemas.YDB))
|
||||
|
||||
err = engine.DropTables(&Users{}, &Series{}, &Seasons{}, &Episodes{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.ImportFile(name)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("insert-data", func(t *testing.T) {
|
||||
users := getUsersData()
|
||||
seriesData, seasonsData, episodesData := getData()
|
||||
|
||||
_, err = engine.Insert(&seriesData, &seasonsData, &episodesData, &users)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestImportDDL(t *testing.T) {
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.ImportFile("testdata/DDL.yql")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestImportDML(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
defer session.Rollback()
|
||||
|
||||
assert.NoError(t, session.Begin())
|
||||
|
||||
_, err = session.ImportFile("testdata/DML.yql")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, session.Commit())
|
||||
}
|
||||
|
||||
func TestDBVersion(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
version, err := engine.DBVersion()
|
||||
assert.NoError(t, err)
|
||||
t.Log(version.Edition + " " + version.Number)
|
||||
}
|
||||
|
||||
func TestRetry(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
err = engine.Do(enginePool.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
return session.DropTable(&Users{})
|
||||
}, retry.WithID("retry-test-drop-table"),
|
||||
retry.WithIdempotent(true))
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = engine.Do(enginePool.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
return session.CreateTable(&Users{})
|
||||
}, retry.WithID("retry-test-create-table"),
|
||||
retry.WithIdempotent(true))
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := getUsersData()
|
||||
err = engine.Do(enginePool.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
_, err = session.Insert(users)
|
||||
return err
|
||||
}, retry.WithID("retry-test-insert"),
|
||||
retry.WithIdempotent(true))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRetryTx(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Seasons{}, &Series{}, &Episodes{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
series, seasons, episodes := getData()
|
||||
err = engine.DoTx(enginePool.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
_, err = session.Insert(&series)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = session.Insert(&seasons)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = session.Insert(&episodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, retry.WithID("retry-test-insert-tx"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var seriesCnt, seasonsCnt, episodesCnt int64 = 0, 0, 0
|
||||
err = engine.DoTx(enginePool.ctx, func(ctx context.Context, session *xorm.Session) (err error) {
|
||||
seriesCnt, err = engine.Table(&Series{}).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
seasonsCnt, err = engine.Table(&Seasons{}).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
episodesCnt, err = engine.Table(&Episodes{}).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, retry.WithIdempotent(true))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, seasonsCnt, len(seasons))
|
||||
assert.EqualValues(t, seriesCnt, len(series))
|
||||
assert.EqualValues(t, episodesCnt, len(episodes))
|
||||
}
|
||||
|
||||
func TestCreateTableWithTableParameters(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
type SeriesTableWithParams struct {
|
||||
Hash uint64 `xorm:"pk"`
|
||||
*Series `xorm:"extends"`
|
||||
}
|
||||
|
||||
tableParams := map[string]string{
|
||||
"AUTO_PARTITIONING_BY_SIZE": "ENABLED",
|
||||
"AUTO_PARTITIONING_BY_LOAD": "ENABLED",
|
||||
"AUTO_PARTITIONING_PARTITION_SIZE_MB": "1",
|
||||
"AUTO_PARTITIONING_MIN_PARTITIONS_COUNT": "6",
|
||||
"AUTO_PARTITIONING_MAX_PARTITIONS_COUNT": "1000",
|
||||
"UNIFORM_PARTITIONS": "6",
|
||||
}
|
||||
|
||||
engine.Dialect().SetParams(tableParams)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
defer session.Engine().Dialect().SetParams(nil)
|
||||
|
||||
err = session.DropTable(&SeriesTableWithParams{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = session.CreateTable(&SeriesTableWithParams{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("check-YQL-script", func(t *testing.T) {
|
||||
createTableYQL, _ := session.LastSQL()
|
||||
for params, value := range tableParams {
|
||||
pattern := params + `\s*=\s*` + value
|
||||
assert.Regexp(t, pattern, createTableYQL)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateTableWithNilTableParameters(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
type SeriesTableWithParams struct {
|
||||
Hash uint64 `xorm:"pk"`
|
||||
*Series `xorm:"extends"`
|
||||
}
|
||||
|
||||
tableParams := make(map[string]string)
|
||||
engine.Dialect().SetParams(tableParams)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err = session.DropTable(&SeriesTableWithParams{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = session.CreateTable(&SeriesTableWithParams{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("check-YQL-script", func(t *testing.T) {
|
||||
createTableYQL, _ := session.LastSQL()
|
||||
pattern := "WITH" + `\s*\(\s*.*\s*\)\s*`
|
||||
assert.NotRegexp(t, pattern, createTableYQL)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xormLog "xorm.io/xorm/log"
|
||||
|
||||
_ "github.com/ydb-platform/ydb-go-sdk/v3"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type QueryMode int
|
||||
|
||||
const (
|
||||
DataQueryMode = QueryMode(iota)
|
||||
ExplainQueryMode
|
||||
ScanQueryMode
|
||||
SchemeQueryMode
|
||||
ScriptingQueryMode
|
||||
|
||||
DefaultQueryMode = DataQueryMode
|
||||
)
|
||||
|
||||
func (mode QueryMode) String() string {
|
||||
switch mode {
|
||||
case DataQueryMode:
|
||||
return "data"
|
||||
case ScanQueryMode:
|
||||
return "scan"
|
||||
case ExplainQueryMode:
|
||||
return "explain"
|
||||
case SchemeQueryMode:
|
||||
return "scheme"
|
||||
case ScriptingQueryMode:
|
||||
return "scripting"
|
||||
default:
|
||||
return "data"
|
||||
}
|
||||
}
|
||||
|
||||
type EngineWithMode struct {
|
||||
engineCached map[QueryMode]*xorm.Engine
|
||||
dsn string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewEngineWithMode(ctx context.Context, dsn string) *EngineWithMode {
|
||||
return &EngineWithMode{
|
||||
ctx: ctx,
|
||||
dsn: dsn,
|
||||
engineCached: make(map[QueryMode]*xorm.Engine),
|
||||
}
|
||||
}
|
||||
|
||||
func createEngine(dsn string) (*xorm.Engine, error) {
|
||||
log.Printf("> connect: %s\n", dsn)
|
||||
return xorm.NewEngine(*db, dsn)
|
||||
}
|
||||
|
||||
func constructDSN(dsn string, query ...string) string {
|
||||
info, err := url.Parse(dsn)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse dsn: %s", dsn))
|
||||
}
|
||||
|
||||
if info.RawQuery != "" {
|
||||
dsn = strings.Join(append([]string{dsn}, query...), "&")
|
||||
} else {
|
||||
q := strings.Join(query, "&")
|
||||
dsn = dsn + "?" + q
|
||||
}
|
||||
|
||||
return dsn
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) getEngine(queryMode QueryMode) (*xorm.Engine, error) {
|
||||
if e, has := em.engineCached[queryMode]; has {
|
||||
if e.PingContext(em.ctx) == nil {
|
||||
return em.engineCached[queryMode], nil
|
||||
}
|
||||
}
|
||||
|
||||
dsn := constructDSN(em.dsn, fmt.Sprintf("go_query_mode=%s", queryMode))
|
||||
engine, err := createEngine(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
engine.ShowSQL(*showSQL)
|
||||
engine.SetLogLevel(xormLog.LOG_DEBUG)
|
||||
|
||||
appLoc, _ := time.LoadLocation("Asia/Ho_Chi_Minh")
|
||||
DbLoc, _ := time.LoadLocation("Europe/Moscow")
|
||||
engine.SetTZLocation(appLoc)
|
||||
engine.SetTZDatabase(DbLoc)
|
||||
|
||||
engine.SetDefaultContext(em.ctx)
|
||||
|
||||
engine.SetMaxOpenConns(50)
|
||||
engine.SetMaxIdleConns(50)
|
||||
engine.SetConnMaxIdleTime(time.Second)
|
||||
engine.EnableSessionID(true)
|
||||
|
||||
em.engineCached[queryMode] = engine
|
||||
return em.engineCached[queryMode], nil
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) Close() error {
|
||||
for mode, engine := range em.engineCached {
|
||||
if err := engine.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("> close engine: %s\n", mode)
|
||||
delete(em.engineCached, mode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) GetDefaultEngine() (*xorm.Engine, error) {
|
||||
return em.getEngine(DefaultQueryMode)
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) GetDataQueryEngine() (*xorm.Engine, error) {
|
||||
return em.getEngine(DataQueryMode)
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) GetScanQueryEngine() (*xorm.Engine, error) {
|
||||
return em.getEngine(ScanQueryMode)
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) GetExplainQueryEngine() (*xorm.Engine, error) {
|
||||
return em.getEngine(ExplainQueryMode)
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) GetSchemeQueryEngine() (*xorm.Engine, error) {
|
||||
return em.getEngine(SchemeQueryMode)
|
||||
}
|
||||
|
||||
func (em *EngineWithMode) GetScriptQueryEngine() (*xorm.Engine, error) {
|
||||
return em.getEngine(ScriptingQueryMode)
|
||||
}
|
||||
|
||||
func PrepareScheme(bean ...interface{}) error {
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := engine.DropTables(bean...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := engine.CreateTables(bean...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanUp() error {
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tables, err := engine.Dialect().GetTables(engine.DB(), enginePool.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
beans := make([]interface{}, 0)
|
||||
for _, table := range tables {
|
||||
beans = append(beans, table.Name)
|
||||
}
|
||||
|
||||
err = engine.DropTables(beans...)
|
||||
return err
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
var (
|
||||
enginePool *EngineWithMode
|
||||
connString string
|
||||
)
|
||||
|
||||
var (
|
||||
db = flag.String("db", "sqlite3", "the tested database")
|
||||
showSQL = flag.Bool("show_sql", true, "show generated SQLs")
|
||||
ptrConnStr = flag.String("conn_str", "./test.db?cache=shared&mode=rwc", "test database connection string")
|
||||
cacheFlag = flag.Bool("cache", false, "if enable cache")
|
||||
quotePolicyStr = flag.String("quote", "always", "quote could be always, none, reversed")
|
||||
)
|
||||
|
||||
func MainTest(m *testing.M) int {
|
||||
flag.Parse()
|
||||
|
||||
if db == nil || *db != string(schemas.YDB) {
|
||||
log.Println("this tests only apply for ydb")
|
||||
return -1
|
||||
}
|
||||
|
||||
if ptrConnStr == nil {
|
||||
log.Println("you should indicate conn string")
|
||||
return -1
|
||||
}
|
||||
connString = *ptrConnStr
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
enginePool = NewEngineWithMode(ctx, connString)
|
||||
defer func() {
|
||||
_ = enginePool.Close()
|
||||
}()
|
||||
|
||||
code := m.Run()
|
||||
defer func(code int) {
|
||||
log.Println("> Clean up")
|
||||
_ = CleanUp()
|
||||
}(code)
|
||||
|
||||
return code
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// !datbeohbbh! This is CI test for YDB
|
||||
// YDB has some features that are different from other RDBMS.
|
||||
// Such as, no auto increment primary key, various query mode, transaction query structure.
|
||||
// So I decided to write its own tests.
|
||||
// Note: Some tests in the original tests are copied.
|
||||
package ydb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
_ "github.com/ydb-platform/ydb-go-sdk/v3"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(MainTest(m))
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Series struct {
|
||||
SeriesID []byte `xorm:"pk 'series_id'"`
|
||||
Title string `xorm:"'title' index(index_series_title)"`
|
||||
SeriesInfo string `xorm:"'series_info'"`
|
||||
ReleaseDate time.Time `xorm:"'release_date'"`
|
||||
Comment string `xorm:"'comment'"`
|
||||
}
|
||||
|
||||
type Seasons struct {
|
||||
SeriesID []byte `xorm:"pk 'series_id'"`
|
||||
SeasonID []byte `xorm:"pk 'season_id'"`
|
||||
Title string `xorm:"'title' index(index_series_title)"`
|
||||
FirstAired time.Time `xorm:"'first_aired' index(index_season_first_aired)"`
|
||||
LastAired time.Time `xorm:"'last_aired'"`
|
||||
}
|
||||
|
||||
type Episodes struct {
|
||||
SeriesID []byte `xorm:"pk 'series_id'"`
|
||||
SeasonID []byte `xorm:"pk 'season_id'"`
|
||||
EpisodeID []byte `xorm:"pk 'episode_id'"`
|
||||
Title string `xorm:"'title'"`
|
||||
AirDate time.Time `xorm:"'air_date' index(index_episodes_air_date)"`
|
||||
Views uint64 `xorm:"'views'"`
|
||||
}
|
||||
|
||||
type TestEpisodes struct {
|
||||
Episodes `xorm:"extends"`
|
||||
}
|
||||
|
||||
type Users struct {
|
||||
Name string `xorm:"'name' INDEX"`
|
||||
Age uint32 `xorm:"'age' INDEX"`
|
||||
Account `xorm:"extends"`
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
UserID sql.NullInt64 `xorm:"pk 'user_id'"`
|
||||
Number string `xorm:"pk 'number'"`
|
||||
Created time.Time `xorm:"created 'created_at'"`
|
||||
Updated time.Time `xorm:"updated 'updated_at'"`
|
||||
}
|
||||
|
||||
// table name method
|
||||
func (*Series) TableName() string {
|
||||
return "series"
|
||||
}
|
||||
|
||||
func (*Seasons) TableName() string {
|
||||
return "seasons"
|
||||
}
|
||||
|
||||
func (*Episodes) TableName() string {
|
||||
return "episodes"
|
||||
}
|
||||
|
||||
func (*TestEpisodes) TableName() string {
|
||||
return "test/episodes"
|
||||
}
|
||||
|
||||
func (*Users) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func getUsersData() (users []*Users) {
|
||||
for i := 0; i < 20; i++ {
|
||||
users = append(users, &Users{
|
||||
Name: fmt.Sprintf("Dat - %d", i),
|
||||
Age: uint32(22 + i),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: int64(i), Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func seriesData(id string, released time.Time, title, info, comment string) *Series {
|
||||
return &Series{
|
||||
SeriesID: []byte(id),
|
||||
Title: title,
|
||||
SeriesInfo: info,
|
||||
ReleaseDate: released,
|
||||
Comment: comment,
|
||||
}
|
||||
}
|
||||
|
||||
func seasonData(seriesID, seasonID string, title string, first, last time.Time) *Seasons {
|
||||
return &Seasons{
|
||||
SeriesID: []byte(seriesID),
|
||||
SeasonID: []byte(seasonID),
|
||||
Title: title,
|
||||
FirstAired: first,
|
||||
LastAired: last,
|
||||
}
|
||||
}
|
||||
|
||||
func episodeData(seriesID, seasonID, episodeID string, title string, date time.Time) *Episodes {
|
||||
return &Episodes{
|
||||
SeriesID: []byte(seriesID),
|
||||
SeasonID: []byte(seasonID),
|
||||
EpisodeID: []byte(episodeID),
|
||||
Title: title,
|
||||
AirDate: date,
|
||||
}
|
||||
}
|
||||
|
||||
func getData() (series []*Series, seasons []*Seasons, episodes []*Episodes) {
|
||||
for seriesID, fill := range map[string]func(seriesID string) (seriesData *Series, seasons []*Seasons, episodes []*Episodes){
|
||||
uuid.New().String(): getDataForITCrowd,
|
||||
uuid.New().String(): getDataForSiliconValley,
|
||||
} {
|
||||
seriesData, seasonsData, episodesData := fill(seriesID)
|
||||
series = append(series, seriesData)
|
||||
seasons = append(seasons, seasonsData...)
|
||||
episodes = append(episodes, episodesData...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getDataForITCrowd(seriesID string) (series *Series, seasons []*Seasons, episodes []*Episodes) {
|
||||
series = seriesData(
|
||||
seriesID, date("2006-02-03"), "IT Crowd", ""+
|
||||
"The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by "+
|
||||
"Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry.",
|
||||
"", // NULL comment.
|
||||
)
|
||||
for _, season := range []struct {
|
||||
title string
|
||||
first time.Time
|
||||
last time.Time
|
||||
episodes map[string]time.Time
|
||||
}{
|
||||
{"Season 1", date("2006-02-03"), date("2006-03-03"), map[string]time.Time{
|
||||
"Yesterday's Jam": date("2006-02-03"),
|
||||
"Calamity Jen": date("2006-02-03"),
|
||||
"Fifty-Fifty": date("2006-02-10"),
|
||||
"The Red Door": date("2006-02-17"),
|
||||
"The Haunting of Bill Crouse": date("2006-02-24"),
|
||||
"Aunt Irma Visits": date("2006-03-03"),
|
||||
}},
|
||||
{"Season 2", date("2007-08-24"), date("2007-09-28"), map[string]time.Time{
|
||||
"The Work Outing": date("2006-08-24"),
|
||||
"Return of the Golden Child": date("2007-08-31"),
|
||||
"Moss and the German": date("2007-09-07"),
|
||||
"The Dinner Party": date("2007-09-14"),
|
||||
"Smoke and Mirrors": date("2007-09-21"),
|
||||
"Men Without Women": date("2007-09-28"),
|
||||
}},
|
||||
{"Season 3", date("2008-11-21"), date("2008-12-26"), map[string]time.Time{
|
||||
"From Hell": date("2008-11-21"),
|
||||
"Are We Not Men?": date("2008-11-28"),
|
||||
"Tramps Like Us": date("2008-12-05"),
|
||||
"The Speech": date("2008-12-12"),
|
||||
"Friendface": date("2008-12-19"),
|
||||
"Calendar Geeks": date("2008-12-26"),
|
||||
}},
|
||||
{"Season 4", date("2010-06-25"), date("2010-07-30"), map[string]time.Time{
|
||||
"Jen The Fredo": date("2010-06-25"),
|
||||
"The Final Countdown": date("2010-07-02"),
|
||||
"Something Happened": date("2010-07-09"),
|
||||
"Italian For Beginners": date("2010-07-16"),
|
||||
"Bad Boys": date("2010-07-23"),
|
||||
"Reynholm vs Reynholm": date("2010-07-30"),
|
||||
}},
|
||||
} {
|
||||
seasonID := uuid.New().String()
|
||||
seasons = append(seasons, seasonData(seriesID, seasonID, season.title, season.first, season.last))
|
||||
for title, date := range season.episodes {
|
||||
episodes = append(episodes, episodeData(seriesID, seasonID, uuid.New().String(), title, date))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getDataForSiliconValley(seriesID string) (series *Series, seasons []*Seasons, episodes []*Episodes) {
|
||||
series = seriesData(
|
||||
seriesID, date("2014-04-06"), "Silicon Valley", ""+
|
||||
"Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and "+
|
||||
"Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley.",
|
||||
"Some comment here",
|
||||
)
|
||||
for _, season := range []struct {
|
||||
title string
|
||||
first time.Time
|
||||
last time.Time
|
||||
episodes map[string]time.Time
|
||||
}{
|
||||
{"Season 1", date("2014-04-06"), date("2014-06-01"), map[string]time.Time{
|
||||
"Minimum Viable Product": date("2014-04-06"),
|
||||
"The Cap Table": date("2014-04-13"),
|
||||
"Articles of Incorporation": date("2014-04-20"),
|
||||
"Fiduciary Duties": date("2014-04-27"),
|
||||
"Signaling Risk": date("2014-05-04"),
|
||||
"Third Party Insourcing": date("2014-05-11"),
|
||||
"Proof of Concept": date("2014-05-18"),
|
||||
"Optimal Tip-to-Tip Efficiency": date("2014-06-01"),
|
||||
}},
|
||||
{"Season 2", date("2015-04-12"), date("2015-06-14"), map[string]time.Time{
|
||||
"Sand Hill Shuffle": date("2015-04-12"),
|
||||
"Runaway Devaluation": date("2015-04-19"),
|
||||
"Bad Money": date("2015-04-26"),
|
||||
"The Lady": date("2015-05-03"),
|
||||
"Server Space": date("2015-05-10"),
|
||||
"Homicide": date("2015-05-17"),
|
||||
"Adult Content": date("2015-05-24"),
|
||||
"White Hat/Black Hat": date("2015-05-31"),
|
||||
"Binding Arbitration": date("2015-06-07"),
|
||||
"Two Days of the Condor": date("2015-06-14"),
|
||||
}},
|
||||
{"Season 3", date("2016-04-24"), date("2016-06-26"), map[string]time.Time{
|
||||
"Founder Friendly": date("2016-04-24"),
|
||||
"Two in the Box": date("2016-05-01"),
|
||||
"Meinertzhagen's Haversack": date("2016-05-08"),
|
||||
"Maleant Data Systems Solutions": date("2016-05-15"),
|
||||
"The Empty Chair": date("2016-05-22"),
|
||||
"Bachmanity Insanity": date("2016-05-29"),
|
||||
"To Build a Better Beta": date("2016-06-05"),
|
||||
"Bachman's Earnings Over-Ride": date("2016-06-12"),
|
||||
"Daily Active Users": date("2016-06-19"),
|
||||
"The Uptick": date("2016-06-26"),
|
||||
}},
|
||||
{"Season 4", date("2017-04-23"), date("2017-06-25"), map[string]time.Time{
|
||||
"Success Failure": date("2017-04-23"),
|
||||
"Terms of Service": date("2017-04-30"),
|
||||
"Intellectual Property": date("2017-05-07"),
|
||||
"Teambuilding Exercise": date("2017-05-14"),
|
||||
"The Blood Boy": date("2017-05-21"),
|
||||
"Customer Service": date("2017-05-28"),
|
||||
"The Patent Troll": date("2017-06-04"),
|
||||
"The Keenan Vortex": date("2017-06-11"),
|
||||
"Hooli-Con": date("2017-06-18"),
|
||||
"Server Error": date("2017-06-25"),
|
||||
}},
|
||||
{"Season 5", date("2018-03-25"), date("2018-05-13"), map[string]time.Time{
|
||||
"Grow Fast or Die Slow": date("2018-03-25"),
|
||||
"Reorientation": date("2018-04-01"),
|
||||
"Chief Operating Officer": date("2018-04-08"),
|
||||
"Tech Evangelist": date("2018-04-15"),
|
||||
"Facial Recognition": date("2018-04-22"),
|
||||
"Artificial Emotional Intelligence": date("2018-04-29"),
|
||||
"Initial Coin Offering": date("2018-05-06"),
|
||||
"Fifty-One Percent": date("2018-05-13"),
|
||||
}},
|
||||
} {
|
||||
seasonID := uuid.New().String()
|
||||
seasons = append(seasons, seasonData(seriesID, seasonID, season.title, season.first, season.last))
|
||||
for title, date := range season.episodes {
|
||||
episodes = append(episodes, episodeData(seriesID, seasonID, uuid.New().String(), title, date))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const dateISO8601 = "2006-01-02"
|
||||
|
||||
func date(date string) time.Time {
|
||||
t, err := time.Parse(dateISO8601, date)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func TestSetExpr(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
SetExpr("age", uint32(100)).
|
||||
ID(schemas.PK{
|
||||
sql.NullInt64{Int64: 0, Valid: true},
|
||||
usersData[0].Number,
|
||||
}).
|
||||
Update(&Users{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCols(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
Cols("name").
|
||||
Cols("age").
|
||||
ID(schemas.PK{
|
||||
user.UserID,
|
||||
user.Number,
|
||||
}).
|
||||
Update(&Users{
|
||||
Name: "",
|
||||
Age: uint32(0),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret := Users{}
|
||||
_, err = engine.
|
||||
ID(schemas.PK{
|
||||
user.UserID,
|
||||
user.Number,
|
||||
}).Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "", ret.Name)
|
||||
assert.EqualValues(t, 0, ret.Age)
|
||||
}
|
||||
|
||||
func TestMustCols(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
type OnlyUuid struct {
|
||||
UserId sql.NullInt64
|
||||
}
|
||||
|
||||
updData := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
}
|
||||
_, err = engine.
|
||||
MustCols("age").
|
||||
Update(&updData, &OnlyUuid{UserId: sql.NullInt64{Int64: 1, Valid: true}})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestBuilder(t *testing.T) {
|
||||
const (
|
||||
OpEqual int32 = iota
|
||||
OpGreatThan
|
||||
OpLessThan
|
||||
)
|
||||
|
||||
type Condition struct {
|
||||
CondId int64 `xorm:"pk"`
|
||||
TableName string
|
||||
ColName string
|
||||
Op int32
|
||||
Value string
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Condition{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
_, err = engine.Insert(&Condition{CondId: int64(1), TableName: "table1", ColName: "col1", Op: OpEqual, Value: "1"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var cond Condition
|
||||
var q = engine.Quote
|
||||
has, err := engine.Where(builder.Eq{q("col_name"): "col1"}).Get(&cond)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, has, "records should exist")
|
||||
|
||||
has, err = engine.Where(builder.Eq{q("col_name"): "col1"}.
|
||||
And(builder.Eq{q("op"): OpEqual})).
|
||||
NoAutoCondition().
|
||||
Get(&cond)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, has, "records should exist")
|
||||
|
||||
has, err = engine.Where(builder.Eq{q("col_name"): "col1", q("op"): OpEqual, q("value"): "1"}).
|
||||
NoAutoCondition().
|
||||
Get(&cond)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, has, "records should exist")
|
||||
|
||||
has, err = engine.Where(builder.Eq{q("col_name"): "col1"}.
|
||||
And(builder.Neq{q("op"): OpEqual})).
|
||||
NoAutoCondition().
|
||||
Get(&cond)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, has, "records should not exist")
|
||||
|
||||
var conds []Condition
|
||||
err = engine.Where(builder.Eq{q("col_name"): "col1"}.
|
||||
And(builder.Eq{q("op"): OpEqual})).
|
||||
Find(&conds)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(conds), "records should exist")
|
||||
|
||||
conds = make([]Condition, 0)
|
||||
err = engine.Where(builder.Like{q("col_name"), "col"}).Find(&conds)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(conds), "records should exist")
|
||||
|
||||
conds = make([]Condition, 0)
|
||||
err = engine.Where(builder.Expr(q("col_name")+" = ?", "col1")).Find(&conds)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(conds), "records should exist")
|
||||
|
||||
conds = make([]Condition, 0)
|
||||
err = engine.Where(builder.In(q("col_name"), "col1", "col2")).Find(&conds)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(conds), "records should exist")
|
||||
|
||||
conds = make([]Condition, 0)
|
||||
err = engine.NotIn("col_name", "col1", "col2").Find(&conds)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, len(conds), "records should not exist")
|
||||
|
||||
// complex condtions
|
||||
var where = builder.NewCond()
|
||||
if true {
|
||||
where = where.And(builder.Eq{q("col_name"): "col1"})
|
||||
where = where.Or(builder.And(builder.In(q("col_name"), "col1", "col2"), builder.Expr(q("col_name")+" = ?", "col1")))
|
||||
}
|
||||
|
||||
conds = make([]Condition, 0)
|
||||
err = engine.Where(where).Find(&conds)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(conds), "records should exist")
|
||||
}
|
||||
|
||||
func TestIn(t *testing.T) {
|
||||
type Userinfo struct {
|
||||
Uid int64 `xorm:"pk"`
|
||||
Username string
|
||||
Departname string
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Userinfo{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
_, err = engine.Insert([]Userinfo{
|
||||
{
|
||||
Uid: int64(1),
|
||||
Username: "user1",
|
||||
Departname: "dev",
|
||||
},
|
||||
{
|
||||
Uid: int64(2),
|
||||
Username: "user2",
|
||||
Departname: "dev",
|
||||
},
|
||||
{
|
||||
Uid: int64(3),
|
||||
Username: "user3",
|
||||
Departname: "dev",
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
department := "`" + engine.GetColumnMapper().Obj2Table("Departname") + "`"
|
||||
var usrs []Userinfo
|
||||
err = engine.Where(department+" = ?", "dev").Limit(3).Find(&usrs)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, len(usrs))
|
||||
|
||||
var ids []int64
|
||||
var idsStr string
|
||||
for _, u := range usrs {
|
||||
ids = append(ids, u.Uid)
|
||||
idsStr = fmt.Sprintf("%d,", u.Uid)
|
||||
}
|
||||
idsStr = idsStr[:len(idsStr)-1]
|
||||
|
||||
users := make([]Userinfo, 0)
|
||||
err = engine.In("uid", ids[0], ids[1], ids[2]).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, len(users))
|
||||
|
||||
users = make([]Userinfo, 0)
|
||||
err = engine.In("uid", ids).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, len(users))
|
||||
|
||||
for _, user := range users {
|
||||
if user.Uid != ids[0] && user.Uid != ids[1] && user.Uid != ids[2] {
|
||||
err = errors.New("in uses should be " + idsStr + " total 3")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
users = make([]Userinfo, 0)
|
||||
var idsInterface []interface{}
|
||||
for _, id := range ids {
|
||||
idsInterface = append(idsInterface, id)
|
||||
}
|
||||
|
||||
err = engine.Where(department+" = ?", "dev").In("uid", idsInterface...).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, len(users))
|
||||
|
||||
for _, user := range users {
|
||||
if user.Uid != ids[0] && user.Uid != ids[1] && user.Uid != ids[2] {
|
||||
err = errors.New("in uses should be " + idsStr + " total 3")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
dev := engine.GetColumnMapper().Obj2Table("Dev")
|
||||
|
||||
err = engine.In("uid", int64(1)).In("uid", int64(2)).In(department, dev).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.In("uid", ids[0]).Update(&Userinfo{Departname: "dev-"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
user := new(Userinfo)
|
||||
has, err := engine.ID(ids[0]).Get(user)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, "dev-", user.Departname)
|
||||
|
||||
_, err = engine.In("uid", ids[0]).Update(&Userinfo{Departname: "dev"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.In("uid", ids[1]).Delete(&Userinfo{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIn2(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ids := []sql.NullInt64{}
|
||||
for i := 10; i < 20; i++ {
|
||||
ids = append(ids, sql.NullInt64{Int64: int64(i), Valid: true})
|
||||
}
|
||||
|
||||
users := []Users{}
|
||||
cond := builder.In("user_id", ids)
|
||||
err = engine.Where(cond).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(ids), len(users))
|
||||
|
||||
for i, user := range users {
|
||||
assert.Equal(t, sql.NullInt64{Int64: int64(i + 10), Valid: true}, user.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestView(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
conds := builder.
|
||||
Select("*").
|
||||
From((&Users{}).TableName() + " VIEW age").
|
||||
Where(builder.Gt{"age": uint32(21)}).
|
||||
Where(builder.Lt{"user_id": sql.NullInt64{Int64: 5, Valid: true}}).
|
||||
OrderBy("user_id ASC")
|
||||
|
||||
users := []Users{}
|
||||
err = engine.
|
||||
SQL(conds).
|
||||
Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 5, len(users))
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
assert.EqualValues(t, usersData[i].UserID, users[i].UserID)
|
||||
assert.EqualValues(t, usersData[i].Age, users[i].Age)
|
||||
assert.EqualValues(t, usersData[i].Number, users[i].Number)
|
||||
assert.EqualValues(t, usersData[i].Name, users[i].Name)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestCount(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cond := builder.Lt{"user_id": sql.NullInt64{Int64: 5, Valid: true}}
|
||||
|
||||
cnt, err := engine.
|
||||
Where(cond).
|
||||
Count(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 5, cnt)
|
||||
|
||||
cnt, err = engine.Where(cond).Table("users").Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 5, cnt)
|
||||
|
||||
cnt, err = engine.Table("users").Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), cnt)
|
||||
}
|
||||
|
||||
func TestSQLCount(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sql := "SELECT COUNT(`user_id`) FROM `users`"
|
||||
cnt, err := engine.SQL(sql).Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), cnt)
|
||||
}
|
||||
|
||||
func TestCountWithTableName(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := engine.Count(new(Users))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), cnt)
|
||||
|
||||
cnt, err = engine.Count(Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), cnt)
|
||||
}
|
||||
|
||||
func TestCountWithSelectCols(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := engine.Cols("user_id").Count(new(Users))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), cnt)
|
||||
|
||||
cnt, err = engine.Select("COUNT(`user_id`)").Count(Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), cnt)
|
||||
}
|
||||
|
||||
func TestCountWithGroupBy(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
for w, g := len(usersData)/4, 0; g < 4; g++ {
|
||||
for i := 0; i < w; i++ {
|
||||
usersData[w*g+i].Age = uint32(22 + g)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := engine.Cols("age").GroupBy("age").Count(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, cnt)
|
||||
|
||||
cnt, err = engine.Select("COUNT(`age`)").GroupBy("age").Count(Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, cnt)
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := getUsersData()
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
_, err = session.Insert(users)
|
||||
return nil, err
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
_, err = session.Delete(&Users{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
},
|
||||
})
|
||||
return nil, err
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
user := Users{}
|
||||
has, err := engine.Where("user_id = ?", sql.NullInt64{Int64: 1, Valid: true}).Get(&user)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
_, err = session.Table((&Users{}).TableName()).Where(builder.Between{
|
||||
Col: "user_id",
|
||||
LessVal: sql.NullInt64{Int64: 10, Valid: true},
|
||||
MoreVal: sql.NullInt64{Int64: 15, Valid: true},
|
||||
}).Delete()
|
||||
return nil, err
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := engine.Table(&Users{}).Where(builder.Between{
|
||||
Col: "user_id",
|
||||
LessVal: sql.NullInt64{Int64: 10, Valid: true},
|
||||
MoreVal: sql.NullInt64{Int64: 15, Valid: true},
|
||||
}).Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, cnt)
|
||||
|
||||
has, err = engine.Get(&Users{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 0, Valid: true},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
// FIXME: failed on get `deleted` field, unknown problem that return argument
|
||||
// of time.Time type as string type.
|
||||
/* type UsersDeleted struct {
|
||||
Name string `xorm:"'name'"`
|
||||
Age uint32 `xorm:"'age'"`
|
||||
Deleted time.Time `xorm:"deleted"`
|
||||
Account `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (*UsersDeleted) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// !datbeohbbh! not supported
|
||||
func TestDeleted(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, engine.Sync(&UsersDeleted{}))
|
||||
|
||||
user := UsersDeleted{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret := UsersDeleted{}
|
||||
has, err := session.Get(&UsersDeleted{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
_, err = session.Delete(&UsersDeleted{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret = UsersDeleted{}
|
||||
has, err = session.Get(&UsersDeleted{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
_, err = session.Delete(&UsersDeleted{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1, Valid: true},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret = UsersDeleted{}
|
||||
has, err = session.
|
||||
Unscoped().
|
||||
Where("user_id = ?", sql.NullInt64{Int64: 1, Valid: true}).
|
||||
Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
_, err = session.
|
||||
Table("users").
|
||||
Unscoped().
|
||||
Where("user_id = ?", sql.NullInt64{Int64: 1, Valid: true}).
|
||||
Delete()
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret = UsersDeleted{}
|
||||
has, err = session.
|
||||
Unscoped().
|
||||
Where("user_id = ?", sql.NullInt64{Int64: 1, Valid: true}).
|
||||
Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
}
|
||||
*/
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func TestExistStruct(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 22, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err = session.DropTable(new(Users))
|
||||
assert.NoError(t, err)
|
||||
|
||||
has, err := session.Exist(new(Users))
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "Cannot find table")
|
||||
assert.False(t, has)
|
||||
|
||||
err = session.CreateTable(&Users{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Insert(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
has, err = session.Exist(new(Users))
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = session.Exist(&Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = session.Exist(&Users{
|
||||
Name: "datbeohbbh-non-exist",
|
||||
Age: uint32(22),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
has, err = session.Where("name = ?", "datbeohbbh").Exist(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = session.Where("name = ?", "datbeohbbh-test").Exist(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
has, err = session.
|
||||
SQL("SELECT * FROM users WHERE name = ? AND age = ?", "datbeohbbh", uint32(22)).
|
||||
Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = session.
|
||||
SQL("SELECT * FROM users WHERE name = ? AND age = ?", "datbeohbbh-test", uint32(22)).
|
||||
Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
has, err = session.Table("users").Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = session.Table("users").Where("name = ?", "datbeohbbh").Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
has, err = session.Table("users").Where("name = ?", "datbeohbbh-test").Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
has, err = session.Table(new(Users)).
|
||||
ID(
|
||||
schemas.PK{
|
||||
sql.NullInt64{Int64: 22, Valid: true},
|
||||
user.Number,
|
||||
},
|
||||
).
|
||||
Cols("number").
|
||||
Exist(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
func TestExistStructForJoin(t *testing.T) {
|
||||
type Number struct {
|
||||
Uuid []byte `xorm:"pk"`
|
||||
Lid []byte
|
||||
}
|
||||
|
||||
type OrderList struct {
|
||||
Uuid []byte `xorm:"pk"`
|
||||
Eid []byte
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
Uuid []byte `xorm:"pk"`
|
||||
Name string
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Number{}, &OrderList{}, &Player{}))
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
defer func() { // clean up
|
||||
session := engine.NewSession()
|
||||
assert.NoError(t, session.DropTable(&Number{}))
|
||||
assert.NoError(t, session.DropTable(&OrderList{}))
|
||||
assert.NoError(t, session.DropTable(&Player{}))
|
||||
session.Close()
|
||||
}()
|
||||
|
||||
ply := Player{
|
||||
Uuid: []byte(uuid.NewString()),
|
||||
Name: "datbeohbbh",
|
||||
}
|
||||
_, err = engine.Insert(&ply)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var orderlist = OrderList{
|
||||
Uuid: []byte(uuid.NewString()),
|
||||
Eid: ply.Uuid,
|
||||
}
|
||||
_, err = engine.Insert(&orderlist)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var um = Number{
|
||||
Uuid: []byte(uuid.NewString()),
|
||||
Lid: orderlist.Uuid,
|
||||
}
|
||||
_, err = engine.Insert(&um)
|
||||
assert.NoError(t, err)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
session.Table("number").
|
||||
Join("INNER", "order_list", "`order_list`.`uuid` = `number`.`lid`").
|
||||
Join("LEFT", "player", "`player`.`uuid` = `order_list`.`eid`").
|
||||
Where("`number`.`lid` = ?", um.Lid)
|
||||
has, err := session.Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
session.Table("number").
|
||||
Join("INNER", "order_list", "`order_list`.`uuid` = `number`.`lid`").
|
||||
Join("LEFT", "player", "`player`.`uuid` = `order_list`.`eid`").
|
||||
Where("`number`.`lid` = ?", []byte("fake data"))
|
||||
has, err = session.Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
session.Table("number").
|
||||
Select("`order_list`.`uuid`").
|
||||
Join("INNER", "order_list", "`order_list`.`uuid` = `number`.`lid`").
|
||||
Join("LEFT", "player", "`player`.`uuid` = `order_list`.`eid`").
|
||||
Where("`order_list`.`uuid` = ?", orderlist.Uuid)
|
||||
has, err = session.Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
session.Table("number").
|
||||
Select("player.uuid").
|
||||
Join("INNER", "order_list", "`order_list`.`uuid` = `number`.`lid`").
|
||||
Join("LEFT", "player", "`player`.`uuid` = `order_list`.`eid`").
|
||||
Where("`player`.`uuid` = ?", []byte("fake data"))
|
||||
has, err = session.Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
session.Table("number").
|
||||
Select("player.uuid").
|
||||
Join("INNER", "order_list", "`order_list`.`uuid` = `number`.`lid`").
|
||||
Join("LEFT", "player", "`player`.`uuid` = `order_list`.`eid`")
|
||||
has, err = session.Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
err = session.DropTable("order_list")
|
||||
assert.NoError(t, err)
|
||||
|
||||
exist, err := session.IsTableExist("order_list")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
|
||||
session.Table("number").
|
||||
Select("player.uuid").
|
||||
Join("INNER", "order_list", "`order_list`.`uuid` = `number`.`lid`").
|
||||
Join("LEFT", "player", "`player`.`uuid` = `order_list`.`eid`")
|
||||
has, err = session.Exist()
|
||||
assert.Error(t, err)
|
||||
assert.False(t, has)
|
||||
|
||||
session.Table("number").
|
||||
Select("player.uuid").
|
||||
Join("LEFT", "player", "`player`.`uuid` = `number`.`lid`")
|
||||
has, err = session.Exist()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
func TestExistContext(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 22, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancel()
|
||||
|
||||
time.Sleep(time.Nanosecond)
|
||||
|
||||
has, err := engine.Context(ctx).Exist(&user)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "context deadline exceeded")
|
||||
assert.False(t, has)
|
||||
}
|
||||
|
|
@ -0,0 +1,656 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestFindJoinLimit(t *testing.T) {
|
||||
type Salary struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Lid int64
|
||||
}
|
||||
|
||||
type CheckList struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Eid int64
|
||||
}
|
||||
|
||||
type Empsetting struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Name string
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&Salary{}, &CheckList{}, &Empsetting{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
emp := Empsetting{
|
||||
Uuid: int64(1),
|
||||
Name: "datbeohbbh",
|
||||
}
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&emp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
checklist := CheckList{Uuid: int64(1), Eid: emp.Uuid}
|
||||
_, err = session.Insert(&checklist)
|
||||
assert.NoError(t, err)
|
||||
|
||||
salary := Salary{Uuid: int64(1), Lid: checklist.Uuid}
|
||||
_, err = session.Insert(&salary)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var salaries []Salary
|
||||
err = engine.Table("salary").
|
||||
Cols("`salary`.`uuid`", "`salary`.`lid`").
|
||||
Join("INNER", "check_list", "`check_list`.`uuid` = `salary`.`lid`").
|
||||
Join("LEFT", "empsetting", "`empsetting`.`uuid` = `check_list`.`eid`").
|
||||
Limit(10, 0).
|
||||
Find(&salaries)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFindCond(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := make([]Users, 0)
|
||||
|
||||
err = engine.Where("user_id > ?", sql.NullInt64{Int64: 5, Valid: true}).Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData)-6, len(users))
|
||||
|
||||
users = []Users{}
|
||||
err = engine.
|
||||
Where(builder.Between{
|
||||
Col: "user_id",
|
||||
LessVal: sql.NullInt64{Int64: 5, Valid: true},
|
||||
MoreVal: sql.NullInt64{Int64: 15, Valid: true},
|
||||
}).
|
||||
Where(builder.Gt{"age": int32(30)}).
|
||||
Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 7, len(users))
|
||||
}
|
||||
|
||||
func TestFindMap(t *testing.T) {
|
||||
type Salary struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Lid int64
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Salary{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
salaryData := []Salary{}
|
||||
for uuid := 0; uuid <= 20; uuid++ {
|
||||
salaryData = append(salaryData, Salary{
|
||||
Uuid: int64(uuid),
|
||||
})
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&salaryData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
salaries := make(map[int64]Salary)
|
||||
|
||||
err = engine.Find(&salaries)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(salaryData), len(salaries))
|
||||
|
||||
salariesPtr := map[int64]*Salary{}
|
||||
err = engine.Cols("lid").Find(&salariesPtr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(salaryData), len(salariesPtr))
|
||||
}
|
||||
|
||||
func TestFindDistinct(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
for w, g := len(usersData)/4, 0; g < 4; g++ {
|
||||
for i := 0; i < w; i++ {
|
||||
usersData[w*g+i].Age = uint32(22 + g)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := []Users{}
|
||||
err = engine.Distinct("age").Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 4, len(users))
|
||||
}
|
||||
|
||||
func TestFindOrder(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := make([]Users, 0)
|
||||
err = engine.OrderBy("`user_id` desc").Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(users))
|
||||
|
||||
for i := len(usersData) - 1; i >= 0; i-- {
|
||||
assert.Equal(t, usersData[i].UserID, users[len(users)-i-1].UserID)
|
||||
}
|
||||
|
||||
users = []Users{}
|
||||
err = engine.Asc("user_id").Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(users))
|
||||
for i := 0; i < len(usersData); i++ {
|
||||
assert.Equal(t, usersData[i].UserID, users[i].UserID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindGroupBy(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := make([]Users, 0)
|
||||
err = engine.GroupBy("`age`, `user_id`, `number`").Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(users))
|
||||
}
|
||||
|
||||
func TestFindHaving(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := make([]Users, 0)
|
||||
err = engine.
|
||||
GroupBy("`age`, `user_id`, `number`").
|
||||
Having("`user_id` = 0").
|
||||
Find(&users)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFindInt(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
age := []uint32{}
|
||||
err = engine.Table(&Users{}).Cols("age").Asc("age").Find(&age)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(age))
|
||||
|
||||
userIds := []int64{}
|
||||
err = engine.Table(&Users{}).Cols("user_id").Desc("user_id").Find(&userIds)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(userIds))
|
||||
}
|
||||
|
||||
func TestFindString(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedName := []string{}
|
||||
for _, user := range usersData {
|
||||
expectedName = append(expectedName, user.Name)
|
||||
}
|
||||
|
||||
expectedNumber := []string{}
|
||||
for _, user := range usersData {
|
||||
expectedNumber = append(expectedNumber, user.Number)
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
err = engine.Table(&Users{}).Cols("name").Asc("name").Find(&names)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(names))
|
||||
assert.ElementsMatch(t, expectedName, names)
|
||||
|
||||
numbers := []string{}
|
||||
err = engine.Table(&Users{}).Cols("number").Find(&numbers)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(numbers))
|
||||
assert.ElementsMatch(t, expectedNumber, numbers)
|
||||
}
|
||||
|
||||
func TestFindCustomType(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
type cstring string
|
||||
|
||||
expectedName := []cstring{}
|
||||
for _, user := range usersData {
|
||||
expectedName = append(expectedName, cstring(user.Name))
|
||||
}
|
||||
|
||||
names := []cstring{}
|
||||
err = engine.Table(&Users{}).Cols("name").Asc("name").Find(&names)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(names))
|
||||
assert.ElementsMatch(t, expectedName, names)
|
||||
}
|
||||
|
||||
func TestFindInterface(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
age := []interface{}{}
|
||||
err = engine.Table(&Users{}).Cols("age").Asc("age").Find(&age)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(usersData), len(age))
|
||||
|
||||
for i := 0; i < len(usersData); i++ {
|
||||
assert.Equal(t, usersData[i].Age, age[i].(uint32))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSliceBytes(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Series{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
seriesData, _, _ := getData()
|
||||
_, err = engine.Insert(&seriesData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedSeriesId := []string{}
|
||||
for _, series := range seriesData {
|
||||
expectedSeriesId = append(expectedSeriesId, string(series.SeriesID))
|
||||
}
|
||||
|
||||
seriesIds := make([]string, 0)
|
||||
err = engine.Table(&Series{}).Cols("series_id").Find(&seriesIds)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, expectedSeriesId, seriesIds)
|
||||
}
|
||||
|
||||
func TestFindBool(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
type FindBoolStruct struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg bool
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&FindBoolStruct{}))
|
||||
|
||||
_, err = engine.Insert([]FindBoolStruct{
|
||||
{
|
||||
Uuid: int64(1),
|
||||
Msg: false,
|
||||
},
|
||||
{
|
||||
Uuid: int64(2),
|
||||
Msg: true,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
results := make([]FindBoolStruct, 0)
|
||||
err = engine.Asc("uuid").Find(&results)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, len(results))
|
||||
|
||||
assert.False(t, results[0].Msg)
|
||||
assert.True(t, results[1].Msg)
|
||||
}
|
||||
|
||||
func TestFindAndCount(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := make([]Users, 0)
|
||||
cnt, err := engine.FindAndCount(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), cnt)
|
||||
|
||||
users = []Users{}
|
||||
_, err = engine.Limit(10, 0).FindAndCount(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, len(users))
|
||||
}
|
||||
|
||||
func TestFindAndCountDistinct(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
for w, g := len(usersData)/4, 0; g < 4; g++ {
|
||||
for i := 0; i < w; i++ {
|
||||
usersData[w*g+i].Age = uint32(22 + g)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := make([]Users, 0)
|
||||
cnt, err := engine.Distinct("age").FindAndCount(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, cnt)
|
||||
assert.EqualValues(t, 4, len(users))
|
||||
}
|
||||
|
||||
func TestFindAndCountGroupBy(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
for w, g := len(usersData)/4, 0; g < 4; g++ {
|
||||
for i := 0; i < w; i++ {
|
||||
usersData[w*g+i].Age = uint32(22 + g)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := make([]Users, 0)
|
||||
cnt, err := engine.GroupBy("age").FindAndCount(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, cnt)
|
||||
assert.EqualValues(t, 4, len(users))
|
||||
}
|
||||
|
||||
func TestFindTime(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
usersData := getUsersData()
|
||||
_, err = engine.Insert(&usersData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
createdAt := make([]string, 0)
|
||||
err = engine.Table(&Users{}).Cols("created_at").Find(&createdAt)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(usersData), len(createdAt))
|
||||
}
|
||||
|
||||
func TestFindStringArray(t *testing.T) {
|
||||
type TestString struct {
|
||||
Id string `xorm:"pk VARCHAR"`
|
||||
Data *[]string `xorm:"TEXT"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&TestString{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Insert(&TestString{
|
||||
Id: uuid.NewString(),
|
||||
Data: &[]string{"a", "b", "c"},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestString
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
assert.EqualValues(t, []string{"a", "b", "c"}, *(ret.Data))
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = engine.Insert(&TestString{
|
||||
Id: uuid.NewString(),
|
||||
Data: &[]string{"a", "b", "c"},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
var arr []TestString
|
||||
err = engine.Asc("id").Find(&arr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, v := range arr {
|
||||
res := *(v.Data)
|
||||
assert.EqualValues(t, []string{"a", "b", "c"}, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCustomTypeAllField(t *testing.T) {
|
||||
type RowID = uint64
|
||||
type Str = *string
|
||||
type Double = *float64
|
||||
type Timestamp = *time.Time
|
||||
|
||||
type Row struct {
|
||||
ID RowID `xorm:"pk 'id'"`
|
||||
PayloadStr Str `xorm:"'payload_str'"`
|
||||
PayloadDouble Double `xorm:"'payload_double'"`
|
||||
PayloadTimestamp Timestamp `xorm:"'payload_timestamp'"`
|
||||
}
|
||||
|
||||
rows := make([]Row, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
rows = append(rows, Row{
|
||||
ID: RowID(i),
|
||||
PayloadStr: func(s string) *string { return &s }(fmt.Sprintf("payload#%d", i)),
|
||||
PayloadDouble: func(f float64) *float64 { return &f }((float64)(i)),
|
||||
PayloadTimestamp: func(t time.Time) *time.Time { return &t }(time.Now()),
|
||||
})
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Row{}))
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&rows)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.Count(&Row{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, cnt)
|
||||
|
||||
res := make([]Row, 0)
|
||||
err = session.Asc("id").Find(&res)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(rows), len(res))
|
||||
|
||||
for i, v := range rows {
|
||||
assert.EqualValues(t, v.ID, res[i].ID)
|
||||
assert.EqualValues(t, v.PayloadStr, res[i].PayloadStr)
|
||||
assert.EqualValues(t, v.PayloadDouble, res[i].PayloadDouble)
|
||||
assert.EqualValues(t, v.PayloadTimestamp.Unix(), res[i].PayloadTimestamp.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSqlNullable(t *testing.T) {
|
||||
type SqlNullable struct {
|
||||
ID sql.NullInt64 `xorm:"pk 'id'"`
|
||||
Bool *sql.NullBool `xorm:"'bool'"`
|
||||
Int32 *sql.NullInt32 `xorm:"'int32'"`
|
||||
String sql.NullString `xorm:"'string'"`
|
||||
Time *sql.NullTime `xorm:"'time'"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&SqlNullable{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldTzLoc := engine.GetTZLocation()
|
||||
oldDbLoc := engine.GetTZDatabase()
|
||||
|
||||
defer func() {
|
||||
engine.SetTZLocation(oldTzLoc)
|
||||
engine.SetTZDatabase(oldDbLoc)
|
||||
}()
|
||||
|
||||
engine.SetTZLocation(time.UTC)
|
||||
engine.SetTZDatabase(time.UTC)
|
||||
|
||||
data := make([]*SqlNullable, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
data = append(data, &SqlNullable{
|
||||
ID: sql.NullInt64{Int64: int64(i), Valid: true},
|
||||
Bool: &sql.NullBool{},
|
||||
Int32: &sql.NullInt32{Int32: int32(i), Valid: true},
|
||||
String: sql.NullString{String: fmt.Sprintf("data#%d", i), Valid: true},
|
||||
Time: &sql.NullTime{Time: time.Now().In(time.UTC), Valid: true},
|
||||
})
|
||||
}
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res := make([]*SqlNullable, 0)
|
||||
err = session.Table(&SqlNullable{}).OrderBy("id").Find(&res)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i, v := range data {
|
||||
assert.EqualValues(t, v.ID, res[i].ID)
|
||||
assert.Nil(t, res[i].Bool)
|
||||
assert.EqualValues(t, v.Int32, res[i].Int32)
|
||||
assert.EqualValues(t, v.String, res[i].String)
|
||||
assert.EqualValues(t, v.Time.Time.Format(time.RFC3339), res[i].Time.Time.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindEmptyField(t *testing.T) {
|
||||
type EmptyField struct {
|
||||
ID uint64 `xorm:"pk 'id'"`
|
||||
|
||||
Bool bool
|
||||
|
||||
Int64 int64
|
||||
Uint64 uint64
|
||||
|
||||
Int32 int32
|
||||
Uint32 uint32
|
||||
|
||||
Uint8 uint8
|
||||
|
||||
Float float32
|
||||
Double float64
|
||||
|
||||
Utf8 string
|
||||
|
||||
Timestamp time.Time
|
||||
|
||||
Interval time.Duration
|
||||
|
||||
String []byte
|
||||
}
|
||||
|
||||
PrepareScheme(&EmptyField{})
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
data := make([]EmptyField, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
data = append(data, EmptyField{
|
||||
ID: uint64(i),
|
||||
})
|
||||
data[i].String = []uint8{}
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res := make([]EmptyField, 0)
|
||||
err = engine.Asc("id").Find(&res)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, data, res)
|
||||
}
|
||||
|
|
@ -0,0 +1,400 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 22, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.InsertOne(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var name string
|
||||
has, err := engine.Table("users").Cols("name").Get(&name)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, user.Name, name)
|
||||
|
||||
var age uint64
|
||||
has, err = engine.Table("users").Cols("age").Get(&age)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, user.Age, uint32(age))
|
||||
|
||||
var userId sql.NullInt64
|
||||
has, err = engine.Table("users").Cols("user_id").Get(&userId)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, user.UserID, userId)
|
||||
|
||||
var number string
|
||||
has, err = engine.Table("users").Cols("number").Get(&number)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, user.Number, number)
|
||||
|
||||
has, err = engine.
|
||||
Table("users").
|
||||
Cols("name", "age", "user_id", "number").
|
||||
Get(&name, &age, &userId, &number)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, user.Name, name)
|
||||
assert.Equal(t, user.Age, uint32(age))
|
||||
assert.Equal(t, user.UserID, userId)
|
||||
assert.Equal(t, user.Number, number)
|
||||
}
|
||||
|
||||
func TestGetStruct(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 22, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.InsertOne(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret Users
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, user.Name, ret.Name)
|
||||
assert.Equal(t, user.Age, ret.Age)
|
||||
assert.Equal(t, user.UserID, ret.UserID)
|
||||
assert.Equal(t, user.Number, ret.Number)
|
||||
|
||||
_, err = engine.Delete(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret = Users{}
|
||||
has, err = engine.Where("user_id = ?", user.UserID).Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, has)
|
||||
assert.Equal(t, Users{}, ret)
|
||||
}
|
||||
|
||||
func TestGetMap(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 22, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.InsertOne(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret := make(map[string]string)
|
||||
has, err := engine.Table("users").Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
assert.Equal(t, 6, len(ret))
|
||||
assert.Equal(t, "datbeohbbh", ret["name"])
|
||||
assert.Equal(t, "22", ret["age"])
|
||||
assert.Equal(t, "22", ret["user_id"])
|
||||
assert.Equal(t, user.Number, ret["number"])
|
||||
assert.True(t, len(ret["created_at"]) > 0)
|
||||
assert.True(t, len(ret["updated_at"]) > 0)
|
||||
}
|
||||
|
||||
func TestGetNullValue(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 22, Valid: true},
|
||||
Number: uuid.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.InsertOne(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var name string
|
||||
var age uint64
|
||||
has, err := engine.Table("users").Cols("name", "age").Get(&name, &age)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, "", name)
|
||||
assert.Equal(t, uint64(0), age)
|
||||
}
|
||||
|
||||
func TestCustomTypes(t *testing.T) {
|
||||
type CustomInt int64
|
||||
type CustomString string
|
||||
|
||||
type TestCustomizeStruct struct {
|
||||
Uuid []byte `xorm:"pk"`
|
||||
Name CustomString
|
||||
Age CustomInt
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestCustomizeStruct{}))
|
||||
|
||||
data := TestCustomizeStruct{
|
||||
Uuid: []byte(uuid.NewString()),
|
||||
Name: "datbeohbbh",
|
||||
Age: 22,
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
defer func() {
|
||||
assert.NoError(t, session.DropTable(&TestCustomizeStruct{}))
|
||||
}()
|
||||
|
||||
_, err = session.Insert(&data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var name CustomString
|
||||
has, err := session.Table(&TestCustomizeStruct{}).Cols("name").Get(&name)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, CustomString("datbeohbbh"), name)
|
||||
|
||||
var age CustomInt
|
||||
has, err = session.Table(&TestCustomizeStruct{}).Cols("age").Get(&age)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, CustomInt(22), age)
|
||||
}
|
||||
|
||||
func TestGetTime(t *testing.T) {
|
||||
type GetTimeStruct struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
CreateTime time.Time
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&GetTimeStruct{}))
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
defer func() {
|
||||
assert.NoError(t, session.DropTable(&GetTimeStruct{}))
|
||||
}()
|
||||
|
||||
gts := GetTimeStruct{
|
||||
Uuid: int64(1),
|
||||
CreateTime: time.Now().In(engine.GetTZLocation()),
|
||||
}
|
||||
_, err = session.Insert(>s)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var gn time.Time
|
||||
has, err := session.Table(&GetTimeStruct{}).Cols("create_time").Get(&gn)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, gts.CreateTime.Format(time.RFC3339), gn.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestGetMapField(t *testing.T) {
|
||||
type TestMap struct {
|
||||
Id string `xorm:"pk VARCHAR"`
|
||||
Data map[string]interface{} `xorm:"TEXT"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&TestMap{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
m := map[string]interface{}{
|
||||
"abc": "1",
|
||||
"xyz": "abc",
|
||||
"uvc": map[string]interface{}{
|
||||
"1": "abc",
|
||||
"2": "xyz",
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&TestMap{
|
||||
Id: uuid.NewString(),
|
||||
Data: m,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestMap
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
assert.EqualValues(t, m, ret.Data)
|
||||
}
|
||||
|
||||
func TestGetInt(t *testing.T) {
|
||||
type PR int64
|
||||
type TestInt struct {
|
||||
Id string `xorm:"pk VARCHAR"`
|
||||
Data *PR
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&TestInt{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
data := PR(1)
|
||||
_, err = engine.Insert(&TestInt{
|
||||
Id: uuid.NewString(),
|
||||
Data: &data,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestInt
|
||||
has, err := engine.Where("data = ?", PR(1)).Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
func TestGetCustomTypeAllField(t *testing.T) {
|
||||
type RowID = uint32
|
||||
type Str = *string
|
||||
type Double = *float32
|
||||
type Timestamp = *uint64
|
||||
|
||||
type Row struct {
|
||||
ID RowID `xorm:"pk 'id'"`
|
||||
PayloadStr Str `xorm:"'payload_str'"`
|
||||
PayloadDouble Double `xorm:"'payload_double'"`
|
||||
PayloadTimestamp Timestamp `xorm:"'payload_timestamp'"`
|
||||
}
|
||||
|
||||
rows := make([]Row, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
rows = append(rows, Row{
|
||||
ID: RowID(i),
|
||||
PayloadStr: func(s string) *string { return &s }(fmt.Sprintf("payload#%d", i)),
|
||||
PayloadDouble: func(f float32) *float32 { return &f }((float32)(i)),
|
||||
PayloadTimestamp: func(t time.Time) *uint64 {
|
||||
unix := uint64(t.Unix())
|
||||
return &unix
|
||||
}(time.Now()),
|
||||
})
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Row{}))
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&rows)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.Count(&Row{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, cnt)
|
||||
|
||||
for i := RowID(0); i < 10; i++ {
|
||||
res := Row{ID: i}
|
||||
has, err := session.Get(&res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, rows[i], res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEmptyField(t *testing.T) {
|
||||
type EmptyField struct {
|
||||
ID uint64 `xorm:"pk 'id'"`
|
||||
|
||||
Bool bool
|
||||
|
||||
Int64 int64
|
||||
Uint64 uint64
|
||||
|
||||
Int32 int32
|
||||
Uint32 uint32
|
||||
|
||||
Uint8 uint8
|
||||
|
||||
Float float32
|
||||
Double float64
|
||||
|
||||
Utf8 string
|
||||
|
||||
Timestamp *time.Time
|
||||
|
||||
Interval *time.Duration
|
||||
|
||||
String *[]byte
|
||||
}
|
||||
|
||||
PrepareScheme(&EmptyField{})
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
data := make([]EmptyField, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
data = append(data, EmptyField{
|
||||
ID: uint64(i),
|
||||
Timestamp: &time.Time{},
|
||||
Interval: func(d time.Duration) *time.Duration { return &d }(time.Duration(0)),
|
||||
String: &[]uint8{},
|
||||
})
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
res := EmptyField{ID: uint64(i)}
|
||||
has, err := engine.Get(&res)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
t.Logf("%d: %+v\n", i, res)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInsertOne(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := Users{
|
||||
Name: "Dat",
|
||||
Age: 21,
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1234, Valid: true},
|
||||
Number: "56789",
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.InsertOne(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
has, err := engine.Exist(&user)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
func TestInsertMultiStruct(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := engine.Count(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(len(users)), cnt)
|
||||
}
|
||||
|
||||
func TestInsertCreated(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
curTime := time.Now()
|
||||
users := getUsersData()
|
||||
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = engine.Table(&Users{}).Cols("created_at").Find(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
loc := engine.GetTZLocation()
|
||||
for _, user := range users {
|
||||
layout := "2006-01-02 15:04:05"
|
||||
assert.EqualValues(t, curTime.In(loc).Format(layout), user.Created.In(loc).Format(layout))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertMapInterface(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
user := map[string]interface{}{
|
||||
"name": "Dat",
|
||||
"age": uint32(22),
|
||||
"user_id": sql.NullInt64{Int64: int64(1), Valid: true},
|
||||
"number": uuid.NewString(),
|
||||
}
|
||||
|
||||
_, err = engine.Table("users").Insert(user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res := Users{
|
||||
Account: Account{
|
||||
UserID: user["user_id"].(sql.NullInt64),
|
||||
Number: user["number"].(string),
|
||||
},
|
||||
}
|
||||
has, err := engine.Get(&res)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
assert.Equal(t, res.Name, user["name"])
|
||||
assert.Equal(t, res.Age, user["age"])
|
||||
assert.Equal(t, res.UserID, user["user_id"])
|
||||
assert.Equal(t, res.Number, user["number"])
|
||||
}
|
||||
|
||||
func TestInsertMultiMapInterface(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := []map[string]interface{}{}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
users = append(users, map[string]interface{}{
|
||||
"name": fmt.Sprintf("Dat - %d", i),
|
||||
"age": uint32(22 + i),
|
||||
"user_id": sql.NullInt64{Int64: int64(i + 1), Valid: true},
|
||||
"number": uuid.NewString(),
|
||||
})
|
||||
}
|
||||
|
||||
_, err = engine.Table("users").Insert(users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := engine.Table("users").Count()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(len(users)), cnt)
|
||||
}
|
||||
|
||||
func TestInsertCustomType(t *testing.T) {
|
||||
type RowID = uint64
|
||||
|
||||
type Row struct {
|
||||
ID RowID `xorm:"pk 'id'"`
|
||||
PayloadStr *string `xorm:"'payload_str'"`
|
||||
PayloadDouble *float64 `xorm:"'payload_double'"`
|
||||
PayloadTimestamp *time.Time `xorm:"'payload_timestamp'"`
|
||||
}
|
||||
|
||||
rows := make([]Row, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
rows = append(rows, Row{
|
||||
ID: RowID(i),
|
||||
PayloadStr: func(s string) *string { return &s }(fmt.Sprintf("payload#%d", i)),
|
||||
PayloadDouble: func(f float64) *float64 { return &f }((float64)(i)),
|
||||
PayloadTimestamp: func(t time.Time) *time.Time { return &t }(time.Now()),
|
||||
})
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Row{}))
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&rows)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.Count(&Row{})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, cnt)
|
||||
}
|
||||
|
||||
func TestInsertWithTableParams(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
type SeriesTableWithParams struct {
|
||||
Hash uint64 `xorm:"pk hash"`
|
||||
Series *Series `xorm:"extends"`
|
||||
}
|
||||
|
||||
tableParams := map[string]string{
|
||||
"AUTO_PARTITIONING_BY_SIZE": "ENABLED",
|
||||
"AUTO_PARTITIONING_BY_LOAD": "ENABLED",
|
||||
"AUTO_PARTITIONING_PARTITION_SIZE_MB": "1",
|
||||
"AUTO_PARTITIONING_MIN_PARTITIONS_COUNT": "6",
|
||||
"AUTO_PARTITIONING_MAX_PARTITIONS_COUNT": "1000",
|
||||
"UNIFORM_PARTITIONS": "6",
|
||||
}
|
||||
|
||||
engine.Dialect().SetParams(tableParams)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
defer session.Engine().Dialect().SetParams(nil)
|
||||
|
||||
err = session.DropTable(&SeriesTableWithParams{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = session.CreateTable(&SeriesTableWithParams{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("check-YQL-script", func(t *testing.T) {
|
||||
createTableYQL, _ := session.LastSQL()
|
||||
for params, value := range tableParams {
|
||||
pattern := params + `\s*=\s*` + value
|
||||
assert.Regexp(t, pattern, createTableYQL)
|
||||
}
|
||||
})
|
||||
|
||||
computeHash := func(bean interface{}) {
|
||||
data := bean.(*SeriesTableWithParams)
|
||||
err := session.
|
||||
DB().
|
||||
QueryRow(fmt.Sprintf("SELECT Digest::IntHash64(%d)", data.Hash)).
|
||||
Scan(&data.Hash)
|
||||
assert.NoError(t, err)
|
||||
t.Log(data.Hash)
|
||||
}
|
||||
|
||||
for i := uint64(1); i < 100; i++ {
|
||||
_, err = session.
|
||||
Before(computeHash).
|
||||
Insert(&SeriesTableWithParams{
|
||||
Hash: i,
|
||||
Series: &Series{
|
||||
SeriesID: []byte(uuid.New().String()),
|
||||
ReleaseDate: time.Now(),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertWithTableParams2(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
tableParams := map[string]string{
|
||||
"AUTO_PARTITIONING_BY_SIZE": "ENABLED",
|
||||
"AUTO_PARTITIONING_BY_LOAD": "ENABLED",
|
||||
"AUTO_PARTITIONING_PARTITION_SIZE_MB": "1",
|
||||
"AUTO_PARTITIONING_MIN_PARTITIONS_COUNT": "3",
|
||||
"AUTO_PARTITIONING_MAX_PARTITIONS_COUNT": "5",
|
||||
}
|
||||
|
||||
engine.Dialect().SetParams(tableParams)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
defer session.Engine().Dialect().SetParams(nil)
|
||||
|
||||
err = session.DropTable(&Series{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = session.CreateTable(&Series{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("check-YQL-script", func(t *testing.T) {
|
||||
createTableYQL, _ := session.LastSQL()
|
||||
for params, value := range tableParams {
|
||||
pattern := params + `\s*=\s*` + value
|
||||
assert.Regexp(t, pattern, createTableYQL)
|
||||
}
|
||||
})
|
||||
|
||||
for i := uint64(1); i < 100; i++ {
|
||||
s := &Series{
|
||||
SeriesID: []byte(uuid.New().String()),
|
||||
Title: fmt.Sprintf("series#%d", i),
|
||||
SeriesInfo: fmt.Sprintf("series_info#%d", i),
|
||||
Comment: fmt.Sprintf("comment#%d", i),
|
||||
}
|
||||
_, err = session.Insert(s)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertEmptyField(t *testing.T) {
|
||||
type EmptyField struct {
|
||||
ID uint64 `xorm:"pk 'id'"`
|
||||
|
||||
Bool bool
|
||||
|
||||
Int64 int64
|
||||
Uint64 uint64
|
||||
|
||||
Int32 int32
|
||||
Uint32 uint32
|
||||
|
||||
Uint8 uint8
|
||||
|
||||
Float float32
|
||||
Double float64
|
||||
|
||||
Utf8 string
|
||||
|
||||
Timestamp time.Time
|
||||
|
||||
Interval time.Duration
|
||||
|
||||
String []byte
|
||||
}
|
||||
|
||||
PrepareScheme(&EmptyField{})
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = engine.Insert(&EmptyField{
|
||||
ID: uint64(i),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestIterate(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
type UserIterate struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
IsMan bool
|
||||
}
|
||||
|
||||
assert.NoError(t, engine.NewSession().DropTable(&UserIterate{}))
|
||||
|
||||
assert.NoError(t, engine.Sync(new(UserIterate)))
|
||||
|
||||
_, err = engine.Insert(&UserIterate{
|
||||
Uuid: int64(1),
|
||||
IsMan: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Insert(&UserIterate{
|
||||
Uuid: int64(2),
|
||||
IsMan: false,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt := int64(0)
|
||||
err = engine.Iterate(new(UserIterate), func(i int, bean interface{}) error {
|
||||
user := bean.(*UserIterate)
|
||||
if cnt == int64(0) {
|
||||
assert.EqualValues(t, 1, user.Uuid)
|
||||
assert.EqualValues(t, true, user.IsMan)
|
||||
} else {
|
||||
assert.EqualValues(t, 2, user.Uuid)
|
||||
assert.EqualValues(t, false, user.IsMan)
|
||||
}
|
||||
cnt++
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, cnt)
|
||||
}
|
||||
|
||||
func TestBufferIterate(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
type UserBufferIterate struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
IsMan bool
|
||||
}
|
||||
|
||||
assert.NoError(t, engine.NewSession().DropTable(&UserBufferIterate{}))
|
||||
|
||||
assert.NoError(t, engine.Sync(new(UserBufferIterate)))
|
||||
|
||||
var size = 20
|
||||
for i := 0; i < size; i++ {
|
||||
_, err := engine.Insert(&UserBufferIterate{
|
||||
Uuid: int64(i + 1),
|
||||
IsMan: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
var cnt int64 = 0
|
||||
err = engine.BufferSize(9).Iterate(new(UserBufferIterate), func(i int, bean interface{}) error {
|
||||
user := bean.(*UserBufferIterate)
|
||||
assert.EqualValues(t, cnt+1, user.Uuid)
|
||||
assert.EqualValues(t, true, user.IsMan)
|
||||
cnt++
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, size, cnt)
|
||||
|
||||
cnt = int64(0)
|
||||
err = engine.Limit(20).BufferSize(9).Iterate(new(UserBufferIterate), func(i int, bean interface{}) error {
|
||||
user := bean.(*UserBufferIterate)
|
||||
assert.EqualValues(t, cnt+1, user.Uuid)
|
||||
assert.EqualValues(t, true, user.IsMan)
|
||||
cnt++
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, size, cnt)
|
||||
|
||||
cnt = int64(0)
|
||||
err = engine.Limit(7).BufferSize(9).Iterate(new(UserBufferIterate), func(i int, bean interface{}) error {
|
||||
user := bean.(*UserBufferIterate)
|
||||
assert.EqualValues(t, cnt+1, user.Uuid)
|
||||
assert.EqualValues(t, true, user.IsMan)
|
||||
cnt++
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 7, cnt)
|
||||
|
||||
cnt = int64(0)
|
||||
err = engine.Where(builder.Lte{"uuid": int64(10)}).BufferSize(2).Iterate(new(UserBufferIterate), func(i int, bean interface{}) error {
|
||||
user := bean.(*UserBufferIterate)
|
||||
assert.EqualValues(t, cnt+1, user.Uuid)
|
||||
assert.EqualValues(t, true, user.IsMan)
|
||||
cnt++
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, cnt)
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func TestIntPK(t *testing.T) {
|
||||
type Int64PK struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
}
|
||||
|
||||
type Int32PK struct {
|
||||
Uuid int32 `xorm:"pk"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Int64PK{}, &Int32PK{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = session.Insert(&Int64PK{Uuid: int64(i)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Insert(&Int32PK{Uuid: int32(i)})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
var uuidsInt64 []int64
|
||||
err = session.
|
||||
Table(engine.GetTableMapper().Obj2Table("Int64PK")).
|
||||
Cols("uuid").
|
||||
Find(&uuidsInt64)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, len(uuidsInt64))
|
||||
|
||||
var uuidsInt32 []int32
|
||||
err = session.
|
||||
Table(engine.GetTableMapper().Obj2Table("Int32PK")).
|
||||
Cols("uuid").
|
||||
Find(&uuidsInt32)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, len(uuidsInt32))
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
assert.Equal(t, int64(i), uuidsInt64[i])
|
||||
assert.Equal(t, int32(i), uuidsInt32[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUintPK(t *testing.T) {
|
||||
type Uint8PK struct {
|
||||
Uuid uint8 `xorm:"pk"`
|
||||
}
|
||||
|
||||
type Uint32PK struct {
|
||||
Uuid uint32 `xorm:"pk"`
|
||||
}
|
||||
|
||||
type Uint64PK struct {
|
||||
Uuid uint64 `xorm:"pk"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Uint8PK{}, &Uint32PK{}, &Uint64PK{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = session.Insert(&Uint8PK{Uuid: uint8(i)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Insert(&Uint64PK{Uuid: uint64(i)})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Insert(&Uint32PK{Uuid: uint32(i)})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
var uuidsUint64 []uint64
|
||||
err = session.
|
||||
Table(engine.GetTableMapper().Obj2Table("Uint64PK")).
|
||||
Cols("uuid").
|
||||
Find(&uuidsUint64)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, len(uuidsUint64))
|
||||
|
||||
var uuidsUint32 []uint32
|
||||
err = session.
|
||||
Table(engine.GetTableMapper().Obj2Table("Uint32PK")).
|
||||
Cols("uuid").
|
||||
Find(&uuidsUint32)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, len(uuidsUint32))
|
||||
|
||||
var uuidsUint8 []uint8
|
||||
err = session.
|
||||
Table(engine.GetTableMapper().Obj2Table("Uint8PK")).
|
||||
Cols("uuid").
|
||||
Find(&uuidsUint8)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 10, len(uuidsUint32))
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
assert.Equal(t, uint64(i), uuidsUint64[i])
|
||||
assert.Equal(t, uint32(i), uuidsUint32[i])
|
||||
assert.Equal(t, uint8(i), uuidsUint8[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringPK(t *testing.T) {
|
||||
type CustomString string
|
||||
type StringPK struct {
|
||||
Uuid CustomString `xorm:"pk"`
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
tbName := engine.GetTableMapper().Obj2Table("StringPK")
|
||||
|
||||
assert.NoError(t, engine.NewSession().DropTable(tbName))
|
||||
assert.NoError(t, engine.Sync(&StringPK{}))
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = session.Insert(&StringPK{Uuid: CustomString(fmt.Sprintf("pk_%d", i))})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
id := rand.Int31n(10)
|
||||
var data StringPK
|
||||
has, err := session.ID(schemas.PK{fmt.Sprintf("pk_%d", id)}).Get(&data)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, fmt.Sprintf("pk_%d", id), data.Uuid)
|
||||
}
|
||||
|
||||
func TestBytePK(t *testing.T) {
|
||||
type BytePK struct {
|
||||
Uuid []byte `xorm:"pk"`
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
tbName := engine.GetTableMapper().Obj2Table("BytePK")
|
||||
|
||||
assert.NoError(t, engine.NewSession().DropTable(tbName))
|
||||
assert.NoError(t, engine.Sync(&BytePK{}))
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = session.Insert(&BytePK{Uuid: []byte(fmt.Sprintf("pk_%d", i))})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
id := rand.Int31n(10)
|
||||
var data BytePK
|
||||
has, err := session.ID(schemas.PK{[]byte(fmt.Sprintf("pk_%d", id))}).Get(&data)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, []byte(fmt.Sprintf("pk_%d", id)), data.Uuid)
|
||||
}
|
||||
|
||||
func TestCompositePK(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i, user := range users {
|
||||
var data Users
|
||||
_, err = session.ID(schemas.PK{
|
||||
sql.NullInt64{Int64: int64(i), Valid: true},
|
||||
user.Number,
|
||||
}).Get(&data)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.Name, data.Name)
|
||||
assert.Equal(t, user.Age, data.Age)
|
||||
assert.Equal(t, user.UserID, data.UserID)
|
||||
assert.Equal(t, user.Number, data.Number)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestQueryString(t *testing.T) {
|
||||
type GetVar2 struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string `xorm:"varchar(255)"`
|
||||
Age int32
|
||||
Money float64
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&GetVar2{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var data = GetVar2{
|
||||
Uuid: int64(1),
|
||||
Msg: "hi",
|
||||
Age: 28,
|
||||
Money: 1.5,
|
||||
}
|
||||
_, err = engine.InsertOne(data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
records, err := engine.QueryString("select * from " + engine.Quote(engine.TableName("get_var2", true)))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(records))
|
||||
assert.Equal(t, 5, len(records[0]))
|
||||
assert.Equal(t, "1", records[0]["uuid"])
|
||||
assert.Equal(t, "hi", records[0]["msg"])
|
||||
assert.Equal(t, "28", records[0]["age"])
|
||||
assert.Equal(t, "1.5", records[0]["money"])
|
||||
}
|
||||
|
||||
func TestQueryString2(t *testing.T) {
|
||||
type GetVar3 struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg bool
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&GetVar3{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var data = GetVar3{
|
||||
Uuid: int64(1),
|
||||
Msg: false,
|
||||
}
|
||||
_, err = engine.Insert(data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
records, err := engine.QueryString("select * from " + engine.Quote(engine.TableName("get_var3", true)))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(records))
|
||||
assert.Equal(t, 2, len(records[0]))
|
||||
assert.Equal(t, "1", records[0]["uuid"])
|
||||
assert.True(t, "false" == records[0]["msg"])
|
||||
}
|
||||
|
||||
func toBool(i interface{}) bool {
|
||||
switch t := i.(type) {
|
||||
case int32:
|
||||
return t > 0
|
||||
case bool:
|
||||
return t
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestQueryInterface(t *testing.T) {
|
||||
type GetVarInterface struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string `xorm:"varchar(255)"`
|
||||
Age int32
|
||||
Money float64
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&GetVarInterface{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var data = GetVarInterface{
|
||||
Uuid: int64(1),
|
||||
Msg: "hi",
|
||||
Age: int32(28),
|
||||
Money: 1.5,
|
||||
}
|
||||
_, err = engine.InsertOne(data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
records, err := engine.QueryInterface("select * from " + engine.Quote(engine.TableName("get_var_interface", true)))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(records))
|
||||
assert.Equal(t, 5, len(records[0]))
|
||||
assert.EqualValues(t, 1, records[0]["uuid"].(int64))
|
||||
assert.Equal(t, "hi", string(records[0]["msg"].(string)))
|
||||
assert.EqualValues(t, 28, records[0]["age"].(int32))
|
||||
assert.EqualValues(t, 1.5, records[0]["money"].(float64))
|
||||
}
|
||||
|
||||
func TestQueryNoParams(t *testing.T) {
|
||||
type QueryNoParams struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string `xorm:"varchar(255)"`
|
||||
Age int32
|
||||
Money float64
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&QueryNoParams{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
engine.ShowSQL(true)
|
||||
|
||||
var q = QueryNoParams{
|
||||
Uuid: int64(1),
|
||||
Msg: "message",
|
||||
Age: 20,
|
||||
Money: 3000,
|
||||
}
|
||||
_, err = engine.Insert(&q)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertResult := func(t *testing.T, results []map[string][]byte) {
|
||||
assert.EqualValues(t, 1, len(results))
|
||||
id, err := strconv.ParseInt(string(results[0]["uuid"]), 10, 64)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, id)
|
||||
assert.Equal(t, "message", string(results[0]["msg"]))
|
||||
|
||||
age, err := strconv.Atoi(string(results[0]["age"]))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 20, age)
|
||||
|
||||
money, err := strconv.ParseFloat(string(results[0]["money"]), 32)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3000, money)
|
||||
}
|
||||
|
||||
results, err := engine.Table("query_no_params").Limit(10).Query()
|
||||
assert.NoError(t, err)
|
||||
assertResult(t, results)
|
||||
|
||||
results, err = engine.SQL("select * from " + engine.Quote(engine.TableName("query_no_params", true))).Query()
|
||||
assert.NoError(t, err)
|
||||
assertResult(t, results)
|
||||
}
|
||||
|
||||
func TestQueryStringNoParam(t *testing.T) {
|
||||
type GetVar4 struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg bool
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&GetVar4{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var data = GetVar4{
|
||||
Uuid: int64(1),
|
||||
Msg: false,
|
||||
}
|
||||
_, err = engine.Insert(data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
records, err := engine.Table("get_var4").Limit(1).QueryString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, "1", records[0]["uuid"])
|
||||
assert.EqualValues(t, "false", records[0]["msg"])
|
||||
|
||||
records, err = engine.Table("get_var4").Where(builder.Eq{"`uuid`": int64(1)}).QueryString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, "1", records[0]["uuid"])
|
||||
assert.EqualValues(t, "false", records[0]["msg"])
|
||||
}
|
||||
|
||||
func TestQuerySliceStringNoParam(t *testing.T) {
|
||||
type GetVar6 struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg bool
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&GetVar6{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var data = GetVar6{
|
||||
Uuid: int64(1),
|
||||
Msg: false,
|
||||
}
|
||||
_, err = engine.Insert(data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
records, err := engine.Table("get_var6").Cols("uuid", "msg").Limit(1).QuerySliceString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, "1", records[0][0])
|
||||
assert.EqualValues(t, "false", records[0][1])
|
||||
|
||||
records, err = engine.
|
||||
Table("get_var6").
|
||||
Cols("uuid", "msg").
|
||||
Where(builder.Eq{"`uuid`": int64(1)}).
|
||||
QuerySliceString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, "1", records[0][0])
|
||||
assert.EqualValues(t, "false", records[0][1])
|
||||
}
|
||||
|
||||
func TestQueryInterfaceNoParam(t *testing.T) {
|
||||
type GetVar5 struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg bool
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&GetVar5{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var data = GetVar5{
|
||||
Uuid: int64(1),
|
||||
Msg: false,
|
||||
}
|
||||
_, err = engine.Insert(data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
records, err := engine.Table("get_var5").Cols("uuid", "msg").Limit(1).QueryInterface()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, 1, records[0]["uuid"].(int64))
|
||||
assert.EqualValues(t, false, records[0]["msg"].(bool))
|
||||
|
||||
records, err = engine.Table("get_var5").Cols("uuid", "msg").Where(builder.Eq{"`uuid`": int64(1)}).QueryInterface()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(records))
|
||||
assert.EqualValues(t, 1, records[0]["uuid"].(int64))
|
||||
assert.EqualValues(t, false, records[0]["msg"].(bool))
|
||||
}
|
||||
|
||||
func TestQueryWithBuilder(t *testing.T) {
|
||||
type QueryWithBuilder struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string `xorm:"varchar(255)"`
|
||||
Age int32
|
||||
Money float64
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&QueryWithBuilder{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var q = QueryWithBuilder{
|
||||
Uuid: int64(1),
|
||||
Msg: "message",
|
||||
Age: 20,
|
||||
Money: 3000,
|
||||
}
|
||||
_, err = engine.Insert(&q)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertResult := func(t *testing.T, results []map[string][]byte) {
|
||||
assert.EqualValues(t, 1, len(results))
|
||||
id, err := strconv.ParseInt(string(results[0]["uuid"]), 10, 64)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, id)
|
||||
assert.Equal(t, "message", string(results[0]["msg"]))
|
||||
|
||||
age, err := strconv.Atoi(string(results[0]["age"]))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 20, age)
|
||||
|
||||
money, err := strconv.ParseFloat(string(results[0]["money"]), 32)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3000, money)
|
||||
}
|
||||
|
||||
results, err := engine.Query(builder.Select("*").From(engine.Quote(engine.TableName("query_with_builder", true))))
|
||||
assert.NoError(t, err)
|
||||
assertResult(t, results)
|
||||
}
|
||||
|
||||
func TestJoinWithSubQuery(t *testing.T) {
|
||||
type JoinWithSubQuery1 struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string `xorm:"varchar(255)"`
|
||||
DepartId int64
|
||||
Money float64
|
||||
}
|
||||
|
||||
type JoinWithSubQueryDepart struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Name string
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, engine.NewSession().DropTable(new(JoinWithSubQuery1)))
|
||||
assert.NoError(t, engine.NewSession().DropTable(new(JoinWithSubQueryDepart)))
|
||||
assert.NoError(t, engine.Sync(new(JoinWithSubQuery1), new(JoinWithSubQueryDepart)))
|
||||
|
||||
var depart = JoinWithSubQueryDepart{
|
||||
Uuid: int64(1),
|
||||
Name: "depart1",
|
||||
}
|
||||
_, err = engine.Insert(&depart)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var q = JoinWithSubQuery1{
|
||||
Uuid: int64(1),
|
||||
Msg: "message",
|
||||
DepartId: depart.Uuid,
|
||||
Money: 3000,
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&q)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tbName := engine.Quote(engine.TableName("join_with_sub_query_depart", true))
|
||||
var querys []JoinWithSubQuery1
|
||||
err = engine.
|
||||
Table("join_with_sub_query1").
|
||||
Alias("jq1").
|
||||
Cols("jq1.uuid as uuid", "jq1.msg as msg", "jq1.depart_id as depart_id", "jq1.money as money").
|
||||
Join("INNER",
|
||||
builder.Select("`uuid`").From(tbName),
|
||||
"`join_with_sub_query_depart`.`uuid` = `jq1`.`depart_id`").
|
||||
Find(&querys)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(querys))
|
||||
assert.EqualValues(t, q, querys[0])
|
||||
|
||||
querys = make([]JoinWithSubQuery1, 0, 1)
|
||||
err = engine.
|
||||
Table("join_with_sub_query1").
|
||||
Alias("jq1").
|
||||
Cols("jq1.uuid as uuid", "jq1.msg as msg", "jq1.depart_id as depart_id", "jq1.money as money").
|
||||
Join("INNER", "(SELECT `uuid` FROM "+tbName+") `a`", "`a`.`uuid` = `jq1`.`depart_id`").
|
||||
Find(&querys)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(querys))
|
||||
assert.EqualValues(t, q, querys[0])
|
||||
}
|
||||
|
||||
func TestQueryStringWithLimit(t *testing.T) {
|
||||
type QueryWithLimit struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string `xorm:"varchar(255)"`
|
||||
DepartId int64
|
||||
Money float64
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&QueryWithLimit{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
data, err := engine.Table("query_with_limit").Limit(20, 20).QueryString()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, len(data))
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExecAndQuery(t *testing.T) {
|
||||
type UserinfoQuery struct {
|
||||
Uid int64 `xorm:"pk"`
|
||||
Name string
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&UserinfoQuery{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
Exec("INSERT INTO "+engine.TableName("`userinfo_query`", true)+" (`uid`, `name`) VALUES (?, ?)", int64(1), "user")
|
||||
assert.NoError(t, err)
|
||||
|
||||
results, err := engine.
|
||||
Query("select * from " + engine.Quote(engine.TableName("userinfo_query", true)))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(results))
|
||||
|
||||
id, err := strconv.Atoi(string(results[0]["uid"]))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, id)
|
||||
assert.Equal(t, "user", string(results[0]["name"]))
|
||||
}
|
||||
|
||||
func TestExecTime(t *testing.T) {
|
||||
type UserinfoExecTime struct {
|
||||
Uid int64 `xorm:"pk"`
|
||||
Name string
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&UserinfoExecTime{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
_, err = engine.
|
||||
Exec("INSERT INTO "+engine.TableName("`userinfo_exec_time`", true)+" (`uid`, `name`, `created`) VALUES (?, ?, ?)", int64(1), "user", now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var uet UserinfoExecTime
|
||||
has, err := engine.Where("`uid`=?", int64(1)).Get(&uet)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t,
|
||||
now.In(engine.GetTZLocation()).Format(time.RFC3339),
|
||||
uet.Created.In(engine.TZLocation).Format(time.RFC3339))
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type ReplaceUserA struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string
|
||||
Age uint32
|
||||
}
|
||||
|
||||
func (*ReplaceUserA) TableName() string {
|
||||
return "replace_user_a"
|
||||
}
|
||||
|
||||
type ReplaceUserB struct {
|
||||
ReplaceUserA `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (*ReplaceUserB) TableName() string {
|
||||
return "test/replace_user_b"
|
||||
}
|
||||
|
||||
func TestYQLReplaceSinglePK(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&ReplaceUserA{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert([]*ReplaceUserA{
|
||||
{
|
||||
Uuid: int64(1),
|
||||
Msg: fmt.Sprintf("msg_%d", 1),
|
||||
Age: uint32(22),
|
||||
},
|
||||
{
|
||||
Uuid: int64(2),
|
||||
Msg: fmt.Sprintf("msg_%d", 2),
|
||||
Age: uint32(22),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.
|
||||
Exec(
|
||||
"REPLACE INTO `replace_user_a` (`uuid`, `msg`, `age`) VALUES "+
|
||||
"($1, $2, $3), ($4, $5, $6);",
|
||||
int64(3), "msg_3", uint32(22), int64(4), "msg_4", uint32(22),
|
||||
)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.Table((&ReplaceUserA{}).TableName()).Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, cnt)
|
||||
|
||||
_, err = session.
|
||||
Exec(
|
||||
"REPLACE INTO `replace_user_a` (`uuid`, `msg`) VALUES "+
|
||||
"($1, $2);",
|
||||
int64(1), "replace_msg",
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret ReplaceUserA
|
||||
has, err := session.ID(int64(1)).Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, 1, ret.Uuid)
|
||||
assert.EqualValues(t, "replace_msg", ret.Msg)
|
||||
assert.EqualValues(t, 0, ret.Age) // replace with default value
|
||||
}
|
||||
|
||||
func TestYQLReplaceSinglePKByFetch(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
assert.NoError(t, engine.Sync(&ReplaceUserA{}, &ReplaceUserB{}))
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.
|
||||
Exec("REPLACE INTO `test/replace_user_b` (`uuid`, `msg`, `age`) "+
|
||||
"SELECT `uuid`, `msg`, `age` FROM `replace_user_a` WHERE `msg` = $1;", "replace_msg")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret ReplaceUserB
|
||||
has, err := session.Table(&ReplaceUserB{}).Where("msg = ?", "replace_msg").Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
type ReplaceUsers struct {
|
||||
Users `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (*ReplaceUsers) TableName() string {
|
||||
return "replace_users"
|
||||
}
|
||||
|
||||
func TestYQLReplaceCompositePK(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}, &ReplaceUsers{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
uuidArg := uuid.NewString()
|
||||
now := time.Now()
|
||||
|
||||
_, err = session.
|
||||
Exec("REPLACE INTO `users` (`name`, `age`, `user_id`, `number`, `created_at`, `updated_at`) "+
|
||||
"VALUES ($1, $2, $3, $4, $5, $6);", "datbeohbbh", uint32(22), sql.NullInt64{Int64: int64(1), Valid: true}, uuidArg, now, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.
|
||||
Exec("REPLACE INTO `replace_users` (`user_id`, `number`) "+
|
||||
"VALUES ($1, $2);", sql.NullInt64{Int64: int64(1), Valid: true}, uuidArg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Exec("REPLACE INTO `replace_users` SELECT `user_id`, `number`, `name` FROM `users`;")
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.Table((&ReplaceUsers{}).TableName()).Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, cnt)
|
||||
|
||||
var ret ReplaceUsers
|
||||
has, err := session.
|
||||
Table(&ReplaceUsers{}).
|
||||
ID(schemas.PK{
|
||||
sql.NullInt64{Int64: int64(1), Valid: true},
|
||||
uuidArg,
|
||||
}).
|
||||
Get(&ret)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.True(t, has)
|
||||
assert.NotNil(t, ret)
|
||||
|
||||
assert.EqualValues(t, int64(1), ret.UserID.Int64)
|
||||
assert.EqualValues(t, uuidArg, ret.Number)
|
||||
assert.EqualValues(t, "datbeohbbh", ret.Name)
|
||||
|
||||
// overwritten with default values
|
||||
assert.EqualValues(t, 0, ret.Age)
|
||||
assert.EqualValues(t, time.Time{}.Format(time.RFC3339), ret.Created.Format(time.RFC3339))
|
||||
assert.EqualValues(t, time.Time{}.Format(time.RFC3339), ret.Updated.Format(time.RFC3339))
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func TestRowsStruct(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rows, err := session.Asc("user_id").Rows(&Users{})
|
||||
assert.NoError(t, err)
|
||||
defer rows.Close()
|
||||
|
||||
for i := 0; rows.Next(); i++ {
|
||||
var user Users
|
||||
assert.NoError(t, rows.Scan(&user))
|
||||
assert.Equal(t, users[i].UserID, user.UserID)
|
||||
assert.Equal(t, users[i].Number, user.Number)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowsCond(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
rows, err := session.
|
||||
Cols("user_id", "number", "name", "age").
|
||||
Where("user_id >= ?", sql.NullInt64{Int64: 5, Valid: true}).
|
||||
Where("user_id < ?", sql.NullInt64{Int64: 10, Valid: true}).
|
||||
Asc("user_id").
|
||||
Rows(&Users{})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rows, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
rows, ok := result.(*xorm.Rows)
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, rows)
|
||||
defer rows.Close()
|
||||
|
||||
for i := 5; rows.Next(); i++ {
|
||||
var (
|
||||
userID int64
|
||||
number string
|
||||
name string
|
||||
age uint32
|
||||
)
|
||||
assert.NoError(t, rows.Scan(&userID, &number, &name, &age))
|
||||
assert.Equal(t, users[i].UserID.Int64, userID)
|
||||
assert.Equal(t, users[i].Number, number)
|
||||
assert.Equal(t, users[i].Name, name)
|
||||
assert.Equal(t, users[i].Age, age)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowsRawYQL(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rows, err := session.SQL(`
|
||||
SELECT user_id, number, age, created_at FROM users
|
||||
ORDER BY created_at DESC, age ASC
|
||||
LIMIT 10 OFFSET 5;
|
||||
`).
|
||||
Rows(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, rows)
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var user Users
|
||||
assert.NoError(t, rows.Scan(&user))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowsOverManyResultSet(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Series{}, &Seasons{}, &Episodes{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
series, seasons, episodes := getData()
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
_, err := session.Insert(&series)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = session.Insert(&seasons)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = session.Insert(&episodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
query := fmt.Sprintf("SELECT * FROM `%s`; SELECT * FROM `%s`; SELECT * FROM `%s`;",
|
||||
(&Series{}).TableName(),
|
||||
(&Seasons{}).TableName(),
|
||||
(&Episodes{}).TableName())
|
||||
|
||||
rows, err := session.DB().QueryContext(enginePool.ctx, query)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedColumns := [][]string{
|
||||
{"series_id", "title", "series_info", "release_date", "comment"},
|
||||
{"series_id", "season_id", "title", "first_aired", "last_aired"},
|
||||
{"series_id", "season_id", "episode_id", "title", "air_date", "views"},
|
||||
}
|
||||
|
||||
expectedTypes := [][]string{
|
||||
{"String", "Utf8", "Utf8", "Timestamp", "Utf8"},
|
||||
{"String", "String", "Utf8", "Timestamp", "Timestamp"},
|
||||
{"String", "String", "String", "Utf8", "Timestamp", "Uint64"},
|
||||
}
|
||||
|
||||
for i := 0; rows.NextResultSet(); i++ {
|
||||
for rows.Next() {
|
||||
columns, err := rows.Columns()
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, expectedColumns[i], columns)
|
||||
|
||||
var types []string
|
||||
li, err := rows.ColumnTypes()
|
||||
assert.NoError(t, err)
|
||||
for _, val := range li {
|
||||
tp := val.DatabaseTypeName()
|
||||
if strings.HasPrefix(tp, "Optional") {
|
||||
tp = strings.TrimPrefix(tp, "Optional<")
|
||||
tp = strings.TrimSuffix(tp, ">")
|
||||
}
|
||||
types = append(types, tp)
|
||||
}
|
||||
assert.ElementsMatch(t, expectedTypes[i], types)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func TestCreateTable(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
assert.NoError(t, session.DropTable(&Users{}))
|
||||
assert.NoError(t, session.CreateTable(&Users{}))
|
||||
// assert.NoError(t, session.CreateTable(&Users{}))
|
||||
}
|
||||
|
||||
func TestIsTableEmpty(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
|
||||
assert.NoError(t, session.DropTable(&Users{}))
|
||||
assert.NoError(t, session.CreateTable(&Users{}))
|
||||
|
||||
session.Close()
|
||||
|
||||
isEmpty, err := engine.IsTableEmpty(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isEmpty)
|
||||
|
||||
tbName := engine.GetTableMapper().Obj2Table("users")
|
||||
isEmpty, err = engine.IsTableEmpty(tbName)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isEmpty)
|
||||
}
|
||||
|
||||
func TestCreateMultiTables(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
for _, tblNames := range []string{"users", "series", "seasons", "episodes"} {
|
||||
assert.NoError(t, session.DropTable(tblNames))
|
||||
}
|
||||
|
||||
assert.NoError(t, session.CreateTable(&Users{}))
|
||||
assert.NoError(t, session.CreateTable(&Series{}))
|
||||
assert.NoError(t, session.CreateTable(&Seasons{}))
|
||||
assert.NoError(t, session.CreateTable(&Episodes{}))
|
||||
}
|
||||
|
||||
func TestIsTableExists(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
assert.NoError(t, session.DropTable(&Users{}))
|
||||
|
||||
exist, err := session.IsTableExist(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
|
||||
assert.NoError(t, session.CreateTable(&Users{}))
|
||||
|
||||
exist, err = session.IsTableExist(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
}
|
||||
|
||||
func TestIsColumnExist(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
dialect := engine.Dialect()
|
||||
cols := []string{"name", "age", "user_id", "number", "created_at", "updated_at"}
|
||||
|
||||
for _, col := range cols {
|
||||
exist, err := dialect.IsColumnExist(engine.DB(), enginePool.ctx, (&Users{}).TableName(), col)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
}
|
||||
|
||||
cols = []string{"name_", "age_", "user_id_", "number_", "created_at_", "updated_at_"}
|
||||
for _, col := range cols {
|
||||
exist, err := dialect.IsColumnExist(engine.DB(), enginePool.ctx, (&Users{}).TableName(), col)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exist)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTables(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}, &Account{}, &Series{}, &Seasons{}, &Episodes{}, &TestEpisodes{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
tables, err := engine.Dialect().GetTables(engine.DB(), enginePool.ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
"users",
|
||||
"account",
|
||||
"series",
|
||||
"seasons",
|
||||
"episodes",
|
||||
"test/episodes",
|
||||
}
|
||||
|
||||
tableNames := []string{}
|
||||
for _, table := range tables {
|
||||
tableNames = append(tableNames, table.Name)
|
||||
}
|
||||
|
||||
for _, e := range expected {
|
||||
assert.Contains(t, tableNames, e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIndexes(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Seasons{}, &Series{}, &Episodes{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
dialect := engine.Dialect()
|
||||
index, err := dialect.GetIndexes(engine.DB(), enginePool.ctx, (&Series{}).TableName())
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, index["index_series_title"])
|
||||
assert.EqualValues(t, index["index_series_title"].Cols, []string{"title"})
|
||||
|
||||
index, err = dialect.GetIndexes(engine.DB(), enginePool.ctx, (&Seasons{}).TableName())
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, index["index_series_title"])
|
||||
assert.EqualValues(t, index["index_series_title"].Cols, []string{"title"})
|
||||
assert.NotNil(t, index["index_season_first_aired"])
|
||||
assert.EqualValues(t, index["index_season_first_aired"].Cols, []string{"first_aired"})
|
||||
|
||||
index, err = dialect.GetIndexes(engine.DB(), enginePool.ctx, (&Episodes{}).TableName())
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, index["index_episodes_air_date"])
|
||||
assert.EqualValues(t, index["index_episodes_air_date"].Cols, []string{"air_date"})
|
||||
|
||||
type TestIndex struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
IndexA int64 `xorm:"index(a)"`
|
||||
IndexB int64 `xorm:"index(a)"`
|
||||
|
||||
IndexC int64 `xorm:"index(b)"`
|
||||
IndexD int64 `xorm:"index(b)"`
|
||||
IndexE int64 `xorm:"index(b)"`
|
||||
|
||||
IndexF int64 `xorm:"index(c)"`
|
||||
IndexG int64 `xorm:"index(c)"`
|
||||
IndexH int64 `xorm:"index(c)"`
|
||||
IndexI int64 `xorm:"index(c)"`
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestIndex{}))
|
||||
|
||||
index, err = dialect.GetIndexes(engine.DB(), enginePool.ctx, "test_index")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, index["a"])
|
||||
assert.EqualValues(t, 2, len(index["a"].Cols))
|
||||
assert.ElementsMatch(t, []string{"index_a", "index_b"}, index["a"].Cols)
|
||||
|
||||
assert.NotNil(t, index["b"])
|
||||
assert.EqualValues(t, 3, len(index["b"].Cols))
|
||||
assert.ElementsMatch(t, []string{"index_c", "index_d", "index_e"}, index["b"].Cols)
|
||||
|
||||
assert.NotNil(t, index["c"])
|
||||
assert.EqualValues(t, 4, len(index["c"].Cols))
|
||||
assert.ElementsMatch(t, []string{"index_f", "index_g", "index_h", "index_i"}, index["c"].Cols)
|
||||
}
|
||||
|
||||
func TestGetColumns(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
dialect := engine.Dialect()
|
||||
cols, colsMap, err := dialect.GetColumns(engine.DB(), enginePool.ctx, (&Users{}).TableName())
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, cols)
|
||||
|
||||
expectedCols := []string{"name", "age", "user_id", "number", "created_at", "updated_at"}
|
||||
assert.ElementsMatch(t, expectedCols, cols)
|
||||
|
||||
expectedType := []string{"VARCHAR", "UNSIGNED MEDIUMINT", "BIGINT", "VARCHAR", "TIMESTAMP", "TIMESTAMP"}
|
||||
for i, col := range expectedCols {
|
||||
assert.Equal(t, expectedType[i], colsMap[col].SQLType.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncNewTable(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
assert.NoError(t, session.DropTable(&Users{}))
|
||||
assert.NoError(t, session.DropTable(&Account{}))
|
||||
assert.NoError(t, session.DropTable(&Series{}))
|
||||
assert.NoError(t, session.DropTable(&Seasons{}))
|
||||
assert.NoError(t, session.DropTable(&Episodes{}))
|
||||
assert.NoError(t, session.DropTable(&TestEpisodes{}))
|
||||
|
||||
assert.NoError(t, session.Sync(
|
||||
&Users{},
|
||||
&Account{},
|
||||
&Series{},
|
||||
&Seasons{},
|
||||
&Episodes{},
|
||||
&TestEpisodes{}))
|
||||
}
|
||||
|
||||
func TestSyncOldTable(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
assert.NoError(t, PrepareScheme(&Users{}, &Account{}, &Series{}, &Seasons{}, &Episodes{}, &TestEpisodes{}))
|
||||
|
||||
assert.NoError(t, session.Sync(
|
||||
&Users{},
|
||||
&Account{},
|
||||
&Series{}))
|
||||
|
||||
assert.NoError(t, session.Sync(
|
||||
&Seasons{},
|
||||
&Episodes{},
|
||||
&TestEpisodes{}))
|
||||
}
|
||||
|
||||
type oriIndexSync struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
|
||||
A int64 `xorm:"index(idx_a)"`
|
||||
B int64 `xorm:"index(idx_a)"`
|
||||
C int64 `xorm:"index(idx_a)"`
|
||||
|
||||
D int64 `xorm:"index(idx_b)"`
|
||||
E int64 `xorm:"index(idx_b)"`
|
||||
F int64 `xorm:"index(idx_b)"`
|
||||
|
||||
G int64 `xorm:"index(idx_c)"`
|
||||
|
||||
H int64
|
||||
I int64
|
||||
}
|
||||
|
||||
func (*oriIndexSync) TableName() string {
|
||||
return "test_sync_index"
|
||||
}
|
||||
|
||||
type newIndexSync struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
|
||||
A int64
|
||||
B int64 `xorm:"index(idx_a)"`
|
||||
C int64 `xorm:"index(idx_a)"`
|
||||
|
||||
D int64 `xorm:"index(idx_b)"`
|
||||
E int64 `xorm:"index(idx_b)"`
|
||||
F int64 `xorm:"index(idx_b)"`
|
||||
|
||||
G int64
|
||||
|
||||
H int64 `xorm:"index(idx_c)"`
|
||||
I int64 `xorm:"index(idx_d)"`
|
||||
}
|
||||
|
||||
func (*newIndexSync) TableName() string {
|
||||
return "test_sync_index"
|
||||
}
|
||||
|
||||
func TestIndexSync(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
assert.NoError(t, engine.Sync(&oriIndexSync{}))
|
||||
assert.NoError(t, engine.Sync(&newIndexSync{}))
|
||||
|
||||
dialect := engine.Dialect()
|
||||
index, err := dialect.GetIndexes(engine.DB(), enginePool.ctx, "test_sync_index")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, index["idx_a"])
|
||||
assert.ElementsMatch(t, []string{"b", "c"}, index["idx_a"].Cols)
|
||||
|
||||
assert.NotNil(t, index["idx_b"])
|
||||
assert.ElementsMatch(t, []string{"d", "e", "f"}, index["idx_b"].Cols)
|
||||
|
||||
assert.NotNil(t, index["idx_c"])
|
||||
assert.ElementsMatch(t, []string{"h"}, index["idx_c"].Cols)
|
||||
|
||||
assert.NotNil(t, index["idx_d"])
|
||||
assert.ElementsMatch(t, []string{"i"}, index["idx_d"].Cols)
|
||||
|
||||
assert.NoError(t, engine.Sync(&newIndexSync{}))
|
||||
assert.NoError(t, engine.Sync(&oriIndexSync{}))
|
||||
index, err = dialect.GetIndexes(engine.DB(), enginePool.ctx, "test_sync_index")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, index["idx_a"])
|
||||
assert.ElementsMatch(t, []string{"a", "b", "c"}, index["idx_a"].Cols)
|
||||
|
||||
assert.NotNil(t, index["idx_b"])
|
||||
assert.ElementsMatch(t, []string{"d", "e", "f"}, index["idx_b"].Cols)
|
||||
|
||||
assert.NotNil(t, index["idx_c"])
|
||||
assert.ElementsMatch(t, []string{"g"}, index["idx_c"].Cols)
|
||||
|
||||
assert.Nil(t, index["idx_d"])
|
||||
}
|
||||
|
||||
type oriCols struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
A int64
|
||||
B int64
|
||||
C int64
|
||||
D int64
|
||||
NewType int64
|
||||
}
|
||||
|
||||
func (*oriCols) TableName() string {
|
||||
return "test_sync_cols"
|
||||
}
|
||||
|
||||
type newCols struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
A int64
|
||||
B int64
|
||||
C int64
|
||||
D int64
|
||||
E int64
|
||||
F int64
|
||||
G int64
|
||||
NewType string
|
||||
}
|
||||
|
||||
func (*newCols) TableName() string {
|
||||
return "test_sync_cols"
|
||||
}
|
||||
|
||||
func TestSyncCols(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
assert.NoError(t, engine.Sync(&oriCols{}))
|
||||
assert.NoError(t, engine.Sync(&newCols{}))
|
||||
|
||||
dialect := engine.Dialect()
|
||||
cols, colMaps, err := dialect.GetColumns(engine.DB(), enginePool.ctx, "test_sync_cols")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, colMaps)
|
||||
assert.ElementsMatch(t, []string{"uuid", "a", "b", "c", "d", "e", "f", "g", "new_type"}, cols)
|
||||
assert.EqualValues(t, schemas.BigInt, colMaps["new_type"].SQLType.Name)
|
||||
}
|
||||
|
||||
type syncA struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
A int64 `xorm:"index(idx_a)"`
|
||||
B int64 `xorm:"index(idx_b)"`
|
||||
C int64 `xorm:"index(idx_c)"`
|
||||
D int64
|
||||
NewType int64
|
||||
}
|
||||
|
||||
func (*syncA) TableName() string {
|
||||
return "test_overall_sync"
|
||||
}
|
||||
|
||||
type syncB struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
A int64 `xorm:"index(idx_a)"` // common index
|
||||
B int64 `xorm:"index(idx_bb)"` // common index but keep old name: `idx_b``
|
||||
C int64
|
||||
D int64 `xorm:"index(idx_c)"`
|
||||
E int64 `xorm:"index(idx_d)"`
|
||||
F int64 `xorm:"index(idx_e)"`
|
||||
G int64
|
||||
NewType string `xorm:"index(idx_f)"`
|
||||
}
|
||||
|
||||
func (*syncB) TableName() string {
|
||||
return "test_overall_sync"
|
||||
}
|
||||
|
||||
func TestSyncOverall(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
assert.NoError(t, engine.Sync(&syncA{}))
|
||||
assert.NoError(t, engine.Sync(&syncB{}))
|
||||
|
||||
dialect := engine.Dialect()
|
||||
cols, colMaps, err := dialect.GetColumns(engine.DB(), enginePool.ctx, (&syncA{}).TableName())
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, colMaps)
|
||||
assert.ElementsMatch(t, []string{"uuid", "a", "b", "c", "d", "e", "f", "g", "new_type"}, cols)
|
||||
assert.EqualValues(t, schemas.BigInt, colMaps["new_type"].SQLType.Name)
|
||||
|
||||
indexesMap, err := dialect.GetIndexes(engine.DB(), enginePool.ctx, (&syncB{}).TableName())
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, indexesMap)
|
||||
|
||||
assert.NotNil(t, indexesMap["idx_a"])
|
||||
assert.ElementsMatch(t, []string{"a"}, indexesMap["idx_a"].Cols)
|
||||
|
||||
assert.NotNil(t, indexesMap["idx_b"])
|
||||
assert.ElementsMatch(t, []string{"b"}, indexesMap["idx_b"].Cols)
|
||||
|
||||
assert.NotNil(t, indexesMap["idx_c"])
|
||||
assert.ElementsMatch(t, []string{"d"}, indexesMap["idx_c"].Cols)
|
||||
|
||||
assert.NotNil(t, indexesMap["idx_d"])
|
||||
assert.ElementsMatch(t, []string{"e"}, indexesMap["idx_d"].Cols)
|
||||
|
||||
assert.NotNil(t, indexesMap["idx_e"])
|
||||
assert.ElementsMatch(t, []string{"f"}, indexesMap["idx_e"].Cols)
|
||||
|
||||
assert.NotNil(t, indexesMap["idx_f"])
|
||||
assert.ElementsMatch(t, []string{"new_type"}, indexesMap["idx_f"].Cols)
|
||||
}
|
||||
|
||||
func TestDBMetas(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
dialect := engine.Dialect()
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
assert.NoError(t, session.Sync(&Users{}))
|
||||
|
||||
exist, err := dialect.IsTableExist(session.DB(), enginePool.ctx, (&Users{}).TableName())
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assert.True(t, exist)
|
||||
|
||||
tables, err := dialect.GetTables(session.DB(), enginePool.ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tables)
|
||||
ok := false
|
||||
for _, table := range tables {
|
||||
if strings.HasSuffix(table.Name, (&Users{}).TableName()) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, ok)
|
||||
return nil, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/dialects"
|
||||
"xorm.io/xorm/retry"
|
||||
)
|
||||
|
||||
func TestSelectView(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Series{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
series, _, _ := getData()
|
||||
err = engine.DoTx(enginePool.ctx, func(ctx context.Context, session *xorm.Session) error {
|
||||
_, err := session.Insert(&series)
|
||||
return err
|
||||
},
|
||||
retry.WithID(t.Name()),
|
||||
retry.WithIdempotent(true))
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
yql, _, err := builder.Select("COUNT(*)").From((&Series{}).TableName() + " VIEW index_series_title").ToSQL()
|
||||
assert.NoError(t, err)
|
||||
|
||||
series_title, err := engine.SQL(yql).Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(series), series_title)
|
||||
}
|
||||
|
||||
func TestViewCond(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Seasons{}, &Episodes{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
_, seasons, episodes := getData()
|
||||
err = engine.DoTx(enginePool.ctx, func(ctx context.Context, session *xorm.Session) error {
|
||||
_, err := session.Insert(&seasons, &episodes)
|
||||
return err
|
||||
},
|
||||
retry.WithID(t.Name()),
|
||||
retry.WithIdempotent(true))
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("query-view", func(t *testing.T) {
|
||||
var season Seasons
|
||||
t.Run("get-season", func(t *testing.T) {
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
// data: Silicon Valley - season 1
|
||||
yql, args, err := builder.
|
||||
Select("season_id, title, first_aired, last_aired").
|
||||
From((&Seasons{}).TableName() + " VIEW index_season_first_aired").
|
||||
Where(builder.Eq{
|
||||
"first_aired": date("2014-04-06"),
|
||||
}).
|
||||
ToSQL()
|
||||
assert.NoError(t, err)
|
||||
|
||||
has, err := session.SQL(yql, args...).Get(&season)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
t.Log(season)
|
||||
})
|
||||
|
||||
t.Run("count-episodes", func(t *testing.T) {
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
// data: count episodes of Silicon Valley - season 1
|
||||
// expected: 8
|
||||
yql, args, err := builder.
|
||||
Select("COUNT(*)").
|
||||
From((&Episodes{}).TableName() + " VIEW index_episodes_air_date").
|
||||
Where(builder.Between{
|
||||
Col: "air_date",
|
||||
LessVal: season.FirstAired,
|
||||
MoreVal: season.LastAired,
|
||||
}).
|
||||
ToSQL()
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.SQL(yql, args...).Count()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 8, cnt)
|
||||
})
|
||||
|
||||
t.Run("get-episodes", func(t *testing.T) {
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
var episodeData []Episodes
|
||||
err := session.
|
||||
Table((&Episodes{}).TableName()).
|
||||
Where("season_id = ?", season.SeasonID).
|
||||
Find(&episodeData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// data: get episodes of Silicon Valley - season 1
|
||||
yql, args, err := builder.
|
||||
Select("*").
|
||||
From((&Episodes{}).TableName() + " VIEW index_episodes_air_date").
|
||||
Where(builder.Between{
|
||||
Col: "air_date",
|
||||
LessVal: season.FirstAired,
|
||||
MoreVal: season.LastAired,
|
||||
}).
|
||||
ToSQL()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var res []Episodes
|
||||
err = session.SQL(yql, args...).Find(&res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, episodeData, res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestJoinView(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Series{}, &Seasons{}, &Episodes{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
series, seasons, episodes := getData()
|
||||
err = engine.DoTx(enginePool.ctx, func(ctx context.Context, session *xorm.Session) error {
|
||||
_, err := session.Insert(&series, &seasons, &episodes)
|
||||
return err
|
||||
},
|
||||
retry.WithID(t.Name()),
|
||||
retry.WithIdempotent(true))
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
type JoinResult struct {
|
||||
SeriesID []byte `xorm:"'series_id'"`
|
||||
SeasonID []byte `xorm:"'season_id'"`
|
||||
Title string `xorm:"'title'"`
|
||||
SeriesInfo string `xorm:"'series_info'"`
|
||||
}
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
session.Engine().SetQuotePolicy(dialects.QuotePolicyNone)
|
||||
defer session.Engine().SetQuotePolicy(dialects.QuotePolicyAlways)
|
||||
|
||||
res := make([]JoinResult, 0)
|
||||
|
||||
err = session.
|
||||
Table(&Seasons{}).
|
||||
Alias("ss").
|
||||
Select("ss.series_id as series_id, ss.season_id as season_id, ss.title as title, se.series_info as series_info").
|
||||
Join("LEFT", []string{(&Series{}).TableName() + " VIEW index_series_title", "se"}, "ss.title = se.title").
|
||||
Find(&res)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, len(seasons), len(res))
|
||||
}
|
||||
|
||||
func TestJoinViewCond(t *testing.T) {
|
||||
type A struct {
|
||||
Id int64 `xorm:"pk 'id'"`
|
||||
ColA int64 `xorm:"'col_a' index(index_col_a)"`
|
||||
}
|
||||
|
||||
type B struct {
|
||||
Id int64 `xorm:"pk 'id'"`
|
||||
ColB int64 `xorm:"'col_b' index(index_col_b)"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&A{}, &B{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
for i := 1; i <= 10; i++ {
|
||||
_, err = session.Insert(&A{Id: int64(i), ColA: int64(i)}, &B{Id: int64(i), ColB: int64(i)})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
session.Engine().SetQuotePolicy(dialects.QuotePolicyNone)
|
||||
defer session.Engine().SetQuotePolicy(dialects.QuotePolicyAlways)
|
||||
|
||||
type Result struct {
|
||||
Id int64 `xorm:"'id'"`
|
||||
Col int64 `xorm:"'col'"`
|
||||
}
|
||||
|
||||
res := make([]Result, 0)
|
||||
|
||||
err = session.
|
||||
Table("a VIEW index_col_a").
|
||||
Alias("table_a").
|
||||
Select("table_a.id as id, table_a.col_a as col").
|
||||
Join("INNER", []string{"b VIEW index_col_b", "table_b"}, "table_a.col_a = table_b.col_b").
|
||||
Where("table_a.col_a >= ?", 5).
|
||||
Asc("id").
|
||||
Find(&res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 6, len(res))
|
||||
|
||||
t.Log(res)
|
||||
t.Log(session.LastSQL())
|
||||
|
||||
for i := 0; i < len(res); i++ {
|
||||
assert.EqualValues(t, Result{Id: int64(i + 5), Col: int64(i + 5)}, res[i])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func isFloatEq(i, j float64, precision int) bool {
|
||||
return fmt.Sprintf("%."+strconv.Itoa(precision)+"f", i) == fmt.Sprintf("%."+strconv.Itoa(precision)+"f", j)
|
||||
}
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
type SumStruct struct {
|
||||
Int int64 `xorm:"pk"`
|
||||
Float float32
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&SumStruct{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var (
|
||||
cases = []SumStruct{
|
||||
{int64(1), 6.2},
|
||||
{int64(2), 5.3},
|
||||
{int64(92), -0.2},
|
||||
}
|
||||
)
|
||||
|
||||
var i int64
|
||||
var f float32
|
||||
for _, v := range cases {
|
||||
i += int64(v.Int)
|
||||
f += v.Float
|
||||
}
|
||||
|
||||
_, err = engine.Insert(cases)
|
||||
assert.NoError(t, err)
|
||||
|
||||
colInt := engine.GetColumnMapper().Obj2Table("Int")
|
||||
colFloat := engine.GetColumnMapper().Obj2Table("Float")
|
||||
|
||||
sumInt, err := engine.Sum(new(SumStruct), colInt)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, int64(sumInt), i)
|
||||
|
||||
sumFloat, err := engine.Sum(new(SumStruct), colFloat)
|
||||
assert.NoError(t, err)
|
||||
assert.Condition(t, func() bool {
|
||||
return isFloatEq(sumFloat, float64(f), 2)
|
||||
})
|
||||
|
||||
sums, err := engine.Sums(new(SumStruct), colInt, colFloat)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, len(sums))
|
||||
assert.EqualValues(t, i, int64(sums[0]))
|
||||
assert.Condition(t, func() bool {
|
||||
return isFloatEq(sums[1], float64(f), 2)
|
||||
})
|
||||
|
||||
sumsInt, err := engine.SumsInt(new(SumStruct), colInt)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(sumsInt))
|
||||
assert.EqualValues(t, i, int64(sumsInt[0]))
|
||||
}
|
||||
|
||||
type SumStructWithTableName struct {
|
||||
Int int64 `xorm:"pk"`
|
||||
Float float32
|
||||
}
|
||||
|
||||
func (s SumStructWithTableName) TableName() string {
|
||||
return "sum_struct_with_table_name_1"
|
||||
}
|
||||
|
||||
func TestSumWithTableName(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&SumStructWithTableName{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var (
|
||||
cases = []SumStructWithTableName{
|
||||
{int64(1), 6.2},
|
||||
{int64(2), 5.3},
|
||||
{int64(92), -0.2},
|
||||
}
|
||||
)
|
||||
|
||||
var i int64
|
||||
var f float32
|
||||
for _, v := range cases {
|
||||
i += int64(v.Int)
|
||||
f += v.Float
|
||||
}
|
||||
|
||||
_, err = engine.Insert(cases)
|
||||
assert.NoError(t, err)
|
||||
|
||||
colInt := engine.GetColumnMapper().Obj2Table("Int")
|
||||
colFloat := engine.GetColumnMapper().Obj2Table("Float")
|
||||
|
||||
sumInt, err := engine.Sum(new(SumStructWithTableName), colInt)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, int64(sumInt), i)
|
||||
|
||||
sumFloat, err := engine.Sum(new(SumStructWithTableName), colFloat)
|
||||
assert.NoError(t, err)
|
||||
assert.Condition(t, func() bool {
|
||||
return isFloatEq(sumFloat, float64(f), 2)
|
||||
})
|
||||
|
||||
sums, err := engine.Sums(new(SumStructWithTableName), colInt, colFloat)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 2, len(sums))
|
||||
assert.EqualValues(t, i, int64(sums[0]))
|
||||
assert.Condition(t, func() bool {
|
||||
return isFloatEq(sums[1], float64(f), 2)
|
||||
})
|
||||
|
||||
sumsInt, err := engine.SumsInt(new(SumStructWithTableName), colInt)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(sumsInt))
|
||||
assert.EqualValues(t, i, int64(sumsInt[0]))
|
||||
}
|
||||
|
||||
func TestSumCustomColumn(t *testing.T) {
|
||||
type SumStruct2 struct {
|
||||
Int int64 `xorm:"pk"`
|
||||
Float float32
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&SumStruct2{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var (
|
||||
cases = []SumStruct2{
|
||||
{int64(1), 6.2},
|
||||
{int64(2), 5.3},
|
||||
{int64(92), -0.2},
|
||||
}
|
||||
)
|
||||
|
||||
_, err = engine.Insert(cases)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sumInt, err := engine.Sum(new(SumStruct2),
|
||||
"CASE WHEN `int` <= 2 THEN `int` ELSE 0 END")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, int64(sumInt))
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/retry"
|
||||
)
|
||||
|
||||
// !datbeohbbh! transactions concept
|
||||
// https://ydb.tech/en/docs/concepts/transactions
|
||||
|
||||
func TestTx(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
userData := Users{
|
||||
Name: "Dat",
|
||||
Age: 22,
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1234, Valid: true},
|
||||
Number: "56789",
|
||||
},
|
||||
}
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err = session.Begin()
|
||||
assert.NoError(t, err)
|
||||
|
||||
before, err := session.Count(&userData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Insert(&userData)
|
||||
if !assert.NoError(t, err) {
|
||||
session.Rollback()
|
||||
}
|
||||
|
||||
err = session.Commit()
|
||||
assert.NoError(t, err)
|
||||
|
||||
after, err := session.Count(&userData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, after, before+1)
|
||||
}
|
||||
|
||||
func TestMultipleTx(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
userDataA := Users{
|
||||
Name: "Dat",
|
||||
Age: 21,
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1234, Valid: true},
|
||||
Number: "56789",
|
||||
},
|
||||
}
|
||||
|
||||
userDataB := Users{
|
||||
Name: "Dat",
|
||||
Age: 22,
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 5678, Valid: true},
|
||||
Number: "102030",
|
||||
},
|
||||
}
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err = session.Begin()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Insert(&userDataA)
|
||||
if !assert.NoError(t, err) {
|
||||
session.Rollback()
|
||||
}
|
||||
|
||||
err = session.Commit()
|
||||
if !assert.NoError(t, err) {
|
||||
session.Rollback()
|
||||
}
|
||||
|
||||
err = session.Begin()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Exec(
|
||||
fmt.Sprintf("INSERT INTO `%s` (name, age, user_id, number) VALUES (\"%s\", %d, %d, \"%s\")",
|
||||
(&Users{}).TableName(),
|
||||
userDataB.Name,
|
||||
userDataB.Age,
|
||||
userDataB.UserID.Int64,
|
||||
userDataB.Number))
|
||||
assert.NoError(t, err)
|
||||
if !assert.NoError(t, err) {
|
||||
session.Rollback()
|
||||
}
|
||||
|
||||
err = session.Commit()
|
||||
if !assert.NoError(t, err) {
|
||||
session.Rollback()
|
||||
}
|
||||
|
||||
after, err := session.Count(&Users{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, after, int64(2))
|
||||
}
|
||||
|
||||
func TestEngineTx(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
userDataA := Users{
|
||||
Name: "Dat",
|
||||
Age: 21,
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 1234, Valid: true},
|
||||
Number: "56789",
|
||||
},
|
||||
}
|
||||
|
||||
userDataB := Users{
|
||||
Name: "Dat",
|
||||
Age: 22,
|
||||
Account: Account{
|
||||
UserID: sql.NullInt64{Int64: 5678, Valid: true},
|
||||
Number: "102030",
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
users := []*Users{&userDataA, &userDataB}
|
||||
_, err := session.Insert(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
_, err := session.Table(&Users{}).Delete(userDataA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
hasA, err := session.Exist(&userDataA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assert.False(t, hasA)
|
||||
|
||||
hasB, err := session.Exist(&userDataB)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
assert.True(t, hasB)
|
||||
|
||||
return nil, nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDDLTx(t *testing.T) {
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
err = engine.DoTx(enginePool.ctx, func(ctx context.Context, session *xorm.Session) error {
|
||||
for _, bean := range []interface{}{
|
||||
&Users{},
|
||||
&Series{},
|
||||
&Seasons{},
|
||||
&Episodes{},
|
||||
} {
|
||||
if err := session.DropTable(bean); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := session.CreateTable(bean); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}, retry.WithIdempotent(true))
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDDLTxSync(t *testing.T) {
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
err = engine.DoTx(enginePool.ctx, func(ctx context.Context, session *xorm.Session) error {
|
||||
for _, bean := range []interface{}{
|
||||
&Users{},
|
||||
&Series{},
|
||||
&Seasons{},
|
||||
&Episodes{},
|
||||
} {
|
||||
if err := session.DropTable(bean); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := session.Sync(&Users{}, &Series{}, &Seasons{}, &Episodes{})
|
||||
return err
|
||||
}, retry.WithIdempotent(true))
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInsertMulti2InterfaceTransaction(t *testing.T) {
|
||||
type Multi2InterfaceTransaction struct {
|
||||
ID uint64 `xorm:"id pk"`
|
||||
Name string
|
||||
Alias string
|
||||
CreateTime time.Time `xorm:"created"`
|
||||
UpdateTime time.Time `xorm:"updated"`
|
||||
}
|
||||
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
assert.NoError(t, engine.Sync(&Multi2InterfaceTransaction{}))
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err = session.Begin()
|
||||
assert.NoError(t, err)
|
||||
|
||||
users := []interface{}{
|
||||
&Multi2InterfaceTransaction{ID: 1, Name: "a", Alias: "A"},
|
||||
&Multi2InterfaceTransaction{ID: 2, Name: "b", Alias: "B"},
|
||||
&Multi2InterfaceTransaction{ID: 3, Name: "c", Alias: "C"},
|
||||
&Multi2InterfaceTransaction{ID: 4, Name: "d", Alias: "D"},
|
||||
}
|
||||
_, err = session.Insert(&users)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
err = session.Commit()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm/internal/statements"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func TestUpdateMap(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
loc := engine.GetTZLocation()
|
||||
|
||||
users := []map[string]interface{}{}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
users = append(users, map[string]interface{}{
|
||||
"name": fmt.Sprintf("Dat - %d", i),
|
||||
"age": uint32(22 + i),
|
||||
"user_id": sql.NullInt64{Int64: int64(i + 1), Valid: true},
|
||||
"number": uuid.NewString(),
|
||||
})
|
||||
}
|
||||
_, err = engine.Table((&Users{}).TableName()).Insert(users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
Table((&Users{}).TableName()).
|
||||
Where(builder.Between{
|
||||
Col: "user_id",
|
||||
LessVal: sql.NullInt64{Int64: 1, Valid: true},
|
||||
MoreVal: sql.NullInt64{Int64: 5, Valid: true},
|
||||
}).
|
||||
Update(map[string]interface{}{
|
||||
"name": "datbeohbbh",
|
||||
"age": uint32(22),
|
||||
"updated_at": time.Now().In(loc),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
Table((&Users{}).TableName()).
|
||||
Update(map[string]interface{}{
|
||||
"name": "datbeohbbh - test",
|
||||
"updated_at": time.Now().In(loc),
|
||||
}, &Users{
|
||||
Account: Account{UserID: sql.NullInt64{Int64: 6, Valid: true}},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
Table((&Users{}).TableName()).
|
||||
ID(schemas.PK{
|
||||
sql.NullInt64{Int64: 7, Valid: true},
|
||||
users[6]["number"].(string),
|
||||
}).
|
||||
Update(map[string]interface{}{
|
||||
"name": "datbeohbbh - test - 2",
|
||||
"updated_at": time.Now().In(loc),
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, statements.IsIDConditionWithNoTableErr(err))
|
||||
|
||||
_, err = engine.
|
||||
Table((&Users{}).TableName()).
|
||||
Where("user_id = ? AND number = ?",
|
||||
sql.NullInt64{Int64: 7, Valid: true},
|
||||
users[6]["number"].(string)).
|
||||
Update(map[string]interface{}{
|
||||
"name": "datbeohbbh - test - 2",
|
||||
"updated_at": time.Now().In(loc),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateIn(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
userIds := []sql.NullInt64{}
|
||||
for i := 0; i < 10; i++ {
|
||||
userIds = append(userIds, sql.NullInt64{
|
||||
Int64: int64(i),
|
||||
Valid: true,
|
||||
})
|
||||
}
|
||||
_, err = engine.In("user_id", userIds).Update(&Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateStruct(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
Age: uint32(22),
|
||||
}
|
||||
_, err = engine.
|
||||
ID(schemas.PK{
|
||||
sql.NullInt64{Int64: 0, Valid: true},
|
||||
users[0].Number,
|
||||
}).
|
||||
Update(&user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Update(&user, &Users{
|
||||
Account: Account{
|
||||
Number: users[0].Number,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateIncrDecr(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
userIncr := Users{
|
||||
Name: "datbeohbbh - incr",
|
||||
}
|
||||
|
||||
_, err = engine.
|
||||
ID(schemas.PK{
|
||||
sql.NullInt64{Int64: 0, Valid: true},
|
||||
users[0].Number,
|
||||
}).
|
||||
Incr("age", uint32(10)).
|
||||
Update(&userIncr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
userDecr := Users{
|
||||
Name: "datbeohbbh - decr",
|
||||
}
|
||||
_, err = engine.
|
||||
ID(schemas.PK{
|
||||
sql.NullInt64{Int64: 1, Valid: true},
|
||||
users[1].Number,
|
||||
}).
|
||||
Decr("age", uint32(10)).
|
||||
Update(&userDecr)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateMapCondition(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
}
|
||||
|
||||
_, err = engine.Update(&user, map[string]interface{}{
|
||||
"user_id": sql.NullInt64{Int64: 0, Valid: true},
|
||||
"number": users[0].Number,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret := Users{}
|
||||
has, err := engine.ID(schemas.PK{
|
||||
sql.NullInt64{Int64: 0, Valid: true},
|
||||
users[0].Number,
|
||||
}).Get(&ret)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, user.Name, ret.Name)
|
||||
}
|
||||
|
||||
func TestUpdateExprs(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user := Users{
|
||||
Name: "datbeohbbh",
|
||||
}
|
||||
|
||||
_, err = engine.
|
||||
SetExpr("age", uint32(0)).
|
||||
// AllCols().
|
||||
Update(&user)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateExprs2(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}))
|
||||
engine, err := enginePool.GetDefaultEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
users := getUsersData()
|
||||
_, err = engine.Insert(&users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
SetExpr("age", uint32(20)).
|
||||
SetExpr("name", "\"test\"").
|
||||
Where(builder.Gte{
|
||||
"user_id": sql.NullInt64{Int64: 5, Valid: true},
|
||||
}).
|
||||
Where(builder.Lt{
|
||||
"user_id": sql.NullInt64{Int64: 10, Valid: true},
|
||||
}).
|
||||
And("age > ?", uint32(22)).
|
||||
Update(new(Users))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type UpsertUserA struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Msg string
|
||||
Age uint32
|
||||
}
|
||||
|
||||
func (*UpsertUserA) TableName() string {
|
||||
return "upsert_user_a"
|
||||
}
|
||||
|
||||
type UpsertUserB struct {
|
||||
UpsertUserA `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (*UpsertUserB) TableName() string {
|
||||
return "test/upsert_user_b"
|
||||
}
|
||||
|
||||
func TestYQLUpsertSinglePK(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&UpsertUserA{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert([]*UpsertUserA{
|
||||
{
|
||||
Uuid: int64(1),
|
||||
Msg: fmt.Sprintf("msg_%d", 1),
|
||||
Age: uint32(22),
|
||||
},
|
||||
{
|
||||
Uuid: int64(2),
|
||||
Msg: fmt.Sprintf("msg_%d", 2),
|
||||
Age: uint32(22),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.
|
||||
Exec(
|
||||
"UPSERT INTO `upsert_user_a` (`uuid`, `msg`, `age`) VALUES "+
|
||||
"($1, $2, $3), ($4, $5, $6);",
|
||||
int64(3), "msg_3", uint32(22),
|
||||
int64(4), "msg_4", uint32(22),
|
||||
)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.Table((&UpsertUserA{}).TableName()).Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, cnt)
|
||||
|
||||
_, err = session.
|
||||
Exec(
|
||||
"UPSERT INTO `upsert_user_a` (`uuid`, `msg`) VALUES "+
|
||||
"($1, $2);",
|
||||
int64(1), "upsert_msg",
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret UpsertUserA
|
||||
has, err := session.ID(int64(1)).Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, 1, ret.Uuid)
|
||||
assert.EqualValues(t, "upsert_msg", ret.Msg)
|
||||
assert.EqualValues(t, uint32(22), ret.Age) // value are preserved
|
||||
}
|
||||
|
||||
func TestYQLUpsertSinglePKByFetch(t *testing.T) {
|
||||
engine, err := enginePool.GetScriptQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
assert.NoError(t, engine.Sync(&UpsertUserA{}, &UpsertUserB{}))
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.
|
||||
Exec("UPSERT INTO `test/upsert_user_b` (`uuid`, `msg`, `age`) "+
|
||||
"SELECT `uuid`, `msg`, `age` FROM `upsert_user_a` WHERE `msg` = $1;", "upsert_msg")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret UpsertUserB
|
||||
has, err := session.Table(&UpsertUserB{}).Where("msg = ?", "upsert_msg").Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
type UpsertUsers struct {
|
||||
Users `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (*UpsertUsers) TableName() string {
|
||||
return "upsert_users"
|
||||
}
|
||||
|
||||
func TestYQLUpsertCompositePK(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Users{}, &UpsertUsers{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
uuidArg := uuid.NewString()
|
||||
now := time.Now()
|
||||
|
||||
_, err = session.
|
||||
Exec("UPSERT INTO `users` (`name`, `age`, `user_id`, `number`, `created_at`, `updated_at`) "+
|
||||
"VALUES ($1, $2, $3, $4, $5, $6);", "datbeohbbh", uint32(22), sql.NullInt64{Int64: int64(1), Valid: true}, uuidArg, now, now)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.
|
||||
Exec("UPSERT INTO `upsert_users` (`user_id`, `number`,`name`) "+
|
||||
"VALUES ($1, $2, $3);", sql.NullInt64{Int64: int64(1), Valid: true}, uuidArg, "test")
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = session.Exec("UPSERT INTO `upsert_users` SELECT `user_id`, `number`, `age`, `created_at`, `updated_at` FROM `users`;")
|
||||
assert.NoError(t, err)
|
||||
|
||||
cnt, err := session.Table((&UpsertUsers{}).TableName()).Count()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, cnt)
|
||||
|
||||
var ret UpsertUsers
|
||||
has, err := session.
|
||||
Table(&UpsertUsers{}).
|
||||
ID(schemas.PK{
|
||||
sql.NullInt64{Int64: int64(1), Valid: true},
|
||||
uuidArg,
|
||||
}).
|
||||
Get(&ret)
|
||||
|
||||
loc := engine.GetTZLocation()
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.True(t, has)
|
||||
assert.NotNil(t, ret)
|
||||
|
||||
assert.EqualValues(t, int64(1), ret.UserID.Int64)
|
||||
assert.EqualValues(t, uuidArg, ret.Number)
|
||||
assert.EqualValues(t, "test", ret.Name) // value of column `name` is preserved
|
||||
|
||||
// values are updated after fetched
|
||||
assert.EqualValues(t, 22, ret.Age)
|
||||
assert.EqualValues(t, now.In(loc).Format(time.RFC3339), ret.Created.Format(time.RFC3339))
|
||||
assert.EqualValues(t, now.In(loc).Format(time.RFC3339), ret.Updated.Format(time.RFC3339))
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkSync(b *testing.B) {
|
||||
assert.NoError(b, PrepareScheme(&Users{}, &Series{}, &Seasons{}, &Episodes{}))
|
||||
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
assert.NoError(b, err)
|
||||
assert.NotNil(b, engine)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
assert.NoError(b, engine.Sync(
|
||||
&Users{},
|
||||
&Series{},
|
||||
&Seasons{},
|
||||
&Episodes{},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
-- Create tables
|
||||
|
||||
CREATE TABLE `episodes` (
|
||||
`series_id` String NOT NULL,
|
||||
`season_id` String NOT NULL,
|
||||
`episode_id` String NOT NULL,
|
||||
`title` Utf8,
|
||||
`air_date` Timestamp,
|
||||
`views` Uint64,
|
||||
INDEX `index_episodes_air_date` GLOBAL ON ( `air_date` ),
|
||||
PRIMARY KEY ( `series_id`, `season_id`, `episode_id` )
|
||||
);
|
||||
|
||||
CREATE TABLE `seasons` (
|
||||
`series_id` String NOT NULL,
|
||||
`season_id` String NOT NULL,
|
||||
`title` Utf8,
|
||||
`first_aired` Timestamp,
|
||||
`last_aired` Timestamp,
|
||||
INDEX `index_season_first_aired` GLOBAL ON ( `first_aired` ),
|
||||
INDEX `index_series_title` GLOBAL ON ( `title` ),
|
||||
PRIMARY KEY ( `series_id`, `season_id` )
|
||||
);
|
||||
|
||||
CREATE TABLE `series` (
|
||||
`series_id` String NOT NULL,
|
||||
`title` Utf8,
|
||||
`series_info` Utf8,
|
||||
`release_date` Timestamp,
|
||||
`comment` Utf8,
|
||||
INDEX `index_series_title` GLOBAL ON ( `title` ),
|
||||
PRIMARY KEY ( `series_id` )
|
||||
);
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
-- Insert data into `/local/series`
|
||||
|
||||
UPSERT INTO `series` (`series_id`, `title`, `series_info`, `release_date`, `comment`) VALUES
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7',CAST('Silicon Valley' AS Optional<Utf8>),CAST('Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley.' AS Optional<Utf8>),CAST(1396742400000000 AS Optional<Timestamp>),CAST('Some comment here' AS Optional<Utf8>)),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88',CAST('IT Crowd' AS Optional<Utf8>),CAST('The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by Ash Atalla and starring Chris O\'\'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry.' AS Optional<Utf8>),CAST(1138924800000000 AS Optional<Timestamp>), '');
|
||||
|
||||
-- Insert data into `/local/seasons`
|
||||
|
||||
UPSERT INTO `seasons` (`series_id`, `season_id`, `title`, `first_aired`, `last_aired`) VALUES
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171',CAST('Season 4' AS Optional<Utf8>),CAST(1277424000000000 AS Optional<Timestamp>),CAST(1280448000000000 AS Optional<Timestamp>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319',CAST('Season 5' AS Optional<Utf8>),CAST(1521936000000000 AS Optional<Timestamp>),CAST(1526169600000000 AS Optional<Timestamp>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c',CAST('Season 1' AS Optional<Utf8>),CAST(1138924800000000 AS Optional<Timestamp>),CAST(1141344000000000 AS Optional<Timestamp>)),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224',CAST('Season 1' AS Optional<Utf8>),CAST(1138924800000000 AS Optional<Timestamp>),CAST(1141344000000000 AS Optional<Timestamp>)),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950',CAST('Season 4' AS Optional<Utf8>),CAST(1277424000000000 AS Optional<Timestamp>),CAST(1280448000000000 AS Optional<Timestamp>));
|
||||
|
||||
-- Insert data into `/local/episodes`
|
||||
|
||||
UPSERT INTO `episodes` (`series_id`, `season_id`, `episode_id`, `title`, `air_date`, `views`) VALUES
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','0a958d0a-920b-4745-9d79-d7e403e23903',CAST('White Hat/Black Hat' AS Optional<Utf8>),CAST(1433030400000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','1187c66e-db36-4f43-8f07-fd4f29384087',CAST('Homicide' AS Optional<Utf8>),CAST(1431820800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','21d1b5b0-22c3-449d-bd88-9b5b5ed54a3f',CAST('test' AS Optional<Utf8>),CAST(1430611200000000 AS Optional<Timestamp>),CAST('999' AS Optional<Uint64>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','41137bf8-815c-4dc6-abc0-b6f4b2ca785f',CAST('Two Days of the Condor' AS Optional<Utf8>),CAST(1434240000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','493d04f0-dac8-42e2-bc1b-84dc9bb1e099',CAST('Runaway Devaluation' AS Optional<Utf8>),CAST(1429401600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','56113b02-ed67-4ad1-ad87-5516c12582e9',CAST('Bad Money' AS Optional<Utf8>),CAST(1430006400000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','636c215d-bcdb-4571-a779-cedd07c703ca',CAST('Sand Hill Shuffle' AS Optional<Utf8>),CAST(1428796800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','9b1432d0-850d-44bb-bc76-1d4fe0e64101',CAST('Server Space' AS Optional<Utf8>),CAST(1431216000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','df6cdb08-2fc4-43bf-80b5-c970f752c7a5',CAST('Adult Content' AS Optional<Utf8>),CAST(1432425600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','e02cd943-31c1-4185-bcbb-cf803be61dc7',CAST('Binding Arbitration' AS Optional<Utf8>),CAST(1433635200000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','1589f366-2239-4c37-909b-797439094b53',CAST('Server Error' AS Optional<Utf8>),CAST(1498348800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','3385778f-0206-4f94-b80a-b5542a2bd657',CAST('Customer Service' AS Optional<Utf8>),CAST(1495929600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','3bde20f8-ba00-418d-8c6b-058110575879',CAST('Success Failure' AS Optional<Utf8>),CAST(1492905600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','6818d030-ee1f-4626-92fc-03965fcebce5',CAST('Terms of Service' AS Optional<Utf8>),CAST(1493510400000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','6d6616bb-c98e-4f0c-a097-c9aad558dfd8',CAST('Hooli-Con' AS Optional<Utf8>),CAST(1497744000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','78005ee7-7ad6-4c58-aac3-cd6eb2871017',CAST('test' AS Optional<Utf8>),CAST(1497139200000000 AS Optional<Timestamp>),CAST('999' AS Optional<Uint64>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','7900f321-3907-4cc1-826a-69344359ca28',CAST('test' AS Optional<Utf8>),CAST(1496534400000000 AS Optional<Timestamp>),CAST('999' AS Optional<Uint64>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','7b1afb5f-5259-4aca-b54f-84d72b995748',CAST('Intellectual Property' AS Optional<Utf8>),CAST(1494115200000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','83f046ff-6e1b-4f84-9db2-5a20527382a4',CAST('Teambuilding Exercise' AS Optional<Utf8>),CAST(1494720000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','aef2d1e5-02c0-41ac-926b-fad43d6bc0d8',CAST('test' AS Optional<Utf8>),CAST(1495324800000000 AS Optional<Timestamp>),CAST('999' AS Optional<Uint64>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','0fe8e4a2-2b1a-4a61-95ec-b4a6432c038c',CAST('Initial Coin Offering' AS Optional<Utf8>),CAST(1525564800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','382b35eb-2615-4b58-afb1-b591d905135f',CAST('Grow Fast or Die Slow' AS Optional<Utf8>),CAST(1521936000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','7b8db0cb-1ba3-4329-abfe-bd053624dcb2',CAST('Tech Evangelist' AS Optional<Utf8>),CAST(1523750400000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','8eb19cb1-c3cf-473b-9b88-248558422731',CAST('Facial Recognition' AS Optional<Utf8>),CAST(1524355200000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','a8c01056-3ed5-4615-87fa-f0047ccf612d',CAST('Artificial Emotional Intelligence' AS Optional<Utf8>),CAST(1524960000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','e270ba41-ce67-4725-a192-6df9d97e6bdf',CAST('Reorientation' AS Optional<Utf8>),CAST(1522540800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','e67bc0f1-bc04-4079-be07-15dadddb560b',CAST('Chief Operating Officer' AS Optional<Utf8>),CAST(1523145600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','e913806f-16b8-4298-b7d2-533352b5659a',CAST('Fifty-One Percent' AS Optional<Utf8>),CAST(1526169600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','20745b9d-c9d8-443f-b27f-8f3633b076a6',CAST('Founder Friendly' AS Optional<Utf8>),CAST(1461456000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','59968ede-61ed-4547-af4a-9a1b26ece417',CAST('test' AS Optional<Utf8>),CAST(1463875200000000 AS Optional<Timestamp>),CAST('999' AS Optional<Uint64>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','5bfd22db-e967-46c2-91c5-5e1a79aeee17',CAST('Bachmanity Insanity' AS Optional<Utf8>),CAST(1464480000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','622d5664-8361-4a21-b90e-febc6920e8a7',CAST('Meinertzhagen\'\'s Haversack' AS Optional<Utf8>),CAST(1462665600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','6717a4d1-1510-4cda-bf1b-bd428f01078a',CAST('Maleant Data Systems Solutions' AS Optional<Utf8>),CAST(1463270400000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','7fc1149e-78d7-47b4-88ff-cca0337e06fe',CAST('To Build a Better Beta' AS Optional<Utf8>),CAST(1465084800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','c93e9ff1-c9e2-4b21-9c08-23993b5de66c',CAST('test' AS Optional<Utf8>),CAST(1466899200000000 AS Optional<Timestamp>),CAST('999' AS Optional<Uint64>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','e5d88eb6-25e0-451d-a558-f8a7821e0e33',CAST('Two in the Box' AS Optional<Utf8>),CAST(1462060800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','f3f4b8bb-0ebd-4c2d-aab2-c6e3152fbdab',CAST('Bachman\'\'s Earnings Over-Ride' AS Optional<Utf8>),CAST(1465689600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','fbdf6b46-417a-4978-bbe8-146cdb235a6d',CAST('Daily Active Users' AS Optional<Utf8>),CAST(1466294400000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','49548f34-319f-4c25-a0ea-c0eaf795bd8c',CAST('Fiduciary Duties' AS Optional<Utf8>),CAST(1398556800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','527f28f6-f865-48fc-b22a-d3c69d168408',CAST('Proof of Concept' AS Optional<Utf8>),CAST(1400371200000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','5ef1805b-0cdf-451a-9831-e039931a6203',CAST('Signaling Risk' AS Optional<Utf8>),CAST(1399161600000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','71fb225c-327a-430c-ba57-244b49404dd5',CAST('Optimal Tip-to-Tip Efficiency' AS Optional<Utf8>),CAST(1401580800000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','a955e7be-43da-4ba4-ac71-5dad86ecccd5',CAST('Articles of Incorporation' AS Optional<Utf8>),CAST(1397952000000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','bf2ba43c-9542-41a3-994c-6edf96f5138e',CAST('test' AS Optional<Utf8>),CAST(1397347200000000 AS Optional<Timestamp>),CAST('999' AS Optional<Uint64>)),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','d56fcf97-c7bb-4c96-a51b-b35363525dc9',CAST('Minimum Viable Product' AS Optional<Utf8>),CAST(1396742400000000 AS Optional<Timestamp>),NULL),
|
||||
('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','e181a2f2-1ffa-4283-b5b1-0226f3805a2c',CAST('Third Party Insourcing' AS Optional<Utf8>),CAST(1399766400000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','47118ec6-d0e2-4164-9876-415512cd32ef',CAST('Yesterday\'\'s Jam' AS Optional<Utf8>),CAST(1138924800000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','4c72e475-2003-4e7f-a7e7-5c2c27512fc8',CAST('The Red Door' AS Optional<Utf8>),CAST(1140134400000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','98c194f8-622d-4f5c-9e6d-653bcf6cb157',CAST('Aunt Irma Visits' AS Optional<Utf8>),CAST(1141344000000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','9fa9aeb5-347a-4888-b451-c6edaad9ecc0',CAST('The Haunting of Bill Crouse' AS Optional<Utf8>),CAST(1140739200000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','b46d5977-6891-46ad-878c-7ef9c4e10ad3',CAST('Fifty-Fifty' AS Optional<Utf8>),CAST(1139529600000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','de57626f-5b43-4673-bc9c-a0d1fef2fb20',CAST('Calamity Jen' AS Optional<Utf8>),CAST(1138924800000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','158a5818-f853-499c-aa1f-9088c0229054',CAST('Calendar Geeks' AS Optional<Utf8>),CAST(1230249600000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','19f4c4f4-4018-4139-9c48-02e491560f79',CAST('Friendface' AS Optional<Utf8>),CAST(1229644800000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','71eda16f-10b8-42c7-ac6e-fbf5ad61c049',CAST('Are We Not Men?' AS Optional<Utf8>),CAST(1227830400000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','7d558a59-e17f-481e-8b3b-03b54cf6a635',CAST('Tramps Like Us' AS Optional<Utf8>),CAST(1228435200000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','80541d00-ca6d-4380-87bb-619894dd1997',CAST('From Hell' AS Optional<Utf8>),CAST(1227225600000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','eaf80f6b-8b1e-4f04-8da2-070791afeba4',CAST('The Speech' AS Optional<Utf8>),CAST(1229040000000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','2c1776bf-8861-4728-8a71-2d62ccd608ef',CAST('Reynholm vs Reynholm' AS Optional<Utf8>),CAST(1280448000000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','46b4b4c8-0ce7-48b2-912e-a964161e8ec3',CAST('Bad Boys' AS Optional<Utf8>),CAST(1279843200000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','584360cf-8ff3-47ba-b44e-4c026e545f3d',CAST('Something Happened' AS Optional<Utf8>),CAST(1278633600000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','96d6fda1-84f6-4c56-8763-9604b51faa11',CAST('The Final Countdown' AS Optional<Utf8>),CAST(1278028800000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','9c73e47e-1e20-49a0-9285-7b08d0fd1b06',CAST('Jen The Fredo' AS Optional<Utf8>),CAST(1277424000000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','b123a783-ddfe-42b7-9593-c3160d68db68',CAST('Italian For Beginners' AS Optional<Utf8>),CAST(1279238400000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','169bce9d-70ca-4c3e-8cd0-c11dd704feca',CAST('Smoke and Mirrors' AS Optional<Utf8>),CAST(1190332800000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','2ff32dfb-1b2f-48cb-8366-2ddfdadbea69',CAST('The Work Outing' AS Optional<Utf8>),CAST(1156377600000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','4d59dbf3-53cb-4b4e-ba8c-a04b8679afcc',CAST('Moss and the German' AS Optional<Utf8>),CAST(1189123200000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','ad69fc17-9aff-4c80-b65e-06553cd7375a',CAST('Men Without Women' AS Optional<Utf8>),CAST(1190937600000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','af704207-8bba-4fdc-8bf0-331c8cdd8d4e',CAST('Return of the Golden Child' AS Optional<Utf8>),CAST(1188518400000000 AS Optional<Timestamp>),NULL),
|
||||
('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','d6f6b37f-a1f2-490f-a142-a9d8ecb65014',CAST('The Dinner Party' AS Optional<Utf8>),CAST(1189728000000000 AS Optional<Timestamp>),NULL);
|
||||
|
|
@ -0,0 +1,394 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Uuid string `xorm:"pk"`
|
||||
OperTime time.Time
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
tm := TestTime{
|
||||
Uuid: "datbeohbb",
|
||||
OperTime: time.Now().In(engine.GetTZLocation()),
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&tm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestTime
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, tm.OperTime.Unix(), ret.OperTime.Unix())
|
||||
assert.EqualValues(t, tm.OperTime.Format(time.RFC3339), ret.OperTime.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestTimeInDiffLoc(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Uuid string `xorm:"pk"`
|
||||
OperTime *time.Time
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
newTzLoc, err := time.LoadLocation("Europe/Berlin")
|
||||
assert.NoError(t, err)
|
||||
|
||||
newDbLoc, err := time.LoadLocation("America/New_York")
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldTzLoc := engine.GetTZLocation()
|
||||
oldDbLoc := engine.GetTZDatabase()
|
||||
|
||||
defer func() {
|
||||
engine.SetTZLocation(oldTzLoc)
|
||||
engine.SetTZDatabase(oldDbLoc)
|
||||
}()
|
||||
|
||||
engine.SetTZLocation(newTzLoc)
|
||||
engine.SetTZDatabase(newDbLoc)
|
||||
|
||||
now := time.Now().In(newTzLoc)
|
||||
tm := TestTime{
|
||||
Uuid: "datbeohbbh",
|
||||
OperTime: &now,
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&tm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestTime
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
assert.EqualValues(t, tm.OperTime.Unix(), ret.OperTime.Unix())
|
||||
assert.EqualValues(t, tm.OperTime.Format(time.RFC3339), ret.OperTime.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestTimeUserCreated(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Uuid string `xorm:"pk"`
|
||||
CreatedAt time.Time `xorm:"created"`
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
tm := TestTime{
|
||||
Uuid: "datbeohbbh",
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&tm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestTime
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
t.Log(":", tm.CreatedAt)
|
||||
t.Log(":", ret.CreatedAt)
|
||||
|
||||
assert.EqualValues(t, tm.CreatedAt.UnixMicro(), ret.CreatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm.CreatedAt.Format(time.RFC3339), ret.CreatedAt.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestTimeUserCreatedDiffLoc(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Uuid string `xorm:"pk"`
|
||||
CreatedAt time.Time `xorm:"created"`
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
newTzLoc, err := time.LoadLocation("Asia/Ho_Chi_Minh")
|
||||
assert.NoError(t, err)
|
||||
|
||||
newDbLoc, err := time.LoadLocation("Europe/Berlin")
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldTzLoc := engine.GetTZLocation()
|
||||
oldDbLoc := engine.GetTZDatabase()
|
||||
|
||||
defer func() {
|
||||
engine.SetTZLocation(oldTzLoc)
|
||||
engine.SetTZDatabase(oldDbLoc)
|
||||
}()
|
||||
|
||||
engine.SetTZLocation(newTzLoc)
|
||||
engine.SetTZDatabase(newDbLoc)
|
||||
|
||||
tm := TestTime{
|
||||
Uuid: "datbeohbbh",
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&tm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret := TestTime{}
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
t.Log(":", tm.CreatedAt)
|
||||
t.Log(":", ret.CreatedAt)
|
||||
|
||||
assert.EqualValues(t, tm.CreatedAt.UnixMicro(), ret.CreatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm.CreatedAt.Format(time.RFC3339), ret.CreatedAt.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestTimeUserUpdated(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Uuid string `xorm:"pk"`
|
||||
Count int64
|
||||
CreatedAt time.Time `xorm:"created"`
|
||||
UpdatedAt time.Time `xorm:"updated"`
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
tm := TestTime{
|
||||
Uuid: "datbeohbbh",
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&tm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestTime
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
t.Log("created 1:", tm.CreatedAt)
|
||||
t.Log("updated 1:", tm.UpdatedAt)
|
||||
t.Log("created 2:", ret.CreatedAt)
|
||||
t.Log("updated 2:", ret.UpdatedAt)
|
||||
|
||||
assert.EqualValues(t, tm.CreatedAt.UnixMicro(), ret.CreatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm.UpdatedAt.UnixMicro(), ret.UpdatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm.CreatedAt.Format(time.RFC3339), ret.CreatedAt.Format(time.RFC3339))
|
||||
assert.EqualValues(t, tm.UpdatedAt.Format(time.RFC3339), ret.UpdatedAt.Format(time.RFC3339))
|
||||
|
||||
tm2 := TestTime{
|
||||
CreatedAt: tm.CreatedAt,
|
||||
}
|
||||
_, err = engine.Incr("count", int64(1)).Update(&tm2, map[string]interface{}{"uuid": "datbeohbbh"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret = TestTime{}
|
||||
has, err = engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
assert.EqualValues(t, tm2.CreatedAt.UnixMicro(), ret.CreatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm2.UpdatedAt.UnixMicro(), ret.UpdatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm2.CreatedAt.Format(time.RFC3339), ret.CreatedAt.Format(time.RFC3339))
|
||||
assert.EqualValues(t, tm2.UpdatedAt.Format(time.RFC3339), ret.UpdatedAt.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestTimeUserUpdatedDiffLoc(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Uuid string `xorm:"pk"`
|
||||
Count int64
|
||||
CreatedAt time.Time `xorm:"created"`
|
||||
UpdatedAt time.Time `xorm:"updated"`
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
newTzLoc, err := time.LoadLocation("Europe/Moscow")
|
||||
assert.NoError(t, err)
|
||||
|
||||
newDbLoc, err := time.LoadLocation("Europe/Berlin")
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldTzLoc := engine.GetTZLocation()
|
||||
oldDbLoc := engine.GetTZDatabase()
|
||||
|
||||
defer func() {
|
||||
engine.SetTZLocation(oldTzLoc)
|
||||
engine.SetTZDatabase(oldDbLoc)
|
||||
}()
|
||||
|
||||
engine.SetTZLocation(newTzLoc)
|
||||
engine.SetTZDatabase(newDbLoc)
|
||||
|
||||
tm := TestTime{
|
||||
Uuid: "datbeohbbh",
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&tm)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret TestTime
|
||||
has, err := engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
t.Log("created 1:", tm.CreatedAt)
|
||||
t.Log("updated 1:", tm.UpdatedAt)
|
||||
t.Log("created 2:", ret.CreatedAt)
|
||||
t.Log("updated 2:", ret.UpdatedAt)
|
||||
|
||||
assert.EqualValues(t, tm.CreatedAt.UnixMicro(), ret.CreatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm.UpdatedAt.UnixMicro(), ret.UpdatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm.CreatedAt.Format(time.RFC3339), ret.CreatedAt.Format(time.RFC3339))
|
||||
assert.EqualValues(t, tm.UpdatedAt.Format(time.RFC3339), ret.UpdatedAt.Format(time.RFC3339))
|
||||
|
||||
tm2 := TestTime{
|
||||
CreatedAt: tm.CreatedAt,
|
||||
}
|
||||
_, err = engine.Incr("count", int64(1)).Update(&tm2, map[string]interface{}{"uuid": "datbeohbbh"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
ret = TestTime{}
|
||||
has, err = engine.Get(&ret)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
|
||||
assert.EqualValues(t, tm2.CreatedAt.UnixMicro(), ret.CreatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm2.UpdatedAt.UnixMicro(), ret.UpdatedAt.UnixMicro())
|
||||
assert.EqualValues(t, tm2.CreatedAt.Format(time.RFC3339), ret.CreatedAt.Format(time.RFC3339))
|
||||
assert.EqualValues(t, tm2.UpdatedAt.Format(time.RFC3339), ret.UpdatedAt.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
type JSONDate time.Time
|
||||
|
||||
func (j JSONDate) MarshalJSON() ([]byte, error) {
|
||||
if time.Time(j).IsZero() {
|
||||
return []byte(`""`), nil
|
||||
}
|
||||
return []byte(`"` + time.Time(j).Format("2006-01-02 15:04:05") + `"`), nil
|
||||
}
|
||||
|
||||
func (j *JSONDate) UnmarshalJSON(value []byte) error {
|
||||
var v = strings.TrimSpace(strings.Trim(string(value), "\""))
|
||||
|
||||
t, err := time.ParseInLocation("2006-01-02 15:04:05", v, time.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*j = JSONDate(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *JSONDate) Unix() int64 {
|
||||
return (*time.Time)(j).Unix()
|
||||
}
|
||||
|
||||
func TestCustomTimeUser(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Id string `xorm:"pk"`
|
||||
CreatedAt JSONDate `xorm:"created"`
|
||||
UpdatedAt JSONDate `xorm:"updated"`
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var user = TestTime{
|
||||
Id: "datbeohbbh",
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&user)
|
||||
assert.NoError(t, err)
|
||||
t.Log("user", user.CreatedAt, user.UpdatedAt)
|
||||
|
||||
var user2 TestTime
|
||||
has, err := engine.Get(&user2)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix())
|
||||
assert.EqualValues(t, time.Time(user.CreatedAt).Format(time.RFC3339), time.Time(user2.CreatedAt).Format(time.RFC3339))
|
||||
assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix())
|
||||
assert.EqualValues(t, time.Time(user.UpdatedAt).Format(time.RFC3339), time.Time(user2.UpdatedAt).Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestFindTimeDiffLoc(t *testing.T) {
|
||||
type TestTime struct {
|
||||
Uuid string `xorm:"pk 'uuid'"`
|
||||
OperTime time.Time `xorm:"'oper_time'"`
|
||||
}
|
||||
assert.NoError(t, PrepareScheme(&TestTime{}))
|
||||
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
newTzLoc, err := time.LoadLocation("America/New_York")
|
||||
assert.NoError(t, err)
|
||||
|
||||
newDbLoc, err := time.LoadLocation("Europe/Berlin")
|
||||
assert.NoError(t, err)
|
||||
|
||||
oldTzLoc := engine.GetTZLocation()
|
||||
oldDbLoc := engine.GetTZDatabase()
|
||||
|
||||
defer func() {
|
||||
engine.SetTZLocation(oldTzLoc)
|
||||
engine.SetTZDatabase(oldDbLoc)
|
||||
}()
|
||||
|
||||
engine.SetTZLocation(newTzLoc)
|
||||
engine.SetTZDatabase(newDbLoc)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
var (
|
||||
now = time.Now().In(newTzLoc)
|
||||
expected = make([]TestTime, 0)
|
||||
actual = make([]TestTime, 0)
|
||||
)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
now = now.Add(time.Minute).In(newTzLoc)
|
||||
data := TestTime{
|
||||
Uuid: fmt.Sprintf("%d", i),
|
||||
OperTime: now,
|
||||
}
|
||||
_, err = session.Insert(&data)
|
||||
assert.NoError(t, err)
|
||||
expected = append(expected, data)
|
||||
}
|
||||
|
||||
err = session.Table(&TestTime{}).Asc("oper_time").Find(&actual)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(expected), len(actual))
|
||||
|
||||
for i, e := range expected {
|
||||
assert.EqualValues(t, e.OperTime.Unix(), actual[i].OperTime.Unix())
|
||||
assert.EqualValues(t, e.OperTime.Format(time.RFC3339), actual[i].OperTime.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
t.Log(expected)
|
||||
t.Log(actual)
|
||||
}
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type NullStruct struct {
|
||||
Id int64 `xorm:"pk"`
|
||||
Name sql.NullString
|
||||
Age sql.NullInt64
|
||||
Height sql.NullFloat64
|
||||
IsMan sql.NullBool `xorm:"null"`
|
||||
Nil driver.Valuer
|
||||
CustomStruct CustomStruct `xorm:"VARCHAR null"`
|
||||
}
|
||||
|
||||
type CustomStruct struct {
|
||||
Year int64
|
||||
Month int64
|
||||
Day int64
|
||||
}
|
||||
|
||||
func (CustomStruct) String() string {
|
||||
return "CustomStruct"
|
||||
}
|
||||
|
||||
func (m *CustomStruct) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
m.Year, m.Month, m.Day = 0, 0, 0
|
||||
return nil
|
||||
}
|
||||
|
||||
var s string
|
||||
switch t := value.(type) {
|
||||
case string:
|
||||
s = t
|
||||
case []byte:
|
||||
s = string(t)
|
||||
}
|
||||
if len(s) > 0 {
|
||||
seps := strings.Split(s, "/")
|
||||
Y, _ := strconv.Atoi(seps[0])
|
||||
M, _ := strconv.Atoi(seps[1])
|
||||
D, _ := strconv.Atoi(seps[2])
|
||||
m.Year = int64(Y)
|
||||
m.Month = int64(M)
|
||||
m.Day = int64(D)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("scan data %#v not fit []byte", value)
|
||||
}
|
||||
|
||||
func (m CustomStruct) Value() (driver.Value, error) {
|
||||
return fmt.Sprintf("%d/%d/%d", m.Year, m.Month, m.Day), nil
|
||||
}
|
||||
|
||||
func TestCreateNullStructTable(t *testing.T) {
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, engine.NewSession().DropTable(&NullStruct{}))
|
||||
assert.NoError(t, engine.NewSession().CreateTable(&NullStruct{}))
|
||||
}
|
||||
|
||||
func TestDropNullStructTable(t *testing.T) {
|
||||
engine, err := enginePool.GetSchemeQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, engine.NewSession().DropTable(&NullStruct{}))
|
||||
}
|
||||
|
||||
func TestNullStructInsert(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&NullStruct{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
items := []NullStruct{}
|
||||
for i := 0; i < 5; i++ {
|
||||
item := NullStruct{
|
||||
Id: int64(i),
|
||||
Name: sql.NullString{String: "haolei_" + fmt.Sprint(i+1), Valid: true},
|
||||
Age: sql.NullInt64{Int64: 30 + int64(i), Valid: true},
|
||||
Height: sql.NullFloat64{Float64: 1.5 + 1.1*float64(i), Valid: true},
|
||||
IsMan: sql.NullBool{Bool: true, Valid: true},
|
||||
CustomStruct: CustomStruct{int64(i), int64(i + 1), int64(i + 2)},
|
||||
Nil: nil,
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&items)
|
||||
assert.NoError(t, err)
|
||||
|
||||
items = make([]NullStruct, 0)
|
||||
err = engine.Find(&items)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 5, len(items))
|
||||
}
|
||||
|
||||
// FIXME
|
||||
func TestNullStructUpdate(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&NullStruct{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.InsertOne(NullStruct{
|
||||
Id: int64(1),
|
||||
Name: sql.NullString{
|
||||
String: "name1",
|
||||
Valid: false,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Insert([]NullStruct{
|
||||
{
|
||||
Id: int64(2),
|
||||
Name: sql.NullString{
|
||||
String: "name2",
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: int64(3),
|
||||
Name: sql.NullString{
|
||||
String: "name3",
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: int64(4),
|
||||
Name: sql.NullString{
|
||||
String: "name4",
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
if true { // 测试可插入NULL
|
||||
item := new(NullStruct)
|
||||
item.Age = sql.NullInt64{Int64: 23, Valid: true}
|
||||
item.Height = sql.NullFloat64{Float64: 0, Valid: true} // update to NULL
|
||||
|
||||
_, err := engine.ID(int64(2)).Cols("age", "height", "is_man").Update(item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if true { // 测试In update
|
||||
item := new(NullStruct)
|
||||
item.Age = sql.NullInt64{Int64: 23, Valid: true}
|
||||
_, err := engine.In("id", int64(3), int64(4)).Cols("age", "height", "is_man").Update(item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if true { // 测试where
|
||||
item := new(NullStruct)
|
||||
item.Name = sql.NullString{String: "nullname", Valid: true}
|
||||
item.IsMan = sql.NullBool{Bool: true, Valid: true}
|
||||
item.Age = sql.NullInt64{Int64: 34, Valid: true}
|
||||
|
||||
_, err := engine.Where("`age` > ?", int64(34)).Update(item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if true { // 修改全部时,插入空值
|
||||
// !datbeohbbh! YDB: if session.statement.ColumnStr() == ""
|
||||
// the 'arg' is inferred as <nil>, so can not correctly generate 'DECLARE' section
|
||||
t.Skipf("FIXME")
|
||||
item := &NullStruct{
|
||||
Name: sql.NullString{String: "winxxp", Valid: true},
|
||||
Age: sql.NullInt64{Int64: 30, Valid: true},
|
||||
Height: sql.NullFloat64{Float64: 1.72, Valid: true},
|
||||
}
|
||||
|
||||
log.Println("BEGIN")
|
||||
_, err := engine.AllCols().Omit("id").ID(int64(6)).Update(item)
|
||||
log.Println("END")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullStructFind(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&NullStruct{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.InsertOne(NullStruct{
|
||||
Id: int64(1),
|
||||
Name: sql.NullString{
|
||||
String: "name1",
|
||||
Valid: false,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Insert([]NullStruct{
|
||||
{
|
||||
Id: int64(2),
|
||||
Name: sql.NullString{
|
||||
String: "name2",
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: int64(3),
|
||||
Name: sql.NullString{
|
||||
String: "name3",
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: int64(4),
|
||||
Name: sql.NullString{
|
||||
String: "name4",
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
if true {
|
||||
item := new(NullStruct)
|
||||
has, err := engine.ID(int64(1)).Get(item)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, 1, item.Id)
|
||||
assert.False(t, item.Name.Valid)
|
||||
assert.False(t, item.Age.Valid)
|
||||
assert.False(t, item.Height.Valid)
|
||||
assert.False(t, item.IsMan.Valid)
|
||||
}
|
||||
|
||||
if true {
|
||||
item := new(NullStruct)
|
||||
item.Id = int64(2)
|
||||
has, err := engine.Get(item)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
}
|
||||
|
||||
if true {
|
||||
item := make([]NullStruct, 0)
|
||||
err := engine.ID(int64(2)).Find(&item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if true {
|
||||
item := make([]NullStruct, 0)
|
||||
err := engine.Asc("age").Find(&item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullStructIterate(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
if true {
|
||||
err := engine.Where("`age` IS NOT NULL").OrderBy("age").Iterate(new(NullStruct),
|
||||
func(i int, bean interface{}) error {
|
||||
nultype := bean.(*NullStruct)
|
||||
fmt.Println(i, nultype)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullStructCount(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
if true {
|
||||
item := new(NullStruct)
|
||||
_, err := engine.Where("`age` IS NOT NULL").Count(item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullStructRows(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
item := new(NullStruct)
|
||||
rows, err := engine.Where("`id` > ?", int64(1)).Rows(item)
|
||||
assert.NoError(t, err)
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
err = rows.Scan(item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullStructDelete(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
|
||||
item := new(NullStruct)
|
||||
|
||||
_, err = engine.ID(int64(1)).Delete(item)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.Where("`id` > ?", int64(1)).Delete(item)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
package ydb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/convert"
|
||||
)
|
||||
|
||||
type Models struct {
|
||||
Log *LogEntry `xorm:"BLOB"`
|
||||
ModelID string `xorm:"pk 'model_id'" json:"model_id"`
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
LogID string `json:"log_id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
CreatedAt time.Time `json:"create_at"`
|
||||
UpdateAt time.Time `json:"update_at"`
|
||||
}
|
||||
|
||||
func (l *LogEntry) FromDB(data []byte) error {
|
||||
if data == nil {
|
||||
l = nil
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, l)
|
||||
}
|
||||
|
||||
func (l *LogEntry) ToDB() ([]byte, error) {
|
||||
if l == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return json.MarshalIndent(l, "\t", "")
|
||||
}
|
||||
|
||||
func TestConversionModels(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Models{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
m := Models{
|
||||
ModelID: "model_abc",
|
||||
Log: &LogEntry{
|
||||
LogID: "log_abc",
|
||||
Name: "abc",
|
||||
Data: "xyz",
|
||||
CreatedAt: time.Now(),
|
||||
UpdateAt: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err = engine.Insert(&m)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var ret Models
|
||||
has, err := engine.Get(&ret)
|
||||
assert.True(t, has)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, m.ModelID, ret.ModelID)
|
||||
assert.EqualValues(t, m.Log.LogID, ret.Log.LogID)
|
||||
assert.EqualValues(t, m.Log.Name, ret.Log.Name)
|
||||
assert.EqualValues(t, m.Log.Data, ret.Log.Data)
|
||||
assert.EqualValues(t, m.Log.CreatedAt.Format(time.RFC3339), ret.Log.CreatedAt.Format(time.RFC3339))
|
||||
assert.EqualValues(t, m.Log.UpdateAt.Format(time.RFC3339), ret.Log.UpdateAt.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
func TestConversionModelsCond(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Models{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
m := make([]*Models, 0)
|
||||
for i := 0; i <= 10; i++ {
|
||||
m = append(m, &Models{
|
||||
ModelID: fmt.Sprintf("%d", i),
|
||||
Log: &LogEntry{
|
||||
LogID: fmt.Sprintf("log - %d", i),
|
||||
Name: fmt.Sprintf("%d", i),
|
||||
Data: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_, err = engine.Insert(m)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res := make([]*Models, 0)
|
||||
err = engine.
|
||||
In("model_id", []string{"0", "1", "2", "3", "4", "5"}).
|
||||
Find(&res)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, m[:6], res)
|
||||
}
|
||||
|
||||
func TestConversionModelsUpdate(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&Models{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
m := make([]*Models, 0)
|
||||
for i := 0; i <= 10; i++ {
|
||||
m = append(m, &Models{
|
||||
ModelID: fmt.Sprintf("%d", i),
|
||||
Log: &LogEntry{
|
||||
LogID: fmt.Sprintf("log - %d", i),
|
||||
Name: fmt.Sprintf("%d", i),
|
||||
Data: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_, err = engine.Insert(m)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = engine.
|
||||
In("model_id", []string{"0", "1", "2", "3", "4", "5"}).
|
||||
Update(&Models{
|
||||
Log: &LogEntry{
|
||||
LogID: fmt.Sprintf("log - %d", 2023),
|
||||
Name: fmt.Sprintf("%d", 2023),
|
||||
Data: fmt.Sprintf("%d", 2023),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
res := make([]*Models, 0)
|
||||
err = engine.
|
||||
In("model_id", []string{"0", "1", "2", "3", "4", "5"}).
|
||||
Find(&res)
|
||||
assert.NoError(t, err)
|
||||
for i := 0; i < 5; i++ {
|
||||
assert.EqualValues(t, &LogEntry{
|
||||
LogID: fmt.Sprintf("log - %d", 2023),
|
||||
Name: fmt.Sprintf("%d", 2023),
|
||||
Data: fmt.Sprintf("%d", 2023),
|
||||
}, res[i].Log)
|
||||
}
|
||||
}
|
||||
|
||||
type MyDecimal big.Int
|
||||
|
||||
func (d *MyDecimal) FromDB(data []byte) error {
|
||||
i, _ := strconv.ParseInt(string(data), 10, 64)
|
||||
if d == nil {
|
||||
d = (*MyDecimal)(big.NewInt(i))
|
||||
} else {
|
||||
(*big.Int)(d).SetInt64(i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MyDecimal) ToDB() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%d", (*big.Int)(d).Int64())), nil
|
||||
}
|
||||
|
||||
func (d *MyDecimal) AsBigInt() *big.Int {
|
||||
return (*big.Int)(d)
|
||||
}
|
||||
|
||||
func (d *MyDecimal) AsInt64() int64 {
|
||||
return d.AsBigInt().Int64()
|
||||
}
|
||||
|
||||
func TestDecimal(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
type MyMoney struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Account *MyDecimal
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&MyMoney{}))
|
||||
|
||||
_, err = engine.Insert(&MyMoney{
|
||||
Account: (*MyDecimal)(big.NewInt(10000000000000000)),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var m MyMoney
|
||||
has, err := engine.Get(&m)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.NotNil(t, m.Account)
|
||||
assert.EqualValues(t, 10000000000000000, m.Account.AsInt64())
|
||||
}
|
||||
|
||||
type MyArray [20]byte
|
||||
|
||||
func (d *MyArray) FromDB(data []byte) error {
|
||||
for i, b := range data[:20] {
|
||||
(*d)[i] = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d MyArray) ToDB() ([]byte, error) {
|
||||
return d[:], nil
|
||||
}
|
||||
|
||||
func TestMyArray(t *testing.T) {
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
type MyArrayStruct struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Content MyArray
|
||||
}
|
||||
|
||||
assert.NoError(t, PrepareScheme(&MyArrayStruct{}))
|
||||
|
||||
v := [20]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
_, err = engine.Insert(&MyArrayStruct{
|
||||
Content: v,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var m MyArrayStruct
|
||||
has, err := engine.Get(&m)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.EqualValues(t, v, m.Content)
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Name string
|
||||
Color string
|
||||
}
|
||||
|
||||
var (
|
||||
_ convert.Conversion = &Status{}
|
||||
Registered = Status{"Registered", "white"}
|
||||
Approved = Status{"Approved", "green"}
|
||||
Removed = Status{"Removed", "red"}
|
||||
Statuses = map[string]Status{
|
||||
Registered.Name: Registered,
|
||||
Approved.Name: Approved,
|
||||
Removed.Name: Removed,
|
||||
}
|
||||
)
|
||||
|
||||
func (s *Status) FromDB(bytes []byte) error {
|
||||
if r, ok := Statuses[string(bytes)]; ok {
|
||||
*s = r
|
||||
return nil
|
||||
}
|
||||
return errors.New("no this data")
|
||||
}
|
||||
|
||||
func (s *Status) ToDB() ([]byte, error) {
|
||||
return []byte(s.Name), nil
|
||||
}
|
||||
|
||||
type UserCus struct {
|
||||
Uuid int64 `xorm:"pk"`
|
||||
Name string
|
||||
Status Status `xorm:"VARCHAR"`
|
||||
}
|
||||
|
||||
func TestCustomType2(t *testing.T) {
|
||||
assert.NoError(t, PrepareScheme(&UserCus{}))
|
||||
engine, err := enginePool.GetDataQueryEngine()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, engine)
|
||||
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
_, err = session.Insert(&UserCus{int64(1), "xlw", Registered})
|
||||
assert.NoError(t, err)
|
||||
|
||||
user := UserCus{}
|
||||
exist, err := engine.ID(int64(1)).Get(&user)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exist)
|
||||
|
||||
users := make([]UserCus, 0)
|
||||
err = engine.Where("`"+engine.GetColumnMapper().Obj2Table("Status")+"` = ?", "Registered").Find(&users)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, len(users))
|
||||
}
|
||||
Loading…
Reference in New Issue