Files
mariadb-connector-cpp/test/unit/classes/preparedstatement.cpp
Lawrin Novitsky 7471580570 Extended a bit the test Bytes array parameter from previous commit
to demonstatrate and check its work, and a bit of sql::bytes

Corrected vendor name in cpack configuration.
2025-02-17 01:06:56 +01:00

2250 lines
64 KiB
C++

/*
* Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
* 2020, 2025 MariaDB Corporation plc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0, as
* published by the Free Software Foundation.
*
* This program is also distributed with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an
* additional permission to link the program and your derivative works
* with the separately licensed software that they have included with
* MySQL.
*
* Without limiting anything contained in the foregoing, this file,
* which is part of MySQL Connector/C++, is also subject to the
* Universal FOSS Exception, version 1.0, a copy of which can be found at
* http://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "PreparedStatement.hpp"
#include "Connection.hpp"
#include "Warning.hpp"
#include "preparedstatementtest.h"
#include <stdlib.h>
#include <memory>
namespace testsuite
{
namespace classes
{
void preparedstatement::setUp()
{
sql::SQLString sspsOptionValue;
sql::Properties::iterator useServerPrepStmts= commonProperties.find("useServerPrepStmts");
bool hadOption= useServerPrepStmts != commonProperties.end();
if (hadOption) {
sspsOptionValue= useServerPrepStmts->second;
}
commonProperties["useServerPrepStmts"]= "false";
super::setUp();
commonProperties["useServerPrepStmts"] = "true";
try
{
sspsCon.reset(this->getConnection(&commonProperties));
}
catch (sql::SQLException& sqle)
{
logErr(String("Couldn't get connection") + sqle.what());
throw sqle;
}
sspsCon->setSchema(db);
}
void preparedstatement::InsertSelectAllTypes()
{
logMsg("preparedstatement::InsertSelectAllTypes() - MySQL_PreparedStatement::*");
//TODO: Enable it after fixing
std::stringstream sql;
std::vector<columndefinition>::iterator it;
stmt.reset(con->createStatement());
bool got_warning=false;
size_t len;
try
{
bool blobTestMakesSense= true;
for (it = columns.end(), it--; it != columns.begin(); it--)
{
stmt->execute("DROP TABLE IF EXISTS test");
sql.str("");
sql << "CREATE TABLE test(dummy TIMESTAMP, id " << it->sqldef << ")";
try
{
stmt->execute(sql.str());
sql.str("");
sql << "... testing '" << it->sqldef << "'";
logMsg(sql.str());
}
catch (sql::SQLException&)
{
sql.str("");
sql << "... skipping '" << it->sqldef << "'";
logMsg(sql.str());
continue;
}
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
if (it->name == "BIT")
{
pstmt->setLong(1, std::stoull(it->value));
}
else
{
pstmt->setString(1, it->value);
}
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt.reset(con->prepareStatement("SELECT id, NULL FROM test"));
res.reset(pstmt->executeQuery());
checkResultSetScrolling(res);
ASSERT(res->next());
res.reset();
res.reset(pstmt->executeQuery());
checkResultSetScrolling(res);
ASSERT(res->next());
if (it->check_as_string && (res->getString(1) != it->as_string))
{
sql.str("");
sql << "... \t\tWARNING - SQL: '" << it->sqldef << "' - expecting '" << it->as_string << "'";
sql << " got '" << res->getString(1) << "'";
logMsg(sql.str());
got_warning = true;
}
sql::SQLString value = res->getString(1);
ASSERT_EQUALS(res->getString("id"), String(value.c_str(), value.length()));
try
{
res->getString(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
try
{
res->getString(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
if (pstmt->getResultSetType() == sql::ResultSet::TYPE_FORWARD_ONLY) {
continue;
}
res->beforeFirst();
try
{
res->getString(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getString(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->first();
try
{
ASSERT_EQUALS(res->getDouble("id"), res->getDouble(1));
try
{
res->getDouble(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
try
{
res->getDouble(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
res->beforeFirst();
try
{
res->getDouble(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getDouble(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->first();
ASSERT_EQUALS(res->getInt(1), res->getInt("id"));
try
{
res->getInt(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
try
{
res->getInt(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
res->beforeFirst();
try
{
res->getInt(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getInt(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->first();
ASSERT_EQUALS(res->getUInt(1), res->getUInt("id"));
try
{
res->getUInt(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
try
{
res->getUInt(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
res->beforeFirst();
try
{
res->getUInt(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getUInt(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->first();
ASSERT_EQUALS(res->getInt64("id"), res->getInt64(1));
try
{
res->getInt64(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
try
{
res->getInt64(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
res->beforeFirst();
try
{
res->getInt64(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getInt64(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->first();
ASSERT_EQUALS(res->getUInt64("id"), res->getUInt64(1));
try
{
res->getUInt64(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
try
{
res->getUInt64(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
res->beforeFirst();
try
{
res->getUInt64(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getUInt64(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
}
catch (sql::SQLException & e)
{
//All is good
if ((e.getErrorCode() != 1264 || e.getSQLState().compare("22003") != 0) && !e.getMessage().startsWith("getDouble not available for data field type"))
{
throw e;
}
}
res->first();
ASSERT_EQUALS(res->getBoolean("id"), res->getBoolean(1));
try
{
res->getBoolean(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
try
{
res->getBoolean(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
res->beforeFirst();
try
{
res->getBoolean(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getBoolean(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->first();
/* YEAR is first type after char/bin types(traversing backwards). The way getBlob test is written now,
it will work correctly only for those types(and maybe some others), and YEAR is firdt type it will not */
if (it->name == "YEAR")
{
blobTestMakesSense= false;
}
// TODO - make BLOB
if (it->check_as_string && blobTestMakesSense)
{
{
std::unique_ptr<std::istream> blob_output_stream(res->getBlob(1));
len=it->as_string.length();
std::unique_ptr<char[]> blob_out(new char[len]);
blob_output_stream->read(blob_out.get(), len);
if (it->as_string.compare(0, static_cast<std::size_t>(blob_output_stream->gcount())
, blob_out.get(), static_cast<std::size_t>(blob_output_stream->gcount())))
{
sql.str("");
sql << "... \t\tWARNING - SQL: '" << it->sqldef << "' - expecting '" << it->as_string << "'";
sql << " got '" << res->getString(1) << "'";
logMsg(sql.str());
got_warning=false;
}
}
{
std::unique_ptr<std::istream> blob_output_stream(res->getBlob("id"));
len=it->as_string.length();
std::unique_ptr<char[]> blob_out(new char[len]);
blob_output_stream->read(blob_out.get(), len);
if (it->as_string.compare(0, static_cast<std::size_t>(blob_output_stream->gcount())
, blob_out.get(), static_cast<std::size_t>(blob_output_stream->gcount())))
{
sql.str("");
sql << "... \t\tWARNING - SQL: '" << it->sqldef << "' - expecting '" << it->as_string << "'";
sql << " got '" << res->getString(1) << "'";
logMsg(sql.str());
got_warning=false;
}
}
}
try
{
res->getBlob(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException &)
{
}
try
{
res->getBlob(3);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException &)
{
}
res->beforeFirst();
try
{
res->getBlob(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->afterLast();
try
{
res->getBlob(1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLDataException&)
{
}
res->first();
}
stmt->execute("DROP TABLE IF EXISTS test");
if (got_warning)
FAIL("See warnings");
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::assortedSetType()
{
logMsg("preparedstatement::assortedSetType() - MySQL_PreparedStatement::set*");
//TODO: Enable it after fixing
//SKIP("Removed until fixed");
std::stringstream sql;
std::vector<columndefinition>::iterator it;
stmt.reset(con->createStatement());
bool got_warning=false;
try
{
for (it=columns.end(), it--; it != columns.begin(); it--)
{
stmt->execute("DROP TABLE IF EXISTS test");
sql.str("");
sql << "CREATE TABLE test(dummy TIMESTAMP, id " << it->sqldef << ")";
try
{
stmt->execute(sql.str());
sql.str("");
sql << "... testing '" << it->sqldef << "'";
logMsg(sql.str());
}
catch (sql::SQLException &)
{
sql.str("");
sql << "... skipping '" << it->sqldef << "'";
logMsg(sql.str());
continue;
}
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
if (it->name == "BIT")
{
pstmt->setLong(1, std::stoull(it->value));
ASSERT_EQUALS(1, pstmt->executeUpdate());
}
else
{
pstmt->setString(1, it->value);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt->clearParameters();
pstmt->setDateTime(1, it->value);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt->clearParameters();
pstmt->setBigInt(1, it->value);
ASSERT_EQUALS(1, pstmt->executeUpdate());
}
pstmt->clearParameters();
try
{
pstmt->setString(0, "overflow");
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setString(2, "invalid");
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setBigInt(0, it->value);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setBigInt(2, it->value);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
// enum won't accept 0 as a value. Same for TIMESTAMP on MySQL
pstmt->setBoolean(1, it->name.compare("ENUM") == 0);
try
{
ASSERT_EQUALS(1, pstmt->executeUpdate());
}
catch (sql::SQLException& e)
{
if (isMySQL() && (it->name.compare("TIMESTAMP") == 0 ||
it->name.compare("DATETIME") == 0 ||
it->name.compare("DATE") == 0))
{
ASSERT_EQUALS("22007", e.getSQLState());
}
else
{
TEST_THROW(sql::SQLException, e);
}
}
pstmt->clearParameters();
try
{
pstmt->setBoolean(0, false);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setBoolean(2, false);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setDateTime(0, it->value);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setDateTime(2, it->value);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
pstmt->setDouble(1, 1.23);
if (it->name != "TIMESTAMP" && it->name != "DATETIME" && it->name != "DATE")
{
try
{
ASSERT_EQUALS(1, pstmt->executeUpdate());
}
catch(sql::SQLException& e)
{
if (!((it->name == "VARBINARY" || it->name == "BINARY" || it->name == "CHAR" || it->name == "VARCHAR") &&
it->precision < 30 && e.getSQLState() == "22001" && e.getErrorCode() == 1406))
{
TEST_THROW(sql::SQLException, e);
}
}
pstmt->clearParameters();
try
{
pstmt->setDouble(0, 1.23);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setDouble(2, 1.23);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
pstmt->setInt(1, 1);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt->clearParameters();
try
{
pstmt->setInt(0, (int32_t)-1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setInt(2, (int32_t)-1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
pstmt->setUInt(1, (uint32_t)1);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt->clearParameters();
try
{
pstmt->setUInt(0, (uint32_t)1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setUInt(2, (uint32_t)1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
pstmt->setInt64(1, 1);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt->clearParameters();
pstmt->setUInt64(1, (uint64_t)1);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt->clearParameters();
try
{
pstmt->setUInt64(0, (uint64_t)1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setUInt64(2, (uint64_t)-1);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
}
if (it->is_nullable)
{
pstmt->clearParameters();
pstmt->setNull(1, it->ctype);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt->clearParameters();
try
{
pstmt->setNull(0, it->ctype);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setNull(2, it->ctype);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
}
pstmt->clearParameters();
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
std::stringstream blob_input_stream;
blob_input_stream.str(it->value);
pstmt->setBlob(1, &blob_input_stream);
try
{
ASSERT_EQUALS(1, pstmt->executeUpdate());
}
catch (sql::SQLException & e)
{
if (!(e.getErrorCode() == 1265 || e.getErrorCode() == 1406) || (it->name.compare("SET") != 0 && it->name.compare("ENUM") != 0 && it->name.compare("BIT") != 0))
{
throw e;
}
}
pstmt->clearParameters();
try
{
pstmt->setBlob(0, &blob_input_stream);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt->clearParameters();
try
{
pstmt->setBlob(2, &blob_input_stream);
FAIL("Invalid argument not detected");
}
catch (sql::SQLException&)
{
}
pstmt.reset(con->prepareStatement("SELECT COUNT(IFNULL(id, 1)) AS _num FROM test"));
res.reset(pstmt->executeQuery());
checkResultSetScrolling(res);
ASSERT(res->next());
if (res->getInt("_num") != (11 + (int) it->is_nullable))
{
sql.str("");
sql << "....\t\tWARNING, SQL: " << it->sqldef << ", nullable " << std::boolalpha;
sql << it->is_nullable << ", found " << res->getInt(1) << "columns but";
sql << " expecting " << (11 + (int) it->is_nullable);
logMsg(sql.str());
got_warning=true;
}
}
stmt->execute("DROP TABLE IF EXISTS test");
if (got_warning)
{
SKIP("There were warnings, but that is due to changes in the test, that made detecting of warnings obsolete");
}
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
std::string message("The error occured on type:");
message.append(it->sqldef).append(".").append(e.what());
fail(message.c_str(), __FILE__, __LINE__);
}
}
void preparedstatement::setNull()
{
logMsg("preparedstatement::setNull() - MySQL_PreparedStatement::*");
std::stringstream sql;
stmt.reset(con->createStatement());
try
{
stmt->execute("DROP TABLE IF EXISTS test");
stmt->execute("CREATE TABLE test(id INT)");
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
pstmt->setNull(1, sql::DataType::INTEGER);
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt.reset(con->prepareStatement("SELECT id FROM test"));
res.reset(pstmt->executeQuery());
checkResultSetScrolling(res);
ASSERT(res->next());
ASSERT(res->isNull(1));
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
try
{
stmt->execute("DROP TABLE IF EXISTS test");
stmt->execute("CREATE TABLE test(id INT NOT NULL)");
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
pstmt->setNull(1, sql::DataType::INTEGER);
pstmt->executeUpdate();
FAIL("Should fail");
}
catch (sql::SQLException &)
{
}
}
void preparedstatement::checkClosed()
{
logMsg("preparedstatement::checkClosed() - MySQL_PreparedStatement::close()");
try
{
pstmt.reset(con->prepareStatement("SELECT 1"));
pstmt->close();
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::getMetaData()
{
logMsg("preparedstatement::getMetaData() - MySQL_PreparedStatement::getMetaData()");
std::stringstream sql;
std::vector<columndefinition>::iterator it;
stmt.reset(con->createStatement());
ResultSetMetaData meta_ps;
ResultSetMetaData meta_st;
ResultSet res_st;
bool got_warning=false;
unsigned int i;
try
{
for (it=columns.end(), it--; it != columns.begin(); it--)
{
stmt->execute("DROP TABLE IF EXISTS test");
sql.str("");
sql << "CREATE TABLE test(dummy TIMESTAMP, id " << it->sqldef << ")";
try
{
stmt->execute(sql.str());
sql.str("");
sql << "... testing '" << it->sqldef << "'";
logMsg(sql.str());
}
catch (sql::SQLException &)
{
sql.str("");
sql << "... skipping '" << it->sqldef << "'";
logMsg(sql.str());
continue;
}
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
if (it->name == "BIT")
{
pstmt->setLong(1, std::stoull(it->value));
}
else
{
pstmt->setString(1, it->value);
}
ASSERT_EQUALS(1, pstmt->executeUpdate());
pstmt.reset(con->prepareStatement("SELECT id, dummy, NULL, -1.1234, 'Warum nicht...' FROM test"));
res.reset(pstmt->executeQuery());
meta_ps.reset(res->getMetaData());
res_st.reset(stmt->executeQuery("SELECT id, dummy, NULL, -1.1234, 'Warum nicht...' FROM test"));
meta_st.reset(res->getMetaData());
ASSERT_EQUALS(meta_ps->getColumnCount(), meta_st->getColumnCount());
for (i=1; i <= meta_ps->getColumnCount(); i++)
{
ASSERT_EQUALS(meta_ps->getCatalogName(i), meta_st->getCatalogName(i));
ASSERT_EQUALS(meta_ps->getColumnDisplaySize(i), meta_st->getColumnDisplaySize(i));
ASSERT_EQUALS(meta_ps->getColumnLabel(i), meta_st->getColumnLabel(i));
ASSERT_EQUALS(meta_ps->getColumnName(i), meta_st->getColumnName(i));
ASSERT_EQUALS(meta_ps->getColumnType(i), meta_st->getColumnType(i));
ASSERT_EQUALS(meta_ps->getColumnTypeName(i), meta_st->getColumnTypeName(i));
ASSERT_EQUALS(meta_ps->getPrecision(i), meta_st->getPrecision(i));
ASSERT_EQUALS(meta_ps->getScale(i), meta_st->getScale(i));
ASSERT_EQUALS(meta_ps->getSchemaName(i), meta_st->getSchemaName(i));
ASSERT_EQUALS(meta_ps->getTableName(i), meta_st->getTableName(i));
ASSERT_EQUALS(meta_ps->isAutoIncrement(i), meta_st->isAutoIncrement(i));
ASSERT_EQUALS(meta_ps->isCaseSensitive(i), meta_st->isCaseSensitive(i));
ASSERT_EQUALS(meta_ps->isCurrency(i), meta_st->isCurrency(i));
ASSERT_EQUALS(meta_ps->isDefinitelyWritable(i), meta_st->isDefinitelyWritable(i));
ASSERT_EQUALS(meta_ps->isNullable(i), meta_st->isNullable(i));
ASSERT_EQUALS(meta_ps->isReadOnly(i), meta_st->isReadOnly(i));
ASSERT_EQUALS(meta_ps->isSearchable(i), meta_st->isSearchable(i));
ASSERT_EQUALS(meta_ps->isSigned(i), meta_st->isSigned(i));
ASSERT_EQUALS(meta_ps->isWritable(i), meta_st->isWritable(i));
}
try
{
meta_ps->getCatalogName(0);
FAIL("Invalid argument not detected");
}
catch (sql::InvalidArgumentException&)
{
}
}
stmt->execute("DROP TABLE IF EXISTS test");
if (got_warning)
FAIL("See warnings");
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
}
bool preparedstatement::createSP(std::string sp_code)
{
try
{
stmt.reset(con->createStatement());
stmt->execute("DROP PROCEDURE IF EXISTS p");
}
catch (sql::SQLException &e)
{
logMsg(e.what());
return false;
}
try
{
pstmt.reset(con->prepareStatement(sp_code));
ASSERT(!pstmt->execute());
logMsg("... using PS for everything");
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg;
msg.str("");
msg << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg.str());
fail(e.what(), __FILE__, __LINE__);
}
stmt->execute(sp_code);
}
return true;
}
void preparedstatement::callSP()
{
logMsg("preparedstatement::callSP() - MySQL_PreparedStatement::*()");
std::string sp_code("CREATE PROCEDURE p(OUT ver_param VARCHAR(250)) BEGIN SELECT VERSION() INTO ver_param; END;");
DatabaseMetaData dbmeta(con->getMetaData());
bool autoCommit= con->getAutoCommit();
/* Version on the server can be different from the one reported by MaxScale. And we are testing here SP, not the connection metadata */
if (isSkySqlHA() || isMaxScale())
{
sp_code= "CREATE PROCEDURE p(OUT ver_param VARCHAR(250)) BEGIN SELECT '" + dbmeta->getDatabaseProductVersion() + "' INTO ver_param; END;";
}
try
{
if (!createSP(sp_code))
{
logMsg("... skipping:");
return;
}
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(false);
}
try
{
pstmt.reset(con->prepareStatement("CALL p(@version)"));
ASSERT(!pstmt->execute());
ASSERT(!pstmt->execute());
}
catch (sql::SQLException & e)
{
if (isSkySqlHA() || isMaxScale())
{
con->rollback();
con->setAutoCommit(autoCommit);
}
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg;
msg.str("");
msg << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS protocol does not support CALL
return;
}
pstmt.reset(con->prepareStatement("SELECT @version AS _version"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(dbmeta->getDatabaseProductVersion(), res->getString("_version"));
pstmt.reset(con->prepareStatement("SET @version='no_version'"));
ASSERT(!pstmt->execute());
pstmt.reset(con->prepareStatement("CALL p(@version)"));
ASSERT(!pstmt->execute());
pstmt.reset(con->prepareStatement("SELECT @version AS _version"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(dbmeta->getDatabaseProductVersion(), res->getString("_version"));
if (isSkySqlHA() || isMaxScale())
{
con->commit();
con->setAutoCommit(autoCommit);
}
}
catch (sql::SQLException &e)
{
if (isSkySqlHA() || isMaxScale())
{
con->rollback();
con->setAutoCommit(autoCommit);
}
logErr(e.what());
std::stringstream msg;
msg.str("");
msg << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg.str());
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::callSPInOut()
{
logMsg("preparedstatement::callSPInOut() - MySQL_PreparedStatement::*()");
std::string sp_code("CREATE PROCEDURE p(IN ver_in VARCHAR(25), OUT ver_out VARCHAR(25)) BEGIN SELECT ver_in INTO ver_out; END;");
bool autoCommit = con->getAutoCommit();
/* Version on the server can be different from the one reported by MaxScale. And we are testing here SP, not the connection metadata */
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(false);
}
try
{
if (!createSP(sp_code))
{
logMsg("... skipping: cannot create SP");
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
return;
}
try
{
cstmt.reset(con->prepareCall(" {CALL p('myver', @version) }"));
ASSERT(!cstmt->execute());
}
catch (sql::SQLException &e)
{
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg1;
msg1.str("");
msg1 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg1.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS protocol does not support CALL
logMsg("... skipping: PS protocol does not support CALL");
return;
}
pstmt.reset(con->prepareStatement("SELECT @version AS _version"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS("myver", res->getString("_version"));
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
}
catch (sql::SQLException &e)
{
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
logErr(e.what());
std::stringstream msg2;
msg2.str("");
msg2 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg2.str());
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::callSPInOutWithPs()
{
logMsg("preparedstatement::callSPInOut() - MySQL_PreparedStatement::*()");
std::string sp_code("CREATE PROCEDURE p(IN ver_in VARCHAR(25), OUT ver_out VARCHAR(25)) BEGIN SELECT ver_in INTO ver_out; END;");
bool autoCommit = con->getAutoCommit();
/* Version on the server can be different from the one reported by MaxScale. And we are testing here SP, not the connection metadata */
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(false);
}
try
{
if (!createSP(sp_code))
{
logMsg("... skipping: cannot create SP");
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
return;
}
try
{
pstmt.reset(con->prepareStatement("CALL p('myver', @version)"));
ASSERT(!pstmt->execute());
}
catch (sql::SQLException &e)
{
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg1;
msg1.str("");
msg1 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg1.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS protocol does not support CALL
logMsg("... skipping: PS protocol does not support CALL");
return;
}
pstmt.reset(con->prepareStatement("SELECT @version AS _version"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS("myver", res->getString("_version"));
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
}
catch (sql::SQLException &e)
{
if (isSkySqlHA() || isMaxScale())
{
con->setAutoCommit(autoCommit);
}
logErr(e.what());
std::stringstream msg2;
msg2.str("");
msg2 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg2.str());
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::callSP2()
{
logMsg("preparedstatement::callSPWithPS() - MySQL_PreparedStatement::*()");
try
{
std::string sp_code("CREATE PROCEDURE p(IN val VARCHAR(25)) BEGIN SET @sql = CONCAT('SELECT \"', val, '\"'); PREPARE stmt FROM @sql; EXECUTE stmt; DROP PREPARE stmt; END;");
if (!createSP(sp_code))
{
logMsg("... skipping:");
return;
}
try
{
cstmt.reset(con->prepareCall("CALL p('abc')"));
res.reset(cstmt->executeQuery());
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg1;
msg1.str("");
msg1 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg1.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS interface cannot call this kind of statement
return;
}
ASSERT(res->next());
ASSERT_EQUALS("abc", res->getString(1));
std::stringstream msg2;
msg2.str("");
msg2 << "... val = '" << res->getString(1) << "'";
logMsg(msg2.str());
while (cstmt->getMoreResults())
{
}
try
{
cstmt.reset(con->prepareCall("CALL p(?)"));
cstmt->setString(1, "123");
res.reset(cstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS("123", res->getString(1));
ASSERT(!res->next());
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg3;
msg3.str("");
msg3 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg3.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS interface cannot call this kind of statement
return;
}
res->close();
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg4;
msg4.str("");
msg4 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg4.str());
fail(e.what(), __FILE__, __LINE__);
}
}
}
void preparedstatement::callSP2WithPS()
{
logMsg("preparedstatement::callSPWithPS() - MySQL_PreparedStatement::*()");
try
{
std::string sp_code("CREATE PROCEDURE p(IN val VARCHAR(25)) BEGIN SET @sql = CONCAT('SELECT \"', val, '\"'); PREPARE stmt FROM @sql; EXECUTE stmt; DROP PREPARE stmt; END;");
if (!createSP(sp_code))
{
logMsg("... skipping:");
return;
}
try
{
pstmt.reset(con->prepareStatement("CALL p('abc')"));
res.reset(pstmt->executeQuery());
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg1;
msg1.str("");
msg1 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg1.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS interface cannot call this kind of statement
return;
}
ASSERT(res->next());
ASSERT_EQUALS("abc", res->getString(1));
std::stringstream msg2;
msg2.str("");
msg2 << "... val = '" << res->getString(1) << "'";
logMsg(msg2.str());
while(pstmt->getMoreResults())
{}
try
{
pstmt.reset(con->prepareCall("CALL p(?)"));
pstmt->setString(1, "123");
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS("123", res->getString(1));
ASSERT(!res->next());
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg3;
msg3.str("");
msg3 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg3.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS interface cannot call this kind of statement
return;
}
res->close();
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg4;
msg4.str("");
msg4 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg4.str());
fail(e.what(), __FILE__, __LINE__);
}
}
}
void preparedstatement::callSPMultiRes()
{
logMsg("preparedstatement::callSPMultiRes() - MySQL_PreparedStatement::*()");
try
{
std::string sp_code("CREATE PROCEDURE p() BEGIN SELECT 1; SELECT 2; SELECT 3; END;");
if (!createSP(sp_code))
{
logMsg("... skipping:");
return;
}
try
{
pstmt.reset(con->prepareStatement("CALL p()"));
ASSERT(pstmt->execute());
}
catch (sql::SQLException &e)
{
if (e.getErrorCode() != 1295)
{
logErr(e.what());
std::stringstream msg1;
msg1.str("");
msg1 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg1.str());
fail(e.what(), __FILE__, __LINE__);
}
// PS interface cannot call this kind of statement
return;
}
// Should not work prior to MySQL 6.0
std::stringstream msg2;
msg2.str("");
do
{
res.reset(pstmt->getResultSet());
while (res->next())
{
msg2 << res->getString(1);
}
}
while (pstmt->getMoreResults());
ASSERT_EQUALS("123", msg2.str());
}
catch (sql::SQLException &e)
{
logErr(e.what());
std::stringstream msg3;
msg3.str("");
msg3 << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg3.str());
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::anonymousSelect()
{
logMsg("preparedstatement::anonymousSelect() - MySQL_PreparedStatement::*, MYSQL_PS_Resultset::*");
try
{
pstmt.reset(con->prepareStatement("SELECT ' ', NULL"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(" ", res->getString(1));
std::string mynull(res->getString(2));
ASSERT(res->isNull(2));
ASSERT(res->wasNull());
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::crash()
{
logMsg("preparedstatement::crash() - MySQL_PreparedStatement::*");
try
{
stmt.reset(con->createStatement());
stmt->execute("DROP TABLE IF EXISTS test");
stmt->execute("CREATE TABLE test(dummy TIMESTAMP, id VARCHAR(1))");
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
pstmt->clearParameters();
pstmt->setDouble(1, 1.23);
ASSERT_EQUALS(1, pstmt->executeUpdate());
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + e.getSQLState());
ASSERT_EQUALS("22001", e.getSQLState());
ASSERT_EQUALS(1406, e.getErrorCode());
logErr("Error of field overflow is expected");
}
}
void preparedstatement::getWarnings()
{
logMsg("preparedstatement::getWarnings() - MySQL_PreparedStatement::get|clearWarnings()");
//TODO: Enable it after fixing
SKIP("Testcase needs to be fixed");
std::stringstream msg;
stmt.reset(con->createStatement());
try
{
stmt->execute("DROP TABLE IF EXISTS test");
stmt->execute("CREATE TABLE test(id INT UNSIGNED)");
// Generating 2 warnings to make sure we get only the last 1 - won't hurt
// Lets hope that this will always cause a 1264 or similar warning
pstmt.reset(con->prepareStatement("INSERT INTO test(id) VALUES (?)"));
pstmt->setInt(1, -2);
pstmt->executeUpdate();
pstmt->setInt(1, -1);
pstmt->executeUpdate();
int count= 0;
for (const sql::SQLWarning* warn=pstmt->getWarnings(); warn; warn=warn->getNextWarning())
{
msg.str("");
msg << "... ErrorCode = '" << warn->getErrorCode() << "', ";
msg << "SQLState = '" << warn->getSQLState() << "', ";
msg << "ErrorMessage = '" << warn->getMessage() << "'";
logMsg(msg.str());
ASSERT((0 != warn->getErrorCode()));
if (1264 == warn->getErrorCode())
{
ASSERT_EQUALS("22003", warn->getSQLState());
}
else
{
ASSERT(("" != warn->getSQLState()));
}
ASSERT(("" != warn->getMessage()));
++count;
}
ASSERT_EQUALS(1, count);
for (const sql::SQLWarning* warn=pstmt->getWarnings(); warn; warn=warn->getNextWarning())
{
msg.str("");
msg << "... ErrorCode = '" << warn->getErrorCode() << "', ";
msg << "SQLState = '" << warn->getSQLState() << "', ";
msg << "ErrorMessage = '" << warn->getMessage() << "'";
logMsg(msg.str());
ASSERT((0 != warn->getErrorCode()));
if (1264 == warn->getErrorCode())
{
ASSERT_EQUALS("22003", warn->getSQLState());
}
else
{
ASSERT(("" != warn->getSQLState()));
}
ASSERT(("" != warn->getMessage()));
}
pstmt->clearWarnings();
for (const sql::SQLWarning* warn=pstmt->getWarnings(); warn; warn=warn->getNextWarning())
{
FAIL("There should be no more warnings!");
}
pstmt->setInt(1, -3);
pstmt->executeUpdate();
ASSERT(pstmt->getWarnings() != NULL);
// Statement without tables access does not reset warnings.
pstmt.reset(con->prepareStatement("SELECT 1"));
res.reset(pstmt->executeQuery());
ASSERT(pstmt->getWarnings() == NULL);
ASSERT(res->next());
// TODO - how to use getNextWarning() ?
stmt->execute("DROP TABLE IF EXISTS test");
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::blob()
{
logMsg("preparedstatement::blob() - MySQL_PreparedStatement::*");
char blob_input[512];
std::stringstream blob_input_stream;
std::stringstream msg;
char blob_output[512];
int id;
int offset=0;
try
{
pstmt.reset(con->prepareStatement("DROP TABLE IF EXISTS test"));
pstmt->execute();
pstmt.reset(con->prepareStatement("CREATE TABLE test(id INT, col1 TINYBLOB, col2 TINYBLOB)"));
pstmt->execute();
// Most basic INSERT/SELECT...
pstmt.reset(con->prepareStatement("INSERT INTO test(id, col1) VALUES (?, ?)"));
for (char ascii_code=CHAR_MIN; ascii_code < CHAR_MAX; ascii_code++)
{
blob_output[offset]='\0';
blob_input[offset++]=ascii_code;
}
blob_input[offset]='\0';
blob_output[offset]='\0';
for (char ascii_code=CHAR_MAX; ascii_code > CHAR_MIN; ascii_code--)
{
blob_output[offset]='\0';
blob_input[offset++]=ascii_code;
}
blob_input[offset]='\0';
blob_output[offset]='\0';
id=1;
blob_input_stream << blob_input;
pstmt->setInt(1, id);
pstmt->setBlob(2, &blob_input_stream);
try
{
pstmt->setBlob(3, &blob_input_stream);
FAIL("Invalid index not detected");
}
catch (sql::SQLException&)
{
}
pstmt->execute();
pstmt.reset(con->prepareStatement("SELECT id, col1 FROM test WHERE id = 1"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
msg.str("");
msg << "... simple INSERT/SELECT, '" << std::endl << blob_input << std::endl << "' =? '" << std::endl << res->getString(2) << "'";
logMsg(msg.str());
ASSERT_EQUALS(res->getInt(1), id);
ASSERT_EQUALS(res->getString(2), blob_input_stream.str());
ASSERT_EQUALS(res->getString(2), blob_input);
ASSERT_EQUALS(res->getString("col1"), blob_input_stream.str());
ASSERT_EQUALS(res->getString("col1"), blob_input);
std::unique_ptr< std::istream > blob_output_stream(res->getBlob(2));
blob_output_stream->seekg(std::ios::beg);
blob_output_stream->get(blob_output, offset + 1);
ASSERT_EQUALS(blob_input_stream.str(), blob_output);
blob_output_stream.reset(res->getBlob("col1"));
blob_output_stream->seekg(std::ios::beg);
blob_output_stream->get(blob_output, offset + 1);
ASSERT_EQUALS(blob_input, blob_output);
msg.str("");
msg << "... second check, '"<< std::endl << blob_input << std::endl << "' =? '" << std::endl << blob_output << "'";
logMsg(msg.str());
ASSERT(!res->next());
res->close();
msg.str("");
// Data is too long to be stored in a TINYBLOB column
msg << "... this is more than TINYBLOB can hold: ";
msg << "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
pstmt.reset(con->prepareStatement("INSERT INTO test(id, col1) VALUES (?, ?)"));
id=2;
pstmt->setInt(1, id);
pstmt->setBlob(2, &msg);
try {
pstmt->execute();
pstmt.reset(con->prepareStatement("SELECT id, col1 FROM test WHERE id = 2"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(res->getInt(1), id);
ASSERT_GT((int)(res->getString(2).length()), (int)(msg.str().length()));
ASSERT(!res->next());
res->close();
msg << "- what has happened to the stream?";
logMsg(msg.str());
}
catch (sql::SQLException & e) {
// If the server is in the strict mode, inserting too long data causes error
if (e.getErrorCode() == 1406 && e.getSQLState().compare("22001") == 0) {
logMsg("The server is in the strict mode - couldn't insert too long stream");
}
else {
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
}
pstmt.reset(con->prepareStatement("DROP TABLE IF EXISTS test"));
pstmt->execute();
}
catch (sql::SQLException &e)
{
logErr(e.what());
logErr("SQLState: " + std::string(e.getSQLState()));
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::executeQuery()
{
logMsg("preparedstatement::executeQuery() - MySQL_PreparedStatement::executeQuery");
try
{
const sql::SQLString option("defaultPreparedStatementResultType");
int value=sql::ResultSet::TYPE_FORWARD_ONLY;
con->setClientOption(option, static_cast<void *> (&value));
}
catch (sql::MethodNotImplementedException &/*e*/)
{
/* not available */
return;
}
try
{
stmt.reset(con->createStatement());
stmt->execute("DROP TABLE IF EXISTS test");
stmt->execute("CREATE TABLE test(id INT UNSIGNED)");
stmt->execute("INSERT INTO test(id) VALUES (1), (2), (3)");
pstmt.reset(con->prepareStatement("SELECT id FROM test ORDER BY id ASC"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(res->getInt("id"), 1);
pstmt.reset(con->prepareStatement("DROP TABLE IF EXISTS test"));
pstmt->execute();
}
catch (sql::SQLException &e)
{
logErr(e.what());
std::stringstream msg;
msg.str("");
msg << "SQLState: " << e.getSQLState() << ", MySQL error code: " << e.getErrorCode();
logErr(msg.str());
fail(e.what(), __FILE__, __LINE__);
}
}
void preparedstatement::addBatch()
{
createSchemaObject("TABLE", "testAddBatchPs", "(id int not NULL)");
pstmt.reset(con->prepareStatement("INSERT INTO testAddBatchPs VALUES(?)"));
pstmt->setInt(1, 1);
pstmt->addBatch();
pstmt->setInt(1, 2);
pstmt->addBatch();
pstmt->setInt(1, 3);
pstmt->addBatch();
const sql::Ints& batchRes = pstmt->executeBatch();
// Checking results in other connection since connector may turn autocommit off and we have to be sure
// that it still commits the batch
stmt.reset(sspsCon->createStatement());
res.reset(stmt->executeQuery("SELECT MIN(id), MAX(id), SUM(id), count(*) FROM testAddBatchPs"));
ASSERT(res->next());
ASSERT_EQUALS(1, res->getInt(1));
ASSERT_EQUALS(3, res->getInt(2));
ASSERT_EQUALS(6, res->getInt(3));
ASSERT_EQUALS(3, res->getInt(4));
ASSERT_EQUALS(3ULL, static_cast<uint64_t>(batchRes.size()));
ASSERT_EQUALS(1, batchRes[0]);
ASSERT_EQUALS(1, batchRes[1]);
ASSERT_EQUALS(1, batchRes[2]);
////// The same, but for executeLargeBatch
stmt->executeUpdate("DELETE FROM testAddBatchPs");
pstmt->clearBatch();
pstmt->clearParameters();
pstmt->setInt(1, 4);
pstmt->addBatch();
pstmt->setInt(1, 5);
pstmt->addBatch();
pstmt->setInt(1, 6);
pstmt->addBatch();
const sql::Longs& batchLRes = pstmt->executeLargeBatch();
res.reset(stmt->executeQuery("SELECT MIN(id), MAX(id), SUM(id), count(*) FROM testAddBatchPs"));
ASSERT(res->next());
ASSERT_EQUALS(4, res->getInt(1));
ASSERT_EQUALS(6, res->getInt(2));
ASSERT_EQUALS(15, res->getInt(3));
ASSERT_EQUALS(3, res->getInt(4));
ASSERT_EQUALS(3ULL, static_cast<uint64_t>(batchLRes.size()));
ASSERT_EQUALS(1LL, batchLRes[0]);
ASSERT_EQUALS(1LL, batchLRes[1]);
ASSERT_EQUALS(1LL, batchLRes[2]);
}
/* Connector wasn't precise enough with double numbers operations and could lose significant digits */
void preparedstatement::bugConcpp96()
{
createSchemaObject("TABLE", "bugConcpp96", "(`as_double` double NOT NULL, `as_string` varchar(20) NOT NULL, `as_decimal` decimal(15,4) NOT NULL)");
sql::SQLString selectQuery("SELECT CAST(? AS DOUBLE), ?, CAST(? AS DECIMAL(15,4))");
pstmt.reset(con->prepareStatement(selectQuery));
ssps.reset(sspsCon->prepareStatement(selectQuery));
PreparedStatement ssps2(sspsCon->prepareStatement("INSERT INTO bugConcpp96 (as_double, as_string, as_decimal) VALUES (?, ?, ?)"));
double numberToInsert(98.765432109), epsilon(0.0001);
do {
pstmt->setDouble(1, numberToInsert);
pstmt->setString(2, std::to_string(numberToInsert));
pstmt->setDouble(3, numberToInsert);
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS_EPSILON(numberToInsert, res->getDouble(1), epsilon);
ASSERT_EQUALS(std::to_string(numberToInsert), res->getString(2));
ASSERT_EQUALS_EPSILON(numberToInsert, res->getDouble(3), epsilon);
ssps->setDouble(1, numberToInsert);
ssps->setString(2, std::to_string(numberToInsert));
ssps->setDouble(3, numberToInsert);
ResultSet res2(ssps->executeQuery());
ASSERT(res2->next());
ASSERT_EQUALS_EPSILON(numberToInsert, res2->getDouble(1), epsilon);
ASSERT_EQUALS(std::to_string(numberToInsert), res2->getString(2));
ASSERT_EQUALS_EPSILON(numberToInsert, res2->getDouble(3), epsilon);
ssps2->setDouble(1, numberToInsert);
ssps2->setString(2, std::to_string(numberToInsert));
ssps2->setDouble(3, numberToInsert);
ASSERT_EQUALS(1, ssps2->executeUpdate());
numberToInsert*= 10.0;
} while (numberToInsert < 1000000000);
res.reset(stmt->executeQuery("SELECT as_double, as_string, as_decimal FROM bugConcpp96 ORDER BY 1 DESC"));
while (res->next()) {
// Initially the value is 10 times bigger as the last inserted value
numberToInsert/= 10.0;
ASSERT_EQUALS_EPSILON(numberToInsert, res->getDouble(1), epsilon);
ASSERT_EQUALS(std::to_string(numberToInsert), res->getString(2));
ASSERT_EQUALS_EPSILON(numberToInsert, res->getDouble(3), epsilon);
}
}
/** Test of rewriteBatchedStatements option. The test does cannot test if the batch is really rewritten, though.
*/
void preparedstatement::concpp99_batchRewrite()
{
sql::ConnectOptionsMap connection_properties{{"userName", user}, {"password", passwd}, {"rewriteBatchedStatements", "true"}, {"useTls", useTls ? "true" : "false"}};
con.reset(driver->connect(url, connection_properties));
// Reading results must be on the different connection to ensure that the driver commits the batch
stmt.reset(sspsCon->createStatement());
createSchemaObject("TABLE", "concpp99_batchRewrite", "(id int not NULL PRIMARY KEY, val VARCHAR(31) NOT NULL DEFAULT '')");
const sql::SQLString insertQuery[]{"INSERT INTO concpp99_batchRewrite VALUES(?,?)",
"INSERT INTO concpp99_batchRewrite(id) VALUES(?) ON DUPLICATE KEY UPDATE val=?"};
const int32_t id[]{1, 2, 3}, batchResult[]{sql::Statement::SUCCESS_NO_INFO, 1};
const sql::SQLString val[][3]{{"X'1", "y\"2", "xxx"}, {"","",""}},
selectQuery("SELECT id, val FROM concpp99_batchRewrite ORDER BY id"),
deleteQuery("DELETE FROM concpp99_batchRewrite");
for (std::size_t i= 0; i < sizeof(insertQuery) / sizeof(insertQuery[0]); ++i) {
pstmt.reset(con->prepareStatement(insertQuery[i]));
for (size_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
pstmt->setInt(1, id[row]);
pstmt->setString(2, val[i][row]);
pstmt->addBatch();
}
const sql::Ints& batchRes = pstmt->executeBatch();
ASSERT_EQUALS(static_cast<uint64_t>(sizeof(id) / sizeof(id[0])), static_cast<uint64_t>(batchRes.size()));
res.reset(stmt->executeQuery(selectQuery));
for (size_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
ASSERT(res->next());
ASSERT_EQUALS(id[row], res->getInt(1));
ASSERT_EQUALS(val[i][row], res->getString(2));
// With rewriteBatchedStatements we don't have separate results for each parameters set - only SUCCESS_NO_INFO
ASSERT_EQUALS(batchResult[i], batchRes[row]);
}
ASSERT(!res->next());
////// The same, but for executeLargeBatch
stmt->executeUpdate(deleteQuery);
pstmt->clearBatch();
pstmt->clearParameters();
for (size_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
pstmt->setInt(1, id[row] + 3);
pstmt->setString(2, val[0][row]);
pstmt->addBatch();
}
const sql::Longs& batchLRes = pstmt->executeLargeBatch();
ASSERT_EQUALS(3ULL, static_cast<uint64_t>(batchLRes.size()));
res.reset(stmt->executeQuery(selectQuery));
for (size_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
ASSERT(res->next());
ASSERT_EQUALS(id[row] + 3, res->getInt(1));
ASSERT_EQUALS(val[i][row], res->getString(2));
// With rewriteBatchedStatements we don't have separate results for each parameters set - only SUCCESS_NO_INFO
ASSERT_EQUALS(static_cast<int64_t>(batchResult[i]), batchLRes[row]);
}
ASSERT(!res->next());
stmt->executeUpdate(deleteQuery);
}
// To make sure the framework provides next test with "standard" connection
con.reset();
}
/** Test of useBulkStmts option. The test does cannot test if the batch is really rewritten, though.
*/
void preparedstatement::concpp106_batchBulk()
{
sql::ConnectOptionsMap connection_properties{ {"userName", user}, {"password", passwd}, {"useBulkStmts", "true"}, {"useTls", useTls ? "true" : "false"} };
con.reset(driver->connect(url, connection_properties));
// Reading results must be on the different connection to ensure that the driver commits the batch
stmt.reset(sspsCon->createStatement());
createSchemaObject("TABLE", "concpp106_batchBulk", "(id int not NULL PRIMARY KEY, val VARCHAR(31))");
const sql::SQLString insertQuery[]{ "INSERT INTO concpp106_batchBulk VALUES(?,?)",
"INSERT INTO concpp106_batchBulk(id) VALUES(?) ON DUPLICATE KEY UPDATE val=?" };
const int32_t id[]{1, 2, 5, 3}, batchResult[]{ sql::Statement::SUCCESS_NO_INFO, sql::Statement::SUCCESS_NO_INFO }, id_expected[]{1,2,3,5};
const char* val[][4]{{nullptr , "X'1", "y\"2", "xxx"}, {nullptr, nullptr, nullptr, nullptr}},
*val_expected[][4]{{nullptr, "X'1", "xxx", "y\"2"}, {nullptr, nullptr, nullptr, nullptr}};
const sql::SQLString selectQuery("SELECT id, val FROM concpp106_batchBulk ORDER BY id"),
deleteQuery("DELETE FROM concpp106_batchBulk");
for (std::size_t i = 0; i < sizeof(insertQuery) / sizeof(insertQuery[0]); ++i) {
pstmt.reset(con->prepareStatement(insertQuery[i]));
//ssps.reset(sspsCon->prepareStatement(insertQuery[i]));
for (uint32_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
pstmt->setInt(1, id[row]);
if (val[i][row] != nullptr) {
pstmt->setString(2, val[0][row]);
}
else {
pstmt->setNull(2, sql::Types::VARCHAR);
}
pstmt->addBatch();
}
logMsg("Executing batch");
const sql::Ints& batchRes = pstmt->executeBatch();
logMsg("Executing batch - finished");
ASSERT_EQUALS(static_cast<uint64_t>(sizeof(id) / sizeof(id[0])), static_cast<uint64_t>(batchRes.size()));
res.reset(stmt->executeQuery(selectQuery));
for (uint32_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
ASSERT(res->next());
ASSERT_EQUALS(id_expected[row], res->getInt(1));
if (val_expected[i][row] == nullptr) {
ASSERT(res->isNull(2));
/* SQLString does not have 3rd "null" state, thus for null it returns empty string */
ASSERT_EQUALS("", res->getString(2));
}
else {
ASSERT_EQUALS(val_expected[i][row], res->getString(2));
}
// With bulk we don't have separate results for each parameters set - only SUCCESS_NO_INFO.
// Unless with mysql where it is not supported
ASSERT_EQUALS(isMySQL() ? 1 : batchResult[i], batchRes[row]);
}
ASSERT(!res->next());
////// The same, but for executeLargeBatch
stmt->executeUpdate(deleteQuery);
//const sql::Ints& batchRes2= ssps->executeBatch();
pstmt->clearBatch();
pstmt->clearParameters();
for (uint32_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
pstmt->setInt(1, id[row] + 3);
if (val[i][row] != nullptr) {
pstmt->setString(2, val[0][row]);
}
else {
pstmt->setNull(2, sql::Types::VARCHAR);
}
pstmt->addBatch();
}
logMsg("Executing largeBatch");
const sql::Longs& batchLRes = pstmt->executeLargeBatch();
logMsg("Executing largeBatch - finished");
ASSERT_EQUALS(static_cast<uint64_t>(sizeof(id) / sizeof(id[0])), static_cast<uint64_t>(batchLRes.size()));
res.reset(stmt->executeQuery(selectQuery));
for (uint32_t row = 0; row < sizeof(id) / sizeof(id[0]); ++row) {
ASSERT(res->next());
ASSERT_EQUALS(id_expected[row] + 3, res->getInt(1));
if (val_expected[i][row] == nullptr) {
ASSERT(res->isNull(2));
/* SQLString does not have 3rd "null" state, thus for null it returns empty string */
ASSERT_EQUALS("", res->getString(2));
}
else {
ASSERT_EQUALS(val_expected[i][row], res->getString(2));
}
// With rewriteBatchedStatements we don't have separate results for each parameters set - only SUCCESS_NO_INFO
ASSERT_EQUALS(isMySQL() ? 1LL : static_cast<int64_t>(batchResult[i]), batchLRes[row]);
}
ASSERT(!res->next());
stmt->executeUpdate(deleteQuery);
}
// To make sure the framework provides next test with "standard" connection
con.reset();
}
void preparedstatement::concpp116_getByte()
{
pstmt.reset(sspsCon->prepareStatement("SELECT ?"));
// Check for all target locations of the segmentation fault
for (int8_t i = 0; i < 16; ++i)
{
int8_t value= i << 4;
pstmt->setByte(1, value);
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(value, res->getByte(1));
}
pstmt.reset(sspsCon->prepareStatement("SELECT '-128', 0xA1B2C3D4, 0x81, 0x881"));
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(-128, res->getByte(1));
ASSERT_EQUALS(int32_t(0xA1B2C3D4), res->getInt(2));
ASSERT_EQUALS(static_cast<int8_t>(129), res->getByte(3));
ASSERT_EQUALS(static_cast<int16_t>(129), res->getShort(3));
ASSERT_EQUALS(129, res->getInt(3));
ASSERT_EQUALS(129LL, res->getLong(3));
ASSERT_EQUALS(129, res->getUInt(3));
ASSERT_EQUALS(129ULL, res->getUInt64(3));
ASSERT_EQUALS(static_cast<int16_t>(2177), res->getShort(4));//0x881=2177
ASSERT_EQUALS(2177, res->getInt(4));
ASSERT_EQUALS(2177U, res->getUInt(4));
ASSERT_EQUALS(2177ULL, res->getUInt64(4));
ASSERT_EQUALS(2177LL, res->getLong(4));
}
/* CONCPP - 133 */
void preparedstatement::multirs_caching()
{
//SKIP("This is not working and won't be fixed in this version");
createSchemaObject("PROCEDURE", "ccpptest_multirs_caching", "()\
BEGIN\
SELECT 1 as id, 'text' as val UNION SELECT 7 as id, 'seven' as val;\
SELECT 'some text';\
SELECT 2;\
END");
PreparedStatement pstmt1(sspsCon->prepareStatement("CALL ccpptest_multirs_caching()"));
ResultSet res1(pstmt1->executeQuery());
ASSERT(res1->next()); // next() does not read the record - only increments internal cursor position
/* We need stmt to use the same conneciton as pstmt1*/
stmt.reset(sspsCon->createStatement());
/* Executing another query - stmt1 has to cache pending results */
res.reset(stmt->executeQuery("SELECT 100"));
/* Making sure we are at same position after caching */
ASSERT_EQUALS(1, res1->getInt(1));
ASSERT_EQUALS("text", res1->getString("val"));
ASSERT(res1->next());
ASSERT_EQUALS(7, res1->getInt("id"));
ASSERT_EQUALS("seven", res1->getString(2));
ASSERT(!res1->next());
ASSERT(pstmt1->getMoreResults());
res1.reset(pstmt1->getResultSet());
ASSERT(res1->next());
ASSERT_EQUALS("some text", res1->getString(1));
ASSERT(!res1->next());
/* Now reading 2nd query result*/
ASSERT(res->next());
ASSERT_EQUALS(100, res->getInt(1));
ASSERT(!res->next());
ASSERT(!stmt->getMoreResults());
ASSERT_EQUALS(-1, stmt->getUpdateCount());
/* Getting back to 1st query */
ASSERT(pstmt1->getMoreResults());
res1.reset(pstmt1->getResultSet());
ASSERT(res1->next());
ASSERT_EQUALS(2, res1->getInt(1));
ASSERT(!res1->next());
// Now SP execution result code
ASSERT(!pstmt1->getMoreResults());
ASSERT_EQUALS(0, pstmt1->getUpdateCount());
// Nothing else
ASSERT(!pstmt1->getMoreResults());
ASSERT_EQUALS(-1, pstmt1->getUpdateCount());
}
void preparedstatement::bytesArrParam()
{
pstmt.reset(con->prepareStatement("SELECT ?"));
char charArray[3]= {'\1', '\0', '\1'};
sql::bytes sqlBytes(charArray, 3), b2{'\1', '\0', '\2'};
b2[2]= '\0';
// sqlBytes has internally a negative length, i.e. it does not own the array. let's see if it throws
pstmt->setBytes(1, &sqlBytes);
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(65537, res->getInt(1));
// b2 owns the array and internal length is positive - checking it's also alright
pstmt->setBytes(1, &b2);
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(65536, res->getInt(1));
sqlBytes[0]= '\0';
// Just to show, that original array has been changed
ASSERT_EQUALS('\0', charArray[0]);
pstmt->setBytes(1, &sqlBytes);
res.reset(pstmt->executeQuery());
ASSERT(res->next());
ASSERT_EQUALS(1, res->getInt(1));
}
} /* namespace preparedstatement */
} /* namespace testsuite */