From db3745ad65c7be4550d22332795f46fc4311edd7 Mon Sep 17 00:00:00 2001 From: avtarraikmo <118772076+avtarraikmo@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:34:53 +0100 Subject: [PATCH] [API-2104] Update C++ Client --- README.adoc | 2 +- .../pages/cpp-client-getting-started.adoc | 433 +++++++++++++----- 2 files changed, 317 insertions(+), 118 deletions(-) diff --git a/README.adoc b/README.adoc index fbae97b..6e05692 100644 --- a/README.adoc +++ b/README.adoc @@ -1,3 +1,3 @@ // Replace with the name of your repository, and replace with the title of the tutorial. // For guidance on using this template, see .github/CONTRIBUTING.adoc -This repository hosts the documentation and code samples for the link:https://docs.hazelcast.com/tutorials/cpp-client-getting-started[C++ Client Getting Started tutorial]. +This repository hosts the documentation and code samples for the link:https://docs.hazelcast.com/tutorials/cpp-client-getting-started[C++ tutorial]. diff --git a/docs/modules/ROOT/pages/cpp-client-getting-started.adoc b/docs/modules/ROOT/pages/cpp-client-getting-started.adoc index 6f8d439..8763445 100644 --- a/docs/modules/ROOT/pages/cpp-client-getting-started.adoc +++ b/docs/modules/ROOT/pages/cpp-client-getting-started.adoc @@ -1,10 +1,11 @@ = Getting Started with the Hazelcast C++ Client :page-layout: tutorial :page-product: platform -:page-categories: Caching, Getting Started +:page-categories: Get Started :page-lang: cplus +:page-enterprise: :page-est-time: 5-10 mins -:description: This tutorial will get you started with the Hazelcast C++ client. +:description: This tutorial will get you started with the Hazelcast C++ client and manipulate a map. == What You'll Learn @@ -12,29 +13,30 @@ == Before you Begin +* C++ 11 or above +* https://hazelcast.com/products/viridian/[Hazelcast Viridian Cloud Account] * A text editor or IDE -* Docker -* C++ 11+ supporting compiler -* Vcpkg -== Start a Hazelcast Member +== Start a Hazelcast Viridian Cloud Cluster -We will use the 5.1 version of Hazelcast for this tutorial. +1. Sign up for a Hazelcast Viridian Cloud account (free trial is available). +2. Log in to your Hazelcast Viridian Cloud account and start your trial by filling in the welcome questionnaire. +3. A Viridian cluster will be created automatically when you start your trial. +4. Press the Connect Cluster dialog and switch over to the Advanced setup tab for connection information needed below. +5. From the Advanced setup tab, download the keystore files and take note of your Cluster ID, Discovery Token and Password as you will need them later. -In this tutorial, we will use Docker for simplicity. You can also use https://docs.hazelcast.com/hazelcast/5.1/getting-started/get-started-cli[CLI], https://docs.hazelcast.com/hazelcast/5.1/getting-started/get-started-binary[Binary] and https://docs.hazelcast.com/hazelcast/5.1/getting-started/get-started-java[Maven]. +== Setup a Hazelcast Client -[source,bash] +Create a new folder and navigate to it: + +[source] ---- -docker run -p 5701:5701 hazelcast/hazelcast:5.1 +mkdir hazelcast-cpp-example +cd hazelcast-cpp-example ---- -This will start a new Hazelcast member at port 5701. Now, we have a Hazelcast cluster with just one member. +Download and install Vcpkg: + -== Install Hazelcast C++ Client -In this tutorial we will use Vcpkg for installing the C++ client. You can also use https://github.com/hazelcast/hazelcast-cpp-client/blob/master/Reference_Manual.md#111-conan-users[Conan] or install from source using CMake as explained https://github.com/hazelcast/hazelcast-cpp-client/blob/master/Reference_Manual.md#113-install-from-source-code-using-cmake[here]. - - -Before starting download and install Vcpkg itself if you haven't already: + for Windows; [source,bash] ---- @@ -48,172 +50,369 @@ for non-Windows; git clone https://github.com/microsoft/vcpkg ./vcpkg/bootstrap-vcpkg.sh ---- -First, execute the following to install `hazelcast-cpp-client` with its `boost` dependency: + + +Download and install hazelcast-cpp-client: + + for Windows; [source,bash] ---- -.\vcpkg\vcpkg.exe install hazelcast-cpp-client +.\vcpkg\vcpkg.exe install "hazelcast-cpp-client[openssl]" --recurse ---- + for non-Windows; [source,bash] ---- -./vcpkg/vcpkg install hazelcast-cpp-client +./vcpkg/vcpkg install "hazelcast-cpp-client[openssl]" --recurse ---- -After the installation, the library is available for usage. -For example, if you are using CMake for your builds, you can use the following cmake build command with the `CMAKE_TOOLCHAIN_FILE` cmake option to be the `vcpkg.cmake`. +NOTE: Avoid directory names in your path that contain spaces or other non-standard characters. + +Extract the keystore files you downloaded from Viridian into this directory. The files you need for this tutorial are: [source,bash] ---- -cmake -B [build directory] -S . -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake -cmake --build [build directory] +ca.pem +cert.pem +key.pem ---- -For more information about Vcpkg installation check https://github.com/hazelcast/hazelcast-cpp-client/blob/master/Reference_Manual.md#112-vcpkg-users[here]. -== Starting the C++ Client -In this tutorial we use CMake for compilation, for other options you can check https://github.com/hazelcast/hazelcast-cpp-client/blob/master/Reference_Manual.md#13-compiling-your-project[here]. -If you are using CMake like we do, you can easily find and link against the client library: -[source] ----- -find_package(hazelcast-cpp-client CONFIG REQUIRED) +Create a CMake file in this directory, named "CMakeLists.txt" as follows: -target_link_libraries(mytarget PRIVATE hazelcast-cpp-client::hazelcast-cpp-client) +[source,bash] ---- -Make sure you add the installation prefix of the client library to CMAKE_PREFIX_PATH if you are using a custom installation location. +cmake_minimum_required(VERSION 3.10) -Then, You can include the library and start a client using the following code: -[source,cpp] ----- -#include +project(HazelcastCloud) -int main() { - auto hz = hazelcast::new_client().get(); - std::cout << "Hazelcast client started and connected to Hazelcast cluster successfully" << std::endl; - return 0; -} ----- +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -The following line creates and starts a new Hazelcast C++ client with the default configuration. +find_package(hazelcast-cpp-client CONFIG REQUIRED) -[source,cpp] +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/example.cpp) + add_executable(example example.cpp) + target_link_libraries(example PRIVATE hazelcast-cpp-client::hazelcast-cpp-client) +endif() ---- -auto hz = hazelcast::new_client().get(); + +You should have the following entries in the directory: +[source,bash] +---- +CMakeLists.txt +ca.pem +cert.pem +key.pem +vcpkg ---- -The client automatically connects to the Hazelcast member available on the local machine. Client also automatically disconnects upon its destruction. +== Understanding the C++ Client -== Use Map +The following section creates and starts a Hazelcast client with default configuration, connects to your Viridian cluster before shutting the client down at the end. -A Hazelcast map is a distributed key-value store, similar to JavaScript Map class or plain objects. You can store key-value pairs in a map. -In the following example, we will work with map entries where the keys are session ids and the values are emails. +Create a C++ file named “example.cpp” and put the following code inside it: [source,cpp] ---- +#include #include -int main() { - auto hz = hazelcast::new_client().get(); - auto map = hz.get_map("some_map").get(); - map->put("sid12345","example1@email.com"); - map->put("sid12346","example2@email.com"); - std::cout << map->get("sid12345").get() << std::endl; - std::cout << map->get("sid12346").get() << std::endl; +int +main(int argc, char** argv) +{ + hazelcast::client::client_config config; + + // Viridian Cluster Name and Token + config.set_cluster_name(""); + auto& cloud_configuration = config.get_network_config().get_cloud_config(); + cloud_configuration.enabled = true; + cloud_configuration.discovery_token = ""; + + // configure SSL + boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); + + try { + ctx.load_verify_file("ca.pem"); + ctx.use_certificate_file("cert.pem", boost::asio::ssl::context::pem); + ctx.set_password_callback( + [&](std::size_t max_length, + boost::asio::ssl::context::password_purpose purpose) { + return ""; + }); + ctx.use_private_key_file("key.pem", boost::asio::ssl::context::pem); + } catch (std::exception& e) { + std::cerr << "You should copy ca.pem, cert.pem and key.pem files to " + "the working directory, exception cause " + << e.what() << std::endl; + exit(EXIT_FAILURE); + } + config.get_network_config().get_ssl_config().set_context(std::move(ctx)); + + // Connect to your Hazelcast Cluster + auto client = hazelcast::new_client(std::move(config)).get(); + + // take actions + std::cout << "Welcome to your Hazelcast Viridian Cluster!" << std::endl; + + // Shutdown the client connection + client.shutdown().get(); } ---- -The output of this snippet is given below: +Compile using CMake as follows: [source,bash] ---- -example1@email.com -example2@email.com +cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake +cmake --build build ---- -The following line returns a map proxy object for the 'some_map' map: +Once complete, run the example: -[source,cpp] +[source,bash] ---- -auto map = hz.get_map("some_map").get(); +./build/example ---- -If the map called “some_map” does not exist in the Hazelcast cluster, it will be automatically created. All the clients that connect to the same cluster will have access to the same map. +For more information about Vcpkg installation check https://github.com/hazelcast/hazelcast-cpp-client/blob/master/Reference_Manual.md#112-vcpkg-users[here]. +In this tutorial we use CMake for compilation, for other options you can check https://github.com/hazelcast/hazelcast-cpp-client/blob/master/Reference_Manual.md#13-compiling-your-project[here]. -With these two lines, the C++ client adds data to the map. The first parameter is the key of the entry, the second one is the value: +To understand and use the client, review the https://hazelcast.github.io/hazelcast-cpp-client/api-index.html[C++ API documentation] to better understand what is possible. -[source,cpp] ----- -map->put("sid12345","example1@email.com"); -map->put("sid12346","example2@email.com"); ----- +== Understanding the Hazelcast SQL API -Finally, we get the values we added to the map with the get method: +Hazelcast SQL API is a Calcite SQL based interface to allow you to interact with Hazelcast much like any other datastore. + +In the following example, we will create a map and insert into it, entries where the keys are ids and the values are defined as an object representing a city. [source,cpp] ---- -std::cout << map->get("sid12345").get() << std::endl; -std::cout << map->get("sid12346").get() << std::endl; ----- - -== Add a Listener to the Map - -You can add an entry listener using the `add_entry_listener` method available on map proxy object. -This will allow you to listen to certain events that happen in the map across the cluster. +#include +#include -The first argument to the `add_entry_listener` method is an object that is used to define listeners. -In this example, we registered listeners for the `on_added`, `on_removed` and `on_updated` events. +void +create_mapping(hazelcast::client::hazelcast_client client); +void +insert_cities(hazelcast::client::hazelcast_client client); +void +fetch_cities(hazelcast::client::hazelcast_client client); + +struct CityDTO +{ + std::string cityName; + std::string country; + int population; +}; + +// CityDTO serializer +namespace hazelcast { +namespace client { +namespace serialization { + +template<> +struct hz_serializer : compact::compact_serializer +{ + static void write(const CityDTO& object, compact::compact_writer& out) + { + out.write_int32("population", object.population); + out.write_string("city", object.cityName); + out.write_string("country", object.country); + } + + static CityDTO read(compact::compact_reader& in) + { + CityDTO c; + + c.population = in.read_int32("population"); + boost::optional city = in.read_string("city"); + + if (city) { + c.cityName = *city; + } + + boost::optional country = in.read_string("country"); + + if (country) { + c.country = *country; + } + + return c; + } + + static std::string type_name() { return "CityDTO"; } +}; + +} // namespace serialization +} // namespace client +} // namespace hazelcast + +int +main(int argc, char** argv) +{ + hazelcast::client::client_config config; + + // Viridian Cluster Name and Token + config.set_cluster_name(""); + auto& cloud_configuration = config.get_network_config().get_cloud_config(); + cloud_configuration.enabled = true; + cloud_configuration.discovery_token = ""; + + // configure SSL + boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); + + try { + ctx.load_verify_file("ca.pem"); + ctx.use_certificate_file("cert.pem", boost::asio::ssl::context::pem); + ctx.set_password_callback( + [&](std::size_t max_length, + boost::asio::ssl::context::password_purpose purpose) { + return ""; + }); + ctx.use_private_key_file("key.pem", boost::asio::ssl::context::pem); + } catch (std::exception& e) { + std::cerr << "You should copy ca.pem, cert.pem and key.pem files to " + "the working directory, exception cause " + << e.what() << std::endl; + exit(EXIT_FAILURE); + } + config.get_network_config().get_ssl_config().set_context(std::move(ctx)); + + // Connect to your Hazelcast Cluster + auto client = hazelcast::new_client(std::move(config)).get(); + + // take actions + create_mapping(client); + insert_cities(client); + fetch_cities(client); + + // Shutdown the client connection + client.shutdown().get(); +} -The second argument in the `add_entry_listener` method is `include_value`. It is a boolean parameter, and if it is true, the entry event contains the entry value. -In this example, it will be true. +void +create_mapping(hazelcast::client::hazelcast_client client) +{ + // Mapping is required for your distributed map to be queried over SQL. + // See: https://docs.hazelcast.com/hazelcast/latest/sql/mapping-to-maps + + std::cout << "Creating the mapping..."; + + auto sql = client.get_sql(); + + auto result = sql + .execute(R"(CREATE OR REPLACE MAPPING + cities ( + __key INT, + country VARCHAR, + city VARCHAR, + population INT) TYPE IMAP + OPTIONS ( + 'keyFormat' = 'int', + 'valueFormat' = 'compact', + 'valueCompactTypeName' = 'CityDTO'))") + .get(); + + std::cout << "OK." << std::endl; +} -[source,cpp] ----- -#include +void +insert_cities(hazelcast::client::hazelcast_client client) +{ + auto sql = client.get_sql(); + + try { + sql.execute("DELETE FROM cities").get(); + + std::cout << "Inserting data..."; + + // Create mapping for the integers. This needs to be done only once per + // map. + auto result = sql + .execute(R"(INSERT INTO cities + (__key, city, country, population) VALUES + (1, 'London', 'United Kingdom', 9540576), + (2, 'Manchester', 'United Kingdom', 2770434), + (3, 'New York', 'United States', 19223191), + (4, 'Los Angeles', 'United States', 3985520), + (5, 'Istanbul', 'Türkiye', 15636243), + (6, 'Ankara', 'Türkiye', 5309690), + (7, 'Sao Paulo ', 'Brazil', 22429800))") + .get(); + + std::cout << "OK." << std::endl; + } catch (hazelcast::client::exception::iexception& e) { + // don't panic for duplicated keys. + std::cerr << "FAILED, duplicated keys " << e.what() << std::endl; + } +} -int main(){ - auto client = hazelcast::new_client().get(); - auto map = client.get_map("some_map").get(); - - map->add_entry_listener( - hazelcast::client::entry_listener().on_added([](hazelcast::client::entry_event &&event) { - std::cout << "Entry added. Key:" << event.get_key().get() << " Value: " << event.get_value().get() << std::endl; - }).on_removed([](hazelcast::client::entry_event &&event) { - std::cout << "Entry removed. Key: " << event.get_key().get() << std::endl; - }).on_updated([](hazelcast::client::entry_event &&event) { - std::cout << "Entry updated. Key: " << event.get_key().get() << " Value change: " << event.get_old_value().get() << " -> " << event.get_value().get() << std::endl; - }), true).get(); - - map->clear().get(); - - map->put("sid12345", "example1@email.com").get(); - map->put("sid12346", "example2@email.com").get(); - map->delete_entry("sid12345").get(); - map->put("sid12346", "example1@email.com").get(); +void +fetch_cities(hazelcast::client::hazelcast_client client) +{ + std::cout << "Fetching cities..."; + + auto result = + client.get_sql().execute("SELECT __key, this FROM cities").get(); + + std::cout << "OK." << std::endl; + std::cout << "--Results of 'SELECT __key, this FROM cities'" << std::endl; + + std::printf("| %-4s | %-20s | %-20s | %-15s |\n", + "id", + "country", + "city", + "population"); + + for (auto itr = result->iterator(); itr.has_next();) { + auto page = itr.next().get(); + + for (auto const& row : page->rows()) { + + auto id = row.get_object("__key"); + auto city = row.get_object("this"); + std::printf("| %-4d | %-20s | %-20s | %-15d |\n", + *id, + city->country.c_str(), + city->cityName.c_str(), + city->population); + } + } + + std::cout + << "\n!! Hint !! You can execute your SQL queries on your Viridian " + "cluster over the management center. \n 1. Go to 'Management Center' " + "of your Hazelcast Viridian cluster. \n 2. Open the 'SQL Browser'. \n " + "3. Try to execute 'SELECT * FROM cities'.\n"; } ---- -First, the map is cleared to fire events even if there are some entries in the map. Then, two session entries are added, and they are logged. -After that, we remove one of the entries and update the other one. Then, we log the session entries again. - -The output is as follows: +The output of this code is given below: [source,bash] ---- -Entry added. Key: sid12345 Value: example1@email.com -Entry added. Key: sid12346 Value: example2@email.com -Entry removed. Key: sid12345 -Entry updated. Key: sid12346 Value change: example2@email.com -> example1@email.com +Creating the mapping...OK. +Inserting data...OK. +Fetching cities...OK. +--Results of 'SELECT __key, this FROM cities' +| id | country | city | population | +| 2 | United Kingdom | Manchester | 2770434 | +| 6 | Turkiye | Ankara | 5309690 | +| 1 | United Kingdom | London | 9540576 | +| 7 | Brazil | Sao Paulo | 22429800 | +| 4 | United States | Los Angeles | 3985520 | +| 5 | Turkiye | Istanbul | 15636243 | +| 3 | United States | New York | 19223191 | ---- - +NOTE: Ordering of the keys is NOT enforced and results may NOT correspond to insertion order. == Summary -In this tutorial, you learned how to get started with Hazelcast C++ Client using a distributed map. +In this tutorial, you learned how to get started with the Hazelcast C++ Client, connect to a Viridian instance and put data into a distributed map. == See Also -There are a lot of things that you can do with the C++ client. For more, such as how you can query a map with predicates, -check out our https://github.com/hazelcast/hazelcast-cpp-client[client repository.] +There are a lot of things that you can do with the C++ Client. For more, such as how you can query a map with predicates and SQL, +check out our https://github.com/hazelcast/hazelcast-cpp-client[C++ Client repository] and our https://hazelcast.github.io/hazelcast-cpp-client/api-index.html[C++ API documentation] to better understand what is possible. -If you have any questions, suggestions, or feedback please do not hesitate to reach out to us via https://slack.hazelcast.com/[Hazelcast Community Slack.] +If you have any questions, suggestions, or feedback please do not hesitate to reach out to us via https://slack.hazelcast.com/[Hazelcast Community Slack]. Also, please take a look at https://github.com/hazelcast/hazelcast-cpp-client/issues[the issue list] if you would like to contribute to the client.