diff Framework/PostgreSQL/PostgreSQLStatement.cpp @ 0:7cea966b6829

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 04 Jul 2018 08:16:29 +0200
parents
children b2ff1cd2907a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/PostgreSQL/PostgreSQLStatement.cpp	Wed Jul 04 08:16:29 2018 +0200
@@ -0,0 +1,541 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLStatement.h"
+
+#include "../Common/BinaryStringValue.h"
+#include "../Common/FileValue.h"
+#include "../Common/Integer64Value.h"
+#include "../Common/NullValue.h"
+#include "../Common/ResultBase.h"
+#include "../Common/Utf8StringValue.h"
+#include "PostgreSQLResult.h"
+
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Toolbox.h>
+
+#include <cassert>
+
+// PostgreSQL includes
+#include <libpq-fe.h>
+#include <c.h>
+#include <catalog/pg_type.h>
+
+#include <Core/Endianness.h>
+
+
+namespace OrthancDatabases
+{
+  class PostgreSQLStatement::Inputs : public boost::noncopyable
+  {
+  private:
+    std::vector<char*> values_;
+    std::vector<int> sizes_;
+
+    static char* Allocate(const void* source, int size)
+    {
+      if (size == 0)
+      {
+        return NULL;
+      }
+      else
+      {
+        char* ptr = reinterpret_cast<char*>(malloc(size));
+
+        if (source != NULL)
+        {
+          memcpy(ptr, source, size);
+        }
+
+        return ptr;
+      }
+    }
+
+    void Resize(size_t size)
+    {
+      // Shrinking of the vector
+      for (size_t i = size; i < values_.size(); i++)
+      {
+        if (values_[i] != NULL)
+          free(values_[i]);
+      }
+
+      values_.resize(size, NULL);
+      sizes_.resize(size, 0);
+    }
+
+    void EnlargeForIndex(size_t index)
+    {
+      if (index >= values_.size())
+      {
+        // The vector is too small
+        Resize(index + 1);
+      }
+    }
+
+  public:
+    Inputs()
+    {
+    }
+
+    ~Inputs()
+    {
+      Resize(0);
+    }
+
+    void SetItem(size_t pos, const void* source, int size)
+    {
+      EnlargeForIndex(pos);
+
+      if (sizes_[pos] == size)
+      {
+        if (source && size != 0)
+        {
+          memcpy(values_[pos], source, size);
+        }
+      }
+      else
+      {
+        if (values_[pos] != NULL)
+        {
+          free(values_[pos]);
+        }
+
+        values_[pos] = Allocate(source, size);
+        sizes_[pos] = size;
+      }
+    }
+
+    void SetItem(size_t pos, int size)
+    {
+      SetItem(pos, NULL, size);
+    }
+
+    void* GetItem(size_t pos) const
+    {
+      if (pos >= values_.size())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+
+      return values_[pos];
+    }
+
+    const std::vector<char*>& GetValues() const
+    {
+      return values_;
+    }
+
+    const std::vector<int>& GetSizes() const
+    {
+      return sizes_;
+    }
+  };
+
+
+  void PostgreSQLStatement::Prepare()
+  {
+    if (id_.size() > 0)
+    {
+      // Already prepared
+      return;
+    }
+
+    for (size_t i = 0; i < oids_.size(); i++)
+    {
+      if (oids_[i] == 0)
+      {
+        // The type of an input parameter was not set
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    id_ = Orthanc::Toolbox::GenerateUuid();
+
+    const unsigned int* tmp = oids_.size() ? &oids_[0] : NULL;
+
+    PGresult* result = PQprepare(reinterpret_cast<PGconn*>(database_.pg_),
+                                 id_.c_str(), sql_.c_str(), oids_.size(), tmp);
+
+    if (result == NULL)
+    {
+      id_.clear();
+      database_.ThrowException(true);
+    }
+
+    bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK);
+    if (ok)
+    {
+      PQclear(result);
+    }
+    else
+    {
+      std::string message = PQresultErrorMessage(result);
+      PQclear(result);
+      id_.clear();
+      LOG(ERROR) << "PostgreSQL error: " << message;
+      database_.ThrowException(false);
+    }
+  }
+
+
+  void PostgreSQLStatement::Unprepare()
+  {
+    if (id_.size() > 0)
+    {
+      // "Although there is no libpq function for deleting a
+      // prepared statement, the SQL DEALLOCATE statement can be
+      // used for that purpose."
+      //database_.Execute("DEALLOCATE " + id_);
+    }
+
+    id_.clear();
+  }
+
+
+  void PostgreSQLStatement::DeclareInputInternal(unsigned int param,
+                                                 unsigned int /*Oid*/ type)
+  {
+    Unprepare();
+
+    if (oids_.size() <= param)
+    {
+      oids_.resize(param + 1, 0);
+      binary_.resize(param + 1);
+    }
+
+    oids_[param] = type;
+    binary_[param] = (type == TEXTOID || type == BYTEAOID || type == OIDOID) ? 0 : 1;
+  }
+
+
+  void PostgreSQLStatement::DeclareInputInteger(unsigned int param)
+  {
+    DeclareInputInternal(param, INT4OID);
+  }
+    
+
+  void PostgreSQLStatement::DeclareInputInteger64(unsigned int param)
+  {
+    DeclareInputInternal(param, INT8OID);
+  }
+
+
+  void PostgreSQLStatement::DeclareInputString(unsigned int param)
+  {
+    DeclareInputInternal(param, TEXTOID);
+  }
+
+
+  void PostgreSQLStatement::DeclareInputBinary(unsigned int param)
+  {
+    DeclareInputInternal(param, BYTEAOID);
+  }
+
+
+  void PostgreSQLStatement::DeclareInputLargeObject(unsigned int param)
+  {
+    DeclareInputInternal(param, OIDOID);
+  }
+
+
+  void* /* PGresult* */ PostgreSQLStatement::Execute()
+  {
+    Prepare();
+
+    PGresult* result;
+
+    if (oids_.size() == 0)
+    {
+      // No parameter
+      result = PQexecPrepared(reinterpret_cast<PGconn*>(database_.pg_),
+                              id_.c_str(), 0, NULL, NULL, NULL, 1);
+    }
+    else
+    {
+      // At least 1 parameter
+      result = PQexecPrepared(reinterpret_cast<PGconn*>(database_.pg_),
+                              id_.c_str(),
+                              oids_.size(),
+                              &inputs_->GetValues()[0],
+                              &inputs_->GetSizes()[0],
+                              &binary_[0],
+                              1);
+    }
+
+    if (result == NULL)
+    {
+      database_.ThrowException(true);
+    }
+
+    return result;
+  }
+
+
+  PostgreSQLStatement::PostgreSQLStatement(PostgreSQLDatabase& database,
+                                           const std::string& sql,
+                                           bool readOnly) :
+    database_(database),
+    readOnly_(readOnly),
+    sql_(sql),
+    inputs_(new Inputs),
+    formatter_(Dialect_PostgreSQL)
+  {
+    LOG(TRACE) << "PostgreSQL: " << sql;
+  }
+
+
+  PostgreSQLStatement::PostgreSQLStatement(PostgreSQLDatabase& database,
+                                           const Query& query) :
+    database_(database),
+    readOnly_(query.IsReadOnly()),
+    inputs_(new Inputs),
+    formatter_(Dialect_PostgreSQL)
+  {
+    query.Format(sql_, formatter_);
+    LOG(TRACE) << "PostgreSQL: " << sql_;
+
+    for (size_t i = 0; i < formatter_.GetParametersCount(); i++)
+    {
+      switch (formatter_.GetParameterType(i))
+      {
+        case ValueType_Integer64:
+          DeclareInputInteger64(i);
+          break;
+
+        case ValueType_Utf8String:
+          DeclareInputString(i);
+          break;
+
+        case ValueType_BinaryString:
+          DeclareInputBinary(i);
+          break;
+
+        case ValueType_File:
+          DeclareInputLargeObject(i);
+          break;
+
+        case ValueType_Null:
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+  }
+
+
+  void PostgreSQLStatement::Run()
+  {
+    PGresult* result = reinterpret_cast<PGresult*>(Execute());
+    assert(result != NULL);   // An exception would have been thrown otherwise
+
+    bool ok = (PQresultStatus(result) == PGRES_COMMAND_OK ||
+               PQresultStatus(result) == PGRES_TUPLES_OK);
+    if (ok)
+    {
+      PQclear(result);
+    }
+    else
+    {
+      std::string error = PQresultErrorMessage(result);
+      PQclear(result);
+      LOG(ERROR) << "PostgreSQL error: " << error;
+      database_.ThrowException(false);
+    }
+  }
+
+
+  void PostgreSQLStatement::BindNull(unsigned int param)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    inputs_->SetItem(param, 0);
+  }
+
+
+  void PostgreSQLStatement::BindInteger(unsigned int param,
+                                        int value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != INT4OID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    assert(sizeof(int32_t) == 4);
+    int32_t v = htobe32(static_cast<int32_t>(value));
+    inputs_->SetItem(param, &v, sizeof(int32_t));
+  }
+
+
+  void PostgreSQLStatement::BindInteger64(unsigned int param,
+                                          int64_t value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != INT8OID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    assert(sizeof(int64_t) == 8);
+    int64_t v = htobe64(value);
+    inputs_->SetItem(param, &v, sizeof(int64_t));
+  }
+
+
+  void PostgreSQLStatement::BindString(unsigned int param,
+                                       const std::string& value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != TEXTOID && oids_[param] != BYTEAOID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    if (value.size() == 0)
+    {
+      inputs_->SetItem(param, "", 1 /* end-of-string character */);
+    }
+    else
+    {
+      inputs_->SetItem(param, value.c_str(), 
+                       value.size() + 1);  // "+1" for end-of-string character
+    }
+  }
+
+
+  void PostgreSQLStatement::BindLargeObject(unsigned int param,
+                                            const PostgreSQLLargeObject& value)
+  {
+    if (param >= oids_.size())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (oids_[param] != OIDOID)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType);
+    }
+
+    inputs_->SetItem(param, value.GetOid().c_str(), 
+                     value.GetOid().size() + 1);  // "+1" for end-of-string character
+  }
+
+
+  class PostgreSQLStatement::ResultWrapper : public ResultBase
+  {
+  private:
+    std::auto_ptr<PostgreSQLResult>  result_;
+
+  protected:
+    virtual IValue* FetchField(size_t index)
+    {
+      return result_->GetValue(index);
+    }
+
+  public:
+    ResultWrapper(PostgreSQLStatement& statement) :
+      result_(new PostgreSQLResult(statement))
+    {
+      SetFieldsCount(result_->GetColumnsCount());
+      FetchFields();
+    }
+
+    virtual void Next()
+    {
+      result_->Next();
+      FetchFields();
+    }
+
+    virtual bool IsDone() const
+    {
+      return result_->IsDone();
+    }
+  };
+
+
+  IResult* PostgreSQLStatement::Execute(PostgreSQLTransaction& transaction,
+                                        const Dictionary& parameters)
+  {
+    for (size_t i = 0; i < formatter_.GetParametersCount(); i++)
+    {
+      const std::string& name = formatter_.GetParameterName(i);
+      
+      switch (formatter_.GetParameterType(i))
+      {
+        case ValueType_Integer64:
+          BindInteger64(i, dynamic_cast<const Integer64Value&>(parameters.GetValue(name)).GetValue());
+          break;
+
+        case ValueType_Null:
+          BindNull(i);
+          break;
+
+        case ValueType_Utf8String:
+          BindString(i, dynamic_cast<const Utf8StringValue&>
+                     (parameters.GetValue(name)).GetContent());
+          break;
+
+        case ValueType_BinaryString:
+          BindString(i, dynamic_cast<const BinaryStringValue&>
+                     (parameters.GetValue(name)).GetContent());
+          break;
+
+        case ValueType_File:
+        {
+          const FileValue& blob =
+            dynamic_cast<const FileValue&>(parameters.GetValue(name));
+
+          PostgreSQLLargeObject largeObject(database_, blob.GetContent());
+          BindLargeObject(i, largeObject);
+          break;
+        }
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    return new ResultWrapper(*this);
+  }
+
+
+  void PostgreSQLStatement::ExecuteWithoutResult(PostgreSQLTransaction& transaction,
+                                                 const Dictionary& parameters)
+  {
+    std::auto_ptr<IResult> dummy(Execute(transaction, parameters));
+  }
+}