SQLamarr
The stand-alone ultra-fast simulation option for the LHCb experiment
db_functions.cpp
1 // (c) Copyright 2022 CERN for the benefit of the LHCb Collaboration.
2 //
3 // This software is distributed under the terms of the GNU General Public
4 // Licence version 3 (GPL Version 3), copied verbatim in the file "LICENCE".
5 //
6 // In applying this licence, CERN does not waive the privileges and immunities
7 // granted to it by virtue of its status as an Intergovernmental Organization
8 // or submit itself to any jurisdiction.
9 
10 
11 #include <iostream>
12 #include <stdexcept>
13 #include <sstream>
14 #include <algorithm>
15 #include <cmath>
16 
17 #include "SQLamarr/db_functions.h"
18 #include "SQLamarr/GlobalPRNG.h"
19 #include "SQLamarr/SQLiteError.h"
20 #include "SQLamarr/custom_sql_functions.h"
21 #include "schema.sql"
22 
23 namespace SQLamarr
24 {
25  //==========================================================================
26  // _string_field (internal helper function)
27  //==========================================================================
28  template <typename T>
29  std::string _string_field (T* s, int length, int column_type)
30  {
31  std::stringstream ret;
32 
33  int iCh;
34  for (iCh = 0; s[iCh] != '\0'; ++iCh);
35  const int s_len = iCh;
36 
37  if (s_len < length)
38  {
39  ret << s;
40  for (iCh = s_len; iCh < length; iCh++)
41  ret << ' ';
42  }
43  else if (column_type == SQLITE_TEXT)
44  {
45  for (iCh = 0; iCh < length/2 - 1; iCh++)
46  ret << s[iCh];
47  ret << "..";
48  for (iCh = s_len - length/2 + 1; iCh < s_len; iCh++)
49  ret << s[iCh];
50  }
51  else if (column_type == SQLITE_INTEGER || column_type == SQLITE_FLOAT)
52  {
53  char buf[1024];
54  sprintf(buf, "%g%100s", atof(reinterpret_cast<const char *>(s)), "");
55  buf[length+1] = '\0';
56  ret << buf;
57  }
58 
59  return ret.str();
60  }
61 
62 
63  //==========================================================================
64  // _get_field_length (internal helper function)
65  //==========================================================================
66  int _get_field_length (int column_type)
67  {
68  switch (column_type)
69  {
70  case SQLITE_INTEGER:
71  case SQLITE_FLOAT:
72  case SQLITE_NULL:
73  return 12;
74  case SQLITE_TEXT:
75  return 30;
76 
77  }
78  return 8;
79  }
80 
81  //==========================================================================
82  // make_database
83  //==========================================================================
84  SQLite3DB make_database (std::string filename, int flags, std::string init)
85  {
86  sqlite3* db;
87  char *zErrMsg;
88  int retcode;
89 
90  if (init == "")
91  init = SQL_CREATE_SCHEMA;
92 
93  retcode = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr);
94  if (retcode)
95  {
96  std::cerr << "Failure while initializing " << filename << ": "
97  << sqlite3_errmsg(db)
98  << std::endl;
99 
100  throw SQLiteError("Failed to instantiate SQLite3 DB");
101  }
102 
103  retcode = sqlite3_exec(db, init.c_str(), nullptr, nullptr, &zErrMsg);
104  if (retcode)
105  {
106  std::cerr << sqlite3_errmsg(db) << std::endl;
107  throw (SQLiteError("SQL Error in make_database"));
108  }
109 
110  return SQLite3DB(
111  db,
112  [](sqlite3* ptr) {
114  int ret = sqlite3_close(ptr);
115  if (ret != SQLITE_OK)
116  {
117  std::cerr << "sqlite3_close returned errorcode: " << ret << std::endl;
118  throw (SQLiteError("Failed closing the connection"));
119  }
120  }
121  );
122  }
123 
124  //==========================================================================
125  // prepare_statement
126  //==========================================================================
127  sqlite3_stmt* prepare_statement (SQLite3DB& db, const std::string& query)
128  {
129  sqlite3_stmt* stmt;
130 
131  int retcode = sqlite3_prepare_v2(
132  db.get(), query.c_str(), -1, &stmt, nullptr
133  );
134 
135  if (retcode != SQLITE_OK)
136  {
137  std::cerr << sqlite3_errmsg(db.get()) << std::endl;
138  throw SQLiteError("Failed to compile query");
139  }
140 
141  return stmt;
142  }
143 
144  //==========================================================================
145  // dump_table
146  //==========================================================================
147  std::string dump_table(SQLite3DB& db, const std::string& query)
148  {
149  std::stringstream ret;
150  constexpr char SEPARATOR[] = " ";
151 
152  sqlite3_stmt* stmt = prepare_statement(db, query.c_str());
153  int nRows = 0;
154  int nColumns = 0;
155 
156  while (sqlite3_step(stmt) == SQLITE_ROW)
157  {
158  nColumns = sqlite3_column_count(stmt);
159  int iCol;
160  nRows ++;
161 
162  if (nRows == 1)
163  {
164  for (iCol = 0; iCol < nColumns; ++iCol)
165  {
166  auto s = sqlite3_column_name (stmt, iCol);
167  auto t = sqlite3_column_type (stmt, iCol);
168  auto n = _get_field_length(t);
169  auto f = _string_field(s, n, SQLITE_TEXT);
170  ret << f << SEPARATOR;
171  }
172  ret << std::endl;
173  }
174 
175  if (nRows >= 10)
176  continue;
177 
178 
179 
180  for (iCol = 0; iCol < nColumns; ++iCol)
181  {
182  auto t = sqlite3_column_type (stmt, iCol);
183  auto n = _get_field_length(t);
184  if (t == SQLITE_NULL)
185  {
186  ret << "NaN";
187  for (int iCh = 0; iCh < n-3; ++iCh) ret << " ";
188  ret << SEPARATOR;
189  continue;
190  }
191 
192  auto s = sqlite3_column_text (stmt, iCol);
193  auto f = _string_field(s, n, t);
194  ret << f << SEPARATOR;
195  }
196  ret << std::endl;
197  }
198 
199  ret << "\n[" << nRows << " rows x " << nColumns << " columns]" << std::endl;
200 
201  sqlite3_finalize(stmt);
202  return ret.str();
203  }
204 
205  //==========================================================================
206  // read_as_float
207  //==========================================================================
208  float read_as_float(sqlite3_stmt* stmt, int iCol)
209  {
210  float buf;
211  switch (sqlite3_column_type(stmt, iCol))
212  {
213  case SQLITE_INTEGER:
214  buf = static_cast<float>(sqlite3_column_int(stmt, iCol));
215  break;
216  case SQLITE_FLOAT:
217  buf = static_cast<float>(sqlite3_column_double(stmt, iCol));
218  break;
219  default:
220  buf = NAN;
221  }
222 
223  return buf;
224  }
225 
226 
227  //==========================================================================
228  // validate_token
229  //==========================================================================
230  void validate_token(const std::string& token)
231  {
232  const int forbidden = std::count_if(token.begin(), token.end(),
233  [](unsigned char c){ return !std::isalnum(c) && c != '_'; }
234  );
235 
236  if (forbidden)
237  {
238  std::cerr
239  << "Found non alphanumeric token in SQL query: "
240  << token
241  << std::endl;
242  throw std::runtime_error("Invalid token");
243  }
244  }
245 
246 
247  //==========================================================================
248  // update_db_connection
249  //==========================================================================
250  void update_db_connection(SQLite3DB& old_db, const std::string& db_uri, int flags)
251  {
252  // Create the new connection to the database
253  SQLite3DB new_database = make_database(db_uri, flags);
254 
255  // Generate a seed for the new database, using the chain of random numbers
256  // of the previous one.
257  auto old_generator = GlobalPRNG::get_or_create(old_db.get());
258  std::uniform_int_distribution<long> distribution(0, 0xFFFFFFFFL);
259  uint64_t new_seed = distribution(*old_generator);
260  GlobalPRNG::get_or_create(new_database.get(), new_seed);
261 
262  // Define the SQLamarr-custom SQLite functions for the new connection
263  sqlamarr_create_sql_functions(new_database.get());
264 
265  // Replace the old db connection with the new one, and then destroy it
266  old_db.swap(new_database);
267  }
268 
269 }
static PRNG * get_or_create(const sqlite3_context *db, uint64_t seed=no_seed)
Return a pointer to an initialized generator.
Definition: GlobalPRNG.h:99
static bool release(const sqlite3 *db)
Releases a generator (delete). May require re-seeding.
Definition: GlobalPRNG.h:157
A database connection handler easying sharing the DB between C++ and Python.
Definition: db_functions.py:24
int _get_field_length(int column_type)
sqlite3_stmt * prepare_statement(SQLite3DB &db, const std::string &query)
Prpare a statement.
void update_db_connection(SQLite3DB &old_db, const std::string &db_uri, int flags=SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_URI)
Force synchronization to disk by closing and opening the connection.
void validate_token(const std::string &token)
Ensure a token is alphanumeric.
SQLite3DB make_database(std::string filename, int flags=SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_URI, std::string init=std::string())
Initialize the database.
float read_as_float(sqlite3_stmt *, int)
Read a column field from a sqlite3 statement and convert it to float.
std::unique_ptr< sqlite3, void(*)(sqlite3 *)> SQLite3DB
Unique pointer to the sqlite3 connection.
Definition: db_functions.h:19
std::string _string_field(T *s, int length, int column_type)
std::string dump_table(SQLite3DB &db, const std::string &query)
Dump the result of a query to a string.