Skip to content

Commit 27cc1e6

Browse files
authored
page_iterator_sync is added. [API-1761] (#1051)
1 parent 6269351 commit 27cc1e6

File tree

6 files changed

+621
-49
lines changed

6 files changed

+621
-49
lines changed

Reference_Manual.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@
146146
* [7.11.5 Read Row Metadata](#7115-read-row-metadata)
147147
* [7.11.6 Read Table Column Value](#7116-read-table-column-value)
148148
* [7.11.7 Error Handling](#7117-error-handling)
149+
* [7.11.8 Iterators](#7118-iterators)
150+
* [7.11.8.1 page_iterator](#71181-page_iterator)
151+
* [7.11.8.2 page_iterator_sync](#71182-page_iterator)
149152
* [8. Development and Testing](#8-development-and-testing)
150153
* [8.1. Testing](#81-testing)
151154
* [9. Getting Help](#9-getting-help)
@@ -3680,13 +3683,64 @@ Exceptions which can be thrown by SQL API are stated at below:
36803683
- `illegal_access` is thrown if page fetch operation is already progress. To prevent this wait for the `boost::future<sql_page>` which belongs to previous `next()` call.
36813684
- `sql_result::iterator()` can throw two types of exceptions.
36823685
- `illegal_state` is thrown if it is not an `SELECT` query or `sql_result::iterator()` is requested more than once.
3686+
- `sql_result::iterator()` throws `hazelcast_sql_exception` if it sql_result is already closed.
3687+
- `sql_result::pbegin()` calls `sql_result::iterator()` method internally, so it throws same exceptions.
36833688
- `sql_result::row_metadata()` can throw `illegal_state` exception if the result contains only update count.
36843689
- `sql_page::sql_row::get_object(int)` can throw `index_out_of_bounds` exception if the index is out of range.
36853690
- `sql_page::sql_row::get_object(std::string)` can throw `illegal_argument` exception if the column doesn't exist.
3691+
- `sql_result::page_iterator_sync::operator++()` can throw `no_such_element` exception if the fetch operation is timed out.
3692+
- `sql_result::page_iterator_sync::operator*()` can throw `no_such_element` exception if the iterator points to past-end element;
36863693

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

3697+
### 7.11.8 Iterators
3698+
There are several iterators in SQL Api to satisfy different use cases. They are listed below:
3699+
3700+
- `page_iterator` (Async page iterator)
3701+
- `page_iterator_sync` (Sync page iterator)
3702+
3703+
Reminder 1, only one type of iterator can be used. So it is suggested to choose appropriate one to fetch `SELECT` query results.
3704+
3705+
Reminder 2, it is not possible to iterate from beginning to end more than once.
3706+
3707+
Reminder 3, it is not possible to acquire iterators more than once, so consecutive calls to `sql_result::iterator()`, `sql_result::pbegin(std::chrono::milliseconds timeout)`, `sql_result::begin(std::chrono::milliseconds timeout)` methods will throw `illegal_state` exception.
3708+
3709+
#### 7.11.8.1 page_iterator
3710+
`page_iterator` is the basic and the most essential one. It supports fetching pages asynchronously by `next()` method which returns `boost::future<sql_page>`.
3711+
Therefore, users are able to fetch `SELECT` query results page by page. It is acquired by calling `sql_result::iterator()` method.
3712+
3713+
``` C++
3714+
auto result = sql.execute("SELECT * FROM integers").get();
3715+
3716+
for (auto itr = result->iterator(); itr.has_next();) {
3717+
auto page = itr.next().get();
3718+
3719+
std::cout << "There are " << page->row_count() << " rows the page."
3720+
<< std::endl;
3721+
3722+
for (auto const& row : page->rows()) {
3723+
std::cout << "(" << row.get_object<int>(0) << ")" << std::endl;
3724+
}
3725+
}
3726+
```
3727+
3728+
#### 7.11.8.2 page_iterator_sync
3729+
`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.
3730+
It is similar to native C++ iterators. So it can be used with `algorithm` header.
3731+
It supports `operator*()`, `operator->()`, `operator++()` operators. Post increment operator is marked as `delete`.
3732+
`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 timed out.
3733+
`page_iterator_sync` is copyable but it is there only for convenience. Copy is shallow copy so copied instances should not be used simultaneously.
3734+
This iterator is acquired by `sql_result::pbegin(std::chrono::milliseconds timeout)` and `sql_result::pend()`. `timeout` is defaulted to `std::chrono::milliseconds{ -1 }` which means wait forever.
3735+
3736+
``` C++
3737+
auto result = client.get_sql().execute("SELECT * FROM integers").get();
3738+
3739+
std::vector<std::shared_ptr<sql_page>> pages;
3740+
3741+
copy(result->pbegin(), result->pend(), back_inserter(pages));
3742+
```
3743+
36903744
# 8. Development and Testing
36913745
36923746
Hazelcast C++ client is developed using C++. If you want to help with bug fixes, develop new features or

examples/sql/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ add_executable(sql_query_with_portable sql_query_with_portable.cpp)
1818
add_executable(sql_cancellation_example sql_cancellation_example.cpp)
1919
add_executable(sql_json_example sql_json_example.cpp)
2020
add_executable(sql_order_by_limit_offset sql_order_by_limit_offset.cpp)
21+
add_executable(sql_page_iterator_sync sql_page_iterator_sync.cpp)
2122
add_executable(sql_advanced_query_options sql_advanced_query_options.cpp)
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
2+
/*
3+
* Copyright (c) 2008-2023, Hazelcast, Inc. All Rights Reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#include <algorithm>
18+
#include <iterator>
19+
20+
#include <boost/algorithm/string.hpp>
21+
22+
#include <hazelcast/client/hazelcast_client.h>
23+
24+
using hazelcast::client::hazelcast_client;
25+
26+
void
27+
populate_map(hazelcast_client&);
28+
void
29+
create_mapping(hazelcast_client&);
30+
void
31+
for_loop(hazelcast_client&);
32+
void
33+
algorithm_copy(hazelcast_client&);
34+
void
35+
algorithm_filter(hazelcast_client&);
36+
void
37+
timeout(hazelcast_client&);
38+
39+
/**
40+
* Normally, cpp-client provides an async api for every features.
41+
* But for sake of convenience and similar usages with native C++ iterators
42+
* cpp-client provides sync page iterator. So `page_iterator_sync` is a blocking
43+
* iterator. It wraps the `page_iterator` and allows to be used in sync
44+
* manner. This example demonstrates how to use `page_iterator_sync` and what
45+
* the use cases are.
46+
*/
47+
int
48+
main()
49+
{
50+
auto hz = hazelcast::new_client().get();
51+
52+
// Preparation
53+
populate_map(hz);
54+
create_mapping(hz);
55+
56+
// Use cases, examples
57+
for_loop(hz);
58+
algorithm_copy(hz);
59+
algorithm_filter(hz);
60+
timeout(hz);
61+
62+
return 0;
63+
}
64+
65+
void
66+
populate_map(hazelcast_client& client)
67+
{
68+
// Populate a map before using it in sql.
69+
auto map = client.get_map("integers").get();
70+
71+
for (int i = 0; i < 100; ++i) {
72+
map->put(i, i).get();
73+
}
74+
}
75+
76+
void
77+
create_mapping(hazelcast_client& client)
78+
{
79+
// Create mapping for the integers.
80+
// This needs to be done only once per map.
81+
// It is required to use a map in SQL query.
82+
auto result = client.get_sql()
83+
.execute(R"(
84+
CREATE OR REPLACE MAPPING integers
85+
TYPE IMap
86+
OPTIONS (
87+
'keyFormat' = 'int',
88+
'valueFormat' = 'int'
89+
)
90+
)")
91+
.get();
92+
}
93+
94+
std::shared_ptr<hazelcast::client::sql::sql_result>
95+
select_numbers(hazelcast::client::hazelcast_client& client)
96+
{
97+
using namespace hazelcast::client::sql;
98+
99+
sql_statement statement(client, "SELECT * FROM integers");
100+
101+
// Set cursor buffer size to 5
102+
// So there will be 20 pages(100 / 5 = 20)
103+
statement.cursor_buffer_size(5);
104+
105+
return client.get_sql().execute(statement).get();
106+
}
107+
108+
void
109+
seperator(const std::string& text = std::string{})
110+
{
111+
std::string output(60, '=');
112+
boost::replace_first(output, std::string(text.size(), '='), text);
113+
114+
output = std::string(20, '=') + output;
115+
116+
std::cout << output << std::endl;
117+
}
118+
119+
void
120+
for_loop(hazelcast_client& client)
121+
{
122+
seperator("for_loop() - BEGIN");
123+
124+
auto result = select_numbers(client);
125+
126+
for (auto it = result->pbegin(); it != result->pend(); ++it) {
127+
seperator();
128+
for (const auto& row : it->rows()) {
129+
std::cout << *row.get_object<int>(0);
130+
}
131+
132+
std::cout << std::endl;
133+
}
134+
135+
seperator("for_loop() - END");
136+
seperator();
137+
}
138+
139+
void
140+
algorithm_copy(hazelcast_client& client)
141+
{
142+
using hazelcast::client::sql::sql_page;
143+
144+
seperator("algorithm_copy() - BEGIN");
145+
146+
auto result = select_numbers(client);
147+
148+
std::vector<std::shared_ptr<sql_page>> pages;
149+
150+
copy(result->pbegin(), result->pend(), back_inserter(pages));
151+
152+
std::vector<int> numbers;
153+
154+
for (const auto& page : pages) {
155+
transform(
156+
begin(page->rows()),
157+
end(page->rows()),
158+
back_inserter(numbers),
159+
[](const sql_page::sql_row& row) { return *row.get_object<int>(0); });
160+
}
161+
162+
sort(begin(numbers), end(numbers));
163+
copy(begin(numbers),
164+
end(numbers),
165+
std::ostream_iterator<int>(std::cout, "\n"));
166+
167+
seperator("algorithm_copy - END");
168+
}
169+
170+
void
171+
algorithm_filter(hazelcast_client& client)
172+
{
173+
using hazelcast::client::sql::sql_page;
174+
175+
seperator("algorithm_filter - BEGIN");
176+
177+
auto result = select_numbers(client);
178+
179+
std::vector<std::shared_ptr<sql_page>> pages;
180+
181+
copy_if(result->pbegin(),
182+
result->pend(),
183+
back_inserter(pages),
184+
[](const std::shared_ptr<sql_page>& p) {
185+
// Filter out the pages which contains a number which is
186+
// divisable by 20
187+
return any_of(begin(p->rows()),
188+
end(p->rows()),
189+
[](const sql_page::sql_row& row) {
190+
return *row.get_object<int>(0) % 20 == 0;
191+
});
192+
});
193+
194+
for (const auto& page : pages) {
195+
for (const sql_page::sql_row& row : page->rows()) {
196+
std::cout << row.get_object<int>(0) << " ";
197+
}
198+
199+
std::cout << std::endl;
200+
}
201+
202+
seperator("algorithm_filter - END");
203+
}
204+
205+
void
206+
timeout(hazelcast_client& client)
207+
{
208+
seperator("timeout - BEGIN");
209+
210+
// `generate_stream(1)` generates a row per seconds, so it will guaranteed
211+
// that it will timeout
212+
auto result =
213+
client.get_sql().execute("SELECT * FROM TABLE(generate_stream(1))").get();
214+
215+
auto it = result->pbegin(std::chrono::milliseconds{ 1 });
216+
217+
try {
218+
++it;
219+
++it;
220+
} catch (hazelcast::client::exception::no_such_element& e) {
221+
std::cout << "Timedout" << std::endl;
222+
}
223+
224+
seperator("timeout - END");
225+
}

0 commit comments

Comments
 (0)