From 18e75dc2d90845a403a5be54bfb18e2a339f702d Mon Sep 17 00:00:00 2001 From: Evgenii Zhemchugov Date: Tue, 19 Dec 2023 15:33:16 +0000 Subject: [PATCH 1/4] PSQLInterface: implement timeouts and Store& parameters 1. Constructor now accepts the default timeout. Each function takes an optional timeout. 2. Overload SQLQuery to accomodate usage with no results expected, result returned as a string (single row), results returned as a Store (single row). 3. Overload SendMonitoringData, SendConfig, GetConfig to work with a Store payload. 4. Escape quotes in all strings sent to the database. --- PGStarter/DM/PSQLInterface.cpp | 368 +++++++++++++++++++++------------ PGStarter/DM/PSQLInterface.h | 173 ++++++++++++++-- 2 files changed, 392 insertions(+), 149 deletions(-) diff --git a/PGStarter/DM/PSQLInterface.cpp b/PGStarter/DM/PSQLInterface.cpp index 327e77a..67ee4bc 100644 --- a/PGStarter/DM/PSQLInterface.cpp +++ b/PGStarter/DM/PSQLInterface.cpp @@ -1,147 +1,259 @@ -#include +#include -PSQLInterface::PSQLInterface(){} -PSQLInterface::~PSQLInterface(){} +#include "PSQLInterface.h" - -bool PSQLInterface::Initialise(zmq::context_t* context, std::string device_name, std::string config_file){ - - m_context=context; - - m_name=device_name; +bool PSQLInterface::Initialise( + zmq::context_t* context, + std::string device_name, + std::string config_file, + int timeout +) { + m_context = context; + m_name = std::move(device_name); + m_dbname = "daq"; + m_timeout = timeout; m_pgclient.SetUp(m_context); - - m_dbname="daq"; - - if(!m_pgclient.Initialise(config_file)){ - - std::cout<<"error initialising pgclient"<> json; + return SendMonitoringData(json, device, timeout); +}; + +bool PSQLInterface::SendConfig( + std::string json, + std::string author, + int version, + const std::string& device, + int timeout +) { + std::stringstream query; + query + << "insert into device_config (time, device, version, author, data) " + "values (now(), '" + << Quote(device.empty() ? m_name : device) + << "', " + << version + << ", '" + << Quote(std::move(author)) + << "', '" + << Quote(std::move(json)) + << "')"; + return Query("SendConfig", query.str(), nullptr, timeout); +}; + +bool PSQLInterface::SendConfig( + Store& config, + const std::string& author, + int version, + const std::string& device, + int timeout +) { + std::string json; + config >> json; + return SendConfig(json, author, version, device, timeout); +}; + +bool PSQLInterface::GetConfig( + std::string& json, + int version, + const std::string& device, + int timeout +) { + std::stringstream query; + query + << "select data from device_config where device = '" + << Quote(device.empty() ? m_name : device) + << "' and version = " + << version; + std::string data; + if (!Query("GetConfig", query.str(), &data, timeout)) return false; - } - - return true; -} - -bool PSQLInterface::SendMonitoringData(std::string json_data, std::string device){ - - if(device=="") device=m_name; - std::string err=""; - int timeout=300; - std::string result; - std::string query_string="insert into monitoring (time, name, data) values (now(), '" + device + "', '" + json_data + "');"; - - if(!SQLQuery(m_dbname , query_string, result, timeout, err)){ - std::cerr<<"SendMonitoringData error: "< 0) { + string.insert(0, dsize, ' '); + auto dst = string.begin(); + auto src = dst + dsize; + do { + if (*src == '\'') *dst++ = *src; + *dst++ = *src++; + } while (dst != src); + }; + + return std::move(string); +}; diff --git a/PGStarter/DM/PSQLInterface.h b/PGStarter/DM/PSQLInterface.h index faa8488..78a3915 100644 --- a/PGStarter/DM/PSQLInterface.h +++ b/PGStarter/DM/PSQLInterface.h @@ -1,34 +1,165 @@ #ifndef PSQL_INTERFACE_H #define PSQL_INTERFACE_H -#include #include -#include -#include +#include + +#include "PGClient.h" + +class PSQLInterface { + private: + zmq::context_t* m_context; + PGClient m_pgclient; + std::string m_dbname; + std::string m_name; + int m_timeout; + + public: + bool Initialise( + zmq::context_t*, + std::string device_name, + std::string config_file, + int timeout = 300 /* ms */ + ); + + bool Finalise(); + + // For the methods of this class, negative timeout means the default + // timeout, zero timeout means no timeout + + bool SQLQuery( + std::string dbname, + std::string query, + std::string* error = nullptr, + int timeout = -1 + ) { + return Query( + std::move(dbname), std::move(query), nullptr, error, timeout + ); + }; + + bool SQLQuery( + std::string query, + std::string* error = nullptr, + int timeout = -1 + ) { + return SQLQuery(m_dbname, std::move(query), error, timeout); + }; + + bool SQLQuery( + std::string dbname, + std::string query, + std::string& result, + std::string* error = nullptr, + int timeout = -1 + ) { + return Query( + std::move(dbname), std::move(query), &result, error, timeout + ); + }; + + bool SQLQuery( + std::string query, + std::string& result, + std::string* error = nullptr, + int timeout = -1 + ) { + return SQLQuery(m_dbname, std::move(query), result, error, timeout); + }; + + bool SQLQuery( + std::string dbname, + std::string query, + Store& result, + std::string* error = nullptr, + int timeout = -1 + ); + + bool SQLQuery( + std::string query, + Store& result, + std::string* error = nullptr, + int timeout = -1 + ) { + return SQLQuery(m_dbname, std::move(query), result, error, timeout); + }; + + bool SendLog( + std::string message, + int severity = 2, + const std::string& device = "", + int timeout = 0 + ); + + bool SendAlarm( + std::string type, + std::string message, + const std::string& device = "", + int timeout = 0 + ); + + bool SendMonitoringData( + std::string json, + const std::string& device = "", + int timeout = 0 + ); + + bool SendMonitoringData( + Store& data, + const std::string& device = "", + int timeout = 0 + ); + + bool SendConfig( + std::string json, + std::string author, + int version, + const std::string& device = "", + int timeout = -1 + ); + + bool SendConfig( + Store& config, + const std::string& author, + int version, + const std::string& device = "", + int timeout = -1 + ); -class PSQLInterface{ + bool GetConfig( + std::string& json, + int version, + const std::string& device = "", + int timeout = -1 + ); - private: + bool GetConfig( + Store& config, + int version, + const std::string& device = "", + int timeout = -1 + ); - zmq::context_t* m_context; - PGClient m_pgclient; - std::string m_dbname; - std::string m_name; + // quote the string so that it can be used as SQL literal + static std::string Quote(const std::string&); + static std::string&& Quote(std::string&&); - public: + private: + bool Query( + std::string dbname, + std::string query, + std::string* result, + std::string* error, + int timeout + ); - PSQLInterface(); - ~PSQLInterface(); - bool Initialise(zmq::context_t* context, std::string device_name, std::string config_file); - bool Finalise(); - bool SQLQuery(std::string dbname, std::string query_string, std::string &result, int &timeout, std::string& err); - bool SendLog(std::string message, int severity=2, std::string device=""); - bool SendAlarm(std::string message, std::string device=""); - bool SendMonitoringData(std::string json_data, std::string device=""); - bool SendConfig(std::string json_data, std::string device=""); - bool GetConfig(std::string &json_data, int version, std::string device=""); - + bool Query( + const char* subsystem, + std::string query, + std::string* result, + int timeout + ); }; #endif From 3283ee9e72cb2e9f8aeb67c50dce0194512bf5e4 Mon Sep 17 00:00:00 2001 From: Evgenii Zhemchugov Date: Fri, 5 Jan 2024 16:22:00 +0000 Subject: [PATCH 2/4] Implement Plot --- PGStarter/Plot.cpp | 199 +++++++++++++++++++++++++++++++++++++++++++++ PGStarter/Plot.h | 66 +++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 PGStarter/Plot.cpp create mode 100644 PGStarter/Plot.h diff --git a/PGStarter/Plot.cpp b/PGStarter/Plot.cpp new file mode 100644 index 0000000..02e8a7c --- /dev/null +++ b/PGStarter/Plot.cpp @@ -0,0 +1,199 @@ +#include "Plot.h" + +// Put concat into utilities? +static +std::stringstream& +concat_(std::stringstream& ss) { + return ss; +}; + +template +std::stringstream& +concat_(std::stringstream& ss, const T& x, const Rest&... rest) { + ss << x; + return concat_(ss, rest...); +}; + +template +std::string +concat(const Args&... args) { + std::stringstream ss; + concat_(ss, args...); + return ss.str(); +}; + +static std::string encodePGArray(const std::vector& array) { + std::stringstream ss; + ss << "'{"; + bool first = true; + for (float x : array) { + if (!first) ss << ','; + first = false; + ss << x; + }; + ss << "}'"; + return ss.str(); +}; + +static std::vector decodePGArray(const std::string& string, size_t length) { + std::vector result; + result.reserve(length); + + std::stringstream ss(string); + char c; + ss >> c; // { + while (ss && c != '}') { + float x; + ss >> x >> c; + result.push_back(x); + }; + return result; +}; + +static +void failQuery( + const std::string& id, + const std::string& query, + const std::string& error +) { + throw std::runtime_error( + concat( + "Plot `", id, "': query `", query, "' resulted in error `", error, '\'' + ) + ); +}; + +Plot::Plot(std::string name, PSQLInterface& db, bool own): + id(db.Quote(std::move(name))), db(db), own_(own) +{ + if (own_ + && !query1(concat("select true result from plots where plot = '", id, '\'')) + .Has("result")) + // TODO: use "on conflict do nothing" when a more recent version of + // PostgreSQL is available + query0(concat("insert into plots (plot) values ('", id, "') ")); +}; + +Plot::~Plot() { + if (own_) db.SQLQuery(concat("delete from plots where plot = '", id, '\'')); + own_ = false; +}; + +void Plot::query0(const std::string& query) const { + std::string error; + if (!db.SQLQuery(query, &error)) failQuery(id, query, error); +}; + +Store Plot::query1(const std::string& query) const { + std::string error; + Store result; + if (!db.SQLQuery(query, result, &error)) failQuery(id, query, error); + return result; +}; + +std::vector Plot::getArray(const std::string& axis) const { + Store reply = query1( + concat( + "select array_length(", axis, ", 1) length ", axis, + " from plots where plot = '", id, '\'' + ) + ); + return decodePGArray(*reply[axis], reply.Get("length")); +}; + +void Plot::setArray(const std::string& axis, const std::vector& array) { + query0( + concat( + "update plots set ", axis, " = ", encodePGArray(array), + " where plot = '", id, '\'' + ) + ); +}; + +float Plot::getArrayValue(const std::string& axis, size_t index) const { + Store reply = query1( + concat("select ", axis, '[', index, "] from plots where plot = '", id, '\'') + ); + // NOTE: returns 0 for indices out of range. Check? + return reply.Get(axis); +}; + +void Plot::setArrayValue(const std::string& axis, size_t index, float value) { + query0( + concat( + "update plots set ", axis, '[', index, "] = ", value, + " where plot = '", id, '\'' + ) + ); +}; + +std::pair, std::vector> +Plot::getXY() const { + Store reply = query1( + concat( + "select array_length(x) xl, x, array_length(y), yl" + " from plots where plot = '", id, '\'' + ) + ); + return { + decodePGArray(*reply["x"], reply.Get("xl")), + decodePGArray(*reply["y"], reply.Get("yl")) + }; +}; + +std::pair +Plot::getXY(size_t index) const { + Store reply = query1( + concat( + "select x[", index, "], y[", index, "] from plots" + " where plot = '", id, '\'' + ) + ); + return { reply.Get("x"), reply.Get("y") }; +}; + +void Plot::setXY(const std::vector& x, const std::vector& y) { + query0( + concat( + "update plots set x = ", encodePGArray(x), ", y = ", encodePGArray(y), + " where plot = '", id, '\'' + ) + ); +}; + +void Plot::setXY(size_t index, float x, float y) { + query0( + concat( + "update plots set x[", index, "] = ", x, ", y[", index, "] = ", y, + " where plot = '", id, '\'' + ) + ); +}; + +std::string Plot::getString(const std::string& field) const { + return std::move( + *query1( + concat("select ", field, " from plots where plot = '", id, '\'') + )[field] + ); +}; + +void Plot::setString(const std::string& field, const std::string& value) { + query0( + concat( + "update plots set ", field, " = ", value, " where plot = '", id, '\'' + ) + ); +}; + +Store Plot::getInfo() const { + Store result; + result.JsonParser(getString("info")); + return result; +}; + +void Plot::setInfo(Store& info) { + std::string string; + info >> string; + setString("info", string); +}; diff --git a/PGStarter/Plot.h b/PGStarter/Plot.h new file mode 100644 index 0000000..1c30f04 --- /dev/null +++ b/PGStarter/Plot.h @@ -0,0 +1,66 @@ +#ifndef DAQ_PLOT_H +#define DAQ_PLOT_H + +#include "PSQLInterface.h" + +class Plot { + public: + Plot(std::string name, PSQLInterface& db, bool own = false); + ~Plot(); + + std::vector getX() const { return getArray("x"); }; + float getX(size_t index) const { return getArrayValue("x", index); }; + + void setX(const std::vector& x) { setArray("x", x); }; + void setX(size_t index, float x) { setArrayValue("x", index, x); }; + + std::vector getY() const { return getArray("y"); }; + float getY(size_t index) const { return getArrayValue("y", index); }; + + void setY(const std::vector& y) { setArray("y", y); }; + void setY(size_t index, float y) { setArrayValue("y", index, y); }; + + std::pair, std::vector> getXY() const; + std::pair getXY(size_t index) const; + + void setXY(const std::vector& x, const std::vector& y); + void setXY(size_t index, float x, float y); + + std::string getXLabel() const { return getString("xlabel"); }; + void setXLabel(const std::string& label) { setString("xlabel", label); }; + + std::string getYLabel() const { return getString("ylabel"); }; + void setYLabel(const std::string& label) { setString("ylabel", label); }; + + std::string getTitle() const { return getString("title"); }; + void setTitle(const std::string& title) { setString("title", title); }; + + Store getInfo() const; + void setInfo(Store&); + void setInfo(const std::string& info) { setString("info", info); }; + + // If we own a plot, we delete it when an instance of this class is + // destroyed. + bool own() const { return own_; }; + void setOwn(bool own) { own_ = own; }; + void disown() { own_ = false; }; + + private: + PSQLInterface& db; + std::string id; // name, escaped for SQL + bool own_; + + void query0(const std::string&) const; + Store query1(const std::string&) const; + + std::vector getArray(const std::string& axis) const; + void setArray(const std::string& axis, const std::vector&); + + float getArrayValue(const std::string& axis, size_t index) const; + void setArrayValue(const std::string& axis, size_t index, float value); + + std::string getString(const std::string& field) const; + void setString(const std::string& field, const std::string& value); +}; + +#endif From 87ff79bf7a04677b89f595dc55fa984f29a7eb24 Mon Sep 17 00:00:00 2001 From: Eugene Zhemchugov Date: Thu, 14 Mar 2024 17:31:08 +0000 Subject: [PATCH 3/4] Plot::Plot: add the plot to the database even when we don't own it --- PGStarter/Plot.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/PGStarter/Plot.cpp b/PGStarter/Plot.cpp index 02e8a7c..405f2ec 100644 --- a/PGStarter/Plot.cpp +++ b/PGStarter/Plot.cpp @@ -66,9 +66,8 @@ void failQuery( Plot::Plot(std::string name, PSQLInterface& db, bool own): id(db.Quote(std::move(name))), db(db), own_(own) { - if (own_ - && !query1(concat("select true result from plots where plot = '", id, '\'')) - .Has("result")) + if (!query1(concat("select true result from plots where plot = '", id, '\'')) + .Has("result")) // TODO: use "on conflict do nothing" when a more recent version of // PostgreSQL is available query0(concat("insert into plots (plot) values ('", id, "') ")); From 016aa5f0604974a3e63f691a9c79319003074d16 Mon Sep 17 00:00:00 2001 From: Eugene Zhemchugov Date: Thu, 14 Mar 2024 17:58:56 +0000 Subject: [PATCH 4/4] Plot::getString: quote value --- PGStarter/Plot.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/PGStarter/Plot.cpp b/PGStarter/Plot.cpp index 405f2ec..b8416c2 100644 --- a/PGStarter/Plot.cpp +++ b/PGStarter/Plot.cpp @@ -180,7 +180,13 @@ std::string Plot::getString(const std::string& field) const { void Plot::setString(const std::string& field, const std::string& value) { query0( concat( - "update plots set ", field, " = ", value, " where plot = '", id, '\'' + "update plots set ", + field, + " = '", + db.Quote(value), + "' where plot = '", + id, + '\'' ) ); };