From 3adfeac614fc6ad3c9f7edd6c9edbf098ab4c60d Mon Sep 17 00:00:00 2001 From: Steven Murray <steven.murray@cern.ch> Date: Thu, 19 May 2016 18:26:00 +0200 Subject: [PATCH] Added unit-tests for OcciRset::bind() --- catalogue/OcciRset.hpp | 8 +-- catalogue/OcciRsetTest.cpp | 31 +++++++++-- catalogue/OcciStmt.cpp | 104 ++++++++++++++++++++++++++++++++++--- catalogue/OcciStmt.hpp | 17 ++++++ 4 files changed, 145 insertions(+), 15 deletions(-) diff --git a/catalogue/OcciRset.hpp b/catalogue/OcciRset.hpp index 326a5eb152..b4f600976d 100644 --- a/catalogue/OcciRset.hpp +++ b/catalogue/OcciRset.hpp @@ -151,8 +151,8 @@ private: /** * Forward declaration of the nested class ColumnNameIdx that is intentionally - * hidden in the cpp file of the SqliteRset class. The class is hidden in - * order to enable the SqliteRset class to be used by code compiled against + * hidden in the cpp file of the OcciRset class. The class is hidden in + * order to enable the OcciRset class to be used by code compiled against * the CXX11 ABI and used by code compiled against the pre-CXX11 ABI. */ class ColumnNameToIdx; @@ -168,8 +168,8 @@ private: /** * Forward declaration of the nest class TextColumnCache that is intentionally - * hidden in the cpp file of the SqliteRset class. The class is hidden in - * order to enable the SqliteRset class to be used by code compiled against + * hidden in the cpp file of the OcciRset class. The class is hidden in + * order to enable the OcciRset class to be used by code compiled against * the CXX11 ABI and used by code compiled against the pre-CXX11 ABI. */ class TextColumnCache; diff --git a/catalogue/OcciRsetTest.cpp b/catalogue/OcciRsetTest.cpp index 75ba12b9da..e7bf61ce79 100644 --- a/catalogue/OcciRsetTest.cpp +++ b/catalogue/OcciRsetTest.cpp @@ -95,9 +95,8 @@ TEST_F(cta_catalogue_OcciRsetTest, executeQuery_uint32_t) { ASSERT_FALSE(rset->next()); } -// TODO - Implement 64-bit int test because the current code will fail +// TODO - Implement 64-bit int executeQuery test because the current code will fail -/* TEST_F(cta_catalogue_OcciRsetTest, bind_c_string) { using namespace cta; using namespace cta::catalogue; @@ -110,12 +109,34 @@ TEST_F(cta_catalogue_OcciRsetTest, bind_c_string) { dbLogin.database.c_str())); const char *const sql = "SELECT DUMMY FROM DUAL WHERE DUMMY = :DUMMY"; std::unique_ptr<DbStmt> stmt(conn->createStmt(sql)); + stmt->bind(":DUMMY", "X"); std::unique_ptr<DbRset> rset(stmt->executeQuery()); ASSERT_TRUE(rset->next()); - const uint32_t i = rset->columnUint64("I"); - ASSERT_EQ(1234, i); + std::string text(rset->columnText("DUMMY")); + ASSERT_EQ(std::string("X"), text); ASSERT_FALSE(rset->next()); } -*/ + +TEST_F(cta_catalogue_OcciRsetTest, bind_uint32_t) { + using namespace cta; + using namespace cta::catalogue; + + const DbLogin dbLogin = DbLogin::readFromFile(g_cmdLineArgs.oraDbConnFile); + OcciEnv env; + std::unique_ptr<OcciConn> conn(env.createConn( + dbLogin.username.c_str(), + dbLogin.password.c_str(), + dbLogin.database.c_str())); + const char *const sql = "SELECT :N AS AN_UNSIGNED_INT FROM DUAL"; + std::unique_ptr<DbStmt> stmt(conn->createStmt(sql)); + stmt->bind(":N", 1234); + std::unique_ptr<DbRset> rset(stmt->executeQuery()); + ASSERT_TRUE(rset->next()); + const uint32_t n = rset->columnUint64("AN_UNSIGNED_INT"); + ASSERT_EQ(1234, n); + ASSERT_FALSE(rset->next()); +} + +// TODO - Implement 64-bit int bind test because the current code will fail } // namespace unitTests diff --git a/catalogue/OcciStmt.cpp b/catalogue/OcciStmt.cpp index 9c9aa3da18..6daa0d8f70 100644 --- a/catalogue/OcciStmt.cpp +++ b/catalogue/OcciStmt.cpp @@ -24,11 +24,94 @@ #include "catalogue/OcciStmt.hpp" #include <cstring> +#include <iostream> +#include <map> +#include <sstream> #include <stdexcept> namespace cta { namespace catalogue { +class OcciStmt::ParamNameToIdx { +public: + /** + * Constructor. + * + * Parses the specified SQL statement to populate an internal map from SQL + * parameter name to parameter index. + * + * @param sql The SQL statement to be parsed for SQL parameter names. + */ + ParamNameToIdx(const char *const sql) { + bool waitingForAParam = true; + std::ostringstream paramName; + unsigned int paramIdx = 1; + + for(const char *ptr = sql; ; ptr++) { + if(waitingForAParam) { + if('\0' == *ptr) { + break; + } else if(':' == *ptr) { + waitingForAParam = false; + paramName << ":"; + } + } else { + if(!isValidParamNameChar(*ptr)) { + if(paramName.str().empty()) { + throw std::runtime_error("Parse error: Empty SQL parameter name"); + } + if(m_nameToIdx.find(paramName.str()) != m_nameToIdx.end()) { + throw std::runtime_error("Parse error: SQL parameter " + paramName.str() + " is a duplicate"); + } + m_nameToIdx[paramName.str()] = paramIdx; + paramName.clear(); + paramIdx++; + waitingForAParam = true; + } + + if('\0' == *ptr) { + break; + } + + if(':' == *ptr) { + throw std::runtime_error("Parse error: Consecutive SQL parameter names are not permitted"); + } else { + paramName << *ptr; + } + } + } + } + + /** + * Returns the index of teh specified SQL parameter. + * + * @param paramNAme The name of the SQL parameter. + * @return The index of the SQL parameter. + */ + unsigned int getIdx(const char *const paramName) const { + auto itor = m_nameToIdx.find(paramName); + if(itor == m_nameToIdx.end()) { + throw std::runtime_error(std::string(__FUNCTION__) + " failed: The SQL parameter " + paramName + + " does not exist"); + } + return itor->second; + } + +private: + + /** + * Map from SQL parameter name to parameter index. + */ + std::map<std::string, unsigned int> m_nameToIdx; + + bool isValidParamNameChar(const char c) { + return ('0' <= c && c <= '9') || + ('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z') || + c == '_'; + } +}; + //------------------------------------------------------------------------------ // constructor //------------------------------------------------------------------------------ @@ -48,6 +131,8 @@ OcciStmt::OcciStmt(const char *const sql, OcciConn &conn, oracle::occi::Statemen m_sql.reset(new char[sqlLen + 1]); std::memcpy(m_sql.get(), sql, sqlLen); m_sql[sqlLen] = '\0'; + + m_paramNameToIdx.reset(new ParamNameToIdx(sql)); } //------------------------------------------------------------------------------ @@ -84,16 +169,24 @@ const char *OcciStmt::getSql() const { // bind //------------------------------------------------------------------------------ void OcciStmt::bind(const char *paramName, const uint64_t paramValue) { - std::runtime_error ex(std::string(__FUNCTION__) + " is not implemented"); - throw ex; + try { + const unsigned paramIdx = m_paramNameToIdx->getIdx(paramName); + m_stmt->setUInt(paramIdx, paramValue); + } catch(std::exception &ne) { + throw std::runtime_error(std::string(__FUNCTION__) + " failed: " + ne.what()); + } } //------------------------------------------------------------------------------ // bind //------------------------------------------------------------------------------ void OcciStmt::bind(const char *paramName, const char *paramValue) { - std::runtime_error ex(std::string(__FUNCTION__) + " is not implemented"); - throw ex; + try { + const unsigned paramIdx = m_paramNameToIdx->getIdx(paramName); + m_stmt->setString(paramIdx, paramValue); + } catch(std::exception &ne) { + throw std::runtime_error(std::string(__FUNCTION__) + " failed: " + ne.what()); + } } //------------------------------------------------------------------------------ @@ -105,8 +198,7 @@ DbRset *OcciStmt::executeQuery() { try { return new OcciRset(*this, m_stmt->executeQuery()); } catch(std::exception &ne) { - throw std::runtime_error(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + - ": " + ne.what()); + throw std::runtime_error(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + ": " + ne.what()); } } diff --git a/catalogue/OcciStmt.hpp b/catalogue/OcciStmt.hpp index efc1a6db6b..1aadf6061c 100644 --- a/catalogue/OcciStmt.hpp +++ b/catalogue/OcciStmt.hpp @@ -139,6 +139,23 @@ private: */ std::unique_ptr<char[]> m_sql; + /** + * Forward declaration of the nested class ParamNameToIdx that is intentionally + * hidden in the cpp file of the OcciStmt class. The class is hidden in + * order to enable the OcciStmt class to be used by code compiled against + * the CXX11 ABI and used by code compiled against the pre-CXX11 ABI. + */ + class ParamNameToIdx; + + /** + * Map from SQL parameter name to parameter index. + * + * Please note that the type of the map is intentionally forward declared in + * order to avoid std::string being used. This is to aid with working with + * pre and post CXX11 ABIs. + */ + std::unique_ptr<ParamNameToIdx> m_paramNameToIdx; + /** * The database connection. */ -- GitLab