Files
Giriraj Singh d2ea819af0 Added support to connect and perform CRUD operations with couchbase (#3138)
* Implemented Couchbase binary protocol support

* added support for single connection type for couchbase

* removed unnecessary cout statements

* added protocol code for helo packet

* fixed vbucketID code for identification, fixed add and get functions

* Added test cases for threaded get and add functions

* Added Error Handling code and made upsert and delete examples

* added makefile for example/couchbase_c++

* fixed bugs in couchbase header files

* Added License and formatted to google c++ norms

* fixed bugs, added support for collections and added couchbase_client.md

* fixed license issue

* added custom logic for caching collectionIDs

* added caching of collection manifests

* Added example code for multithreaded demonstration

* updated CMake

* Abstracted CRUD operations

* Added pipeline/batching support

* commented unused variables

* Updated support for C++17

* fixed some issue.

* Using Mutex instead of shared lock to support c++11

* Formatted code to google c++ format

* Introduced local cache per-instance of CouchbaseOperations and added functionality to handle server side manifest updates.

* Delete MODULE.bazel.lock

Unnecessary file

* Fixed bugs in local collection cache and collection refresh logic

* remove recurring statements

* Fixed bugs/repetitive calls to refreshing manifest on server

* Formatted function/variable naming scheme and formatted code in c++ google format

* removed unnecessary code

* updated comments

* updated comments

* updated documentation

* updated documentation

* updated documentation

* updated documentation

* Updated documentation

* Updated documentation

* Update documentation

* Added features and fixed bugs in multithreaded environment

Using connection_groups to differentiate between connections across CouchbaseOperations instances to different buckets.

Renamed CollectionManifestTracker class to CollectionManifestManager and all the related functionality inside it as before refreshing method was outside this class

Added two different authenticate method authenticate(not secure) and authenticateSSL(secure)

* Updated multithreaded and single threaded code.

Added an example where a single instance is being shared across the threads when operating on single bucket.

* updated documentation

updated the documentation on thread safe operations and fixed small small discrepancies.

* removed commented code and updated readme to have links for cluster download certificate

* removed unused code.

* Added traditional bRPC coding approach

Traditional bRPC coding approach doesn't uses high level functions but provides more control to the user

fixed formatting issues.

fixed the bug in couchbase.cpp where logic to check the cache is empty was inverted

* updated couchbase_example.md

* added unit test cases

* removed using namespace std from couchbase.h

* restored original CMakeLists.txt
2025-11-25 16:56:37 +08:00

452 lines
18 KiB
C++

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <brpc/couchbase.h>
#include <butil/logging.h>
#include <gflags/gflags.h>
#include <iostream>
#include <string>
// ANSI color codes for console output
#define GREEN "\033[32m"
#define RED "\033[31m"
#define RESET "\033[0m"
DEFINE_string(server, "localhost:11210", "IP Address of server");
int performOperations(brpc::CouchbaseOperations& couchbase_ops) {
std::string add_key = "user::test_brpc_binprot";
std::string add_value =
R"({"name": "John Doe", "age": 30, "email": "john@example.com"})";
brpc::CouchbaseOperations::Result add_result =
couchbase_ops.add(add_key, add_value);
if (add_result.success) {
std::cout << GREEN << "ADD operation successful" << RESET << std::endl;
} else {
std::cout << RED << "ADD operation failed: " << add_result.error_message
<< RESET << std::endl;
}
// Try to ADD the same key again (should fail with key exists)
brpc::CouchbaseOperations::Result add_result2 =
couchbase_ops.add(add_key, add_value);
if (add_result2.success) {
std::cout << GREEN << "Second ADD operation unexpectedly successful"
<< RESET << std::endl;
} else {
std::cout << RED << "Second ADD operation failed as expected: "
<< add_result2.error_message << RESET << std::endl;
}
// Get operation using high-level method
brpc::CouchbaseOperations::Result get_result = couchbase_ops.get(add_key);
if (get_result.success) {
std::cout << GREEN << "GET operation successful" << RESET << std::endl;
std::cout << "GET value: " << get_result.value << std::endl;
} else {
std::cout << RED << "GET operation failed: " << get_result.error_message
<< RESET << std::endl;
}
// Add binprot item1 using high-level method
std::string item1_key = "binprot_item1";
brpc::CouchbaseOperations::Result item1_result =
couchbase_ops.add(item1_key, add_value);
if (item1_result.success) {
std::cout << GREEN << "ADD binprot item1 successful" << RESET << std::endl;
} else {
std::cout << RED
<< "ADD binprot item1 failed: " << item1_result.error_message
<< RESET << std::endl;
}
// Add binprot item2 using high-level method
std::string item2_key = "binprot_item2";
brpc::CouchbaseOperations::Result item2_result =
couchbase_ops.add(item2_key, add_value);
if (item2_result.success) {
std::cout << GREEN << "ADD binprot item2 successful" << RESET << std::endl;
} else {
std::cout << RED
<< "ADD binprot item2 failed: " << item2_result.error_message
<< RESET << std::endl;
}
// Add binprot item3 using high-level method
std::string item3_key = "binprot_item3";
brpc::CouchbaseOperations::Result item3_result =
couchbase_ops.add(item3_key, add_value);
if (item3_result.success) {
std::cout << GREEN << "ADD binprot item3 successful" << RESET << std::endl;
} else {
std::cout << RED
<< "ADD binprot item3 failed: " << item3_result.error_message
<< RESET << std::endl;
}
// Perform an UPSERT on the existing key using high-level method
std::string upsert_key = "user::test_brpc_binprot";
std::string upsert_value =
R"({"name": "Upserted Jane Doe", "age": 28, "email": "upserted.doe@example.com"})";
brpc::CouchbaseOperations::Result upsert_result =
couchbase_ops.upsert(upsert_key, upsert_value);
if (upsert_result.success) {
std::cout
<< GREEN
<< "UPSERT operation successful when the document exists in the server"
<< RESET << std::endl;
} else {
std::cout
<< RED
<< "UPSERT operation failed when the document exists in the server: "
<< upsert_result.error_message << RESET << std::endl;
}
// Do UPSERT operation on a new document using high-level method
std::string new_upsert_key = "user::test_brpc_new_upsert";
std::string new_upsert_value =
R"({"name": "Jane Doe", "age": 28, "email": "jane.doe@example.com"})";
brpc::CouchbaseOperations::Result new_upsert_result =
couchbase_ops.upsert(new_upsert_key, new_upsert_value);
if (new_upsert_result.success) {
std::cout << GREEN
<< "UPSERT operation successful when the document doesn't exist "
"in the server"
<< RESET << std::endl;
} else {
std::cout << RED
<< "UPSERT operation failed when document does not exist in the "
"server: "
<< new_upsert_result.error_message << RESET << std::endl;
}
// Check the upserted data using high-level method
std::string check_key = "user::test_brpc_new_upsert";
brpc::CouchbaseOperations::Result check_result = couchbase_ops.get(check_key);
if (check_result.success) {
std::cout << GREEN << "GET after UPSERT operation successful - Value: "
<< check_result.value << RESET << std::endl;
} else {
std::cout << RED << "GET after UPSERT operation failed: "
<< check_result.error_message << RESET << std::endl;
}
// Delete a non-existent key using high-level method
std::string delete_key = "Nonexistent_key";
brpc::CouchbaseOperations::Result delete_result =
couchbase_ops.delete_(delete_key);
if (delete_result.success) {
std::cout << GREEN << "DELETE operation successful" << RESET << std::endl;
} else {
std::cout << RED << "DELETE operation failed: as expected "
<< delete_result.error_message << RESET << std::endl;
}
// Delete the existing key using high-level method
std::string delete_existing_key = "user::test_brpc_binprot";
brpc::CouchbaseOperations::Result delete_existing_result =
couchbase_ops.delete_(delete_existing_key);
if (delete_existing_result.success) {
std::cout << GREEN << "DELETE operation successful" << RESET << std::endl;
} else {
std::cout << RED << "DELETE operation failed: "
<< delete_existing_result.error_message << RESET << std::endl;
}
// Retrieve Collection ID for scope `_default` and collection
// `col1`
const std::string scope_name = "_default"; // default scope
std::string collection_name = "col1"; // target collection
// ------------------------------------------------------------------
// Collection-scoped CRUD operations (only if collection id was retrieved)
// ------------------------------------------------------------------
// 1. ADD in collection using high-level method
std::string coll_key = "user::collection_doc";
std::string coll_value = R"({"type":"collection","op":"add","v":1})";
brpc::CouchbaseOperations::Result coll_add_result =
couchbase_ops.add(coll_key, coll_value, collection_name);
if (coll_add_result.success) {
std::cout << GREEN << "Collection ADD success" << RESET << std::endl;
} else {
std::cout << RED
<< "Collection ADD failed: " << coll_add_result.error_message
<< RESET << std::endl;
}
// 2. GET from collection using high-level method
brpc::CouchbaseOperations::Result coll_get_result =
couchbase_ops.get(coll_key, collection_name);
if (coll_get_result.success) {
std::cout << GREEN
<< "Collection GET success value=" << coll_get_result.value
<< RESET << std::endl;
} else {
std::cout << RED
<< "Collection GET failed: " << coll_get_result.error_message
<< RESET << std::endl;
}
// 3. UPSERT in collection using high-level method
std::string coll_upsert_value =
R"({"type":"collection","op":"upsert","v":2})";
brpc::CouchbaseOperations::Result coll_upsert_result =
couchbase_ops.upsert(coll_key, coll_upsert_value, collection_name);
if (coll_upsert_result.success) {
std::cout << GREEN << "Collection UPSERT success" << RESET << std::endl;
} else {
std::cout << RED << "Collection UPSERT failed: "
<< coll_upsert_result.error_message << RESET << std::endl;
}
// 4. GET again to verify upsert using high-level method
brpc::CouchbaseOperations::Result coll_get2_result =
couchbase_ops.get(coll_key, collection_name);
if (coll_get2_result.success) {
std::cout << GREEN
<< "Collection GET(after upsert) value=" << coll_get2_result.value
<< RESET << std::endl;
}
// 5. DELETE from collection using high-level method
brpc::CouchbaseOperations::Result coll_del_result =
couchbase_ops.delete_(coll_key, collection_name);
if (coll_del_result.success) {
std::cout << GREEN << "Collection DELETE success" << RESET << std::endl;
} else {
std::cout << RED
<< "Collection DELETE failed: " << coll_del_result.error_message
<< RESET << std::endl;
}
// ------------------------------------------------------------------
// Pipeline Operations Demo
// ------------------------------------------------------------------
std::cout << GREEN << "\n=== Pipeline Operations Demo ===" << RESET
<< std::endl;
// Begin a new pipeline
if (!couchbase_ops.beginPipeline()) {
std::cout << RED << "Failed to begin pipeline" << RESET << std::endl;
return -1;
}
std::cout << "Pipeline started. Adding multiple operations..." << std::endl;
// Add multiple operations to the pipeline
std::string pipeline_key1 = "pipeline::doc1";
std::string pipeline_key2 = "pipeline::doc2";
std::string pipeline_key3 = "pipeline::doc3";
std::string pipeline_value1 = R"({"operation": "pipeline_add", "id": 1})";
std::string pipeline_value2 = R"({"operation": "pipeline_upsert", "id": 2})";
std::string pipeline_value3 = R"({"operation": "pipeline_add", "id": 3})";
// Pipeline operations - all prepared but not yet executed
bool pipeline_success = true;
pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::ADD, pipeline_key1, pipeline_value1);
pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::UPSERT, pipeline_key2, pipeline_value2);
pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::ADD, pipeline_key3, pipeline_value3);
pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::GET, pipeline_key1);
pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::GET, pipeline_key2);
if (!pipeline_success) {
std::cout << RED << "Failed to add operations to pipeline" << RESET
<< std::endl;
couchbase_ops.clearPipeline();
return -1;
}
std::cout << "Added " << couchbase_ops.getPipelineSize()
<< " operations to pipeline" << std::endl;
// Execute all operations in a single network call
std::cout << "Executing pipeline operations..." << std::endl;
std::vector<brpc::CouchbaseOperations::Result> pipeline_results =
couchbase_ops.executePipeline();
// Process results in order
std::cout << GREEN << "Pipeline execution completed. Results:" << RESET
<< std::endl;
for (size_t i = 0; i < pipeline_results.size(); ++i) {
const auto& result = pipeline_results[i];
if (result.success) {
if (!result.value.empty()) {
std::cout << GREEN << " Operation " << (i + 1)
<< " SUCCESS - Value: " << result.value << RESET << std::endl;
} else {
std::cout << GREEN << " Operation " << (i + 1) << " SUCCESS" << RESET
<< std::endl;
}
} else {
std::cout << RED << " Operation " << (i + 1)
<< " FAILED: " << result.error_message << RESET << std::endl;
}
}
// Demonstrate pipeline with collection operations
std::cout << GREEN << "\n=== Pipeline with Collection Operations ===" << RESET
<< std::endl;
if (!couchbase_ops.beginPipeline()) {
std::cout << RED << "Failed to begin collection pipeline" << RESET
<< std::endl;
return -1;
}
std::string coll_pipeline_key1 = "coll_pipeline::doc1";
std::string coll_pipeline_key2 = "coll_pipeline::doc2";
std::string coll_pipeline_value1 =
R"({"collection_operation": "pipeline_add", "id": 1})";
std::string coll_pipeline_value2 =
R"({"collection_operation": "pipeline_upsert", "id": 2})";
// Add collection-scoped operations to pipeline
bool coll_pipeline_success = true;
coll_pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::ADD, coll_pipeline_key1, coll_pipeline_value1,
collection_name);
coll_pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::UPSERT, coll_pipeline_key2,
coll_pipeline_value2, collection_name);
coll_pipeline_success &= couchbase_ops.pipelineRequest(
brpc::CouchbaseOperations::GET, coll_pipeline_key1, "", collection_name);
coll_pipeline_success &=
couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE,
coll_pipeline_key1, "", collection_name);
if (!coll_pipeline_success) {
std::cout << RED << "Failed to add collection operations to pipeline"
<< RESET << std::endl;
couchbase_ops.clearPipeline();
return -1;
}
// Execute collection pipeline
std::vector<brpc::CouchbaseOperations::Result> coll_pipeline_results =
couchbase_ops.executePipeline();
std::cout << GREEN
<< "Collection pipeline execution completed. Results:" << RESET
<< std::endl;
for (size_t i = 0; i < coll_pipeline_results.size(); ++i) {
const auto& result = coll_pipeline_results[i];
if (result.success) {
if (!result.value.empty()) {
std::cout << GREEN << " Collection Operation " << (i + 1)
<< " SUCCESS - Value: " << result.value << RESET << std::endl;
} else {
std::cout << GREEN << " Collection Operation " << (i + 1) << " SUCCESS"
<< RESET << std::endl;
}
} else {
std::cout << RED << " Collection Operation " << (i + 1)
<< " FAILED: " << result.error_message << RESET << std::endl;
}
}
// Clean up remaining pipeline documents
std::cout << GREEN << "\n=== Cleanup Pipeline Demo ===" << RESET << std::endl;
if (couchbase_ops.beginPipeline()) {
couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE,
pipeline_key1);
couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE,
pipeline_key2);
couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE,
pipeline_key3);
couchbase_ops.pipelineRequest(brpc::CouchbaseOperations::DELETE,
coll_pipeline_key2, "", collection_name);
std::vector<brpc::CouchbaseOperations::Result> cleanup_results =
couchbase_ops.executePipeline();
std::cout << "Cleanup completed (" << cleanup_results.size()
<< " operations)" << std::endl;
}
std::cout << GREEN
<< "\n=== All operations completed successfully! ===" << RESET
<< std::endl;
}
int main() {
// Create CouchbaseOperations instance for high-level operations
brpc::CouchbaseOperations couchbase_ops;
// std::cout << GREEN << "Using high-level CouchbaseOperations interface"
// << RESET << std::endl;
// Ask username and password for authentication
std::string username = "Administrator";
std::string password = "password";
while (username.empty() || password.empty()) {
std::cout << "Enter Couchbase username: ";
std::cin >> username;
if (username.empty()) {
std::cout << "Username cannot be empty. Please enter again." << std::endl;
continue;
}
std::cout << "Enter Couchbase password: ";
std::cin >> password;
if (password.empty()) {
std::cout << "Password cannot be empty. Please enter again." << std::endl;
continue;
}
}
// Use high-level authentication method
// when connecting to capella use couchbase_ops.authenticate(username,
// password, FLAGS_server, true, "path/to/cert.txt");
brpc::CouchbaseOperations::Result auth_result =
couchbase_ops.authenticate(username, password, FLAGS_server, "testing");
if (!auth_result.success) {
LOG(ERROR) << "Authentication failed: " << auth_result.error_message;
return -1;
}
std::cout
<< GREEN
<< "Authentication successful, proceeding with Couchbase operations..."
<< RESET << std::endl;
performOperations(couchbase_ops);
// Change bucket Selection
std::string bucket_name = "testing";
while (bucket_name.empty()) {
std::cout << "Enter Couchbase bucket name: ";
std::cin >> bucket_name;
if (bucket_name.empty()) {
std::cout << "Bucket name cannot be empty. Please enter again."
<< std::endl;
continue;
}
}
// Use high-level bucket selection method
brpc::CouchbaseOperations::Result bucket_result =
couchbase_ops.selectBucket(bucket_name);
if (!bucket_result.success) {
LOG(ERROR) << "Bucket selection failed: " << bucket_result.error_message;
return -1;
} else {
std::cout << GREEN << "Bucket Selection Successful" << RESET << std::endl;
}
// Add operation using high-level method
performOperations(couchbase_ops);
return 0;
}