├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── analyticsindexes_links.go ├── analyticsindexprovider.go ├── analyticsindexprovider_core.go ├── analyticsprovider.go ├── analyticsprovider_core.go ├── analyticsquery_options.go ├── asyncopmanager.go ├── auth.go ├── bucket.go ├── bucket_collectionsmgr.go ├── bucket_collectionsmgr_test.go ├── bucket_collectionsmgrv2.go ├── bucket_collectionsmgrv2_test.go ├── bucket_internal.go ├── bucket_internal_test.go ├── bucket_ping.go ├── bucket_ping_test.go ├── bucket_test.go ├── bucket_viewindexes.go ├── bucket_viewindexes_test.go ├── bucket_viewquery.go ├── bucket_viewquery_test.go ├── bucketmgmtprovider.go ├── bucketmgmtprovider_ps.go ├── buckettmgmtprovider_core.go ├── circuitbreaker.go ├── client.go ├── client_core.go ├── client_ps.go ├── cluster.go ├── cluster_analyticsindexes.go ├── cluster_analyticsindexes_test.go ├── cluster_analyticsquery.go ├── cluster_analyticsquery_test.go ├── cluster_bucketmgr.go ├── cluster_bucketmgr_test.go ├── cluster_diag.go ├── cluster_diag_test.go ├── cluster_eventingmgr.go ├── cluster_eventingmgr_test.go ├── cluster_internal.go ├── cluster_internal_test.go ├── cluster_ping.go ├── cluster_ping_test.go ├── cluster_query.go ├── cluster_query_test.go ├── cluster_queryindexes.go ├── cluster_queryindexes_test.go ├── cluster_searchindexes.go ├── cluster_searchindexes_test.go ├── cluster_searchquery.go ├── cluster_searchquery_test.go ├── cluster_test.go ├── cluster_usermgr.go ├── cluster_usermgr_test.go ├── collection.go ├── collection_binary_crud.go ├── collection_binary_crud_test.go ├── collection_bulk.go ├── collection_bulk_bench_test.go ├── collection_bulk_test.go ├── collection_crud.go ├── collection_crud_bench_test.go ├── collection_crud_test.go ├── collection_ds.go ├── collection_ds_test.go ├── collection_dura.go ├── collection_queryindexes.go ├── collection_queryindexes_test.go ├── collection_rangescan.go ├── collection_rangescan_test.go ├── collection_subdoc.go ├── collection_subdoc_test.go ├── collection_test.go ├── collectionsmgmtprovider.go ├── collectionsmgmtprovider_core.go ├── collectionsmgmtprovider_ps.go ├── compressor.go ├── compressor_test.go ├── config_profile.go ├── config_profile_test.go ├── constants.go ├── constants_str.go ├── diagnosticsprovider.go ├── diagnosticsprovider_core.go ├── error.go ├── error_analytics.go ├── error_analytics_test.go ├── error_generic.go ├── error_http.go ├── error_http_test.go ├── error_keyvalue.go ├── error_keyvalue_test.go ├── error_ps.go ├── error_query.go ├── error_query_test.go ├── error_search.go ├── error_search_test.go ├── error_timeout.go ├── error_timeout_test.go ├── error_view.go ├── error_view_test.go ├── error_wrapping.go ├── errors_transactions.go ├── errors_transactions_query.go ├── eventingmgmtprovider.go ├── eventingmgmtprovider_core.go ├── go.mod ├── go.sum ├── internalProvider.go ├── internalProviderCore.go ├── kvbulk_provider_core.go ├── kvbulk_provider_ps.go ├── kvopmanager_core.go ├── kvopmanager_core_test.go ├── kvopmanager_ps.go ├── kvprovider.go ├── kvprovider_core.go ├── kvprovider_core_provider.go ├── kvprovider_coresubdoc.go ├── kvprovider_ps.go ├── logging.go ├── logging_meter.go ├── logging_meter_test.go ├── logging_ps.go ├── metrics.go ├── metrics_test.go ├── mgmt_http.go ├── mock_analyticsProviderCoreProvider_test.go ├── mock_analyticsProvider_test.go ├── mock_connectionManager_test.go ├── mock_diagnosticsProviderCoreProvider_test.go ├── mock_diagnosticsProvider_test.go ├── mock_httpProvider_test.go ├── mock_kvCapabilityVerifier_test.go ├── mock_kvProviderCoreProvider_test.go ├── mock_kvProvider_test.go ├── mock_mgmtProvider_test.go ├── mock_pendingOp_test.go ├── mock_queryProviderCoreProvider_test.go ├── mock_queryProvider_test.go ├── mock_searchCapabilityVerifier_test.go ├── mock_searchProviderCoreProvider_test.go ├── mock_searchProvider_test.go ├── mock_viewProviderCoreProvider_test.go ├── mock_viewProvider_test.go ├── mock_waitUntilReadyProvider_test.go ├── nodeversion_test.go ├── providers.go ├── query_options.go ├── queryindexprovider.go ├── queryindexprovider_core.go ├── queryindexprovider_ps.go ├── queryprovider.go ├── queryprovider_core.go ├── queryprovider_ps.go ├── rangescanopmanager.go ├── rangescanopmanager_test.go ├── results.go ├── results_test.go ├── retry.go ├── retry_core.go ├── retry_test.go ├── scope.go ├── scope_analyticsquery.go ├── scope_analyticsquery_test.go ├── scope_eventingmgr.go ├── scope_eventingmgr_test.go ├── scope_query.go ├── scope_query_test.go ├── scope_searchindexes.go ├── scope_searchindexes_test.go ├── scope_searchquery.go ├── scope_searchquery_test.go ├── search ├── facets.go ├── internal.go ├── queries.go └── sorting.go ├── search_request.go ├── searchindexprovider.go ├── searchindexprovider_core.go ├── searchindexprovider_ps.go ├── searchprovider.go ├── searchprovider_core.go ├── searchprovider_ps.go ├── searchquery_options.go ├── singlequerytransactionresult.go ├── subdocspecs.go ├── testcluster_test.go ├── testdata ├── analytics_timeout.json ├── beer_sample_analytics_dataset.json ├── beer_sample_analytics_temp_error.json ├── beer_sample_brewery_five.json ├── beer_sample_brewery_single.json ├── beer_sample_query_dataset.json ├── beer_sample_query_dataset_no_metrics.json ├── beer_sample_query_error.json ├── beer_sample_query_temp_error.json ├── beer_sample_query_timeout.json ├── beer_sample_search_dataset.json ├── beer_sample_single.json ├── beer_sample_views_dataset.json ├── enhanced_beer_sample_query_dataset.json ├── ping_result.json ├── prepared_beer_sample_query_dataset.json ├── projection_doc.json ├── query_index_response.json ├── search_analyzedoc.json ├── transaction_begin_work_response.json ├── transaction_gocbcore_cause_error.json ├── travel-sample-index-stored.json ├── uisearchindex.json └── views_response_70.json ├── testmain_test.go ├── testsuite_test.go ├── testsuite_utils_test.go ├── thresholdlogtracer.go ├── thresholdlogtracer_test.go ├── token.go ├── token_test.go ├── tracing.go ├── tracing_test.go ├── transaction_attemptcontext.go ├── transaction_attemptcontext_query.go ├── transaction_bulkget.go ├── transaction_getresult.go ├── transaction_hooks.go ├── transaction_logger.go ├── transaction_queryresult.go ├── transaction_result.go ├── transactions.go ├── transactions_cleanup.go ├── transactions_compatibility.go ├── transactions_configs.go ├── transactions_constants.go ├── transactions_query_test.go ├── transactions_test.go ├── transactionsprovider.go ├── transactionsprovider_core.go ├── transactionsprovider_ps.go ├── transcoding.go ├── transcoding_test.go ├── usermanagerprovider.go ├── usermanagerprovider_core.go ├── utils_test.go ├── vector ├── query.go └── search.go ├── version.go ├── viewindexprovider.go ├── viewindexprovider_core.go ├── viewprovider.go ├── viewprovider_core.go ├── viewquery_options.go ├── viewquery_options_test.go └── waituntilreadyprovider.go /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | .project 4 | .vscode 5 | .idea 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: false 3 | skip-files: 4 | - logging.go # Logging has some utility functions that are useful to have around which get flagged up 5 | linters: 6 | enable: 7 | - bodyclose 8 | - revive 9 | - gosec 10 | - unconvert 11 | linters-settings: 12 | revive: 13 | set-exit-status: true 14 | min-confidence: 0.81 15 | rules: 16 | - name: var-naming 17 | arguments: [["URL"]] 18 | errcheck: 19 | check-type-assertions: true 20 | check-blank: true 21 | gosec: 22 | excludes: 23 | - G115 # Potential integer overflow when converting between integer types - TODO(GOCBC-1668): Fix all instances of this and remove the exclusion 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | devsetup: 2 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 3 | go install github.com/vektra/mockery/v2@v2.46.2 4 | 5 | test: 6 | go test ./ 7 | fasttest: 8 | go test -short ./ 9 | 10 | cover: 11 | go test -coverprofile=cover.out ./ 12 | 13 | lint: 14 | golangci-lint run -v 15 | 16 | check: lint 17 | go test -short -cover -race ./ 18 | 19 | bench: 20 | go test -bench=. -run=none --disable-logger=true 21 | 22 | updatetestcases: 23 | git submodule update --remote --init --recursive 24 | 25 | updatemocks: 26 | mockery --name=connectionManager --output=. --testonly --inpackage 27 | mockery --name=kvProvider --output=. --testonly --inpackage 28 | mockery --name=httpProvider --output=. --testonly --inpackage 29 | mockery --name=diagnosticsProvider --output=. --testonly --inpackage 30 | mockery --name=mgmtProvider --output=. --testonly --inpackage 31 | mockery --name=analyticsProvider --output=. --testonly --inpackage 32 | mockery --name=queryProvider --output=. --testonly --inpackage 33 | mockery --name=searchProvider --output=. --testonly --inpackage 34 | mockery --name=viewProvider --output=. --testonly --inpackage 35 | mockery --name=waitUntilReadyProvider --output=. --testonly --inpackage 36 | mockery --name=kvCapabilityVerifier --output=. --testonly --inpackage 37 | mockery --name=kvProviderCoreProvider --output=. --testonly --inpackage 38 | mockery --name=queryProviderCoreProvider --output=. --testonly --inpackage 39 | mockery --name=searchProviderCoreProvider --output=. --testonly --inpackage 40 | mockery --name=searchCapabilityVerifier --output=. --testonly --inpackage 41 | mockery --name=viewProviderCoreProvider --output=. --testonly --inpackage 42 | mockery --name=analyticsProviderCoreProvider --output=. --testonly --inpackage 43 | mockery --name=diagnosticsProviderCoreProvider --output=. --testonly --inpackage 44 | # pendingOp is manually mocked 45 | 46 | .PHONY: all test devsetup fasttest lint cover check bench updatetestcases updatemocks 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/couchbase/gocb?status.png)](https://pkg.go.dev/github.com/couchbase/gocb) 2 | 3 | # Couchbase Go Client 4 | 5 | The Go SDK library allows you to connect to a Couchbase cluster from Go. 6 | It is written in pure Go, and uses the included gocbcore library to handle communicating to the cluster over the Couchbase binary protocol. 7 | 8 | 9 | ## Useful Links 10 | 11 | ### Source 12 | The project source is hosted at [https://github.com/couchbase/gocb](https://github.com/couchbase/gocb). 13 | 14 | ### Documentation 15 | You can explore our API reference through godoc at [https://pkg.go.dev/github.com/couchbase/gocb](https://pkg.go.dev/github.com/couchbase/gocb). 16 | 17 | You can also find documentation for the Go SDK on the [official Couchbase docs](https://docs.couchbase.com/go-sdk/current/hello-world/overview.html). 18 | 19 | ### Bug Tracker 20 | Issues are tracked on Couchbase's public [issues.couchbase.com](http://www.couchbase.com/issues/browse/GOCBC). 21 | Contact [the site admins](https://issues.couchbase.com/secure/ContactAdministrators!default.jspa) regarding login or other problems at issues.couchbase.com (officially) or ask around [on the forum](https://forums.couchbase.com/) (unofficially). 22 | 23 | ### Discussion 24 | You can chat with us on [Discord](https://discord.com/invite/sQ5qbPZuTh) or the [official Couchbase forums](https://forums.couchbase.com/c/go-sdk/23). 25 | 26 | ## Installing 27 | 28 | To install the latest stable version, run: 29 | ```bash 30 | go get github.com/couchbase/gocb/v2@latest 31 | ``` 32 | 33 | To install the latest developer version, run: 34 | ```bash 35 | go get github.com/couchbase/gocb/v2@master 36 | ``` 37 | 38 | ## Testing 39 | 40 | You can run tests in the usual Go way: 41 | 42 | `go test -race ./...` 43 | 44 | Which will execute both the unit test suite and the integration test suite. 45 | By default, the integration test suite is run against a mock Couchbase Server. 46 | See the `testmain_test.go` file for information on command line arguments for running tests against a real server instance. 47 | 48 | ## Release train 49 | 50 | Releases are targeted for every third Tuesday of the month. 51 | This is subject to change based on priorities. 52 | 53 | ## Linting 54 | 55 | Linting is performed used `golangci-lint`. 56 | To run: 57 | 58 | `make lint` 59 | 60 | ## License 61 | Copyright 2016 Couchbase Inc. 62 | 63 | Licensed under the Apache License, Version 2.0. 64 | 65 | See 66 | [LICENSE](https://github.com/couchbase/gocb/blob/master/LICENSE) 67 | for further details. 68 | -------------------------------------------------------------------------------- /analyticsindexprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type analyticsIndexProvider interface { 4 | CreateDataverse(dataverseName string, opts *CreateAnalyticsDataverseOptions) error 5 | DropDataverse(dataverseName string, opts *DropAnalyticsDataverseOptions) error 6 | CreateDataset(datasetName, bucketName string, opts *CreateAnalyticsDatasetOptions) error 7 | DropDataset(datasetName string, opts *DropAnalyticsDatasetOptions) error 8 | GetAllDatasets(opts *GetAllAnalyticsDatasetsOptions) ([]AnalyticsDataset, error) 9 | CreateIndex(datasetName, indexName string, fields map[string]string, opts *CreateAnalyticsIndexOptions) error 10 | DropIndex(datasetName, indexName string, opts *DropAnalyticsIndexOptions) error 11 | GetAllIndexes(opts *GetAllAnalyticsIndexesOptions) ([]AnalyticsIndex, error) 12 | ConnectLink(opts *ConnectAnalyticsLinkOptions) error 13 | DisconnectLink(opts *DisconnectAnalyticsLinkOptions) error 14 | GetPendingMutations(opts *GetPendingMutationsAnalyticsOptions) (map[string]map[string]int, error) 15 | CreateLink(link AnalyticsLink, opts *CreateAnalyticsLinkOptions) error 16 | ReplaceLink(link AnalyticsLink, opts *ReplaceAnalyticsLinkOptions) error 17 | DropLink(linkName, dataverseName string, opts *DropAnalyticsLinkOptions) error 18 | GetLinks(opts *GetAnalyticsLinksOptions) ([]AnalyticsLink, error) 19 | } 20 | -------------------------------------------------------------------------------- /analyticsprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type analyticsProvider interface { 4 | AnalyticsQuery(statement string, scope *Scope, opts *AnalyticsOptions) (*AnalyticsResult, error) 5 | } 6 | 7 | type analyticsRowReader interface { 8 | NextRow() []byte 9 | Err() error 10 | MetaData() ([]byte, error) 11 | Close() error 12 | } 13 | -------------------------------------------------------------------------------- /analyticsprovider_core.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/couchbase/gocbcore/v10" 10 | ) 11 | 12 | type analyticsProviderCoreProvider interface { 13 | AnalyticsQuery(ctx context.Context, opts gocbcore.AnalyticsQueryOptions) (analyticsRowReader, error) 14 | } 15 | 16 | type analyticsProviderCore struct { 17 | provider analyticsProviderCoreProvider 18 | mgmtProvider mgmtProvider 19 | 20 | retryStrategyWrapper *coreRetryStrategyWrapper 21 | transcoder Transcoder 22 | analyticsTimeout time.Duration 23 | tracer *tracerWrapper 24 | } 25 | 26 | type jsonAnalyticsMetrics struct { 27 | ElapsedTime string `json:"elapsedTime"` 28 | ExecutionTime string `json:"executionTime"` 29 | ResultCount uint64 `json:"resultCount"` 30 | ResultSize uint64 `json:"resultSize"` 31 | MutationCount uint64 `json:"mutationCount,omitempty"` 32 | SortCount uint64 `json:"sortCount,omitempty"` 33 | ErrorCount uint64 `json:"errorCount,omitempty"` 34 | WarningCount uint64 `json:"warningCount,omitempty"` 35 | ProcessedObjects uint64 `json:"processedObjects,omitempty"` 36 | } 37 | 38 | type jsonAnalyticsWarning struct { 39 | Code uint32 `json:"code"` 40 | Message string `json:"msg"` 41 | } 42 | 43 | type jsonAnalyticsResponse struct { 44 | RequestID string `json:"requestID"` 45 | ClientContextID string `json:"clientContextID"` 46 | Status string `json:"status"` 47 | Warnings []jsonAnalyticsWarning `json:"warnings"` 48 | Metrics jsonAnalyticsMetrics `json:"metrics"` 49 | Signature interface{} `json:"signature"` 50 | } 51 | 52 | func (ap *analyticsProviderCore) AnalyticsQuery(statement string, scope *Scope, opts *AnalyticsOptions) (*AnalyticsResult, error) { 53 | if opts == nil { 54 | opts = &AnalyticsOptions{} 55 | } 56 | 57 | span := ap.tracer.createSpan(opts.ParentSpan, "analytics", "analytics") 58 | span.SetAttribute("db.statement", statement) 59 | if scope != nil { 60 | span.SetAttribute("db.name", scope.BucketName()) 61 | span.SetAttribute("db.couchbase.scope", scope.Name()) 62 | } 63 | defer span.End() 64 | 65 | timeout := opts.Timeout 66 | if opts.Timeout == 0 { 67 | timeout = ap.analyticsTimeout 68 | } 69 | deadline := time.Now().Add(timeout) 70 | 71 | retryStrategy := ap.retryStrategyWrapper 72 | if opts.RetryStrategy != nil { 73 | retryStrategy = newCoreRetryStrategyWrapper(opts.RetryStrategy) 74 | } 75 | 76 | queryOpts, err := opts.toMap() 77 | if err != nil { 78 | return nil, &AnalyticsError{ 79 | InnerError: wrapError(err, "failed to generate query options"), 80 | Statement: statement, 81 | ClientContextID: opts.ClientContextID, 82 | } 83 | } 84 | 85 | var priorityInt int32 86 | if opts.Priority { 87 | priorityInt = -1 88 | } 89 | 90 | queryOpts["statement"] = statement 91 | if scope != nil { 92 | queryOpts["query_context"] = fmt.Sprintf("default:`%s`.`%s`", scope.BucketName(), scope.Name()) 93 | } 94 | 95 | eSpan := ap.tracer.createSpan(span, "request_encoding", "") 96 | reqBytes, err := json.Marshal(queryOpts) 97 | eSpan.End() 98 | if err != nil { 99 | return nil, &AnalyticsError{ 100 | InnerError: wrapError(err, "failed to marshall query body"), 101 | Statement: maybeGetAnalyticsOption(queryOpts, "statement"), 102 | ClientContextID: maybeGetAnalyticsOption(queryOpts, "client_context_id"), 103 | } 104 | } 105 | 106 | res, err := ap.provider.AnalyticsQuery(opts.Context, gocbcore.AnalyticsQueryOptions{ 107 | Payload: reqBytes, 108 | Priority: int(priorityInt), 109 | RetryStrategy: retryStrategy, 110 | Deadline: deadline, 111 | TraceContext: span.Context(), 112 | User: opts.Internal.User, 113 | }) 114 | if err != nil { 115 | return nil, maybeEnhanceAnalyticsError(err) 116 | } 117 | 118 | return newAnalyticsResult(res), nil 119 | } 120 | -------------------------------------------------------------------------------- /analyticsquery_options.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | // AnalyticsScanConsistency indicates the level of data consistency desired for an analytics query. 12 | type AnalyticsScanConsistency uint 13 | 14 | const ( 15 | // AnalyticsScanConsistencyNotBounded indicates no data consistency is required. 16 | AnalyticsScanConsistencyNotBounded AnalyticsScanConsistency = iota + 1 17 | // AnalyticsScanConsistencyRequestPlus indicates that request-level data consistency is required. 18 | AnalyticsScanConsistencyRequestPlus 19 | ) 20 | 21 | // AnalyticsOptions is the set of options available to an Analytics query. 22 | type AnalyticsOptions struct { 23 | // ClientContextID provides a unique ID for this query which can be used matching up requests between connectionManager and 24 | // server. If not provided will be assigned a uuid value. 25 | ClientContextID string 26 | 27 | // Priority sets whether this query should be assigned as high priority by the analytics engine. 28 | Priority bool 29 | PositionalParameters []interface{} 30 | NamedParameters map[string]interface{} 31 | Readonly bool 32 | ScanConsistency AnalyticsScanConsistency 33 | 34 | // Raw provides a way to provide extra parameters in the request body for the query. 35 | Raw map[string]interface{} 36 | 37 | Timeout time.Duration 38 | RetryStrategy RetryStrategy 39 | 40 | ParentSpan RequestSpan 41 | 42 | // Using a deadlined Context alongside a Timeout will cause the shorter of the two to cause cancellation, this 43 | // also applies to global level timeouts. 44 | // UNCOMMITTED: This API may change in the future. 45 | Context context.Context 46 | 47 | // Internal: This should never be used and is not supported. 48 | Internal struct { 49 | User string 50 | } 51 | } 52 | 53 | func (opts *AnalyticsOptions) toMap() (map[string]interface{}, error) { 54 | execOpts := make(map[string]interface{}) 55 | 56 | if opts.ClientContextID == "" { 57 | execOpts["client_context_id"] = uuid.New().String() 58 | } else { 59 | execOpts["client_context_id"] = opts.ClientContextID 60 | } 61 | 62 | if opts.ScanConsistency != 0 { 63 | if opts.ScanConsistency == AnalyticsScanConsistencyNotBounded { 64 | execOpts["scan_consistency"] = "not_bounded" 65 | } else if opts.ScanConsistency == AnalyticsScanConsistencyRequestPlus { 66 | execOpts["scan_consistency"] = "request_plus" 67 | } else { 68 | return nil, makeInvalidArgumentsError("unexpected consistency option") 69 | } 70 | } 71 | 72 | if opts.PositionalParameters != nil && opts.NamedParameters != nil { 73 | return nil, makeInvalidArgumentsError("positional and named parameters must be used exclusively") 74 | } 75 | 76 | if opts.PositionalParameters != nil { 77 | execOpts["args"] = opts.PositionalParameters 78 | } 79 | 80 | if opts.NamedParameters != nil { 81 | for key, value := range opts.NamedParameters { 82 | if !strings.HasPrefix(key, "$") { 83 | key = "$" + key 84 | } 85 | execOpts[key] = value 86 | } 87 | } 88 | 89 | if opts.Readonly { 90 | execOpts["readonly"] = true 91 | } 92 | 93 | if opts.Raw != nil { 94 | for k, v := range opts.Raw { 95 | execOpts[k] = v 96 | } 97 | } 98 | 99 | return execOpts, nil 100 | } 101 | -------------------------------------------------------------------------------- /asyncopmanager.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | gocbcore "github.com/couchbase/gocbcore/v10" 6 | ) 7 | 8 | type asyncOpManager struct { 9 | signal chan struct{} 10 | 11 | cancelCh chan struct{} 12 | wasResolved bool 13 | ctx context.Context 14 | } 15 | 16 | func (m *asyncOpManager) SetCancelCh(cancelCh chan struct{}) { 17 | m.cancelCh = cancelCh 18 | } 19 | 20 | func (m *asyncOpManager) Reject() { 21 | m.signal <- struct{}{} 22 | } 23 | 24 | func (m *asyncOpManager) Resolve() { 25 | m.wasResolved = true 26 | m.signal <- struct{}{} 27 | } 28 | 29 | func (m *asyncOpManager) Wait(op gocbcore.PendingOp, err error) error { 30 | if err != nil { 31 | return err 32 | } 33 | 34 | select { 35 | case <-m.signal: 36 | // Good to go 37 | case <-m.ctx.Done(): 38 | op.Cancel() 39 | <-m.signal 40 | case <-m.cancelCh: 41 | op.Cancel() 42 | <-m.signal 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func newAsyncOpManager(ctx context.Context) *asyncOpManager { 49 | if ctx == nil { 50 | ctx = context.Background() 51 | } 52 | return &asyncOpManager{ 53 | signal: make(chan struct{}, 1), 54 | ctx: ctx, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bucket_collectionsmgrv2_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | func (suite *IntegrationTestSuite) TestCollectionManagerCrudV2() { 4 | suite.runCollectionManagerCrudTest(true) 5 | } 6 | 7 | func (suite *IntegrationTestSuite) TestDropNonExistentScopeV2() { 8 | suite.runDropNonExistentScopeTest(true) 9 | } 10 | 11 | func (suite *IntegrationTestSuite) TestDropNonExistentCollectionV2() { 12 | suite.runTestDropNonExistentCollectionTest(true) 13 | } 14 | 15 | func (suite *IntegrationTestSuite) TestCollectionsAreNotPresentV2() { 16 | suite.runCollectionsAreNotPresentTest(true) 17 | } 18 | 19 | func (suite *IntegrationTestSuite) TestDropScopesAreNotExistV2() { 20 | suite.runDropScopesAreNotExistTest(true) 21 | } 22 | 23 | func (suite *IntegrationTestSuite) TestGetAllScopesV2() { 24 | suite.runGetAllScopesTest(true) 25 | } 26 | 27 | func (suite *IntegrationTestSuite) TestCollectionsInBucketV2() { 28 | suite.runCollectionsInBucketTest(true) 29 | } 30 | 31 | func (suite *IntegrationTestSuite) TestNumberOfCollectionsInScopeV2() { 32 | suite.runNumberOfCollectionInScopeTest(true) 33 | } 34 | 35 | func (suite *IntegrationTestSuite) TestMaxNumberOfCollectionsInScopeV2() { 36 | suite.runMaxNumberOfCollectionsInScopeTest(true) 37 | } 38 | 39 | func (suite *IntegrationTestSuite) TestCollectionHistoryRetentionTestV2() { 40 | suite.runCollectionHistoryRetentionTest(true) 41 | } 42 | 43 | func (suite *IntegrationTestSuite) TestCollectionHistoryRetentionUnsupportedV2() { 44 | suite.runCollectionHistoryRetentionUnsupportedTest(true) 45 | } 46 | 47 | func (suite *IntegrationTestSuite) TestCreateCollectionWithMaxExpiryAsNoExpiryV2() { 48 | suite.runCreateCollectionWithMaxExpiryAsNoExpiryTest(true) 49 | } 50 | 51 | func (suite *IntegrationTestSuite) TestUpdateCollectionWithMaxExpiryAsNoExpiryV2() { 52 | suite.runUpdateCollectionWithMaxExpiryAsNoExpiryTest(true) 53 | } 54 | 55 | func (suite *UnitTestSuite) TestGetAllScopesMgmtRequestFailsV2() { 56 | suite.runGetAllScopesMgmtRequestFailsTest(true) 57 | } 58 | -------------------------------------------------------------------------------- /bucket_internal.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/couchbase/gocbcore/v10" 4 | 5 | type kvCapabilityVerifier interface { 6 | BucketCapabilityStatus(cap gocbcore.BucketCapability) gocbcore.CapabilityStatus 7 | } 8 | 9 | // InternalBucket is used for internal functionality. 10 | // Internal: This should never be used and is not supported. 11 | type InternalBucket struct { 12 | bucket *Bucket 13 | } 14 | 15 | // Internal returns an InternalBucket. 16 | // Internal: This should never be used and is not supported. 17 | func (b *Bucket) Internal() *InternalBucket { 18 | return &InternalBucket{bucket: b} 19 | } 20 | 21 | // IORouter returns the collection's internal core router. 22 | func (ib *InternalBucket) IORouter() (*gocbcore.Agent, error) { 23 | return ib.bucket.connectionManager.connection(ib.bucket.Name()) 24 | } 25 | 26 | // HasCapabilityStatus verifies whether support for a server capability is in a given state. 27 | func (ib *InternalBucket) CapabilityStatus(cap Capability) (CapabilityStatus, error) { 28 | switch cap { 29 | case CapabilityCreateAsDeleted: 30 | return ib.bucketCapabilityStatus(gocbcore.BucketCapabilityCreateAsDeleted) 31 | case CapabilityDurableWrites: 32 | return ib.bucketCapabilityStatus(gocbcore.BucketCapabilityDurableWrites) 33 | case CapabilityReplaceBodyWithXattr: 34 | return ib.bucketCapabilityStatus(gocbcore.BucketCapabilityReplaceBodyWithXattr) 35 | default: 36 | return CapabilityStatusUnsupported, nil 37 | } 38 | } 39 | 40 | func (ib *InternalBucket) bucketCapabilityStatus(capability gocbcore.BucketCapability) (CapabilityStatus, error) { 41 | provider, err := ib.bucket.getKvCapabilitiesProvider() 42 | if err != nil { 43 | return 0, err 44 | } 45 | 46 | return CapabilityStatus(provider.BucketCapabilityStatus(capability)), nil 47 | } 48 | -------------------------------------------------------------------------------- /bucket_internal_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/couchbase/gocbcore/v10" 4 | 5 | func (suite *UnitTestSuite) TestCollectionInternal_BucketCapabilityStatus_Supported() { 6 | provider := new(mockKvCapabilityVerifier) 7 | provider.On( 8 | "BucketCapabilityStatus", 9 | gocbcore.BucketCapabilityDurableWrites, 10 | ).Return(gocbcore.CapabilityStatusSupported) 11 | 12 | cli := new(mockConnectionManager) 13 | cli.On("getKvCapabilitiesProvider", "mock").Return(provider, nil) 14 | 15 | b := suite.bucket("mock", suite.defaultTimeoutConfig(), cli) 16 | 17 | ib := b.Internal() 18 | status, err := ib.CapabilityStatus(CapabilityDurableWrites) 19 | suite.Require().Nil(err) 20 | suite.Assert().Equal(CapabilityStatusSupported, status) 21 | } 22 | 23 | func (suite *UnitTestSuite) TestCollectionInternal_BucketCapabilityStatus_Unsupported() { 24 | provider := new(mockKvCapabilityVerifier) 25 | provider.On( 26 | "BucketCapabilityStatus", 27 | gocbcore.BucketCapabilityDurableWrites, 28 | ).Return(gocbcore.CapabilityStatusUnsupported) 29 | 30 | cli := new(mockConnectionManager) 31 | cli.On("getKvCapabilitiesProvider", "mock").Return(provider, nil) 32 | 33 | b := suite.bucket("mock", suite.defaultTimeoutConfig(), cli) 34 | 35 | ib := b.Internal() 36 | status, err := ib.CapabilityStatus(CapabilityDurableWrites) 37 | suite.Require().Nil(err) 38 | suite.Assert().Equal(CapabilityStatusUnsupported, status) 39 | } 40 | -------------------------------------------------------------------------------- /bucket_ping.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | // EndpointPingReport represents a single entry in a ping report. 10 | type EndpointPingReport struct { 11 | ID string 12 | Local string 13 | Remote string 14 | State PingState 15 | Error string 16 | Namespace string 17 | Latency time.Duration 18 | } 19 | 20 | // PingResult encapsulates the details from a executed ping operation. 21 | type PingResult struct { 22 | ID string 23 | Services map[ServiceType][]EndpointPingReport 24 | 25 | sdk string 26 | } 27 | 28 | type jsonEndpointPingReport struct { 29 | ID string `json:"id,omitempty"` 30 | Local string `json:"local,omitempty"` 31 | Remote string `json:"remote,omitempty"` 32 | State string `json:"state,omitempty"` 33 | Error string `json:"error,omitempty"` 34 | Namespace string `json:"namespace,omitempty"` 35 | LatencyUs uint64 `json:"latency_us"` 36 | } 37 | 38 | type jsonPingReport struct { 39 | Version uint16 `json:"version"` 40 | SDK string `json:"sdk,omitempty"` 41 | ID string `json:"id,omitempty"` 42 | Services map[string][]jsonEndpointPingReport `json:"services,omitempty"` 43 | } 44 | 45 | // MarshalJSON generates a JSON representation of this ping report. 46 | func (report *PingResult) MarshalJSON() ([]byte, error) { 47 | jsonReport := jsonPingReport{ 48 | Version: 2, 49 | SDK: report.sdk, 50 | ID: report.ID, 51 | Services: make(map[string][]jsonEndpointPingReport), 52 | } 53 | 54 | for serviceType, serviceInfo := range report.Services { 55 | serviceStr := serviceTypeToString(serviceType) 56 | if _, ok := jsonReport.Services[serviceStr]; !ok { 57 | jsonReport.Services[serviceStr] = make([]jsonEndpointPingReport, 0) 58 | } 59 | 60 | for _, service := range serviceInfo { 61 | jsonReport.Services[serviceStr] = append(jsonReport.Services[serviceStr], jsonEndpointPingReport{ 62 | ID: service.ID, 63 | Local: service.Local, 64 | Remote: service.Remote, 65 | State: pingStateToString(service.State), 66 | Error: service.Error, 67 | Namespace: service.Namespace, 68 | LatencyUs: uint64(service.Latency / time.Nanosecond), 69 | }) 70 | } 71 | } 72 | 73 | return json.Marshal(&jsonReport) 74 | } 75 | 76 | // PingOptions are the options available to the Ping operation. 77 | type PingOptions struct { 78 | ServiceTypes []ServiceType 79 | ReportID string 80 | Timeout time.Duration 81 | ParentSpan RequestSpan 82 | 83 | // Using a deadlined Context alongside a Timeout will cause the shorter of the two to cause cancellation, this 84 | // also applies to global level timeouts. 85 | // UNCOMMITTED: This API may change in the future. 86 | Context context.Context 87 | } 88 | 89 | // Ping will ping a list of services and verify they are active and 90 | // responding in an acceptable period of time. 91 | func (b *Bucket) Ping(opts *PingOptions) (*PingResult, error) { 92 | return autoOpControl(b.diagnosticsController(), "", func(provider diagnosticsProvider) (*PingResult, error) { 93 | if opts == nil { 94 | opts = &PingOptions{} 95 | } 96 | 97 | return provider.Ping(opts) 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /bucket_ping_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/stretchr/testify/mock" 8 | 9 | gocbcore "github.com/couchbase/gocbcore/v10" 10 | ) 11 | 12 | func (suite *UnitTestSuite) TestPingAll() { 13 | expectedResults := map[gocbcore.ServiceType][]gocbcore.EndpointPingResult{ 14 | gocbcore.MemdService: { 15 | { 16 | Endpoint: "server1", 17 | Latency: 25 * time.Millisecond, 18 | Scope: "default", 19 | State: gocbcore.PingStateOK, 20 | }, 21 | { 22 | Endpoint: "server2", 23 | Latency: 42 * time.Millisecond, 24 | Error: errors.New("something"), 25 | Scope: "default", 26 | State: gocbcore.PingStateError, 27 | }, 28 | { 29 | Endpoint: "server3", 30 | Latency: 100 * time.Millisecond, 31 | Error: gocbcore.ErrUnambiguousTimeout, 32 | Scope: "default", 33 | State: gocbcore.PingStateTimeout, 34 | }, 35 | }, 36 | gocbcore.N1qlService: { 37 | { 38 | Endpoint: "server1", 39 | Latency: 50 * time.Millisecond, 40 | Scope: "default", 41 | State: gocbcore.PingStateOK, 42 | }, 43 | { 44 | Endpoint: "server2", 45 | Latency: 34 * time.Millisecond, 46 | Error: errors.New("something"), 47 | Scope: "default", 48 | State: gocbcore.PingStateError, 49 | }, 50 | }, 51 | gocbcore.CbasService: { 52 | { 53 | Endpoint: "server1", 54 | Latency: 50 * time.Millisecond, 55 | Scope: "default", 56 | State: gocbcore.PingStateOK, 57 | }, 58 | }, 59 | gocbcore.FtsService: { 60 | { 61 | Endpoint: "server3", 62 | Latency: 20 * time.Millisecond, 63 | Scope: "default", 64 | State: gocbcore.PingStateOK, 65 | }, 66 | }, 67 | gocbcore.CapiService: { 68 | { 69 | Endpoint: "server2", 70 | Latency: 30 * time.Millisecond, 71 | Error: gocbcore.ErrUnambiguousTimeout, 72 | Scope: "default", 73 | State: gocbcore.PingStateTimeout, 74 | }, 75 | }, 76 | } 77 | pingResult := &gocbcore.PingResult{ 78 | ConfigRev: 64, 79 | Services: expectedResults, 80 | } 81 | 82 | c := suite.pingCluster(func(args mock.Arguments) { 83 | if len(args) != 2 { 84 | suite.T().Fatalf("Expected options to contain two arguments, was: %v", args) 85 | } 86 | opts := args.Get(1).(gocbcore.PingOptions) 87 | 88 | if len(opts.ServiceTypes) != 0 { 89 | suite.T().Errorf("Expected service types to be len 0 but was %v", opts.ServiceTypes) 90 | } 91 | }, pingResult, nil) 92 | 93 | b := suite.bucket("mock", suite.defaultTimeoutConfig(), c.connectionManager) 94 | 95 | report, err := b.Ping(nil) 96 | if err != nil { 97 | suite.T().Fatalf("Expected ping to not return error but was %v", err) 98 | } 99 | 100 | if report.ID == "" { 101 | suite.T().Fatalf("Report ID was empty") 102 | } 103 | 104 | if len(report.Services) != 5 { 105 | suite.T().Fatalf("Expected services length to be 5 but was %d", len(report.Services)) 106 | } 107 | 108 | for serviceType, services := range report.Services { 109 | expectedServices, ok := expectedResults[gocbcore.ServiceType(serviceType)] 110 | if !ok { 111 | suite.T().Errorf("Unexpected service type in result: %v", serviceType) 112 | continue 113 | } 114 | for i, service := range services { 115 | expectedService := expectedServices[i] 116 | 117 | suite.Assert().Equal(expectedService.Latency, service.Latency) 118 | suite.Assert().Equal(expectedService.Scope, service.Namespace) 119 | if expectedService.Error == nil { 120 | suite.Assert().Empty(service.Error) 121 | } else { 122 | suite.Assert().Equal(expectedService.Error.Error(), service.Error) 123 | } 124 | suite.Assert().Equal(PingState(expectedService.State), service.State) 125 | suite.Assert().Equal(expectedService.Endpoint, service.Remote) 126 | suite.Assert().Equal(expectedService.ID, service.ID) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /bucket_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | func (suite *IntegrationTestSuite) TestBucketWaitUntilReady() { 9 | suite.skipIfUnsupported(WaitUntilReadyFeature) 10 | 11 | c, err := Connect(globalConfig.connstr, ClusterOptions{ 12 | Authenticator: globalConfig.Auth, 13 | SecurityConfig: globalConfig.SecurityConfig, 14 | }) 15 | suite.Require().Nil(err, err) 16 | defer c.Close(nil) 17 | 18 | b := c.Bucket(globalConfig.Bucket) 19 | 20 | err = b.WaitUntilReady(globalCluster.waitUntilReadyTimeout(), nil) 21 | suite.Require().Nil(err, err) 22 | 23 | // Just test that we can use the bucket. 24 | _, err = b.DefaultCollection().Upsert(generateDocId("TestBucketWaitUntilReady"), "test", nil) 25 | suite.Require().Nil(err, err) 26 | } 27 | 28 | func (suite *IntegrationTestSuite) TestBucketWaitUntilReadyInvalidAuth() { 29 | suite.skipIfUnsupported(WaitUntilReadyFeature) 30 | suite.skipIfUnsupported(WaitUntilReadyAuthFailFeature) 31 | 32 | c, err := Connect(globalConfig.connstr, ClusterOptions{ 33 | Authenticator: PasswordAuthenticator{ 34 | Username: globalConfig.User, 35 | Password: globalConfig.Password + "nopethisshouldntwork", 36 | }, 37 | SecurityConfig: globalConfig.SecurityConfig, 38 | }) 39 | suite.Require().Nil(err, err) 40 | defer c.Close(nil) 41 | 42 | b := c.Bucket(globalConfig.Bucket) 43 | 44 | start := time.Now() 45 | err = b.WaitUntilReady(globalCluster.waitUntilReadyTimeout(), nil) 46 | if !errors.Is(err, ErrUnambiguousTimeout) { 47 | suite.T().Fatalf("Expected unambiguous timeout error but was %v", err) 48 | } 49 | 50 | elapsed := time.Since(start) 51 | suite.Assert().GreaterOrEqual(int64(elapsed), int64(globalCluster.waitUntilReadyTimeout())) 52 | suite.Assert().LessOrEqual(int64(elapsed), int64(globalCluster.waitUntilReadyTimeout()+time.Second)) 53 | } 54 | 55 | func (suite *IntegrationTestSuite) TestBucketWaitUntilReadyFastFailAuth() { 56 | suite.skipIfUnsupported(WaitUntilReadyFeature) 57 | suite.skipIfUnsupported(WaitUntilReadyFastFailFeature) 58 | 59 | c, err := Connect(globalConfig.connstr, ClusterOptions{ 60 | Authenticator: PasswordAuthenticator{ 61 | Username: globalConfig.User, 62 | Password: "thisisaprettyunlikelypasswordtobeused", 63 | }, 64 | SecurityConfig: globalConfig.SecurityConfig, 65 | }) 66 | suite.Require().Nil(err, err) 67 | defer c.Close(nil) 68 | 69 | b := c.Bucket(globalConfig.Bucket) 70 | 71 | err = b.WaitUntilReady(globalCluster.waitUntilReadyTimeout(), &WaitUntilReadyOptions{ 72 | RetryStrategy: newFailFastRetryStrategy(), 73 | }) 74 | if !errors.Is(err, ErrAuthenticationFailure) { 75 | suite.T().Fatalf("Expected authentication error but was: %v", err) 76 | } 77 | } 78 | 79 | func (suite *IntegrationTestSuite) TestBucketWaitUntilReadyFastFailConnStr() { 80 | suite.skipIfUnsupported(WaitUntilReadyFeature) 81 | suite.skipIfUnsupported(WaitUntilReadyClusterFeature) 82 | suite.skipIfUnsupported(WaitUntilReadyFastFailFeature) 83 | 84 | c, err := Connect("10.10.10.10", ClusterOptions{ 85 | Authenticator: PasswordAuthenticator{ 86 | Username: globalConfig.User, 87 | Password: globalConfig.Password, 88 | }, 89 | SecurityConfig: globalConfig.SecurityConfig, 90 | }) 91 | suite.Require().Nil(err, err) 92 | defer c.Close(nil) 93 | 94 | b := c.Bucket(globalConfig.Bucket) 95 | 96 | err = b.WaitUntilReady(globalCluster.waitUntilReadyTimeout(), &WaitUntilReadyOptions{ 97 | RetryStrategy: newFailFastRetryStrategy(), 98 | }) 99 | if !errors.Is(err, ErrTimeout) { 100 | suite.T().Fatalf("Expected timeout error but was: %v", err) 101 | } 102 | } 103 | 104 | func (suite *IntegrationTestSuite) TestBucketOpsAfterClusterClose() { 105 | _, b := suite.CreateAndCloseNewClusterAndBucket() 106 | suite.Run("Ping", func() { 107 | _, err := b.Ping(nil) 108 | suite.Require().ErrorIs(err, ErrShutdown) 109 | }) 110 | suite.Run("WaitUntilReady", func() { 111 | err := b.WaitUntilReady(5*time.Second, nil) 112 | suite.Require().ErrorIs(err, ErrShutdown) 113 | }) 114 | suite.Run("Internal.IORouter", func() { 115 | _, err := b.Internal().IORouter() 116 | suite.Require().ErrorIs(err, ErrShutdown) 117 | }) 118 | suite.Run("ViewQuery", func() { 119 | _, b := suite.CreateAndCloseNewClusterAndBucket() 120 | _, err := b.ViewQuery("test", "test", nil) 121 | suite.Require().ErrorIs(err, ErrShutdown) 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /bucketmgmtprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type bucketManagementProvider interface { 4 | GetBucket(bucketName string, opts *GetBucketOptions) (*BucketSettings, error) 5 | GetAllBuckets(opts *GetAllBucketsOptions) (map[string]BucketSettings, error) 6 | CreateBucket(settings CreateBucketSettings, opts *CreateBucketOptions) error 7 | UpdateBucket(settings BucketSettings, opts *UpdateBucketOptions) error 8 | DropBucket(name string, opts *DropBucketOptions) error 9 | FlushBucket(name string, opts *FlushBucketOptions) error 10 | } 11 | -------------------------------------------------------------------------------- /circuitbreaker.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "time" 4 | 5 | // CircuitBreakerCallback is the callback used by the circuit breaker to determine if an error should count toward 6 | // the circuit breaker failure count. 7 | type CircuitBreakerCallback func(error) bool 8 | 9 | // CircuitBreakerConfig are the settings for configuring circuit breakers. 10 | type CircuitBreakerConfig struct { 11 | Disabled bool 12 | VolumeThreshold int64 13 | ErrorThresholdPercentage float64 14 | SleepWindow time.Duration 15 | RollingWindow time.Duration 16 | CompletionCallback CircuitBreakerCallback 17 | CanaryTimeout time.Duration 18 | } 19 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | gocbcore "github.com/couchbase/gocbcore/v10" 5 | "time" 6 | ) 7 | 8 | type connectionManager interface { 9 | connect() error 10 | openBucket(bucketName string) error 11 | buildConfig(cluster *Cluster) error 12 | connection(bucketName string) (*gocbcore.Agent, error) 13 | close() error 14 | 15 | getKvProvider(bucketName string) (kvProvider, error) 16 | getKvBulkProvider(bucketName string) (kvBulkProvider, error) 17 | getKvCapabilitiesProvider(bucketName string) (kvCapabilityVerifier, error) 18 | getViewProvider(bucketName string) (viewProvider, error) 19 | getViewIndexProvider(bucketName string) (viewIndexProvider, error) 20 | getQueryProvider() (queryProvider, error) 21 | getQueryIndexProvider() (queryIndexProvider, error) 22 | getAnalyticsProvider() (analyticsProvider, error) 23 | getAnalyticsIndexProvider() (analyticsIndexProvider, error) 24 | getSearchProvider() (searchProvider, error) 25 | getHTTPProvider(bucketName string) (httpProvider, error) 26 | getDiagnosticsProvider(bucketName string) (diagnosticsProvider, error) 27 | getWaitUntilReadyProvider(bucketName string) (waitUntilReadyProvider, error) 28 | getCollectionsManagementProvider(bucketName string) (collectionsManagementProvider, error) 29 | getBucketManagementProvider() (bucketManagementProvider, error) 30 | getSearchIndexProvider() (searchIndexProvider, error) 31 | getSearchCapabilitiesProvider() (searchCapabilityVerifier, error) 32 | getEventingManagementProvider() (eventingManagementProvider, error) 33 | getUserManagerProvider() (userManagerProvider, error) 34 | getInternalProvider() (internalProvider, error) 35 | 36 | initTransactions(config TransactionsConfig, cluster *Cluster) error 37 | getTransactionsProvider() (transactionsProvider, error) 38 | 39 | getMeter() *meterWrapper 40 | 41 | opController 42 | } 43 | 44 | type opController interface { 45 | MarkOpBeginning() 46 | MarkOpCompleted() 47 | } 48 | 49 | type providerController[P any] struct { 50 | get func() (P, error) 51 | opController 52 | 53 | // Metrics-related fields 54 | meter *meterWrapper 55 | keyspace *keyspace 56 | service string 57 | } 58 | 59 | func autoOpControl[T any, P any](controller *providerController[P], operation string, opFn func(P) (T, error)) (T, error) { 60 | controller.MarkOpBeginning() 61 | defer controller.MarkOpCompleted() 62 | 63 | p, err := controller.get() 64 | if err != nil { 65 | var emptyT T 66 | return emptyT, err 67 | } 68 | 69 | start := time.Now() 70 | retT, err := opFn(p) 71 | 72 | if operation != "" && controller.meter != nil { 73 | defer controller.meter.ValueRecord(controller.service, operation, start, controller.keyspace, err) 74 | } 75 | 76 | if err != nil { 77 | var emptyT T 78 | return emptyT, err 79 | } 80 | 81 | return retT, nil 82 | } 83 | 84 | func autoOpControlErrorOnly[P any](controller *providerController[P], operation string, opFn func(P) error) error { 85 | _, err := autoOpControl(controller, operation, func(provider P) (struct{}, error) { 86 | err := opFn(provider) 87 | return struct{}{}, err 88 | }) 89 | 90 | return err 91 | } 92 | 93 | type newConnectionMgrOptions struct { 94 | tracer *tracerWrapper 95 | meter *meterWrapper 96 | 97 | preferredServerGroup string 98 | } 99 | 100 | func (c *Cluster) newConnectionMgr(protocol string, opts *newConnectionMgrOptions) connectionManager { 101 | switch protocol { 102 | case "couchbase2": 103 | return &psConnectionMgr{ 104 | timeouts: c.timeoutsConfig, 105 | tracer: opts.tracer, 106 | meter: opts.meter, 107 | defaultRetry: c.retryStrategyWrapper.wrapped, 108 | } 109 | default: 110 | return &stdConnectionMgr{ 111 | retryStrategyWrapper: c.retryStrategyWrapper, 112 | transcoder: c.transcoder, 113 | timeouts: c.timeoutsConfig, 114 | tracer: opts.tracer, 115 | meter: opts.meter, 116 | preferredServerGroup: opts.preferredServerGroup, 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cluster_diag.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | ) 9 | 10 | // EndPointDiagnostics represents a single entry in a diagnostics report. 11 | type EndPointDiagnostics struct { 12 | Type ServiceType 13 | ID string 14 | Local string 15 | Remote string 16 | LastActivity time.Time 17 | State EndpointState 18 | Namespace string 19 | } 20 | 21 | // DiagnosticsResult encapsulates the results of a Diagnostics operation. 22 | type DiagnosticsResult struct { 23 | ID string 24 | Services map[string][]EndPointDiagnostics 25 | sdk string 26 | State ClusterState 27 | } 28 | 29 | type jsonDiagnosticEntry struct { 30 | ID string `json:"id,omitempty"` 31 | LastActivityUs uint64 `json:"last_activity_us,omitempty"` 32 | Remote string `json:"remote,omitempty"` 33 | Local string `json:"local,omitempty"` 34 | State string `json:"state,omitempty"` 35 | Details string `json:"details,omitempty"` 36 | Namespace string `json:"namespace,omitempty"` 37 | } 38 | 39 | type jsonDiagnosticReport struct { 40 | Version int16 `json:"version"` 41 | SDK string `json:"sdk,omitempty"` 42 | ID string `json:"id,omitempty"` 43 | Services map[string][]jsonDiagnosticEntry `json:"services"` 44 | State string `json:"state"` 45 | } 46 | 47 | // MarshalJSON generates a JSON representation of this diagnostics report. 48 | func (report *DiagnosticsResult) MarshalJSON() ([]byte, error) { 49 | jsonReport := jsonDiagnosticReport{ 50 | Version: 2, 51 | SDK: report.sdk, 52 | ID: report.ID, 53 | Services: make(map[string][]jsonDiagnosticEntry), 54 | State: clusterStateToString(report.State), 55 | } 56 | 57 | for _, serviceType := range report.Services { 58 | for _, service := range serviceType { 59 | serviceStr := serviceTypeToString(service.Type) 60 | stateStr := endpointStateToString(service.State) 61 | 62 | jsonReport.Services[serviceStr] = append(jsonReport.Services[serviceStr], jsonDiagnosticEntry{ 63 | ID: service.ID, 64 | LastActivityUs: uint64(time.Since(service.LastActivity).Nanoseconds()), 65 | Remote: service.Remote, 66 | Local: service.Local, 67 | State: stateStr, 68 | Details: "", 69 | Namespace: service.Namespace, 70 | }) 71 | } 72 | } 73 | 74 | return json.Marshal(&jsonReport) 75 | } 76 | 77 | // DiagnosticsOptions are the options that are available for use with the Diagnostics operation. 78 | type DiagnosticsOptions struct { 79 | ReportID string 80 | } 81 | 82 | // Diagnostics returns information about the internal state of the SDK. 83 | func (c *Cluster) Diagnostics(opts *DiagnosticsOptions) (*DiagnosticsResult, error) { 84 | return autoOpControl(c.diagnosticsController(), "", func(provider diagnosticsProvider) (*DiagnosticsResult, error) { 85 | if opts == nil { 86 | opts = &DiagnosticsOptions{} 87 | } 88 | 89 | if opts.ReportID == "" { 90 | opts.ReportID = uuid.New().String() 91 | } 92 | 93 | return provider.Diagnostics(opts) 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /cluster_internal.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "github.com/couchbase/gocbcore/v10" 6 | "time" 7 | ) 8 | 9 | // InternalCluster is used for internal functionality. 10 | // Internal: This should never be used and is not supported. 11 | type InternalCluster struct { 12 | cluster *Cluster 13 | } 14 | 15 | // Internal returns an InternalCluster. 16 | // Internal: This should never be used and is not supported. 17 | func (c *Cluster) Internal() *InternalCluster { 18 | return &InternalCluster{ 19 | cluster: c, 20 | } 21 | } 22 | 23 | // NodeMetadata contains information about a node in the cluster. 24 | // Internal: This should never be used and is not supported. 25 | type NodeMetadata struct { 26 | ClusterCompatibility int 27 | ClusterMembership string 28 | CouchAPIBase string 29 | Hostname string 30 | InterestingStats map[string]float64 31 | MCDMemoryAllocated float64 32 | MCDMemoryReserved float64 33 | MemoryFree float64 34 | MemoryTotal float64 35 | OS string 36 | Ports map[string]int 37 | Status string 38 | Uptime int 39 | Version string 40 | ThisNode bool 41 | } 42 | 43 | type jsonClusterCfg struct { 44 | Nodes []jsonNodeMetadata `json:"nodes"` 45 | } 46 | 47 | type jsonNodeMetadata struct { 48 | ClusterCompatibility int `json:"clusterCompatibility"` 49 | ClusterMembership string `json:"clusterMembership"` 50 | CouchAPIBase string `json:"couchApiBase"` 51 | Hostname string `json:"hostname"` 52 | InterestingStats map[string]float64 `json:"interestingStats,omitempty"` 53 | MCDMemoryAllocated float64 `json:"mcdMemoryAllocated"` 54 | MCDMemoryReserved float64 `json:"mcdMemoryReserved"` 55 | MemoryFree float64 `json:"memoryFree"` 56 | MemoryTotal float64 `json:"memoryTotal"` 57 | OS string `json:"os"` 58 | Ports map[string]int `json:"ports"` 59 | Status string `json:"status"` 60 | Uptime int `json:"uptime,string"` 61 | Version string `json:"version"` 62 | ThisNode bool `json:"thisNode,omitempty"` 63 | } 64 | 65 | // GetNodesMetadataOptions is the set of options available to the GetNodesMetadata operation. 66 | // Internal: This should never be used and is not supported. 67 | type GetNodesMetadataOptions struct { 68 | Timeout time.Duration 69 | RetryStrategy RetryStrategy 70 | ParentSpan RequestSpan 71 | 72 | // Using a deadlined Context alongside a Timeout will cause the shorter of the two to cause cancellation, this 73 | // also applies to global level timeouts. 74 | // UNCOMMITTED: This API may change in the future. 75 | Context context.Context 76 | } 77 | 78 | // GetNodesMetadata returns a list of information about nodes in the cluster. 79 | func (ic *InternalCluster) GetNodesMetadata(opts *GetNodesMetadataOptions) ([]NodeMetadata, error) { 80 | return autoOpControl(ic.cluster.internalController(), "", func(provider internalProvider) ([]NodeMetadata, error) { 81 | if opts == nil { 82 | opts = &GetNodesMetadataOptions{} 83 | } 84 | 85 | return provider.GetNodesMetadata(opts) 86 | }) 87 | } 88 | 89 | type clusterLabelsProvider interface { 90 | ClusterLabels() gocbcore.ClusterLabels 91 | } 92 | -------------------------------------------------------------------------------- /cluster_internal_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | func (suite *IntegrationTestSuite) TestInternalClusterGetNodesMetadata() { 4 | suite.skipIfUnsupported(NodesMetadataFeature) 5 | 6 | ic := globalCluster.Internal() 7 | 8 | nodes, err := ic.GetNodesMetadata(nil) 9 | suite.Require().Nil(err, err) 10 | 11 | suite.Assert().GreaterOrEqual(len(nodes), 1) 12 | } 13 | -------------------------------------------------------------------------------- /cluster_ping.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | // Ping will ping a list of services and verify they are active and 4 | // responding in an acceptable period of time. 5 | func (c *Cluster) Ping(opts *PingOptions) (*PingResult, error) { 6 | return autoOpControl(c.diagnosticsController(), "", func(provider diagnosticsProvider) (*PingResult, error) { 7 | if opts == nil { 8 | opts = &PingOptions{} 9 | } 10 | 11 | return provider.Ping(opts) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /collection.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | // Collection represents a single collection. 4 | type Collection struct { 5 | collectionName string 6 | scope string 7 | bucket *Bucket 8 | 9 | timeoutsConfig TimeoutsConfig 10 | 11 | transcoder Transcoder 12 | retryStrategyWrapper *coreRetryStrategyWrapper 13 | compressor *compressor 14 | 15 | useMutationTokens bool 16 | 17 | keyspace keyspace 18 | 19 | opController opController 20 | 21 | getKvProvider func() (kvProvider, error) 22 | getKvBulkProvider func() (kvBulkProvider, error) 23 | getQueryIndexProvider func() (queryIndexProvider, error) 24 | } 25 | 26 | func newCollection(scope *Scope, collectionName string) *Collection { 27 | return &Collection{ 28 | collectionName: collectionName, 29 | scope: scope.Name(), 30 | bucket: scope.bucket, 31 | 32 | timeoutsConfig: scope.timeoutsConfig, 33 | 34 | transcoder: scope.transcoder, 35 | retryStrategyWrapper: scope.retryStrategyWrapper, 36 | compressor: scope.compressor, 37 | 38 | useMutationTokens: scope.useMutationTokens, 39 | 40 | keyspace: keyspace{ 41 | bucketName: scope.BucketName(), 42 | scopeName: scope.Name(), 43 | collectionName: collectionName, 44 | }, 45 | 46 | opController: scope.opController, 47 | 48 | getKvProvider: scope.getKvProvider, 49 | getKvBulkProvider: scope.getKvBulkProvider, 50 | getQueryIndexProvider: scope.getQueryIndexProvider, 51 | } 52 | } 53 | 54 | func (c *Collection) name() string { 55 | return c.collectionName 56 | } 57 | 58 | // ScopeName returns the name of the scope to which this collection belongs. 59 | func (c *Collection) ScopeName() string { 60 | return c.scope 61 | } 62 | 63 | // Bucket returns the bucket to which this collection belongs. 64 | // UNCOMMITTED: This API may change in the future. 65 | func (c *Collection) Bucket() *Bucket { 66 | return c.bucket 67 | } 68 | 69 | // Name returns the name of the collection. 70 | func (c *Collection) Name() string { 71 | return c.collectionName 72 | } 73 | 74 | // QueryIndexes returns a CollectionQueryIndexManager for managing query indexes. 75 | // UNCOMMITTED: This API may change in the future. 76 | func (c *Collection) QueryIndexes() *CollectionQueryIndexManager { 77 | return &CollectionQueryIndexManager{ 78 | controller: &providerController[queryIndexProvider]{ 79 | get: c.getQueryIndexProvider, 80 | opController: c.opController, 81 | 82 | meter: c.bucket.connectionManager.getMeter(), 83 | service: serviceValueManagement, 84 | keyspace: &c.keyspace, 85 | }, 86 | 87 | c: c, 88 | } 89 | } 90 | 91 | func (c *Collection) startKvOpTrace(operationName string, parentSpan RequestSpan, tracer *tracerWrapper, noAttributes bool) RequestSpan { 92 | var span RequestSpan 93 | if noAttributes { 94 | span = tracer.createSpan(parentSpan, operationName, "") 95 | } else { 96 | span = tracer.createSpan(parentSpan, operationName, "kv") 97 | span.SetAttribute(spanAttribOperationKey, operationName) 98 | span.SetAttribute(spanAttribDBNameKey, c.bucket.Name()) 99 | span.SetAttribute(spanAttribDBCollectionNameKey, c.Name()) 100 | span.SetAttribute(spanAttribDBScopeNameKey, c.ScopeName()) 101 | } 102 | return span 103 | } 104 | 105 | func (c *Collection) bucketName() string { 106 | return c.bucket.Name() 107 | } 108 | 109 | func (c *Collection) isDefault() bool { 110 | return (c.scope == "" || c.scope == "_default") && (c.collectionName == "" || c.collectionName == "_default") 111 | } 112 | 113 | func (c *Collection) kvController() *providerController[kvProvider] { 114 | var meter *meterWrapper 115 | if c.bucket.connectionManager != nil { 116 | meter = c.bucket.connectionManager.getMeter() 117 | } 118 | 119 | return &providerController[kvProvider]{ 120 | get: c.getKvProvider, 121 | opController: c.opController, 122 | 123 | meter: meter, 124 | service: serviceValueKV, 125 | keyspace: &c.keyspace, 126 | } 127 | } 128 | 129 | func (c *Collection) kvBulkController() *providerController[kvBulkProvider] { 130 | return &providerController[kvBulkProvider]{ 131 | get: c.getKvBulkProvider, 132 | opController: c.opController, 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /collection_bulk_bench_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | ) 9 | 10 | func BenchmarkBulkUpsert(b *testing.B) { 11 | b.ReportAllocs() 12 | // Generate 256 bytes of random data for the document 13 | randomBytes := make([]byte, 256) 14 | for i := 0; i < len(randomBytes); i++ { 15 | randomBytes[i] = byte(i) 16 | } 17 | doc := benchDoc{Data: randomBytes} 18 | 19 | // We need to ensure that connections are up and ready before starting the benchmark 20 | err := globalBucket.WaitUntilReady(5*time.Second, &WaitUntilReadyOptions{ 21 | ServiceTypes: []ServiceType{ServiceTypeKeyValue}, 22 | }) 23 | if err != nil { 24 | b.Fatalf("Wait until ready failed: %v", err) 25 | } 26 | 27 | bulkSize := 200 28 | 29 | b.ResetTimer() 30 | b.RunParallel(func(pb *testing.PB) { 31 | for pb.Next() { 32 | var ops []BulkOp 33 | for i := 0; i < bulkSize; i++ { 34 | ops = append(ops, &UpsertOp{ 35 | ID: uuid.NewString()[:6], 36 | Value: doc, 37 | }) 38 | } 39 | 40 | err := globalCollection.Do(ops, nil) 41 | if err != nil { 42 | b.Fatalf("failed to bulk upsert %v", err) 43 | return 44 | } 45 | 46 | for _, op := range ops { 47 | item := op.(*UpsertOp) 48 | if item.Err != nil { 49 | b.Fatalf("bulk upsert op failed %s: %v", item.ID, err) 50 | } 51 | } 52 | } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /collection_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | func (suite *UnitTestSuite) TestCollectionName() { 4 | bName := "bucket" 5 | sName := "scope" 6 | cName := "collection" 7 | 8 | b := suite.bucket(bName, suite.defaultTimeoutConfig(), nil) 9 | s := b.Scope(sName) 10 | c := s.Collection(cName) 11 | 12 | suite.Assert().Equal(bName, c.bucketName()) 13 | suite.Assert().Equal(sName, c.ScopeName()) 14 | suite.Assert().Equal(cName, c.Name()) 15 | } 16 | 17 | func (suite *UnitTestSuite) TestDefaultScopeCollectionName() { 18 | bName := "bucket" 19 | cName := "collection" 20 | 21 | b := suite.bucket(bName, suite.defaultTimeoutConfig(), nil) 22 | c := b.Collection(cName) 23 | 24 | suite.Assert().Equal(bName, c.bucketName()) 25 | suite.Assert().Equal("_default", c.ScopeName()) 26 | suite.Assert().Equal(cName, c.Name()) 27 | } 28 | 29 | func (suite *UnitTestSuite) TestDefaultScopeDefaultCollectionName() { 30 | bName := "bucket" 31 | 32 | b := suite.bucket(bName, suite.defaultTimeoutConfig(), nil) 33 | c := b.DefaultCollection() 34 | 35 | suite.Assert().Equal(bName, c.bucketName()) 36 | suite.Assert().Equal("_default", c.ScopeName()) 37 | suite.Assert().Equal("_default", c.Name()) 38 | } 39 | -------------------------------------------------------------------------------- /collectionsmgmtprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type collectionsManagementProvider interface { 4 | GetAllScopes(opts *GetAllScopesOptions) ([]ScopeSpec, error) 5 | CreateCollection(scopeName string, collectionName string, settings *CreateCollectionSettings, opts *CreateCollectionOptions) error 6 | UpdateCollection(scopeName string, collectionName string, settings UpdateCollectionSettings, opts *UpdateCollectionOptions) error 7 | DropCollection(scopeName string, collectionName string, opts *DropCollectionOptions) error 8 | CreateScope(scopeName string, opts *CreateScopeOptions) error 9 | DropScope(scopeName string, opts *DropScopeOptions) error 10 | } 11 | -------------------------------------------------------------------------------- /compressor.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/golang/snappy" 4 | 5 | type compressor struct { 6 | CompressionEnabled bool 7 | CompressionMinSize uint32 8 | CompressionMinRatio float64 9 | } 10 | 11 | type possiblyCompressedResponse interface { 12 | GetContentUncompressed() []byte 13 | GetContentCompressed() []byte 14 | } 15 | 16 | func (c *compressor) Compress(val []byte) ([]byte, bool) { 17 | if !c.CompressionEnabled { 18 | return val, false 19 | } 20 | 21 | valueLen := len(val) 22 | if valueLen > int(c.CompressionMinSize) { 23 | compressedValue := snappy.Encode(nil, val) 24 | if float64(len(compressedValue))/float64(valueLen) <= c.CompressionMinRatio { 25 | return compressedValue, true 26 | } 27 | } 28 | 29 | return val, false 30 | } 31 | 32 | func (c *compressor) Decompress(val possiblyCompressedResponse) ([]byte, error) { 33 | if val.GetContentUncompressed() != nil { 34 | return val.GetContentUncompressed(), nil 35 | } 36 | 37 | newValue, err := snappy.Decode(nil, val.GetContentCompressed()) 38 | if err != nil { 39 | return nil, wrapError(err, "failed to decode content") 40 | } 41 | 42 | return newValue, nil 43 | } 44 | -------------------------------------------------------------------------------- /compressor_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type maybeCompressedResponseValue struct { 4 | compressed []byte 5 | uncompressed []byte 6 | } 7 | 8 | func (c *maybeCompressedResponseValue) GetContentCompressed() []byte { 9 | return c.compressed 10 | } 11 | 12 | func (c *maybeCompressedResponseValue) GetContentUncompressed() []byte { 13 | return c.uncompressed 14 | } 15 | 16 | func (suite *UnitTestSuite) TestCompressor() { 17 | suite.Run("Value should compress and decompress", func() { 18 | var val []byte 19 | for i := 0; i < 500; i++ { 20 | val = append(val, byte(i)) 21 | } 22 | compressor := &compressor{ 23 | CompressionEnabled: true, 24 | CompressionMinRatio: 1.0, 25 | CompressionMinSize: 5, 26 | } 27 | 28 | after, isCompressed := compressor.Compress(val) 29 | suite.Assert().True(isCompressed) 30 | suite.Assert().NotEqual(val, after) 31 | 32 | decompressed, err := compressor.Decompress(&maybeCompressedResponseValue{compressed: after}) 33 | suite.Require().NoError(err) 34 | suite.Assert().Equal(val, decompressed) 35 | }) 36 | 37 | suite.Run("Value too small", func() { 38 | var val []byte 39 | for i := 0; i < 2; i++ { 40 | val = append(val, byte(i)) 41 | } 42 | compressor := &compressor{ 43 | CompressionEnabled: true, 44 | CompressionMinRatio: 1.0, 45 | CompressionMinSize: 5, 46 | } 47 | 48 | after, isCompressed := compressor.Compress(val) 49 | suite.Assert().False(isCompressed) 50 | suite.Assert().Equal(val, after) 51 | }) 52 | 53 | suite.Run("Value above ratio", func() { 54 | var val []byte 55 | for i := 0; i < 50; i++ { 56 | val = append(val, byte(i)) 57 | } 58 | compressor := &compressor{ 59 | CompressionEnabled: true, 60 | CompressionMinRatio: 0.05, 61 | CompressionMinSize: 5, 62 | } 63 | 64 | after, isCompressed := compressor.Compress(val) 65 | suite.Assert().False(isCompressed) 66 | suite.Assert().Equal(val, after) 67 | }) 68 | 69 | suite.Run("Compression disabled", func() { 70 | var val []byte 71 | for i := 0; i < 500; i++ { 72 | val = append(val, byte(i)) 73 | } 74 | compressor := &compressor{ 75 | CompressionEnabled: false, 76 | CompressionMinRatio: 1.0, 77 | CompressionMinSize: 5, 78 | } 79 | 80 | after, isCompressed := compressor.Compress(val) 81 | suite.Assert().False(isCompressed) 82 | suite.Assert().Equal(val, after) 83 | }) 84 | 85 | suite.Run("Decompress uncompressed value", func() { 86 | var val []byte 87 | for i := 0; i < 500; i++ { 88 | val = append(val, byte(i)) 89 | } 90 | 91 | compressor := &compressor{} 92 | 93 | decompressed, err := compressor.Decompress(&maybeCompressedResponseValue{uncompressed: val}) 94 | suite.Require().NoError(err) 95 | suite.Assert().Equal(val, decompressed) 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /config_profile.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "time" 4 | 5 | var developmentProfile = ClusterOptions{ 6 | TimeoutsConfig: TimeoutsConfig{ 7 | KVTimeout: 20 * time.Second, 8 | ConnectTimeout: 20 * time.Second, 9 | KVDurableTimeout: 20 * time.Second, 10 | KVScanTimeout: 20 * time.Second, 11 | ViewTimeout: 120 * time.Second, 12 | AnalyticsTimeout: 120 * time.Second, 13 | SearchTimeout: 120 * time.Second, 14 | ManagementTimeout: 120 * time.Second, 15 | QueryTimeout: 120 * time.Second, 16 | }, 17 | } 18 | 19 | // ClusterConfigProfile represents a named profile that can be applied to ClusterOptions. 20 | // VOLATILE: This API is subject to change at any time. 21 | type ClusterConfigProfile string 22 | 23 | const ( 24 | // ClusterConfigProfileWanDevelopment represents a wan development profile that can be applied to the ClusterOptions 25 | // overwriting any properties that exist on the profile. 26 | // VOLATILE: This API is subject to change at any time. 27 | ClusterConfigProfileWanDevelopment ClusterConfigProfile = "wan-development" 28 | ) 29 | 30 | // ApplyProfile will apply a named profile to the ClusterOptions overwriting any properties that 31 | // exist on the profile. 32 | // VOLATILE: This API is subject to change at any time. 33 | func (opts *ClusterOptions) ApplyProfile(profile ClusterConfigProfile) error { 34 | if profile == ClusterConfigProfileWanDevelopment { 35 | opts.TimeoutsConfig = developmentProfile.TimeoutsConfig 36 | return nil 37 | } 38 | 39 | return makeInvalidArgumentsError("unknown configuration profile") 40 | } 41 | -------------------------------------------------------------------------------- /config_profile_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "time" 4 | 5 | var defaultConfig = ClusterOptions{ 6 | TimeoutsConfig: TimeoutsConfig{ 7 | KVTimeout: 2500 * time.Millisecond, 8 | ConnectTimeout: 10 * time.Second, 9 | KVDurableTimeout: 10 * time.Second, 10 | KVScanTimeout: 10 * time.Second, 11 | ViewTimeout: 75 * time.Second, 12 | AnalyticsTimeout: 75 * time.Second, 13 | SearchTimeout: 75 * time.Second, 14 | ManagementTimeout: 75 * time.Second, 15 | QueryTimeout: 75 * time.Second, 16 | }, 17 | Transcoder: NewJSONTranscoder(), 18 | Tracer: NewThresholdLoggingTracer(nil), 19 | Meter: newAggregatingMeter(nil), 20 | RetryStrategy: NewBestEffortRetryStrategy(nil), 21 | } 22 | 23 | func (suite *UnitTestSuite) TestDevelopmentConfigProfile() { 24 | auth := PasswordAuthenticator{ 25 | Username: globalConfig.User, 26 | Password: globalConfig.Password, 27 | } 28 | 29 | options := defaultConfig 30 | options.Authenticator = auth 31 | err := options.ApplyProfile(ClusterConfigProfileWanDevelopment) 32 | suite.Require().Nil(err) 33 | 34 | suite.Assert().Equal(20*time.Second, options.TimeoutsConfig.KVTimeout) 35 | suite.Assert().Equal(20*time.Second, options.TimeoutsConfig.ConnectTimeout) 36 | suite.Assert().Equal(20*time.Second, options.TimeoutsConfig.KVDurableTimeout) 37 | suite.Assert().Equal(20*time.Second, options.TimeoutsConfig.KVScanTimeout) 38 | suite.Assert().Equal(120*time.Second, options.TimeoutsConfig.ViewTimeout) 39 | suite.Assert().Equal(120*time.Second, options.TimeoutsConfig.AnalyticsTimeout) 40 | suite.Assert().Equal(120*time.Second, options.TimeoutsConfig.SearchTimeout) 41 | suite.Assert().Equal(120*time.Second, options.TimeoutsConfig.ManagementTimeout) 42 | suite.Assert().Equal(120*time.Second, options.TimeoutsConfig.QueryTimeout) 43 | } 44 | 45 | func (suite *UnitTestSuite) TestUnknownConfigProfile() { 46 | auth := PasswordAuthenticator{ 47 | Username: globalConfig.User, 48 | Password: globalConfig.Password, 49 | } 50 | 51 | options := defaultConfig 52 | options.Authenticator = auth 53 | err := options.ApplyProfile("unknown") 54 | suite.Require().ErrorIs(err, ErrInvalidArgument) 55 | } 56 | -------------------------------------------------------------------------------- /constants_str.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | func serviceTypeToString(service ServiceType) string { 4 | switch service { 5 | case ServiceTypeManagement: 6 | return "mgmt" 7 | case ServiceTypeKeyValue: 8 | return "kv" 9 | case ServiceTypeViews: 10 | return "views" 11 | case ServiceTypeQuery: 12 | return "query" 13 | case ServiceTypeSearch: 14 | return "search" 15 | case ServiceTypeAnalytics: 16 | return "analytics" 17 | } 18 | return "" 19 | } 20 | 21 | func clusterStateToString(state ClusterState) string { 22 | switch state { 23 | case ClusterStateOnline: 24 | return "online" 25 | case ClusterStateDegraded: 26 | return "degraded" 27 | case ClusterStateOffline: 28 | return "offline" 29 | } 30 | return "" 31 | } 32 | 33 | func endpointStateToString(state EndpointState) string { 34 | switch state { 35 | case EndpointStateDisconnected: 36 | return "disconnected" 37 | case EndpointStateConnecting: 38 | return "connecting" 39 | case EndpointStateConnected: 40 | return "connected" 41 | case EndpointStateDisconnecting: 42 | return "disconnecting" 43 | } 44 | return "" 45 | } 46 | 47 | func pingStateToString(state PingState) string { 48 | switch state { 49 | case PingStateOk: 50 | return "ok" 51 | case PingStateTimeout: 52 | return "timeout" 53 | case PingStateError: 54 | return "error" 55 | } 56 | return "" 57 | } 58 | -------------------------------------------------------------------------------- /diagnosticsprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type diagnosticsProvider interface { 4 | Diagnostics(opts *DiagnosticsOptions) (*DiagnosticsResult, error) 5 | Ping(opts *PingOptions) (*PingResult, error) 6 | } 7 | -------------------------------------------------------------------------------- /diagnosticsprovider_core.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "github.com/google/uuid" 6 | "time" 7 | 8 | "github.com/couchbase/gocbcore/v10" 9 | ) 10 | 11 | type diagnosticsProviderCoreProvider interface { 12 | Diagnostics(opts gocbcore.DiagnosticsOptions) (*gocbcore.DiagnosticInfo, error) 13 | Ping(ctx context.Context, opts gocbcore.PingOptions) (*gocbcore.PingResult, error) 14 | } 15 | 16 | type diagnosticsProviderCore struct { 17 | provider diagnosticsProviderCoreProvider 18 | 19 | tracer *tracerWrapper 20 | timeouts TimeoutsConfig 21 | } 22 | 23 | func (d *diagnosticsProviderCore) Diagnostics(opts *DiagnosticsOptions) (*DiagnosticsResult, error) { 24 | if opts == nil { 25 | opts = &DiagnosticsOptions{} 26 | } 27 | 28 | if opts.ReportID == "" { 29 | opts.ReportID = uuid.New().String() 30 | } 31 | 32 | agentReport, err := d.provider.Diagnostics(gocbcore.DiagnosticsOptions{}) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | report := &DiagnosticsResult{ 38 | ID: opts.ReportID, 39 | Services: make(map[string][]EndPointDiagnostics), 40 | sdk: Identifier(), 41 | State: ClusterState(agentReport.State), 42 | } 43 | 44 | report.Services["kv"] = make([]EndPointDiagnostics, 0) 45 | 46 | for _, conn := range agentReport.MemdConns { 47 | state := EndpointState(conn.State) 48 | 49 | report.Services["kv"] = append(report.Services["kv"], EndPointDiagnostics{ 50 | Type: ServiceTypeKeyValue, 51 | State: state, 52 | Local: conn.LocalAddr, 53 | Remote: conn.RemoteAddr, 54 | LastActivity: conn.LastActivity, 55 | Namespace: conn.Scope, 56 | ID: conn.ID, 57 | }) 58 | } 59 | 60 | return report, nil 61 | } 62 | 63 | func (d *diagnosticsProviderCore) Ping(opts *PingOptions) (*PingResult, error) { 64 | span := d.tracer.createSpan(opts.ParentSpan, "ping", "kv") 65 | defer span.End() 66 | 67 | services := opts.ServiceTypes 68 | 69 | gocbcoreServices := make([]gocbcore.ServiceType, len(services)) 70 | for i, svc := range services { 71 | gocbcoreServices[i] = gocbcore.ServiceType(svc) 72 | } 73 | 74 | coreopts := gocbcore.PingOptions{ 75 | ServiceTypes: gocbcoreServices, 76 | TraceContext: span.Context(), 77 | } 78 | 79 | now := time.Now() 80 | timeout := opts.Timeout 81 | if timeout == 0 { 82 | coreopts.KVDeadline = now.Add(d.timeouts.KVTimeout) 83 | coreopts.CapiDeadline = now.Add(d.timeouts.ViewTimeout) 84 | coreopts.N1QLDeadline = now.Add(d.timeouts.QueryTimeout) 85 | coreopts.CbasDeadline = now.Add(d.timeouts.AnalyticsTimeout) 86 | coreopts.FtsDeadline = now.Add(d.timeouts.SearchTimeout) 87 | coreopts.MgmtDeadline = now.Add(d.timeouts.ManagementTimeout) 88 | } else { 89 | coreopts.KVDeadline = now.Add(timeout) 90 | coreopts.CapiDeadline = now.Add(timeout) 91 | coreopts.N1QLDeadline = now.Add(timeout) 92 | coreopts.CbasDeadline = now.Add(timeout) 93 | coreopts.FtsDeadline = now.Add(timeout) 94 | coreopts.MgmtDeadline = now.Add(timeout) 95 | } 96 | 97 | id := opts.ReportID 98 | if id == "" { 99 | id = uuid.New().String() 100 | } 101 | 102 | result, err := d.provider.Ping(opts.Context, coreopts) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | reportSvcs := make(map[ServiceType][]EndpointPingReport) 108 | for svcType, svc := range result.Services { 109 | st := ServiceType(svcType) 110 | 111 | svcs := make([]EndpointPingReport, len(svc)) 112 | for i, rep := range svc { 113 | var errStr string 114 | if rep.Error != nil { 115 | errStr = rep.Error.Error() 116 | } 117 | svcs[i] = EndpointPingReport{ 118 | ID: rep.ID, 119 | Remote: rep.Endpoint, 120 | State: PingState(rep.State), 121 | Error: errStr, 122 | Namespace: rep.Scope, 123 | Latency: rep.Latency, 124 | } 125 | } 126 | 127 | reportSvcs[st] = svcs 128 | } 129 | 130 | return &PingResult{ 131 | ID: id, 132 | sdk: Identifier() + " " + "gocbcore/" + gocbcore.Version(), 133 | Services: reportSvcs, 134 | }, nil 135 | } 136 | -------------------------------------------------------------------------------- /error_analytics.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | gocbcore "github.com/couchbase/gocbcore/v10" 6 | ) 7 | 8 | // AnalyticsErrorDesc represents a specific error returned from the analytics service. 9 | type AnalyticsErrorDesc struct { 10 | Code uint32 11 | Message string 12 | } 13 | 14 | func translateCoreAnalyticsErrorDesc(descs []gocbcore.AnalyticsErrorDesc) []AnalyticsErrorDesc { 15 | descsOut := make([]AnalyticsErrorDesc, len(descs)) 16 | for descIdx, desc := range descs { 17 | descsOut[descIdx] = AnalyticsErrorDesc{ 18 | Code: desc.Code, 19 | Message: desc.Message, 20 | } 21 | } 22 | return descsOut 23 | } 24 | 25 | // AnalyticsError is the error type of all analytics query errors. 26 | // UNCOMMITTED: This API may change in the future. 27 | type AnalyticsError struct { 28 | InnerError error `json:"-"` 29 | Statement string `json:"statement,omitempty"` 30 | ClientContextID string `json:"client_context_id,omitempty"` 31 | Errors []AnalyticsErrorDesc `json:"errors,omitempty"` 32 | Endpoint string `json:"endpoint,omitempty"` 33 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 34 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 35 | ErrorText string `json:"error_text,omitempty"` 36 | HTTPStatusCode int `json:"http_status_code,omitempty"` 37 | } 38 | 39 | // MarshalJSON implements the Marshaler interface. 40 | 41 | func (e AnalyticsError) MarshalJSON() ([]byte, error) { 42 | var innerError string 43 | if e.InnerError != nil { 44 | innerError = e.InnerError.Error() 45 | } 46 | return json.Marshal(struct { 47 | InnerError string `json:"msg,omitempty"` 48 | Statement string `json:"statement,omitempty"` 49 | ClientContextID string `json:"client_context_id,omitempty"` 50 | Errors []AnalyticsErrorDesc `json:"errors,omitempty"` 51 | Endpoint string `json:"endpoint,omitempty"` 52 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 53 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 54 | HTTPStatusCode int `json:"http_status_code,omitempty"` 55 | }{ 56 | InnerError: innerError, 57 | Statement: e.Statement, 58 | ClientContextID: e.ClientContextID, 59 | Errors: e.Errors, 60 | Endpoint: e.Endpoint, 61 | RetryReasons: e.RetryReasons, 62 | RetryAttempts: e.RetryAttempts, 63 | HTTPStatusCode: e.HTTPStatusCode, 64 | }) 65 | } 66 | 67 | // Error returns the string representation of this error. 68 | func (e AnalyticsError) Error() string { 69 | errBytes, serErr := json.Marshal(struct { 70 | InnerError error `json:"-"` 71 | Statement string `json:"statement,omitempty"` 72 | ClientContextID string `json:"client_context_id,omitempty"` 73 | Errors []AnalyticsErrorDesc `json:"errors,omitempty"` 74 | Endpoint string `json:"endpoint,omitempty"` 75 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 76 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 77 | ErrorText string `json:"error_text,omitempty"` 78 | HTTPStatusCode int `json:"http_status_code,omitempty"` 79 | }{ 80 | InnerError: e.InnerError, 81 | Statement: e.Statement, 82 | ClientContextID: e.ClientContextID, 83 | Errors: e.Errors, 84 | Endpoint: e.Endpoint, 85 | RetryReasons: e.RetryReasons, 86 | RetryAttempts: e.RetryAttempts, 87 | ErrorText: e.ErrorText, 88 | HTTPStatusCode: e.HTTPStatusCode, 89 | }) 90 | if serErr != nil { 91 | logErrorf("failed to serialize error to json: %s", serErr.Error()) 92 | } 93 | 94 | return e.InnerError.Error() + " | " + string(errBytes) 95 | } 96 | 97 | // Unwrap returns the underlying cause for this error. 98 | func (e AnalyticsError) Unwrap() error { 99 | return e.InnerError 100 | } 101 | -------------------------------------------------------------------------------- /error_analytics_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | func (suite *UnitTestSuite) TestAnalyticsError() { 8 | aErr := &AnalyticsError{ 9 | InnerError: ErrDatasetNotFound, 10 | Statement: "select * from dataset", 11 | ClientContextID: "12345", 12 | Errors: []AnalyticsErrorDesc{{ 13 | Code: 1000, 14 | Message: "error 1000", 15 | }}, 16 | Endpoint: "http://127.0.0.1:8095", 17 | RetryReasons: []RetryReason{AnalyticsTemporaryFailureRetryReason}, 18 | RetryAttempts: 3, 19 | } 20 | 21 | b, err := json.Marshal(aErr) 22 | suite.Require().Nil(err) 23 | 24 | suite.Assert().Equal( 25 | []byte("{\"msg\":\"dataset not found\",\"statement\":\"select * from dataset\",\"client_context_id\":\"12345\",\"errors\":[{\"Code\":1000,\"Message\":\"error 1000\"}],\"endpoint\":\"http://127.0.0.1:8095\",\"retry_reasons\":[\"ANALYTICS_TEMPORARY_FAILURE\"],\"retry_attempts\":3}"), 26 | b, 27 | ) 28 | suite.Assert().Equal( 29 | "dataset not found | {\"statement\":\"select * from dataset\",\"client_context_id\":\"12345\",\"errors\":[{\"Code\":1000,\"Message\":\"error 1000\"}],\"endpoint\":\"http://127.0.0.1:8095\",\"retry_reasons\":[\"ANALYTICS_TEMPORARY_FAILURE\"],\"retry_attempts\":3}", 30 | aErr.Error(), 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /error_generic.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // GenericError wraps errors that come from the SDK, and can be returned from any service. 8 | // Errors returned when protostellar is used are of this type. 9 | // 10 | // # UNCOMMITTED 11 | // 12 | // This API is UNCOMMITTED and may change in the future. 13 | type GenericError struct { 14 | InnerError error `json:"-"` 15 | Context map[string]interface{} `json:"context,omitempty"` 16 | } 17 | 18 | // MarshalJSON implements the Marshaler interface. 19 | func (e GenericError) MarshalJSON() ([]byte, error) { 20 | var innerError string 21 | if e.InnerError != nil { 22 | innerError = e.InnerError.Error() 23 | } 24 | return json.Marshal(struct { 25 | InnerError string `json:"msg,omitempty"` 26 | Context map[string]interface{} `json:"context,omitempty"` 27 | }{ 28 | InnerError: innerError, 29 | Context: e.Context, 30 | }) 31 | } 32 | 33 | // Error returns the string representation of a kv error. 34 | func (e GenericError) Error() string { 35 | errBytes, serErr := json.Marshal(struct { 36 | InnerError error `json:"-"` 37 | Context map[string]interface{} `json:"context,omitempty"` 38 | }{ 39 | InnerError: e.InnerError, 40 | Context: e.Context, 41 | }) 42 | if serErr != nil { 43 | logErrorf("failed to serialize error to json: %s", serErr.Error()) 44 | } 45 | 46 | return e.InnerError.Error() + " | " + string(errBytes) 47 | } 48 | 49 | // Unwrap returns the underlying reason for the error 50 | func (e GenericError) Unwrap() error { 51 | return e.InnerError 52 | } 53 | 54 | func makeGenericError(baseErr error, context map[string]interface{}) *GenericError { 55 | return &GenericError{ 56 | InnerError: baseErr, 57 | Context: context, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /error_http.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "github.com/couchbase/gocbcore/v10" 7 | "io" 8 | ) 9 | 10 | // HTTPError is the error type of management HTTP errors. 11 | // UNCOMMITTED: This API may change in the future. 12 | type HTTPError struct { 13 | InnerError error `json:"-"` 14 | UniqueID string `json:"unique_id,omitempty"` 15 | Endpoint string `json:"endpoint,omitempty"` 16 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 17 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 18 | ErrorText string `json:"error_text,omitempty"` 19 | StatusCode uint32 `json:"status_code,omitempty"` 20 | } 21 | 22 | // MarshalJSON implements the Marshaler interface. 23 | func (e HTTPError) MarshalJSON() ([]byte, error) { 24 | var innerError string 25 | if e.InnerError != nil { 26 | innerError = e.InnerError.Error() 27 | } 28 | return json.Marshal(struct { 29 | InnerError string `json:"msg,omitempty"` 30 | UniqueID string `json:"unique_id,omitempty"` 31 | Endpoint string `json:"endpoint,omitempty"` 32 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 33 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 34 | ErrorText string `json:"error_text,omitempty"` 35 | StatusCode uint32 `json:"status_code,omitempty"` 36 | }{ 37 | InnerError: innerError, 38 | UniqueID: e.UniqueID, 39 | Endpoint: e.Endpoint, 40 | RetryReasons: e.RetryReasons, 41 | RetryAttempts: e.RetryAttempts, 42 | ErrorText: e.ErrorText, 43 | StatusCode: e.StatusCode, 44 | }) 45 | } 46 | 47 | // Error returns the string representation of this error. 48 | func (e HTTPError) Error() string { 49 | errBytes, serErr := json.Marshal(struct { 50 | InnerError error `json:"-"` 51 | UniqueID string `json:"unique_id,omitempty"` 52 | Endpoint string `json:"endpoint,omitempty"` 53 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 54 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 55 | ErrorText string `json:"error_text,omitempty"` 56 | StatusCode uint32 `json:"status_code,omitempty"` 57 | }{ 58 | InnerError: e.InnerError, 59 | UniqueID: e.UniqueID, 60 | Endpoint: e.Endpoint, 61 | RetryReasons: e.RetryReasons, 62 | RetryAttempts: e.RetryAttempts, 63 | ErrorText: e.ErrorText, 64 | StatusCode: e.StatusCode, 65 | }) 66 | if serErr != nil { 67 | logErrorf("failed to serialize error to json: %s", serErr.Error()) 68 | } 69 | 70 | return e.InnerError.Error() + " | " + string(errBytes) 71 | } 72 | 73 | // Unwrap returns the underlying cause for this error. 74 | func (e HTTPError) Unwrap() error { 75 | return e.InnerError 76 | } 77 | 78 | func makeGenericHTTPError(baseErr error, req *gocbcore.HTTPRequest, resp *gocbcore.HTTPResponse) error { 79 | if baseErr == nil { 80 | logErrorf("makeGenericHTTPError got an empty error") 81 | baseErr = errors.New("unknown error") 82 | } 83 | 84 | err := &HTTPError{ 85 | InnerError: baseErr, 86 | } 87 | 88 | if req != nil { 89 | err.UniqueID = req.UniqueID 90 | } 91 | 92 | if resp != nil { 93 | err.Endpoint = resp.Endpoint 94 | err.StatusCode = uint32(resp.StatusCode) 95 | } 96 | 97 | return err 98 | } 99 | 100 | func makeGenericMgmtError(baseErr error, req *mgmtRequest, resp *mgmtResponse, errText string) error { 101 | if baseErr == nil { 102 | logErrorf("makeGenericMgmtError got an empty error") 103 | baseErr = errors.New("unknown error") 104 | } 105 | 106 | err := &HTTPError{ 107 | InnerError: baseErr, 108 | ErrorText: errText, 109 | } 110 | 111 | if req != nil { 112 | err.UniqueID = req.UniqueID 113 | } 114 | 115 | if resp != nil { 116 | err.Endpoint = resp.Endpoint 117 | err.StatusCode = resp.StatusCode 118 | } 119 | 120 | return err 121 | } 122 | 123 | func makeMgmtBadStatusError(message string, req *mgmtRequest, resp *mgmtResponse) error { 124 | var errText string 125 | if resp != nil { 126 | b, err := io.ReadAll(resp.Body) 127 | if err != nil { 128 | logDebugf("failed to read http body: %s", err) 129 | return nil 130 | } 131 | 132 | errText = string(b) 133 | } 134 | return makeGenericMgmtError(errors.New(message), req, resp, errText) 135 | } 136 | -------------------------------------------------------------------------------- /error_http_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | func (suite *UnitTestSuite) TestHTTPError() { 9 | aErr := &HTTPError{ 10 | InnerError: errors.New("uh oh"), 11 | Endpoint: "http://127.0.0.1:8091", 12 | UniqueID: "123445", 13 | RetryReasons: nil, 14 | RetryAttempts: 0, 15 | } 16 | 17 | b, err := json.Marshal(aErr) 18 | suite.Require().Nil(err) 19 | 20 | suite.Assert().Equal( 21 | []byte("{\"msg\":\"uh oh\",\"unique_id\":\"123445\",\"endpoint\":\"http://127.0.0.1:8091\"}"), 22 | b, 23 | ) 24 | suite.Assert().Equal( 25 | "uh oh | {\"unique_id\":\"123445\",\"endpoint\":\"http://127.0.0.1:8091\"}", 26 | aErr.Error(), 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /error_keyvalue_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/couchbase/gocbcore/v10/memd" 6 | ) 7 | 8 | func (suite *UnitTestSuite) TestKeyValueError() { 9 | aErr := &KeyValueError{ 10 | InnerError: ErrPathNotFound, 11 | StatusCode: memd.StatusBusy, 12 | DocumentID: "key", 13 | BucketName: "bucket", 14 | ScopeName: "scope", 15 | CollectionName: "collection", 16 | CollectionID: 9, 17 | ErrorName: "barry", 18 | ErrorDescription: "sheen", 19 | Opaque: 0xa1, 20 | RetryReasons: []RetryReason{CircuitBreakerOpenRetryReason}, 21 | RetryAttempts: 3, 22 | LastDispatchedTo: "10.112.210.101", 23 | LastDispatchedFrom: "10.112.210.1", 24 | LastConnectionID: "123456", 25 | } 26 | 27 | b, err := json.Marshal(aErr) 28 | suite.Require().Nil(err) 29 | 30 | suite.Assert().Equal( 31 | []byte("{\"msg\":\"path not found\",\"status_code\":133,\"document_id\":\"key\",\"bucket\":\"bucket\",\"scope\":\"scope\",\"collection\":\"collection\",\"collection_id\":9,\"error_name\":\"barry\",\"error_description\":\"sheen\",\"opaque\":161,\"retry_reasons\":[\"CIRCUIT_BREAKER_OPEN\"],\"retry_attempts\":3,\"last_dispatched_to\":\"10.112.210.101\",\"last_dispatched_from\":\"10.112.210.1\",\"last_connection_id\":\"123456\"}"), 32 | b, 33 | ) 34 | suite.Assert().Equal( 35 | "path not found | {\"status_code\":133,\"document_id\":\"key\",\"bucket\":\"bucket\",\"scope\":\"scope\",\"collection\":\"collection\",\"collection_id\":9,\"error_name\":\"barry\",\"error_description\":\"sheen\",\"opaque\":161,\"retry_reasons\":[\"CIRCUIT_BREAKER_OPEN\"],\"retry_attempts\":3,\"last_dispatched_to\":\"10.112.210.101\",\"last_dispatched_from\":\"10.112.210.1\",\"last_connection_id\":\"123456\"}", 36 | aErr.Error(), 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /error_query_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "encoding/json" 4 | 5 | func (suite *UnitTestSuite) TestQueryError() { 6 | aErr := &QueryError{ 7 | InnerError: ErrIndexFailure, 8 | Statement: "select * from dataset", 9 | ClientContextID: "12345", 10 | Errors: []QueryErrorDesc{{ 11 | Code: 1000, 12 | Message: "error 1000", 13 | }}, 14 | Endpoint: "http://127.0.0.1:8093", 15 | RetryReasons: []RetryReason{QueryIndexNotFoundRetryReason}, 16 | RetryAttempts: 3, 17 | } 18 | 19 | b, err := json.Marshal(aErr) 20 | suite.Require().Nil(err) 21 | 22 | suite.Assert().Equal( 23 | "{\"msg\":\"index failure\",\"statement\":\"select * from dataset\",\"client_context_id\":\"12345\",\"errors\":[{\"code\":1000,\"message\":\"error 1000\"}],\"endpoint\":\"http://127.0.0.1:8093\",\"retry_reasons\":[\"QUERY_INDEX_NOT_FOUND\"],\"retry_attempts\":3}", 24 | string(b), 25 | ) 26 | suite.Assert().Equal( 27 | "index failure | {\"statement\":\"select * from dataset\",\"client_context_id\":\"12345\",\"errors\":[{\"code\":1000,\"message\":\"error 1000\"}],\"endpoint\":\"http://127.0.0.1:8093\",\"retry_reasons\":[\"QUERY_INDEX_NOT_FOUND\"],\"retry_attempts\":3}", 28 | aErr.Error(), 29 | ) 30 | } 31 | 32 | func (suite *UnitTestSuite) TestQueryErrorImproved() { 33 | aErr := &QueryError{ 34 | InnerError: ErrIndexFailure, 35 | Statement: "select * from dataset", 36 | ClientContextID: "12345", 37 | Errors: []QueryErrorDesc{{ 38 | Code: 1000, 39 | Message: "error 1000", 40 | Reason: map[string]interface{}{ 41 | "code": 17029, 42 | }, 43 | Retry: true, 44 | }}, 45 | Endpoint: "http://127.0.0.1:8093", 46 | RetryReasons: []RetryReason{QueryIndexNotFoundRetryReason}, 47 | RetryAttempts: 3, 48 | } 49 | 50 | b, err := json.Marshal(aErr) 51 | suite.Require().Nil(err) 52 | 53 | suite.Assert().Equal( 54 | "{\"msg\":\"index failure\",\"statement\":\"select * from dataset\",\"client_context_id\":\"12345\",\"errors\":[{\"code\":1000,\"message\":\"error 1000\",\"retry\":true,\"reason\":{\"code\":17029}}],\"endpoint\":\"http://127.0.0.1:8093\",\"retry_reasons\":[\"QUERY_INDEX_NOT_FOUND\"],\"retry_attempts\":3}", 55 | string(b), 56 | ) 57 | suite.Assert().Equal( 58 | "index failure | {\"statement\":\"select * from dataset\",\"client_context_id\":\"12345\",\"errors\":[{\"code\":1000,\"message\":\"error 1000\",\"retry\":true,\"reason\":{\"code\":17029}}],\"endpoint\":\"http://127.0.0.1:8093\",\"retry_reasons\":[\"QUERY_INDEX_NOT_FOUND\"],\"retry_attempts\":3}", 59 | aErr.Error(), 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /error_search.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // SearchError is the error type of all search query errors. 8 | // UNCOMMITTED: This API may change in the future. 9 | type SearchError struct { 10 | InnerError error `json:"-"` 11 | Query interface{} `json:"query,omitempty"` 12 | Endpoint string `json:"endpoint,omitempty"` 13 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 14 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 15 | ErrorText string `json:"error_text"` 16 | IndexName string `json:"index_name,omitempty"` 17 | HTTPStatusCode int `json:"http_status_code,omitempty"` 18 | } 19 | 20 | // MarshalJSON implements the Marshaler interface. 21 | 22 | func (e SearchError) MarshalJSON() ([]byte, error) { 23 | var innerError string 24 | if e.InnerError != nil { 25 | innerError = e.InnerError.Error() 26 | } 27 | return json.Marshal(struct { 28 | InnerError string `json:"msg,omitempty"` 29 | IndexName string `json:"index_name,omitempty"` 30 | Query interface{} `json:"query,omitempty"` 31 | ErrorText string `json:"error_text"` 32 | Endpoint string `json:"endpoint,omitempty"` 33 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 34 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 35 | HTTPStatusCode int `json:"http_status_code,omitempty"` 36 | }{ 37 | InnerError: innerError, 38 | IndexName: e.IndexName, 39 | Query: e.Query, 40 | ErrorText: e.ErrorText, 41 | Endpoint: e.Endpoint, 42 | RetryReasons: e.RetryReasons, 43 | RetryAttempts: e.RetryAttempts, 44 | HTTPStatusCode: e.HTTPStatusCode, 45 | }) 46 | } 47 | 48 | // Error returns the string representation of this error. 49 | func (e SearchError) Error() string { 50 | errBytes, serErr := json.Marshal(struct { 51 | InnerError error `json:"-"` 52 | IndexName string `json:"index_name,omitempty"` 53 | Query interface{} `json:"query,omitempty"` 54 | ErrorText string `json:"error_text"` 55 | HTTPResponseCode int `json:"status_code,omitempty"` 56 | Endpoint string `json:"endpoint,omitempty"` 57 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 58 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 59 | HTTPStatusCode int `json:"http_status_code,omitempty"` 60 | }{ 61 | InnerError: e.InnerError, 62 | IndexName: e.IndexName, 63 | Query: e.Query, 64 | ErrorText: e.ErrorText, 65 | Endpoint: e.Endpoint, 66 | RetryReasons: e.RetryReasons, 67 | RetryAttempts: e.RetryAttempts, 68 | HTTPStatusCode: e.HTTPStatusCode, 69 | }) 70 | if serErr != nil { 71 | logErrorf("failed to serialize error to json: %s", serErr.Error()) 72 | } 73 | 74 | return e.InnerError.Error() + " | " + string(errBytes) 75 | } 76 | 77 | // Unwrap returns the underlying cause for this error. 78 | func (e SearchError) Unwrap() error { 79 | return e.InnerError 80 | } 81 | -------------------------------------------------------------------------------- /error_search_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/couchbase/gocb/v2/search" 6 | ) 7 | 8 | func (suite *UnitTestSuite) TestSearchError() { 9 | aErr := &SearchError{ 10 | InnerError: ErrIndexFailure, 11 | Query: search.NewMatchAllQuery(), 12 | Endpoint: "http://127.0.0.1:8094", 13 | RetryReasons: []RetryReason{SearchTooManyRequestsRetryReason}, 14 | RetryAttempts: 3, 15 | ErrorText: "error text", 16 | IndexName: "barry", 17 | } 18 | 19 | b, err := json.Marshal(aErr) 20 | suite.Require().Nil(err) 21 | 22 | suite.Assert().Equal( 23 | []byte("{\"msg\":\"index failure\",\"index_name\":\"barry\",\"query\":{\"match_all\":null},\"error_text\":\"error text\",\"endpoint\":\"http://127.0.0.1:8094\",\"retry_reasons\":[\"SEARCH_TOO_MANY_REQUESTS\"],\"retry_attempts\":3}"), 24 | b, 25 | ) 26 | suite.Assert().Equal( 27 | "index failure | {\"index_name\":\"barry\",\"query\":{\"match_all\":null},\"error_text\":\"error text\",\"endpoint\":\"http://127.0.0.1:8094\",\"retry_reasons\":[\"SEARCH_TOO_MANY_REQUESTS\"],\"retry_attempts\":3}", 28 | aErr.Error(), 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /error_timeout.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | // TimeoutError wraps timeout errors that occur within the SDK. 9 | // UNCOMMITTED: This API may change in the future. 10 | type TimeoutError struct { 11 | InnerError error 12 | OperationID string 13 | Opaque string 14 | TimeObserved time.Duration 15 | RetryReasons []RetryReason 16 | RetryAttempts uint32 17 | LastDispatchedTo string 18 | LastDispatchedFrom string 19 | LastConnectionID string 20 | } 21 | 22 | // MarshalJSON implements the Marshaler interface. 23 | func (e TimeoutError) MarshalJSON() ([]byte, error) { 24 | var innerError string 25 | if e.InnerError != nil { 26 | innerError = e.InnerError.Error() 27 | } 28 | var retries []string 29 | for _, rr := range e.RetryReasons { 30 | retries = append(retries, rr.Description()) 31 | } 32 | return json.Marshal(struct { 33 | InnerError string `json:"msg,omitempty"` 34 | OperationID string `json:"operation_id,omitempty"` 35 | Opaque string `json:"opaque,omitempty"` 36 | TimeObserved uint64 `json:"time_observed,omitempty"` 37 | RetryReasons []string `json:"retry_reasons,omitempty"` 38 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 39 | LastDispatchedTo string `json:"last_dispatched_to,omitempty"` 40 | LastDispatchedFrom string `json:"last_dispatched_from,omitempty"` 41 | LastConnectionID string `json:"last_connection_id,omitempty"` 42 | }{ 43 | InnerError: innerError, 44 | OperationID: e.OperationID, 45 | Opaque: e.Opaque, 46 | TimeObserved: uint64(e.TimeObserved / time.Microsecond), 47 | RetryReasons: retries, 48 | RetryAttempts: e.RetryAttempts, 49 | LastDispatchedTo: e.LastDispatchedTo, 50 | LastDispatchedFrom: e.LastDispatchedFrom, 51 | LastConnectionID: e.LastConnectionID, 52 | }) 53 | } 54 | 55 | // Error returns the string representation of this error. 56 | func (e TimeoutError) Error() string { 57 | errBytes, serErr := json.Marshal(struct { 58 | InnerError error `json:"-"` 59 | OperationID string `json:"operation_id,omitempty"` 60 | Opaque string `json:"opaque,omitempty"` 61 | TimeObserved uint64 `json:"time_observed,omitempty"` 62 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 63 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 64 | LastDispatchedTo string `json:"last_dispatched_to,omitempty"` 65 | LastDispatchedFrom string `json:"last_dispatched_from,omitempty"` 66 | LastConnectionID string `json:"last_connection_id,omitempty"` 67 | }{ 68 | InnerError: e.InnerError, 69 | OperationID: e.OperationID, 70 | Opaque: e.Opaque, 71 | TimeObserved: uint64(e.TimeObserved / time.Microsecond), 72 | RetryReasons: e.RetryReasons, 73 | RetryAttempts: e.RetryAttempts, 74 | LastDispatchedTo: e.LastDispatchedTo, 75 | LastDispatchedFrom: e.LastDispatchedFrom, 76 | LastConnectionID: e.LastConnectionID, 77 | }) 78 | if serErr != nil { 79 | logErrorf("failed to serialize error to json: %s", serErr.Error()) 80 | } 81 | 82 | return e.InnerError.Error() + " | " + string(errBytes) 83 | } 84 | 85 | // Unwrap returns the underlying reason for the error 86 | func (e TimeoutError) Unwrap() error { 87 | return e.InnerError 88 | } 89 | -------------------------------------------------------------------------------- /error_view.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | gocbcore "github.com/couchbase/gocbcore/v10" 6 | ) 7 | 8 | // ViewErrorDesc represents a specific error returned from the views service. 9 | type ViewErrorDesc struct { 10 | SourceNode string 11 | Message string 12 | } 13 | 14 | func translateCoreViewErrorDesc(descs []gocbcore.ViewQueryErrorDesc) []ViewErrorDesc { 15 | descsOut := make([]ViewErrorDesc, len(descs)) 16 | for descIdx, desc := range descs { 17 | descsOut[descIdx] = ViewErrorDesc{ 18 | SourceNode: desc.SourceNode, 19 | Message: desc.Message, 20 | } 21 | } 22 | return descsOut 23 | } 24 | 25 | // ViewError is the error type of all view query errors. 26 | // UNCOMMITTED: This API may change in the future. 27 | type ViewError struct { 28 | InnerError error `json:"-"` 29 | DesignDocumentName string `json:"design_document_name,omitempty"` 30 | ViewName string `json:"view_name,omitempty"` 31 | Errors []ViewErrorDesc `json:"errors,omitempty"` 32 | Endpoint string `json:"endpoint,omitempty"` 33 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 34 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 35 | ErrorText string `json:"error_text,omitempty"` 36 | HTTPStatusCode int `json:"http_status_code,omitempty"` 37 | } 38 | 39 | // MarshalJSON implements the Marshaler interface. 40 | func (e ViewError) MarshalJSON() ([]byte, error) { 41 | return json.Marshal(struct { 42 | InnerError string `json:"msg,omitempty"` 43 | DesignDocumentName string `json:"design_document_name,omitempty"` 44 | ViewName string `json:"view_name,omitempty"` 45 | Errors []ViewErrorDesc `json:"errors,omitempty"` 46 | Endpoint string `json:"endpoint,omitempty"` 47 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 48 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 49 | HTTPStatusCode int `json:"http_status_code,omitempty"` 50 | }{ 51 | InnerError: e.InnerError.Error(), 52 | DesignDocumentName: e.DesignDocumentName, 53 | ViewName: e.ViewName, 54 | Errors: e.Errors, 55 | Endpoint: e.Endpoint, 56 | RetryReasons: e.RetryReasons, 57 | RetryAttempts: e.RetryAttempts, 58 | HTTPStatusCode: e.HTTPStatusCode, 59 | }) 60 | } 61 | 62 | // Error returns the string representation of this error. 63 | func (e ViewError) Error() string { 64 | errBytes, serErr := json.Marshal(struct { 65 | InnerError error `json:"-"` 66 | DesignDocumentName string `json:"design_document_name,omitempty"` 67 | ViewName string `json:"view_name,omitempty"` 68 | Errors []ViewErrorDesc `json:"errors,omitempty"` 69 | Endpoint string `json:"endpoint,omitempty"` 70 | RetryReasons []RetryReason `json:"retry_reasons,omitempty"` 71 | RetryAttempts uint32 `json:"retry_attempts,omitempty"` 72 | ErrorText string `json:"error_text,omitempty"` 73 | HTTPStatusCode int `json:"http_status_code,omitempty"` 74 | }{ 75 | InnerError: e.InnerError, 76 | DesignDocumentName: e.DesignDocumentName, 77 | ViewName: e.ViewName, 78 | Errors: e.Errors, 79 | Endpoint: e.Endpoint, 80 | RetryReasons: e.RetryReasons, 81 | RetryAttempts: e.RetryAttempts, 82 | ErrorText: e.ErrorText, 83 | HTTPStatusCode: e.HTTPStatusCode, 84 | }) 85 | if serErr != nil { 86 | logErrorf("failed to serialize error to json: %s", serErr.Error()) 87 | } 88 | 89 | return e.InnerError.Error() + " | " + string(errBytes) 90 | } 91 | 92 | // Unwrap returns the underlying cause for this error. 93 | func (e ViewError) Unwrap() error { 94 | return e.InnerError 95 | } 96 | -------------------------------------------------------------------------------- /error_view_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | func (suite *UnitTestSuite) TestViewError() { 8 | aErr := &ViewError{ 9 | InnerError: ErrViewNotFound, 10 | DesignDocumentName: "designdoc", 11 | ViewName: "viewname", 12 | Errors: []ViewErrorDesc{ 13 | { 14 | SourceNode: "http://127.0.0.1:8092", 15 | Message: "error message", 16 | }, 17 | }, 18 | Endpoint: "http://127.0.0.1:8092", 19 | } 20 | 21 | b, err := json.Marshal(aErr) 22 | suite.Require().Nil(err) 23 | 24 | suite.Assert().Equal( 25 | []byte("{\"msg\":\"view not found\",\"design_document_name\":\"designdoc\",\"view_name\":\"viewname\",\"errors\":[{\"SourceNode\":\"http://127.0.0.1:8092\",\"Message\":\"error message\"}],\"endpoint\":\"http://127.0.0.1:8092\"}"), 26 | b, 27 | ) 28 | suite.Assert().Equal( 29 | "view not found | {\"design_document_name\":\"designdoc\",\"view_name\":\"viewname\",\"errors\":[{\"SourceNode\":\"http://127.0.0.1:8092\",\"Message\":\"error message\"}],\"endpoint\":\"http://127.0.0.1:8092\"}", 30 | aErr.Error(), 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /eventingmgmtprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type eventingManagementProvider interface { 4 | UpsertFunction(scope *Scope, function EventingFunction, opts *UpsertEventingFunctionOptions) error 5 | DropFunction(scope *Scope, name string, opts *DropEventingFunctionOptions) error 6 | DeployFunction(scope *Scope, name string, opts *DeployEventingFunctionOptions) error 7 | UndeployFunction(scope *Scope, name string, opts *UndeployEventingFunctionOptions) error 8 | GetAllFunctions(scope *Scope, opts *GetAllEventingFunctionsOptions) ([]EventingFunction, error) 9 | GetFunction(scope *Scope, name string, opts *GetEventingFunctionOptions) (*EventingFunction, error) 10 | PauseFunction(scope *Scope, name string, opts *PauseEventingFunctionOptions) error 11 | ResumeFunction(scope *Scope, name string, opts *ResumeEventingFunctionOptions) error 12 | FunctionsStatus(scope *Scope, opts *EventingFunctionsStatusOptions) (*EventingStatus, error) 13 | } 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/couchbase/gocb/v2 2 | 3 | require ( 4 | github.com/couchbase/gocbcore/v10 v10.7.0 5 | github.com/couchbase/gocbcoreps v0.1.3 6 | github.com/couchbase/goprotostellar v1.0.2 7 | github.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8 8 | github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28 9 | github.com/golang/snappy v0.0.4 10 | github.com/google/uuid v1.6.0 11 | github.com/stretchr/testify v1.10.0 12 | go.opentelemetry.io/otel/metric v1.24.0 13 | go.opentelemetry.io/otel/trace v1.24.0 14 | go.uber.org/zap v1.27.0 15 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda 16 | google.golang.org/grpc v1.63.2 17 | google.golang.org/protobuf v1.33.0 18 | ) 19 | 20 | require ( 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/go-logr/logr v1.4.1 // indirect 23 | github.com/go-logr/stdr v1.2.2 // indirect 24 | github.com/gorilla/websocket v1.5.3 // indirect 25 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect 26 | github.com/pmezard/go-difflib v1.0.0 // indirect 27 | github.com/stretchr/objx v0.5.2 // indirect 28 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 29 | go.opentelemetry.io/otel v1.24.0 // indirect 30 | go.uber.org/multierr v1.11.0 // indirect 31 | golang.org/x/net v0.24.0 // indirect 32 | golang.org/x/sys v0.19.0 // indirect 33 | golang.org/x/text v0.14.0 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | 37 | go 1.21 38 | -------------------------------------------------------------------------------- /internalProvider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type internalProvider interface { 4 | GetNodesMetadata(opts *GetNodesMetadataOptions) ([]NodeMetadata, error) 5 | } 6 | -------------------------------------------------------------------------------- /internalProviderCore.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/google/uuid" 6 | ) 7 | 8 | type internalProviderCore struct { 9 | provider mgmtProvider 10 | 11 | tracer *tracerWrapper 12 | meter *meterWrapper 13 | } 14 | 15 | func (ic *internalProviderCore) GetNodesMetadata(opts *GetNodesMetadataOptions) ([]NodeMetadata, error) { 16 | path := "/pools/default" 17 | 18 | span := ic.tracer.createSpan(opts.ParentSpan, "internal_get_nodes_metadata", "management") 19 | span.SetAttribute("db.operation", "GET "+path) 20 | defer span.End() 21 | 22 | req := mgmtRequest{ 23 | Service: ServiceTypeManagement, 24 | Path: path, 25 | Method: "GET", 26 | IsIdempotent: true, 27 | RetryStrategy: opts.RetryStrategy, 28 | UniqueID: uuid.New().String(), 29 | Timeout: opts.Timeout, 30 | parentSpanCtx: span.Context(), 31 | } 32 | 33 | resp, err := ic.provider.executeMgmtRequest(opts.Context, req) 34 | if err != nil { 35 | return nil, makeGenericMgmtError(err, &req, resp, "") 36 | } 37 | defer ensureBodyClosed(resp.Body) 38 | 39 | if resp.StatusCode != 200 { 40 | return nil, makeMgmtBadStatusError("failed to get nodes metadata", &req, resp) 41 | } 42 | 43 | var nodesData jsonClusterCfg 44 | jsonDec := json.NewDecoder(resp.Body) 45 | err = jsonDec.Decode(&nodesData) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | nodes := make([]NodeMetadata, len(nodesData.Nodes)) 51 | for i, nodeData := range nodesData.Nodes { 52 | nodes[i] = NodeMetadata(nodeData) 53 | } 54 | 55 | return nodes, nil 56 | } 57 | -------------------------------------------------------------------------------- /kvprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type kvProvider interface { 8 | Insert(*Collection, string, interface{}, *InsertOptions) (*MutationResult, error) // Done 9 | Upsert(*Collection, string, interface{}, *UpsertOptions) (*MutationResult, error) // Done 10 | Replace(*Collection, string, interface{}, *ReplaceOptions) (*MutationResult, error) // Done 11 | Remove(*Collection, string, *RemoveOptions) (*MutationResult, error) // Done 12 | 13 | Get(*Collection, string, *GetOptions) (*GetResult, error) // Done 14 | Exists(*Collection, string, *ExistsOptions) (*ExistsResult, error) // Done 15 | GetAndTouch(*Collection, string, time.Duration, *GetAndTouchOptions) (*GetResult, error) // Done 16 | GetAndLock(*Collection, string, time.Duration, *GetAndLockOptions) (*GetResult, error) // Done 17 | Unlock(*Collection, string, Cas, *UnlockOptions) error // Done 18 | Touch(*Collection, string, time.Duration, *TouchOptions) (*MutationResult, error) // Done 19 | 20 | GetAnyReplica(c *Collection, id string, opts *GetAnyReplicaOptions) (*GetReplicaResult, error) 21 | GetAllReplicas(*Collection, string, *GetAllReplicaOptions) (*GetAllReplicasResult, error) 22 | 23 | LookupIn(*Collection, string, []LookupInSpec, *LookupInOptions) (*LookupInResult, error) 24 | LookupInAnyReplica(*Collection, string, []LookupInSpec, *LookupInAnyReplicaOptions) (*LookupInReplicaResult, error) 25 | LookupInAllReplicas(*Collection, string, []LookupInSpec, *LookupInAllReplicaOptions) (*LookupInAllReplicasResult, error) 26 | MutateIn(*Collection, string, []MutateInSpec, *MutateInOptions) (*MutateInResult, error) 27 | 28 | Increment(*Collection, string, *IncrementOptions) (*CounterResult, error) // Done 29 | Decrement(*Collection, string, *DecrementOptions) (*CounterResult, error) // Done 30 | Append(*Collection, string, []byte, *AppendOptions) (*MutationResult, error) // Done 31 | Prepend(*Collection, string, []byte, *PrependOptions) (*MutationResult, error) // Done 32 | 33 | Scan(*Collection, ScanType, *ScanOptions) (*ScanResult, error) 34 | 35 | StartKvOpTrace(*Collection, string, RequestSpan, bool) RequestSpan 36 | } 37 | 38 | type kvBulkProvider interface { 39 | Do(*Collection, []BulkOp, *BulkOpOptions) error 40 | } 41 | -------------------------------------------------------------------------------- /kvprovider_core_provider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/couchbase/gocbcore/v10" 8 | ) 9 | 10 | // kvProviderCoreProvider provides us with a way to unit test what the kvProviderCore layer is doing. 11 | type kvProviderCoreProvider interface { 12 | Add(opts gocbcore.AddOptions, cb gocbcore.StoreCallback) (gocbcore.PendingOp, error) 13 | Set(opts gocbcore.SetOptions, cb gocbcore.StoreCallback) (gocbcore.PendingOp, error) 14 | Replace(opts gocbcore.ReplaceOptions, cb gocbcore.StoreCallback) (gocbcore.PendingOp, error) 15 | Get(opts gocbcore.GetOptions, cb gocbcore.GetCallback) (gocbcore.PendingOp, error) 16 | GetOneReplica(opts gocbcore.GetOneReplicaOptions, cb gocbcore.GetReplicaCallback) (gocbcore.PendingOp, error) 17 | Observe(opts gocbcore.ObserveOptions, cb gocbcore.ObserveCallback) (gocbcore.PendingOp, error) 18 | ObserveVb(opts gocbcore.ObserveVbOptions, cb gocbcore.ObserveVbCallback) (gocbcore.PendingOp, error) 19 | GetMeta(opts gocbcore.GetMetaOptions, cb gocbcore.GetMetaCallback) (gocbcore.PendingOp, error) 20 | Delete(opts gocbcore.DeleteOptions, cb gocbcore.DeleteCallback) (gocbcore.PendingOp, error) 21 | LookupIn(opts gocbcore.LookupInOptions, cb gocbcore.LookupInCallback) (gocbcore.PendingOp, error) 22 | MutateIn(opts gocbcore.MutateInOptions, cb gocbcore.MutateInCallback) (gocbcore.PendingOp, error) 23 | GetAndTouch(opts gocbcore.GetAndTouchOptions, cb gocbcore.GetAndTouchCallback) (gocbcore.PendingOp, error) 24 | GetAndLock(opts gocbcore.GetAndLockOptions, cb gocbcore.GetAndLockCallback) (gocbcore.PendingOp, error) 25 | Unlock(opts gocbcore.UnlockOptions, cb gocbcore.UnlockCallback) (gocbcore.PendingOp, error) 26 | Touch(opts gocbcore.TouchOptions, cb gocbcore.TouchCallback) (gocbcore.PendingOp, error) 27 | Increment(opts gocbcore.CounterOptions, cb gocbcore.CounterCallback) (gocbcore.PendingOp, error) 28 | Decrement(opts gocbcore.CounterOptions, cb gocbcore.CounterCallback) (gocbcore.PendingOp, error) 29 | Append(opts gocbcore.AdjoinOptions, cb gocbcore.AdjoinCallback) (gocbcore.PendingOp, error) 30 | Prepend(opts gocbcore.AdjoinOptions, cb gocbcore.AdjoinCallback) (gocbcore.PendingOp, error) 31 | WaitForConfigSnapshot(deadline time.Time, opts gocbcore.WaitForConfigSnapshotOptions, cb gocbcore.WaitForConfigSnapshotCallback) (gocbcore.PendingOp, error) 32 | RangeScanCreate(vbID uint16, opts gocbcore.RangeScanCreateOptions, cb gocbcore.RangeScanCreateCallback) (gocbcore.PendingOp, error) 33 | GetCollectionID(scopeName string, collectionName string, opts gocbcore.GetCollectionIDOptions, cb gocbcore.GetCollectionIDCallback) (gocbcore.PendingOp, error) 34 | } 35 | 36 | type kvProviderConfigSnapshotProvider interface { 37 | WaitForConfigSnapshot(ctx context.Context, deadline time.Time) (coreConfigSnapshot, error) 38 | } 39 | 40 | type coreConfigSnapshot interface { 41 | RevID() int64 42 | NumVbuckets() (int, error) 43 | NumReplicas() (int, error) 44 | NumServers() (int, error) 45 | VbucketsOnServer(index int) ([]uint16, error) 46 | KeyToServersByServerGroup(key []byte) (map[string][]int, error) 47 | } 48 | 49 | type stdCoreConfigSnapshotProvider struct { 50 | agent kvProviderCoreProvider 51 | } 52 | 53 | func (p *stdCoreConfigSnapshotProvider) WaitForConfigSnapshot(ctx context.Context, deadline time.Time) (coreConfigSnapshot, error) { 54 | if ctx == nil { 55 | ctx = context.Background() 56 | } 57 | 58 | var snapOut coreConfigSnapshot 59 | var errOut error 60 | opm := newAsyncOpManager(ctx) 61 | err := opm.Wait(p.agent.WaitForConfigSnapshot(deadline, gocbcore.WaitForConfigSnapshotOptions{}, func(result *gocbcore.WaitForConfigSnapshotResult, err error) { 62 | if err != nil { 63 | errOut = err 64 | opm.Reject() 65 | return 66 | } 67 | 68 | snapOut = result.Snapshot 69 | opm.Resolve() 70 | })) 71 | if err != nil { 72 | errOut = err 73 | } 74 | 75 | return snapOut, errOut 76 | } 77 | -------------------------------------------------------------------------------- /logging_ps.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | "go.uber.org/zap/zapcore" 6 | ) 7 | 8 | // gocbZapCore is a wrapper around our own Logger type which satisfies the zapcore.Core interface. 9 | // This allows us to forward logging created by zap into any Logger specified by the user. 10 | type gocbZapCore struct { 11 | enc zapcore.Encoder 12 | } 13 | 14 | func (g *gocbZapCore) clone() *gocbZapCore { 15 | return &gocbZapCore{ 16 | enc: g.enc.Clone(), 17 | } 18 | } 19 | 20 | func newZapLogger() *zap.Logger { 21 | // This is pretty barebones as we just want to receive messages and we'll deal with them from there. 22 | return zap.New(&gocbZapCore{ 23 | enc: zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ 24 | MessageKey: "msg", 25 | SkipLineEnding: true, 26 | ConsoleSeparator: " ", 27 | }), 28 | }) 29 | } 30 | 31 | func (g *gocbZapCore) Enabled(level zapcore.Level) bool { 32 | // We don't know the log level so just pass-through messages allowing the higher level logger to filter. 33 | return true 34 | } 35 | 36 | func (g *gocbZapCore) With(fields []zapcore.Field) zapcore.Core { 37 | clone := g.clone() 38 | 39 | for i := range fields { 40 | fields[i].AddTo(clone.enc) 41 | } 42 | 43 | return clone 44 | } 45 | 46 | func (g *gocbZapCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry { 47 | return checked.AddCore(entry, g) 48 | } 49 | 50 | func (g *gocbZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { 51 | // Using wrapEntry allows us to defer the call to EncodeEntry until it's 52 | // actually required - which means that we do not have to encode entries which 53 | // are at log levels which won't actually get logged. 54 | 55 | // offset of 2 lifts the line number out of this function, to the actual origin. 56 | logExf(g.logLevel(entry.Level), 2, "%s", g.wrapEntry(entry, fields)) 57 | 58 | return nil 59 | } 60 | 61 | func (g *gocbZapCore) Sync() error { 62 | return nil 63 | } 64 | 65 | func (g *gocbZapCore) logLevel(level zapcore.Level) LogLevel { 66 | switch level { 67 | case zapcore.FatalLevel: 68 | return LogError 69 | case zapcore.PanicLevel: 70 | return LogError 71 | case zapcore.DPanicLevel: 72 | return LogError 73 | case zapcore.ErrorLevel: 74 | return LogError 75 | case zapcore.WarnLevel: 76 | return LogWarn 77 | case zapcore.InfoLevel: 78 | return LogInfo 79 | default: 80 | return LogDebug 81 | } 82 | } 83 | 84 | func (g *gocbZapCore) wrapEntry(entry zapcore.Entry, fields []zapcore.Field) *zapLazyEntry { 85 | return &zapLazyEntry{ 86 | wrapped: entry, 87 | enc: g.enc, 88 | fields: fields, 89 | } 90 | } 91 | 92 | type zapLazyEntry struct { 93 | wrapped zapcore.Entry 94 | enc zapcore.Encoder 95 | fields []zapcore.Field 96 | } 97 | 98 | func (z *zapLazyEntry) String() string { 99 | buf, err := z.enc.EncodeEntry(z.wrapped, z.fields) 100 | if err != nil { 101 | return "failed to encode log entry: " + err.Error() 102 | } 103 | 104 | str := buf.String() 105 | buf.Free() 106 | 107 | return str 108 | } 109 | -------------------------------------------------------------------------------- /metrics_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | type testCounter struct { 9 | count uint64 10 | } 11 | 12 | func (tc *testCounter) IncrementBy(val uint64) { 13 | atomic.AddUint64(&tc.count, val) 14 | } 15 | 16 | type testValueRecorder struct { 17 | values []uint64 18 | lock sync.Mutex 19 | } 20 | 21 | func (tvr *testValueRecorder) RecordValue(val uint64) { 22 | tvr.lock.Lock() 23 | tvr.values = append(tvr.values, val) 24 | tvr.lock.Unlock() 25 | } 26 | 27 | type testMeter struct { 28 | lock sync.Mutex 29 | counters map[string]*testCounter 30 | recorders map[string]*testValueRecorder 31 | } 32 | 33 | func newTestMeter() *testMeter { 34 | return &testMeter{ 35 | counters: make(map[string]*testCounter), 36 | recorders: make(map[string]*testValueRecorder), 37 | } 38 | } 39 | 40 | func (tm *testMeter) Reset() { 41 | tm.lock.Lock() 42 | tm.counters = make(map[string]*testCounter) 43 | tm.recorders = make(map[string]*testValueRecorder) 44 | tm.lock.Unlock() 45 | } 46 | 47 | func (tc *testMeter) Counter(name string, tags map[string]string) (Counter, error) { 48 | key := name + ":" + tags["db.operation"] 49 | tc.lock.Lock() 50 | counter := tc.counters[key] 51 | if counter == nil { 52 | counter = &testCounter{} 53 | tc.counters[key] = counter 54 | } 55 | tc.lock.Unlock() 56 | return counter, nil 57 | } 58 | 59 | func (tc *testMeter) ValueRecorder(name string, tags map[string]string) (ValueRecorder, error) { 60 | key := name + ":" + tags["db.couchbase.service"] 61 | if op, ok := tags["db.operation"]; ok { 62 | key = key + ":" + op 63 | } 64 | tc.lock.Lock() 65 | recorder := tc.recorders[key] 66 | if recorder == nil { 67 | recorder = &testValueRecorder{} 68 | tc.recorders[key] = recorder 69 | } 70 | tc.lock.Unlock() 71 | return recorder, nil 72 | } 73 | -------------------------------------------------------------------------------- /mgmt_http.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "strings" 7 | "time" 8 | 9 | gocbcore "github.com/couchbase/gocbcore/v10" 10 | ) 11 | 12 | type mgmtRequest struct { 13 | Service ServiceType 14 | Method string 15 | Path string 16 | Body []byte 17 | Headers map[string]string 18 | ContentType string 19 | IsIdempotent bool 20 | UniqueID string 21 | Endpoint string 22 | 23 | Timeout time.Duration 24 | RetryStrategy RetryStrategy 25 | 26 | parentSpanCtx RequestSpanContext 27 | } 28 | 29 | type mgmtResponse struct { 30 | Endpoint string 31 | StatusCode uint32 32 | Body io.ReadCloser 33 | } 34 | 35 | type mgmtProvider interface { 36 | executeMgmtRequest(ctx context.Context, req mgmtRequest) (*mgmtResponse, error) 37 | } 38 | 39 | type mgmtProviderCore struct { 40 | provider httpProvider 41 | mgmtTimeout time.Duration 42 | retryStrategyWrapper *coreRetryStrategyWrapper 43 | } 44 | 45 | func (mpc *mgmtProviderCore) executeMgmtRequest(ctx context.Context, req mgmtRequest) (mgmtRespOut *mgmtResponse, errOut error) { 46 | timeout := req.Timeout 47 | if timeout == 0 { 48 | timeout = mpc.mgmtTimeout 49 | } 50 | 51 | retryStrategy := mpc.retryStrategyWrapper 52 | if req.RetryStrategy != nil { 53 | retryStrategy = newCoreRetryStrategyWrapper(req.RetryStrategy) 54 | } 55 | 56 | corereq := &gocbcore.HTTPRequest{ 57 | Service: gocbcore.ServiceType(req.Service), 58 | Method: req.Method, 59 | Path: req.Path, 60 | Body: req.Body, 61 | Headers: req.Headers, 62 | ContentType: req.ContentType, 63 | IsIdempotent: req.IsIdempotent, 64 | UniqueID: req.UniqueID, 65 | Deadline: time.Now().Add(timeout), 66 | RetryStrategy: retryStrategy, 67 | TraceContext: req.parentSpanCtx, 68 | Endpoint: req.Endpoint, 69 | } 70 | 71 | coreresp, err := mpc.provider.DoHTTPRequest(ctx, corereq) 72 | if err != nil { 73 | return nil, makeGenericHTTPError(err, corereq, coreresp) 74 | } 75 | 76 | resp := &mgmtResponse{ 77 | Endpoint: coreresp.Endpoint, 78 | StatusCode: uint32(coreresp.StatusCode), 79 | Body: coreresp.Body, 80 | } 81 | return resp, nil 82 | } 83 | 84 | func ensureBodyClosed(body io.ReadCloser) { 85 | err := body.Close() 86 | if err != nil { 87 | logDebugf("Failed to close socket: %v", err) 88 | } 89 | } 90 | 91 | func checkForRateLimitError(statusCode uint32, errMsg string) error { 92 | if statusCode != 429 { 93 | return nil 94 | } 95 | 96 | errMsg = strings.ToLower(errMsg) 97 | var err error 98 | if strings.Contains(errMsg, "limit(s) exceeded") { 99 | err = ErrRateLimitedFailure 100 | } else if strings.Contains(errMsg, "maximum number of collections has been reached for scope") { 101 | err = ErrQuotaLimitedFailure 102 | } 103 | 104 | return err 105 | } 106 | -------------------------------------------------------------------------------- /mock_analyticsProviderCoreProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | 8 | gocbcore "github.com/couchbase/gocbcore/v10" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // mockAnalyticsProviderCoreProvider is an autogenerated mock type for the analyticsProviderCoreProvider type 13 | type mockAnalyticsProviderCoreProvider struct { 14 | mock.Mock 15 | } 16 | 17 | // AnalyticsQuery provides a mock function with given fields: ctx, opts 18 | func (_m *mockAnalyticsProviderCoreProvider) AnalyticsQuery(ctx context.Context, opts gocbcore.AnalyticsQueryOptions) (analyticsRowReader, error) { 19 | ret := _m.Called(ctx, opts) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for AnalyticsQuery") 23 | } 24 | 25 | var r0 analyticsRowReader 26 | var r1 error 27 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.AnalyticsQueryOptions) (analyticsRowReader, error)); ok { 28 | return rf(ctx, opts) 29 | } 30 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.AnalyticsQueryOptions) analyticsRowReader); ok { 31 | r0 = rf(ctx, opts) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(analyticsRowReader) 35 | } 36 | } 37 | 38 | if rf, ok := ret.Get(1).(func(context.Context, gocbcore.AnalyticsQueryOptions) error); ok { 39 | r1 = rf(ctx, opts) 40 | } else { 41 | r1 = ret.Error(1) 42 | } 43 | 44 | return r0, r1 45 | } 46 | 47 | // newMockAnalyticsProviderCoreProvider creates a new instance of mockAnalyticsProviderCoreProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 48 | // The first argument is typically a *testing.T value. 49 | func newMockAnalyticsProviderCoreProvider(t interface { 50 | mock.TestingT 51 | Cleanup(func()) 52 | }) *mockAnalyticsProviderCoreProvider { 53 | mock := &mockAnalyticsProviderCoreProvider{} 54 | mock.Mock.Test(t) 55 | 56 | t.Cleanup(func() { mock.AssertExpectations(t) }) 57 | 58 | return mock 59 | } 60 | -------------------------------------------------------------------------------- /mock_analyticsProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockAnalyticsProvider is an autogenerated mock type for the analyticsProvider type 8 | type mockAnalyticsProvider struct { 9 | mock.Mock 10 | } 11 | 12 | // AnalyticsQuery provides a mock function with given fields: statement, scope, opts 13 | func (_m *mockAnalyticsProvider) AnalyticsQuery(statement string, scope *Scope, opts *AnalyticsOptions) (*AnalyticsResult, error) { 14 | ret := _m.Called(statement, scope, opts) 15 | 16 | if len(ret) == 0 { 17 | panic("no return value specified for AnalyticsQuery") 18 | } 19 | 20 | var r0 *AnalyticsResult 21 | var r1 error 22 | if rf, ok := ret.Get(0).(func(string, *Scope, *AnalyticsOptions) (*AnalyticsResult, error)); ok { 23 | return rf(statement, scope, opts) 24 | } 25 | if rf, ok := ret.Get(0).(func(string, *Scope, *AnalyticsOptions) *AnalyticsResult); ok { 26 | r0 = rf(statement, scope, opts) 27 | } else { 28 | if ret.Get(0) != nil { 29 | r0 = ret.Get(0).(*AnalyticsResult) 30 | } 31 | } 32 | 33 | if rf, ok := ret.Get(1).(func(string, *Scope, *AnalyticsOptions) error); ok { 34 | r1 = rf(statement, scope, opts) 35 | } else { 36 | r1 = ret.Error(1) 37 | } 38 | 39 | return r0, r1 40 | } 41 | 42 | // newMockAnalyticsProvider creates a new instance of mockAnalyticsProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 43 | // The first argument is typically a *testing.T value. 44 | func newMockAnalyticsProvider(t interface { 45 | mock.TestingT 46 | Cleanup(func()) 47 | }) *mockAnalyticsProvider { 48 | mock := &mockAnalyticsProvider{} 49 | mock.Mock.Test(t) 50 | 51 | t.Cleanup(func() { mock.AssertExpectations(t) }) 52 | 53 | return mock 54 | } 55 | -------------------------------------------------------------------------------- /mock_diagnosticsProviderCoreProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | 8 | gocbcore "github.com/couchbase/gocbcore/v10" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // mockDiagnosticsProviderCoreProvider is an autogenerated mock type for the diagnosticsProviderCoreProvider type 13 | type mockDiagnosticsProviderCoreProvider struct { 14 | mock.Mock 15 | } 16 | 17 | // Diagnostics provides a mock function with given fields: opts 18 | func (_m *mockDiagnosticsProviderCoreProvider) Diagnostics(opts gocbcore.DiagnosticsOptions) (*gocbcore.DiagnosticInfo, error) { 19 | ret := _m.Called(opts) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for Diagnostics") 23 | } 24 | 25 | var r0 *gocbcore.DiagnosticInfo 26 | var r1 error 27 | if rf, ok := ret.Get(0).(func(gocbcore.DiagnosticsOptions) (*gocbcore.DiagnosticInfo, error)); ok { 28 | return rf(opts) 29 | } 30 | if rf, ok := ret.Get(0).(func(gocbcore.DiagnosticsOptions) *gocbcore.DiagnosticInfo); ok { 31 | r0 = rf(opts) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(*gocbcore.DiagnosticInfo) 35 | } 36 | } 37 | 38 | if rf, ok := ret.Get(1).(func(gocbcore.DiagnosticsOptions) error); ok { 39 | r1 = rf(opts) 40 | } else { 41 | r1 = ret.Error(1) 42 | } 43 | 44 | return r0, r1 45 | } 46 | 47 | // Ping provides a mock function with given fields: ctx, opts 48 | func (_m *mockDiagnosticsProviderCoreProvider) Ping(ctx context.Context, opts gocbcore.PingOptions) (*gocbcore.PingResult, error) { 49 | ret := _m.Called(ctx, opts) 50 | 51 | if len(ret) == 0 { 52 | panic("no return value specified for Ping") 53 | } 54 | 55 | var r0 *gocbcore.PingResult 56 | var r1 error 57 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.PingOptions) (*gocbcore.PingResult, error)); ok { 58 | return rf(ctx, opts) 59 | } 60 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.PingOptions) *gocbcore.PingResult); ok { 61 | r0 = rf(ctx, opts) 62 | } else { 63 | if ret.Get(0) != nil { 64 | r0 = ret.Get(0).(*gocbcore.PingResult) 65 | } 66 | } 67 | 68 | if rf, ok := ret.Get(1).(func(context.Context, gocbcore.PingOptions) error); ok { 69 | r1 = rf(ctx, opts) 70 | } else { 71 | r1 = ret.Error(1) 72 | } 73 | 74 | return r0, r1 75 | } 76 | 77 | // newMockDiagnosticsProviderCoreProvider creates a new instance of mockDiagnosticsProviderCoreProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 78 | // The first argument is typically a *testing.T value. 79 | func newMockDiagnosticsProviderCoreProvider(t interface { 80 | mock.TestingT 81 | Cleanup(func()) 82 | }) *mockDiagnosticsProviderCoreProvider { 83 | mock := &mockDiagnosticsProviderCoreProvider{} 84 | mock.Mock.Test(t) 85 | 86 | t.Cleanup(func() { mock.AssertExpectations(t) }) 87 | 88 | return mock 89 | } 90 | -------------------------------------------------------------------------------- /mock_diagnosticsProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockDiagnosticsProvider is an autogenerated mock type for the diagnosticsProvider type 8 | type mockDiagnosticsProvider struct { 9 | mock.Mock 10 | } 11 | 12 | // Diagnostics provides a mock function with given fields: opts 13 | func (_m *mockDiagnosticsProvider) Diagnostics(opts *DiagnosticsOptions) (*DiagnosticsResult, error) { 14 | ret := _m.Called(opts) 15 | 16 | if len(ret) == 0 { 17 | panic("no return value specified for Diagnostics") 18 | } 19 | 20 | var r0 *DiagnosticsResult 21 | var r1 error 22 | if rf, ok := ret.Get(0).(func(*DiagnosticsOptions) (*DiagnosticsResult, error)); ok { 23 | return rf(opts) 24 | } 25 | if rf, ok := ret.Get(0).(func(*DiagnosticsOptions) *DiagnosticsResult); ok { 26 | r0 = rf(opts) 27 | } else { 28 | if ret.Get(0) != nil { 29 | r0 = ret.Get(0).(*DiagnosticsResult) 30 | } 31 | } 32 | 33 | if rf, ok := ret.Get(1).(func(*DiagnosticsOptions) error); ok { 34 | r1 = rf(opts) 35 | } else { 36 | r1 = ret.Error(1) 37 | } 38 | 39 | return r0, r1 40 | } 41 | 42 | // Ping provides a mock function with given fields: opts 43 | func (_m *mockDiagnosticsProvider) Ping(opts *PingOptions) (*PingResult, error) { 44 | ret := _m.Called(opts) 45 | 46 | if len(ret) == 0 { 47 | panic("no return value specified for Ping") 48 | } 49 | 50 | var r0 *PingResult 51 | var r1 error 52 | if rf, ok := ret.Get(0).(func(*PingOptions) (*PingResult, error)); ok { 53 | return rf(opts) 54 | } 55 | if rf, ok := ret.Get(0).(func(*PingOptions) *PingResult); ok { 56 | r0 = rf(opts) 57 | } else { 58 | if ret.Get(0) != nil { 59 | r0 = ret.Get(0).(*PingResult) 60 | } 61 | } 62 | 63 | if rf, ok := ret.Get(1).(func(*PingOptions) error); ok { 64 | r1 = rf(opts) 65 | } else { 66 | r1 = ret.Error(1) 67 | } 68 | 69 | return r0, r1 70 | } 71 | 72 | // newMockDiagnosticsProvider creates a new instance of mockDiagnosticsProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 73 | // The first argument is typically a *testing.T value. 74 | func newMockDiagnosticsProvider(t interface { 75 | mock.TestingT 76 | Cleanup(func()) 77 | }) *mockDiagnosticsProvider { 78 | mock := &mockDiagnosticsProvider{} 79 | mock.Mock.Test(t) 80 | 81 | t.Cleanup(func() { mock.AssertExpectations(t) }) 82 | 83 | return mock 84 | } 85 | -------------------------------------------------------------------------------- /mock_httpProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | 8 | gocbcore "github.com/couchbase/gocbcore/v10" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // mockHttpProvider is an autogenerated mock type for the httpProvider type 13 | type mockHttpProvider struct { 14 | mock.Mock 15 | } 16 | 17 | // DoHTTPRequest provides a mock function with given fields: ctx, req 18 | func (_m *mockHttpProvider) DoHTTPRequest(ctx context.Context, req *gocbcore.HTTPRequest) (*gocbcore.HTTPResponse, error) { 19 | ret := _m.Called(ctx, req) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for DoHTTPRequest") 23 | } 24 | 25 | var r0 *gocbcore.HTTPResponse 26 | var r1 error 27 | if rf, ok := ret.Get(0).(func(context.Context, *gocbcore.HTTPRequest) (*gocbcore.HTTPResponse, error)); ok { 28 | return rf(ctx, req) 29 | } 30 | if rf, ok := ret.Get(0).(func(context.Context, *gocbcore.HTTPRequest) *gocbcore.HTTPResponse); ok { 31 | r0 = rf(ctx, req) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(*gocbcore.HTTPResponse) 35 | } 36 | } 37 | 38 | if rf, ok := ret.Get(1).(func(context.Context, *gocbcore.HTTPRequest) error); ok { 39 | r1 = rf(ctx, req) 40 | } else { 41 | r1 = ret.Error(1) 42 | } 43 | 44 | return r0, r1 45 | } 46 | 47 | // newMockHttpProvider creates a new instance of mockHttpProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 48 | // The first argument is typically a *testing.T value. 49 | func newMockHttpProvider(t interface { 50 | mock.TestingT 51 | Cleanup(func()) 52 | }) *mockHttpProvider { 53 | mock := &mockHttpProvider{} 54 | mock.Mock.Test(t) 55 | 56 | t.Cleanup(func() { mock.AssertExpectations(t) }) 57 | 58 | return mock 59 | } 60 | -------------------------------------------------------------------------------- /mock_kvCapabilityVerifier_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | gocbcore "github.com/couchbase/gocbcore/v10" 7 | mock "github.com/stretchr/testify/mock" 8 | ) 9 | 10 | // mockKvCapabilityVerifier is an autogenerated mock type for the kvCapabilityVerifier type 11 | type mockKvCapabilityVerifier struct { 12 | mock.Mock 13 | } 14 | 15 | // BucketCapabilityStatus provides a mock function with given fields: cap 16 | func (_m *mockKvCapabilityVerifier) BucketCapabilityStatus(cap gocbcore.BucketCapability) gocbcore.CapabilityStatus { 17 | ret := _m.Called(cap) 18 | 19 | if len(ret) == 0 { 20 | panic("no return value specified for BucketCapabilityStatus") 21 | } 22 | 23 | var r0 gocbcore.CapabilityStatus 24 | if rf, ok := ret.Get(0).(func(gocbcore.BucketCapability) gocbcore.CapabilityStatus); ok { 25 | r0 = rf(cap) 26 | } else { 27 | r0 = ret.Get(0).(gocbcore.CapabilityStatus) 28 | } 29 | 30 | return r0 31 | } 32 | 33 | // newMockKvCapabilityVerifier creates a new instance of mockKvCapabilityVerifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 34 | // The first argument is typically a *testing.T value. 35 | func newMockKvCapabilityVerifier(t interface { 36 | mock.TestingT 37 | Cleanup(func()) 38 | }) *mockKvCapabilityVerifier { 39 | mock := &mockKvCapabilityVerifier{} 40 | mock.Mock.Test(t) 41 | 42 | t.Cleanup(func() { mock.AssertExpectations(t) }) 43 | 44 | return mock 45 | } 46 | -------------------------------------------------------------------------------- /mock_mgmtProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | ) 10 | 11 | // mockMgmtProvider is an autogenerated mock type for the mgmtProvider type 12 | type mockMgmtProvider struct { 13 | mock.Mock 14 | } 15 | 16 | // executeMgmtRequest provides a mock function with given fields: ctx, req 17 | func (_m *mockMgmtProvider) executeMgmtRequest(ctx context.Context, req mgmtRequest) (*mgmtResponse, error) { 18 | ret := _m.Called(ctx, req) 19 | 20 | if len(ret) == 0 { 21 | panic("no return value specified for executeMgmtRequest") 22 | } 23 | 24 | var r0 *mgmtResponse 25 | var r1 error 26 | if rf, ok := ret.Get(0).(func(context.Context, mgmtRequest) (*mgmtResponse, error)); ok { 27 | return rf(ctx, req) 28 | } 29 | if rf, ok := ret.Get(0).(func(context.Context, mgmtRequest) *mgmtResponse); ok { 30 | r0 = rf(ctx, req) 31 | } else { 32 | if ret.Get(0) != nil { 33 | r0 = ret.Get(0).(*mgmtResponse) 34 | } 35 | } 36 | 37 | if rf, ok := ret.Get(1).(func(context.Context, mgmtRequest) error); ok { 38 | r1 = rf(ctx, req) 39 | } else { 40 | r1 = ret.Error(1) 41 | } 42 | 43 | return r0, r1 44 | } 45 | 46 | // newMockMgmtProvider creates a new instance of mockMgmtProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 47 | // The first argument is typically a *testing.T value. 48 | func newMockMgmtProvider(t interface { 49 | mock.TestingT 50 | Cleanup(func()) 51 | }) *mockMgmtProvider { 52 | mock := &mockMgmtProvider{} 53 | mock.Mock.Test(t) 54 | 55 | t.Cleanup(func() { mock.AssertExpectations(t) }) 56 | 57 | return mock 58 | } 59 | -------------------------------------------------------------------------------- /mock_pendingOp_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/stretchr/testify/mock" 4 | 5 | type mockPendingOp struct { 6 | mock.Mock 7 | } 8 | 9 | func (_m *mockPendingOp) Cancel() { 10 | _m.Called() 11 | } 12 | -------------------------------------------------------------------------------- /mock_queryProviderCoreProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | 8 | gocbcore "github.com/couchbase/gocbcore/v10" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // mockQueryProviderCoreProvider is an autogenerated mock type for the queryProviderCoreProvider type 13 | type mockQueryProviderCoreProvider struct { 14 | mock.Mock 15 | } 16 | 17 | // N1QLQuery provides a mock function with given fields: ctx, opts 18 | func (_m *mockQueryProviderCoreProvider) N1QLQuery(ctx context.Context, opts gocbcore.N1QLQueryOptions) (queryRowReader, error) { 19 | ret := _m.Called(ctx, opts) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for N1QLQuery") 23 | } 24 | 25 | var r0 queryRowReader 26 | var r1 error 27 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.N1QLQueryOptions) (queryRowReader, error)); ok { 28 | return rf(ctx, opts) 29 | } 30 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.N1QLQueryOptions) queryRowReader); ok { 31 | r0 = rf(ctx, opts) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(queryRowReader) 35 | } 36 | } 37 | 38 | if rf, ok := ret.Get(1).(func(context.Context, gocbcore.N1QLQueryOptions) error); ok { 39 | r1 = rf(ctx, opts) 40 | } else { 41 | r1 = ret.Error(1) 42 | } 43 | 44 | return r0, r1 45 | } 46 | 47 | // PreparedN1QLQuery provides a mock function with given fields: ctx, opts 48 | func (_m *mockQueryProviderCoreProvider) PreparedN1QLQuery(ctx context.Context, opts gocbcore.N1QLQueryOptions) (queryRowReader, error) { 49 | ret := _m.Called(ctx, opts) 50 | 51 | if len(ret) == 0 { 52 | panic("no return value specified for PreparedN1QLQuery") 53 | } 54 | 55 | var r0 queryRowReader 56 | var r1 error 57 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.N1QLQueryOptions) (queryRowReader, error)); ok { 58 | return rf(ctx, opts) 59 | } 60 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.N1QLQueryOptions) queryRowReader); ok { 61 | r0 = rf(ctx, opts) 62 | } else { 63 | if ret.Get(0) != nil { 64 | r0 = ret.Get(0).(queryRowReader) 65 | } 66 | } 67 | 68 | if rf, ok := ret.Get(1).(func(context.Context, gocbcore.N1QLQueryOptions) error); ok { 69 | r1 = rf(ctx, opts) 70 | } else { 71 | r1 = ret.Error(1) 72 | } 73 | 74 | return r0, r1 75 | } 76 | 77 | // newMockQueryProviderCoreProvider creates a new instance of mockQueryProviderCoreProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 78 | // The first argument is typically a *testing.T value. 79 | func newMockQueryProviderCoreProvider(t interface { 80 | mock.TestingT 81 | Cleanup(func()) 82 | }) *mockQueryProviderCoreProvider { 83 | mock := &mockQueryProviderCoreProvider{} 84 | mock.Mock.Test(t) 85 | 86 | t.Cleanup(func() { mock.AssertExpectations(t) }) 87 | 88 | return mock 89 | } 90 | -------------------------------------------------------------------------------- /mock_queryProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockQueryProvider is an autogenerated mock type for the queryProvider type 8 | type mockQueryProvider struct { 9 | mock.Mock 10 | } 11 | 12 | // Query provides a mock function with given fields: statement, s, opts 13 | func (_m *mockQueryProvider) Query(statement string, s *Scope, opts *QueryOptions) (*QueryResult, error) { 14 | ret := _m.Called(statement, s, opts) 15 | 16 | if len(ret) == 0 { 17 | panic("no return value specified for Query") 18 | } 19 | 20 | var r0 *QueryResult 21 | var r1 error 22 | if rf, ok := ret.Get(0).(func(string, *Scope, *QueryOptions) (*QueryResult, error)); ok { 23 | return rf(statement, s, opts) 24 | } 25 | if rf, ok := ret.Get(0).(func(string, *Scope, *QueryOptions) *QueryResult); ok { 26 | r0 = rf(statement, s, opts) 27 | } else { 28 | if ret.Get(0) != nil { 29 | r0 = ret.Get(0).(*QueryResult) 30 | } 31 | } 32 | 33 | if rf, ok := ret.Get(1).(func(string, *Scope, *QueryOptions) error); ok { 34 | r1 = rf(statement, s, opts) 35 | } else { 36 | r1 = ret.Error(1) 37 | } 38 | 39 | return r0, r1 40 | } 41 | 42 | // newMockQueryProvider creates a new instance of mockQueryProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 43 | // The first argument is typically a *testing.T value. 44 | func newMockQueryProvider(t interface { 45 | mock.TestingT 46 | Cleanup(func()) 47 | }) *mockQueryProvider { 48 | mock := &mockQueryProvider{} 49 | mock.Mock.Test(t) 50 | 51 | t.Cleanup(func() { mock.AssertExpectations(t) }) 52 | 53 | return mock 54 | } 55 | -------------------------------------------------------------------------------- /mock_searchCapabilityVerifier_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | gocbcore "github.com/couchbase/gocbcore/v10" 7 | mock "github.com/stretchr/testify/mock" 8 | ) 9 | 10 | // mockSearchCapabilityVerifier is an autogenerated mock type for the searchCapabilityVerifier type 11 | type mockSearchCapabilityVerifier struct { 12 | mock.Mock 13 | } 14 | 15 | // SearchCapabilityStatus provides a mock function with given fields: cap 16 | func (_m *mockSearchCapabilityVerifier) SearchCapabilityStatus(cap gocbcore.SearchCapability) gocbcore.CapabilityStatus { 17 | ret := _m.Called(cap) 18 | 19 | if len(ret) == 0 { 20 | panic("no return value specified for SearchCapabilityStatus") 21 | } 22 | 23 | var r0 gocbcore.CapabilityStatus 24 | if rf, ok := ret.Get(0).(func(gocbcore.SearchCapability) gocbcore.CapabilityStatus); ok { 25 | r0 = rf(cap) 26 | } else { 27 | r0 = ret.Get(0).(gocbcore.CapabilityStatus) 28 | } 29 | 30 | return r0 31 | } 32 | 33 | // newMockSearchCapabilityVerifier creates a new instance of mockSearchCapabilityVerifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 34 | // The first argument is typically a *testing.T value. 35 | func newMockSearchCapabilityVerifier(t interface { 36 | mock.TestingT 37 | Cleanup(func()) 38 | }) *mockSearchCapabilityVerifier { 39 | mock := &mockSearchCapabilityVerifier{} 40 | mock.Mock.Test(t) 41 | 42 | t.Cleanup(func() { mock.AssertExpectations(t) }) 43 | 44 | return mock 45 | } 46 | -------------------------------------------------------------------------------- /mock_searchProviderCoreProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | 8 | gocbcore "github.com/couchbase/gocbcore/v10" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // mockSearchProviderCoreProvider is an autogenerated mock type for the searchProviderCoreProvider type 13 | type mockSearchProviderCoreProvider struct { 14 | mock.Mock 15 | } 16 | 17 | // SearchQuery provides a mock function with given fields: ctx, opts 18 | func (_m *mockSearchProviderCoreProvider) SearchQuery(ctx context.Context, opts gocbcore.SearchQueryOptions) (searchRowReader, error) { 19 | ret := _m.Called(ctx, opts) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for SearchQuery") 23 | } 24 | 25 | var r0 searchRowReader 26 | var r1 error 27 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.SearchQueryOptions) (searchRowReader, error)); ok { 28 | return rf(ctx, opts) 29 | } 30 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.SearchQueryOptions) searchRowReader); ok { 31 | r0 = rf(ctx, opts) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(searchRowReader) 35 | } 36 | } 37 | 38 | if rf, ok := ret.Get(1).(func(context.Context, gocbcore.SearchQueryOptions) error); ok { 39 | r1 = rf(ctx, opts) 40 | } else { 41 | r1 = ret.Error(1) 42 | } 43 | 44 | return r0, r1 45 | } 46 | 47 | // newMockSearchProviderCoreProvider creates a new instance of mockSearchProviderCoreProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 48 | // The first argument is typically a *testing.T value. 49 | func newMockSearchProviderCoreProvider(t interface { 50 | mock.TestingT 51 | Cleanup(func()) 52 | }) *mockSearchProviderCoreProvider { 53 | mock := &mockSearchProviderCoreProvider{} 54 | mock.Mock.Test(t) 55 | 56 | t.Cleanup(func() { mock.AssertExpectations(t) }) 57 | 58 | return mock 59 | } 60 | -------------------------------------------------------------------------------- /mock_searchProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | search "github.com/couchbase/gocb/v2/search" 7 | mock "github.com/stretchr/testify/mock" 8 | ) 9 | 10 | // mockSearchProvider is an autogenerated mock type for the searchProvider type 11 | type mockSearchProvider struct { 12 | mock.Mock 13 | } 14 | 15 | // Search provides a mock function with given fields: scope, indexName, request, opts 16 | func (_m *mockSearchProvider) Search(scope *Scope, indexName string, request SearchRequest, opts *SearchOptions) (*SearchResult, error) { 17 | ret := _m.Called(scope, indexName, request, opts) 18 | 19 | if len(ret) == 0 { 20 | panic("no return value specified for Search") 21 | } 22 | 23 | var r0 *SearchResult 24 | var r1 error 25 | if rf, ok := ret.Get(0).(func(*Scope, string, SearchRequest, *SearchOptions) (*SearchResult, error)); ok { 26 | return rf(scope, indexName, request, opts) 27 | } 28 | if rf, ok := ret.Get(0).(func(*Scope, string, SearchRequest, *SearchOptions) *SearchResult); ok { 29 | r0 = rf(scope, indexName, request, opts) 30 | } else { 31 | if ret.Get(0) != nil { 32 | r0 = ret.Get(0).(*SearchResult) 33 | } 34 | } 35 | 36 | if rf, ok := ret.Get(1).(func(*Scope, string, SearchRequest, *SearchOptions) error); ok { 37 | r1 = rf(scope, indexName, request, opts) 38 | } else { 39 | r1 = ret.Error(1) 40 | } 41 | 42 | return r0, r1 43 | } 44 | 45 | // SearchQuery provides a mock function with given fields: indexName, query, opts 46 | func (_m *mockSearchProvider) SearchQuery(indexName string, query search.Query, opts *SearchOptions) (*SearchResult, error) { 47 | ret := _m.Called(indexName, query, opts) 48 | 49 | if len(ret) == 0 { 50 | panic("no return value specified for SearchQuery") 51 | } 52 | 53 | var r0 *SearchResult 54 | var r1 error 55 | if rf, ok := ret.Get(0).(func(string, search.Query, *SearchOptions) (*SearchResult, error)); ok { 56 | return rf(indexName, query, opts) 57 | } 58 | if rf, ok := ret.Get(0).(func(string, search.Query, *SearchOptions) *SearchResult); ok { 59 | r0 = rf(indexName, query, opts) 60 | } else { 61 | if ret.Get(0) != nil { 62 | r0 = ret.Get(0).(*SearchResult) 63 | } 64 | } 65 | 66 | if rf, ok := ret.Get(1).(func(string, search.Query, *SearchOptions) error); ok { 67 | r1 = rf(indexName, query, opts) 68 | } else { 69 | r1 = ret.Error(1) 70 | } 71 | 72 | return r0, r1 73 | } 74 | 75 | // newMockSearchProvider creates a new instance of mockSearchProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 76 | // The first argument is typically a *testing.T value. 77 | func newMockSearchProvider(t interface { 78 | mock.TestingT 79 | Cleanup(func()) 80 | }) *mockSearchProvider { 81 | mock := &mockSearchProvider{} 82 | mock.Mock.Test(t) 83 | 84 | t.Cleanup(func() { mock.AssertExpectations(t) }) 85 | 86 | return mock 87 | } 88 | -------------------------------------------------------------------------------- /mock_viewProviderCoreProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | 8 | gocbcore "github.com/couchbase/gocbcore/v10" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // mockViewProviderCoreProvider is an autogenerated mock type for the viewProviderCoreProvider type 13 | type mockViewProviderCoreProvider struct { 14 | mock.Mock 15 | } 16 | 17 | // ViewQuery provides a mock function with given fields: ctx, opts 18 | func (_m *mockViewProviderCoreProvider) ViewQuery(ctx context.Context, opts gocbcore.ViewQueryOptions) (viewRowReader, error) { 19 | ret := _m.Called(ctx, opts) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for ViewQuery") 23 | } 24 | 25 | var r0 viewRowReader 26 | var r1 error 27 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.ViewQueryOptions) (viewRowReader, error)); ok { 28 | return rf(ctx, opts) 29 | } 30 | if rf, ok := ret.Get(0).(func(context.Context, gocbcore.ViewQueryOptions) viewRowReader); ok { 31 | r0 = rf(ctx, opts) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(viewRowReader) 35 | } 36 | } 37 | 38 | if rf, ok := ret.Get(1).(func(context.Context, gocbcore.ViewQueryOptions) error); ok { 39 | r1 = rf(ctx, opts) 40 | } else { 41 | r1 = ret.Error(1) 42 | } 43 | 44 | return r0, r1 45 | } 46 | 47 | // newMockViewProviderCoreProvider creates a new instance of mockViewProviderCoreProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 48 | // The first argument is typically a *testing.T value. 49 | func newMockViewProviderCoreProvider(t interface { 50 | mock.TestingT 51 | Cleanup(func()) 52 | }) *mockViewProviderCoreProvider { 53 | mock := &mockViewProviderCoreProvider{} 54 | mock.Mock.Test(t) 55 | 56 | t.Cleanup(func() { mock.AssertExpectations(t) }) 57 | 58 | return mock 59 | } 60 | -------------------------------------------------------------------------------- /mock_viewProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // mockViewProvider is an autogenerated mock type for the viewProvider type 8 | type mockViewProvider struct { 9 | mock.Mock 10 | } 11 | 12 | // ViewQuery provides a mock function with given fields: designDoc, viewName, opts 13 | func (_m *mockViewProvider) ViewQuery(designDoc string, viewName string, opts *ViewOptions) (*ViewResult, error) { 14 | ret := _m.Called(designDoc, viewName, opts) 15 | 16 | if len(ret) == 0 { 17 | panic("no return value specified for ViewQuery") 18 | } 19 | 20 | var r0 *ViewResult 21 | var r1 error 22 | if rf, ok := ret.Get(0).(func(string, string, *ViewOptions) (*ViewResult, error)); ok { 23 | return rf(designDoc, viewName, opts) 24 | } 25 | if rf, ok := ret.Get(0).(func(string, string, *ViewOptions) *ViewResult); ok { 26 | r0 = rf(designDoc, viewName, opts) 27 | } else { 28 | if ret.Get(0) != nil { 29 | r0 = ret.Get(0).(*ViewResult) 30 | } 31 | } 32 | 33 | if rf, ok := ret.Get(1).(func(string, string, *ViewOptions) error); ok { 34 | r1 = rf(designDoc, viewName, opts) 35 | } else { 36 | r1 = ret.Error(1) 37 | } 38 | 39 | return r0, r1 40 | } 41 | 42 | // newMockViewProvider creates a new instance of mockViewProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 43 | // The first argument is typically a *testing.T value. 44 | func newMockViewProvider(t interface { 45 | mock.TestingT 46 | Cleanup(func()) 47 | }) *mockViewProvider { 48 | mock := &mockViewProvider{} 49 | mock.Mock.Test(t) 50 | 51 | t.Cleanup(func() { mock.AssertExpectations(t) }) 52 | 53 | return mock 54 | } 55 | -------------------------------------------------------------------------------- /mock_waitUntilReadyProvider_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.46.2. DO NOT EDIT. 2 | 3 | package gocb 4 | 5 | import ( 6 | context "context" 7 | time "time" 8 | 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // mockWaitUntilReadyProvider is an autogenerated mock type for the waitUntilReadyProvider type 13 | type mockWaitUntilReadyProvider struct { 14 | mock.Mock 15 | } 16 | 17 | // WaitUntilReady provides a mock function with given fields: ctx, deadline, opts 18 | func (_m *mockWaitUntilReadyProvider) WaitUntilReady(ctx context.Context, deadline time.Time, opts *WaitUntilReadyOptions) error { 19 | ret := _m.Called(ctx, deadline, opts) 20 | 21 | if len(ret) == 0 { 22 | panic("no return value specified for WaitUntilReady") 23 | } 24 | 25 | var r0 error 26 | if rf, ok := ret.Get(0).(func(context.Context, time.Time, *WaitUntilReadyOptions) error); ok { 27 | r0 = rf(ctx, deadline, opts) 28 | } else { 29 | r0 = ret.Error(0) 30 | } 31 | 32 | return r0 33 | } 34 | 35 | // newMockWaitUntilReadyProvider creates a new instance of mockWaitUntilReadyProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 36 | // The first argument is typically a *testing.T value. 37 | func newMockWaitUntilReadyProvider(t interface { 38 | mock.TestingT 39 | Cleanup(func()) 40 | }) *mockWaitUntilReadyProvider { 41 | mock := &mockWaitUntilReadyProvider{} 42 | mock.Mock.Test(t) 43 | 44 | t.Cleanup(func() { mock.AssertExpectations(t) }) 45 | 46 | return mock 47 | } 48 | -------------------------------------------------------------------------------- /nodeversion_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type NodeVersion struct { 11 | Major int 12 | Minor int 13 | Patch int 14 | Build int 15 | Edition NodeEdition 16 | Modifier string 17 | IsMock bool 18 | } 19 | 20 | type NodeEdition int 21 | 22 | const ( 23 | CommunityNodeEdition = NodeEdition(1) 24 | EnterpriseNodeEdition = NodeEdition(2) 25 | ProtostellarNodeEdition = NodeEdition(3) 26 | ) 27 | 28 | func (v NodeVersion) Equal(ov NodeVersion) bool { 29 | if v.Major == ov.Major && v.Minor == ov.Minor && 30 | v.Patch == ov.Patch && v.Edition == ov.Edition && v.Modifier == ov.Modifier { 31 | return true 32 | } 33 | return false 34 | } 35 | 36 | func (v NodeVersion) Higher(ov NodeVersion) bool { 37 | if v.Major > ov.Major { 38 | return true 39 | } else if v.Major < ov.Major { 40 | return false 41 | } 42 | 43 | if v.Minor > ov.Minor { 44 | return true 45 | } else if v.Minor < ov.Minor { 46 | return false 47 | } 48 | 49 | if v.Patch > ov.Patch { 50 | return true 51 | } else if v.Patch < ov.Patch { 52 | return false 53 | } 54 | 55 | if v.Build > ov.Build { 56 | return true 57 | } else if v.Build < ov.Build { 58 | return false 59 | } 60 | 61 | if v.Edition > ov.Edition { 62 | return true 63 | } 64 | 65 | return false 66 | } 67 | 68 | func (v NodeVersion) Lower(ov NodeVersion) bool { 69 | return !v.Higher(ov) && !v.Equal(ov) 70 | } 71 | 72 | func newNodeVersion(version string, isMock bool) (*NodeVersion, error) { 73 | nodeVersion, err := nodeVersionFromString(version) 74 | if err != nil { 75 | return nil, err 76 | } 77 | nodeVersion.IsMock = isMock 78 | 79 | return nodeVersion, nil 80 | } 81 | 82 | func nodeVersionFromString(version string) (*NodeVersion, error) { 83 | vSplit := strings.Split(version, ".") 84 | lenSplit := len(vSplit) 85 | if lenSplit == 0 { 86 | return nil, fmt.Errorf("must provide at least a major version") 87 | } 88 | 89 | var err error 90 | nodeVersion := NodeVersion{} 91 | nodeVersion.Major, err = strconv.Atoi(vSplit[0]) 92 | if err != nil { 93 | return nil, fmt.Errorf("major version is not a valid integer") 94 | } 95 | if lenSplit == 1 { 96 | return &nodeVersion, nil 97 | } 98 | 99 | nodeVersion.Minor, err = strconv.Atoi(vSplit[1]) 100 | if err != nil { 101 | return nil, fmt.Errorf("minor version is not a valid integer") 102 | } 103 | if lenSplit == 2 { 104 | return &nodeVersion, nil 105 | } 106 | 107 | nodeBuild := strings.Split(vSplit[2], "-") 108 | nodeVersion.Patch, err = strconv.Atoi(nodeBuild[0]) 109 | if err != nil { 110 | return nil, fmt.Errorf("patch version is not a valid integer") 111 | } 112 | if len(nodeBuild) == 1 { 113 | return &nodeVersion, nil 114 | } 115 | 116 | buildEdition := strings.Split(nodeBuild[1], "-") 117 | nodeVersion.Build, err = strconv.Atoi(buildEdition[0]) 118 | if err != nil { 119 | edition, modifier, err := editionModifierFromString(buildEdition[0]) 120 | if err != nil { 121 | return nil, err 122 | } 123 | nodeVersion.Edition = edition 124 | nodeVersion.Modifier = modifier 125 | 126 | return &nodeVersion, nil 127 | } 128 | if len(buildEdition) == 1 { 129 | return &nodeVersion, nil 130 | } 131 | 132 | edition, modifier, err := editionModifierFromString(buildEdition[1]) 133 | if err != nil { 134 | return nil, err 135 | } 136 | nodeVersion.Edition = edition 137 | nodeVersion.Modifier = modifier 138 | 139 | return &nodeVersion, nil 140 | } 141 | 142 | func editionModifierFromString(editionModifier string) (NodeEdition, string, error) { 143 | split := strings.Split(editionModifier, "-") 144 | editionStr := strings.ToLower(split[0]) 145 | var edition NodeEdition 146 | var modifier string 147 | if editionStr == "enterprise" { 148 | edition = EnterpriseNodeEdition 149 | } else if editionStr == "community" { 150 | edition = CommunityNodeEdition 151 | } else if editionStr == "dp" { 152 | modifier = editionStr 153 | } else if editionStr == "protostellar" { 154 | edition = ProtostellarNodeEdition 155 | } else { 156 | return 0, "", errors.New("Unrecognised edition or modifier: " + editionStr) 157 | } 158 | if len(split) == 1 { 159 | return edition, modifier, nil 160 | } 161 | 162 | return edition, strings.ToLower(split[1]), nil 163 | } 164 | -------------------------------------------------------------------------------- /providers.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | 6 | gocbcore "github.com/couchbase/gocbcore/v10" 7 | ) 8 | 9 | // NOTE: context in these provider functions can be passed as a nil value. 10 | // The async op manager will check for a nil context.Context, the context values should never be assumed to be non-nil. 11 | 12 | type httpProvider interface { 13 | DoHTTPRequest(ctx context.Context, req *gocbcore.HTTPRequest) (*gocbcore.HTTPResponse, error) 14 | } 15 | 16 | type gocbcoreDiagnosticsProvider interface { 17 | Diagnostics(opts gocbcore.DiagnosticsOptions) (*gocbcore.DiagnosticInfo, error) 18 | Ping(opts gocbcore.PingOptions, cb gocbcore.PingCallback) (gocbcore.PendingOp, error) 19 | } 20 | 21 | type gocbcoreHTTPProvider interface { 22 | DoHTTPRequest(req *gocbcore.HTTPRequest, cb gocbcore.DoHTTPRequestCallback) (gocbcore.PendingOp, error) 23 | } 24 | 25 | type diagnosticsProviderWrapper struct { 26 | provider gocbcoreDiagnosticsProvider 27 | } 28 | 29 | func (dpw *diagnosticsProviderWrapper) Diagnostics(opts gocbcore.DiagnosticsOptions) (*gocbcore.DiagnosticInfo, error) { 30 | return dpw.provider.Diagnostics(opts) 31 | } 32 | 33 | func (dpw *diagnosticsProviderWrapper) Ping(ctx context.Context, opts gocbcore.PingOptions) (pOut *gocbcore.PingResult, errOut error) { 34 | opm := newAsyncOpManager(ctx) 35 | err := opm.Wait(dpw.provider.Ping(opts, func(res *gocbcore.PingResult, err error) { 36 | if err != nil { 37 | errOut = err 38 | opm.Reject() 39 | return 40 | } 41 | 42 | pOut = res 43 | opm.Resolve() 44 | })) 45 | if err != nil { 46 | errOut = err 47 | } 48 | 49 | return 50 | } 51 | 52 | type httpProviderWrapper struct { 53 | provider gocbcoreHTTPProvider 54 | } 55 | 56 | func (hpw *httpProviderWrapper) DoHTTPRequest(ctx context.Context, req *gocbcore.HTTPRequest) (respOut *gocbcore.HTTPResponse, errOut error) { 57 | opm := newAsyncOpManager(ctx) 58 | err := opm.Wait(hpw.provider.DoHTTPRequest(req, func(res *gocbcore.HTTPResponse, err error) { 59 | if err != nil { 60 | errOut = err 61 | opm.Reject() 62 | return 63 | } 64 | 65 | respOut = res 66 | opm.Resolve() 67 | })) 68 | if err != nil { 69 | errOut = err 70 | } 71 | 72 | return 73 | } 74 | 75 | type analyticsProviderWrapper struct { 76 | provider *gocbcore.AgentGroup 77 | } 78 | 79 | func (apw *analyticsProviderWrapper) AnalyticsQuery(ctx context.Context, opts gocbcore.AnalyticsQueryOptions) (aOut analyticsRowReader, errOut error) { 80 | opm := newAsyncOpManager(ctx) 81 | err := opm.Wait(apw.provider.AnalyticsQuery(opts, func(reader *gocbcore.AnalyticsRowReader, err error) { 82 | if err != nil { 83 | errOut = err 84 | opm.Reject() 85 | return 86 | } 87 | 88 | aOut = reader 89 | opm.Resolve() 90 | })) 91 | if err != nil { 92 | errOut = err 93 | } 94 | 95 | return 96 | } 97 | 98 | type queryProviderWrapper struct { 99 | provider *gocbcore.AgentGroup 100 | } 101 | 102 | func (apw *queryProviderWrapper) N1QLQuery(ctx context.Context, opts gocbcore.N1QLQueryOptions) (qOut queryRowReader, errOut error) { 103 | opm := newAsyncOpManager(ctx) 104 | err := opm.Wait(apw.provider.N1QLQuery(opts, func(reader *gocbcore.N1QLRowReader, err error) { 105 | if err != nil { 106 | errOut = err 107 | opm.Reject() 108 | return 109 | } 110 | 111 | qOut = reader 112 | opm.Resolve() 113 | })) 114 | if err != nil { 115 | errOut = err 116 | } 117 | 118 | return 119 | } 120 | 121 | func (apw *queryProviderWrapper) PreparedN1QLQuery(ctx context.Context, opts gocbcore.N1QLQueryOptions) (qOut queryRowReader, errOut error) { 122 | opm := newAsyncOpManager(ctx) 123 | err := opm.Wait(apw.provider.PreparedN1QLQuery(opts, func(reader *gocbcore.N1QLRowReader, err error) { 124 | if err != nil { 125 | errOut = err 126 | opm.Reject() 127 | return 128 | } 129 | 130 | qOut = reader 131 | opm.Resolve() 132 | })) 133 | if err != nil { 134 | errOut = err 135 | } 136 | 137 | return 138 | } 139 | -------------------------------------------------------------------------------- /queryindexprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "time" 4 | 5 | type queryIndexProvider interface { 6 | CreatePrimaryIndex(c *Collection, bucketName string, opts *CreatePrimaryQueryIndexOptions) error 7 | CreateIndex(c *Collection, bucketName, indexName string, fields []string, opts *CreateQueryIndexOptions) error 8 | DropPrimaryIndex(c *Collection, bucketName string, opts *DropPrimaryQueryIndexOptions) error 9 | DropIndex(c *Collection, bucketName, indexName string, opts *DropQueryIndexOptions) error 10 | GetAllIndexes(c *Collection, bucketName string, opts *GetAllQueryIndexesOptions) ([]QueryIndex, error) 11 | BuildDeferredIndexes(c *Collection, bucketName string, opts *BuildDeferredQueryIndexOptions) ([]string, error) 12 | WatchIndexes(c *Collection, bucketName string, watchList []string, timeout time.Duration, opts *WatchQueryIndexOptions) error 13 | } 14 | -------------------------------------------------------------------------------- /queryprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type queryProvider interface { 4 | Query(statement string, s *Scope, opts *QueryOptions) (*QueryResult, error) 5 | } 6 | 7 | type queryRowReader interface { 8 | NextRow() []byte 9 | Err() error 10 | MetaData() ([]byte, error) 11 | Close() error 12 | PreparedName() (string, error) 13 | Endpoint() string 14 | } 15 | -------------------------------------------------------------------------------- /retry_core.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/couchbase/gocbcore/v10" 4 | 5 | func translateCoreRetryReasons(reasons []gocbcore.RetryReason) []RetryReason { 6 | var reasonsOut []RetryReason 7 | 8 | for _, retryReason := range reasons { 9 | gocbReason, ok := retryReason.(RetryReason) 10 | if !ok { 11 | logErrorf("Failed to assert gocbcore retry reason to gocb retry reason: %v", retryReason) 12 | continue 13 | } 14 | reasonsOut = append(reasonsOut, gocbReason) 15 | } 16 | 17 | return reasonsOut 18 | } 19 | 20 | type wrappedCoreRetryRequest struct { 21 | req gocbcore.RetryRequest 22 | } 23 | 24 | func (req *wrappedCoreRetryRequest) RetryAttempts() uint32 { 25 | return req.req.RetryAttempts() 26 | } 27 | 28 | func (req *wrappedCoreRetryRequest) Identifier() string { 29 | return req.req.Identifier() 30 | } 31 | 32 | func (req *wrappedCoreRetryRequest) Idempotent() bool { 33 | return req.req.Idempotent() 34 | } 35 | 36 | func (req *wrappedCoreRetryRequest) RetryReasons() []RetryReason { 37 | return translateCoreRetryReasons(req.req.RetryReasons()) 38 | } 39 | 40 | func newCoreRetryStrategyWrapper(strategy RetryStrategy) *coreRetryStrategyWrapper { 41 | return &coreRetryStrategyWrapper{ 42 | wrapped: strategy, 43 | } 44 | } 45 | 46 | type coreRetryStrategyWrapper struct { 47 | wrapped RetryStrategy 48 | } 49 | 50 | // RetryAfter calculates and returns a RetryAction describing how long to wait before retrying an operation. 51 | func (rs *coreRetryStrategyWrapper) RetryAfter(req gocbcore.RetryRequest, reason gocbcore.RetryReason) gocbcore.RetryAction { 52 | wreq := &wrappedCoreRetryRequest{ 53 | req: req, 54 | } 55 | wrappedAction := rs.wrapped.RetryAfter(wreq, RetryReason(reason)) 56 | return gocbcore.RetryAction(wrappedAction) 57 | } 58 | -------------------------------------------------------------------------------- /scope_analyticsquery.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | // AnalyticsQuery executes the analytics query statement on the server, constraining the query to the bucket and scope. 4 | func (s *Scope) AnalyticsQuery(statement string, opts *AnalyticsOptions) (*AnalyticsResult, error) { 5 | return autoOpControl(s.analyticsController(), "analytics", func(provider analyticsProvider) (*AnalyticsResult, error) { 6 | if opts == nil { 7 | opts = &AnalyticsOptions{} 8 | } 9 | 10 | return provider.AnalyticsQuery(statement, s, opts) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /scope_analyticsquery_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func (suite *IntegrationTestSuite) TestScopeAnalyticsQuery() { 9 | suite.skipIfUnsupported(CollectionsAnalyticsFeature) 10 | 11 | n := suite.setupScopeAnalytics() 12 | query := fmt.Sprintf("SELECT * FROM %s.%s.%s WHERE service=? LIMIT %d;", 13 | globalBucket.Name(), 14 | globalScope.Name(), 15 | globalCollection.Name(), n) 16 | suite.runAnalyticsTest(n, query, globalBucket.Name(), globalScope.Name(), globalScope) 17 | } 18 | 19 | func (suite *IntegrationTestSuite) setupScopeAnalytics() int { 20 | n, err := suite.createBreweryDataset("beer_sample_brewery_five", "analytics", "", globalCollection.Name()) 21 | suite.Require().Nil(err, "Failed to create dataset %v", err) 22 | 23 | results, err := globalCluster.AnalyticsQuery( 24 | fmt.Sprintf("ALTER COLLECTION %s.%s.%s ENABLE ANALYTICS", 25 | globalBucket.Name(), 26 | globalScope.Name(), 27 | globalCollection.Name(), 28 | ), 29 | &AnalyticsOptions{ 30 | Timeout: 10 * time.Second, 31 | }, 32 | ) 33 | suite.Require().Nil(err, "Failed to create analytics collection %v", err) 34 | 35 | suite.Require().Nil(results.Close()) 36 | 37 | return n 38 | } 39 | -------------------------------------------------------------------------------- /scope_query.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | // Query executes the query statement on the server, constraining the query to the bucket and scope. 4 | func (s *Scope) Query(statement string, opts *QueryOptions) (*QueryResult, error) { 5 | return autoOpControl(s.queryController(), "query", func(provider queryProvider) (*QueryResult, error) { 6 | if opts == nil { 7 | opts = &QueryOptions{} 8 | } 9 | 10 | if opts.AsTransaction != nil { 11 | return s.getTransactions().singleQuery(statement, s, *opts) 12 | } 13 | 14 | return provider.Query(statement, s, opts) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /scope_searchquery.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | // Search executes the search request on the server using a scope-level FTS index. 4 | func (s *Scope) Search(indexName string, request SearchRequest, opts *SearchOptions) (*SearchResult, error) { 5 | return autoOpControl(s.searchController(), "search", func(provider searchProvider) (*SearchResult, error) { 6 | if request.VectorSearch == nil && request.SearchQuery == nil { 7 | return nil, makeInvalidArgumentsError("the search request cannot be empty") 8 | } 9 | 10 | if opts == nil { 11 | opts = &SearchOptions{} 12 | } 13 | 14 | return provider.Search(s, indexName, request, opts) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /search/facets.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // Facet represents a facet for a search query. 8 | type Facet interface { 9 | } 10 | 11 | type termFacetData struct { 12 | Field string `json:"field,omitempty"` 13 | Size uint64 `json:"size,omitempty"` 14 | } 15 | 16 | // TermFacet is an search term facet. 17 | type TermFacet struct { 18 | data termFacetData 19 | } 20 | 21 | // MarshalJSON marshal's this facet to JSON for the search REST API. 22 | func (f TermFacet) MarshalJSON() ([]byte, error) { 23 | return json.Marshal(f.data) 24 | } 25 | 26 | // NewTermFacet creates a new TermFacet 27 | func NewTermFacet(field string, size uint64) *TermFacet { 28 | mq := &TermFacet{} 29 | mq.data.Field = field 30 | mq.data.Size = size 31 | return mq 32 | } 33 | 34 | type numericFacetRange struct { 35 | Name string `json:"name,omitempty"` 36 | Start float64 `json:"min,omitempty"` 37 | End float64 `json:"max,omitempty"` 38 | } 39 | type numericFacetData struct { 40 | Field string `json:"field,omitempty"` 41 | Size uint64 `json:"size,omitempty"` 42 | NumericRanges []numericFacetRange `json:"numeric_ranges,omitempty"` 43 | } 44 | 45 | // NumericFacet is an search numeric range facet. 46 | type NumericFacet struct { 47 | data numericFacetData 48 | } 49 | 50 | // MarshalJSON marshal's this facet to JSON for the search REST API. 51 | func (f NumericFacet) MarshalJSON() ([]byte, error) { 52 | return json.Marshal(f.data) 53 | } 54 | 55 | // AddRange adds a new range to this numeric range facet. 56 | func (f *NumericFacet) AddRange(name string, start, end float64) *NumericFacet { 57 | f.data.NumericRanges = append(f.data.NumericRanges, numericFacetRange{ 58 | Name: name, 59 | Start: start, 60 | End: end, 61 | }) 62 | return f 63 | } 64 | 65 | // NewNumericFacet creates a new numeric range facet. 66 | func NewNumericFacet(field string, size uint64) *NumericFacet { 67 | mq := &NumericFacet{} 68 | mq.data.Field = field 69 | mq.data.Size = size 70 | return mq 71 | } 72 | 73 | type dateFacetRange struct { 74 | Name string `json:"name,omitempty"` 75 | Start string `json:"start,omitempty"` 76 | End string `json:"end,omitempty"` 77 | } 78 | type dateFacetData struct { 79 | Field string `json:"field,omitempty"` 80 | Size uint64 `json:"size,omitempty"` 81 | DateRanges []dateFacetRange `json:"date_ranges,omitempty"` 82 | } 83 | 84 | // DateFacet is an search date range facet. 85 | type DateFacet struct { 86 | data dateFacetData 87 | } 88 | 89 | // MarshalJSON marshal's this facet to JSON for the search REST API. 90 | func (f DateFacet) MarshalJSON() ([]byte, error) { 91 | return json.Marshal(f.data) 92 | } 93 | 94 | // AddRange adds a new range to this date range facet. 95 | func (f *DateFacet) AddRange(name string, start, end string) *DateFacet { 96 | f.data.DateRanges = append(f.data.DateRanges, dateFacetRange{ 97 | Name: name, 98 | Start: start, 99 | End: end, 100 | }) 101 | return f 102 | } 103 | 104 | // NewDateFacet creates a new date range facet. 105 | func NewDateFacet(field string, size uint64) *DateFacet { 106 | mq := &DateFacet{} 107 | mq.data.Field = field 108 | mq.data.Size = size 109 | return mq 110 | } 111 | -------------------------------------------------------------------------------- /search_request.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "github.com/couchbase/gocb/v2/search" 5 | "github.com/couchbase/gocb/v2/vector" 6 | ) 7 | 8 | // SearchRequest is used for describing a search request used with Search. 9 | type SearchRequest struct { 10 | SearchQuery search.Query 11 | VectorSearch *vector.Search 12 | } 13 | -------------------------------------------------------------------------------- /searchindexprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type searchIndexProvider interface { 4 | GetAllIndexes(scope *Scope, opts *GetAllSearchIndexOptions) ([]SearchIndex, error) 5 | GetIndex(scope *Scope, indexName string, opts *GetSearchIndexOptions) (*SearchIndex, error) 6 | UpsertIndex(scope *Scope, indexDefinition SearchIndex, opts *UpsertSearchIndexOptions) error 7 | DropIndex(scope *Scope, indexName string, opts *DropSearchIndexOptions) error 8 | AnalyzeDocument(scope *Scope, indexName string, doc interface{}, opts *AnalyzeDocumentOptions) ([]interface{}, error) 9 | GetIndexedDocumentsCount(scope *Scope, indexName string, opts *GetIndexedDocumentsCountOptions) (uint64, error) 10 | PauseIngest(scope *Scope, indexName string, opts *PauseIngestSearchIndexOptions) error 11 | ResumeIngest(scope *Scope, indexName string, opts *ResumeIngestSearchIndexOptions) error 12 | AllowQuerying(scope *Scope, indexName string, opts *AllowQueryingSearchIndexOptions) error 13 | DisallowQuerying(scope *Scope, indexName string, opts *DisallowQueryingSearchIndexOptions) error 14 | FreezePlan(scope *Scope, indexName string, opts *FreezePlanSearchIndexOptions) error 15 | UnfreezePlan(scope *Scope, indexName string, opts *UnfreezePlanSearchIndexOptions) error 16 | } 17 | -------------------------------------------------------------------------------- /searchprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import cbsearch "github.com/couchbase/gocb/v2/search" 4 | 5 | type searchProvider interface { 6 | SearchQuery(indexName string, query cbsearch.Query, opts *SearchOptions) (*SearchResult, error) 7 | Search(scope *Scope, indexName string, request SearchRequest, opts *SearchOptions) (*SearchResult, error) 8 | } 9 | -------------------------------------------------------------------------------- /singlequerytransactionresult.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | // // SingleQueryTransactionResult allows access to the results of a single query transaction. 4 | // type SingleQueryTransactionResult struct { 5 | // wrapped *TransactionQueryResult 6 | // unstagingComplete bool 7 | // } 8 | // 9 | // // TransactionQueryResult returns the result of the query. 10 | // func (qr *SingleQueryTransactionResult) QueryResult() *TransactionQueryResult { 11 | // return qr.wrapped 12 | // } 13 | // 14 | // // UnstagingComplete returns whether all documents were successfully unstaged (committed). 15 | // // 16 | // // This will only return true if the transaction reached the COMMIT point and then went on to reach 17 | // // the COMPLETE point. 18 | // // 19 | // // It will be false for transactions that: 20 | // // - Rolled back 21 | // // - Were read-only 22 | // func (qr *SingleQueryTransactionResult) UnstagingComplete() bool { 23 | // return qr.unstagingComplete 24 | // } 25 | -------------------------------------------------------------------------------- /testdata/analytics_timeout.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "74ed0ec2-8699-417d-b807-599520b70b4c", 3 | "signature": { 4 | "*": "*" 5 | }, 6 | "errors": [{ 7 | "code": 21002, 8 | "msg": "Request timed out and will be cancelled" 9 | }], 10 | "status": "timeout", 11 | "metrics": { 12 | "elapsedTime": "24.187696ms", 13 | "executionTime": "19.366336ms", 14 | "resultCount": 0, 15 | "resultSize": 0, 16 | "processedObjects": 0, 17 | "errorCount": 1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /testdata/beer_sample_analytics_temp_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "fbe9ac66-a7ed-4b09-b1dc-4d3c791d8953", 3 | "clientContextID": "62d29101-0c9f-400d-af2b-9bd44a557a7c", 4 | "errors": [ 5 | { 6 | "code": 23000, 7 | "msg": "Analytics Service is temporarily unavailable" 8 | } 9 | ], 10 | "status": "errors", 11 | "metrics": { 12 | "elapsedTime": "837.425µs", 13 | "executionTime": "732.345µs", 14 | "resultCount": 0, 15 | "resultSize": 0, 16 | "errorCount": 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testdata/beer_sample_brewery_single.json: -------------------------------------------------------------------------------- 1 | { 2 | "city": "Portland", 3 | "code": "", 4 | "country": "United States", 5 | "description": "", 6 | "geo": { 7 | "accuracy": "APPROXIMATE", 8 | "lat": 45.5325, 9 | "lon": -122.686 10 | }, 11 | "name": "Blitz-Weinhard Brewing", 12 | "phone": "", 13 | "state": "Oregon", 14 | "type": "brewery", 15 | "updated": "2010-07-22 20:00:20", 16 | "website": "" 17 | } 18 | -------------------------------------------------------------------------------- /testdata/beer_sample_query_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "fbe9ac66-a7ed-4b09-b1dc-4d3c791d8953", 3 | "clientContextID": "62d29101-0c9f-400d-af2b-9bd44a557a7c", 4 | "errors": [ 5 | { 6 | "code": 3000, 7 | "msg": "syntax error - at *" 8 | } 9 | ], 10 | "status": "fatal", 11 | "metrics": { 12 | "elapsedTime": "837.425µs", 13 | "executionTime": "732.345µs", 14 | "resultCount": 0, 15 | "resultSize": 0, 16 | "errorCount": 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testdata/beer_sample_query_temp_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "fbe9ac66-a7ed-4b09-b1dc-4d3c791d8953", 3 | "clientContextID": "62d29101-0c9f-400d-af2b-9bd44a557a7c", 4 | "errors": [ 5 | { 6 | "code": 4050, 7 | "msg": "temporary error" 8 | } 9 | ], 10 | "status": "errors", 11 | "metrics": { 12 | "elapsedTime": "837.425µs", 13 | "executionTime": "732.345µs", 14 | "resultCount": 0, 15 | "resultSize": 0, 16 | "errorCount": 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testdata/beer_sample_query_timeout.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "59bf7bff-f190-4132-9e5b-4f4564d9b269", 3 | "clientContextID": "9f6b7330-4623-4123-b340-d991594a90af", 4 | "signature": { 5 | "type":"json" 6 | }, 7 | "results": [ 8 | ], 9 | "errors": [ 10 | {"code":1080, 11 | "msg":"Timeout 50ms exceeded" 12 | } 13 | ], 14 | "status": "timeout", 15 | "metrics": { 16 | "elapsedTime": "20.323957ms", 17 | "executionTime": "20.237546ms", 18 | "resultCount": 0, 19 | "resultSize": 0, 20 | "errorCount": 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /testdata/beer_sample_search_dataset.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": { 3 | "total": 5, 4 | "failed": 0, 5 | "successful": 5 6 | }, 7 | "request": { 8 | "query": { 9 | "query": "London" 10 | }, 11 | "size": 5, 12 | "from": 0, 13 | "highlight": null, 14 | "fields": null, 15 | "facets": null, 16 | "explain": false, 17 | "sort": [ 18 | "-_score" 19 | ], 20 | "includeLocations": false 21 | }, 22 | "hits": [ 23 | { 24 | "index": "hotels_384418a18e59eba2_acbbef99", 25 | "id": "landmark_37138", 26 | "score": 1.156383395549805, 27 | "sort": [ 28 | "_score" 29 | ], 30 | "fields": { 31 | "name": "landmark_37138" 32 | } 33 | }, 34 | { 35 | "index": "hotels_384418a18e59eba2_acbbef99", 36 | "id": "landmark_16309", 37 | "score": 1.1315120632881468, 38 | "sort": [ 39 | "_score" 40 | ], 41 | "fields": { 42 | "name": "landmark_16309" 43 | } 44 | }, 45 | { 46 | "index": "hotels_384418a18e59eba2_acbbef99", 47 | "id": "landmark_16073", 48 | "score": 1.1059247262932046, 49 | "sort": [ 50 | "_score" 51 | ], 52 | "fields": { 53 | "name": "landmark_16073" 54 | } 55 | }, 56 | { 57 | "index": "hotels_384418a18e59eba2_acbbef99", 58 | "id": "landmark_16288", 59 | "score": 1.0926796273206518, 60 | "sort": [ 61 | "_score" 62 | ], 63 | "fields": { 64 | "name": "landmark_16288" 65 | } 66 | }, 67 | { 68 | "index": "hotels_384418a18e59eba2_acbbef99", 69 | "id": "landmark_16071", 70 | "score": 1.0798993013885354, 71 | "sort": [ 72 | "_score" 73 | ], 74 | "fields": { 75 | "name": "landmark_16071" 76 | } 77 | } 78 | ], 79 | "total_hits": 809, 80 | "max_score": 1.156383395549805, 81 | "took": 62511375, 82 | "facets": { 83 | "type": { 84 | "name": "", 85 | "field": "country", 86 | "total": 7, 87 | "missing": 0, 88 | "other": 0 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /testdata/beer_sample_single.json: -------------------------------------------------------------------------------- 1 | { 2 | "abv": 7.6, 3 | "brewery_id": "512_brewing_company", 4 | "category": "North American Ale", 5 | "description": "At once cuddly and ferocious, (512) BRUIN combines a smooth, rich maltiness and mahogany color with a solid hop backbone and stealthy 7.6% alcohol. Made with Organic 2 Row and Munich malts, plus Chocolate and Crystal malts, domestic hops, and a touch of molasses, this brew has notes of raisins, dark sugars, and cocoa, and pairs perfectly with food and the crisp fall air.", 6 | "ibu": 0, 7 | "name": "(512) Bruin", 8 | "srm": 0, 9 | "style": "American-Style Brown Ale", 10 | "type": "beer", 11 | "upc": 0, 12 | "updated": "2010-07-22T20:00:20Z", 13 | "profile": { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /testdata/ping_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "id": "0xdeadbeef userstring", 4 | "config_rev": 53, 5 | "sdk": "gocbcore/v7.0.2", 6 | "services": { 7 | "fts": [ 8 | { 9 | "id": "0x1415F11", 10 | "latency_us": 877909, 11 | "remote": "centos7-lx1.home.ingenthron.org:8094", 12 | "local": "127.0.0.1:54669", 13 | "state": "ok" 14 | } 15 | ], 16 | "kv": [ 17 | { 18 | "id": "0x1415F12", 19 | "latency_us": 1182000, 20 | "remote": "centos7-lx1.home.ingenthron.org:11210", 21 | "local": "127.0.0.1:54670", 22 | "state": "ok", 23 | "scope" : "bucketname" 24 | } 25 | ], 26 | "n1ql": [ 27 | { 28 | "id": "0x1415F13", 29 | "latency_us": 6217, 30 | "remote": "centos7-lx1.home.ingenthron.org:8093", 31 | "local": "127.0.0.1:54671", 32 | "state": "ok" 33 | }, 34 | { 35 | "id": "0x1415F14", 36 | "latency_us": 2213, 37 | "remote": "centos7-lx2.home.ingenthron.org:8095", 38 | "local": "127.0.0.1:54682", 39 | "state": "timeout" 40 | } 41 | ], 42 | "cbas": [ 43 | { 44 | "id": "0x1415F15", 45 | "latency_us": 2213, 46 | "remote": "centos7-lx1.home.ingenthron.org:8095", 47 | "local": "127.0.0.1:54675", 48 | "state": "error", 49 | "details": "endpoint returned HTTP code 500!" 50 | } 51 | ], 52 | "view": [ 53 | { 54 | "id": "0x1415F16", 55 | "latency_us": 45585, 56 | "remote": "centos7-lx1.home.ingenthron.org:8092", 57 | "local": "127.0.0.1:54672", 58 | "state": "ok" 59 | } 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /testdata/prepared_beer_sample_query_dataset.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "a2c943b7-2950-432d-823b-4587ecabfe54", 3 | "clientContextID": "c6766223-4fb9-4369-96c1-4e7b28d0cf68", 4 | "signature": "json", 5 | "results": [ 6 | { 7 | "encoded_plan": "H4sIAAAAAAAA/6STUWvbMBSF/4o5eysKdG1CYu0plBQGGTNJ2csIrmJfO1pl2buSQ7Lg/fYhJ6EkHWNsj7Y+H93zyTqAbFbnlKeNURYSEChI+Zbpobaea+MgbwW0zWk3bfQXYqdrC3kvYFVFkMjfx9l6vB4O7iYxDYZ3xWQQr0frQZzlt3GhJuMh3UOgboiVrxnygHevD1jS95ZsRhD4mW20yZks5NdLaNr6Tc36R6Aa1lttqCQXouba+R5/UlySD/NQoVrjpWe1JTNwqmpM+C5hvYUcd6vuvNM/jfJXUMK6UrxfZsqG7r2942hpc1w6v00brr9R5nupB5xW0xfaQ3puqRN4ob1rVBZcX3cyutKh8wjH4zhzJwcQaJ22JSRKp9GJyykfyWcb/HGD34VexySKlTFk8F9eP1rttTLJUQcEmFxrfOqJK9fTtGsC6MgUEHBe8UnR6k0xbV+TulX3lpj34sQ5c9RTV8zSM6kK/Q/jdGn7WxG63UDiBp2Ap12wnyxmyXQxi5az+ezhKbqJHhefP0XPFy6fo/6sotEHdL8CAAD//5NLCoZ1AwAA", 8 | "featureControls": 0, 9 | "indexApiVersion": 3, 10 | "name": "[127.0.0.1:8091]d19cb7b4-289e-42f8-9b5b-9cd09fa874e3", 11 | "operator": { 12 | "#operator": "Sequence", 13 | "~children": [ 14 | { 15 | "#operator": "Authorize", 16 | "privileges": { 17 | "List": [ 18 | { 19 | "Priv": 7, 20 | "Target": "default:travel-sample" 21 | } 22 | ] 23 | }, 24 | "~child": { 25 | "#operator": "Sequence", 26 | "~children": [ 27 | { 28 | "#operator": "Sequence", 29 | "~children": [ 30 | { 31 | "#operator": "PrimaryScan3", 32 | "index": "def_primary", 33 | "index_projection": { 34 | "primary_key": true 35 | }, 36 | "keyspace": "travel-sample", 37 | "limit": "5", 38 | "namespace": "default", 39 | "using": "gsi" 40 | }, 41 | { 42 | "#operator": "Fetch", 43 | "keyspace": "travel-sample", 44 | "namespace": "default" 45 | }, 46 | { 47 | "#operator": "Parallel", 48 | "~child": { 49 | "#operator": "Sequence", 50 | "~children": [ 51 | { 52 | "#operator": "InitialProject", 53 | "result_terms": [ 54 | { 55 | "expr": "self", 56 | "star": true 57 | } 58 | ] 59 | }, 60 | { 61 | "#operator": "FinalProject" 62 | } 63 | ] 64 | } 65 | } 66 | ] 67 | }, 68 | { 69 | "#operator": "Limit", 70 | "expr": "5" 71 | } 72 | ] 73 | } 74 | }, 75 | { 76 | "#operator": "Stream" 77 | } 78 | ] 79 | }, 80 | "signature": { 81 | "*": "*" 82 | }, 83 | "text": "PREPARE SELECT * FROM `travel-sample` limit 5;" 84 | } 85 | ], 86 | "status": "success", 87 | "metrics": { 88 | "elapsedTime": "13.491479ms", 89 | "executionTime": "13.376689ms", 90 | "resultCount": 1, 91 | "resultSize": 1473 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /testdata/projection_doc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Emmy-lou Dickerson", 3 | "age": 26, 4 | "animals": ["cat", "dog", "parrot"], 5 | "attributes": { 6 | "hair": "brown", 7 | "dimensions": { 8 | "height": 67, 9 | "weight": 175 10 | }, 11 | "hobbies": [ 12 | { 13 | "type": "winter sports", 14 | "name": "curling" 15 | }, 16 | { 17 | "type": "summer sports", 18 | "name": "water skiing", 19 | "details": { 20 | "location": { 21 | "lat": 49.282730, 22 | "long": -123.120735 23 | } 24 | } 25 | } 26 | ] 27 | }, 28 | "tracking": { 29 | "locations": [ 30 | [ 31 | { 32 | "lat": 49.282730, 33 | "long": -123.120735 34 | }, 35 | { 36 | "lat": 49.282330, 37 | "long": -123.120345 38 | } 39 | ], 40 | [ 41 | { 42 | "lat": 50.282730, 43 | "long": -121.120735 44 | }, 45 | { 46 | "lat": 29.282330, 47 | "long": -126.120345 48 | } 49 | ], 50 | [ 51 | { 52 | "lat": 49.282130, 53 | "long": -121.120735 54 | }, 55 | { 56 | "lat": 4.282330, 57 | "long": -1.120345 58 | } 59 | ] 60 | ], 61 | "raw": [ 62 | [49.282730, -123.120735], 63 | [50.282730, -121.120735], 64 | [49.282130, -1.120345] 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /testdata/query_index_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "a2c943b7-2950-432d-823b-4587ecabfe54", 3 | "clientContextID": "c6766223-4fb9-4369-96c1-4e7b28d0cf68", 4 | "signature": "*:*", 5 | "results": [ 6 | { 7 | "datastore_id": "http://127.0.0.1:8091", 8 | "id": "c8b609c6d50798e0", 9 | "index_key": [ 10 | "`_type`" 11 | ], 12 | "keyspace_id": "test", 13 | "name": "ih", 14 | "namespace_id": "default", 15 | "partition": "HASH(`_type`)", 16 | "state": "online", 17 | "using": "gsi" 18 | } 19 | ], 20 | "status": "success", 21 | "metrics": { 22 | "elapsedTime": "13.491479ms", 23 | "executionTime": "13.376689ms", 24 | "resultCount": 1, 25 | "resultSize": 1473 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /testdata/search_analyzedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "ok", 3 | "analyzed": [ 4 | { 5 | "couchbase blr": { 6 | "Term": "Y291Y2hiYXNlIGJscg==", 7 | "Locations": [ 8 | { 9 | "Field": "title", 10 | "ArrayPositions": [], 11 | "Start": 0, 12 | "End": 13, 13 | "Position": 1 14 | } 15 | ] 16 | } 17 | }, 18 | { 19 | "he": { 20 | "Term": "aGU=", 21 | "Locations": [ 22 | { 23 | "Field": "name", 24 | "ArrayPositions": [], 25 | "Start": 0, 26 | "End": 5, 27 | "Position": 1 28 | } 29 | ] 30 | }, 31 | "hel": { 32 | "Term": "aGVs", 33 | "Locations": [ 34 | { 35 | "Field": "name", 36 | "ArrayPositions": [], 37 | "Start": 0, 38 | "End": 5, 39 | "Position": 1 40 | } 41 | ] 42 | }, 43 | "hell": { 44 | "Term": "aGVsbA==", 45 | "Locations": [ 46 | { 47 | "Field": "name", 48 | "ArrayPositions": [], 49 | "Start": 0, 50 | "End": 5, 51 | "Position": 1 52 | } 53 | ] 54 | }, 55 | "hello": { 56 | "Term": "aGVsbG8=", 57 | "Locations": [ 58 | { 59 | "Field": "name", 60 | "ArrayPositions": [], 61 | "Start": 0, 62 | "End": 5, 63 | "Position": 1 64 | } 65 | ] 66 | }, 67 | "wo": { 68 | "Term": "d28=", 69 | "Locations": [ 70 | { 71 | "Field": "name", 72 | "ArrayPositions": [], 73 | "Start": 6, 74 | "End": 11, 75 | "Position": 2 76 | } 77 | ] 78 | }, 79 | "wor": { 80 | "Term": "d29y", 81 | "Locations": [ 82 | { 83 | "Field": "name", 84 | "ArrayPositions": [], 85 | "Start": 6, 86 | "End": 11, 87 | "Position": 2 88 | } 89 | ] 90 | }, 91 | "worl": { 92 | "Term": "d29ybA==", 93 | "Locations": [ 94 | { 95 | "Field": "name", 96 | "ArrayPositions": [], 97 | "Start": 6, 98 | "End": 11, 99 | "Position": 2 100 | } 101 | ] 102 | }, 103 | "world": { 104 | "Term": "d29ybGQ=", 105 | "Locations": [ 106 | { 107 | "Field": "name", 108 | "ArrayPositions": [], 109 | "Start": 6, 110 | "End": 11, 111 | "Position": 2 112 | } 113 | ] 114 | } 115 | }, 116 | null 117 | ] 118 | } 119 | -------------------------------------------------------------------------------- /testdata/transaction_begin_work_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "e36e0202-7f4f-4083-9b73-993459353544", 3 | "clientContextID": "62d29101-0c9f-400d-af2b-9bd44a557a7c", 4 | "signature": { 5 | "*": "*" 6 | }, 7 | "results": [ 8 | ], 9 | "status": "success", 10 | "metrics": { 11 | "elapsedTime": "9.64915ms", 12 | "executionTime": "9.58744ms", 13 | "resultCount": 0, 14 | "resultSize": 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /testdata/transaction_gocbcore_cause_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestID": "9605e383-3da3-440e-a4e1-47d4b673401f", 3 | "clientContextID": "d4c97655-2e89-41ed-a46c-9f4e2a1eae5a", 4 | "signature": { 5 | "*": "*" 6 | }, 7 | "results": [], 8 | "errors": [ 9 | { 10 | "code": 19000, 11 | "msg": "Some pretend error", 12 | "cause": { 13 | "rollback": true, 14 | "retry": false, 15 | "raise": "expired" 16 | } 17 | } 18 | ], 19 | "status": "errors", 20 | "metrics": { 21 | "elapsedTime": "1.167435ms", 22 | "executionTime": "1.117429ms", 23 | "resultCount": 0, 24 | "resultSize": 0, 25 | "errorCount": 1 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /testdata/travel-sample-index-stored.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "travel-sample-index-stored", 3 | "type": "fulltext-index", 4 | "params": { 5 | "doc_config": { 6 | "mode": "type_field", 7 | "type_field": "type" 8 | }, 9 | "mapping": { 10 | "analysis": { 11 | "analyzers": { 12 | "letterAnalyzer": { 13 | "tokenizer": "letter", 14 | "type": "custom" 15 | } 16 | } 17 | }, 18 | "default_analyzer": "standard", 19 | "default_datetime_parser": "dateTimeOptional", 20 | "default_field": "_all", 21 | "default_mapping": { 22 | "dynamic": true, 23 | "enabled": true 24 | }, 25 | "default_type": "_default", 26 | "index_dynamic": true, 27 | "store_dynamic": true, 28 | "type_field": "type" 29 | }, 30 | "store": { 31 | "kvStoreName": "mossStore" 32 | } 33 | }, 34 | "sourceType": "couchbase", 35 | "sourceName": "travel-sample", 36 | "sourceUUID": "", 37 | "sourceParams": {}, 38 | "planParams": { 39 | "maxPartitionsPerPIndex": 171, 40 | "numReplicas": 0 41 | }, 42 | "uuid": "" 43 | } 44 | -------------------------------------------------------------------------------- /testdata/uisearchindex.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "type": "fulltext-index", 4 | "params": { 5 | "mapping": { 6 | "default_mapping": { 7 | "enabled": true, 8 | "dynamic": true 9 | }, 10 | "default_type": "_default", 11 | "default_analyzer": "standard", 12 | "default_datetime_parser": "dateTimeOptional", 13 | "default_field": "_all", 14 | "analysis": { 15 | "char_filters": { 16 | "a": { 17 | "regexp": "", 18 | "replace": "", 19 | "type": "regexp" 20 | } 21 | } 22 | }, 23 | "store_dynamic": false, 24 | "index_dynamic": true, 25 | "docvalues_dynamic": false 26 | }, 27 | "store": { 28 | "indexType": "scorch", 29 | "kvStoreName": "" 30 | }, 31 | "doc_config": { 32 | "mode": "type_field", 33 | "type_field": "type", 34 | "docid_prefix_delim": "", 35 | "docid_regexp": "" 36 | } 37 | }, 38 | "sourceType": "couchbase", 39 | "sourceName": "default", 40 | "sourceUUID": "9575a0cc8ecfa8bad6168b8d0a4fd411", 41 | "sourceParams": {}, 42 | "planParams": { 43 | "maxPartitionsPerPIndex": 512, 44 | "numReplicas": 2, 45 | "indexPartitions": 2 46 | }, 47 | "uuid": "" 48 | } 49 | -------------------------------------------------------------------------------- /testdata/views_response_70.json: -------------------------------------------------------------------------------- 1 | { 2 | "rows": [ 3 | { 4 | "doc": { 5 | "meta": { 6 | "id": "_design/aaa", 7 | "rev": "1-5d93a19b" 8 | }, 9 | "json": { 10 | "views": { 11 | "aaa": { 12 | "map": "function (doc, meta) {\n emit(meta.id, null);\n}" 13 | } 14 | } 15 | } 16 | }, 17 | "controllers": { 18 | "compact": "/pools/default/buckets/default/ddocs/_design%2Faaa/controller/compactView", 19 | "setUpdateMinChanges": "/pools/default/buckets/default/ddocs/_design%2Faaa/controller/setUpdateMinChanges" 20 | } 21 | }, 22 | { 23 | "doc": { 24 | "meta": { 25 | "id": "_design/dev_aaa", 26 | "rev": "1-53f6e7e2" 27 | }, 28 | "json": { 29 | "views": { 30 | "aaa": { 31 | "map": "function (doc, meta) {\n emit(meta.id, null);\n}" 32 | } 33 | } 34 | } 35 | }, 36 | "controllers": { 37 | "compact": "/pools/default/buckets/default/ddocs/_design%2Fdev_aaa/controller/compactView", 38 | "setUpdateMinChanges": "/pools/default/buckets/default/ddocs/_design%2Fdev_aaa/controller/setUpdateMinChanges" 39 | } 40 | }, 41 | { 42 | "doc": { 43 | "meta": { 44 | "id": "_design/dev_test", 45 | "rev": "1-19f7cc03" 46 | }, 47 | "json": { 48 | "views": { 49 | "test": { 50 | "map": "function (doc, meta) {\n emit(meta.id, null);\n}" 51 | } 52 | } 53 | } 54 | }, 55 | "controllers": { 56 | "compact": "/pools/default/buckets/default/ddocs/_design%2Fdev_test/controller/compactView", 57 | "setUpdateMinChanges": "/pools/default/buckets/default/ddocs/_design%2Fdev_test/controller/setUpdateMinChanges" 58 | } 59 | }, 60 | { 61 | "doc": { 62 | "meta": { 63 | "id": "_design/dev_test12", 64 | "rev": "1-3df87cb6" 65 | }, 66 | "json": { 67 | "views": { 68 | "test12": { 69 | "map": "function (doc, meta) {\n emit(meta.id, null);\n}" 70 | } 71 | } 72 | } 73 | }, 74 | "controllers": { 75 | "compact": "/pools/default/buckets/default/ddocs/_design%2Fdev_test12/controller/compactView", 76 | "setUpdateMinChanges": "/pools/default/buckets/default/ddocs/_design%2Fdev_test12/controller/setUpdateMinChanges" 77 | } 78 | } 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /token_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | gocbcore "github.com/couchbase/gocbcore/v10" 8 | ) 9 | 10 | func (suite *UnitTestSuite) TestMutationState_Add() { 11 | fakeBucket := &Bucket{} 12 | fakeBucket.bucketName = "frank" 13 | 14 | fakeToken1 := MutationToken{ 15 | token: gocbcore.MutationToken{ 16 | VbID: 1, 17 | VbUUID: gocbcore.VbUUID(9), 18 | SeqNo: gocbcore.SeqNo(12), 19 | }, 20 | bucketName: fakeBucket.Name(), 21 | } 22 | fakeToken2 := MutationToken{ 23 | token: gocbcore.MutationToken{ 24 | VbID: 2, 25 | VbUUID: gocbcore.VbUUID(1), 26 | SeqNo: gocbcore.SeqNo(22), 27 | }, 28 | bucketName: fakeBucket.Name(), 29 | } 30 | fakeToken3 := MutationToken{ 31 | token: gocbcore.MutationToken{ 32 | VbID: 2, 33 | VbUUID: gocbcore.VbUUID(4), 34 | SeqNo: gocbcore.SeqNo(99), 35 | }, 36 | bucketName: fakeBucket.Name(), 37 | } 38 | 39 | state := NewMutationState(fakeToken1, fakeToken2) 40 | state.Add(fakeToken3) 41 | 42 | bytes, err := json.Marshal(&state) 43 | if err != nil { 44 | suite.T().Fatalf("Failed to marshal %v", err) 45 | } 46 | 47 | if strings.Compare(string(bytes), "{\"frank\":{\"1\":[12,\"9\"],\"2\":[99,\"4\"]}}") != 0 { 48 | suite.T().Fatalf("Failed to generate correct JSON output %s", bytes) 49 | } 50 | 51 | // So as to avoid testing on private properties we'll check if unmarshal works by marshaling the result. 52 | var afterState MutationState 53 | err = json.Unmarshal(bytes, &afterState) 54 | if err != nil { 55 | suite.T().Fatalf("Failed to unmarshal %v", err) 56 | } 57 | 58 | bytes, err = json.Marshal(&state) 59 | if err != nil { 60 | suite.T().Fatalf("Failed to marshal %v", err) 61 | } 62 | 63 | if strings.Compare(string(bytes), "{\"frank\":{\"1\":[12,\"9\"],\"2\":[99,\"4\"]}}") != 0 { 64 | suite.T().Fatalf("Failed to generate correct JSON output %s", bytes) 65 | } 66 | } 67 | 68 | func (suite *UnitTestSuite) TestMutationState_toSeachMutationState() { 69 | fakeBucket := &Bucket{} 70 | fakeBucket.bucketName = "frank" 71 | 72 | fakeToken1 := MutationToken{ 73 | token: gocbcore.MutationToken{ 74 | VbID: 1, 75 | VbUUID: gocbcore.VbUUID(9), 76 | SeqNo: gocbcore.SeqNo(12), 77 | }, 78 | bucketName: fakeBucket.Name(), 79 | } 80 | fakeToken2 := MutationToken{ 81 | token: gocbcore.MutationToken{ 82 | VbID: 2, 83 | VbUUID: gocbcore.VbUUID(1), 84 | SeqNo: gocbcore.SeqNo(22), 85 | }, 86 | bucketName: fakeBucket.Name(), 87 | } 88 | 89 | state := NewMutationState(fakeToken1, fakeToken2) 90 | 91 | searchToken := state.toSearchMutationState("frankindex") 92 | 93 | // What we actually care about is the format once marshaled. 94 | bytes, err := json.Marshal(&searchToken) 95 | if err != nil { 96 | suite.T().Fatalf("Failed to marshal %v", err) 97 | } 98 | 99 | if strings.Compare(string(bytes), "{\"frankindex\":{\"1/9\":12,\"2/1\":22}}") != 0 { 100 | suite.T().Fatalf("Failed to generate correct JSON output %s", bytes) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /transaction_getresult.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | 7 | "github.com/couchbase/gocbcore/v10" 8 | ) 9 | 10 | // TransactionGetResult represents the result of a Get operation which was performed. 11 | type TransactionGetResult struct { 12 | collection *Collection 13 | docID string 14 | 15 | transcoder Transcoder 16 | flags uint32 17 | 18 | txnMeta json.RawMessage 19 | 20 | coreRes *gocbcore.TransactionGetResult 21 | } 22 | 23 | // Content provides access to the documents contents. 24 | func (d *TransactionGetResult) Content(valuePtr interface{}) error { 25 | return d.transcoder.Decode(d.coreRes.Value, d.flags, valuePtr) 26 | } 27 | 28 | func fromScas(scas string) (gocbcore.Cas, error) { 29 | i, err := strconv.ParseUint(scas, 10, 64) 30 | if err != nil { 31 | return 0, err 32 | } 33 | 34 | return gocbcore.Cas(i), nil 35 | } 36 | 37 | func toScas(cas gocbcore.Cas) string { 38 | return strconv.FormatUint(uint64(cas), 10) 39 | } 40 | -------------------------------------------------------------------------------- /transaction_logger.go: -------------------------------------------------------------------------------- 1 | //nolint:unused 2 | package gocb 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | "sync" 8 | "time" 9 | 10 | "github.com/couchbase/gocbcore/v10" 11 | ) 12 | 13 | type loggableDocKey struct { 14 | bucket string 15 | scope string 16 | collection string 17 | id string 18 | } 19 | 20 | func newLoggableDocKey(bucket, scope, collection string, id string) loggableDocKey { 21 | return loggableDocKey{ 22 | bucket: bucket, 23 | scope: scope, 24 | collection: collection, 25 | id: id, 26 | } 27 | } 28 | 29 | func (rdi loggableDocKey) String() string { 30 | scope := rdi.scope 31 | if scope == "" { 32 | scope = "_default" 33 | } 34 | collection := rdi.collection 35 | if collection == "" { 36 | collection = "_default" 37 | } 38 | return redactUserDataString(rdi.bucket + "." + scope + "." + collection + "." + rdi.id) 39 | } 40 | 41 | // TransactionLogger is the logger used for logging in transactions. 42 | type TransactionLogger interface { 43 | Logs() []TransactionLogItem 44 | } 45 | 46 | // TransactionLogItem represents an entry in the transaction in memory logging. 47 | type TransactionLogItem struct { 48 | Level LogLevel 49 | 50 | args []interface{} 51 | txnID string 52 | attemptID string 53 | timestamp time.Time 54 | fmt string 55 | } 56 | 57 | func (item TransactionLogItem) String() string { 58 | return fmt.Sprintf("%s %s/%s %s", item.timestamp.AppendFormat([]byte{}, "15:04:05.000"), item.txnID, item.attemptID, fmt.Sprintf(item.fmt, item.args...)) 59 | } 60 | 61 | // transactionLogger log to memory, also logging WARN and ERROR logs to the SDK logger. 62 | type transactionLogger struct { 63 | lock sync.Mutex 64 | items []TransactionLogItem 65 | logDirectlyBelowLevel gocbcore.LogLevel 66 | txnID string 67 | } 68 | 69 | func newTransactionLogger() *transactionLogger { 70 | return &transactionLogger{ 71 | logDirectlyBelowLevel: gocbcore.LogInfo, 72 | items: make([]TransactionLogItem, 0, 256), 73 | } 74 | } 75 | 76 | func (tl *transactionLogger) setTxnID(txnID string) { 77 | tl.txnID = txnID[:5] 78 | } 79 | 80 | func (tl *transactionLogger) Logs() []TransactionLogItem { 81 | tl.lock.Lock() 82 | logs := make([]TransactionLogItem, len(tl.items)) 83 | copy(logs, tl.items) 84 | tl.lock.Unlock() 85 | 86 | return logs 87 | } 88 | 89 | func (tl *transactionLogger) Log(level gocbcore.LogLevel, offset int, txnID, attemptID, fmt string, args ...interface{}) error { 90 | item := TransactionLogItem{ 91 | Level: LogLevel(level), 92 | args: args, 93 | txnID: txnID, 94 | attemptID: attemptID, 95 | timestamp: time.Now(), 96 | fmt: fmt, 97 | } 98 | tl.lock.Lock() 99 | tl.items = append(tl.items, item) 100 | tl.lock.Unlock() 101 | 102 | if level <= gocbcore.LogWarn { 103 | logExf(LogLevel(level), offset, txnID+"/"+attemptID+" "+fmt, args...) 104 | } 105 | 106 | return nil 107 | } 108 | 109 | func (tl *transactionLogger) logExf(attemptID string, level gocbcore.LogLevel, fmt string, args ...interface{}) { 110 | if attemptID != "" { 111 | attemptID = attemptID[:5] 112 | } 113 | 114 | err := tl.Log(level, 1, tl.txnID, attemptID, fmt, args...) 115 | if err != nil { 116 | log.Printf("Transaction logger error occurred (%s)\n", err) 117 | } 118 | } 119 | 120 | func (tl *transactionLogger) logDebugf(attemptID, format string, v ...interface{}) { 121 | tl.logExf(attemptID, gocbcore.LogDebug, format, v...) 122 | } 123 | 124 | func (tl *transactionLogger) logSchedf(attemptID, format string, v ...interface{}) { 125 | tl.logExf(attemptID, gocbcore.LogSched, format, v...) 126 | } 127 | 128 | func (tl *transactionLogger) logWarnf(attemptID, format string, v ...interface{}) { 129 | tl.logExf(attemptID, gocbcore.LogWarn, format, v...) 130 | } 131 | 132 | func (tl *transactionLogger) logErrorf(attemptID, format string, v ...interface{}) { 133 | tl.logExf(attemptID, gocbcore.LogError, format, v...) 134 | } 135 | 136 | func (tl *transactionLogger) logInfof(attemptID, format string, v ...interface{}) { 137 | tl.logExf(attemptID, gocbcore.LogInfo, format, v...) 138 | } 139 | -------------------------------------------------------------------------------- /transaction_queryresult.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // TransactionQueryResult allows access to the results of a query. 8 | type TransactionQueryResult struct { 9 | results []json.RawMessage 10 | idx int 11 | rowBytes json.RawMessage 12 | 13 | metadata *QueryMetaData 14 | endpoint string 15 | } 16 | 17 | func newTransactionQueryResult(results []json.RawMessage, meta *QueryMetaData, endpoint string) *TransactionQueryResult { 18 | return &TransactionQueryResult{ 19 | results: results, 20 | metadata: meta, 21 | endpoint: endpoint, 22 | } 23 | } 24 | 25 | // Next assigns the next result from the results into the value pointer, returning whether the read was successful. 26 | func (r *TransactionQueryResult) Next() bool { 27 | if r.idx >= len(r.results) { 28 | return false 29 | } 30 | 31 | r.rowBytes = r.results[r.idx] 32 | r.idx++ 33 | 34 | return true 35 | } 36 | 37 | // Row returns the contents of the current row 38 | func (r *TransactionQueryResult) Row(valuePtr interface{}) error { 39 | if r.rowBytes == nil { 40 | return ErrNoResult 41 | } 42 | 43 | if bytesPtr, ok := valuePtr.(*json.RawMessage); ok { 44 | *bytesPtr = r.rowBytes 45 | return nil 46 | } 47 | 48 | return json.Unmarshal(r.rowBytes, valuePtr) 49 | } 50 | 51 | // One assigns the first value from the results into the value pointer. 52 | func (r *TransactionQueryResult) One(valuePtr interface{}) error { 53 | // Prime the row 54 | if !r.Next() { 55 | return ErrNoResult 56 | } 57 | 58 | err := r.Row(valuePtr) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // MetaData returns any meta-data that was available from this query. Note that 67 | // the meta-data will only be available once the object has been closed (either 68 | // implicitly or explicitly). 69 | func (r *TransactionQueryResult) MetaData() (*QueryMetaData, error) { 70 | return r.metadata, nil 71 | } 72 | -------------------------------------------------------------------------------- /transaction_result.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "github.com/couchbase/gocbcore/v10" 5 | ) 6 | 7 | // TransactionAttemptState represents the current state of a transaction attempt. 8 | // Internal: This should never be used and is not supported. 9 | type TransactionAttemptState int 10 | 11 | const ( 12 | // TransactionAttemptStateNothingWritten indicates that nothing has been written in this attempt. 13 | // Internal: This should never be used and is not supported. 14 | TransactionAttemptStateNothingWritten = TransactionAttemptState(gocbcore.TransactionAttemptStateNothingWritten) 15 | 16 | // TransactionAttemptStatePending indicates that this attempt is in pending state. 17 | // Internal: This should never be used and is not supported. 18 | TransactionAttemptStatePending = TransactionAttemptState(gocbcore.TransactionAttemptStatePending) 19 | 20 | // TransactionAttemptStateCommitting indicates that this attempt is in committing state. 21 | // Internal: This should never be used and is not supported. 22 | TransactionAttemptStateCommitting = TransactionAttemptState(gocbcore.TransactionAttemptStateCommitting) 23 | 24 | // TransactionAttemptStateCommitted indicates that this attempt is in committed state. 25 | // Internal: This should never be used and is not supported. 26 | TransactionAttemptStateCommitted = TransactionAttemptState(gocbcore.TransactionAttemptStateCommitted) 27 | 28 | // TransactionAttemptStateCompleted indicates that this attempt is in completed state. 29 | // Internal: This should never be used and is not supported. 30 | TransactionAttemptStateCompleted = TransactionAttemptState(gocbcore.TransactionAttemptStateCompleted) 31 | 32 | // TransactionAttemptStateAborted indicates that this attempt is in aborted state. 33 | // Internal: This should never be used and is not supported. 34 | TransactionAttemptStateAborted = TransactionAttemptState(gocbcore.TransactionAttemptStateAborted) 35 | 36 | // TransactionAttemptStateRolledBack indicates that this attempt is in rolled back state. 37 | // Internal: This should never be used and is not supported. 38 | TransactionAttemptStateRolledBack = TransactionAttemptState(gocbcore.TransactionAttemptStateRolledBack) 39 | ) 40 | 41 | // TransactionResult represents the result of a transaction which was executed. 42 | type TransactionResult struct { 43 | // TransactionID represents the UUID assigned to this transaction 44 | TransactionID string 45 | 46 | // UnstagingComplete indicates whether the transaction was succesfully 47 | // unstaged, or if a later cleanup job will be responsible. 48 | UnstagingComplete bool 49 | 50 | // Logs returns the set of logs that were created during this transaction. 51 | // UNCOMMITTED: This API may change in the future. 52 | Logs []TransactionLogItem 53 | } 54 | -------------------------------------------------------------------------------- /transactions_compatibility.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "github.com/couchbase/gocbcore/v10" 5 | ) 6 | 7 | // TransactionsProtocolVersion returns the protocol version that this library supports. 8 | func TransactionsProtocolVersion() string { 9 | return gocbcore.TransactionsProtocolVersion() 10 | } 11 | 12 | // TransactionsProtocolExtensions returns a list strings representing the various features 13 | // that this specific version of the library supports within its protocol version. 14 | func TransactionsProtocolExtensions() []string { 15 | return gocbcore.TransactionsProtocolExtensions() 16 | } 17 | -------------------------------------------------------------------------------- /transactions_constants.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/couchbase/gocbcore/v10" 4 | 5 | // TransactionErrorReason is the reason why a transaction should be failed. 6 | // Internal: This should never be used and is not supported. 7 | type TransactionErrorReason uint8 8 | 9 | const ( 10 | // TransactionErrorReasonSuccess indicates the transaction succeeded and did not fail. 11 | TransactionErrorReasonSuccess TransactionErrorReason = TransactionErrorReason(gocbcore.TransactionErrorReasonSuccess) 12 | 13 | // TransactionErrorReasonTransactionFailed indicates the transaction should be failed because it failed. 14 | TransactionErrorReasonTransactionFailed = TransactionErrorReason(gocbcore.TransactionErrorReasonTransactionFailed) 15 | 16 | // TransactionErrorReasonTransactionExpired indicates the transaction should be failed because it expired. 17 | TransactionErrorReasonTransactionExpired = TransactionErrorReason(gocbcore.TransactionErrorReasonTransactionExpired) 18 | 19 | // TransactionErrorReasonTransactionCommitAmbiguous indicates the transaction should be failed and the commit was ambiguous. 20 | TransactionErrorReasonTransactionCommitAmbiguous = TransactionErrorReason(gocbcore.TransactionErrorReasonTransactionCommitAmbiguous) 21 | 22 | // TransactionErrorReasonTransactionFailedPostCommit indicates the transaction should be failed because it failed post commit. 23 | TransactionErrorReasonTransactionFailedPostCommit = TransactionErrorReason(gocbcore.TransactionErrorReasonTransactionFailedPostCommit) 24 | ) 25 | -------------------------------------------------------------------------------- /transactionsprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/couchbase/gocbcore/v10" 4 | 5 | type transactionsProvider interface { 6 | Run(logicFn AttemptFunc, perConfig *TransactionOptions, singleQueryMode bool) (*TransactionResult, error) 7 | 8 | Internal() transactionsInternal 9 | } 10 | 11 | type transactionsInternal interface { 12 | ForceCleanupQueue() []TransactionCleanupAttempt 13 | CleanupQueueLength() int32 14 | ClientCleanupEnabled() bool 15 | CleanupLocations() []gocbcore.TransactionLostATRLocation 16 | } 17 | -------------------------------------------------------------------------------- /transactionsprovider_ps.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import "github.com/couchbase/gocbcore/v10" 4 | 5 | type transactionsProviderPs struct{} 6 | 7 | type transactionsInternalPs struct{} 8 | 9 | func (t *transactionsProviderPs) Run(logicFn AttemptFunc, perConfig *TransactionOptions, singleQueryMode bool) (*TransactionResult, error) { 10 | return nil, wrapError(ErrFeatureNotAvailable, "transactions are not currently supported against the couchbase2 protocol") 11 | } 12 | 13 | func (t *transactionsProviderPs) Internal() transactionsInternal { 14 | return &transactionsInternalPs{} 15 | } 16 | 17 | func (t *transactionsInternalPs) ForceCleanupQueue() []TransactionCleanupAttempt { 18 | return nil 19 | } 20 | 21 | func (t *transactionsInternalPs) CleanupQueueLength() int32 { 22 | return 0 23 | } 24 | 25 | func (t *transactionsInternalPs) ClientCleanupEnabled() bool { 26 | return false 27 | } 28 | 29 | func (t *transactionsInternalPs) CleanupLocations() []gocbcore.TransactionLostATRLocation { 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /usermanagerprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type userManagerProvider interface { 4 | GetAllUsers(opts *GetAllUsersOptions) ([]UserAndMetadata, error) 5 | GetUser(name string, opts *GetUserOptions) (*UserAndMetadata, error) 6 | UpsertUser(user User, opts *UpsertUserOptions) error 7 | DropUser(name string, opts *DropUserOptions) error 8 | GetRoles(opts *GetRolesOptions) ([]RoleAndDescription, error) 9 | GetGroup(groupName string, opts *GetGroupOptions) (*Group, error) 10 | GetAllGroups(opts *GetAllGroupsOptions) ([]Group, error) 11 | UpsertGroup(group Group, opts *UpsertGroupOptions) error 12 | DropGroup(groupName string, opts *DropGroupOptions) error 13 | ChangePassword(newPassword string, opts *ChangePasswordOptions) error 14 | } 15 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "os" 7 | "testing" 8 | 9 | "github.com/google/uuid" 10 | ) 11 | 12 | type testBeerDocument struct { 13 | ABV float32 `json:"abv,omitempty"` 14 | BreweryID string `json:"brewery_id,omitempty"` 15 | Category string `json:"category,omitempty"` 16 | Description string `json:"description,omitempty"` 17 | IBU int `json:"IBU,omitempty"` 18 | Name string `json:"name,omitempty"` 19 | SRM int `json:"srm,omitempty"` 20 | Style string `json:"style,omitempty"` 21 | Type string `json:"type,omitempty"` 22 | UPC int `json:"upc,omitempty"` 23 | Updated string `json:"updated,omitempty"` 24 | } 25 | 26 | type testBreweryGeo struct { 27 | Accuracy string `json:"accuracy,omitempty"` 28 | Lat float32 `json:"lat,omitempty"` 29 | Lon float32 `json:"lon,omitempty"` 30 | } 31 | 32 | type testBreweryDocument struct { 33 | City string `json:"city,omitempty"` 34 | Code string `json:"code,omitempty"` 35 | Country string `json:"country,omitempty"` 36 | Description string `json:"description,omitempty"` 37 | Geo testBreweryGeo `json:"geo,omitempty"` 38 | Name string `json:"name,omitempty"` 39 | Phone string `json:"phone,omitempty"` 40 | State string `json:"state,omitempty"` 41 | Type string `json:"type,omitempty"` 42 | Updated string `json:"updated,omitempty"` 43 | Website string `json:"website,omitempty"` 44 | 45 | Service string `json:"service,omitempty"` 46 | } 47 | 48 | type testMetadata struct { 49 | } 50 | 51 | func loadRawTestDataset(dataset string) ([]byte, error) { 52 | return os.ReadFile("testdata/" + dataset + ".json") 53 | } 54 | 55 | func loadJSONTestDataset(dataset string, valuePtr interface{}) error { 56 | bytes, err := loadRawTestDataset(dataset) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | err = json.Unmarshal(bytes, &valuePtr) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func marshal(t *testing.T, value interface{}) []byte { 70 | b, err := json.Marshal(value) 71 | if err != nil { 72 | t.Fatalf("Could not marshal value: %v", err) 73 | } 74 | 75 | return b 76 | } 77 | 78 | // If using with an empty prefix ensure you prepend at least a single letter so the doc ID does not begin with a number 79 | func generateDocId(prefix string) string { 80 | return prefix + uuid.NewString()[:6] 81 | } 82 | 83 | type testReadCloser struct { 84 | io.Reader 85 | closeErr error 86 | } 87 | 88 | func (trc *testReadCloser) Close() error { 89 | return trc.closeErr 90 | } 91 | -------------------------------------------------------------------------------- /vector/query.go: -------------------------------------------------------------------------------- 1 | package vector 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | // Query specifies a vector Query. 9 | type Query struct { 10 | field string 11 | vector []float32 12 | base64Vector string 13 | 14 | numCandidates *uint32 15 | boost *float32 16 | } 17 | 18 | // NewQuery constructs a new vector Query. 19 | func NewQuery(vectorFieldName string, vector []float32) *Query { 20 | return &Query{ 21 | field: vectorFieldName, 22 | vector: vector, 23 | } 24 | } 25 | 26 | // NewBase64Query constructs a new vector Query using 27 | // a Base64-encoded sequence of little-endian IEEE 754 floats. 28 | func NewBase64Query(vectorFieldName string, base64Vector string) *Query { 29 | return &Query{ 30 | field: vectorFieldName, 31 | base64Vector: base64Vector, 32 | } 33 | } 34 | 35 | // NumCandidates controls how many results are returned for this query. 36 | func (q *Query) NumCandidates(num uint32) *Query { 37 | q.numCandidates = &num 38 | return q 39 | } 40 | 41 | // Boost specifies the boost for this query. 42 | func (q *Query) Boost(boost float32) *Query { 43 | q.boost = &boost 44 | return q 45 | } 46 | 47 | // InternalQuery is used for internal functionality. 48 | // Internal: This should never be used and is not supported. 49 | type InternalQuery struct { 50 | Field string 51 | Vector []float32 52 | Base64Vector string 53 | 54 | NumCandidates *uint32 55 | Boost *float32 56 | } 57 | 58 | // Internal is used for internal functionality. 59 | // Internal: This should never be used and is not supported. 60 | func (q *Query) Internal() InternalQuery { 61 | return InternalQuery{ 62 | Field: q.field, 63 | Vector: q.vector, 64 | Base64Vector: q.base64Vector, 65 | NumCandidates: q.numCandidates, 66 | Boost: q.boost, 67 | } 68 | } 69 | 70 | // Validate verifies that settings in the query are valid. 71 | func (q InternalQuery) Validate() error { 72 | if len(q.Field) == 0 { 73 | return errors.New("vectorFieldName cannot be empty") 74 | } 75 | if len(q.Vector) == 0 && len(q.Base64Vector) == 0 { 76 | return errors.New("one of vector or base64vector must be specified") 77 | } 78 | if q.NumCandidates != nil && *q.NumCandidates == 0 { 79 | return errors.New("when set numCandidates must have a value >= 1") 80 | } 81 | 82 | return nil 83 | } 84 | 85 | // MarshalJSON marshal's this query to JSON for the search REST API. 86 | func (q InternalQuery) MarshalJSON() ([]byte, error) { 87 | outStruct := &struct { 88 | Field string `json:"field"` 89 | Vector []float32 `json:"vector,omitempty"` 90 | Base64Vector string `json:"vector_base64,omitempty"` 91 | NumCandidates *uint32 `json:"k,omitempty"` 92 | Boost *float32 `json:"boost,omitempty"` 93 | }{ 94 | Field: q.Field, 95 | Vector: q.Vector, 96 | Base64Vector: q.Base64Vector, 97 | NumCandidates: q.NumCandidates, 98 | Boost: q.Boost, 99 | } 100 | 101 | return json.Marshal(outStruct) 102 | } 103 | -------------------------------------------------------------------------------- /vector/search.go: -------------------------------------------------------------------------------- 1 | package vector 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // VectorQueryCombination specifies how elements in the array are combined. 8 | type VectorQueryCombination string 9 | 10 | const ( 11 | VectorQueryCombinationNotSet VectorQueryCombination = "" 12 | VectorQueryCombinationAnd VectorQueryCombination = "and" 13 | VectorQueryCombinationOr VectorQueryCombination = "or" 14 | ) 15 | 16 | // SearchOptions specifies the options available to vector Search. 17 | type SearchOptions struct { 18 | VectorQueryCombination VectorQueryCombination 19 | } 20 | 21 | // Search specifies a vector Search. 22 | type Search struct { 23 | queries []*Query 24 | 25 | vectorQueryCombination VectorQueryCombination 26 | } 27 | 28 | // NewSearch constructs a new vector Search. 29 | func NewSearch(queries []*Query, opts *SearchOptions) *Search { 30 | if opts == nil { 31 | opts = &SearchOptions{} 32 | } 33 | 34 | return &Search{ 35 | queries: queries, 36 | vectorQueryCombination: opts.VectorQueryCombination, 37 | } 38 | } 39 | 40 | // InternalSearch is used for internal functionality. 41 | // Internal: This should never be used and is not supported. 42 | type InternalSearch struct { 43 | Queries []InternalQuery 44 | 45 | VectorQueryCombination VectorQueryCombination 46 | } 47 | 48 | // Internal is used for internal functionality. 49 | // Internal: This should never be used and is not supported. 50 | func (s *Search) Internal() InternalSearch { 51 | queries := make([]InternalQuery, len(s.queries)) 52 | for i, query := range s.queries { 53 | queries[i] = query.Internal() 54 | } 55 | return InternalSearch{ 56 | Queries: queries, 57 | VectorQueryCombination: s.vectorQueryCombination, 58 | } 59 | } 60 | 61 | // Validate verifies that settings in the search (including all queries) are valid. 62 | func (s InternalSearch) Validate() error { 63 | if len(s.Queries) == 0 { 64 | return errors.New("at least one vector query must be specified") 65 | } 66 | for _, query := range s.Queries { 67 | if err := query.Validate(); err != nil { 68 | return err 69 | } 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | // Version returns a string representation of the current SDK version. 4 | func Version() string { 5 | return goCbVersionStr 6 | } 7 | 8 | // Identifier returns a string representation of the current SDK identifier. 9 | func Identifier() string { 10 | return "gocb/" + goCbVersionStr 11 | } 12 | -------------------------------------------------------------------------------- /viewindexprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type viewIndexProvider interface { 4 | GetDesignDocument(name string, namespace DesignDocumentNamespace, opts *GetDesignDocumentOptions) (*DesignDocument, error) 5 | GetAllDesignDocuments(namespace DesignDocumentNamespace, opts *GetAllDesignDocumentsOptions) ([]DesignDocument, error) 6 | UpsertDesignDocument(ddoc DesignDocument, namespace DesignDocumentNamespace, opts *UpsertDesignDocumentOptions) error 7 | DropDesignDocument(name string, namespace DesignDocumentNamespace, opts *DropDesignDocumentOptions) error 8 | PublishDesignDocument(name string, opts *PublishDesignDocumentOptions) error 9 | } 10 | -------------------------------------------------------------------------------- /viewprovider.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | type viewProvider interface { 4 | ViewQuery(designDoc string, viewName string, opts *ViewOptions) (*ViewResult, error) 5 | } 6 | 7 | type viewRowReader interface { 8 | NextRow() []byte 9 | Err() error 10 | MetaData() ([]byte, error) 11 | Close() error 12 | } 13 | -------------------------------------------------------------------------------- /viewprovider_core.go: -------------------------------------------------------------------------------- 1 | package gocb 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/url" 7 | "strings" 8 | "time" 9 | 10 | gocbcore "github.com/couchbase/gocbcore/v10" 11 | ) 12 | 13 | type jsonViewResponse struct { 14 | TotalRows uint64 `json:"total_rows,omitempty"` 15 | DebugInfo interface{} `json:"debug_info,omitempty"` 16 | } 17 | 18 | type jsonViewRow struct { 19 | ID string `json:"id"` 20 | Key json.RawMessage `json:"key"` 21 | Value json.RawMessage `json:"value"` 22 | } 23 | 24 | type viewProviderCore struct { 25 | provider viewProviderCoreProvider 26 | 27 | bucketName string 28 | retryStrategyWrapper *coreRetryStrategyWrapper 29 | transcoder Transcoder 30 | timeouts TimeoutsConfig 31 | tracer *tracerWrapper 32 | } 33 | 34 | // ViewQuery performs a view query and returns a list of rows or an error. 35 | func (v *viewProviderCore) ViewQuery(designDoc string, viewName string, opts *ViewOptions) (*ViewResult, error) { 36 | designDoc = v.maybePrefixDevDocument(opts.Namespace, designDoc) 37 | 38 | span := v.tracer.createSpan(opts.ParentSpan, "views", "views") 39 | span.SetAttribute("db.name", v.bucketName) 40 | span.SetAttribute("db.operation", designDoc+"/"+viewName) 41 | defer span.End() 42 | 43 | timeout := opts.Timeout 44 | if timeout == 0 { 45 | timeout = v.timeouts.ViewTimeout 46 | } 47 | deadline := time.Now().Add(timeout) 48 | 49 | retryWrapper := v.retryStrategyWrapper 50 | if opts.RetryStrategy != nil { 51 | retryWrapper = newCoreRetryStrategyWrapper(opts.RetryStrategy) 52 | } 53 | 54 | urlValues, err := opts.toURLValues() 55 | if err != nil { 56 | return nil, wrapError(err, "could not parse query options") 57 | } 58 | 59 | return v.execViewQuery(opts.Context, span.Context(), "_view", designDoc, viewName, *urlValues, deadline, 60 | retryWrapper, opts.Internal.User) 61 | } 62 | 63 | func (v *viewProviderCore) execViewQuery( 64 | ctx context.Context, 65 | span RequestSpanContext, 66 | viewType, ddoc, viewName string, 67 | options url.Values, 68 | deadline time.Time, 69 | wrapper *coreRetryStrategyWrapper, 70 | user string, 71 | ) (*ViewResult, error) { 72 | res, err := v.provider.ViewQuery(ctx, gocbcore.ViewQueryOptions{ 73 | DesignDocumentName: ddoc, 74 | ViewType: viewType, 75 | ViewName: viewName, 76 | Options: options, 77 | RetryStrategy: wrapper, 78 | Deadline: deadline, 79 | TraceContext: span, 80 | User: user, 81 | }) 82 | if err != nil { 83 | return nil, maybeEnhanceViewError(err) 84 | } 85 | 86 | return newViewResult(res), nil 87 | } 88 | 89 | func (v *viewProviderCore) maybePrefixDevDocument(namespace DesignDocumentNamespace, ddoc string) string { 90 | designDoc := ddoc 91 | if namespace == DesignDocumentNamespaceProduction { 92 | designDoc = strings.TrimPrefix(ddoc, "dev_") 93 | } else { 94 | if !strings.HasPrefix(ddoc, "dev_") { 95 | designDoc = "dev_" + ddoc 96 | } 97 | } 98 | 99 | return designDoc 100 | } 101 | 102 | type viewProviderWrapper struct { 103 | provider *gocbcore.Agent 104 | } 105 | 106 | func (apw *viewProviderWrapper) ViewQuery(ctx context.Context, opts gocbcore.ViewQueryOptions) (vOut viewRowReader, errOut error) { 107 | opm := newAsyncOpManager(ctx) 108 | err := opm.Wait(apw.provider.ViewQuery(opts, func(reader *gocbcore.ViewQueryRowReader, err error) { 109 | if err != nil { 110 | errOut = err 111 | opm.Reject() 112 | return 113 | } 114 | 115 | vOut = reader 116 | opm.Resolve() 117 | })) 118 | if err != nil { 119 | errOut = err 120 | } 121 | 122 | return 123 | } 124 | 125 | type viewProviderCoreProvider interface { 126 | ViewQuery(ctx context.Context, opts gocbcore.ViewQueryOptions) (viewRowReader, error) 127 | } 128 | --------------------------------------------------------------------------------