Skip to content

Commit

Permalink
page_iterator_sync and row_iterator_sync have been implemented. Docum…
Browse files Browse the repository at this point in the history
…entation is added.
  • Loading branch information
OzanCansel committed Mar 15, 2023
1 parent 75b231e commit cdd9fee
Show file tree
Hide file tree
Showing 7 changed files with 967 additions and 82 deletions.
63 changes: 63 additions & 0 deletions Reference_Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
* [7.11.5 Read Row Metadata](#7115-read-row-metadata)
* [7.11.6 Read Table Column Value](#7116-read-table-column-value)
* [7.11.7 Error Handling](#7117-error-handling)
* [7.11.8 Iterators](#7118-iterators)
* [8. Development and Testing](#8-development-and-testing)
* [8.1. Testing](#81-testing)
* [9. Getting Help](#9-getting-help)
Expand Down Expand Up @@ -3683,10 +3684,72 @@ Exceptions which can be thrown by SQL API are stated at below:
- `sql_result::row_metadata()` can throw `illegal_state` exception if the result contains only update count.
- `sql_page::sql_row::get_object(int)` can throw `index_out_of_bounds` exception if the index is out of range.
- `sql_page::sql_row::get_object(std::string)` can throw `illegal_argument` exception if the column doesn't exist.
- `sql_result::page_iterator_sync::operator++()` can throw `no_such_element` exception if the fetch operation is timedout.
- `sql_result::page_iterator_sync::operator*()` can throw `no_such_element` exception if the iterator points to past-end element;
- `sql_result::row_iterator_sync::operator++()` can throw `no_such_element` exception if the fetch operation is timedout.
- `sql_result::row_iterator_sync::operator*()` can throw `no_such_element` exception if the iterator points to past-end element;

In addition, any method which returns `boost::future<T>` can throw an exception.
Unless otherwise is stated, `sql::hazelcast_sql_exception` is thrown.

### 7.11.8 Iterators
There are several iterators in SQL Api to satisfy different use cases. They are listed below:

- `page_iterator` (Async page iterator)
- `page_iterator_sync` (Sync page iterator)
- `row_iterator_sync` (Sync row iterator)

Async iterator is the basic one. It supports async calls such as `next()` which it returns `boost::future<sql_page>`.
Therefore, users are able to fetch `SELECT` query results page by page. It is acquired by calling `sql_result::iterator()` method.

`page_iterator_sync` is the second iterator which wraps `page_iterator` and serves it as a sync iterator. It allows users to iterator over pages.
It is similar to native C++ iterators. So it can be used with `algorithm` header.
It supports `operator*()`, `operator->()`, `operator++()` operators. Post increment operator is marked as `delete`.
`operator++()`(pre-increment operator) fetches the next page in a blocking manner. `timeout` can be set. `operator++()` might throw `exception::no_such_element` exception if the fetch operation is timedout.
`page_iterator_sync` is copyable but it is there only for convenience. Copy is shallow copy so copied instances should not be used simultaneously.
This iterator is acquired by `sql_result::pbegin()` and `sql_result::pend()`.

``` C++
auto result = client.get_sql().execute("SELECT * FROM integers").get();

std::vector<std::shared_ptr<sql_page>> pages;

copy(result->pbegin(), result->pend(), back_inserter(pages));
```
`row_iterator_sync` is the third iterator which wraps `page_iterator_sync` and allows user to iterate over rows rather than pages.
It is similar to native C++ iterators. So it can be used with `algorithm` header.
It supports `operator*()`, `operator->()`, `operator++()` operators. Post increment operator is marked as `delete`.
`timeout` can be set, it is used when fetching new page. It throws `exception::no_such_element` exception if the fetch operation is timedout.
`row_iterator_sync` is copyable but it is there only for convenience. Copy is shallow copy so copied instances should not be used simultaneously.
This iterator is acquired by `sql_result::begin()` and `sql_result::end()`. Hence, this iterator can be used in `range-for` loop.
`range-for` loop example:
``` C++
auto result = client.get_sql().execute("SELECT * FROM integers").get();
for (const sql_page::sql_row& row : *result) {
std::cout << row.get_object<int>(0);
}
```

`algorithm` header example:
``` C++
auto result = client.get_sql().execute("SELECT * FROM integers").get();

std::vector<int> numbers;

transform(
begin(*result),
end(*result),
back_inserter(numbers),
[](const sql_page::sql_row& row) { return *row.get_object<int>(0); });
```
Reminder 1, only one type of iterator can be used.
Reminder 2, it is not possible iterate from beginning to end more than once.
# 8. Development and Testing
Hazelcast C++ client is developed using C++. If you want to help with bug fixes, develop new features or
Expand Down
2 changes: 2 additions & 0 deletions examples/sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ add_executable(sql_query_with_portable sql_query_with_portable.cpp)
add_executable(sql_cancellation_example sql_cancellation_example.cpp)
add_executable(sql_json_example sql_json_example.cpp)
add_executable(sql_order_by_limit_offset sql_order_by_limit_offset.cpp)
add_executable(sql_page_iterator_sync sql_page_iterator_sync.cpp)
add_executable(sql_row_iterator_sync sql_row_iterator_sync.cpp)
add_executable(sql_advanced_query_options sql_advanced_query_options.cpp)
225 changes: 225 additions & 0 deletions examples/sql/sql_page_iterator_sync.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@

/*
* Copyright (c) 2008-2023, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 <algorithm>
#include <iterator>

#include <boost/algorithm/string.hpp>

#include <hazelcast/client/hazelcast_client.h>

using hazelcast::client::hazelcast_client;

void
populate_map(hazelcast_client&);
void
create_mapping(hazelcast_client&);
void
for_loop(hazelcast_client&);
void
algorithm_copy(hazelcast_client&);
void
algorithm_filter(hazelcast_client&);
void
timeout(hazelcast_client&);

/**
* Normally, cpp-client provides an async api for every features.
* But for sake of convenience and similar usages with native C++ iterators
* cpp-client provides sync page iterator. So `page_iterator_sync` is a blocking
* iterator. It wraps the `page_iterator` and allows to be used in sync
* manner. This example demonstrates how to use `page_iterator_sync` and what
* the use cases are.
*/
int
main()
{
auto hz = hazelcast::new_client().get();

// Preparation
populate_map(hz);
create_mapping(hz);

// Use cases, examples
for_loop(hz);
algorithm_copy(hz);
algorithm_filter(hz);
timeout(hz);

return 0;
}

void
populate_map(hazelcast_client& client)
{
// Populate a map before using it in sql.
auto map = client.get_map("integers").get();

for (int i = 0; i < 100; ++i) {
map->put(i, i).get();
}
}

void
create_mapping(hazelcast_client& client)
{
// Create mapping for the integers.
// This needs to be done only once per map.
// It is required to use a map in SQL query.
auto result = client.get_sql()
.execute(R"(
CREATE OR REPLACE MAPPING integers
TYPE IMap
OPTIONS (
'keyFormat' = 'int',
'valueFormat' = 'int'
)
)")
.get();
}

std::shared_ptr<hazelcast::client::sql::sql_result>
select_numbers(hazelcast::client::hazelcast_client& client)
{
using namespace hazelcast::client::sql;

sql_statement statement(client, "SELECT * FROM integers");

// Set cursor buffer size to 5
// So there will be 20 pages(100 / 5 = 20)
statement.cursor_buffer_size(5);

return client.get_sql().execute(statement).get();
}

void
seperator(const std::string& text = std::string{})
{
std::string output(60, '=');
boost::replace_first(output, std::string(text.size(), '='), text);

output = std::string(20, '=') + output;

std::cout << output << std::endl;
}

void
for_loop(hazelcast_client& client)
{
seperator("for_loop() - BEGIN");

auto result = select_numbers(client);

for (auto it = result->pbegin(); it != result->pend(); ++it) {
seperator();
for (const auto& row : it->rows()) {
std::cout << *row.get_object<int>(0);
}

std::cout << std::endl;
}

seperator("for_loop() - END");
seperator();
}

void
algorithm_copy(hazelcast_client& client)
{
using hazelcast::client::sql::sql_page;

seperator("algorithm_copy() - BEGIN");

auto result = select_numbers(client);

std::vector<std::shared_ptr<sql_page>> pages;

copy(result->pbegin(), result->pend(), back_inserter(pages));

std::vector<int> numbers;

for (const auto& page : pages) {
transform(
begin(page->rows()),
end(page->rows()),
back_inserter(numbers),
[](const sql_page::sql_row& row) { return *row.get_object<int>(0); });
}

sort(begin(numbers), end(numbers));
copy(begin(numbers),
end(numbers),
std::ostream_iterator<int>(std::cout, "\n"));

seperator("algorithm_copy - END");
}

void
algorithm_filter(hazelcast_client& client)
{
using hazelcast::client::sql::sql_page;

seperator("algorithm_filter - BEGIN");

auto result = select_numbers(client);

std::vector<std::shared_ptr<sql_page>> pages;

copy_if(result->pbegin(),
result->pend(),
back_inserter(pages),
[](const std::shared_ptr<sql_page>& p) {
// Filter out the pages which contains a number which is
// divisable by 20
return any_of(begin(p->rows()),
end(p->rows()),
[](const sql_page::sql_row& row) {
return *row.get_object<int>(0) % 20 == 0;
});
});

for (const auto& page : pages) {
for (const sql_page::sql_row& row : page->rows()) {
std::cout << row.get_object<int>(0) << " ";
}

std::cout << std::endl;
}

seperator("algorithm_filter - END");
}

void
timeout(hazelcast_client& client)
{
seperator("timeout - BEGIN");

// `generate_stream(1)` generates a row per seconds, so it will guaranteed
// that it will timeout
auto result =
client.get_sql().execute("SELECT * FROM TABLE(generate_stream(1))").get();

auto it = result->pbegin(std::chrono::milliseconds{ 1 });

try {
++it;
++it;
} catch (hazelcast::client::exception::no_such_element& e) {
std::cout << "Timedout" << std::endl;
}

seperator("timeout - END");
}

0 comments on commit cdd9fee

Please sign in to comment.