otsdaq  3.04.02
Database_configInterface.cc
1 #include "otsdaq/ConfigurationInterface/Database_configInterface.h"
2 #include "otsdaq/Macros/ConfigurationInterfacePluginMacros.h"
3 #include "otsdaq/Macros/CoutMacros.h"
4 #include "otsdaq/MessageFacility/MessageFacility.h"
5 
6 #include <algorithm>
7 #include <iostream>
8 #include <iterator>
9 #include <string>
10 
11 #include "artdaq-database/BasicTypes/basictypes.h"
12 #include "artdaq-database/ConfigurationDB/configurationdbifc.h"
13 #include "otsdaq/TableCore/TableBase.h"
14 
15 #include "artdaq-database/ConfigurationDB/configuration_common.h"
16 #include "artdaq-database/ConfigurationDB/dispatch_common.h"
17 #include "artdaq-database/StorageProviders/FileSystemDB/provider_filedb.h"
18 #include "artdaq-database/StorageProviders/FileSystemDB/provider_filedb_index.h"
19 
20 //artdaq database may set TRACE_NAME
21 #ifdef TRACE_NAME
22 #undef TRACE_NAME
23 #endif
24 #define TRACE_NAME __MF_DECOR__
25 
26 using namespace ots;
27 
28 using artdaq::database::basictypes::FhiclData;
29 using artdaq::database::basictypes::JsonData;
30 
32 using table_version_map_t = ots::DatabaseConfigurationInterface::table_version_map_t;
33 
34 namespace db = artdaq::database::configuration;
35 using VersionInfoList_t = db::ConfigurationInterface::VersionInfoList_t;
36 
37 constexpr auto default_dbprovider = "filesystem";
38 constexpr auto default_entity = "OTSROOT";
39 
40 //==============================================================================
41 DatabaseConfigurationInterface::DatabaseConfigurationInterface()
42 {
43 #ifdef ARTDAQ_DATABASE_DEBUG_ENABLE
44  // to enable debugging
45  {
46  artdaq::database::configuration::debug::ExportImport();
47  artdaq::database::configuration::debug::ManageAliases();
48  artdaq::database::configuration::debug::ManageConfigs();
49  artdaq::database::configuration::debug::ManageDocuments();
50  artdaq::database::configuration::debug::Metadata();
51 
52  artdaq::database::configuration::debug::detail::ExportImport();
53  artdaq::database::configuration::debug::detail::ManageAliases();
54  artdaq::database::configuration::debug::detail::ManageConfigs();
55  artdaq::database::configuration::debug::detail::ManageDocuments();
56  artdaq::database::configuration::debug::detail::Metadata();
57 
58  artdaq::database::configuration::debug::options::OperationBase();
59  artdaq::database::configuration::debug::options::BulkOperations();
60  artdaq::database::configuration::debug::options::ManageDocuments();
61  artdaq::database::configuration::debug::options::ManageConfigs();
62  artdaq::database::configuration::debug::options::ManageAliases();
63 
64  artdaq::database::configuration::debug::MongoDB();
65  artdaq::database::configuration::debug::UconDB();
66  artdaq::database::configuration::debug::FileSystemDB();
67 
68  artdaq::database::filesystem::index::debug::enable();
69 
70  // THIS TURNS OFF TRACE SLOW PATH!!! (bug? Gennadiy says was trying to avoid slowing down TRACE with too many messages on slow path)
71  artdaq::database::filesystem::debug::enable();
72 
73  // artdaq::database::mongo::debug::enable();
74 
75  // artdaq::database::docrecord::debug::JSONDocumentBuilder();
76  // artdaq::database::docrecord::debug::JSONDocument();
77 
78  // debug::registerUngracefullExitHandlers();
79  // artdaq::database::useFakeTime(true);
80  artdaq::database::configuration::Multitasker();
81  TRACE_CNTL("modeS", true); //TURN BACK ON TRACE SLOW PATH
82  }
83 #endif
84 
85  std::string envVar = __ENV__("ARTDAQ_DATABASE_URI");
86  if(envVar.length() &&
87  envVar[0] != 'f') //e.g., filesystemdb:///path/filesystemdb/test_db
88  IS_FILESYSTEM_DB = false;
89  else
90  IS_FILESYSTEM_DB = true;
91  __COUTV__(IS_FILESYSTEM_DB);
92 } //end constructor()
93 
94 //==============================================================================
98 {
99  auto start = std::chrono::high_resolution_clock::now();
100 
101  auto ifc = db::ConfigurationInterface{default_dbprovider};
102 
103  auto versionstring = version.toString();
104 
105  auto result = ifc.template loadVersion<decltype(table), JsonData>(
106  table, versionstring, default_entity);
107 
108  auto end = std::chrono::high_resolution_clock::now();
109  auto duration =
110  std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
111  __COUTT__ << "Time taken to call DatabaseConfigurationInterface::fill(tableName="
112  << table->getTableName() << ", version=" << versionstring << ") "
113  << duration << " milliseconds." << std::endl;
114 
115  if(result.first)
116  {
117  // make sure version is set.. not clear it was happening in loadVersion
118  table->getViewP()->setVersion(version);
119  return;
120  }
121  if(result.second.find("failed to create a client session") != std::string::npos ||
122  result.second.find("closed connection. calling hello") != std::string::npos)
123  {
124  __SS__ << "Error at time: " << time(0)
125  << "\n\n======> Database Interface Error while filling '"
126  << table->getTableName() << "' version '" << versionstring
127  << "' - it appears that the connection to the database been lost. Please "
128  "check the database server and route to server.\n\n"
129  << "Here is the error detail:\n\n"
130  << result.second << "\n\n"
131  << StringMacros::stackTrace() << __E__;
132  __SS_ONLY_THROW__;
133  }
134 
135  __SS__ << "\n\n======> Database Interface Error while filling '"
136  << table->getTableName() << "' version '" << versionstring
137  << "' - are you sure this version exists? Or has the connection to the "
138  "database been lost?\n\n"
139  << "Here is the error detail:\n\n"
140  << result.second << __E__;
141  __SS_ONLY_THROW__;
142 } // end fill()
143 
144 //==============================================================================
147  bool overwrite) const
148 try
149 {
150  auto start = std::chrono::high_resolution_clock::now();
151 
152  auto ifc = db::ConfigurationInterface{default_dbprovider};
153 
154  auto versionstring = table->getView().getVersion().toString();
155  __COUTTV__(versionstring);
156  std::stringstream preSaveJSONss;
157  table->getView().printJSON(preSaveJSONss);
158 
159  // auto result =
160  // ifc.template storeVersion<decltype(configuration), JsonData>(configuration,
161  // versionstring, default_entity);
162  auto result = overwrite ? ifc.template overwriteVersion<decltype(table), JsonData>(
163  table, versionstring, default_entity)
164  : ifc.template storeVersion<decltype(table), JsonData>(
165  table, versionstring, default_entity);
166 
167  auto end = std::chrono::high_resolution_clock::now();
168  auto duration =
169  std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
170  __COUTT__ << "Time taken to call "
171  "DatabaseConfigurationInterface::saveActiveVersion(tableName="
172  << table->getTableName() << ", versionstring=" << versionstring
173  << " overwrite=" << overwrite << ") " << duration << " milliseconds"
174  << std::endl;
175 
176  __COUTTV__(result.first);
177  __COUTVS__(10, result.second);
178 
179  if(result.first)
180  {
181  //removing readback check - it seems collision checking was fixed by https://github.com/art-daq/artdaq-database/pull/36
182  if(0)
183  { //check that table save worked (FIXME -- this is temporary while waiting for permanent solution from artdaq-database developments)
184 
185  TableBase localDocLoader(
186  table->getTableName()); //can not use special table when filling
187  localDocLoader.changeVersionAndActivateView(
188  localDocLoader.createTemporaryView(), table->getView().getVersion());
189  fill(&localDocLoader, table->getView().getVersion());
190 
191  std::stringstream postSaveJSONss;
192  localDocLoader.getView().printJSON(postSaveJSONss);
193 
194  __COUTVS__(2, preSaveJSONss.str());
195  __COUTVS__(2, postSaveJSONss.str());
196  bool same = true;
197  //compare and ignore white space (since json might shift)
198  {
199  auto preSaveJSON = preSaveJSONss.str();
200  auto postSaveJSON = postSaveJSONss.str();
201  size_t prec = 0, postc = 0;
202  for(; prec < preSaveJSON.size() && postc < postSaveJSON.size();
203  ++prec, ++postc)
204  {
205  if(preSaveJSON[prec] == '\n' || preSaveJSON[prec] == '\t' ||
206  preSaveJSON[prec] == ' ')
207  {
208  //advance only prec to skip whitespace
209  --postc;
210  continue;
211  }
212  else if(postSaveJSON[postc] == '\n' || postSaveJSON[postc] == '\t' ||
213  postSaveJSON[postc] == ' ')
214  {
215  //advance only postc to skip whitespace
216  --prec;
217  continue;
218  }
219  if(preSaveJSON[prec] != postSaveJSON[postc])
220  {
221  __COUTT__ << "Mismatch at preSaveJSON[" << prec
222  << "] != postSaveJSON[" << postc << "] ... "
223  << preSaveJSON.substr(prec, 30)
224  << " != " << postSaveJSON.substr(postc, 30) << __E__;
225  same = false;
226  break;
227  }
228  }
229  //Note: is ok to not consider the case when prec != postc
230  // because json must have a closing bracket and must have been matched to be same.
231  }
232 
233  if(same)
234  __COUTT__ << "Same";
235  else
236  {
237  __COUT__ << "NOT Same";
238  auto end = std::chrono::high_resolution_clock::now();
239  auto duration =
240  std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
241  .count();
242  __COUTT__
243  << "Time taken to call "
244  "DatabaseConfigurationInterface::saveActiveVersion(tableName="
245  << table->getTableName() << ", versionstring=" << versionstring
246  << ") " << duration << " milliseconds" << std::endl;
247 
248  __SS__ << "Error saving table '" << table->getTableName() << "'-v"
249  << versionstring
250  << " (perhaps there was a collision with another user saving the "
251  "same table name/version?! Please try again with an "
252  "incremented table version). "
253  << "Expected data size is " << preSaveJSONss.str().size()
254  << " and readback found size of " << postSaveJSONss.str().size()
255  << " with character mismatches." << __E__;
256  __SS_THROW__;
257  }
258 
259  auto end = std::chrono::high_resolution_clock::now();
260  auto duration =
261  std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
262  .count();
263  __COUTT__ << "Time taken to call "
264  "DatabaseConfigurationInterface::saveActiveVersion(tableName="
265  << table->getTableName() << ", versionstring=" << versionstring
266  << ") " << duration << " milliseconds" << std::endl;
267 
268  } //end check that table save worked
269  return;
270  }
271 
272  __SS__ << "Return value indicates error in Database Interface saveActiveVersion "
273  "attempting to save "
274  << table->getTableName() << "-v" << table->getView().getVersion().toString()
275  << ": " << result.second << __E__;
276  __SS_THROW__;
277 } //end saveActiveVersion()
278 catch(std::exception const& e)
279 {
280  __SS__ << "Database Interface Exception in saveActiveVersion attempting to save "
281  << table->getTableName() << "-v" << table->getView().getVersion().toString()
282  << ": " << e.what() << __E__;
283  __SS_THROW__;
284 } //end saveActiveVersion() catch
285 catch(...)
286 {
287  __SS__
288  << "Database Interface Unknown exception in saveActiveVersion attempting to save "
289  << table->getTableName() << "-v" << table->getView().getVersion().toString()
290  << "." << __E__;
291  __SS_THROW__;
292 } //end saveActiveVersion() catch
293 
294 //==============================================================================
297  const TableBase* table) const noexcept
298 {
299  auto versions = getVersions(table);
300 
301  if(TTEST(1))
302  {
303  __COUTT__ << "Table Name: " << table->getTableName() << __E__;
304  __SS__ << "All Versions: ";
305  for(auto& v : versions)
306  ss << v << " ";
307  ss << __E__;
308  __COUTT__ << "\n" << ss.str();
309  }
310 
311  if(!versions.size())
312  return TableVersion(); // return INVALID
313 
314  return *(versions.rbegin());
315 } //end findLatestVersion()
316 
317 //==============================================================================
320  const TableBase* table) const noexcept
321 try
322 {
323  auto start = std::chrono::high_resolution_clock::now();
324 
325  auto ifc = db::ConfigurationInterface{default_dbprovider};
326  auto result = ifc.template getVersions<decltype(table)>(table, default_entity);
327 
328  auto end = std::chrono::high_resolution_clock::now();
329  auto duration =
330  std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
331  __COUTT__
332  << "Time taken to call DatabaseConfigurationInterface::getVersions(tableName="
333  << table->getTableName() << ") " << duration << " milliseconds." << std::endl;
334 
335  auto resultSet = std::set<TableVersion>{};
336  for(std::string const& version : result)
337  resultSet.insert(TableVersion(std::stol(version, 0, 10)));
338 
339  __COUTVS__(10, StringMacros::setToString(resultSet));
340 
341  return resultSet;
342 } //end getVersions()
343 catch(std::exception const& e)
344 {
345  __COUT_WARN__ << "Database Interface Exception:" << e.what() << "\n";
346  return {};
347 } //end getVersions() catch
348 
349 //==============================================================================
351 std::set<std::string /*name*/> DatabaseConfigurationInterface::getAllTableNames() const
352 try
353 {
354  auto start = std::chrono::high_resolution_clock::now();
355 
356  auto ifc = db::ConfigurationInterface{default_dbprovider};
357  auto collection_name_prefix = std::string{};
358 
359  auto result = ifc.listCollections(collection_name_prefix);
360 
361  auto end = std::chrono::high_resolution_clock::now();
362  auto duration =
363  std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
364  __COUTT__
365  << "Time taken to call "
366  "DatabaseConfigurationInterface::getAllTableNames(collection_name_prefix="
367  << collection_name_prefix << ") " << duration << " milliseconds." << std::endl;
368 
369  return result;
370 } //end getAllTableNames()
371 catch(std::exception const& e)
372 {
373  __SS__ << "Database Interface Exception:" << e.what() << "\n";
374  __SS_THROW__;
375 }
376 catch(...)
377 {
378  __SS__ << "Database Interface Unknown exception.\n";
379  __SS_THROW__;
380 } //end getAllTableNames() catch
381 
382 //==============================================================================
385  std::string const& filterString) const
386 try
387 {
388  auto start = std::chrono::high_resolution_clock::now();
389 
390  auto ifc = db::ConfigurationInterface{default_dbprovider};
391 
392  auto result = std::set<std::string>();
393 
394  if(filterString == "")
395  result = ifc.findGlobalConfigurations(
396  "*"); // GConfig will return all GConfig* with filesystem db.. for mongodb would require reg expr
397  else
398  result = ifc.findGlobalConfigurations(
399  filterString +
400  "*"); // GConfig will return all GConfig* with filesystem db.. for mongodb would// mongodb would require reg expr
401  auto end = std::chrono::high_resolution_clock::now();
402  auto duration =
403  std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
404  __COUTT__ << "Time taken to call "
405  "DatabaseConfigurationInterface::getAllTableGroupNames(filterString="
406  << filterString << ") " << duration << " milliseconds." << std::endl;
407 
408  return result;
409 } //end getAllTableGroupNames()
410 catch(std::exception const& e)
411 {
412  __SS__ << "Filter string '" << filterString
413  << "' yielded Database Interface Exception:" << e.what() << "\n";
414  __SS_THROW__;
415 }
416 catch(...)
417 {
418  __SS__ << "Filter string '" << filterString
419  << "' yielded Database Interface Unknown exception.\n";
420  __SS_THROW__;
421 } //end getAllTableGroupNames() catch
422 
423 //==============================================================================
427  const std::string& groupName) const noexcept
428 {
429  //attempt to use cache first! (potentially way faster .04 s vs 4 s)
430  try
431  {
432  TableBase localGroupMemberCacheLoader(
433  true /*special table*/
434  , //special table only allows 1 view in cache and does not load schema (which is perfect for this temporary table),,
435  TableBase::GROUP_CACHE_PREPEND + groupName);
436  TableVersion lastestGroupCacheKey =
437  findLatestVersion(&localGroupMemberCacheLoader);
438  __COUTTV__(lastestGroupCacheKey);
439  if(!lastestGroupCacheKey.isInvalid())
440  return TableGroupKey(lastestGroupCacheKey.version());
441  }
442  catch(...)
443  {
444  __COUT__ << "Ignoring cache loading error." << __E__;
445  }
446 
447  std::set<TableGroupKey> keys = DatabaseConfigurationInterface::getKeys(groupName);
448  if(keys.size()) // if keys exist, return the last
449  return *(keys.crbegin());
450 
451  // else, return invalid
452  return TableGroupKey();
453 } //end findLatestGroupKey()
454 
455 //==============================================================================
458  const std::string& groupName) const
459 {
460  std::set<TableGroupKey> retSet;
461  std::set<std::string /*name*/> names = getAllTableGroupNames();
462  for(auto& n : names)
463  if(n.find(groupName) == 0)
464  retSet.insert(TableGroupKey(n));
465  return retSet;
466 } // end getKeys()
467 
468 //==============================================================================
471  std::string const& tableGroup, bool includeMetaDataTable /* = false */) const
472 try
473 {
474  auto start = std::chrono::high_resolution_clock::now();
475 
476  //Flow (motivation -- getTableGroupMembers() is super slow; can be 3 to 15 seconds):
477  // when saveTableGroup() is called
478  // saveDocument (collection: "GroupCache" + tableGroup, version: groupKey)
479  // containing --> table group members
480  // when getTableGroupMembers() is called
481  // loadDocument (collection: "GroupCache" + tableGroup, version: groupKey)
482  // if succeeds, use that
483  // else continue with db extended lookup
484  // AND create cache file (in this way slowly populating the cache even without saves)
485 
486  // format: groupName + "_v" + groupKey
487  try
488  {
489  table_version_map_t retMap = getCachedTableGroupMembers(tableGroup);
490  __COUTTV__(tableGroup);
491  __COUTS__(20) << (StringMacros::mapToString(retMap));
492 
493  if(!includeMetaDataTable)
494  {
495  // remove special meta data table from member map
496  auto metaTable = retMap.find(TableBase::GROUP_METADATA_TABLE_NAME);
497  if(metaTable != retMap.end())
498  retMap.erase(metaTable);
499  }
500 
501  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
502  std::chrono::high_resolution_clock::now() - start)
503  .count();
504  __COUTT__ << "Time taken to call "
505  "DatabaseConfigurationInterface::getTableGroupMembers(tableGroup="
506  << tableGroup << ") " << duration << " milliseconds." << std::endl;
507  return retMap;
508  }
509  catch(...) //ignore error and proceed with standard db access
510  {
511  __COUTT__ << "Ignoring getCachedTableGroupMembers() error in "
512  "DatabaseConfigurationInterface::getTableGroupMembers(tableGroup="
513  << tableGroup << ") " << __E__;
514  }
515 
516  auto ifc = db::ConfigurationInterface{default_dbprovider};
517  auto result = ifc.loadGlobalConfiguration(tableGroup);
518 
519  if(TTEST(1))
520  {
521  for(auto& item : result)
522  __COUTT__ << "====================> " << item.configuration << ": "
523  << item.version << __E__;
524  }
525 
526  auto to_map = [](auto const& inputList, bool includeMetaDataTable) {
527  auto resultMap = table_version_map_t{};
528 
529  std::for_each(inputList.begin(), inputList.end(), [&resultMap](auto const& info) {
530  resultMap[info.configuration] = std::stol(info.version, 0, 10);
531  });
532 
533  if(!includeMetaDataTable)
534  {
535  // remove special meta data table from member map
536  auto metaTable = resultMap.find(TableBase::GROUP_METADATA_TABLE_NAME);
537  if(metaTable != resultMap.end())
538  resultMap.erase(metaTable);
539  }
540  return resultMap;
541  };
542 
543  table_version_map_t retMap = to_map(result, includeMetaDataTable);
544 
545  //now create cache for next time!
546  saveTableGroupMemberCache(retMap, tableGroup);
547 
548  __COUTT__ << "Loaded db member map string " << StringMacros::mapToString(retMap)
549  << __E__;
550 
551  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
552  std::chrono::high_resolution_clock::now() - start)
553  .count();
554  __COUTT__ << "Time taken to call "
555  "DatabaseConfigurationInterface::getTableGroupMembers(tableGroup="
556  << tableGroup << ") " << duration << " milliseconds." << std::endl;
557 
558  return retMap;
559 } // end getTableGroupMembers()
560 catch(std::exception const& e)
561 {
562  __SS__ << "Database Interface Exception getting Group's member tables for '"
563  << tableGroup << "':\n\n"
564  << e.what() << "\n";
565  if(std::string(e.what()).find("connection refused") != std::string::npos)
566  {
567  ss << "\n\nConnection to database refused. Perhaps your ssh tunnel has "
568  "closed?\n\n";
569  }
570  __SS_ONLY_THROW__;
571 }
572 catch(...)
573 {
574  __SS__ << "Database Interface Unknown exception getting Group's member tables for '"
575  << tableGroup << ".'\n";
576  __COUT_ERR__ << ss.str();
577  __SS_THROW__;
578 } // end getTableGroupMembers() catch
579 
580 //==============================================================================
583 table_version_map_t DatabaseConfigurationInterface::getCachedTableGroupMembers(
584  std::string const& tableGroup) const
585 try
586 {
587  table_version_map_t retMap;
588 
589  //Flow:
590  // when saveTableGroup() is called
591  // saveDocument (collection: "GroupCache_" + tableGroup, version: groupKey)
592  // containing --> table group members
593  // when getTableGroupMembers() is called
594  // loadDocument (collection: "GroupCache_" + tableGroup, version: groupKey)
595  // if succeeds, use that
596  // else continue with db extended lookup
597  // AND create cache file (in this way slowly populating the cache even without saves)
598 
599  // tableGroup format: groupName + "_v" + groupKey
600 
601  std::size_t vi = tableGroup.rfind("_v");
602  std::string groupName = tableGroup.substr(0, vi);
603  std::string groupKey = tableGroup.substr(vi + 2);
604  __COUTT__ << "Getting cache for " << groupName << "(" << groupKey << ")" << __E__;
605 
606  TableBase localGroupMemberCacheSaver(
607  true /*special table*/
608  , //special table only allows 1 view in cache and does not load schema (which is perfect for this temporary table),
609  TableBase::GROUP_CACHE_PREPEND + groupName);
610  TableVersion localVersion(atoi(groupKey.c_str()));
611 
612  //if filesystem db, as of April 2024, artdaq_database returned latest version when version is missing...
613  if(IS_FILESYSTEM_DB)
614  {
615  __COUTT__ << "IS_FILESYSTEM_DB=true, so checking cached keys for " << groupName
616  << "(" << groupKey << ")" << __E__;
617  std::set<TableVersion> versions = getVersions(&localGroupMemberCacheSaver);
618  if(versions.find(localVersion) == versions.end())
619  {
620  __SS__ << "Cached member table versions not found for " << groupName << "("
621  << groupKey << ")" << __E__;
622  __SS_THROW__;
623  }
624  }
625 
626  localGroupMemberCacheSaver.changeVersionAndActivateView(
627  localGroupMemberCacheSaver.createTemporaryView(), localVersion);
628 
629  fill(&localGroupMemberCacheSaver, localVersion);
630 
631  __COUTS__(20) << "Loaded cache member map string "
632  << localGroupMemberCacheSaver.getViewP()->getCustomStorageData()
633  << __E__;
634 
635  { //get table member map from cleaned json string
636  //remove json { } and all " characters
637  std::string jsonClean = "";
638  const std::string& json =
639  localGroupMemberCacheSaver.getViewP()->getCustomStorageData();
640  for(auto& c : json)
641  if(c == '{' || c == '}' || c == '"' || c == ' ')
642  continue;
643  else
644  jsonClean += c;
645  __COUTVS__(21, jsonClean);
646  StringMacros::getMapFromString(jsonClean, retMap);
647  }
648 
649  __COUTS__(20) << "Loaded cache member map string "
650  << StringMacros::mapToString(retMap) << __E__;
651 
652  return retMap;
653 } //end getCachedTableGroupMembers()
654 catch(std::exception const& e)
655 {
656  __SS__ << "Database Interface Exception getCachedTableGroupMembers for '"
657  << tableGroup << "':\n\n"
658  << e.what() << "\n";
659  __SS_ONLY_THROW__;
660 }
661 catch(...)
662 {
663  __SS__ << "Database Interface Unknown exception getCachedTableGroupMembers for '"
664  << tableGroup << ".'\n";
665  __SS_THROW__;
666 } //end getCachedTableGroupMembers() catch
667 
668 //==============================================================================
670 void DatabaseConfigurationInterface::saveTableGroupMemberCache(
671  table_version_map_t const& memberMap, std::string const& tableGroup) const
672 try
673 {
674  //Flow:
675  // when saveTableGroup() is called
676  // saveDocument (collection: "GroupCache_" + tableGroup, version: groupKey)
677  // containing --> table group members
678  // when getTableGroupMembers() is called
679  // loadDocument (collection: "GroupCache_" + tableGroup, version: groupKey)
680  // if succeeds, use that
681  // else continue with db extended lookup
682  // AND create cache file (in this way slowly populating the cache even without saves)
683 
684  // tableGroup format: groupName + "_v" + groupKey
685 
686  std::size_t vi = tableGroup.rfind("_v");
687  std::string groupName = tableGroup.substr(0, vi);
688  std::string groupKey = tableGroup.substr(vi + 2);
689  __COUTT__ << "Saving cache for " << groupName << "(" << groupKey << ")" << __E__;
690 
691  TableBase localGroupMemberCacheSaver(
692  true /*special table*/
693  , //special table only allows 1 view in cache and does not load schema (which is perfect for this temporary table),
694  TableBase::GROUP_CACHE_PREPEND + groupName);
695  localGroupMemberCacheSaver.changeVersionAndActivateView(
696  localGroupMemberCacheSaver.createTemporaryView(),
697  TableVersion(atoi(groupKey.c_str())));
698 
699  { //set custom storage data
700  std::stringstream groupCacheData;
701  groupCacheData << "{ ";
702  for(const auto& member : memberMap)
703  groupCacheData << (member.first == memberMap.begin()->first ? "" : ", ")
704  << //skip comma on first
705  "\"" << member.first << "\" : \"" << member.second << "\"";
706  groupCacheData << "}";
707 
708  localGroupMemberCacheSaver.getViewP()->setCustomStorageData(groupCacheData.str());
709  } //end set custom storage data
710 
711  __COUTT__ << "Saving member map string "
712  << localGroupMemberCacheSaver.getViewP()->getCustomStorageData() << __E__;
713 
714  __COUTT__ << "Saving cache table "
715  << localGroupMemberCacheSaver.getView().getTableName() << "("
716  << localGroupMemberCacheSaver.getView().getVersion().toString() << ")"
717  << __E__;
718 
719  // save to db, and do not allow overwrite
720  saveActiveVersion(&localGroupMemberCacheSaver, false /* overwrite */);
721 
722 } //end saveTableGroupMemberCache()
723 catch(std::exception const& e)
724 {
725  __SS__ << "Database Interface Exception saveTableGroupMemberCache for '" << tableGroup
726  << "':\n\n"
727  << e.what() << "\n";
728  __COUT_ERR__ << ss.str();
729  __SS_THROW__;
730 }
731 catch(...)
732 {
733  __SS__ << "Database Interface Unknown exception saveTableGroupMemberCache for '"
734  << tableGroup << ".'\n";
735  __COUT_ERR__ << ss.str();
736  __SS_THROW__;
737 } //end saveTableGroupMemberCache() catch
738 
739 //==============================================================================
741 void DatabaseConfigurationInterface::saveTableGroup(table_version_map_t const& memberMap,
742  std::string const& tableGroup) const
743 try
744 {
745  if(memberMap.size() == 0)
746  {
747  __SS__ << "Error: Attempting to save table group '" << tableGroup
748  << "' with empty member map! Please provide at least one member table for "
749  "the group."
750  << __E__;
751  __SS_THROW__;
752  }
753 
754  auto start = std::chrono::high_resolution_clock::now();
755 
756  auto ifc = db::ConfigurationInterface{default_dbprovider};
757 
758  //======
760  auto to_list = [](auto const& inputMap) {
761  auto resultList = VersionInfoList_t{};
762  std::transform(
763  inputMap.begin(),
764  inputMap.end(),
765  std::back_inserter(resultList),
766  [](auto const& mapEntry) {
767  return VersionInfoList_t::value_type{
768  mapEntry.first, mapEntry.second.toString(), default_entity};
769  });
770 
771  return resultList;
772  };
773  __COUTTV__(StringMacros::mapToString(memberMap));
774 
775  auto result = IS_FILESYSTEM_DB
776  ? ifc.storeGlobalConfiguration(to_list(memberMap), tableGroup)
777  : ifc.storeGlobalConfiguration_mt(to_list(memberMap), tableGroup);
778 
779  __COUTTV__(result.first);
780  __COUTVS__(10, result.second);
781 
782  auto end = std::chrono::high_resolution_clock::now();
783  auto duration =
784  std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
785  __COUTT__
786  << "Time taken to call DatabaseConfigurationInterface::saveTableGroup(tableGroup="
787  << tableGroup << ") " << duration << " milliseconds." << std::endl;
788 
789  if(result.first)
790  {
791  if(0) //removing readback check - it seems collision checking was fixed by https://github.com/art-daq/artdaq-database/pull/36
792  { //check that group save worked (FIXME -- this is temporary while waiting for permanent solution from artdaq-database developments)
793  auto readbackResult = ifc.loadGlobalConfiguration(tableGroup);
794 
795  if(TTEST(1))
796  for(auto& item : readbackResult)
797  __COUTT__ << "--==> " << item.configuration << ": " << item.version
798  << __E__;
799 
800  size_t countOfMatches = 0;
801  for(auto& item : readbackResult)
802  {
803  __COUTT__ << "====================> " << item.configuration << ": "
804  << item.version << __E__;
805  const auto& it = memberMap.find(item.configuration);
806  if(it == memberMap.end() ||
807  it->second != TableVersion(std::stol(item.version, 0, 10)))
808  {
809  auto end = std::chrono::high_resolution_clock::now();
810  auto duration =
811  std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
812  .count();
813  __COUTT__
814  << "Time taken to call "
815  "DatabaseConfigurationInterface::saveTableGroup(tableGroup="
816  << tableGroup << ") " << duration << " milliseconds."
817  << std::endl;
818  __SS__ << "Error saving group '" << tableGroup
819  << "' (perhaps there was a collision with another user saving "
820  "the same group name?! Please try again with an "
821  "incremented group key)). Table '"
822  << item.configuration << "'-v" << item.version
823  << " was unexpectedly read back as a member table after the "
824  "attempted group save. Expected member tables of group '"
825  << tableGroup << "' are as follows:" << __E__;
826  for(const auto& memberPair : memberMap)
827  ss << "\t" << memberPair.first << "-v" << memberPair.second
828  << __E__;
829  __SS_THROW__;
830  }
831  else
832  ++countOfMatches;
833  } //end individual member check
834 
835  //make sure all members were found
836  if(countOfMatches != memberMap.size())
837  {
838  auto end = std::chrono::high_resolution_clock::now();
839  auto duration =
840  std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
841  .count();
842  __COUTT__ << "Time taken to call "
843  "DatabaseConfigurationInterface::saveTableGroup(tableGroup="
844  << tableGroup << ") " << duration << " milliseconds."
845  << std::endl;
846  __SS__ << "Error saving group '" << tableGroup
847  << "' (perhaps there was a collision with another user saving the "
848  "same group name?! Please try again with an incremented group "
849  "key). "
850  << "Expected group count is " << memberMap.size() << ", and found "
851  << countOfMatches << " matching tables during readback check."
852  << __E__;
853  __SS_THROW__;
854  }
855  __COUTT__ << "Readback check passed." << __E__;
856  } //end check that group save worked
857 
858  {
859  auto end = std::chrono::high_resolution_clock::now();
860  auto duration =
861  std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
862  .count();
863  __COUTT__ << "Time taken to call "
864  "DatabaseConfigurationInterface::saveTableGroup(tableGroup="
865  << tableGroup << ") " << duration << " milliseconds." << std::endl;
866  }
867 
868  //now save to db cache for reverse index lookup of group members
869  try
870  {
871  saveTableGroupMemberCache(memberMap, tableGroup);
872  }
873  catch(...)
874  {
875  __COUT_WARN__ << "Ignoring errors during saveTableGroupMemberCache()"
876  << __E__;
877  }
878 
879  {
880  auto end = std::chrono::high_resolution_clock::now();
881  auto duration =
882  std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
883  .count();
884  __COUTT__ << "Time taken to call "
885  "DatabaseConfigurationInterface::saveTableGroup(tableGroup="
886  << tableGroup << ") " << duration << " milliseconds." << std::endl;
887  }
888 
889  return;
890  }
891 
892  __SS__ << "Return value indicates failure to save group '" << tableGroup << "':\n"
893  << result.second << __E__;
894  __SS_THROW__;
895 } // end saveTableGroup()
896 catch(std::exception const& e)
897 {
898  __SS__ << "Database Interface Exception saveTableGroup for '" << tableGroup
899  << "':\n\n"
900  << e.what() << "\n";
901  __SS_THROW__;
902 }
903 catch(...)
904 {
905  __SS__ << "Database Interface Unknown exception saveTableGroup for '" << tableGroup
906  << ".'\n";
907  __SS_THROW__;
908 } //end saveTableGroup() catch
909 
910 //==============================================================================
912 std::pair<std::string, TableVersion> DatabaseConfigurationInterface::saveCustomJSON(
913  const std::string& json, const std::string& documentNameToSave) const
914 try
915 {
916  __COUTT__ << "Saving doc '" << documentNameToSave << "'" << __E__;
917 
918  TableBase localDocSaver(
919  true /*special table*/
920  , //special table only allows 1 view in cache and does not load schema (which is perfect for this temporary table)
921  TableBase::JSON_DOC_PREPEND + documentNameToSave);
922 
923  std::set<TableVersion> versions = getVersions(&localDocSaver);
924  TableVersion version;
925  if(versions.size())
926  version = TableVersion::getNextVersion(*versions.rbegin());
927  else
928  version = TableVersion::DEFAULT;
929  __COUTV__(version);
930 
931  localDocSaver.changeVersionAndActivateView(localDocSaver.createTemporaryView(),
932  version);
933 
934  localDocSaver.getViewP()->setCustomStorageData(json);
935 
936  __COUTS__(10) << "Saving JSON string: "
937  << localDocSaver.getViewP()->getCustomStorageData() << __E__;
938 
939  __COUTT__ << "Saving JSON doc as " << localDocSaver.getView().getTableName() << "("
940  << localDocSaver.getView().getVersion().toString() << ")" << __E__;
941 
942  // save to db, and do not allow overwrite
943  saveActiveVersion(&localDocSaver, false /* overwrite */);
944 
945  return std::make_pair(localDocSaver.getTableName(),
946  localDocSaver.getView().getVersion());
947 } //end saveCustomJSON()
948 catch(std::exception const& e)
949 {
950  __SS__ << "Database Interface Exception saveCustomJSON for '" << documentNameToSave
951  << "':\n\n"
952  << e.what() << "\n";
953  __COUT_ERR__ << ss.str();
954  __SS_THROW__;
955 }
956 catch(...)
957 {
958  __SS__ << "Database Interface Unknown exception saveCustomJSON for '"
959  << documentNameToSave << ".'\n";
960  __COUT_ERR__ << ss.str();
961  __SS_THROW__;
962 } //end saveCustomJSON() catch
963 
964 //==============================================================================
966 std::string DatabaseConfigurationInterface::loadCustomJSON(
967  const std::string& documentNameToLoad, TableVersion documentVersionToLoad) const
968 try
969 {
970  __COUTT__ << "Loading doc '" << documentNameToLoad << "-v" << documentVersionToLoad
971  << "'" << __E__;
972 
973  TableBase localDocLoader(
974  true /*special table*/
975  , //special table only allows 1 view in cache and does not load schema (which is perfect for this temporary table),
976  TableBase::JSON_DOC_PREPEND + documentNameToLoad);
977 
978  localDocLoader.changeVersionAndActivateView(localDocLoader.createTemporaryView(),
979  documentVersionToLoad);
980 
981  fill(&localDocLoader, documentVersionToLoad);
982 
983  __COUTS__(10) << "Loaded JSON doc string "
984  << localDocLoader.getViewP()->getCustomStorageData() << __E__;
985 
986  return localDocLoader.getViewP()->getCustomStorageData();
987 } //end loadCustomJSON()
988 catch(std::exception const& e)
989 {
990  __SS__ << "Database Interface Exception saveCustomJSON for '" << documentNameToLoad
991  << "-v" << documentVersionToLoad << "':\n\n"
992  << e.what() << "\n";
993  __COUTS__(3) << ss.str();
994  __SS_ONLY_THROW__;
995 }
996 catch(...)
997 {
998  __SS__ << "Database Interface Unknown exception saveCustomJSON for '"
999  << documentNameToLoad << "-v" << documentVersionToLoad << ".'\n";
1000  __COUTS__(3) << ss.str();
1001  __SS_ONLY_THROW__;
1002 } //end loadCustomJSON() catch
1003 
1004 //==============================================================================
1007 std::set<std::string /*group*/> DatabaseConfigurationInterface::findGroupsWithTable(
1008  std::string const& tableName, TableVersion version) const
1009 try
1010 {
1011  auto start = std::chrono::high_resolution_clock::now();
1012 
1013  auto ifc = db::ConfigurationInterface{default_dbprovider};
1014 
1015  // TODO: Re-enable the lookup of global configurations containing this table once
1016  // the performance concerns that led to disabling it are resolved.
1017  // Previously, this method used `ifc.findGlobalConfigurationsContaining(tableName, version.toString())`
1018  // to query for groups containing `tableName` at `version`. For now, it intentionally
1019  // returns an empty set so callers must not rely on this for group discovery.
1020  std::set<std::string> returnSet =
1021  ifc.findGlobalConfigurationsContaining(tableName, version.toString());
1022 
1023  __COUTT__ << "Number of Groups containing table '" << tableName << "-v" << version
1024  << "': " << returnSet.size() << __E__;
1025 
1026  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
1027  std::chrono::high_resolution_clock::now() - start)
1028  .count();
1029  __COUTT__ << "Time taken to call "
1030  "DatabaseConfigurationInterface::findGroupsWithTable(table="
1031  << tableName << "-v" << version << ") " << duration << " milliseconds."
1032  << std::endl;
1033 
1034  return returnSet;
1035 } //end findGroupsWithTable()
1036 catch(std::exception const& e)
1037 {
1038  __SS__ << "Database Interface Exception running findGroupsWithTable for '"
1039  << tableName << "-v" << version << "':\n\n"
1040  << e.what() << "\n";
1041  __SS_THROW__;
1042 }
1043 catch(...)
1044 {
1045  __SS__ << "Database Interface Unknown exception running findGroupsWithTable for '"
1046  << tableName << "-v" << version << ".'\n";
1047  __SS_THROW__;
1048 } //end findGroupsWithTable() catch
1049 
1050 DEFINE_OTS_CONFIGURATION_INTERFACE(DatabaseConfigurationInterface)
void saveTableGroup(table_version_map_t const &memberMap, std::string const &tableGroup) const override
create a new table group from the contents map
TableVersion findLatestVersion(const TableBase *table) const noexcept override
find the latest table version by table type
table_version_map_t getTableGroupMembers(std::string const &tableGroup, bool includeMetaDataTable=false) const override
return the contents of a table group
std::set< std::string > getAllTableGroupNames(std::string const &filterString="") const override
find all table groups in database
std::set< TableGroupKey > getKeys(const std::string &groupName) const override
find all configuration groups in database
std::set< TableVersion > getVersions(const TableBase *table) const noexcept override
find all table versions by table type
std::set< std::string > getAllTableNames(void) const override
returns a list of all table names
void fill(TableBase *table, TableVersion version) const override
read table from database
TableGroupKey findLatestGroupKey(const std::string &groupName) const noexcept override
void saveActiveVersion(const TableBase *table, bool overwrite=false) const override
write table to database
const std::string & getTableName(void) const
Getters.
Definition: TableBase.cc:814
TableVersion createTemporaryView(TableVersion sourceViewVersion=TableVersion(), TableVersion destTemporaryViewVersion=TableVersion::getNextTemporaryVersion())
source of -1, from MockUp, else from valid view version
Definition: TableBase.cc:1734
std::string toString(void) const
toString
Definition: TableVersion.cc:33
bool isInvalid(void) const
isInvalid
unsigned int version(void) const
Definition: TableVersion.cc:29
void setVersion(const T &version)
< in included .icc source
const std::string & getCustomStorageData(void) const
Getters.
Definition: TableView.h:71
void setCustomStorageData(const std::string &storageData)
Definition: TableView.h:168
defines used also by OtsConfigurationWizardSupervisor
static std::string setToString(const std::set< T > &setToReturn, const std::string &delimeter=", ")
setToString ~
static std::string mapToString(const std::map< std::string, T > &mapToReturn, const std::string &primaryDelimeter=", ", const std::string &secondaryDelimeter=": ")
static void getMapFromString(const std::string &inputString, std::map< S, T > &mapToReturn, const std::set< char > &pairPairDelimiter={',', '|', '&'}, const std::set< char > &nameValueDelimiter={'=', ':'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'})
getMapFromString ~
static std::string stackTrace(void)