Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

row_iterator_sync have been implemented. [API-1762] #1161

Merged
merged 7 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 timed out.
`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)
214 changes: 214 additions & 0 deletions examples/sql/sql_row_iterator_sync.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@

/*
* 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;
akeles85 marked this conversation as resolved.
Show resolved Hide resolved
} 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