Skip to content

Commit

Permalink
row_iterator_sync is added.
Browse files Browse the repository at this point in the history
  • Loading branch information
OzanCansel committed Mar 21, 2023
1 parent 27cc1e6 commit f357382
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 2 deletions.
34 changes: 34 additions & 0 deletions Reference_Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
* [7.11.8 Iterators](#7118-iterators)
* [7.11.8.1 page_iterator](#71181-page_iterator)
* [7.11.8.2 page_iterator_sync](#71182-page_iterator)
* [7.11.8.3 row_iterator_sync](#71182-row_iterator)
* [8. Development and Testing](#8-development-and-testing)
* [8.1. Testing](#81-testing)
* [9. Getting Help](#9-getting-help)
Expand Down Expand Up @@ -3690,6 +3691,8 @@ Exceptions which can be thrown by SQL API are stated at below:
- `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 timed out.
- `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 timed out.
- `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.
Expand All @@ -3699,6 +3702,7 @@ There are several iterators in SQL Api to satisfy different use cases. They are

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

Reminder 1, only one type of iterator can be used. So it is suggested to choose appropriate one to fetch `SELECT` query results.

Expand Down Expand Up @@ -3741,6 +3745,36 @@ std::vector<std::shared_ptr<sql_page>> pages;
copy(result->pbegin(), result->pend(), back_inserter(pages));
```
#### 7.11.8.3 row_iterator_sync
`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. `timeout` is defaulted to `std::chrono::milliseconds{ -1 }` which means wait forever.
`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); });
```
# 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
1 change: 1 addition & 0 deletions examples/sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ 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)
217 changes: 217 additions & 0 deletions examples/sql/sql_row_iterator_sync.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@

/*
* 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 row iterator. So `row_iterator_sync` is a blocking
* iterator. It wraps the `page_iterator_sync`.
* This example demonstrates how to use `row_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 (const auto& row : *result) {
std::cout << row.get_object<int>(0);
}

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<int> numbers;

transform(
begin(*result),
end(*result),
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->begin(std::chrono::milliseconds{ 1 });

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

seperator("timeout - END");
}
67 changes: 67 additions & 0 deletions hazelcast/include/hazelcast/client/sql/sql_result.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,70 @@ class HAZELCAST_API sql_result : public std::enable_shared_from_this<sql_result>
std::chrono::milliseconds timeout_;
};

/**
* Copy is allowed for convenience but it does shallow copy so it should be avoided.
*/
class HAZELCAST_API row_iterator_sync
{
public:
using difference_type = void;
using value_type = sql_page::sql_row;
using pointer = sql_page::sql_row*;
using reference = const sql_page::sql_row&;
using iterator_category = std::input_iterator_tag;

/**
* Sets timeout for page fetch operation.
*/
void set_timeout(std::chrono::milliseconds);

/**
* Retrieves the timeout
*/
std::chrono::milliseconds timeout() const;

friend HAZELCAST_API bool operator==(const row_iterator_sync&,
const row_iterator_sync&);
friend HAZELCAST_API bool operator!=(const row_iterator_sync&,
const row_iterator_sync&);

/**
* Returns current row. It might block in case of the current page is empty.
*
* @throws exception::no_such_element if the iterator points to the past-end
*/
const sql_page::sql_row& operator*() const;


/**
* Returns current row. It might block in case of the current page is empty.
*
* @throws exception::no_such_element if the iterator points to the past-end
*/
const sql_page::sql_row* operator->() const;

/**
* Post increment operator is deleted because copy is discouraged.
*/
row_iterator_sync operator++(int) = delete;

/**
* Fetches next row in blocking manner. If page is already fetched, it doesn't block, otherwise it blocks.
*
* @throws exception::no_such_element if the iterator points to the past-end or operation is timedout.
*/
row_iterator_sync& operator++();

private:

friend class sql_result;
row_iterator_sync(page_iterator_sync&&);
row_iterator_sync() = default;

mutable page_iterator_sync iterator_;
std::size_t row_idx_;
};

/**
* The destructor closes the result if it were open.
*/
Expand Down Expand Up @@ -236,6 +300,9 @@ class HAZELCAST_API sql_result : public std::enable_shared_from_this<sql_result>
page_iterator_sync pbegin(
std::chrono::milliseconds timeout = std::chrono::milliseconds{ -1 });
page_iterator_sync pend();
row_iterator_sync begin(
std::chrono::milliseconds timeout = std::chrono::milliseconds{ -1 });
row_iterator_sync end();

private:
friend class sql_service;
Expand Down

0 comments on commit f357382

Please sign in to comment.