You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2022/12/13 10:33:54 UTC
[ignite-3] branch main updated: IGNITE-17588 SQL API for C++ Client (#1440)
This is an automated email from the ASF dual-hosted git repository.
isapego pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new e09b7fbee5 IGNITE-17588 SQL API for C++ Client (#1440)
e09b7fbee5 is described below
commit e09b7fbee56ce19bf5d3b004d6038a9ef40ba752
Author: Igor Sapego <is...@apache.org>
AuthorDate: Tue Dec 13 13:33:48 2022 +0300
IGNITE-17588 SQL API for C++ Client (#1440)
---
.../ignite/internal/client/proto/ClientOp.java | 2 +-
.../requests/sql/ClientSqlExecuteRequest.java | 9 +-
.../internal/client/sql/ClientColumnMetadata.java | 6 +
modules/platforms/cpp/ignite/client/CMakeLists.txt | 5 +
.../cpp/ignite/client/detail/client_operation.h | 9 +
.../ignite/client/detail/cluster_connection.cpp | 2 +-
.../cpp/ignite/client/detail/cluster_connection.h | 60 +++-
.../cpp/ignite/client/detail/ignite_client_impl.h | 14 +-
.../cpp/ignite/client/detail/node_connection.cpp | 5 +-
.../cpp/ignite/client/detail/node_connection.h | 42 ++-
.../cpp/ignite/client/detail/response_handler.h | 102 ++++++-
.../cpp/ignite/client/detail/sql/result_set_impl.h | 338 +++++++++++++++++++++
.../cpp/ignite/client/detail/sql/sql_impl.cpp | 95 ++++++
.../cpp/ignite/client/detail/sql/sql_impl.h | 71 +++++
.../cpp/ignite/client/detail/table/table_impl.cpp | 59 +---
.../platforms/cpp/ignite/client/detail/utils.cpp | 251 +++++++++++++++
modules/platforms/cpp/ignite/client/detail/utils.h | 74 +++++
.../platforms/cpp/ignite/client/ignite_client.cpp | 4 +
.../platforms/cpp/ignite/client/ignite_client.h | 28 +-
modules/platforms/cpp/ignite/client/primitive.h | 184 +++++++++++
.../cpp/ignite/client/sql/column_metadata.h | 125 ++++++++
.../cpp/ignite/client/sql/column_origin.h | 76 +++++
.../platforms/cpp/ignite/client/sql/result_set.cpp | 59 ++++
.../platforms/cpp/ignite/client/sql/result_set.h | 134 ++++++++
.../cpp/ignite/client/sql/result_set_metadata.h | 78 +++++
.../client/sql/sql.cpp} | 30 +-
modules/platforms/cpp/ignite/client/sql/sql.h | 83 +++++
.../cpp/ignite/client/sql/sql_column_type.h | 87 ++++++
.../cpp/ignite/client/sql/sql_statement.h | 152 +++++++++
.../cpp/ignite/client/table/ignite_tuple.h | 29 +-
.../platforms/cpp/ignite/client/table/tables.cpp | 6 +-
modules/platforms/cpp/ignite/client/table/tables.h | 7 -
modules/platforms/cpp/ignite/protocol/reader.cpp | 11 +-
modules/platforms/cpp/ignite/protocol/reader.h | 49 ++-
modules/platforms/cpp/ignite/protocol/utils.cpp | 63 +++-
modules/platforms/cpp/ignite/protocol/utils.h | 48 +++
.../cpp/ignite/schema/binary_tuple_builder.h | 6 +-
modules/platforms/cpp/ignite/schema/tuple_test.cpp | 2 +-
.../platforms/cpp/tests/client-test/CMakeLists.txt | 1 +
.../cpp/tests/client-test/ignite_client_test.cpp | 2 +-
.../cpp/tests/client-test/ignite_runner_suite.h | 1 +
.../tests/client-test/record_binary_view_test.cpp | 4 +-
.../platforms/cpp/tests/client-test/sql_test.cpp | 323 ++++++++++++++++++++
.../cpp/tests/client-test/tables_test.cpp | 10 +-
.../dotnet/Apache.Ignite.Tests/FakeServer.cs | 3 +
.../dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs | 5 +
46 files changed, 2568 insertions(+), 186 deletions(-)
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java
index 35f01e0cd2..29159cb9c3 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientOp.java
@@ -138,6 +138,6 @@ public class ClientOp {
/** Close cursor. */
public static final int SQL_CURSOR_CLOSE = 52;
- /** Close cursor. */
+ /** Get partition assignment. */
public static final int PARTITION_ASSIGNMENT_GET = 53;
}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
index f193d4928a..cab1b9f98e 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
@@ -45,6 +45,7 @@ import org.apache.ignite.sql.Session.SessionBuilder;
import org.apache.ignite.sql.Statement;
import org.apache.ignite.sql.Statement.StatementBuilder;
import org.apache.ignite.sql.async.AsyncResultSet;
+import org.jetbrains.annotations.Nullable;
/**
* Client SQL execute request.
@@ -160,7 +161,7 @@ public class ClientSqlExecuteRequest {
return sessionBuilder.build();
}
- private static void packMeta(ClientMessagePacker out, ResultSetMetadata meta) {
+ private static void packMeta(ClientMessagePacker out, @Nullable ResultSetMetadata meta) {
// TODO IGNITE-17179 metadata caching - avoid sending same meta over and over.
if (meta == null || meta.columns() == null) {
out.packArrayHeader(0);
@@ -178,6 +179,10 @@ public class ClientSqlExecuteRequest {
for (int i = 0; i < cols.size(); i++) {
ColumnMetadata col = cols.get(i);
+ ColumnOrigin origin = col.origin();
+
+ int fieldsNum = origin == null ? 6 : 9;
+ out.packArrayHeader(fieldsNum);
out.packString(col.name());
out.packBoolean(col.nullable());
@@ -185,8 +190,6 @@ public class ClientSqlExecuteRequest {
out.packInt(col.scale());
out.packInt(col.precision());
- ColumnOrigin origin = col.origin();
-
if (origin == null) {
out.packBoolean(false);
continue;
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java
index c2bd33de42..e70bcd5984 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java
@@ -52,6 +52,10 @@ public class ClientColumnMetadata implements ColumnMetadata {
* @param prevColumns Previous columns.
*/
public ClientColumnMetadata(ClientMessageUnpacker unpacker, List<ColumnMetadata> prevColumns) {
+ var propCnt = unpacker.unpackArrayHeader();
+
+ assert propCnt >= 6;
+
name = unpacker.unpackString();
nullable = unpacker.unpackBoolean();
type = ClientSqlColumnTypeConverter.ordinalToColumnType(unpacker.unpackInt());
@@ -59,6 +63,8 @@ public class ClientColumnMetadata implements ColumnMetadata {
precision = unpacker.unpackInt();
if (unpacker.unpackBoolean()) {
+ assert propCnt >= 9;
+
origin = new ClientColumnOrigin(unpacker, name, prevColumns);
} else {
origin = null;
diff --git a/modules/platforms/cpp/ignite/client/CMakeLists.txt b/modules/platforms/cpp/ignite/client/CMakeLists.txt
index 23147130c2..091873c64a 100644
--- a/modules/platforms/cpp/ignite/client/CMakeLists.txt
+++ b/modules/platforms/cpp/ignite/client/CMakeLists.txt
@@ -21,11 +21,15 @@ set(TARGET ${PROJECT_NAME})
set(SOURCES
ignite_client.cpp
+ sql/sql.cpp
+ sql/result_set.cpp
table/record_view.cpp
table/table.cpp
table/tables.cpp
detail/cluster_connection.cpp
+ detail/utils.cpp
detail/node_connection.cpp
+ detail/sql/sql_impl.cpp
detail/table/table_impl.cpp
detail/table/tables_impl.cpp
)
@@ -34,6 +38,7 @@ set(PUBLIC_HEADERS
ignite_client.h
ignite_client_configuration.h
ignite_logger.h
+ sql/sql.h
table/ignite_tuple.h
table/record_view.h
table/table.h
diff --git a/modules/platforms/cpp/ignite/client/detail/client_operation.h b/modules/platforms/cpp/ignite/client/detail/client_operation.h
index fa72a4019d..030cb1dedc 100644
--- a/modules/platforms/cpp/ignite/client/detail/client_operation.h
+++ b/modules/platforms/cpp/ignite/client/detail/client_operation.h
@@ -76,6 +76,15 @@ enum class client_operation {
/** Get and delete tuple. */
TUPLE_GET_AND_DELETE = 32,
+
+ /** Execute SQL query. */
+ SQL_EXEC = 50,
+
+ /** Get next page. */
+ SQL_CURSOR_NEXT_PAGE = 51,
+
+ /** Close cursor. */
+ SQL_CURSOR_CLOSE = 52,
};
/**
diff --git a/modules/platforms/cpp/ignite/client/detail/cluster_connection.cpp b/modules/platforms/cpp/ignite/client/detail/cluster_connection.cpp
index 531c1cc7dd..2229b2935e 100644
--- a/modules/platforms/cpp/ignite/client/detail/cluster_connection.cpp
+++ b/modules/platforms/cpp/ignite/client/detail/cluster_connection.cpp
@@ -75,7 +75,7 @@ void cluster_connection::on_connection_success(const network::end_point &addr, u
m_logger->log_info("Established connection with remote host " + addr.to_string());
m_logger->log_debug("Connection ID: " + std::to_string(id));
- auto connection = std::make_shared<node_connection>(id, m_pool, m_logger);
+ auto connection = node_connection::make_new(id, m_pool, m_logger);
{
[[maybe_unused]] std::unique_lock<std::recursive_mutex> lock(m_connections_mutex);
diff --git a/modules/platforms/cpp/ignite/client/detail/cluster_connection.h b/modules/platforms/cpp/ignite/client/detail/cluster_connection.h
index 315fed5a5c..3f143e372d 100644
--- a/modules/platforms/cpp/ignite/client/detail/cluster_connection.h
+++ b/modules/platforms/cpp/ignite/client/detail/cluster_connection.h
@@ -89,19 +89,18 @@ public:
void stop();
/**
- * Perform request.
+ * Perform request raw.
*
* @tparam T Result type.
* @param op Operation code.
* @param wr Request writer function.
- * @param rd Response reader function.
- * @param callback Callback to call on result.
+ * @param handler Request handler.
+ * @return Channel used for the request.
*/
template<typename T>
- void perform_request(client_operation op, const std::function<void(protocol::writer &)> &wr,
- std::function<T(protocol::reader &)> rd, ignite_callback<T> callback) {
- auto handler = std::make_shared<response_handler_impl<T>>(std::move(rd), std::move(callback));
-
+ void perform_request_handler(client_operation op,
+ const std::function<void(protocol::writer &)> &wr, const std::shared_ptr<response_handler>& handler)
+ {
while (true) {
auto channel = get_random_channel();
if (!channel)
@@ -113,6 +112,42 @@ public:
}
}
+ /**
+ * Perform request raw.
+ *
+ * @tparam T Result type.
+ * @param op Operation code.
+ * @param wr Request writer function.
+ * @param rd Response reader function.
+ * @param callback Callback to call on result.
+ * @return Channel used for the request.
+ */
+ template<typename T>
+ void perform_request_raw(client_operation op, const std::function<void(protocol::writer &)> &wr,
+ std::function<T(std::shared_ptr<node_connection>, bytes_view)> rd, ignite_callback<T> callback)
+ {
+ auto handler = std::make_shared<response_handler_bytes<T>>(std::move(rd), std::move(callback));
+ perform_request_handler<T>(op, wr, std::move(handler));
+ }
+
+ /**
+ * Perform request.
+ *
+ * @tparam T Result type.
+ * @param op Operation code.
+ * @param wr Request writer function.
+ * @param rd Response reader function.
+ * @param callback Callback to call on result.
+ * @return Channel used for the request.
+ */
+ template<typename T>
+ void perform_request(client_operation op, const std::function<void(protocol::writer &)> &wr,
+ std::function<T(protocol::reader &)> rd, ignite_callback<T> callback)
+ {
+ auto handler = std::make_shared<response_handler_reader<T>>(std::move(rd), std::move(callback));
+ perform_request_handler<T>(op, wr, std::move(handler));
+ }
+
/**
* Perform request without input data.
*
@@ -120,11 +155,12 @@ public:
* @param op Operation code.
* @param rd Response reader function.
* @param callback Callback to call on result.
+ * @return Channel used for the request.
*/
template<typename T>
- void perform_request_rd(client_operation op, std::function<T(protocol::reader &)> rd, ignite_callback<T> callback) {
- perform_request<T>(
- op, [](protocol::writer &) {}, std::move(rd), std::move(callback));
+ void perform_request_rd(
+ client_operation op, std::function<T(protocol::reader &)> rd, ignite_callback<T> callback) {
+ perform_request<T>(op, [](protocol::writer &) {}, std::move(rd), std::move(callback));
}
/**
@@ -134,12 +170,12 @@ public:
* @param op Operation code.
* @param wr Request writer function.
* @param callback Callback to call on result.
+ * @return Channel used for the request.
*/
template<typename T>
void perform_request_wr(
client_operation op, const std::function<void(protocol::writer &)> &wr, ignite_callback<T> callback) {
- perform_request<T>(
- op, wr, [](protocol::reader &) {}, std::move(callback));
+ perform_request<T>(op, wr, [](protocol::reader &) {}, std::move(callback));
}
private:
diff --git a/modules/platforms/cpp/ignite/client/detail/ignite_client_impl.h b/modules/platforms/cpp/ignite/client/detail/ignite_client_impl.h
index ac0a0ce999..69323db1f0 100644
--- a/modules/platforms/cpp/ignite/client/detail/ignite_client_impl.h
+++ b/modules/platforms/cpp/ignite/client/detail/ignite_client_impl.h
@@ -19,6 +19,7 @@
#include <ignite/client/detail/cluster_connection.h>
#include <ignite/client/detail/table/tables_impl.h>
+#include <ignite/client/detail/sql/sql_impl.h>
#include <ignite/client/ignite_client_configuration.h>
#include <ignite/common/ignite_result.h>
@@ -48,7 +49,8 @@ public:
explicit ignite_client_impl(ignite_client_configuration configuration)
: m_configuration(std::move(configuration))
, m_connection(cluster_connection::create(m_configuration))
- , m_tables(std::make_shared<tables_impl>(m_connection)) {}
+ , m_tables(std::make_shared<tables_impl>(m_connection))
+ , m_sql(std::make_shared<sql_impl>(m_connection)) {}
/**
* Destructor.
@@ -82,6 +84,13 @@ public:
*/
[[nodiscard]] std::shared_ptr<tables_impl> get_tables_impl() const { return m_tables; }
+ /**
+ * Get SQL management API implementation.
+ *
+ * @return SQL management API implementation.
+ */
+ [[nodiscard]] std::shared_ptr<sql_impl> get_sql_impl() const { return m_sql; }
+
private:
/** Configuration. */
const ignite_client_configuration m_configuration;
@@ -91,6 +100,9 @@ private:
/** Tables. */
std::shared_ptr<tables_impl> m_tables;
+
+ /** SQL. */
+ std::shared_ptr<sql_impl> m_sql;
};
} // namespace ignite::detail
diff --git a/modules/platforms/cpp/ignite/client/detail/node_connection.cpp b/modules/platforms/cpp/ignite/client/detail/node_connection.cpp
index e6a5e14fd5..b814de709a 100644
--- a/modules/platforms/cpp/ignite/client/detail/node_connection.cpp
+++ b/modules/platforms/cpp/ignite/client/detail/node_connection.cpp
@@ -82,7 +82,6 @@ void node_connection::process_message(bytes_view msg) {
(void) flags; // Flags are unused for now.
auto handler = get_and_remove_handler(reqId);
-
if (!handler) {
m_logger->log_error("Missing handler for request with id=" + std::to_string(reqId));
return;
@@ -98,7 +97,9 @@ void node_connection::process_message(bytes_view msg) {
return;
}
- auto handlingRes = handler->handle(reader);
+ auto pos = reader.position();
+ bytes_view data{msg.data() + pos, msg.size() - pos};
+ auto handlingRes = handler->handle(shared_from_this(), data);
if (handlingRes.has_error())
m_logger->log_error("Uncaught user callback exception: " + handlingRes.error().what_str());
}
diff --git a/modules/platforms/cpp/ignite/client/detail/node_connection.h b/modules/platforms/cpp/ignite/client/detail/node_connection.h
index bf5ca04fd1..12d81e4d1e 100644
--- a/modules/platforms/cpp/ignite/client/detail/node_connection.h
+++ b/modules/platforms/cpp/ignite/client/detail/node_connection.h
@@ -42,7 +42,7 @@ class cluster_connection;
*
* Considered established while there is connection to at least one server.
*/
-class node_connection {
+class node_connection : public std::enable_shared_from_this<node_connection> {
friend class cluster_connection;
public:
@@ -59,14 +59,17 @@ public:
~node_connection();
/**
- * Constructor.
+ * Makes new instance.
*
* @param id Connection ID.
* @param pool Connection pool.
* @param logger Logger.
+ * @return New instance.
*/
- node_connection(
- uint64_t id, std::shared_ptr<network::async_client_pool> pool, std::shared_ptr<ignite_logger> logger);
+ static std::shared_ptr<node_connection> make_new(
+ uint64_t id, std::shared_ptr<network::async_client_pool> pool, std::shared_ptr<ignite_logger> logger) {
+ return std::shared_ptr<node_connection>(new node_connection(id, std::move(pool), std::move(logger)));
+ }
/**
* Get connection ID.
@@ -91,9 +94,8 @@ public:
* @param handler Response handler.
* @return @c true on success and @c false otherwise.
*/
- template<typename T>
bool perform_request(client_operation op, const std::function<void(protocol::writer &)> &wr,
- std::shared_ptr<response_handler_impl<T>> handler) {
+ std::shared_ptr<response_handler> handler) {
auto reqId = generate_request_id();
std::vector<std::byte> message;
{
@@ -121,6 +123,24 @@ public:
return true;
}
+ /**
+ * Perform request.
+ *
+ * @tparam T Result type.
+ * @param op Operation code.
+ * @param wr Request writer function.
+ * @param rd Response reader function.
+ * @param callback Callback to call on result.
+ * @return Channel used for the request.
+ */
+ template<typename T>
+ bool perform_request(client_operation op, const std::function<void(protocol::writer &)> &wr,
+ std::function<T(protocol::reader &)> rd, ignite_callback<T> callback)
+ {
+ auto handler = std::make_shared<response_handler_reader<T>>(std::move(rd), std::move(callback));
+ return perform_request(op, wr, std::move(handler));
+ }
+
/**
* Perform handshake.
*
@@ -150,6 +170,16 @@ public:
const protocol_context& get_protocol_context() const { return m_protocol_context; }
private:
+ /**
+ * Constructor.
+ *
+ * @param id Connection ID.
+ * @param pool Connection pool.
+ * @param logger Logger.
+ */
+ node_connection(
+ uint64_t id, std::shared_ptr<network::async_client_pool> pool, std::shared_ptr<ignite_logger> logger);
+
/**
* Generate next request ID.
*
diff --git a/modules/platforms/cpp/ignite/client/detail/response_handler.h b/modules/platforms/cpp/ignite/client/detail/response_handler.h
index 63f297147f..cd79c93bbf 100644
--- a/modules/platforms/cpp/ignite/client/detail/response_handler.h
+++ b/modules/platforms/cpp/ignite/client/detail/response_handler.h
@@ -17,9 +17,10 @@
#pragma once
-#include <ignite/common/ignite_error.h>
-#include <ignite/common/ignite_result.h>
-#include <ignite/protocol/reader.h>
+#include "ignite/client/detail/node_connection.h"
+#include "ignite/common/ignite_error.h"
+#include "ignite/common/ignite_result.h"
+#include "ignite/protocol/reader.h"
#include <functional>
#include <future>
@@ -28,6 +29,8 @@
namespace ignite::detail {
+class node_connection;
+
/**
* Response handler.
*/
@@ -46,7 +49,7 @@ public:
/**
* Handle response.
*/
- [[nodiscard]] virtual ignite_result<void> handle(protocol::reader &) = 0;
+ [[nodiscard]] virtual ignite_result<void> handle(std::shared_ptr<node_connection>, bytes_view) = 0;
/**
* Set error.
@@ -55,34 +58,107 @@ public:
};
/**
- * Response handler implementation for specific type.
+ * Response handler implementation for bytes.
+ */
+template<typename T>
+class response_handler_bytes final : public response_handler {
+public:
+ // Default
+ response_handler_bytes() = default;
+
+ /**
+ * Constructor.
+ *
+ * @param read_func Read function.
+ * @param callback Callback.
+ */
+ explicit response_handler_bytes(
+ std::function<T(std::shared_ptr<node_connection>, bytes_view)> read_func, ignite_callback<T> callback)
+ : m_read_func(std::move(read_func))
+ , m_callback(std::move(callback))
+ , m_mutex() {}
+
+ /**
+ * Handle response.
+ *
+ * @param channel Channel.
+ * @param msg Message.
+ */
+ [[nodiscard]] ignite_result<void> handle(std::shared_ptr<node_connection> channel, bytes_view msg) final {
+ ignite_callback<T> callback = remove_callback();
+ if (!callback)
+ return {};
+
+ auto res = result_of_operation<T>([&]() { return m_read_func(std::move(channel), msg); });
+ return result_of_operation<void>([&]() { callback(std::move(res)); });
+ }
+
+ /**
+ * Set error.
+ *
+ * @param err Error to set.
+ */
+ [[nodiscard]] ignite_result<void> set_error(ignite_error err) final {
+ ignite_callback<T> callback = remove_callback();
+ if (!callback)
+ return {};
+
+ return result_of_operation<void>([&]() { callback({std::move(err)}); });
+ }
+
+private:
+ /**
+ * Remove callback and return it.
+ *
+ * @return Callback.
+ */
+ ignite_callback<T> remove_callback() {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ ignite_callback<T> callback = {};
+ std::swap(callback, m_callback);
+ return callback;
+ }
+
+ /** Read function. */
+ std::function<T(std::shared_ptr<node_connection>, bytes_view)> m_read_func;
+
+ /** Promise. */
+ ignite_callback<T> m_callback;
+
+ /** Callback mutex. */
+ std::mutex m_mutex;
+};
+
+/**
+ * Response handler implementation for reader.
*/
template<typename T>
-class response_handler_impl final : public response_handler {
+class response_handler_reader final : public response_handler {
public:
// Default
- response_handler_impl() = default;
+ response_handler_reader() = default;
/**
* Constructor.
*
- * @param func Function.
+ * @param read_func Read function.
*/
- explicit response_handler_impl(std::function<T(protocol::reader &)> readFunc, ignite_callback<T> callback)
- : m_read_func(std::move(readFunc))
+ explicit response_handler_reader(std::function<T(protocol::reader &)> read_func, ignite_callback<T> callback)
+ : m_read_func(std::move(read_func))
, m_callback(std::move(callback))
, m_mutex() {}
/**
* Handle response.
*
- * @param reader Reader to be used to read response.
+ * @param msg Message.
*/
- [[nodiscard]] ignite_result<void> handle(protocol::reader &reader) final {
+ [[nodiscard]] ignite_result<void> handle(std::shared_ptr<node_connection>, bytes_view msg) final {
ignite_callback<T> callback = remove_callback();
if (!callback)
return {};
+ protocol::reader reader(msg);
auto res = result_of_operation<T>([&]() { return m_read_func(reader); });
return result_of_operation<void>([&]() { callback(std::move(res)); });
}
@@ -114,7 +190,7 @@ private:
}
/** Read function. */
- std::function<T(protocol::reader &)> m_read_func;
+ std::function<T(protocol::reader&)> m_read_func;
/** Promise. */
ignite_callback<T> m_callback;
diff --git a/modules/platforms/cpp/ignite/client/detail/sql/result_set_impl.h b/modules/platforms/cpp/ignite/client/detail/sql/result_set_impl.h
new file mode 100644
index 0000000000..1462127e0c
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/detail/sql/result_set_impl.h
@@ -0,0 +1,338 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/sql/result_set_metadata.h"
+#include "ignite/client/table/ignite_tuple.h"
+#include "ignite/client/detail/node_connection.h"
+#include "ignite/client/detail/utils.h"
+#include "ignite/schema/binary_tuple_parser.h"
+
+#include <cstdint>
+
+namespace ignite::detail {
+
+/**
+ * Query result set.
+ */
+class result_set_impl : public std::enable_shared_from_this<result_set_impl> {
+public:
+ // Default
+ result_set_impl() = default;
+
+ /**
+ * Constructor.
+ *
+ * @param connection Node connection.
+ * @param data Row set data.
+ */
+ result_set_impl(std::shared_ptr<node_connection> connection, bytes_view data)
+ : m_connection(std::move(connection)) {
+ protocol::reader reader(data);
+
+ m_resource_id = reader.read_object_nullable<std::int64_t>();
+ m_has_rowset = reader.read_bool();
+ m_has_more_pages = reader.read_bool();
+ m_was_applied = reader.read_bool();
+ m_affected_rows = reader.read_int64();
+
+ if (m_has_rowset) {
+ auto columns = read_meta(reader);
+ m_meta = std::move(result_set_metadata(columns));
+ m_page = read_page(reader, m_meta);
+ }
+ }
+
+ /**
+ * Destructor.
+ */
+ ~result_set_impl() {
+ close_async([](auto){});
+ }
+
+ /**
+ * Gets metadata.
+ *
+ * @return Metadata.
+ */
+ [[nodiscard]] const result_set_metadata& metadata() const {
+ return m_meta;
+ }
+
+ /**
+ * Gets a value indicating whether this result set contains a collection of rows.
+ *
+ * @return A value indicating whether this result set contains a collection of rows.
+ */
+ [[nodiscard]] bool has_rowset() const {
+ return m_has_rowset;
+ }
+
+ /**
+ * Gets the number of rows affected by the DML statement execution (such as "INSERT", "UPDATE", etc.), or 0 if
+ * the statement returns nothing (such as "ALTER TABLE", etc), or -1 if not applicable.
+ *
+ * @return The number of rows affected by the DML statement execution.
+ */
+ [[nodiscard]] std::int64_t affected_rows() const {
+ return m_affected_rows;
+ }
+
+ /**
+ * Gets a value indicating whether a conditional query (such as "CREATE TABLE IF NOT EXISTS") was applied
+ * successfully.
+ *
+ * @return A value indicating whether a conditional query was applied successfully.
+ */
+ [[nodiscard]] bool was_applied() const {
+ return m_was_applied;
+ }
+
+ /**
+ * Close result set asynchronously.
+ *
+ * @param callback Callback to call on completion.
+ * @return @c true if the request was sent, and false if the result set was already closed.
+ */
+ bool close_async(std::function<void(ignite_result<void>)> callback) {
+ if (!m_resource_id)
+ return false;
+
+ auto writer_func = [id = m_resource_id.value()](protocol::writer &writer) {
+ writer.write(id);
+ };
+
+ auto reader_func = [weak_self = weak_from_this()](protocol::reader &reader) {
+ auto self = weak_self.lock();
+ if (!self)
+ return;
+
+ self->m_resource_id = std::nullopt;
+ };
+
+ return m_connection->perform_request<void>(
+ client_operation::SQL_CURSOR_CLOSE, writer_func, std::move(reader_func), std::move(callback));
+ }
+
+ /**
+ * Close result set synchronously.
+ *
+ * @return @c true if the request was sent, and false if the result set was already closed.
+ */
+ bool close() {
+ auto pr = std::make_shared<std::promise<void>>();
+ bool res = close_async([pr](auto) mutable {
+ pr->set_value();
+ });
+
+ if (!res)
+ return res;
+
+ pr->get_future().get();
+ return true;
+ }
+
+ /**
+ * Get current page size.
+ *
+ * @return Current page size.
+ */
+ [[nodiscard]] std::vector<ignite_tuple> current_page() {
+ require_result_set();
+
+ auto ret = std::move(m_page);
+ m_page.clear();
+
+ return ret;
+ }
+
+ /**
+ * Checks whether there are more pages of results.
+ *
+ * @return @c true if there are more pages with results and @c false otherwise.
+ */
+ [[nodiscard]] IGNITE_API bool has_more_pages() {
+ return m_resource_id.has_value() && m_has_more_pages;
+ }
+
+ /**
+ * Fetch the next page of results asynchronously.
+ * The current page is changed after the operation is complete.
+ *
+ * @param callback Callback to call on completion.
+ */
+ IGNITE_API void fetch_next_page_async(std::function<void(ignite_result<void>)> callback) {
+ require_result_set();
+
+ if (!m_resource_id)
+ throw ignite_error("Query cursor is closed");
+
+ if (!m_has_more_pages)
+ throw ignite_error("There are no more pages");
+
+ auto writer_func = [id = m_resource_id.value()](protocol::writer &writer) {
+ writer.write(id);
+ };
+
+ auto reader_func = [weak_self = weak_from_this()](protocol::reader &reader) {
+ auto self = weak_self.lock();
+ if (!self)
+ return;
+
+ self->m_page = read_page(reader, self->m_meta);
+ self->m_has_more_pages = reader.read_bool();
+ };
+
+ m_connection->perform_request<void>(
+ client_operation::SQL_CURSOR_NEXT_PAGE, writer_func, std::move(reader_func), std::move(callback));
+ }
+
+private:
+ /**
+ * Checks that query has result set and throws error if it has not.
+ */
+ void require_result_set() const {
+ if (!m_has_rowset)
+ throw ignite_error("Query does not produce result set");
+ }
+
+ /**
+ * Reads result set metadata.
+ *
+ * @param reader Reader.
+ * @return Result set meta coumns.
+ */
+ static std::vector<column_metadata> read_meta(protocol::reader& reader) {
+ auto size = reader.read_array_size();
+
+ std::vector<column_metadata> columns;
+ columns.reserve(size);
+
+ reader.read_array_raw([&columns] (std::uint32_t idx, const msgpack_object &obj) {
+ if (obj.type != MSGPACK_OBJECT_ARRAY)
+ throw ignite_error("Meta column expected to be serialized as array");
+
+ const msgpack_object_array &arr = obj.via.array;
+
+ constexpr std::uint32_t minCount = 6;
+ assert(arr.size >= minCount);
+
+ auto name = protocol::unpack_object<std::string>(arr.ptr[0]);
+ auto nullable = protocol::unpack_object<bool>(arr.ptr[1]);
+ auto typ = column_type(protocol::unpack_object<std::int32_t>(arr.ptr[2]));
+ auto scale = protocol::unpack_object<std::int32_t>(arr.ptr[3]);
+ auto precision = protocol::unpack_object<std::int32_t>(arr.ptr[4]);
+
+ bool origin_present = protocol::unpack_object<bool>(arr.ptr[5]);
+
+ if (!origin_present) {
+ columns.emplace_back(std::move(name), typ, precision, scale, nullable, column_origin{});
+ return;
+ }
+
+ assert(arr.size >= minCount + 3);
+ auto origin_name = arr.ptr[6].type == MSGPACK_OBJECT_NIL
+ ? name
+ : protocol::unpack_object<std::string>(arr.ptr[6]);
+
+ auto origin_schema_id = protocol::try_unpack_object<std::int32_t>(arr.ptr[7]);
+ std::string origin_schema;
+ if (origin_schema_id) {
+ if (*origin_schema_id >= columns.size()) {
+ throw ignite_error("Origin schema ID is too large: " + std::to_string(*origin_schema_id) +
+ ", id=" + std::to_string(idx));
+ }
+ origin_schema = columns[*origin_schema_id].origin().schema_name();
+ } else {
+ origin_schema = protocol::unpack_object<std::string>(arr.ptr[7]);
+ }
+
+ auto origin_table_id = protocol::try_unpack_object<std::int32_t>(arr.ptr[8]);
+ std::string origin_table;
+ if (origin_table_id) {
+ if (*origin_table_id >= columns.size()) {
+ throw ignite_error("Origin table ID is too large: " + std::to_string(*origin_table_id) +
+ ", id=" + std::to_string(idx));
+ }
+ origin_table = columns[*origin_table_id].origin().table_name();
+ } else {
+ origin_table = protocol::unpack_object<std::string>(arr.ptr[8]);
+ }
+
+ column_origin origin{std::move(origin_name), std::move(origin_table), std::move(origin_schema)};
+ columns.emplace_back(std::move(name), typ, precision, scale, nullable, std::move(origin));
+ });
+
+ return columns;
+ }
+
+ /**
+ * Read page.
+ *
+ * @param reader Reader to use.
+ * @return Page.
+ */
+ static std::vector<ignite_tuple> read_page(protocol::reader &reader, const result_set_metadata &meta) {
+ auto size = reader.read_array_size();
+
+ std::vector<ignite_tuple> page;
+ page.reserve(size);
+
+ reader.read_array_raw([&columns = meta.columns(), &page] (std::uint32_t idx, const msgpack_object &obj) {
+ auto tuple_data = protocol::unpack_binary(obj);
+
+ auto columns_cnt = columns.size();
+ ignite_tuple res(columns_cnt);
+ binary_tuple_parser parser(std::int32_t(columns_cnt), tuple_data);
+
+ for (size_t i = 0; i < columns_cnt; ++i) {
+ auto &column = columns[i];
+ res.set(column.name(), read_next_column(parser, column.type()));
+ }
+ page.emplace_back(std::move(res));
+ });
+
+ return page;
+ }
+
+ /** Result set metadata. */
+ result_set_metadata m_meta;
+
+ /** Has row set. */
+ bool m_has_rowset{false};
+
+ /** Affected rows. */
+ std::int64_t m_affected_rows{-1};
+
+ /** Statement was applied. */
+ bool m_was_applied{false};
+
+ /** Connection. */
+ std::shared_ptr<node_connection> m_connection;
+
+ /** Resource ID. */
+ std::optional<std::int64_t> m_resource_id;
+
+ /** Has more pages. */
+ bool m_has_more_pages{false};
+
+ /** Current page. */
+ std::vector<ignite_tuple> m_page;
+};
+
+} // namespace ignite::detail
diff --git a/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.cpp b/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.cpp
new file mode 100644
index 0000000000..655c2157d4
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.cpp
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 "ignite/client/detail/sql/result_set_impl.h"
+#include "ignite/client/detail/sql/sql_impl.h"
+#include "ignite/client/detail/utils.h"
+
+#include "ignite/schema/binary_tuple_builder.h"
+
+namespace ignite::detail {
+
+void sql_impl::execute_async(
+ transaction *tx, const sql_statement &statement, std::vector<primitive>&& args, ignite_callback<result_set>&& callback)
+{
+ transactions_not_implemented(tx);
+
+ auto writer_func = [&statement, &args](protocol::writer &writer) {
+ writer.write_nil(); // TODO: IGNITE-17604 Implement transactions
+ writer.write(statement.schema());
+ writer.write(statement.page_size());
+ writer.write(std::int64_t(statement.timeout().count()));
+ writer.write_nil(); // Session timeout (unused, session is closed by the server immediately).
+
+ const auto& properties = statement.properties();
+ auto props_num = std::int32_t(properties.size());
+
+ writer.write(props_num);
+
+ binary_tuple_builder prop_builder{props_num * 4};
+
+ prop_builder.start();
+ for (const auto &property : properties) {
+ prop_builder.claim_string(property.first);
+ claim_primitive_with_type(prop_builder, property.second);
+ }
+
+ prop_builder.layout();
+ for (const auto &property : properties) {
+ prop_builder.append_string(property.first);
+ append_primitive_with_type(prop_builder, property.second);
+ }
+
+ auto prop_data = prop_builder.build();
+ writer.write_binary(prop_data);
+
+ writer.write(statement.query());
+
+ if (args.empty()) {
+ writer.write_nil();
+ return;
+ }
+
+ auto args_num = std::int32_t(args.size());
+
+ writer.write(args_num);
+
+ binary_tuple_builder args_builder{args_num * 3};
+
+ args_builder.start();
+ for (const auto &arg : args) {
+ claim_primitive_with_type(args_builder, arg);
+ }
+
+ args_builder.layout();
+ for (const auto &arg : args) {
+ append_primitive_with_type(args_builder, arg);
+ }
+
+ auto args_data = args_builder.build();
+ writer.write_binary(args_data);
+ };
+
+ auto reader_func = [](std::shared_ptr<node_connection> channel, bytes_view msg) -> result_set {
+ return result_set{std::make_shared<result_set_impl>(std::move(channel), msg)};
+ };
+
+ m_connection->perform_request_raw<result_set>(
+ client_operation::SQL_EXEC, writer_func, std::move(reader_func), std::move(callback));
+}
+
+} // namespace ignite::detail
diff --git a/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.h b/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.h
new file mode 100644
index 0000000000..7692bbdc25
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.h
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/detail/cluster_connection.h"
+#include "ignite/client/transaction/transaction.h"
+#include "ignite/client/sql/sql_statement.h"
+#include "ignite/client/sql/result_set.h"
+#include "ignite/client/primitive.h"
+
+#include <memory>
+#include <utility>
+
+namespace ignite::detail {
+
+/**
+ * Ignite SQL query facade.
+ */
+class sql_impl {
+public:
+ // Default
+ ~sql_impl() = default;
+ sql_impl(sql_impl &&) noexcept = default;
+ sql_impl &operator=(sql_impl &&) noexcept = default;
+
+ // Deleted
+ sql_impl() = delete;
+ sql_impl(const sql_impl &) = delete;
+ sql_impl &operator=(const sql_impl &) = delete;
+
+ /**
+ * Constructor.
+ *
+ * @param connection Connection.
+ */
+ explicit sql_impl(std::shared_ptr<cluster_connection> connection)
+ : m_connection(std::move(connection)) {}
+
+ /**
+ * Executes single SQL statement and returns rows.
+ *
+ * @param tx Optional transaction. If nullptr implicit transaction for this
+ * single operation is used.
+ * @param statement Statement to execute.
+ * @param args Arguments for the statement.
+ * @param callback A callback called on operation completion with SQL result set.
+ */
+ void execute_async(
+ transaction *tx, const sql_statement& statement, std::vector<primitive>&& args, ignite_callback<result_set>&& callback);
+
+private:
+ /** Cluster connection. */
+ std::shared_ptr<cluster_connection> m_connection;
+};
+
+} // namespace ignite::detail
diff --git a/modules/platforms/cpp/ignite/client/detail/table/table_impl.cpp b/modules/platforms/cpp/ignite/client/detail/table/table_impl.cpp
index 0e7f434555..ac3fef247b 100644
--- a/modules/platforms/cpp/ignite/client/detail/table/table_impl.cpp
+++ b/modules/platforms/cpp/ignite/client/detail/table/table_impl.cpp
@@ -16,6 +16,7 @@
*/
#include "ignite/client/detail/table/table_impl.h"
+#include "ignite/client/detail/utils.h"
#include "ignite/common/bits.h"
#include "ignite/common/ignite_error.h"
@@ -59,10 +60,10 @@ void claim_column(binary_tuple_builder &builder, ignite_type typ, std::int32_t i
builder.claim_uuid(tuple.get<uuid>(index));
break;
case ignite_type::STRING:
- builder.claim(SizeT(tuple.get<const std::string &>(index).size()));
+ builder.claim(SizeT(tuple.get<std::string>(index).size()));
break;
case ignite_type::BINARY:
- builder.claim(SizeT(tuple.get<const std::vector<std::byte> &>(index).size()));
+ builder.claim(SizeT(tuple.get<std::vector<std::byte>>(index).size()));
break;
default:
// TODO: IGNITE-18035 Support other types
@@ -102,13 +103,13 @@ void append_column(binary_tuple_builder &builder, ignite_type typ, std::int32_t
builder.append_uuid(tuple.get<uuid>(index));
break;
case ignite_type::STRING: {
- const auto &str = tuple.get<const std::string &>(index);
+ const auto &str = tuple.get<std::string>(index);
bytes_view view{reinterpret_cast<const std::byte *>(str.data()), str.size()};
builder.append(typ, view);
break;
}
case ignite_type::BINARY:
- builder.append(typ, tuple.get<const std::vector<std::byte> &>(index));
+ builder.append(typ, tuple.get<std::vector<std::byte>>(index));
break;
default:
// TODO: IGNITE-18035 Support other types
@@ -116,56 +117,6 @@ void append_column(binary_tuple_builder &builder, ignite_type typ, std::int32_t
}
}
-/**
- * Read column value from binary tuple.
- *
- * @param parser Binary tuple parser.
- * @param typ Column type.
- * @return Column value.
- */
-std::any read_next_column(binary_tuple_parser &parser, ignite_type typ) {
- auto val_opt = parser.get_next();
- if (!val_opt)
- return {};
-
- auto val = val_opt.value();
-
- switch (typ) {
- case ignite_type::INT8:
- return binary_tuple_parser::get_int8(val);
- case ignite_type::INT16:
- return binary_tuple_parser::get_int16(val);
- case ignite_type::INT32:
- return binary_tuple_parser::get_int32(val);
- case ignite_type::INT64:
- return binary_tuple_parser::get_int64(val);
- case ignite_type::FLOAT:
- return binary_tuple_parser::get_float(val);
- case ignite_type::DOUBLE:
- return binary_tuple_parser::get_double(val);
- case ignite_type::UUID:
- return binary_tuple_parser::get_uuid(val);
- case ignite_type::STRING:
- return std::string(reinterpret_cast<const char *>(val.data()), val.size());
- case ignite_type::BINARY:
- return std::vector<std::byte>(val);
- default:
- // TODO: IGNITE-18035 Support other types
- throw ignite_error("Type with id " + std::to_string(int(typ)) + " is not yet supported");
- }
-}
-
-/**
- * Check transaction and throw an exception if it is not nullptr.
- *
- * @param tx Transaction.
- */
-void transactions_not_implemented(transaction *tx) {
- // TODO: IGNITE-17604 Implement transactions
- if (tx)
- throw ignite_error("Transactions are not implemented");
-}
-
/**
* Serialize tuple using table schema.
*
diff --git a/modules/platforms/cpp/ignite/client/detail/utils.cpp b/modules/platforms/cpp/ignite/client/detail/utils.cpp
new file mode 100644
index 0000000000..d647c679dd
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/detail/utils.cpp
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 "ignite/client/detail/utils.h"
+#include "ignite/common/uuid.h"
+
+#include <string>
+
+namespace ignite::detail {
+
+/**
+ * Claim type and scale header for a value written in binary tuple.
+ *
+ * @param builder Tuple builder.
+ * @param typ Type.
+ * @param scale Scale.
+ */
+void claim_type_and_scale(binary_tuple_builder &builder, ignite_type typ, std::int32_t scale = 0) {
+ builder.claim_int32(std::int32_t(typ));
+ builder.claim_int32(scale);
+}
+
+/**
+ * Append type and scale header for a value written in binary tuple.
+ *
+ * @param builder Tuple builder.
+ * @param typ Type.
+ * @param scale Scale.
+ */
+void append_type_and_scale(binary_tuple_builder &builder, ignite_type typ, std::int32_t scale = 0) {
+ builder.append_int32(std::int32_t(typ));
+ builder.append_int32(scale);
+}
+
+void claim_primitive_with_type(binary_tuple_builder &builder, const primitive &value) {
+ switch (value.get_type()) {
+ case column_type::BOOLEAN: {
+ claim_type_and_scale(builder, ignite_type::INT8);
+ builder.claim_int8(1);
+ break;
+ }
+ case column_type::INT8: {
+ claim_type_and_scale(builder, ignite_type::INT8);
+ builder.claim_int8(value.get<std::int8_t>());
+ break;
+ }
+ case column_type::INT16: {
+ claim_type_and_scale(builder, ignite_type::INT16);
+ builder.claim_int16(value.get<std::int16_t>());
+ break;
+ }
+ case column_type::INT32: {
+ claim_type_and_scale(builder, ignite_type::INT32);
+ builder.claim_int32(value.get<std::int32_t>());
+ break;
+ }
+ case column_type::INT64: {
+ claim_type_and_scale(builder, ignite_type::INT64);
+ builder.claim_int64(value.get<std::int64_t>());
+ break;
+ }
+ case column_type::FLOAT: {
+ claim_type_and_scale(builder, ignite_type::FLOAT);
+ builder.claim_float(value.get<float>());
+ break;
+ }
+ case column_type::DOUBLE: {
+ claim_type_and_scale(builder, ignite_type::DOUBLE);
+ builder.claim_double(value.get<double>());
+ break;
+ }
+ case column_type::UUID: {
+ claim_type_and_scale(builder, ignite_type::UUID);
+ builder.claim_uuid(value.get<uuid>());
+ break;
+ }
+ case column_type::STRING: {
+ claim_type_and_scale(builder, ignite_type::STRING);
+ builder.claim_string(value.get<std::string>());
+ break;
+ }
+ case column_type::BYTE_ARRAY: {
+ claim_type_and_scale(builder, ignite_type::BINARY);
+ auto &data = value.get<std::vector<std::byte>>();
+ builder.claim(ignite_type::BINARY, data);
+ break;
+ }
+
+ case column_type::DECIMAL:
+ case column_type::DATE:
+ case column_type::TIME:
+ case column_type::DATETIME:
+ case column_type::TIMESTAMP:
+ case column_type::BITMASK:
+ case column_type::PERIOD:
+ case column_type::DURATION:
+ case column_type::NUMBER:
+ default:
+ throw ignite_error("Unsupported type: " + std::to_string(int(value.get_type())));
+ }
+}
+
+void append_primitive_with_type(binary_tuple_builder &builder, const primitive &value) {
+ switch (value.get_type()) {
+ case column_type::BOOLEAN: {
+ append_type_and_scale(builder, ignite_type::INT8);
+ builder.append_int8(1);
+ break;
+ }
+ case column_type::INT8: {
+ append_type_and_scale(builder, ignite_type::INT8);
+ builder.append_int8(value.get<std::int8_t>());
+ break;
+ }
+ case column_type::INT16: {
+ append_type_and_scale(builder, ignite_type::INT16);
+ builder.append_int16(value.get<std::int16_t>());
+ break;
+ }
+ case column_type::INT32: {
+ append_type_and_scale(builder, ignite_type::INT32);
+ builder.append_int32(value.get<std::int32_t>());
+ break;
+ }
+ case column_type::INT64: {
+ append_type_and_scale(builder, ignite_type::INT64);
+ builder.append_int64(value.get<std::int64_t>());
+ break;
+ }
+ case column_type::FLOAT: {
+ append_type_and_scale(builder, ignite_type::FLOAT);
+ builder.append_float(value.get<float>());
+ break;
+ }
+ case column_type::DOUBLE: {
+ append_type_and_scale(builder, ignite_type::DOUBLE);
+ builder.append_double(value.get<double>());
+ break;
+ }
+ case column_type::UUID: {
+ append_type_and_scale(builder, ignite_type::UUID);
+ builder.append_uuid(value.get<uuid>());
+ break;
+ }
+ case column_type::STRING: {
+ append_type_and_scale(builder, ignite_type::STRING);
+ builder.append_string(value.get<std::string>());
+ break;
+ }
+ case column_type::BYTE_ARRAY: {
+ append_type_and_scale(builder, ignite_type::BINARY);
+ auto &data = value.get<std::vector<std::byte>>();
+ builder.append(ignite_type::BINARY, data);
+ break;
+ }
+
+ case column_type::DECIMAL:
+ case column_type::DATE:
+ case column_type::TIME:
+ case column_type::DATETIME:
+ case column_type::TIMESTAMP:
+ case column_type::BITMASK:
+ case column_type::PERIOD:
+ case column_type::DURATION:
+ case column_type::NUMBER:
+ default:
+ throw ignite_error("Unsupported type: " + std::to_string(int(value.get_type())));
+ }
+}
+
+primitive read_next_column(binary_tuple_parser &parser, ignite_type typ) {
+ auto val_opt = parser.get_next();
+ if (!val_opt)
+ return {};
+
+ auto val = val_opt.value();
+
+ switch (typ) {
+ case ignite_type::INT8:
+ return binary_tuple_parser::get_int8(val);
+ case ignite_type::INT16:
+ return binary_tuple_parser::get_int16(val);
+ case ignite_type::INT32:
+ return binary_tuple_parser::get_int32(val);
+ case ignite_type::INT64:
+ return binary_tuple_parser::get_int64(val);
+ case ignite_type::FLOAT:
+ return binary_tuple_parser::get_float(val);
+ case ignite_type::DOUBLE:
+ return binary_tuple_parser::get_double(val);
+ case ignite_type::UUID:
+ return binary_tuple_parser::get_uuid(val);
+ case ignite_type::STRING:
+ return std::string(reinterpret_cast<const char *>(val.data()), val.size());
+ case ignite_type::BINARY:
+ return std::vector<std::byte>(val);
+ default:
+ // TODO: IGNITE-18035 Support other types
+ throw ignite_error("Type with id " + std::to_string(int(typ)) + " is not yet supported");
+ }
+}
+
+primitive read_next_column(binary_tuple_parser &parser, column_type typ) {
+ auto val_opt = parser.get_next();
+ if (!val_opt)
+ return {};
+
+ auto val = val_opt.value();
+
+ switch (typ) {
+ case column_type::BOOLEAN:
+ return binary_tuple_parser::get_int8(val) != 0;
+ case column_type::INT8:
+ return binary_tuple_parser::get_int8(val);
+ case column_type::INT16:
+ return binary_tuple_parser::get_int16(val);
+ case column_type::INT32:
+ return binary_tuple_parser::get_int32(val);
+ case column_type::INT64:
+ return binary_tuple_parser::get_int64(val);
+ case column_type::FLOAT:
+ return binary_tuple_parser::get_float(val);
+ case column_type::DOUBLE:
+ return binary_tuple_parser::get_double(val);
+ case column_type::UUID:
+ return binary_tuple_parser::get_uuid(val);
+ case column_type::STRING:
+ return std::string(reinterpret_cast<const char *>(val.data()), val.size());
+ case column_type::BYTE_ARRAY:
+ return std::vector<std::byte>(val);
+ default:
+ // TODO: IGNITE-18035 Support other types
+ throw ignite_error("Type with id " + std::to_string(int(typ)) + " is not yet supported");
+ }
+}
+
+} // namespace ignite::detail
diff --git a/modules/platforms/cpp/ignite/client/detail/utils.h b/modules/platforms/cpp/ignite/client/detail/utils.h
new file mode 100644
index 0000000000..e25be803b1
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/detail/utils.h
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/transaction/transaction.h"
+#include "ignite/client/primitive.h"
+#include "ignite/schema/binary_tuple_builder.h"
+#include "ignite/schema/ignite_type.h"
+#include "ignite/schema/binary_tuple_parser.h"
+
+namespace ignite::detail {
+
+/**
+ * Claim space for a value with type header in tuple builder.
+ *
+ * @param builder Tuple builder.
+ * @param value Value.
+ */
+void claim_primitive_with_type(binary_tuple_builder &builder, const primitive &value);
+
+/**
+ * Append a value with type header in tuple builder.
+ *
+ * @param builder Tuple builder.
+ * @param value Value.
+ */
+void append_primitive_with_type(binary_tuple_builder &builder, const primitive &value);
+
+/**
+ * Check transaction and throw an exception if it is not nullptr.
+ *
+ * @param tx Transaction.
+ */
+inline void transactions_not_implemented(transaction *tx) {
+ // TODO: IGNITE-17604 Implement transactions
+ if (tx)
+ throw ignite_error("Transactions are not implemented");
+}
+
+
+/**
+ * Read column value from binary tuple.
+ *
+ * @param parser Binary tuple parser.
+ * @param typ Column type.
+ * @return Column value.
+ */
+[[nodiscard]] primitive read_next_column(binary_tuple_parser &parser, ignite_type typ);
+
+/**
+ * Read column value from binary tuple.
+ *
+ * @param parser Binary tuple parser.
+ * @param typ Column type.
+ * @return Column value.
+ */
+[[nodiscard]] primitive read_next_column(binary_tuple_parser &parser, column_type typ);
+
+} // namespace ignite::detail
diff --git a/modules/platforms/cpp/ignite/client/ignite_client.cpp b/modules/platforms/cpp/ignite/client/ignite_client.cpp
index db44f6be28..649e3119cd 100644
--- a/modules/platforms/cpp/ignite/client/ignite_client.cpp
+++ b/modules/platforms/cpp/ignite/client/ignite_client.cpp
@@ -70,6 +70,10 @@ tables ignite_client::get_tables() const noexcept {
return tables(impl().get_tables_impl());
}
+sql ignite_client::get_sql() const noexcept {
+ return sql(impl().get_sql_impl());
+}
+
detail::ignite_client_impl &ignite_client::impl() noexcept {
return *((detail::ignite_client_impl *) (m_impl.get()));
}
diff --git a/modules/platforms/cpp/ignite/client/ignite_client.h b/modules/platforms/cpp/ignite/client/ignite_client.h
index 0d90668c91..d3e143f914 100644
--- a/modules/platforms/cpp/ignite/client/ignite_client.h
+++ b/modules/platforms/cpp/ignite/client/ignite_client.h
@@ -17,12 +17,14 @@
#pragma once
-#include <ignite/client/ignite_client_configuration.h>
-#include <ignite/client/table/tables.h>
+#include "ignite/client/ignite_client_configuration.h"
+#include "ignite/client/table/tables.h"
+#include "ignite/client/sql/sql.h"
-#include <ignite/common/config.h>
-#include <ignite/common/ignite_result.h>
+#include "ignite/common/config.h"
+#include "ignite/common/ignite_result.h"
+#include <chrono>
#include <functional>
#include <memory>
@@ -50,7 +52,7 @@ public:
ignite_client &operator=(const ignite_client &) = delete;
/**
- * Start client asynchronously.
+ * Starts client asynchronously.
*
* Client tries to establish connection to every endpoint. First endpoint is
* selected randomly. After that round-robin is used to determine the next
@@ -73,7 +75,7 @@ public:
ignite_callback<ignite_client> callback);
/**
- * Start client synchronously.
+ * Starts client synchronously.
*
* @see start_async for details.
*
@@ -81,22 +83,30 @@ public:
* @param timeout Operation timeout.
* @return ignite_client instance.
*/
- IGNITE_API static ignite_client start(ignite_client_configuration configuration, std::chrono::milliseconds timeout);
+ [[nodiscard]] IGNITE_API static ignite_client start(
+ ignite_client_configuration configuration, std::chrono::milliseconds timeout);
/**
- * Get client configuration.
+ * Gets client configuration.
*
* @return Configuration.
*/
[[nodiscard]] IGNITE_API const ignite_client_configuration &configuration() const noexcept;
/**
- * Get the table API.
+ * Gets the table API.
*
* @return Table API.
*/
[[nodiscard]] IGNITE_API tables get_tables() const noexcept;
+ /**
+ * Gets the SQL API.
+ *
+ * @return SQL API.
+ */
+ [[nodiscard]] IGNITE_API sql get_sql() const noexcept;
+
private:
/**
* Constructor
diff --git a/modules/platforms/cpp/ignite/client/primitive.h b/modules/platforms/cpp/ignite/client/primitive.h
new file mode 100644
index 0000000000..b3c0c0d835
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/primitive.h
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/sql/sql_column_type.h"
+#include "ignite/common/ignite_error.h"
+#include "ignite/common/uuid.h"
+
+#include <cstdint>
+#include <variant>
+#include <vector>
+#include <type_traits>
+
+namespace ignite {
+
+/**
+ * Ignite primitive type.
+ */
+class primitive {
+public:
+ // Default
+ primitive() = default;
+
+ /**
+ * Constructor for boolean value.
+ *
+ * @param value Value.
+ */
+ primitive(bool value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for std::int8_t value.
+ *
+ * @param value Value.
+ */
+ primitive(std::int8_t value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for std::int16_t value.
+ *
+ * @param value Value.
+ */
+ primitive(std::int16_t value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for std::int32_t value.
+ *
+ * @param value Value.
+ */
+ primitive(std::int32_t value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for std::int64_t value.
+ *
+ * @param value Value.
+ */
+ primitive(std::int64_t value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for float value.
+ *
+ * @param value Value.
+ */
+ primitive(float value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for double value.
+ *
+ * @param value Value.
+ */
+ primitive(double value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for UUID value.
+ *
+ * @param value Value.
+ */
+ primitive(uuid value) : m_value(value) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for string value.
+ *
+ * @param value Value.
+ */
+ primitive(std::string value) : m_value(std::move(value)) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for byte array value.
+ *
+ * @param value Value.
+ */
+ primitive(std::vector<std::byte> value) : m_value(std::move(value)) {} // NOLINT(google-explicit-constructor)
+
+ /**
+ * Constructor for byte array value.
+ *
+ * @param buf Buffer.
+ * @param len Buffer length.
+ */
+ primitive(std::byte* buf, std::size_t len) : m_value(std::vector<std::byte>(buf, buf + len)) {}
+
+ /**
+ * Get underlying value.
+ *
+ * @tparam T Type of value to try and get.
+ * @return Value of the specified type.
+ * @throw ignite_error if primitive contains value of any other type.
+ */
+ template<typename T>
+ [[nodiscard]] const T& get() const {
+ if constexpr (
+ std::is_same_v<T, bool> ||
+ std::is_same_v<T, std::int8_t> ||
+ std::is_same_v<T, std::int16_t> ||
+ std::is_same_v<T, std::int32_t> ||
+ std::is_same_v<T, std::int64_t> ||
+ std::is_same_v<T, float> ||
+ std::is_same_v<T, double> ||
+ std::is_same_v<T, uuid> ||
+ std::is_same_v<T, std::string> ||
+ std::is_same_v<T, std::vector<std::byte>>) {
+ return std::get<T>(m_value);
+ } else {
+ static_assert(sizeof(T) == 0, "Type is not an Ignite primitive type or is not yet supported");
+ }
+ }
+
+ /**
+ * Get primitive type.
+ *
+ * @return Primitive type.
+ */
+ [[nodiscard]] column_type get_type() const {
+ // TODO: Ensure by tests
+ return static_cast<column_type>(m_value.index());
+ }
+
+private:
+ /** Unsupported type. */
+ typedef void *unsupported_type;
+
+ /** Value type. */
+ typedef std::variant<
+ bool,
+ std::int8_t,
+ std::int16_t,
+ std::int32_t,
+ std::int64_t,
+ float,
+ double,
+ unsupported_type, // Decimal
+ unsupported_type, // Date
+ unsupported_type, // Time
+ unsupported_type, // Datetime
+ unsupported_type, // Timestamp
+ uuid,
+ unsupported_type, // Bitmask
+ std::string,
+ std::vector<std::byte>,
+ unsupported_type, // Period
+ unsupported_type, // Duration
+ unsupported_type // Number
+ > value_type;
+
+ /** Value. */
+ value_type m_value;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/column_metadata.h b/modules/platforms/cpp/ignite/client/sql/column_metadata.h
new file mode 100644
index 0000000000..466468641f
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/column_metadata.h
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/sql/sql_column_type.h"
+#include "ignite/client/sql/column_origin.h"
+
+#include <string>
+#include <cstdint>
+
+namespace ignite {
+
+/**
+ * Column metadata.
+ */
+class column_metadata {
+public:
+ // Default
+ column_metadata() = default;
+
+ /**
+ * Constructor.
+ *
+ * @param name Column name.
+ * @param type Column type.
+ * @param precision Precision.
+ * @param scale Scale.
+ * @param nullable Column nullability.
+ * @param origin Column origin.
+ */
+ column_metadata(std::string name, column_type type, std::int32_t precision, std::int32_t scale,
+ bool nullable, column_origin origin)
+ : m_name(std::move(name))
+ , m_type(type)
+ , m_precision(precision)
+ , m_scale(scale)
+ , m_nullable(nullable)
+ , m_origin(std::move(origin)) { }
+
+ /**
+ * Gets the column name.
+ *
+ * @return Column name.
+ */
+ [[nodiscard]] const std::string &name() const { return m_name; }
+
+ /**
+ * Gets the column type.
+ *
+ * @return Column type.
+ */
+ [[nodiscard]] column_type type() const { return m_type; }
+
+ /**
+ * Gets the column precision, or -1 when not applicable to the current
+ * column type.
+ *
+ * @return Number of decimal digits for exact numeric types; number of
+ * decimal digits in mantissa for approximate numeric types; number of
+ * decimal digits for fractional seconds of datetime types; length in
+ * characters for character types; length in bytes for binary types;
+ * length in bits for bit types; 1 for BOOLEAN; -1 if precision is not
+ * valid for the type.
+ */
+ [[nodiscard]] std::int32_t precision() const { return m_precision; }
+
+ /**
+ * Gets the column scale.
+ *
+ * @return Number of digits of scale.
+ */
+ [[nodiscard]] std::int32_t scale() const { return m_scale; }
+
+ /**
+ * Gets a value indicating whether the column is nullable.
+ *
+ * @return A value indicating whether the column is nullable.
+ */
+ [[nodiscard]] bool nullable() const { return m_nullable; }
+
+ /**
+ * Gets the column origin.
+ *
+ * For example, for "select foo as bar" query, column name will be "bar", but origin name will be "foo".
+ *
+ * @return The column origin.
+ */
+ [[nodiscard]] const column_origin &origin() const { return m_origin; }
+
+private:
+ /** Column name. */
+ std::string m_name;
+
+ /** Column type. */
+ column_type m_type{column_type::UNDEFINED};
+
+ /** Precision. */
+ std::int32_t m_precision{0};
+
+ /** Scale. */
+ std::int32_t m_scale{0};
+
+ /** Nullable. */
+ bool m_nullable{false};
+
+ /** Origin. */
+ column_origin m_origin;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/column_origin.h b/modules/platforms/cpp/ignite/client/sql/column_origin.h
new file mode 100644
index 0000000000..8efb70019e
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/column_origin.h
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace ignite {
+
+/**
+ * SQL column origin.
+ */
+class column_origin {
+public:
+ // Default
+ column_origin() = default;
+
+ /**
+ * Constructor.
+ *
+ * @param column_name Column name.
+ * @param table_name Table name
+ * @param schema_name Schema name.
+ */
+ column_origin(std::string column_name, std::string table_name, std::string schema_name)
+ : m_column_name(std::move(column_name))
+ , m_table_name(std::move(table_name))
+ , m_schema_name(std::move(schema_name)) {}
+
+ /**
+ * Gets the column name.
+ *
+ * @return Column name.
+ */
+ [[nodiscard]] const std::string &column_name() const { return m_column_name; }
+
+ /**
+ * Gets the table name.
+ *
+ * @return Table name.
+ */
+ [[nodiscard]] const std::string &table_name() const { return m_table_name; }
+
+ /**
+ * Gets the schema name.
+ *
+ * @return Schema name.
+ */
+ [[nodiscard]] const std::string &schema_name() const { return m_schema_name; }
+
+private:
+ /** Column name. */
+ std::string m_column_name;
+
+ /** Table name. */
+ std::string m_table_name;
+
+ /** Schema name. */
+ std::string m_schema_name;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/result_set.cpp b/modules/platforms/cpp/ignite/client/sql/result_set.cpp
new file mode 100644
index 0000000000..78513b2250
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/result_set.cpp
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 "ignite/client/sql/result_set.h"
+#include "ignite/client/detail/sql/result_set_impl.h"
+
+namespace ignite {
+
+const result_set_metadata& result_set::metadata() const {
+ return m_impl->metadata();
+}
+
+bool result_set::has_rowset() const {
+ return m_impl->has_rowset();
+}
+
+std::int64_t result_set::affected_rows() const {
+ return m_impl->affected_rows();
+}
+
+bool result_set::was_applied() const {
+ return m_impl->was_applied();
+}
+
+bool result_set::close_async(std::function<void(ignite_result<void>)> callback) {
+ return m_impl->close_async(std::move(callback));
+}
+
+bool result_set::close() {
+ return m_impl->close();
+}
+
+std::vector<ignite_tuple> result_set::current_page() {
+ return m_impl->current_page();
+}
+
+bool result_set::has_more_pages() {
+ return m_impl->has_more_pages();
+}
+
+void result_set::fetch_next_page_async(std::function<void(ignite_result<void>)> callback) {
+ m_impl->fetch_next_page_async(std::move(callback));
+}
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/result_set.h b/modules/platforms/cpp/ignite/client/sql/result_set.h
new file mode 100644
index 0000000000..fe23d520ce
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/result_set.h
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/sql/result_set_metadata.h"
+#include "ignite/client/table/ignite_tuple.h"
+#include "ignite/common/ignite_result.h"
+#include "ignite/common/config.h"
+
+#include <memory>
+#include <functional>
+
+namespace ignite {
+
+namespace detail {
+class result_set_impl;
+}
+
+/**
+ * Query result set.
+ */
+class result_set {
+public:
+ // Default
+ result_set() = default;
+
+ /**
+ * Constructor
+ *
+ * @param impl Implementation
+ */
+ explicit result_set(std::shared_ptr<detail::result_set_impl> impl)
+ : m_impl(std::move(impl)) {}
+
+ /**
+ * Gets metadata.
+ *
+ * @return Metadata.
+ */
+ [[nodiscard]] IGNITE_API const result_set_metadata& metadata() const;
+
+ /**
+ * Gets a value indicating whether this result set contains a collection of rows.
+ *
+ * @return A value indicating whether this result set contains a collection of rows.
+ */
+ [[nodiscard]] IGNITE_API bool has_rowset() const;
+
+ /**
+ * Gets the number of rows affected by the DML statement execution (such as "INSERT", "UPDATE", etc.), or 0 if
+ * the statement returns nothing (such as "ALTER TABLE", etc), or -1 if not applicable.
+ *
+ * @return The number of rows affected by the DML statement execution.
+ */
+ [[nodiscard]] IGNITE_API std::int64_t affected_rows() const;
+
+ /**
+ * Gets a value indicating whether a conditional query (such as "CREATE TABLE IF NOT EXISTS") was applied
+ * successfully.
+ *
+ * @return A value indicating whether a conditional query was applied successfully.
+ */
+ [[nodiscard]] IGNITE_API bool was_applied() const;
+
+ /**
+ * Close result set asynchronously.
+ *
+ * @param callback Callback to call on completion.
+ * @return @c true if the request was sent, and false if the result set was already closed.
+ */
+ IGNITE_API bool close_async(std::function<void(ignite_result<void>)> callback);
+
+ /**
+ * Close result set synchronously.
+ *
+ * @return @c true if the request was sent, and false if the result set was already closed.
+ */
+ IGNITE_API bool close();
+
+ /**
+ * Retrieves current page.
+ * Result set is left empty after this operation and will return empty page on subsequent request
+ * unless there are more available pages and you call @c fetch_next_page().
+ *
+ * @return Current page.
+ */
+ [[nodiscard]] IGNITE_API std::vector<ignite_tuple> current_page();
+
+ /**
+ * Checks whether there are more pages of results.
+ *
+ * @return @c true if there are more pages with results and @c false otherwise.
+ */
+ [[nodiscard]] IGNITE_API bool has_more_pages();
+
+ /**
+ * Fetch the next page of results asynchronously.
+ * The current page is changed after the operation is complete.
+ *
+ * @param callback Callback to call on completion.
+ */
+ IGNITE_API void fetch_next_page_async(std::function<void(ignite_result<void>)> callback);
+
+ /**
+ * Fetch the next page of results synchronously.
+ * The current page is changed after the operation is complete.
+ */
+ IGNITE_API void fetch_next_page() {
+ return sync<void>([this](auto callback) mutable {
+ fetch_next_page_async(std::move(callback));
+ });
+ }
+
+private:
+ /** Implementation. */
+ std::shared_ptr<detail::result_set_impl> m_impl;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/result_set_metadata.h b/modules/platforms/cpp/ignite/client/sql/result_set_metadata.h
new file mode 100644
index 0000000000..aa806c7f29
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/result_set_metadata.h
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/sql/column_metadata.h"
+
+#include <string>
+#include <vector>
+#include <unordered_map>
+
+namespace ignite {
+
+/**
+ * SQL result set metadata.
+ */
+class result_set_metadata {
+public:
+ // Default
+ result_set_metadata() = default;
+
+ /**
+ * Constructor.
+ *
+ * @param columns Columns.
+ */
+ result_set_metadata(std::vector<column_metadata> columns) : m_columns(std::move(columns)) {}
+
+ /**
+ * Gets the columns in the same order as they appear in the result set data.
+ *
+ * @return The columns metadata.
+ */
+ [[nodiscard]] const std::vector<column_metadata>& columns() const { return m_columns; }
+
+ /**
+ * Gets the index of the specified column, or -1 when there is no column
+ * with the specified name.
+ *
+ * @param name The column name.
+ * @return Column index.
+ */
+ [[nodiscard]] std::int32_t index_of(const std::string& name) const {
+ if (m_indices.empty()) {
+ for (size_t i = 0; i < m_columns.size(); ++i) {
+ m_indices[m_columns[i].name()] = i;
+ }
+ }
+
+ auto it = m_indices.find(name);
+ if (it == m_indices.end())
+ return -1;
+ return std::int32_t(it->second);
+ }
+
+private:
+ /** Columns metadata. */
+ std::vector<column_metadata> m_columns;
+
+ /** Indices of the columns corresponding to their names. */
+ mutable std::unordered_map<std::string, size_t> m_indices;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h b/modules/platforms/cpp/ignite/client/sql/sql.cpp
similarity index 60%
copy from modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
copy to modules/platforms/cpp/ignite/client/sql/sql.cpp
index b72ffe2bd5..6ff7cff94d 100644
--- a/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
+++ b/modules/platforms/cpp/ignite/client/sql/sql.cpp
@@ -15,32 +15,14 @@
* limitations under the License.
*/
-#pragma once
-
-#include "gtest_logger.h"
-
-#include <gtest/gtest.h>
-
-#include <memory>
-#include <string_view>
+#include "ignite/client/sql/sql.h"
+#include "ignite/client/detail/sql/sql_impl.h"
namespace ignite {
-using namespace std::string_view_literals;
-
-/**
- * Test suite.
- */
-class ignite_runner_suite : public ::testing::Test {
-protected:
- static constexpr std::initializer_list<std::string_view> NODE_ADDRS = {"127.0.0.1:10942"sv, "127.0.0.1:10943"sv};
-
- /**
- * Get logger.
- *
- * @return Logger for tests.
- */
- static std::shared_ptr<gtest_logger> get_logger() { return std::make_shared<gtest_logger>(false, true); }
-};
+void sql::execute_async(
+ transaction *tx, const sql_statement &statement, std::vector<primitive> args, ignite_callback<result_set> callback) {
+ m_impl->execute_async(tx, statement, std::move(args), std::move(callback));
+}
} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/sql.h b/modules/platforms/cpp/ignite/client/sql/sql.h
new file mode 100644
index 0000000000..f12b87053f
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/sql.h
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/transaction/transaction.h"
+#include "ignite/client/sql/sql_statement.h"
+#include "ignite/client/sql/result_set.h"
+#include "ignite/client/primitive.h"
+#include "ignite/common/config.h"
+#include "ignite/common/ignite_result.h"
+
+#include <memory>
+#include <utility>
+
+namespace ignite {
+
+namespace detail {
+class sql_impl;
+}
+
+/**
+ * Ignite SQL query facade.
+ */
+class sql {
+ friend class ignite_client;
+public:
+ // Default
+ sql() = default;
+
+ /**
+ * Executes single SQL statement asynchronously and returns rows.
+ *
+ * @param tx Optional transaction. If nullptr implicit transaction for this single operation is used.
+ * @param statement Statement to execute.
+ * @param args Arguments for the statement.
+ * @param callback A callback called on operation completion with SQL result set.
+ */
+ IGNITE_API void execute_async(
+ transaction *tx, const sql_statement &statement, std::vector<primitive> args, ignite_callback<result_set> callback);
+
+ /**
+ * Executes single SQL statement and returns rows.
+ *
+ * @param tx Optional transaction. If nullptr implicit transaction for this single operation is used.
+ * @param statement Statement to execute.
+ * @param args Arguments for the statement.
+ * @return SQL result set.
+ */
+ IGNITE_API result_set execute(transaction *tx, const sql_statement &statement, std::vector<primitive> args) {
+ return sync<result_set>([this, tx, &statement, args = std::move(args)](auto callback) mutable {
+ execute_async(tx, statement, std::move(args), std::move(callback));
+ });
+ }
+
+private:
+ /**
+ * Constructor
+ *
+ * @param impl Implementation
+ */
+ explicit sql(std::shared_ptr<detail::sql_impl> impl)
+ : m_impl(std::move(impl)) {}
+
+ /** Implementation. */
+ std::shared_ptr<detail::sql_impl> m_impl;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/sql_column_type.h b/modules/platforms/cpp/ignite/client/sql/sql_column_type.h
new file mode 100644
index 0000000000..74e4ebb3f2
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/sql_column_type.h
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+namespace ignite {
+
+/**
+ * SQL column type.
+ */
+enum class column_type {
+ /** Boolean. */
+ BOOLEAN = 0,
+
+ /** 8-bit signed integer. */
+ INT8 = 1,
+
+ /** 16-bit signed integer. */
+ INT16 = 2,
+
+ /** 32-bit signed integer. */
+ INT32 = 3,
+
+ /** 64-bit signed integer. */
+ INT64 = 4,
+
+ /** 32-bit single-precision floating-point number. */
+ FLOAT = 5,
+
+ /** 64-bit double-precision floating-point number. */
+ DOUBLE = 6,
+
+ /** A decimal floating-point number. */
+ DECIMAL = 7,
+
+ /** Timezone-free date. */
+ DATE = 8,
+
+ /** Timezone-free time with precision. */
+ TIME = 9,
+
+ /** Timezone-free datetime. */
+ DATETIME = 10,
+
+ /** Number of ticks since Jan 1, 1970 00:00:00.000 (with no timezone). Tick unit depends on precision. */
+ TIMESTAMP = 11,
+
+ /** 128-bit UUID. */
+ UUID = 12,
+
+ /** Bit mask. */
+ BITMASK = 13,
+
+ /** String. */
+ STRING = 14,
+
+ /** Binary data. */
+ BYTE_ARRAY = 15,
+
+ /** Date interval. */
+ PERIOD = 16,
+
+ /** Time interval. */
+ DURATION = 17,
+
+ /** Number. */
+ NUMBER = 18,
+
+ /** Undefined. */
+ UNDEFINED
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/sql/sql_statement.h b/modules/platforms/cpp/ignite/client/sql/sql_statement.h
new file mode 100644
index 0000000000..d0ca4360e5
--- /dev/null
+++ b/modules/platforms/cpp/ignite/client/sql/sql_statement.h
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include "ignite/client/primitive.h"
+
+#include <chrono>
+#include <string>
+#include <unordered_map>
+#include <cstdint>
+
+namespace ignite {
+
+/**
+ * Column metadata.
+ */
+class sql_statement {
+public:
+ /** Default SQL schema name. */
+ static constexpr const char* DEFAULT_SCHEMA{"PUBLIC"};
+
+ /** Default number of rows per data page. */
+ static constexpr std::int32_t DEFAULT_PAGE_SIZE{1024};
+
+ /** Default query timeout (zero means no timeout). */
+ static constexpr std::chrono::milliseconds DEFAULT_TIMEOUT{0};
+
+ // Default
+ sql_statement() = default;
+
+ /**
+ * Constructor.
+ *
+ * @param query Query text.
+ * @param timeout Timeout.
+ * @param schema Schema.
+ * @param page_size Page size.
+ * @param properties Properties list.
+ */
+ sql_statement( // NOLINT(google-explicit-constructor)
+ std::string query, std::chrono::milliseconds timeout = DEFAULT_TIMEOUT,
+ std::string schema = DEFAULT_SCHEMA, std::int32_t page_size = DEFAULT_PAGE_SIZE,
+ std::initializer_list<std::pair<const std::string, primitive>> properties = {})
+ : m_query(std::move(query))
+ , m_timeout(timeout)
+ , m_schema(std::move(schema))
+ , m_page_size(page_size)
+ , m_properties(properties) { }
+
+ /**
+ * Gets the query text.
+ *
+ * @return Query text.
+ */
+ [[nodiscard]] const std::string &query() const { return m_query; }
+
+ /**
+ * Sets the query text.
+ *
+ * @param val Query text.
+ */
+ void query(std::string val) { m_query = std::move(val); }
+
+ /**
+ * Gets the query timeout (zero means no timeout).
+ *
+ * @return Query timeout (zero means no timeout).
+ */
+ [[nodiscard]] std::chrono::milliseconds timeout() const { return m_timeout; }
+
+ /**
+ * Sets the query timeout (zero means no timeout).
+ *
+ * @param val Query timeout (zero means no timeout).
+ */
+ void timeout(std::chrono::milliseconds val) { m_timeout = val; }
+
+ /**
+ * Gets the SQL schema name.
+ *
+ * @return Schema name.
+ */
+ [[nodiscard]] const std::string &schema() const { return m_schema; }
+
+ /**
+ * Sets the SQL schema name.
+ *
+ * @param val Schema name.
+ */
+ void schema(std::string val) { m_schema = std::move(val); }
+
+ /**
+ * Gets the number of rows per data page.
+ *
+ * @return Number of rows per data page.
+ */
+ [[nodiscard]] std::int32_t page_size() const { return m_page_size; }
+
+ /**
+ * Sets the number of rows per data page.
+ *
+ * @param val Number of rows per data page.
+ */
+ void page_size(std::int32_t val) { m_page_size = val; }
+
+ /**
+ * Gets the statement properties.
+ *
+ * @return Properties.
+ */
+ [[nodiscard]] const std::unordered_map<std::string, primitive>& properties() const { return m_properties; }
+
+ /**
+ * Sets the statement properties.
+ *
+ * @param val Properties.
+ */
+ void properties(std::initializer_list<std::pair<const std::string, primitive>> val) { m_properties = val; }
+
+private:
+ /** Query text. */
+ std::string m_query;
+
+ /** Timeout. */
+ std::chrono::milliseconds m_timeout{DEFAULT_TIMEOUT};
+
+ /** Schema. */
+ std::string m_schema{DEFAULT_SCHEMA};
+
+ /** Page size. */
+ std::int32_t m_page_size{DEFAULT_PAGE_SIZE};
+
+ /** Properties. */
+ std::unordered_map<std::string, primitive> m_properties;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/table/ignite_tuple.h b/modules/platforms/cpp/ignite/client/table/ignite_tuple.h
index 11cb8bf0cd..a3933163c3 100644
--- a/modules/platforms/cpp/ignite/client/table/ignite_tuple.h
+++ b/modules/platforms/cpp/ignite/client/table/ignite_tuple.h
@@ -17,10 +17,9 @@
#pragma once
-#include "ignite/common/config.h"
+#include "ignite/client/primitive.h"
#include "ignite/common/ignite_error.h"
-#include <any>
#include <initializer_list>
#include <string_view>
#include <unordered_map>
@@ -29,14 +28,10 @@
namespace ignite {
-class ignite_tuple_builder;
-
/**
* Ignite tuple.
*/
class ignite_tuple {
- friend class ignite_tuple_builder;
-
public:
// Default
ignite_tuple() = default;
@@ -53,7 +48,7 @@ public:
*
* @param pairs Pairs.
*/
- ignite_tuple(std::initializer_list<std::pair<std::string, std::any>> pairs)
+ ignite_tuple(std::initializer_list<std::pair<std::string, primitive>> pairs)
: m_pairs(pairs)
, m_indices() {
for (size_t i = 0; i < m_pairs.size(); ++i)
@@ -73,7 +68,7 @@ public:
* @param idx The column index.
* @return Column value.
*/
- [[nodiscard]] const std::any &get(uint32_t idx) const {
+ [[nodiscard]] const primitive &get(uint32_t idx) const {
if (idx > m_pairs.size()) {
throw ignite_error(
"Index is too large: idx=" + std::to_string(idx) + ", columns_num=" + std::to_string(m_pairs.size()));
@@ -90,7 +85,7 @@ public:
*/
template<typename T>
[[nodiscard]] T get(uint32_t idx) const {
- return std::any_cast<T>(get(idx));
+ return get(idx).template get<T>();
}
/**
@@ -115,7 +110,7 @@ public:
* @param name The column name.
* @return Column value.
*/
- [[nodiscard]] const std::any &get(std::string_view name) const {
+ [[nodiscard]] const primitive &get(std::string_view name) const {
auto it = m_indices.find(parse_name(name));
if (it == m_indices.end())
throw ignite_error("Can not find column with the name '" + std::string(name) + "' in the tuple");
@@ -132,7 +127,7 @@ public:
*/
template<typename T>
[[nodiscard]] T get(std::string_view name) const {
- return std::any_cast<T>(get(name));
+ return get(name).template get<T>();
}
/**
@@ -175,13 +170,13 @@ public:
* the column with the given name does not exist.
*
* @param name The column name.
- * @return Column name.
+ * @return Column index.
*/
- [[nodiscard]] int32_t column_ordinal(std::string_view name) const {
+ [[nodiscard]] std::int32_t column_ordinal(std::string_view name) const {
auto it = m_indices.find(parse_name(name));
if (it == m_indices.end())
return -1;
- return int32_t(it->second);
+ return std::int32_t(it->second);
}
private:
@@ -191,7 +186,7 @@ private:
* @param pairs Pairs.
* @param indices Indices.
*/
- ignite_tuple(std::vector<std::pair<std::string, std::any>> &&pairs, std::unordered_map<std::string, size_t> indices)
+ ignite_tuple(std::vector<std::pair<std::string, primitive>> &&pairs, std::unordered_map<std::string, size_t> indices)
: m_pairs(std::move(pairs))
, m_indices(std::move(indices)) {}
@@ -219,10 +214,10 @@ private:
}
/** Pairs of column names and values. */
- std::vector<std::pair<std::string, std::any>> m_pairs;
+ std::vector<std::pair<std::string, primitive>> m_pairs;
/** Indices of the columns corresponding to their names. */
- std::unordered_map<std::string, size_t> m_indices;
+ std::unordered_map<std::string, std::size_t> m_indices;
};
} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/client/table/tables.cpp b/modules/platforms/cpp/ignite/client/table/tables.cpp
index 54f6c6602d..6796c405cc 100644
--- a/modules/platforms/cpp/ignite/client/table/tables.cpp
+++ b/modules/platforms/cpp/ignite/client/table/tables.cpp
@@ -15,13 +15,9 @@
* limitations under the License.
*/
-#include "tables.h"
-
+#include "ignite/client/table/tables.h"
#include "ignite/client/detail/table/tables_impl.h"
-#include <future>
-#include <utility>
-
namespace ignite {
std::optional<table> tables::get_table(std::string_view name) {
diff --git a/modules/platforms/cpp/ignite/client/table/tables.h b/modules/platforms/cpp/ignite/client/table/tables.h
index b220ce4dce..712cd50c9c 100644
--- a/modules/platforms/cpp/ignite/client/table/tables.h
+++ b/modules/platforms/cpp/ignite/client/table/tables.h
@@ -46,13 +46,6 @@ class tables {
public:
// Default
tables() = default;
- ~tables() = default;
- tables(tables &&) = default;
- tables &operator=(tables &&) = default;
-
- // Deleted
- tables(const tables &) = delete;
- tables &operator=(const tables &) = delete;
/**
* Gets a table by name, if it was created before.
diff --git a/modules/platforms/cpp/ignite/protocol/reader.cpp b/modules/platforms/cpp/ignite/protocol/reader.cpp
index 951ef0e53a..24ddcc18c1 100644
--- a/modules/platforms/cpp/ignite/protocol/reader.cpp
+++ b/modules/platforms/cpp/ignite/protocol/reader.cpp
@@ -17,21 +17,14 @@
#include "ignite/protocol/reader.h"
-#include <ignite/common/bytes.h>
#include <ignite/protocol/utils.h>
namespace ignite::protocol {
reader::reader(bytes_view buffer)
: m_buffer(buffer)
- , m_unpacker()
, m_current_val()
, m_move_res(MSGPACK_UNPACK_SUCCESS) {
- // TODO: Research if we can get rid of copying here.
- msgpack_unpacker_init(&m_unpacker, MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
- msgpack_unpacker_reserve_buffer(&m_unpacker, m_buffer.size());
- memcpy(msgpack_unpacker_buffer(&m_unpacker), m_buffer.data(), m_buffer.size());
- msgpack_unpacker_buffer_consumed(&m_unpacker, m_buffer.size());
msgpack_unpacked_init(&m_current_val);
@@ -49,7 +42,9 @@ bool reader::try_read_nil() {
void reader::next() {
check_data_in_stream();
- m_move_res = msgpack_unpacker_next(&m_unpacker, &m_current_val);
+ m_offset = m_offset_next;
+ m_move_res = msgpack_unpack_next(
+ &m_current_val, reinterpret_cast<const char *>(m_buffer.data()), m_buffer.size(), &m_offset_next);
}
} // namespace ignite::protocol
diff --git a/modules/platforms/cpp/ignite/protocol/reader.h b/modules/platforms/cpp/ignite/protocol/reader.h
index f5f44eb001..2b86956ad1 100644
--- a/modules/platforms/cpp/ignite/protocol/reader.h
+++ b/modules/platforms/cpp/ignite/protocol/reader.h
@@ -51,7 +51,7 @@ public:
/**
* Destructor.
*/
- ~reader() { msgpack_unpacker_destroy(&m_unpacker); }
+ ~reader() { msgpack_unpacked_destroy(&m_current_val); }
/**
* Read object of type T from msgpack stream.
@@ -70,6 +70,24 @@ public:
return res;
}
+ /**
+ * Read object of type T from msgpack stream.
+ *
+ * @tparam T Type of the object to read.
+ * @return Object of type T or @c nullopt if there is object of other type in the stream.
+ * @throw ignite_error if there is no data left in the stream.
+ */
+ template<typename T>
+ [[nodiscard]] std::optional<T> try_read_object() {
+ check_data_in_stream();
+
+ auto res = try_unpack_object<T>(m_current_val.data);
+ if (res)
+ next();
+
+ return res;
+ }
+
/**
* Read object of type T from msgpack stream or nil.
*
@@ -115,6 +133,13 @@ public:
*/
[[nodiscard]] std::int32_t read_int32() { return read_object<std::int32_t>(); }
+ /**
+ * Read int32 or nullopt.
+ *
+ * @return Value or nullopt if the next value in stream is not integer.
+ */
+ [[nodiscard]] std::optional<std::int32_t> try_read_int32() { return try_read_object<std::int32_t>(); }
+
/**
* Read int64 number.
*
@@ -211,10 +236,10 @@ public:
*
* @param read_func Object read function.
*/
- void read_array_raw(const std::function<void(const msgpack_object &)> &read_func) {
+ void read_array_raw(const std::function<void(std::uint32_t idx, const msgpack_object &)> &read_func) {
auto size = read_array_size();
for (std::uint32_t i = 0; i < size; ++i) {
- read_func(m_current_val.data.via.array.ptr[i]);
+ read_func(i, m_current_val.data.via.array.ptr[i]);
}
next();
}
@@ -272,6 +297,15 @@ public:
*/
void skip() { next(); }
+ /**
+ * Position.
+ *
+ * @return Current position in memory.
+ */
+ [[nodiscard]] size_t position() const {
+ return m_offset;
+ }
+
private:
/**
* Move to the next value.
@@ -289,14 +323,17 @@ private:
/** Buffer. */
bytes_view m_buffer;
- /** Unpacker. */
- msgpack_unpacker m_unpacker;
-
/** Current value. */
msgpack_unpacked m_current_val;
/** Result of the last move operation. */
msgpack_unpack_return m_move_res;
+
+ /** Offset to next value. */
+ size_t m_offset_next{0};
+
+ /** Offset. */
+ size_t m_offset{0};
};
} // namespace ignite::protocol
diff --git a/modules/platforms/cpp/ignite/protocol/utils.cpp b/modules/platforms/cpp/ignite/protocol/utils.cpp
index 62888dde9f..841e8e8e63 100644
--- a/modules/platforms/cpp/ignite/protocol/utils.cpp
+++ b/modules/platforms/cpp/ignite/protocol/utils.cpp
@@ -28,6 +28,55 @@
namespace ignite::protocol {
+/**
+ * Check if int value fits in @c T.
+ *
+ * @tparam T Int type to fit value to.
+ * @param value Int value.
+ */
+template<typename T>
+inline void check_int_fits(std::int64_t value) {
+ if (value > std::int64_t(std::numeric_limits<T>::max()))
+ throw ignite_error("The number in stream is too large to fit in type: " + std::to_string(value));
+
+ if (value < std::int64_t(std::numeric_limits<T>::min()))
+ throw ignite_error("The number in stream is too small to fit in type: " + std::to_string(value));
+}
+
+template<typename T>
+std::optional<T> try_unpack_int(const msgpack_object &object) {
+ static_assert(
+ std::numeric_limits<T>::is_integer && std::numeric_limits<T>::is_signed, "Type T is not a signed integer type");
+
+ auto i64_val = try_unpack_object<std::int64_t>(object);
+ if (!i64_val)
+ return std::nullopt;
+
+ check_int_fits<T>(*i64_val);
+ return T(*i64_val);
+}
+
+template<>
+std::optional<std::int64_t> try_unpack_object(const msgpack_object &object) {
+ if (object.type != MSGPACK_OBJECT_NEGATIVE_INTEGER && object.type != MSGPACK_OBJECT_POSITIVE_INTEGER)
+ return std::nullopt;
+
+ return object.via.i64;
+}
+
+template<>
+std::optional<std::int32_t> try_unpack_object(const msgpack_object &object) {
+ return try_unpack_int<std::int32_t>(object);
+}
+
+template<>
+std::optional<std::string> try_unpack_object(const msgpack_object &object) {
+ if (object.type != MSGPACK_OBJECT_STR)
+ return std::nullopt;
+
+ return std::string{object.via.str.ptr, object.via.str.size};
+}
+
template<typename T>
T unpack_int(const msgpack_object &object) {
static_assert(
@@ -35,13 +84,17 @@ T unpack_int(const msgpack_object &object) {
auto i64_val = unpack_object<std::int64_t>(object);
- if (i64_val > std::int64_t(std::numeric_limits<T>::max()))
- throw ignite_error("The number in stream is too large to fit in type: " + std::to_string(i64_val));
+ check_int_fits<T>(i64_val);
+ return T(i64_val);
+}
+
- if (i64_val < std::int64_t(std::numeric_limits<T>::min()))
- throw ignite_error("The number in stream is too small to fit in type: " + std::to_string(i64_val));
+template<>
+std::optional<std::string> unpack_nullable(const msgpack_object &object) {
+ if (object.type == MSGPACK_OBJECT_NIL)
+ return std::nullopt;
- return T(i64_val);
+ return unpack_object<std::string>(object);
}
template<>
diff --git a/modules/platforms/cpp/ignite/protocol/utils.h b/modules/platforms/cpp/ignite/protocol/utils.h
index e6efae2f94..e13297608d 100644
--- a/modules/platforms/cpp/ignite/protocol/utils.h
+++ b/modules/platforms/cpp/ignite/protocol/utils.h
@@ -40,6 +40,54 @@ class reader;
static constexpr std::array<std::byte, 4> MAGIC_BYTES = {
std::byte('I'), std::byte('G'), std::byte('N'), std::byte('I')};
+template<typename T>
+[[nodiscard]] std::optional<T> try_unpack_object(const msgpack_object &) {
+ static_assert(sizeof(T) == 0, "Unpacking is not implemented for the type");
+}
+
+/**
+ * Try unpack number.
+ *
+ * @param object MsgPack object.
+ * @return Number or @c nullopt if the object is not a number.
+ */
+template<>
+[[nodiscard]] std::optional<std::int64_t> try_unpack_object(const msgpack_object &object);
+
+/**
+ * Try unpack number.
+ *
+ * @param object MsgPack object.
+ * @return Number or @c nullopt if the object is not a number.
+ */
+template<>
+[[nodiscard]] std::optional<std::int32_t> try_unpack_object(const msgpack_object &object);
+
+/**
+ * Try unpack string.
+ *
+ * @param object MsgPack object.
+ * @return String or @c nullopt if the object is not a string.
+ */
+template<>
+[[nodiscard]] std::optional<std::string> try_unpack_object(const msgpack_object &object);
+
+
+template<typename T>
+[[nodiscard]] std::optional<T> unpack_nullable(const msgpack_object &) {
+ static_assert(sizeof(T) == 0, "Unpacking is not implemented for the type");
+}
+
+/**
+ * Unpack string.
+ *
+ * @param object MsgPack object.
+ * @return String of @c nullopt if the object is @c nil.
+ * @throw ignite_error if the object is not a string.
+ */
+template<>
+[[nodiscard]] std::optional<std::string> unpack_nullable(const msgpack_object &object);
+
template<typename T>
[[nodiscard]] T unpack_object(const msgpack_object &) {
static_assert(sizeof(T) == 0, "Unpacking is not implemented for the type");
diff --git a/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h b/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h
index 732bb288c8..0dc565c1d5 100644
--- a/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h
+++ b/modules/platforms/cpp/ignite/schema/binary_tuple_builder.h
@@ -202,14 +202,14 @@ public:
*
* @param value Element value.
*/
- void claim_string(const std::string &value) noexcept { claim(value.size()); }
+ void claim_string(const std::string &value) noexcept { claim(SizeT(value.size())); }
/**
* @brief Assigns a binary value for the next element.
*
* @param value Element value.
*/
- void claim_bytes(const bytes_view &value) noexcept { claim(value.size()); }
+ void claim_bytes(const bytes_view &value) noexcept { claim(SizeT(value.size())); }
/**
* @brief Assigns a binary value for the next element.
@@ -659,7 +659,7 @@ private:
* @return Required size.
*/
static SizeT gauge_double(double value) noexcept {
- float floatValue = static_cast<float>(value);
+ auto floatValue = static_cast<float>(value);
return floatValue == value ? gauge_float(floatValue) : sizeof(double);
}
diff --git a/modules/platforms/cpp/ignite/schema/tuple_test.cpp b/modules/platforms/cpp/ignite/schema/tuple_test.cpp
index 8644d6969b..d76801b76e 100644
--- a/modules/platforms/cpp/ignite/schema/tuple_test.cpp
+++ b/modules/platforms/cpp/ignite/schema/tuple_test.cpp
@@ -103,7 +103,7 @@ std::string read_tuple(std::optional<bytes_view> data) {
struct SchemaDescriptor {
std::vector<column_info> columns;
- [[nodiscard]] IntT length() const { return columns.size(); }
+ [[nodiscard]] IntT length() const { return IntT(columns.size()); }
[[nodiscard]] binary_tuple_schema to_tuple_schema() const {
return binary_tuple_schema({columns.begin(), columns.end()});
diff --git a/modules/platforms/cpp/tests/client-test/CMakeLists.txt b/modules/platforms/cpp/tests/client-test/CMakeLists.txt
index 56d3e52aac..b2c8ec187c 100644
--- a/modules/platforms/cpp/tests/client-test/CMakeLists.txt
+++ b/modules/platforms/cpp/tests/client-test/CMakeLists.txt
@@ -25,6 +25,7 @@ set(SOURCES
ignite_runner_suite.h
main.cpp
record_binary_view_test.cpp
+ sql_test.cpp
tables_test.cpp
)
diff --git a/modules/platforms/cpp/tests/client-test/ignite_client_test.cpp b/modules/platforms/cpp/tests/client-test/ignite_client_test.cpp
index e43ce5026f..143476505d 100644
--- a/modules/platforms/cpp/tests/client-test/ignite_client_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/ignite_client_test.cpp
@@ -36,7 +36,7 @@ TEST_F(client_test, get_configuration) {
cfg.set_logger(get_logger());
cfg.set_connection_limit(42);
- auto client = ignite_client::start(cfg, std::chrono::seconds(5));
+ auto client = ignite_client::start(cfg, std::chrono::seconds(30));
const auto &cfg2 = client.configuration();
diff --git a/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h b/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
index b72ffe2bd5..8de0f337ba 100644
--- a/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
+++ b/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
@@ -34,6 +34,7 @@ using namespace std::string_view_literals;
class ignite_runner_suite : public ::testing::Test {
protected:
static constexpr std::initializer_list<std::string_view> NODE_ADDRS = {"127.0.0.1:10942"sv, "127.0.0.1:10943"sv};
+ static constexpr std::string_view TABLE_1 = "tbl1"sv;
/**
* Get logger.
diff --git a/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp b/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp
index d2d104f709..493ca553a8 100644
--- a/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/record_binary_view_test.cpp
@@ -39,8 +39,8 @@ protected:
ignite_client_configuration cfg{NODE_ADDRS};
cfg.set_logger(get_logger());
- m_client = ignite_client::start(cfg, std::chrono::minutes(5));
- auto table = m_client.get_tables().get_table("tbl1");
+ m_client = ignite_client::start(cfg, std::chrono::seconds(30));
+ auto table = m_client.get_tables().get_table(TABLE_1);
tuple_view = table->record_binary_view();
}
diff --git a/modules/platforms/cpp/tests/client-test/sql_test.cpp b/modules/platforms/cpp/tests/client-test/sql_test.cpp
new file mode 100644
index 0000000000..6978f06618
--- /dev/null
+++ b/modules/platforms/cpp/tests/client-test/sql_test.cpp
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 "ignite_runner_suite.h"
+
+#include "ignite/client/ignite_client.h"
+#include "ignite/client/ignite_client_configuration.h"
+
+#include <gtest/gtest.h>
+#include <gmock/gmock-matchers.h>
+
+#include <chrono>
+
+using namespace ignite;
+
+/**
+ * Test suite.
+ */
+class sql_test : public ignite_runner_suite {
+protected:
+ static void SetUpTestSuite() {
+ ignite_client_configuration cfg{NODE_ADDRS};
+ cfg.set_logger(get_logger());
+ auto client = ignite_client::start(cfg, std::chrono::seconds(30));
+
+ auto res = client.get_sql().execute(nullptr,
+ {"CREATE TABLE IF NOT EXISTS TEST(ID INT PRIMARY KEY, VAL VARCHAR)"}, {});
+
+ if (!res.was_applied()) {
+ client.get_sql().execute(nullptr, {"DELETE FROM TEST"}, {});
+ }
+
+ for (std::int32_t i = 0; i < 10; ++i) {
+ client.get_sql().execute(nullptr, {"INSERT INTO TEST VALUES (?, ?)"}, {i, "s-" + std::to_string(i)});
+ }
+ }
+
+ static void TearDownTestSuite() {
+ ignite_client_configuration cfg{NODE_ADDRS};
+ cfg.set_logger(get_logger());
+ auto client = ignite_client::start(cfg, std::chrono::seconds(30));
+
+ client.get_sql().execute(nullptr, {"DROP TABLE TEST"}, {});
+ client.get_sql().execute(nullptr, {"DROP TABLE IF EXISTS TestDdlDml"}, {});
+ }
+
+ void SetUp() override {
+ ignite_client_configuration cfg{NODE_ADDRS};
+ cfg.set_logger(get_logger());
+
+ m_client = ignite_client::start(cfg, std::chrono::seconds(30));
+ }
+
+ void TearDown() override {
+ // remove all
+ }
+
+ /** Ignite client. */
+ ignite_client m_client;
+};
+
+void check_columns(const result_set_metadata& meta,
+ std::initializer_list<std::tuple<std::string, column_type>> columns) {
+
+ ASSERT_EQ(columns.size(), meta.columns().size());
+ size_t i = 0;
+ for (auto &column : columns) {
+ EXPECT_EQ(i, meta.index_of(std::get<0>(column)));
+ EXPECT_EQ(meta.columns()[i].name(), std::get<0>(column));
+ EXPECT_EQ(meta.columns()[i].type(), std::get<1>(column));
+ ++i;
+ }
+}
+
+TEST_F(sql_test, sql_simple_select) {
+ auto result_set = m_client.get_sql().execute(nullptr, {"select 42, 'Lorem'"}, {});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_TRUE(result_set.has_rowset());
+ EXPECT_EQ(-1, result_set.affected_rows());
+
+ check_columns(result_set.metadata(), {{"42", column_type::INT32}, {"'Lorem'", column_type::STRING}});
+
+ auto page = result_set.current_page();
+
+ EXPECT_EQ(1, page.size());
+ EXPECT_EQ(42, page.front().get(0).get<std::int32_t>());
+ EXPECT_EQ("Lorem", page.front().get(1).get<std::string>());
+
+ EXPECT_FALSE(result_set.has_more_pages());
+ EXPECT_EQ(0, result_set.current_page().size());
+}
+
+TEST_F(sql_test, sql_table_select) {
+ auto result_set = m_client.get_sql().execute(nullptr, {"select id, val from TEST order by id"}, {});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_TRUE(result_set.has_rowset());
+ EXPECT_EQ(-1, result_set.affected_rows());
+
+ check_columns(result_set.metadata(), {{"ID", column_type::INT32}, {"VAL", column_type::STRING}});
+
+ auto page = result_set.current_page();
+
+ EXPECT_EQ(10, page.size());
+
+ for (std::int32_t i = 0; i < page.size(); ++i) {
+ EXPECT_EQ(i, page[i].get(0).get<std::int32_t>());
+ EXPECT_EQ("s-" + std::to_string(i), page[i].get(1).get<std::string>());
+ }
+
+ EXPECT_FALSE(result_set.has_more_pages());
+ EXPECT_EQ(0, result_set.current_page().size());
+}
+
+TEST_F(sql_test, sql_select_multiple_pages) {
+ sql_statement statement{"select id, val from TEST order by id"};
+ statement.page_size(1);
+
+ auto result_set = m_client.get_sql().execute(nullptr, statement, {});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_TRUE(result_set.has_rowset());
+ EXPECT_EQ(-1, result_set.affected_rows());
+
+ check_columns(result_set.metadata(), {{"ID", column_type::INT32}, {"VAL", column_type::STRING}});
+
+ for (std::int32_t i = 0; i < 10; ++i) {
+ auto page = result_set.current_page();
+
+ EXPECT_EQ(1, page.size()) << "i=" << i;
+ EXPECT_EQ(i, page.front().get(0).get<std::int32_t>());
+ EXPECT_EQ("s-" + std::to_string(i), page.front().get(1).get<std::string>());
+
+ if (i < 9) {
+ ASSERT_TRUE(result_set.has_more_pages());
+ result_set.fetch_next_page();
+ }
+ }
+
+ EXPECT_FALSE(result_set.has_more_pages());
+ EXPECT_EQ(0, result_set.current_page().size());
+}
+
+TEST_F(sql_test, sql_close_non_empty_cursor) {
+ sql_statement statement{"select id, val from TEST order by id"};
+ statement.page_size(3);
+
+ auto result_set = m_client.get_sql().execute(nullptr, statement, {});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_TRUE(result_set.has_rowset());
+ EXPECT_EQ(-1, result_set.affected_rows());
+
+ auto page = result_set.current_page();
+ ASSERT_TRUE(result_set.has_more_pages());
+
+ result_set.close();
+}
+
+TEST_F(sql_test, sql_ddl_dml) {
+ auto result_set = m_client.get_sql().execute(nullptr, {"DROP TABLE IF EXISTS SQL_DDL_DML_TEST"}, {});
+
+ EXPECT_FALSE(result_set.has_rowset());
+ EXPECT_EQ(-1, result_set.affected_rows());
+ EXPECT_TRUE(result_set.metadata().columns().empty());
+
+ result_set = m_client.get_sql().execute(nullptr,
+ {"CREATE TABLE SQL_DDL_DML_TEST(ID BIGINT PRIMARY KEY, VAL VARCHAR)"}, {});
+
+ EXPECT_TRUE(result_set.was_applied());
+ EXPECT_FALSE(result_set.has_rowset());
+ EXPECT_EQ(-1, result_set.affected_rows());
+ EXPECT_TRUE(result_set.metadata().columns().empty());
+
+ result_set = m_client.get_sql().execute(nullptr, {"INSERT INTO SQL_DDL_DML_TEST VALUES (?, ?)"}, {13LL, "Hello"});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_FALSE(result_set.has_rowset());
+ EXPECT_EQ(1, result_set.affected_rows());
+ EXPECT_TRUE(result_set.metadata().columns().empty());
+
+ result_set = m_client.get_sql().execute(nullptr, {"INSERT INTO SQL_DDL_DML_TEST VALUES (?, ?)"}, {14LL, "World"});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_FALSE(result_set.has_rowset());
+ EXPECT_EQ(1, result_set.affected_rows());
+ EXPECT_TRUE(result_set.metadata().columns().empty());
+
+
+ result_set = m_client.get_sql().execute(nullptr, {"UPDATE SQL_DDL_DML_TEST SET VAL = ?"}, {"Test"});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_FALSE(result_set.has_rowset());
+ EXPECT_EQ(2, result_set.affected_rows());
+ EXPECT_TRUE(result_set.metadata().columns().empty());
+
+ result_set = m_client.get_sql().execute(nullptr, {"DROP TABLE SQL_DDL_DML_TEST"}, {});
+
+ EXPECT_TRUE(result_set.was_applied());
+ EXPECT_FALSE(result_set.has_rowset());
+ EXPECT_EQ(-1, result_set.affected_rows());
+ EXPECT_TRUE(result_set.metadata().columns().empty());
+}
+
+TEST_F(sql_test, sql_insert_null) {
+ auto result_set = m_client.get_sql().execute(nullptr, {"DROP TABLE IF EXISTS SQL_INSERT_NULL_TEST"}, {});
+ result_set = m_client.get_sql().execute(nullptr,
+ {"CREATE TABLE SQL_INSERT_NULL_TEST(ID INT PRIMARY KEY, VAL VARCHAR)"}, {});
+
+ ASSERT_TRUE(result_set.was_applied());
+
+ result_set = m_client.get_sql().execute(nullptr, {"INSERT INTO SQL_INSERT_NULL_TEST VALUES (13, NULL)"}, {});
+
+ EXPECT_FALSE(result_set.was_applied());
+ EXPECT_FALSE(result_set.has_rowset());
+ EXPECT_EQ(1, result_set.affected_rows());
+ EXPECT_TRUE(result_set.metadata().columns().empty());
+
+ result_set = m_client.get_sql().execute(nullptr, {"DROP TABLE SQL_INSERT_NULL_TEST"}, {});
+ EXPECT_TRUE(result_set.was_applied());
+}
+
+TEST_F(sql_test, sql_invalid_query) {
+ EXPECT_THROW(
+ {
+ try {
+ m_client.get_sql().execute(nullptr, {"not a query"}, {});
+ } catch (const ignite_error &e) {
+ EXPECT_THAT(e.what_str(), ::testing::HasSubstr("Failed to parse query: Non-query expression"));
+ throw;
+ }
+ },
+ ignite_error);
+}
+
+TEST_F(sql_test, sql_unknown_table) {
+ EXPECT_THROW(
+ {
+ try {
+ m_client.get_sql().execute(nullptr, {"select id from unknown_table"}, {});
+ } catch (const ignite_error &e) {
+ EXPECT_THAT(e.what_str(), ::testing::HasSubstr("Object 'UNKNOWN_TABLE' not found"));
+ throw;
+ }
+ },
+ ignite_error);
+}
+
+TEST_F(sql_test, sql_unknown_column) {
+ EXPECT_THROW(
+ {
+ try {
+ m_client.get_sql().execute(nullptr, {"select unknown_column from test"}, {});
+ } catch (const ignite_error &e) {
+ EXPECT_THAT(e.what_str(), ::testing::HasSubstr("Column 'UNKNOWN_COLUMN' not found in any table"));
+ throw;
+ }
+ },
+ ignite_error);
+}
+
+TEST_F(sql_test, sql_create_existing_table) {
+ EXPECT_THROW(
+ {
+ try {
+ m_client.get_sql().execute(nullptr, {"CREATE TABLE TEST(ID INT PRIMARY KEY, VAL VARCHAR)"}, {});
+ } catch (const ignite_error &e) {
+ EXPECT_THAT(e.what_str(), ::testing::HasSubstr("Table already exists"));
+ throw;
+ }
+ },
+ ignite_error);
+}
+
+TEST_F(sql_test, sql_add_existing_column) {
+ EXPECT_THROW(
+ {
+ try {
+ m_client.get_sql().execute(nullptr, {"ALTER TABLE TEST ADD COLUMN ID INT"}, {});
+ } catch (const ignite_error &e) {
+ EXPECT_THAT(e.what_str(), ::testing::HasSubstr("Column already exists"));
+ throw;
+ }
+ },
+ ignite_error);
+}
+
+TEST_F(sql_test, sql_alter_nonexisting_table) {
+ EXPECT_THROW(
+ {
+ try {
+ m_client.get_sql().execute(nullptr, {"ALTER TABLE UNKNOWN_TABLE ADD COLUMN ID INT"}, {});
+ } catch (const ignite_error &e) {
+ EXPECT_THAT(e.what_str(), ::testing::HasSubstr("The table does not exist"));
+ throw;
+ }
+ },
+ ignite_error);
+}
+
+TEST_F(sql_test, sql_statement_defaults) {
+ sql_statement statement;
+
+ EXPECT_EQ(statement.page_size(), sql_statement::DEFAULT_PAGE_SIZE);
+ EXPECT_EQ(statement.schema(), sql_statement::DEFAULT_SCHEMA);
+ EXPECT_EQ(statement.timeout(), sql_statement::DEFAULT_TIMEOUT);
+}
+
diff --git a/modules/platforms/cpp/tests/client-test/tables_test.cpp b/modules/platforms/cpp/tests/client-test/tables_test.cpp
index f762eb4d46..04f6bc49e0 100644
--- a/modules/platforms/cpp/tests/client-test/tables_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/tables_test.cpp
@@ -37,7 +37,7 @@ TEST_F(tables_test, tables_get_table) {
ignite_client_configuration cfg{NODE_ADDRS};
cfg.set_logger(get_logger());
- auto client = ignite_client::start(cfg, std::chrono::seconds(5));
+ auto client = ignite_client::start(cfg, std::chrono::seconds(30));
auto tables = client.get_tables();
auto tableUnknown = tables.get_table("some_unknown");
@@ -53,7 +53,7 @@ TEST_F(tables_test, tables_get_table_async_promises) {
cfg.set_logger(get_logger());
auto clientPromise = std::make_shared<std::promise<ignite_client>>();
- ignite_client::start_async(cfg, std::chrono::seconds(5), result_promise_setter(clientPromise));
+ ignite_client::start_async(cfg, std::chrono::seconds(30), result_promise_setter(clientPromise));
auto client = clientPromise->get_future().get();
@@ -83,7 +83,7 @@ TEST_F(tables_test, tables_get_table_async_callbacks) {
ignite_client client;
- ignite_client::start_async(cfg, std::chrono::seconds(5), [&](ignite_result<ignite_client> clientRes) {
+ ignite_client::start_async(cfg, std::chrono::seconds(30), [&](ignite_result<ignite_client> clientRes) {
if (!check_and_set_operation_error(*operation0, clientRes))
return;
@@ -133,7 +133,7 @@ TEST_F(tables_test, tables_get_tables) {
ignite_client_configuration cfg{NODE_ADDRS};
cfg.set_logger(get_logger());
- auto client = ignite_client::start(cfg, std::chrono::seconds(5));
+ auto client = ignite_client::start(cfg, std::chrono::seconds(30));
auto tablesApi = client.get_tables();
@@ -149,7 +149,7 @@ TEST_F(tables_test, tables_get_tables_async_promises) {
ignite_client_configuration cfg{NODE_ADDRS};
cfg.set_logger(get_logger());
- auto client = ignite_client::start(cfg, std::chrono::seconds(5));
+ auto client = ignite_client::start(cfg, std::chrono::seconds(30));
auto tablesApi = client.get_tables();
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
index 28da95ad37..d87f76558f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
@@ -276,6 +276,7 @@ namespace Apache.Ignite.Tests
writer.WriteArrayHeader(2); // Meta.
+ writer.WriteArrayHeader(6); // Column props.
writer.Write("NAME"); // Column name.
writer.Write(false); // Nullable.
writer.Write((int)SqlColumnType.String);
@@ -283,6 +284,7 @@ namespace Apache.Ignite.Tests
writer.Write(0); // Precision.
writer.Write(false); // No origin.
+ writer.WriteArrayHeader(6); // Column props.
writer.Write("VAL"); // Column name.
writer.Write(false); // Nullable.
writer.Write((int)SqlColumnType.String);
@@ -307,6 +309,7 @@ namespace Apache.Ignite.Tests
writer.Write(0); // AffectedRows.
writer.WriteArrayHeader(1); // Meta.
+ writer.WriteArrayHeader(6); // Column props.
writer.Write("ID"); // Column name.
writer.Write(false); // Nullable.
writer.Write((int)SqlColumnType.Int32);
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
index 4862c1d18a..391d39d400 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
@@ -200,6 +200,11 @@ namespace Apache.Ignite.Internal.Sql
for (int i = 0; i < size; i++)
{
+ var propertyCount = reader.ReadArrayHeader();
+ const int minCount = 6;
+
+ Debug.Assert(propertyCount >= minCount, "propertyCount >= " + minCount);
+
var name = reader.ReadString();
var nullable = reader.ReadBoolean();
var type = (SqlColumnType)reader.ReadInt32();