diff --git a/.gitea/workflows/test-ydb.yml b/.gitea/workflows/test-ydb.yml new file mode 100644 index 00000000..f69daae4 --- /dev/null +++ b/.gitea/workflows/test-ydb.yml @@ -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 \ No newline at end of file diff --git a/Makefile b/Makefile index 55183557..bbb86146 100644 --- a/Makefile +++ b/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 ./...) diff --git a/README.md b/README.md index 1348f4f8..31fd6574 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/convert/interface.go b/convert/interface.go index 2cc8d9f4..0a4ae240 100644 --- a/convert/interface.go +++ b/convert/interface.go @@ -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) } diff --git a/convert/time.go b/convert/time.go index d90dc428..73483359 100644 --- a/convert/time.go +++ b/convert/time.go @@ -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 +} diff --git a/dialects/dialect.go b/dialects/dialect.go index 8e512c4f..4ada55b4 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -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{} }}, } diff --git a/dialects/filter.go b/dialects/filter.go index ab7c65b6..38a88c2e 100644 --- a/dialects/filter.go +++ b/dialects/filter.go @@ -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) +} diff --git a/dialects/time.go b/dialects/time.go index cdc896be..843c8daf 100644 --- a/dialects/time.go +++ b/dialects/time.go @@ -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 diff --git a/dialects/ydb.go b/dialects/ydb.go new file mode 100644 index 00000000..e9b99a1b --- /dev/null +++ b/dialects/ydb.go @@ -0,0 +1,1189 @@ +package dialects + +import ( + "context" + "database/sql" + "errors" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "xorm.io/xorm/convert" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" +) + +// from https://github.com/ydb-platform/ydb/blob/main/ydb/library/yql/sql/v1/SQLv1.g.in#L1117 +var ( + ydbReservedWords = map[string]bool{ + "ABORT": true, + "ACTION": true, + "ADD": true, + "AFTER": true, + "ALL": true, + "ALTER": true, + "ANALYZE": true, + "AND": true, + "ANSI": true, + "ANY": true, + "ARRAY": true, + "AS": true, + "ASC": true, + "ASSUME": true, + "ASYNC": true, + "ATTACH": true, + "AUTOINCREMENT": true, + "AUTOMAP": true, + "BEFORE": true, + "BEGIN": true, + "BERNOULLI": true, + "BETWEEN": true, + "BITCAST": true, + "BY": true, + "CALLABLE": true, + "CASCADE": true, + "CASE": true, + "CAST": true, + "CHANGEFEED": true, + "CHECK": true, + "COLLATE": true, + "COLUMN": true, + "COLUMNS": true, + "COMMIT": true, + "COMPACT": true, + "CONDITIONAL": true, + "CONFLICT": true, + "CONSTRAINT": true, + "COVER": true, + "CREATE": true, + "CROSS": true, + "CUBE": true, + "CURRENT": true, + "CURRENT_TIME": true, + "CURRENT_DATE": true, + "CURRENT_TIMESTAMP": true, + "DATABASE": true, + "DECIMAL": true, + "DECLARE": true, + "DEFAULT": true, + "DEFERRABLE": true, + "DEFERRED": true, + "DEFINE": true, + "DELETE": true, + "DESC": true, + "DETACH": true, + "DICT": true, + "DISABLE": true, + "DISCARD": true, + "DISTINCT": true, + "DO": true, + "DROP": true, + "EACH": true, + "ELSE": true, + "ERROR": true, + "EMPTY": true, + "EMPTY_ACTION": true, + "ENCRYPTED": true, + "END": true, + "ENUM": true, + "ERASE": true, + "ESCAPE": true, + "EVALUATE": true, + "EXCEPT": true, + "EXCLUDE": true, + "EXCLUSIVE": true, + "EXCLUSION": true, + "EXISTS": true, + "EXPLAIN": true, + "EXPORT": true, + "EXTERNAL": true, + "FAIL": true, + "FAMILY": true, + "FILTER": true, + "FLATTEN": true, + "FLOW": true, + "FOLLOWING": true, + "FOR": true, + "FOREIGN": true, + "FROM": true, + "FULL": true, + "FUNCTION": true, + "GLOB": true, + "GLOBAL": true, + "GROUP": true, + "GROUPING": true, + "GROUPS": true, + "HASH": true, + "HAVING": true, + "HOP": true, + "IF": true, + "IGNORE": true, + "ILIKE": true, + "IMMEDIATE": true, + "IMPORT": true, + "IN": true, + "INDEX": true, + "INDEXED": true, + "INHERITS": true, + "INITIALLY": true, + "INNER": true, + "INSERT": true, + "INSTEAD": true, + "INTERSECT": true, + "INTO": true, + "IS": true, + "ISNULL": true, + "JOIN": true, + "JSON_EXISTS": true, + "JSON_VALUE": true, + "JSON_QUERY": true, + "KEY": true, + "LEFT": true, + "LIKE": true, + "LIMIT": true, + "LIST": true, + "LOCAL": true, + "MATCH": true, + "NATURAL": true, + "NO": true, + "NOT": true, + "NOTNULL": true, + "NULL": true, + "NULLS": true, + "OBJECT": true, + "OF": true, + "OFFSET": true, + "ON": true, + "ONLY": true, + "OPTIONAL": true, + "OR": true, + "ORDER": true, + "OTHERS": true, + "OUTER": true, + "OVER": true, + "PARTITION": true, + "PASSING": true, + "PASSWORD": true, + "PLAN": true, + "PRAGMA": true, + "PRECEDING": true, + "PRESORT": true, + "PRIMARY": true, + "PROCESS": true, + "RAISE": true, + "RANGE": true, + "REDUCE": true, + "REFERENCES": true, + "REGEXP": true, + "REINDEX": true, + "RELEASE": true, + "RENAME": true, + "REPEATABLE": true, + "REPLACE": true, + "RESET": true, + "RESOURCE": true, + "RESPECT": true, + "RESTRICT": true, + "RESULT": true, + "RETURN": true, + "RETURNING": true, + "REVERT": true, + "RIGHT": true, + "RLIKE": true, + "ROLLBACK": true, + "ROLLUP": true, + "ROW": true, + "ROWS": true, + "SAMPLE": true, + "SAVEPOINT": true, + "SCHEMA": true, + "SELECT": true, + "SEMI": true, + "SET": true, + "SETS": true, + "STREAM": true, + "STRUCT": true, + "SUBQUERY": true, + "SYMBOLS": true, + "SYNC": true, + "SYSTEM": true, + "TABLE": true, + "TABLESAMPLE": true, + "TABLESTORE": true, + "TAGGED": true, + "TEMP": true, + "TEMPORARY": true, + "THEN": true, + "TIES": true, + "TO": true, + "TRANSACTION": true, + "TRIGGER": true, + "TUPLE": true, + "UNBOUNDED": true, + "UNCONDITIONAL": true, + "UNION": true, + "UNIQUE": true, + "UNKNOWN": true, + "UPDATE": true, + "UPSERT": true, + "USE": true, + "USER": true, + "USING": true, + "VACUUM": true, + "VALUES": true, + "VARIANT": true, + "VIEW": true, + "VIRTUAL": true, + "WHEN": true, + "WHERE": true, + "WINDOW": true, + "WITH": true, + "WITHOUT": true, + "WRAPPER": true, + "XOR": true, + "TRUE": true, + "FALSE": true, + } + + ydbQuoter = schemas.Quoter{ + Prefix: '`', + Suffix: '`', + IsReserved: schemas.AlwaysReserve, + } +) + +const ( + // numeric types + yql_Bool = "BOOL" + + yql_Int8 = "INT8" + yql_Int16 = "INT16" + yql_Int32 = "INT32" + yql_Int64 = "INT64" + + yql_Uint8 = "UINT8" + yql_Uint16 = "UINT16" + yql_Uint32 = "UINT32" + yql_Uint64 = "UINT64" + + yql_Float = "FLOAT" + yql_Double = "DOUBLE" + yql_Decimal = "DECIMAL" + + // string types + yql_String = "STRING" + yql_Utf8 = "UTF8" + yql_Json = "JSON" + yql_JsonDocument = "JSONDOCUMENT" + yql_Yson = "YSON" + + // Data and Time + yql_Date = "DATE" + yql_DateTime = "DATETIME" + yql_Timestamp = "TIMESTAMP" + yql_Interval = "INTERVAL" + + // Containers + yql_List = "LIST" +) + +func toYQLDataType(t string, defaultLength, defaultLength2 int64) (yqlType string) { + switch v := t; v { + case schemas.Bool, schemas.Boolean: + yqlType = yql_Bool + return + case schemas.TinyInt: + yqlType = yql_Int8 + return + case schemas.UnsignedTinyInt: + yqlType = yql_Uint8 + return + case schemas.SmallInt: + yqlType = yql_Int16 + return + case schemas.UnsignedSmallInt: + yqlType = yql_Uint16 + return + case schemas.MediumInt: + yqlType = yql_Int32 + return + case schemas.UnsignedMediumInt: + yqlType = yql_Uint32 + return + case schemas.BigInt: + yqlType = yql_Int64 + return + case schemas.UnsignedBigInt: + yqlType = yql_Uint64 + return + case schemas.Int, schemas.Integer: + yqlType = yql_Int32 + return + case schemas.UnsignedInt: + yqlType = yql_Uint32 + return + case schemas.Float: + yqlType = yql_Float + return + case schemas.Double: + yqlType = yql_Double + return + case schemas.Blob: + yqlType = yql_String + return + case schemas.Json: + yqlType = yql_Json + return + case schemas.Array: + yqlType = yql_List + return + case schemas.Varchar, schemas.Text: + yqlType = yql_Utf8 + return + case schemas.TimeStamp, schemas.DateTime: + yqlType = yql_Timestamp + return + case schemas.Interval: + yqlType = yql_Interval + return + default: + yqlType = yql_String + } + return +} + +func yqlToSQLType(yqlType string) (sqlType schemas.SQLType) { + switch yqlType { + case yql_Bool: + sqlType = schemas.SQLType{Name: schemas.Bool, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Int8: + sqlType = schemas.SQLType{Name: schemas.TinyInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Uint8: + sqlType = schemas.SQLType{Name: schemas.UnsignedTinyInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Int16: + sqlType = schemas.SQLType{Name: schemas.SmallInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Uint16: + sqlType = schemas.SQLType{Name: schemas.UnsignedSmallInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Int32: + sqlType = schemas.SQLType{Name: schemas.MediumInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Uint32: + sqlType = schemas.SQLType{Name: schemas.UnsignedMediumInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Int64: + sqlType = schemas.SQLType{Name: schemas.BigInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Uint64: + sqlType = schemas.SQLType{Name: schemas.UnsignedBigInt, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Float: + sqlType = schemas.SQLType{Name: schemas.Float, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Double: + sqlType = schemas.SQLType{Name: schemas.Double, DefaultLength: 0, DefaultLength2: 0} + return + case yql_String: + sqlType = schemas.SQLType{Name: schemas.Blob, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Json: + sqlType = schemas.SQLType{Name: schemas.Json, DefaultLength: 0, DefaultLength2: 0} + return + case yql_List: + sqlType = schemas.SQLType{Name: schemas.Array, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Utf8: + sqlType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 255, DefaultLength2: 0} + return + case yql_Timestamp: + sqlType = schemas.SQLType{Name: schemas.TimeStamp, DefaultLength: 0, DefaultLength2: 0} + return + case yql_Interval: + sqlType = schemas.SQLType{Name: schemas.Interval, DefaultLength: 0, DefaultLength2: 0} + return + default: + sqlType = schemas.SQLType{Name: schemas.Blob, DefaultLength: 0, DefaultLength2: 0} + } + return +} + +func removeOptional(s string) string { + if s = strings.ToUpper(s); strings.HasPrefix(s, "OPTIONAL") { + s = strings.TrimPrefix(s, "OPTIONAL<") + s = strings.TrimSuffix(s, ">") + } + return s +} + +type ydb struct { + Base + + tableParams map[string]string +} + +func (db *ydb) Init(uri *URI) error { + db.quoter = ydbQuoter + return db.Base.Init(db, uri) +} + +func (db *ydb) getDB(queryer interface{}) *core.DB { + if internalDB, ok := queryer.(*core.DB); ok { + return internalDB + } + return nil +} + +func (db *ydb) WithConn(queryer core.Queryer, ctx context.Context, f func(context.Context, *sql.Conn) error) error { + coreDB := db.getDB(queryer) + if coreDB == nil { + return fmt.Errorf("`*core.DB` not found") + } + + cc, err := coreDB.Conn(ctx) + if err != nil { + return err + } + defer cc.Close() + + err = f(ctx, cc) + + return err +} + +func (db *ydb) WithConnRaw(queryer core.Queryer, ctx context.Context, f func(d interface{}) error) (err error) { + err = db.WithConn(queryer, ctx, func(ctx context.Context, cc *sql.Conn) error { + err = cc.Raw(f) + return err + }) + return err +} + +func (db *ydb) SetParams(tableParams map[string]string) { + db.tableParams = tableParams +} + +func (db *ydb) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: -1, + } +} + +// unsupported feature +func (db *ydb) IsSequenceExist(_ context.Context, _ core.Queryer, _ string) (bool, error) { + return false, nil +} + +func (db *ydb) AutoIncrStr() string { + return "" +} + +func (db *ydb) IsReserved(name string) bool { + _, ok := ydbReservedWords[strings.ToUpper(name)] + return ok +} + +func (db *ydb) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + q := ydbQuoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + q := ydbQuoter + q.IsReserved = db.IsReserved + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = ydbQuoter + } +} + +func (db *ydb) SQLType(column *schemas.Column) string { + return toYQLDataType(column.SQLType.Name, column.SQLType.DefaultLength, column.SQLType.DefaultLength2) +} + +// https://pkg.go.dev/database/sql#ColumnType.DatabaseTypeName +func (db *ydb) ColumnTypeKind(t string) int { + switch t { + case "BOOL": + return schemas.BOOL_TYPE + case "INT8", "INT16", "INT32", "INT64", "UINT8", "UINT16", "UINT32", "UINT64": + return schemas.NUMERIC_TYPE + case "UTF8": + return schemas.TEXT_TYPE + case "TIMESTAMP": + return schemas.TIME_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + +func (db *ydb) Version(ctx context.Context, queryer core.Queryer) (_ *schemas.Version, err error) { + var version string + err = db.WithConnRaw(queryer, ctx, func(dc interface{}) error { + q, ok := dc.(interface { + Version(ctx context.Context) (string, error) + }) + if !ok { + return fmt.Errorf("driver does not support query metadata") + } + version, err = q.Version(ctx) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + + return &schemas.Version{ + Edition: version, + }, nil +} + +func (db *ydb) IndexCheckSQL(tableName, indexName string) (string, []interface{}) { + return "", nil +} + +func (db *ydb) IsTableExist( + queryer core.Queryer, + ctx context.Context, + tableName string) (_ bool, err error) { + var exists bool + err = db.WithConnRaw(queryer, ctx, func(dc interface{}) error { + q, ok := dc.(interface { + IsTableExists(context.Context, string) (bool, error) + }) + if !ok { + return fmt.Errorf("driver does not support query metadata") + } + exists, err = q.IsTableExists(ctx, tableName) + if err != nil { + return err + } + return nil + }) + + if err != nil { + return false, err + } + return exists, nil +} + +func (db *ydb) AddColumnSQL(tableName string, col *schemas.Column) string { + quote := db.dialect.Quoter() + tableName = quote.Quote(tableName) + columnName := quote.Quote(col.Name) + dataType := db.SQLType(col) + + var buf strings.Builder + buf.WriteString(fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s;", tableName, columnName, dataType)) + + return buf.String() +} + +// YDB does not support this operation +func (db *ydb) ModifyColumnSQL(tableName string, column *schemas.Column) string { + return "" +} + +// SYNC by default +func (db *ydb) CreateIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quoter() + tableName = quote.Quote(tableName) + indexName := quote.Quote(index.Name) + + colsIndex := make([]string, len(index.Cols)) + for i := 0; i < len(index.Cols); i++ { + colsIndex[i] = quote.Quote(index.Cols[i]) + } + + indexOn := strings.Join(colsIndex, ",") + + var buf strings.Builder + buf.WriteString(fmt.Sprintf("ALTER TABLE %s ADD INDEX %s GLOBAL ON ( %s );", tableName, indexName, indexOn)) + + return buf.String() +} + +func (db *ydb) DropIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quoter() + tableName = quote.Quote(tableName) + indexName := quote.Quote(index.Name) + + var buf strings.Builder + buf.WriteString(fmt.Sprintf("ALTER TABLE %s DROP INDEX %s;", tableName, indexName)) + + return buf.String() +} + +func (db *ydb) IsColumnExist( + queryer core.Queryer, + ctx context.Context, + tableName, + columnName string) (_ bool, err error) { + var exists bool + err = db.WithConnRaw(queryer, ctx, func(dc interface{}) error { + q, ok := dc.(interface { + IsColumnExists(context.Context, string, string) (bool, error) + }) + if !ok { + return fmt.Errorf("driver does not support query metadata") + } + exists, err = q.IsColumnExists(ctx, tableName, columnName) + if err != nil { + return err + } + return nil + }) + + if err != nil { + return false, err + } + return exists, nil +} + +func (db *ydb) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ( + _ []string, + _ map[string]*schemas.Column, + err error) { + colNames := make([]string, 0) + colMaps := make(map[string]*schemas.Column) + + err = db.WithConnRaw(queryer, ctx, func(dc interface{}) error { + q, ok := dc.(interface { + GetColumns(context.Context, string) ([]string, error) + GetColumnType(context.Context, string, string) (string, error) + IsPrimaryKey(context.Context, string, string) (bool, error) + }) + if !ok { + return fmt.Errorf("driver does not support query metadata") + } + + colNames, err = q.GetColumns(ctx, tableName) + if err != nil { + return err + } + + for _, colName := range colNames { + dataType, err := q.GetColumnType(ctx, tableName, colName) + if err != nil { + return err + } + dataType = removeOptional(dataType) + isPK, err := q.IsPrimaryKey(ctx, tableName, colName) + if err != nil { + return err + } + col := &schemas.Column{ + Name: colName, + TableName: tableName, + SQLType: yqlToSQLType(dataType), + IsPrimaryKey: isPK, + Nullable: !isPK, + Indexes: make(map[string]int), + } + colMaps[colName] = col + } + return nil + }) + if err != nil { + return nil, nil, err + } + return colNames, colMaps, nil +} + +func (db *ydb) GetTables(queryer core.Queryer, ctx context.Context) (_ []*schemas.Table, err error) { + tables := make([]*schemas.Table, 0) + err = db.WithConnRaw(queryer, ctx, func(dc interface{}) error { + q, ok := dc.(interface { + GetTables(context.Context, string, bool, bool) ([]string, error) + }) + if !ok { + return fmt.Errorf("driver does not support query metadata") + } + tableNames, err := q.GetTables(ctx, ".", true, true) + if err != nil { + return err + } + for _, tableName := range tableNames { + table := schemas.NewEmptyTable() + table.Name = tableName + tables = append(tables, table) + } + return nil + }) + if err != nil { + return nil, err + } + return tables, nil +} + +func (db *ydb) GetIndexes( + queryer core.Queryer, + ctx context.Context, + tableName string) (_ map[string]*schemas.Index, err error) { + indexes := make(map[string]*schemas.Index, 0) + err = db.WithConnRaw(queryer, ctx, func(dc interface{}) error { + q, ok := dc.(interface { + GetIndexes(context.Context, string) ([]string, error) + GetIndexColumns(context.Context, string, string) ([]string, error) + }) + if !ok { + return fmt.Errorf("driver does not support query metadata") + } + indexNames, err := q.GetIndexes(ctx, tableName) + if err != nil { + return err + } + for _, indexName := range indexNames { + cols, err := q.GetIndexColumns(ctx, tableName, indexName) + if err != nil { + return err + } + indexes[indexName] = &schemas.Index{ + Name: indexName, + Type: schemas.IndexType, + Cols: cols, + } + } + return nil + }) + if err != nil { + return nil, err + } + return indexes, nil +} + +func (db *ydb) CreateTableSQL( + ctx context.Context, + _ core.Queryer, + table *schemas.Table, + tableName string) (string, bool, error) { + quote := db.dialect.Quoter() + tableName = quote.Quote(tableName) + + var buf strings.Builder + buf.WriteString(fmt.Sprintf("CREATE TABLE %s ( ", tableName)) + + // build primary key + if len(table.PrimaryKeys) == 0 { + return "", false, errors.New("table must have at least one primary key") + } + pk := make([]string, len(table.PrimaryKeys)) + pkMap := make(map[string]bool) + for i := 0; i < len(table.PrimaryKeys); i++ { + pk[i] = quote.Quote(table.PrimaryKeys[i]) + pkMap[pk[i]] = true + } + primaryKey := fmt.Sprintf("PRIMARY KEY ( %s )", strings.Join(pk, ", ")) + + // build column + columnsList := []string{} + for _, c := range table.Columns() { + columnName := quote.Quote(c.Name) + dataType := db.SQLType(c) + + if _, isPk := pkMap[columnName]; isPk { + columnsList = append(columnsList, fmt.Sprintf("%s %s NOT NULL", columnName, dataType)) + } else { + columnsList = append(columnsList, fmt.Sprintf("%s %s", columnName, dataType)) + } + } + joinColumns := strings.Join(columnsList, ", ") + + // build index + indexList := []string{} + for indexName, index := range table.Indexes { + name := quote.Quote(indexName) + onCols := make([]string, len(index.Cols)) + for i := 0; i < len(index.Cols); i++ { + onCols[i] = quote.Quote(index.Cols[i]) + } + indexList = append(indexList, + fmt.Sprintf( + "INDEX %s GLOBAL ON ( %s )", + name, strings.Join(onCols, ", "))) + } + joinIndexes := strings.Join(indexList, ", ") + + if joinIndexes != "" { + buf.WriteString(strings.Join([]string{joinColumns, joinIndexes, primaryKey}, ", ")) + } else { + buf.WriteString(strings.Join([]string{joinColumns, primaryKey}, ", ")) + } + + buf.WriteString(" ) ") + + if db.tableParams != nil && len(db.tableParams) > 0 { + params := make([]string, 0) + for param, value := range db.tableParams { + if param == "" || value == "" { + continue + } + params = append(params, fmt.Sprintf("%s = %s", param, value)) + } + if len(params) > 0 { + buf.WriteString(fmt.Sprintf("WITH ( %s ) ", strings.Join(params, ", "))) + } + } + + buf.WriteString("; ") + + return buf.String(), true, nil +} + +func (db *ydb) DropTableSQL(tableName string) (string, bool) { + quote := db.dialect.Quoter() + tableName = quote.Quote(tableName) + + var buf strings.Builder + buf.WriteString(fmt.Sprintf("DROP TABLE %s;", tableName)) + + return buf.String(), false +} + +// https://github.com/ydb-platform/ydb-go-sdk/blob/master/SQL.md#specifying-query-parameters- +func (db *ydb) Filters() []Filter { + return []Filter{&ydbSeqFilter{ + Prefix: "$", + Start: 1, + }} +} + +const ( + ydb_grpc_Canceled uint32 = 1 + ydb_grpc_Unknown uint32 = 2 + ydb_grpc_InvalidArgument uint32 = 3 + ydb_grpc_DeadlineExceeded uint32 = 4 + ydb_grpc_NotFound uint32 = 5 + ydb_grpc_AlreadyExists uint32 = 6 + ydb_grpc_PermissionDenied uint32 = 7 + ydb_grpc_ResourceExhausted uint32 = 8 + ydb_grpc_FailedPrecondition uint32 = 9 + ydb_grpc_Aborted uint32 = 10 + ydb_grpc_OutOfRange uint32 = 11 + ydb_grpc_Unimplemented uint32 = 12 + ydb_grpc_Internal uint32 = 13 + ydb_grpc_Unavailable uint32 = 14 + ydb_grpc_DataLoss uint32 = 15 + ydb_grpc_Unauthenticated uint32 = 16 +) + +const ( + ydb_STATUS_CODE_UNSPECIFIED int32 = 0 + ydb_SUCCESS int32 = 400000 + ydb_BAD_REQUEST int32 = 400010 + ydb_UNAUTHORIZED int32 = 400020 + ydb_INTERNAL_ERROR int32 = 400030 + ydb_ABORTED int32 = 400040 + ydb_UNAVAILABLE int32 = 400050 + ydb_OVERLOADED int32 = 400060 + ydb_SCHEME_ERROR int32 = 400070 + ydb_GENERIC_ERROR int32 = 400080 + ydb_TIMEOUT int32 = 400090 + ydb_BAD_SESSION int32 = 400100 + ydb_PRECONDITION_FAILED int32 = 400120 + ydb_ALREADY_EXISTS int32 = 400130 + ydb_NOT_FOUND int32 = 400140 + ydb_SESSION_EXPIRED int32 = 400150 + ydb_CANCELLED int32 = 400160 + ydb_UNDETERMINED int32 = 400170 + ydb_UNSUPPORTED int32 = 400180 + ydb_SESSION_BUSY int32 = 400190 +) + +// https://github.com/ydb-platform/ydb-go-sdk/blob/ca13feb3ca560ac7385e79d4365ffe0cd8c23e21/errors.go#L27 +func (db *ydb) IsRetryable(err error) bool { + var target interface { + error + Code() int32 + Name() string + } + if errors.Is(err, fmt.Errorf("unknown error")) || + errors.Is(err, context.DeadlineExceeded) || + errors.Is(err, context.Canceled) { + return false + } + if !errors.As(err, &target) { + return false + } + + switch target.Code() { + case + int32(ydb_grpc_Unknown), + int32(ydb_grpc_InvalidArgument), + int32(ydb_grpc_DeadlineExceeded), + int32(ydb_grpc_NotFound), + int32(ydb_grpc_AlreadyExists), + int32(ydb_grpc_PermissionDenied), + int32(ydb_grpc_FailedPrecondition), + int32(ydb_grpc_OutOfRange), + int32(ydb_grpc_Unimplemented), + int32(ydb_grpc_DataLoss), + int32(ydb_grpc_Unauthenticated): + return false + case + int32(ydb_grpc_Canceled), + int32(ydb_grpc_ResourceExhausted), + int32(ydb_grpc_Aborted), + int32(ydb_grpc_Internal), + int32(ydb_grpc_Unavailable): + return true + case + ydb_STATUS_CODE_UNSPECIFIED, + ydb_BAD_REQUEST, + ydb_UNAUTHORIZED, + ydb_INTERNAL_ERROR, + ydb_SCHEME_ERROR, + ydb_GENERIC_ERROR, + ydb_TIMEOUT, + ydb_PRECONDITION_FAILED, + ydb_ALREADY_EXISTS, + ydb_NOT_FOUND, + ydb_SESSION_EXPIRED, + ydb_CANCELLED, + ydb_UNSUPPORTED: + return false + case + ydb_ABORTED, + ydb_UNAVAILABLE, + ydb_OVERLOADED, + ydb_BAD_SESSION, + ydb_UNDETERMINED, + ydb_SESSION_BUSY: + return true + default: + return false + } +} + +type ydbDriver struct { + baseDriver +} + +func (ydbDrv *ydbDriver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: false, + } +} + +// DSN format: https://github.com/ydb-platform/ydb-go-sdk/blob/a804c31be0d3c44dfd7b21ed49d863619217b11d/connection.go#L339 +func (ydbDrv *ydbDriver) Parse(driverName, dataSourceName string) (*URI, error) { + info := &URI{DBType: schemas.YDB} + + uri, err := url.Parse(dataSourceName) + if err != nil { + return nil, fmt.Errorf("failed on parse data source %v", dataSourceName) + } + + const ( + secure = "grpcs" + insecure = "grpc" + ) + + if uri.Scheme != secure && uri.Scheme != insecure { + return nil, fmt.Errorf("unsupported scheme %v", uri.Scheme) + } + + info.Host = uri.Host + if spl := strings.Split(uri.Host, ":"); len(spl) > 1 { + info.Host = spl[0] + info.Port = spl[1] + } + + info.DBName = uri.Path + if info.DBName == "" { + return nil, errors.New("database path can not be empty") + } + + if uri.User != nil { + info.Passwd, _ = uri.User.Password() + info.User = uri.User.Username() + } + + return info, nil +} + +// https://pkg.go.dev/database/sql#ColumnType.DatabaseTypeName +func (ydbDrv *ydbDriver) GenScanResult(columnType string) (interface{}, error) { + switch columnType = removeOptional(columnType); columnType { + case yql_Bool: + var ret sql.NullBool + return &ret, nil + case yql_Int16: + var ret sql.NullInt16 + return &ret, nil + case yql_Int32: + var ret sql.NullInt32 + return &ret, nil + case yql_Int64: + var ret sql.NullInt64 + return &ret, nil + case yql_Uint8: + var ret sql.NullByte + return &ret, nil + case yql_Uint32: + var ret convert.NullUint32 + return &ret, nil + case yql_Uint64: + var ret convert.NullUint64 + return &ret, nil + case yql_Double: + var ret sql.NullFloat64 + return &ret, nil + case yql_Utf8: + var ret sql.NullString + return &ret, nil + case yql_Timestamp: + var ret sql.NullTime + return &ret, nil + case yql_Interval: + var ret convert.NullDuration + return &ret, nil + default: + var ret sql.RawBytes + return &ret, nil + } +} + +func (ydbDrv *ydbDriver) Scan(ctx *ScanContext, rows *core.Rows, types []*sql.ColumnType, v ...interface{}) error { + if err := rows.Scan(v...); err != nil { + return err + } + + if ctx.DBLocation == nil { + return nil + } + + for i := range v { + // !datbeohbbh! YDB saves time in UTC. When returned value is time type, then value will be represented in local time. + // So value in time type must be converted to DBLocation. + switch des := v[i].(type) { + case *time.Time: + *des = (*des).In(ctx.DBLocation) + case *sql.NullTime: + if des.Valid { + (*des).Time = (*des).Time.In(ctx.DBLocation) + } + case *interface{}: + switch t := (*des).(type) { + case time.Time: + *des = t.In(ctx.DBLocation) + case sql.NullTime: + if t.Valid { + *des = t.Time.In(ctx.DBLocation) + } + } + } + } + + return nil +} + +// !datbeohbbh! this is a 'helper' function for YDB to bypass the custom type. +// Example: +// -- +// type CustomInt int64 +// engine.Where("ID > ?", CustomInt(10)).Get(...) +// -- +// ydb-go-sdk does not know about `CustomInt` type and will cause error. +func (ydbDrv *ydbDriver) Cast(paramStr ...interface{}) { + for i := range paramStr { + if paramStr[i] == nil { + continue + } + + var ( + val = reflect.ValueOf(paramStr[i]) + res interface{} + ) + + fieldType := val.Type() + k := fieldType.Kind() + if k == reflect.Ptr { + if val.IsNil() || !val.IsValid() { + paramStr[i] = val.Interface() + continue + } else { + val = val.Elem() + fieldType = val.Type() + k = fieldType.Kind() + } + } + + switch k { + case reflect.Bool: + res = val.Bool() + case reflect.String: + res = val.String() + case reflect.Struct: + if fieldType.ConvertibleTo(schemas.TimeType) { + res = val.Convert(schemas.TimeType).Interface().(time.Time) + } else if fieldType.ConvertibleTo(schemas.IntervalType) { + res = val.Convert(schemas.IntervalType).Interface().(time.Duration) + } else if fieldType.ConvertibleTo(schemas.NullBoolType) { + res = val.Convert(schemas.NullBoolType).Interface().(sql.NullBool) + } else if fieldType.ConvertibleTo(schemas.NullFloat64Type) { + res = val.Convert(schemas.NullFloat64Type).Interface().(sql.NullFloat64) + } else if fieldType.ConvertibleTo(schemas.NullInt16Type) { + res = val.Convert(schemas.NullInt16Type).Interface().(sql.NullInt16) + } else if fieldType.ConvertibleTo(schemas.NullInt32Type) { + res = val.Convert(schemas.NullInt32Type).Interface().(sql.NullInt32) + } else if fieldType.ConvertibleTo(schemas.NullInt64Type) { + res = val.Convert(schemas.NullInt64Type).Interface().(sql.NullInt64) + } else if fieldType.ConvertibleTo(schemas.NullStringType) { + res = val.Convert(schemas.NullStringType).Interface().(sql.NullString) + } else if fieldType.ConvertibleTo(schemas.NullTimeType) { + res = val.Convert(schemas.NullTimeType).Interface().(sql.NullTime) + } else { + res = val.Interface() + } + case reflect.Array, reflect.Slice, reflect.Map: + res = val.Interface() + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + val := val.Uint() + switch k { + case reflect.Uint8: + res = uint8(val) + case reflect.Uint16: + res = uint16(val) + case reflect.Uint32: + res = uint32(val) + case reflect.Uint64: + res = uint64(val) + default: + res = val + } + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + val := val.Int() + switch k { + case reflect.Int8: + res = int8(val) + case reflect.Int16: + res = int16(val) + case reflect.Int32: + res = int32(val) + case reflect.Int64: + res = int64(val) + default: + res = val + } + default: + if val.Interface() == nil { + res = (*[]byte)(nil) + } else { + res = val.Interface() + } + } + paramStr[i] = res + } +} diff --git a/dialects/ydb_test.go b/dialects/ydb_test.go new file mode 100644 index 00000000..fc514dad --- /dev/null +++ b/dialects/ydb_test.go @@ -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) + }) + } +} diff --git a/engine.go b/engine.go index 0cbfdede..ca2e2e18 100644 --- a/engine.go +++ b/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 +} diff --git a/go.mod b/go.mod index b48b3bbc..006100f9 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 5c780ec6..620e2418 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,390 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitee.com/travelliu/dm v1.8.11192 h1:aqJT0xhodZjRutIfEXxKYv0CxqmHUHzsbz6SFaRL6OY= @@ -6,11 +393,36 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbL github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -24,7 +436,24 @@ github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDror github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= @@ -34,26 +463,120 @@ github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= @@ -105,13 +628,18 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= +github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -124,6 +652,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -145,12 +675,19 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -162,6 +699,10 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -171,6 +712,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -178,11 +720,29 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20230801151335-81e01be38941 h1:QXgmY0vkYtOoGEXnTjWkyxhOkIzCosMrnoDyYjrv71Q= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20230801151335-81e01be38941/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-sdk/v3 v3.52.1 h1:08EHJN/+Qe+fS8jZCjgNADqKu7ZR+xNhXKyaHJx1xtU= +github.com/ydb-platform/ydb-go-sdk/v3 v3.52.1/go.mod h1:YBBMbkISt0UKjrO5JQYjM7xkOBbHoNXKJskiHhpETxQ= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -197,90 +757,561 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -292,14 +1323,22 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= @@ -341,5 +1380,8 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= diff --git a/internal/statements/statement.go b/internal/statements/statement.go index c075ec54..a40eebb9 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -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() diff --git a/internal/statements/values.go b/internal/statements/values.go index 4c1360ed..fb69f16e 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -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 } } diff --git a/retry/backoff.go b/retry/backoff.go new file mode 100644 index 00000000..a30790e0 --- /dev/null +++ b/retry/backoff.go @@ -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< 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) +} diff --git a/retry/backoff_test.go b/retry/backoff_test.go new file mode 100644 index 00000000..3fdb0578 --- /dev/null +++ b/retry/backoff_test.go @@ -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) + } +} diff --git a/retry/retry.go b/retry/retry.go new file mode 100644 index 00000000..27202a26 --- /dev/null +++ b/retry/retry.go @@ -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 + } +} diff --git a/retry/retry_test.go b/retry/retry_test.go new file mode 100644 index 00000000..4c4ca90c --- /dev/null +++ b/retry/retry_test.go @@ -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) +} diff --git a/schemas/type.go b/schemas/type.go index 3dbcee7e..e7b892f0 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -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} diff --git a/session.go b/session.go index 14d0781e..31af8b9a 100644 --- a/session.go +++ b/session.go @@ -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 } diff --git a/session_delete.go b/session_delete.go index 7336040f..ba697c2a 100644 --- a/session_delete.go +++ b/session_delete.go @@ -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 } diff --git a/session_insert.go b/session_insert.go index 7cc15241..5a819232 100644 --- a/session_insert.go +++ b/session_insert.go @@ -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 { diff --git a/session_raw.go b/session_raw.go index 99f6be99..2c9cf6b9 100644 --- a/session_raw.go +++ b/session_raw.go @@ -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 } diff --git a/session_update.go b/session_update.go index b3640ad2..f9166041 100644 --- a/session_update.go +++ b/session_update.go @@ -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) { diff --git a/sync.go b/sync.go index adc2d859..f5395b0d 100644 --- a/sync.go +++ b/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 diff --git a/tests/engine_test.go b/tests/engine_test.go index 79ca42f5..032e1fbe 100644 --- a/tests/engine_test.go +++ b/tests/engine_test.go @@ -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()) diff --git a/tests/ydbtest/cache_test.go b/tests/ydbtest/cache_test.go new file mode 100644 index 00000000..941fb597 --- /dev/null +++ b/tests/ydbtest/cache_test.go @@ -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) +} diff --git a/tests/ydbtest/e2e_test.go b/tests/ydbtest/e2e_test.go new file mode 100644 index 00000000..3ba6351e --- /dev/null +++ b/tests/ydbtest/e2e_test.go @@ -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 +} diff --git a/tests/ydbtest/engine_test.go b/tests/ydbtest/engine_test.go new file mode 100644 index 00000000..cf9fd061 --- /dev/null +++ b/tests/ydbtest/engine_test.go @@ -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) + }) +} diff --git a/tests/ydbtest/helpers.go b/tests/ydbtest/helpers.go new file mode 100644 index 00000000..8b337339 --- /dev/null +++ b/tests/ydbtest/helpers.go @@ -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 +} diff --git a/tests/ydbtest/main_start.go b/tests/ydbtest/main_start.go new file mode 100644 index 00000000..7f7e24d8 --- /dev/null +++ b/tests/ydbtest/main_start.go @@ -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 +} diff --git a/tests/ydbtest/main_test.go b/tests/ydbtest/main_test.go new file mode 100644 index 00000000..6d64a117 --- /dev/null +++ b/tests/ydbtest/main_test.go @@ -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)) +} diff --git a/tests/ydbtest/models.go b/tests/ydbtest/models.go new file mode 100644 index 00000000..8ad0012f --- /dev/null +++ b/tests/ydbtest/models.go @@ -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 +} diff --git a/tests/ydbtest/session_cols_test.go b/tests/ydbtest/session_cols_test.go new file mode 100644 index 00000000..745fb800 --- /dev/null +++ b/tests/ydbtest/session_cols_test.go @@ -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) +} diff --git a/tests/ydbtest/session_cond_test.go b/tests/ydbtest/session_cond_test.go new file mode 100644 index 00000000..6a9d6ef2 --- /dev/null +++ b/tests/ydbtest/session_cond_test.go @@ -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) + } +} diff --git a/tests/ydbtest/session_count_test.go b/tests/ydbtest/session_count_test.go new file mode 100644 index 00000000..47b3126d --- /dev/null +++ b/tests/ydbtest/session_count_test.go @@ -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) +} diff --git a/tests/ydbtest/session_delete_test.go b/tests/ydbtest/session_delete_test.go new file mode 100644 index 00000000..b464a7f0 --- /dev/null +++ b/tests/ydbtest/session_delete_test.go @@ -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) +} +*/ diff --git a/tests/ydbtest/session_exist_test.go b/tests/ydbtest/session_exist_test.go new file mode 100644 index 00000000..bb26b48a --- /dev/null +++ b/tests/ydbtest/session_exist_test.go @@ -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) +} diff --git a/tests/ydbtest/session_find_test.go b/tests/ydbtest/session_find_test.go new file mode 100644 index 00000000..4e500f9f --- /dev/null +++ b/tests/ydbtest/session_find_test.go @@ -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) +} diff --git a/tests/ydbtest/session_get_test.go b/tests/ydbtest/session_get_test.go new file mode 100644 index 00000000..d67906b9 --- /dev/null +++ b/tests/ydbtest/session_get_test.go @@ -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) + } +} diff --git a/tests/ydbtest/session_insert_test.go b/tests/ydbtest/session_insert_test.go new file mode 100644 index 00000000..5ae2a6dc --- /dev/null +++ b/tests/ydbtest/session_insert_test.go @@ -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) + } +} diff --git a/tests/ydbtest/session_iterate_test.go b/tests/ydbtest/session_iterate_test.go new file mode 100644 index 00000000..c86d6660 --- /dev/null +++ b/tests/ydbtest/session_iterate_test.go @@ -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) +} diff --git a/tests/ydbtest/session_pk_test.go b/tests/ydbtest/session_pk_test.go new file mode 100644 index 00000000..45730142 --- /dev/null +++ b/tests/ydbtest/session_pk_test.go @@ -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) + } +} diff --git a/tests/ydbtest/session_query_test.go b/tests/ydbtest/session_query_test.go new file mode 100644 index 00000000..1a862cbd --- /dev/null +++ b/tests/ydbtest/session_query_test.go @@ -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)) +} diff --git a/tests/ydbtest/session_raw_test.go b/tests/ydbtest/session_raw_test.go new file mode 100644 index 00000000..0d9da36e --- /dev/null +++ b/tests/ydbtest/session_raw_test.go @@ -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)) +} diff --git a/tests/ydbtest/session_replace_test.go b/tests/ydbtest/session_replace_test.go new file mode 100644 index 00000000..a6432c5b --- /dev/null +++ b/tests/ydbtest/session_replace_test.go @@ -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)) +} diff --git a/tests/ydbtest/session_rows_test.go b/tests/ydbtest/session_rows_test.go new file mode 100644 index 00000000..ad454f81 --- /dev/null +++ b/tests/ydbtest/session_rows_test.go @@ -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) + } + } +} diff --git a/tests/ydbtest/session_schema_test.go b/tests/ydbtest/session_schema_test.go new file mode 100644 index 00000000..ee2be444 --- /dev/null +++ b/tests/ydbtest/session_schema_test.go @@ -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) +} diff --git a/tests/ydbtest/session_secondary_index_test.go b/tests/ydbtest/session_secondary_index_test.go new file mode 100644 index 00000000..fa03f756 --- /dev/null +++ b/tests/ydbtest/session_secondary_index_test.go @@ -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]) + } +} diff --git a/tests/ydbtest/session_sum_test.go b/tests/ydbtest/session_sum_test.go new file mode 100644 index 00000000..9f8fd108 --- /dev/null +++ b/tests/ydbtest/session_sum_test.go @@ -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)) +} diff --git a/tests/ydbtest/session_tx_test.go b/tests/ydbtest/session_tx_test.go new file mode 100644 index 00000000..1a190cf4 --- /dev/null +++ b/tests/ydbtest/session_tx_test.go @@ -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) + }) +} diff --git a/tests/ydbtest/session_update_test.go b/tests/ydbtest/session_update_test.go new file mode 100644 index 00000000..ea5ec453 --- /dev/null +++ b/tests/ydbtest/session_update_test.go @@ -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) +} diff --git a/tests/ydbtest/session_upsert_test.go b/tests/ydbtest/session_upsert_test.go new file mode 100644 index 00000000..5dd21fe1 --- /dev/null +++ b/tests/ydbtest/session_upsert_test.go @@ -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)) +} diff --git a/tests/ydbtest/sync_benchmark.go b/tests/ydbtest/sync_benchmark.go new file mode 100644 index 00000000..29062158 --- /dev/null +++ b/tests/ydbtest/sync_benchmark.go @@ -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{}, + )) + } +} diff --git a/tests/ydbtest/testdata/DDL.yql b/tests/ydbtest/testdata/DDL.yql new file mode 100644 index 00000000..ef3ce255 --- /dev/null +++ b/tests/ydbtest/testdata/DDL.yql @@ -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` ) +); \ No newline at end of file diff --git a/tests/ydbtest/testdata/DML.yql b/tests/ydbtest/testdata/DML.yql new file mode 100644 index 00000000..6f898f07 --- /dev/null +++ b/tests/ydbtest/testdata/DML.yql @@ -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),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),CAST(1396742400000000 AS Optional),CAST('Some comment here' AS Optional)), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88',CAST('IT Crowd' AS Optional),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),CAST(1138924800000000 AS Optional), ''); + +-- 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),CAST(1277424000000000 AS Optional),CAST(1280448000000000 AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319',CAST('Season 5' AS Optional),CAST(1521936000000000 AS Optional),CAST(1526169600000000 AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c',CAST('Season 1' AS Optional),CAST(1138924800000000 AS Optional),CAST(1141344000000000 AS Optional)), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224',CAST('Season 1' AS Optional),CAST(1138924800000000 AS Optional),CAST(1141344000000000 AS Optional)), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950',CAST('Season 4' AS Optional),CAST(1277424000000000 AS Optional),CAST(1280448000000000 AS Optional)); + +-- 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),CAST(1433030400000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','1187c66e-db36-4f43-8f07-fd4f29384087',CAST('Homicide' AS Optional),CAST(1431820800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','21d1b5b0-22c3-449d-bd88-9b5b5ed54a3f',CAST('test' AS Optional),CAST(1430611200000000 AS Optional),CAST('999' AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','41137bf8-815c-4dc6-abc0-b6f4b2ca785f',CAST('Two Days of the Condor' AS Optional),CAST(1434240000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','493d04f0-dac8-42e2-bc1b-84dc9bb1e099',CAST('Runaway Devaluation' AS Optional),CAST(1429401600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','56113b02-ed67-4ad1-ad87-5516c12582e9',CAST('Bad Money' AS Optional),CAST(1430006400000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','636c215d-bcdb-4571-a779-cedd07c703ca',CAST('Sand Hill Shuffle' AS Optional),CAST(1428796800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','9b1432d0-850d-44bb-bc76-1d4fe0e64101',CAST('Server Space' AS Optional),CAST(1431216000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','df6cdb08-2fc4-43bf-80b5-c970f752c7a5',CAST('Adult Content' AS Optional),CAST(1432425600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','078601d7-5a23-429a-932b-5ad19e75b607','e02cd943-31c1-4185-bcbb-cf803be61dc7',CAST('Binding Arbitration' AS Optional),CAST(1433635200000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','1589f366-2239-4c37-909b-797439094b53',CAST('Server Error' AS Optional),CAST(1498348800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','3385778f-0206-4f94-b80a-b5542a2bd657',CAST('Customer Service' AS Optional),CAST(1495929600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','3bde20f8-ba00-418d-8c6b-058110575879',CAST('Success Failure' AS Optional),CAST(1492905600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','6818d030-ee1f-4626-92fc-03965fcebce5',CAST('Terms of Service' AS Optional),CAST(1493510400000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','6d6616bb-c98e-4f0c-a097-c9aad558dfd8',CAST('Hooli-Con' AS Optional),CAST(1497744000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','78005ee7-7ad6-4c58-aac3-cd6eb2871017',CAST('test' AS Optional),CAST(1497139200000000 AS Optional),CAST('999' AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','7900f321-3907-4cc1-826a-69344359ca28',CAST('test' AS Optional),CAST(1496534400000000 AS Optional),CAST('999' AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','7b1afb5f-5259-4aca-b54f-84d72b995748',CAST('Intellectual Property' AS Optional),CAST(1494115200000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','83f046ff-6e1b-4f84-9db2-5a20527382a4',CAST('Teambuilding Exercise' AS Optional),CAST(1494720000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','2b123989-6cf5-4e95-94cc-e5ec6eabd171','aef2d1e5-02c0-41ac-926b-fad43d6bc0d8',CAST('test' AS Optional),CAST(1495324800000000 AS Optional),CAST('999' AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','0fe8e4a2-2b1a-4a61-95ec-b4a6432c038c',CAST('Initial Coin Offering' AS Optional),CAST(1525564800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','382b35eb-2615-4b58-afb1-b591d905135f',CAST('Grow Fast or Die Slow' AS Optional),CAST(1521936000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','7b8db0cb-1ba3-4329-abfe-bd053624dcb2',CAST('Tech Evangelist' AS Optional),CAST(1523750400000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','8eb19cb1-c3cf-473b-9b88-248558422731',CAST('Facial Recognition' AS Optional),CAST(1524355200000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','a8c01056-3ed5-4615-87fa-f0047ccf612d',CAST('Artificial Emotional Intelligence' AS Optional),CAST(1524960000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','e270ba41-ce67-4725-a192-6df9d97e6bdf',CAST('Reorientation' AS Optional),CAST(1522540800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','e67bc0f1-bc04-4079-be07-15dadddb560b',CAST('Chief Operating Officer' AS Optional),CAST(1523145600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','54a1f467-ef2e-454e-977a-a82145484319','e913806f-16b8-4298-b7d2-533352b5659a',CAST('Fifty-One Percent' AS Optional),CAST(1526169600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','20745b9d-c9d8-443f-b27f-8f3633b076a6',CAST('Founder Friendly' AS Optional),CAST(1461456000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','59968ede-61ed-4547-af4a-9a1b26ece417',CAST('test' AS Optional),CAST(1463875200000000 AS Optional),CAST('999' AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','5bfd22db-e967-46c2-91c5-5e1a79aeee17',CAST('Bachmanity Insanity' AS Optional),CAST(1464480000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','622d5664-8361-4a21-b90e-febc6920e8a7',CAST('Meinertzhagen\'\'s Haversack' AS Optional),CAST(1462665600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','6717a4d1-1510-4cda-bf1b-bd428f01078a',CAST('Maleant Data Systems Solutions' AS Optional),CAST(1463270400000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','7fc1149e-78d7-47b4-88ff-cca0337e06fe',CAST('To Build a Better Beta' AS Optional),CAST(1465084800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','c93e9ff1-c9e2-4b21-9c08-23993b5de66c',CAST('test' AS Optional),CAST(1466899200000000 AS Optional),CAST('999' AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','e5d88eb6-25e0-451d-a558-f8a7821e0e33',CAST('Two in the Box' AS Optional),CAST(1462060800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','f3f4b8bb-0ebd-4c2d-aab2-c6e3152fbdab',CAST('Bachman\'\'s Earnings Over-Ride' AS Optional),CAST(1465689600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','57fd7b2c-2757-48f3-b445-4d7b237a1d1c','fbdf6b46-417a-4978-bbe8-146cdb235a6d',CAST('Daily Active Users' AS Optional),CAST(1466294400000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','49548f34-319f-4c25-a0ea-c0eaf795bd8c',CAST('Fiduciary Duties' AS Optional),CAST(1398556800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','527f28f6-f865-48fc-b22a-d3c69d168408',CAST('Proof of Concept' AS Optional),CAST(1400371200000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','5ef1805b-0cdf-451a-9831-e039931a6203',CAST('Signaling Risk' AS Optional),CAST(1399161600000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','71fb225c-327a-430c-ba57-244b49404dd5',CAST('Optimal Tip-to-Tip Efficiency' AS Optional),CAST(1401580800000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','a955e7be-43da-4ba4-ac71-5dad86ecccd5',CAST('Articles of Incorporation' AS Optional),CAST(1397952000000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','bf2ba43c-9542-41a3-994c-6edf96f5138e',CAST('test' AS Optional),CAST(1397347200000000 AS Optional),CAST('999' AS Optional)), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','d56fcf97-c7bb-4c96-a51b-b35363525dc9',CAST('Minimum Viable Product' AS Optional),CAST(1396742400000000 AS Optional),NULL), +('8802e71f-d978-4379-8e7d-80090387c2c7','be84cb36-ac22-4346-a74e-73cde0c7393c','e181a2f2-1ffa-4283-b5b1-0226f3805a2c',CAST('Third Party Insourcing' AS Optional),CAST(1399766400000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','47118ec6-d0e2-4164-9876-415512cd32ef',CAST('Yesterday\'\'s Jam' AS Optional),CAST(1138924800000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','4c72e475-2003-4e7f-a7e7-5c2c27512fc8',CAST('The Red Door' AS Optional),CAST(1140134400000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','98c194f8-622d-4f5c-9e6d-653bcf6cb157',CAST('Aunt Irma Visits' AS Optional),CAST(1141344000000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','9fa9aeb5-347a-4888-b451-c6edaad9ecc0',CAST('The Haunting of Bill Crouse' AS Optional),CAST(1140739200000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','b46d5977-6891-46ad-878c-7ef9c4e10ad3',CAST('Fifty-Fifty' AS Optional),CAST(1139529600000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','51efced2-8c7c-4be0-864f-d3fadfed3224','de57626f-5b43-4673-bc9c-a0d1fef2fb20',CAST('Calamity Jen' AS Optional),CAST(1138924800000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','158a5818-f853-499c-aa1f-9088c0229054',CAST('Calendar Geeks' AS Optional),CAST(1230249600000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','19f4c4f4-4018-4139-9c48-02e491560f79',CAST('Friendface' AS Optional),CAST(1229644800000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','71eda16f-10b8-42c7-ac6e-fbf5ad61c049',CAST('Are We Not Men?' AS Optional),CAST(1227830400000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','7d558a59-e17f-481e-8b3b-03b54cf6a635',CAST('Tramps Like Us' AS Optional),CAST(1228435200000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','80541d00-ca6d-4380-87bb-619894dd1997',CAST('From Hell' AS Optional),CAST(1227225600000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6111aefa-f8de-4b2e-a1a1-d96b31ae3449','eaf80f6b-8b1e-4f04-8da2-070791afeba4',CAST('The Speech' AS Optional),CAST(1229040000000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','2c1776bf-8861-4728-8a71-2d62ccd608ef',CAST('Reynholm vs Reynholm' AS Optional),CAST(1280448000000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','46b4b4c8-0ce7-48b2-912e-a964161e8ec3',CAST('Bad Boys' AS Optional),CAST(1279843200000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','584360cf-8ff3-47ba-b44e-4c026e545f3d',CAST('Something Happened' AS Optional),CAST(1278633600000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','96d6fda1-84f6-4c56-8763-9604b51faa11',CAST('The Final Countdown' AS Optional),CAST(1278028800000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','9c73e47e-1e20-49a0-9285-7b08d0fd1b06',CAST('Jen The Fredo' AS Optional),CAST(1277424000000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','6a916967-2be5-43e5-9356-c99c6233f950','b123a783-ddfe-42b7-9593-c3160d68db68',CAST('Italian For Beginners' AS Optional),CAST(1279238400000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','169bce9d-70ca-4c3e-8cd0-c11dd704feca',CAST('Smoke and Mirrors' AS Optional),CAST(1190332800000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','2ff32dfb-1b2f-48cb-8366-2ddfdadbea69',CAST('The Work Outing' AS Optional),CAST(1156377600000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','4d59dbf3-53cb-4b4e-ba8c-a04b8679afcc',CAST('Moss and the German' AS Optional),CAST(1189123200000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','ad69fc17-9aff-4c80-b65e-06553cd7375a',CAST('Men Without Women' AS Optional),CAST(1190937600000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','af704207-8bba-4fdc-8bf0-331c8cdd8d4e',CAST('Return of the Golden Child' AS Optional),CAST(1188518400000000 AS Optional),NULL), +('e83bf413-6555-4db8-a71d-9d39c2e5cb88','b5b5d9f6-82d4-4405-81e1-596213d50356','d6f6b37f-a1f2-490f-a142-a9d8ecb65014',CAST('The Dinner Party' AS Optional),CAST(1189728000000000 AS Optional),NULL); diff --git a/tests/ydbtest/time_test.go b/tests/ydbtest/time_test.go new file mode 100644 index 00000000..83dbd37b --- /dev/null +++ b/tests/ydbtest/time_test.go @@ -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) +} diff --git a/tests/ydbtest/types_null_test.go b/tests/ydbtest/types_null_test.go new file mode 100644 index 00000000..9560899b --- /dev/null +++ b/tests/ydbtest/types_null_test.go @@ -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 , 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) +} diff --git a/tests/ydbtest/types_test.go b/tests/ydbtest/types_test.go new file mode 100644 index 00000000..6af95488 --- /dev/null +++ b/tests/ydbtest/types_test.go @@ -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)) +}