blob: d5aa36499397296bbb70aa5566fc189985c5dec1 [file]
/*
* 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 "odbc_suite.h"
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include <string>
using namespace ignite;
/**
* Test setup fixture.
*/
struct transaction_test : public odbc_suite {
void SetUp() override {
odbc_connect(get_basic_connection_string());
retry_on_fail([&] { return exec_query("DELETE FROM " + TABLE_NAME_ALL_COLUMNS_SQL); });
odbc_clean_up();
}
/**
* Insert test string value in cache and make all the necessary checks.
*
* @param key Key.
* @param value Value.
*/
void insert_test_value(std::int64_t key, const std::string &value) { insert_test_value(m_statement, key, value); }
/**
* Insert test string value in cache and make all the necessary checks.
*
* @param statement Statement.
* @param key Key.
* @param value Value.
*/
static void insert_test_value(SQLHSTMT statement, std::int64_t key, const std::string &value) {
SQLCHAR insert_req[] = "INSERT INTO TBL_ALL_COLUMNS_SQL(key, str) VALUES(?, ?)";
SQLRETURN ret = SQLPrepare(statement, insert_req, SQL_NTS);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
char str_field[1024] = {0};
SQLLEN str_field_len = 0;
ret = SQLBindParameter(statement, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &key, 0, nullptr);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLBindParameter(statement, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, sizeof(str_field),
sizeof(str_field), &str_field, sizeof(str_field), &str_field_len);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
strncpy(str_field, value.c_str(), sizeof(str_field) - 1);
str_field_len = SQL_NTS;
ret = SQLExecute(statement);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
SQLLEN affected = 0;
ret = SQLRowCount(statement, &affected);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
EXPECT_EQ(affected, 1);
ret = SQLMoreResults(statement);
if (ret != SQL_NO_DATA)
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
reset_statement(statement);
}
/**
* Update test string value in cache and make all the necessary checks.
*
* @param key Key.
* @param value Value.
*/
void update_test_value(std::int64_t key, const std::string &value) { update_test_value(m_statement, key, value); }
/**
* Update test string value in cache and make all the necessary checks.
*
* @param statement Statement.
* @param key Key.
* @param value Value.
*/
static void update_test_value(SQLHSTMT statement, std::int64_t key, const std::string &value) {
SQLCHAR update_req[] = "UPDATE TBL_ALL_COLUMNS_SQL SET str=? WHERE key=?";
SQLRETURN ret = SQLPrepare(statement, update_req, SQL_NTS);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
char str_field[1024] = {0};
SQLLEN str_field_len = 0;
ret = SQLBindParameter(statement, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, sizeof(str_field),
sizeof(str_field), &str_field, sizeof(str_field), &str_field_len);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLBindParameter(statement, 2, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &key, 0, nullptr);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
strncpy(str_field, value.c_str(), sizeof(str_field) - 1);
str_field_len = SQL_NTS;
ret = SQLExecute(statement);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
SQLLEN affected = 0;
ret = SQLRowCount(statement, &affected);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
EXPECT_EQ(affected, 1);
ret = SQLMoreResults(statement);
if (ret != SQL_NO_DATA)
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
reset_statement(statement);
}
/**
* Delete test string value.
*
* @param key Key.
*/
void delete_test_value(std::int64_t key) { delete_test_value(m_statement, key); }
/**
* Delete test string value.
*
* @param statement Statement.
* @param key Key.
*/
static void delete_test_value(SQLHSTMT statement, std::int64_t key) {
SQLCHAR delete_req[] = "DELETE FROM TBL_ALL_COLUMNS_SQL WHERE key=?";
SQLRETURN ret = SQLPrepare(statement, delete_req, SQL_NTS);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLBindParameter(statement, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &key, 0, nullptr);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLExecute(statement);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
SQLLEN affected = 0;
ret = SQLRowCount(statement, &affected);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
EXPECT_EQ(affected, 1);
ret = SQLMoreResults(statement);
if (ret != SQL_NO_DATA)
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
reset_statement(statement);
}
/**
* Selects and checks the value.
*
* @param key Key.
* @param expect Expected value.
*/
void check_test_value(std::int64_t key, const std::string &expect) { check_test_value(m_statement, key, expect); }
/**
* Selects and checks the value.
*
* @param statement Statement.
* @param key Key.
* @param expect Expected value.
*/
static void check_test_value(SQLHSTMT statement, std::int64_t key, std::optional<std::string_view> expect) {
// Just selecting everything to make sure everything is OK
SQLCHAR selectReq[] = "SELECT str FROM TBL_ALL_COLUMNS_SQL WHERE key = ?";
char str_field[1024] = {0};
SQLLEN str_field_len = 0;
SQLRETURN ret = SQLBindCol(statement, 1, SQL_C_CHAR, &str_field, sizeof(str_field), &str_field_len);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLBindParameter(statement, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &key, 0, nullptr);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLExecDirect(statement, selectReq, sizeof(selectReq));
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLFetch(statement);
if (expect) {
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
EXPECT_EQ(std::string(str_field, str_field_len), expect);
ret = SQLFetch(statement);
}
if (ret != SQL_NO_DATA)
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLMoreResults(statement);
if (ret != SQL_NO_DATA)
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
reset_statement(statement);
}
/**
* Selects and checks that value is absent.
*
* @param statement Statement.
* @param key Key.
*/
static void check_no_test_value(SQLHSTMT statement, std::int64_t key) { check_test_value(statement, key, {}); }
/**
* Selects and checks that value is absent.
*
* @param key Key.
*/
void check_no_test_value(std::int64_t key) { check_no_test_value(m_statement, key); }
/**
* Reset statement state.
*/
void reset_statement() { reset_statement(m_statement); }
/**
* Reset statement state.
*
* @param statement Statement.
*/
static void reset_statement(SQLHSTMT statement) {
SQLRETURN ret = SQLFreeStmt(statement, SQL_RESET_PARAMS);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
ret = SQLFreeStmt(statement, SQL_UNBIND);
ODBC_THROW_ON_ERROR(ret, SQL_HANDLE_STMT, statement);
}
};
TEST_F(transaction_test, transaction_connection_commit) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_COMMIT);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_connection_rollback_insert) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_no_test_value(42);
}
TEST_F(transaction_test, transaction_connection_rollback_update_1) {
odbc_connect(get_basic_connection_string());
insert_test_value(42, "Some");
check_test_value(42, "Some");
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
update_test_value(42, "Other");
check_test_value(42, "Other");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_connection_rollback_update_2) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_COMMIT);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
update_test_value(42, "Other");
check_test_value(42, "Other");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_connection_rollback_delete_1) {
odbc_connect(get_basic_connection_string());
insert_test_value(42, "Some");
check_test_value(42, "Some");
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
delete_test_value(42);
check_no_test_value(42);
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_connection_rollback_delete_2) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_COMMIT);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
delete_test_value(42);
check_no_test_value(42);
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_environment_commit) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_COMMIT);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_environment_rollback_insert) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_ENV, m_env, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_no_test_value(42);
}
TEST_F(transaction_test, transaction_environment_rollback_update_1) {
odbc_connect(get_basic_connection_string());
insert_test_value(42, "Some");
check_test_value(42, "Some");
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
update_test_value(42, "Other");
check_test_value(42, "Other");
ret = SQLEndTran(SQL_HANDLE_ENV, m_env, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_environment_rollback_update_2) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_COMMIT);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
update_test_value(42, "Other");
check_test_value(42, "Other");
ret = SQLEndTran(SQL_HANDLE_ENV, m_env, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_environment_rollback_delete_1) {
odbc_connect(get_basic_connection_string());
insert_test_value(42, "Some");
check_test_value(42, "Some");
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
delete_test_value(42);
check_no_test_value(42);
ret = SQLEndTran(SQL_HANDLE_ENV, m_env, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_environment_rollback_delete_2) {
odbc_connect(get_basic_connection_string());
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_COMMIT);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_test_value(42, "Some");
ret = SQLEndTran(SQL_HANDLE_DBC, m_conn, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
delete_test_value(42);
check_no_test_value(42);
ret = SQLEndTran(SQL_HANDLE_ENV, m_env, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_test_value(42, "Some");
}
TEST_F(transaction_test, transaction_error) {
odbc_connect(get_basic_connection_string());
insert_test_value(1, "test_1");
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
check_test_value(1, "test_1");
check_no_test_value(2);
odbc_connection conn2;
conn2.odbc_connect(get_basic_connection_string());
ret = SQLSetConnectAttr(conn2.m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, conn2.m_conn);
EXPECT_THROW(
{
try {
insert_test_value(conn2.m_statement, 2, "test_2");
} catch (const odbc_exception &err) {
EXPECT_THAT(err.message, testing::HasSubstr("Failed to acquire a lock during request handling"));
EXPECT_EQ(err.sql_state, "25000");
throw;
}
},
odbc_exception);
reset_statement(conn2.m_statement);
// If any statement in transaction fails, then the transaction is considered aborted -
// all subsequent statements within that transaction should fail.
EXPECT_THROW(
{
try {
insert_test_value(conn2.m_statement, 2, "test_2");
} catch (const odbc_exception &err) {
EXPECT_THAT(err.message, testing::HasSubstr("Transaction is already finished"));
EXPECT_EQ(err.sql_state, "25000");
throw;
}
},
odbc_exception);
check_no_test_value(2);
}
TEST_F(transaction_test, heartbeat_connection_is_not_closed) {
using namespace std::chrono_literals;
EXPECT_NO_THROW(odbc_connect_throw(get_basic_connection_string()));
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
std::this_thread::sleep_for(7s);
ret = SQLEndTran(SQL_HANDLE_ENV, m_env, SQL_ROLLBACK);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
check_no_test_value(42);
}
TEST_F(transaction_test, heartbeat_disable_connection_is_closed) {
using namespace std::chrono_literals;
EXPECT_NO_THROW(odbc_connect_throw(get_basic_connection_string(0s)));
SQLRETURN ret = SQLSetConnectAttr(m_conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
insert_test_value(42, "Some");
check_test_value(42, "Some");
std::this_thread::sleep_for(7s);
ret = SQLEndTran(SQL_HANDLE_ENV, m_env, SQL_ROLLBACK);
std::cout << "Error message: " << get_odbc_error_message(SQL_HANDLE_ENV, m_env) << std::endl;
EXPECT_TRUE(ret == SQL_ERROR || ret == SQL_SUCCESS_WITH_INFO);
}