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