otsdaq  3.03.00
TableBase.cc
1 #include "otsdaq/TableCore/TableBase.h"
2 
3 #include <iostream> // std::cout
4 #include <typeinfo>
5 
6 #include "otsdaq/TableCore/TableInfoReader.h"
7 
8 using namespace ots;
9 
10 #undef __MF_SUBJECT__
11 #define __MF_SUBJECT__ "TableBase"
12 #undef __COUT_HDR__
13 #define __COUT_HDR__ ("TableBase-" + getTableName() + "\t<> ")
14 
15 const std::string TableBase::GROUP_CACHE_PREPEND = "GroupCache_";
16 const std::string TableBase::JSON_DOC_PREPEND = "JSONDoc_";
17 const std::string TableBase::GROUP_METADATA_TABLE_NAME = "TableGroupMetadata";
18 
19 //==============================================================================
25 TableBase::TableBase(const std::string& tableName,
26  std::string* accumulatedExceptions)
27  : MAX_VIEWS_IN_CACHE(20) // This is done, so that inheriting table classes could have
28  // varying amounts of cache
29  , tableName_(tableName)
30  , activeTableView_(0)
31  , mockupTableView_(tableName)
32 {
33  // December 2021 started seeing an issue where traceTID is found to be cleared to 0
34  // which crashes TRACE if __COUT__ is used in a Table plugin constructor
35  // This check and re-initialization seems to cover up the issue for now.
36  // Why it is cleared to 0 after the constructor sets it to -1 is still unknown.
37  // Note: it seems to only happen on the first alphabetially ARTDAQ Configure Table plugin.
38  if(traceTID == 0)
39  {
40  std::cout << "TableBase Before traceTID=" << traceTID << __E__;
41  char buf[40];
42  traceInit(trace_name(TRACE_NAME, __TRACE_FILE__, buf, sizeof(buf)), 0);
43  std::cout << "TableBase After traceTID=" << traceTID << __E__;
44  __COUT__ << "TableBase TRACE reinit and Constructed." << __E__;
45  }
46 
47  if(tableName == "")
48  {
49  __SS__ << "Do not allow anonymous table view construction!" << __E__;
50  ss << StringMacros::stackTrace() << __E__;
51  __SS_THROW__;
52  }
53 
54  // initialize special group metadata table
55  if(tableName_ == TableBase::GROUP_METADATA_TABLE_NAME)
56  {
58  return;
59  }
60 
61  //if special GROUP CACHE table, handle construction in a special way
62  if(tableName.substr(0, TableBase::GROUP_CACHE_PREPEND.length()) ==
63  TableBase::GROUP_CACHE_PREPEND ||
64  tableName.substr(0, TableBase::JSON_DOC_PREPEND.length()) ==
65  TableBase::JSON_DOC_PREPEND)
66  {
67  __COUTT__ << "TableBase for special table '" << tableName << "' constructed."
68  << __E__;
69  return;
70  } //end special GROUP CACHE table construction
71 
72  bool dbg = false; // tableName == "ARTDAQEventBuilderTable";
73  if(dbg)
74  __COUTV__(tableName);
75  // info reader fills up the mockup view
76  TableInfoReader tableInfoReader(accumulatedExceptions);
77  if(dbg)
78  __COUT__ << "Reading..." << __E__;
79  try // to read info
80  {
81  std::string returnedExceptions = tableInfoReader.read(this);
82  if(dbg)
83  __COUT__ << "Read.";
84  if(returnedExceptions != "")
85  __COUT_ERR__ << returnedExceptions << __E__;
86 
87  if(accumulatedExceptions)
88  *accumulatedExceptions += std::string("\n") + returnedExceptions;
89  }
90  catch(...) // if accumulating exceptions, continue to and return, else throw
91  {
92  __SS__ << "Failure reading table schema info for table '" << tableName << "!' "
93  << "Perhaps you need to run otsdaq_convert_config_to_table? Or the XML "
94  "table definition has moved or link broken? or corrupted? Check your "
95  "Table Info area."
96  << __E__;
97  try
98  {
99  throw;
100  }
101  catch(const std::runtime_error& e)
102  {
103  ss << "Here was the error:\n" << e.what() << __E__;
104  }
105  catch(...)
106  {
107  ;
108  } //ignore unknown types
109  __COUT_ERR__ << "\n" << ss.str();
110  if(accumulatedExceptions)
111  *accumulatedExceptions += std::string("\n") + ss.str();
112  else
113  throw;
114  return; // do not proceed with mockup check if this failed
115  }
116  if(dbg)
117  __COUT__ << "Initializing..." << __E__;
118  // call init on mockup view to verify columns
119  try
120  {
121  getMockupViewP()->init();
122  if(dbg)
123  __COUT__ << "Init." << __E__;
124  }
125  catch(std::runtime_error&
126  e) // if accumulating exceptions, continue to and return, else throw
127  {
128  if(accumulatedExceptions)
129  *accumulatedExceptions += std::string("\n") + e.what();
130  else
131  throw;
132  }
133 } // end constructor()
134 
135 //==============================================================================
140 TableBase::TableBase(bool specialTable, const std::string& specialTableName)
141  : MAX_VIEWS_IN_CACHE(1) // This is done, so that inheriting table classes could have
142  // varying amounts of cache
143  , tableName_(specialTableName)
144  , activeTableView_(0)
145  , mockupTableView_(specialTableName)
146 {
147  // December 2021 started seeing an issue where traceTID is found to be cleared to 0
148  // which crashes TRACE if __COUT__ is used in a Table plugin constructor
149  // This check and re-initialization seems to cover up the issue for now.
150  // Why it is cleared to 0 after the constructor sets it to -1 is still unknown.
151  // Note: it seems to only happen on the first alphabetially ARTDAQ Configure Table plugin.
152  if(traceTID == 0)
153  {
154  std::cout << "TableBase Before traceTID=" << traceTID << __E__;
155  char buf[40];
156  traceInit(trace_name(TRACE_NAME, __TRACE_FILE__, buf, sizeof(buf)), 0);
157  std::cout << "TableBase After traceTID=" << traceTID << __E__;
158  __COUT__ << "TableBase TRACE reinit and Constructed." << __E__;
159  }
160 
161  __COUT__ << "Special table '" << tableName_ << "' constructed. " << specialTable
162  << __E__;
163 
164  // initialize special group metadata table
165  if(tableName_ == TableBase::GROUP_METADATA_TABLE_NAME)
167 
168 } // special table constructor()
169 
170 //==============================================================================
173 {
174  __COUTT__ << "Special table '" << tableName_ << "' constructing..." << __E__;
175  // Note: "TableGroupMetadata" should never be in conflict
176  // because all other tables end in "...Table"
177 
178  // This is a table called TableGroupMetadata
179  // with 4 fields:
180  // - GroupAliases
181  // - GroupAuthor
182  // - GroupCreationTime
183  // - CommentDescription
184 
185  // groupMetadataTable_.setTableName(
186  // TableBase::GROUP_METADATA_TABLE_NAME);
187  std::vector<TableViewColumnInfo>* colInfo = getMockupViewP()->getColumnsInfoP();
188  colInfo->push_back(
189  TableViewColumnInfo(TableViewColumnInfo::TYPE_UID, // just to make init() happy
190  "UnusedUID",
191  "UNUSED_UID",
193  0 /*Default*/,
194  "",
195  0 /*Min*/,
196  0 /*Max*/,
197  0));
198  colInfo->push_back(TableViewColumnInfo(TableViewColumnInfo::TYPE_DATA,
199  "GroupAliases",
200  "GROUP_ALIASES",
201  TableViewColumnInfo::DATATYPE_STRING,
202  0 /*Default*/,
203  "",
204  0 /*Min*/,
205  0 /*Max*/,
206  0));
207  colInfo->push_back(TableViewColumnInfo(
208  TableViewColumnInfo::TYPE_COMMENT, // just to make init() happy
209  TableViewColumnInfo::COL_NAME_COMMENT,
210  "COMMENT_DESCRIPTION",
211  TableViewColumnInfo::DATATYPE_STRING,
212  0 /*Default*/,
213  "",
214  0 /*Min*/,
215  0 /*Max*/,
216  0));
217  colInfo->push_back(TableViewColumnInfo(
218  TableViewColumnInfo::TYPE_AUTHOR, // just to make init() happy
219  "GroupAuthor",
220  "AUTHOR",
221  TableViewColumnInfo::DATATYPE_STRING,
222  0 /*Default*/,
223  "",
224  0 /*Min*/,
225  0 /*Max*/,
226  0));
227  colInfo->push_back(TableViewColumnInfo(TableViewColumnInfo::TYPE_TIMESTAMP,
228  "GroupCreationTime",
229  "GROUP_CREATION_TIME",
230  TableViewColumnInfo::DATATYPE_TIME,
231  0 /*Default*/,
232  "",
233  0 /*Min*/,
234  0 /*Max*/,
235  0));
236  auto tmpVersion = createTemporaryView();
237  setActiveView(tmpVersion);
238  // only need this one and only row for all time
239  getViewP()->addRow();
240 
241  __COUTT__ << "Special table '" << tableName_ << "' constructed." << __E__;
242 } //end specialMetaTableConstructor()
243 
253 //==============================================================================
255 
256 //==============================================================================
257 std::string TableBase::getTypeId() { return typeid(this).name(); }
258 
259 //==============================================================================
260 void TableBase::init(ConfigurationManager* /*tableManager*/)
261 {
262  //__COUT__ << "Default TableBase::init() called." << __E__;
263 }
264 
265 //==============================================================================
266 void TableBase::reset(bool keepTemporaryVersions)
267 {
268  // std::cout << __COUT_HDR_FL__ << "resetting" << __E__;
269  deactivate();
270  if(keepTemporaryVersions)
271  trimCache(0);
272  else // clear all
273  tableViews_.clear();
274 }
275 
276 //==============================================================================
277 void TableBase::print(std::ostream& out) const
278 {
279  // std::cout << __COUT_HDR_FL__ << "activeVersion_ " << activeVersion_ << "
280  // (INVALID_VERSION:=" << INVALID_VERSION << ")" << __E__;
281  if(!activeTableView_)
282  {
283  __COUT_ERR__ << "ERROR: No active view set" << __E__;
284  return;
285  }
286  activeTableView_->print(out);
287 }
288 
289 //==============================================================================
293 {
294  if(!isStored(version))
295  {
296  tableViews_.emplace(std::make_pair(version, TableView(tableName_)));
297  tableViews_.at(version).copy(
298  mockupTableView_, version, mockupTableView_.getAuthor());
299  trimCache();
300  if(!isStored(version)) // the trim cache is misbehaving!
301  {
302  __SS__ << "IMPOSSIBLE ERROR: trimCache() is deleting the "
303  "latest view version "
304  << version << "!" << __E__;
305  __SS_THROW__;
306  }
307  }
308  else
309  {
310  __SS__ << "View to fill with mockup already exists: " << version
311  << ". Cannot overwrite!" << __E__;
312  ss << StringMacros::stackTrace() << __E__;
313  __SS_THROW__;
314  }
315 } // end setupMockupView()
316 
317 //==============================================================================
322 void TableBase::trimCache(unsigned int trimSize)
323 {
324  // delete cached views, if necessary
325 
326  if(trimSize == (unsigned int)-1) // if -1, use MAX_VIEWS_IN_CACHE
327  trimSize = MAX_VIEWS_IN_CACHE;
328 
329  // int i = 0;
330  while(getNumberOfStoredViews() > trimSize)
331  {
332  TableVersion versionToDelete;
333  time_t stalestTime = -1;
334 
335  for(auto& viewPair : tableViews_)
336  if(!viewPair.first.isTemporaryVersion())
337  {
338  if(stalestTime == -1 || viewPair.second.getLastAccessTime() < stalestTime)
339  {
340  versionToDelete = viewPair.first;
341  stalestTime = viewPair.second.getLastAccessTime();
342  if(!trimSize)
343  break; // if trimSize is 0, then just take first found
344  }
345  }
346 
347  if(versionToDelete.isInvalid())
348  {
349  __SS__ << "Can NOT have a stored view with an invalid version!" << __E__;
350  __SS_THROW__;
351  }
352 
353  eraseView(versionToDelete);
354  }
355 }
356 
357 //==============================================================================
363 {
364  if(targetVersion.isInvalid()) // erase all temporary
365  {
366  for(auto it = tableViews_.begin(); it != tableViews_.end(); /*no increment*/)
367  {
368  if(it->first.isTemporaryVersion())
369  {
370  //__COUT__ << "Trimming temporary version: " << it->first << __E__;
371  if(activeTableView_ &&
372  getViewVersion() == it->first) // if activeVersion is being erased!
373  deactivate(); // deactivate active view, instead of guessing at next
374  // active view
375  tableViews_.erase(it++);
376  }
377  else
378  ++it;
379  }
380  }
381  else if(targetVersion.isTemporaryVersion()) // erase target
382  {
383  //__COUT__ << "Trimming temporary version: " << targetVersion << __E__;
384  eraseView(targetVersion);
385  }
386  else
387  {
388  // else this is a persistent version!
389  __SS__ << "Temporary trim target was a persistent version: " << targetVersion
390  << __E__;
391  __SS_THROW__;
392  }
393 }
394 
395 //==============================================================================
405  TableVersion ignoreVersion) const
406 {
407  auto needleIt = tableViews_.find(needleVersion);
408  if(needleIt == tableViews_.end())
409  {
410  // else this is a persistent version!
411  __SS__ << "needleVersion does not exist: " << needleVersion << __E__;
412  __SS_THROW__;
413  }
414 
415  const TableView* needleView = &(needleIt->second);
416  unsigned int rows = needleView->getNumberOfRows();
417  unsigned int cols = needleView->getNumberOfColumns();
418 
419  bool match;
420  unsigned int potentialMatchCount = 0;
421 
422  if(TTEST(9))
423  needleView->print();
424 
425  __COUTTV__(needleVersion);
426  __COUTTV__(ignoreVersion);
427  __COUTTV__(rows);
428  __COUTTV__(cols);
429 
430  // for each table in cache
431  // check each row,col
432  auto viewPairReverseIterator = tableViews_.rbegin();
433  for(; viewPairReverseIterator != tableViews_.rend(); ++viewPairReverseIterator)
434  {
435  __COUTTV__(viewPairReverseIterator->first);
436  if(viewPairReverseIterator->first == needleVersion)
437  continue; // skip needle version
438  __COUTVS__(2, viewPairReverseIterator->first);
439  if(viewPairReverseIterator->first == ignoreVersion)
440  continue; // skip ignore version
441  __COUTVS__(2, viewPairReverseIterator->first);
442  if(viewPairReverseIterator->first.isTemporaryVersion())
443  continue; // skip temporary versions
444  __COUTVS__(2, viewPairReverseIterator->first);
445  if(viewPairReverseIterator->second.getNumberOfRows() != rows)
446  continue; // row mismatch
447  __COUTVS__(2, viewPairReverseIterator->first);
448  if(viewPairReverseIterator->second.getDataColumnSize() != cols ||
449  viewPairReverseIterator->second.getSourceColumnMismatch() != 0)
450  continue; // col mismatch
451 
452  ++potentialMatchCount;
453  __COUTT__ << "Checking version... " << viewPairReverseIterator->first << __E__;
454 
455  // viewPairReverseIterator->second.print();
456 
457  // if column source names do not match then skip
458  // source names are potentially different from
459  // getColumnsInfo()/getColumnStorageNames
460 
461  match = viewPairReverseIterator->second.getSourceColumnNames().size() ==
462  needleView->getSourceColumnNames().size();
463  __COUTTV__(viewPairReverseIterator->second.getSourceColumnNames().size());
464  __COUTTV__(needleView->getSourceColumnNames().size());
465  if(match)
466  {
467  for(auto& haystackColName :
468  viewPairReverseIterator->second.getSourceColumnNames())
469  if(needleView->getSourceColumnNames().find(haystackColName) ==
470  needleView->getSourceColumnNames().end())
471  {
472  __COUT__ << "Found column name mismatch for '" << haystackColName
473  << "'... So allowing same data!" << __E__;
474 
475  match = false;
476  break;
477  }
478  }
479  else if(TTEST(1))
480  {
481  int i = 0;
482  for(auto& srcCol : viewPairReverseIterator->second.getSourceColumnNames())
483  __COUTT__ << "compare Col #" << i++ << " " << srcCol << __E__;
484  i = 0;
485  for(auto& srcCol : needleView->getSourceColumnNames())
486  __COUTT__ << "source Col #" << i++ << " " << srcCol << __E__;
487  }
488 
489  __COUTTV__(match);
490 
491  // checking columnsInfo seems to be wrong approach, use getSourceColumnNames
492  // (above) auto viewColInfoIt =
493  // viewPairReverseIterator->second.getColumnsInfo().begin(); for(unsigned
494  // int col=0; match && //note column size must already match
495  // viewPairReverseIterator->second.getColumnsInfo().size() > 3 &&
496  // col<viewPairReverseIterator->second.getColumnsInfo().size()-3;++col,viewColInfoIt++)
497  // if(viewColInfoIt->getName() !=
498  // needleView->getColumnsInfo()[col].getName())
499  // {
500  // match = false;
504  // }
505 
506  for(unsigned int row = 0; match && row < rows; ++row)
507  {
508  for(unsigned int col = 0; col < cols - 2;
509  ++col) // do not consider author and timestamp
510  if(viewPairReverseIterator->second.getDataView()[row][col] !=
511  needleView->getDataView()[row][col])
512  {
513  match = false;
514 
515  __COUTT__
516  << "Value name mismatch " << col << ":"
517  << viewPairReverseIterator->second.getDataView()[row][col] << "["
518  << viewPairReverseIterator->second.getDataView()[row][col].size()
519  << "]"
520  << " vs " << needleView->getDataView()[row][col] << "["
521  << needleView->getDataView()[row][col].size() << "]" << __E__;
522 
523  break;
524  }
525  }
526  if(match)
527  {
528  __COUT_INFO__ << "Duplicate version found: " << viewPairReverseIterator->first
529  << __E__;
530  return viewPairReverseIterator->first;
531  }
532  } // end table version loop
533 
534  __COUT__ << "No duplicates found in " << potentialMatchCount << " potential matches."
535  << __E__;
536  return TableVersion(); // return invalid if no matches
537 } // end checkForDuplicate()
538 
539 //==============================================================================
543  TableVersion v1,
544  TableVersion v2,
545  std::stringstream* diffReport /* = 0 */,
546  std::map<std::string /* uid */, std::vector<std::string /* colName */>>*
547  v1ModifiedRecords /* = 0 */) const
548 {
549  __COUTT__ << "Diffing version... " << v1 << " vs " << v2 << __E__;
550  auto v1It = tableViews_.find(v1);
551  if(v1It == tableViews_.end())
552  {
553  // else this is a persistent version!
554  __SS__ << "Version v" << v1 << " does not exist." << __E__;
555  __SS_THROW__;
556  }
557  auto v2It = tableViews_.find(v2);
558  if(v2It == tableViews_.end())
559  {
560  // else this is a persistent version!
561  __SS__ << "Version v" << v2 << " does not exist." << __E__;
562  __SS_THROW__;
563  }
564 
565  const TableView* view1 = &(v1It->second);
566  const TableView* view2 = &(v2It->second);
567  unsigned int rows1 = view1->getNumberOfRows();
568  unsigned int cols1 = view1->getNumberOfColumns();
569 
570  bool noDifference = true;
571 
572  // check each row,col
573 
574  // if column source names do not match then note
575  // source names are potentially different from
576  // getColumnsInfo()/getColumnStorageNames
577 
578  if(view1->getSourceColumnNames().size() != view2->getSourceColumnNames().size())
579  {
580  __COUT__ << "Found column count mismatch for '"
581  << view1->getSourceColumnNames().size() << " vs "
582  << view2->getSourceColumnNames().size() << __E__;
583 
584  if(diffReport)
585  *diffReport << "<li>Found column count mismatch. The v" << v1
586  << " column count is <b>'" << view1->getSourceColumnNames().size()
587  << "'</b> and the v" << v2 << " column count is <b>'"
588  << view2->getSourceColumnNames().size() << "'</b>." << __E__;
589 
590  noDifference = false;
591  if(!diffReport)
592  return noDifference; //do not need to continue to create report
593  }
594 
595  for(auto& colName1 : view1->getSourceColumnNames())
596  if(view2->getSourceColumnNames().find(colName1) ==
597  view2->getSourceColumnNames().end())
598  {
599  __COUT__ << "Found column name mismatch for '" << colName1 << __E__;
600 
601  if(diffReport)
602  *diffReport << "<li>Found column name mismatch. The v" << v1
603  << " column <b>'" << colName1 << "'</b> was not found in v"
604  << v2 << "." << __E__;
605 
606  noDifference = false;
607  if(!diffReport)
608  return noDifference; //do not need to continue to create report
609  }
610  for(auto& colName2 : view2->getSourceColumnNames())
611  if(view1->getSourceColumnNames().find(colName2) ==
612  view1->getSourceColumnNames().end())
613  {
614  __COUT__ << "Found column name mismatch for '" << colName2 << __E__;
615 
616  if(diffReport)
617  *diffReport << "<li>Found column name mismatch. The v" << v1
618  << " does not have column <b>'" << colName2
619  << "'</b> that was found in v" << v2 << "." << __E__;
620 
621  noDifference = false;
622  if(!diffReport)
623  return noDifference; //do not need to continue to create report
624  }
625 
626  if(rows1 != view2->getNumberOfRows())
627  {
628  __COUT__ << "Found row count mismatch for '" << rows1 << " vs "
629  << view2->getNumberOfRows() << __E__;
630 
631  if(diffReport)
632  *diffReport << "<li>Found row count mismatch. The v" << v1
633  << " row count is <b>'" << rows1 << "'</b> and the v" << v2
634  << " row count is <b>'" << view2->getNumberOfRows() << "'</b>."
635  << __E__;
636 
637  noDifference = false;
638  if(!diffReport)
639  return noDifference; //do not need to continue to create report
640  }
641 
642  //report on missing UIDs
643  std::set<std::string /*uid*/> uidSet1, uidSet2;
644  for(unsigned int row = 0; row < rows1; ++row)
645  uidSet1.insert(view1->getDataView()[row][view1->getColUID()]);
646  for(unsigned int row = 0; row < view2->getNumberOfRows(); ++row)
647  uidSet2.insert(view2->getDataView()[row][view2->getColUID()]);
648 
649  for(auto& uid1 : uidSet1)
650  if(uidSet2.find(uid1) == uidSet2.end())
651  {
652  __COUT__ << "Found record name mismatch for '" << uid1 << __E__;
653 
654  if(diffReport)
655  *diffReport << "<li>Found record name mismatch. The v" << v1
656  << " record <b>'" << uid1 << "'</b> was not found in v" << v2
657  << "." << __E__;
658 
659  noDifference = false;
660  if(!diffReport)
661  return noDifference; //do not need to continue to create report
662  }
663  for(auto& uid2 : uidSet2)
664  if(uidSet1.find(uid2) == uidSet1.end())
665  {
666  __COUT__ << "Found record name mismatch for '" << uid2 << __E__;
667 
668  if(diffReport)
669  *diffReport << "<li>Found record name mismatch. v" << v1
670  << " does not have record <b>'" << uid2
671  << "'</b> that was found in v" << v2 << "." << __E__;
672 
673  noDifference = false;
674  if(!diffReport)
675  return noDifference; //do not need to continue to create report
676  }
677 
678  unsigned int row2, col2;
679  for(unsigned int row = 0; row < rows1 && row < view2->getNumberOfRows(); ++row)
680  {
681  //do not evaluate if UIDs do not match
682  row2 = row;
683  if(view1->getDataView()[row][view1->getColUID()] !=
684  view2->getDataView()[row2][view2->getColUID()])
685  {
686  bool foundUid2 = false;
687 
688  for(row2 = 0; row2 < view2->getNumberOfRows(); ++row2)
689  if(view1->getDataView()[row][view1->getColUID()] ==
690  view2->getDataView()[row2][view2->getColUID()])
691  {
692  foundUid2 = true;
693  break;
694  }
695  __COUTT__ << "Found row ? '" << foundUid2 << " " << row << "," << row2
696  << __E__;
697  if(!foundUid2)
698  continue; //skip view1 record because no matching record found in view2
699  }
700 
701  __COUTT__ << "Found row "
702  << " " << row << "," << row2 << __E__;
703  for(unsigned int col = 0;
704  col < cols1 - 2 && col < view2->getNumberOfColumns() - 2;
705  ++col) // do not consider author and timestamp
706  {
707  //do not evaluate if column names do not match
708  col2 = col;
709  if(view1->getColumnInfo(col).getName() !=
710  view2->getColumnInfo(col2).getName())
711  {
712  bool foundCol2 = false;
713 
714  for(col2 = 0; col2 < view2->getNumberOfColumns() - 2; ++col2)
715  if(view1->getColumnInfo(col).getName() ==
716  view2->getColumnInfo(col2).getName())
717  {
718  foundCol2 = true;
719  break;
720  }
721 
722  __COUTT__ << "Found column ? '" << foundCol2 << " " << col << "," << col2
723  << __E__;
724  if(!foundCol2)
725  continue; //skip view1 column because no matching column name was found in view2
726  }
727 
728  __COUTT__ << "Found column "
729  << " " << col << "," << col2 << __E__;
730  if(view1->getDataView()[row][col] != view2->getDataView()[row2][col2])
731  {
732  __COUT__ << "Found column value mismatch for '" << row << "," << col
733  << " " << view1->getDataView()[row][col] << __E__;
734 
735  if(diffReport)
736  *diffReport << "<li><b>" << view1->getColumnInfo(col).getName()
737  << "</b> value mismatch at v" << v1 << " {UID,r,c}:{<b>"
738  << view1->getDataView()[row][view1->getColUID()]
739  << "</b>," << row << "," << col << "}: <b>'"
740  << view1->getDataView()[row][col] << "'</b> vs value in v"
741  << v2 << ": <b>'" << view2->getDataView()[row2][col2]
742  << "'</b>." << __E__;
743 
744  noDifference = false;
745  if(!diffReport)
746  return noDifference; //do not need to continue to create report
747 
748  if(v1ModifiedRecords) //add uid/colName difference
749  (*v1ModifiedRecords)[view1->getDataView()[row][view1->getColUID()]]
750  .push_back(view1->getColumnInfo(col).getName());
751  }
752  }
753  }
754 
755  if(noDifference && diffReport)
756  *diffReport << "<li>No difference found between v" << v1 << " and v" << v2 << "."
757  << __E__;
758 
759  return noDifference;
760 } // end diffTwoVersions()
761 
762 //==============================================================================
763 void TableBase::changeVersionAndActivateView(TableVersion temporaryVersion,
764  TableVersion version)
765 {
766  auto tmpIt = tableViews_.find(temporaryVersion);
767  if(tableViews_.find(temporaryVersion) == tableViews_.end())
768  {
769  __SS__ << "ERROR: Temporary view version " << temporaryVersion
770  << " doesn't exists!" << __E__;
771  __SS_THROW__;
772  }
773  if(version.isInvalid())
774  {
775  __SS__ << "ERROR: Attempting to create an invalid version " << version
776  << "! Did you really run out of versions? (this should never happen)"
777  << __E__;
778  __SS_THROW__;
779  }
780 
781  if(tableViews_.find(version) != tableViews_.end())
782  __COUT_WARN__ << "WARNING: View version " << version
783  << " already exists! Overwriting." << __E__;
784 
785  auto emplacePair /*it,bool*/ =
786  tableViews_.emplace(std::make_pair(version, TableView(tableName_)));
787  emplacePair.first->second.copy(tmpIt->second, version, tmpIt->second.getAuthor());
788  setActiveView(version);
789  eraseView(temporaryVersion); // delete temp version from tableViews_
790 } //emd changeVersionAndActivateView()
791 
792 //==============================================================================
793 bool TableBase::isStored(const TableVersion& version) const
794 {
795  return (tableViews_.find(version) != tableViews_.end());
796 } //end isStored()
797 
798 //==============================================================================
799 bool TableBase::eraseView(TableVersion version)
800 {
801  if(!isStored(version))
802  return false;
803 
804  if(activeTableView_ &&
805  getViewVersion() == version) // if activeVersion is being erased!
806  deactivate(); // deactivate active view, instead of guessing at next active view
807 
808  tableViews_.erase(version);
809 
810  return true;
811 } //end eraseView()
812 
813 //==============================================================================
814 const std::string& TableBase::getTableName(void) const { return tableName_; }
815 
816 //==============================================================================
817 const std::string& TableBase::getTableDescription(void) const
818 {
819  return tableDescription_;
820 } //end getTableDescription()
821 
822 //==============================================================================
824 {
825  return getView().getVersion();
826 } //end getViewVersion()
827 
828 //==============================================================================
832 {
833  std::set<TableVersion> retSet = getStoredVersions();
834  if(retSet.size() && !retSet.rbegin()->isTemporaryVersion())
835  {
836  return tableViews_.find(*(retSet.rbegin()))->second.getNumberOfColumns() !=
837  mockupTableView_.getNumberOfColumns();
838  }
839  // there are no latest non-temporary tables so there is a mismatch (by default)
840  return true;
841 } //end latestAndMockupColumnNumberMismatch()
842 
843 //==============================================================================
844 std::set<TableVersion> TableBase::getStoredVersions(void) const
845 {
846  std::set<TableVersion> retSet;
847  for(auto& configs : tableViews_)
848  retSet.emplace(configs.first);
849  return retSet;
850 } //end getStoredVersions()
851 
852 //==============================================================================
856 unsigned int TableBase::getNumberOfStoredViews(void) const
857 {
858  unsigned int sz = 0;
859  for(auto& viewPair : tableViews_)
860  if(viewPair.first.isTemporaryVersion())
861  continue;
862  else if(viewPair.first.isInvalid())
863  {
864  //__SS__ << "Can NOT have a stored view with an invalid version!" << __E__;
865  //__SS_THROW__;
866 
867  // NOTE: if this starts happening a lot, could just auto-correct and remove
868  // the invalid version
869  // but it would be better to fix the cause.
870 
871  // FIXME... for now just auto correcting
872  __COUT__ << "There is an invalid version now!.. where did it come from?"
873  << __E__;
874  }
875  else
876  ++sz;
877  return sz;
878 } // end getNumberOfStoredViews()
879 
880 //==============================================================================
881 const TableView& TableBase::getView(
882  TableVersion version /* = TableVersion::INVALID */) const
883 {
884  try
885  {
886  if(version != TableVersion::INVALID)
887  return tableViews_.at(version);
888  }
889  catch(...)
890  {
891  __SS__ << "Table '" << tableName_ << "' does not have version v" << version
892  << " in the cache." << __E__;
893  __SS_THROW__;
894  }
895 
896  if(!activeTableView_)
897  {
898  __SS__ << "There is no active table view setup! Please check your system "
899  "configuration."
900  << __E__;
901  __SS_ONLY_THROW__;
902  }
903  return *activeTableView_;
904 } //end getView()
905 
906 //==============================================================================
907 TableView* TableBase::getViewP(TableVersion version /* = TableVersion::INVALID */)
908 {
909  try
910  {
911  if(version != TableVersion::INVALID)
912  return &tableViews_.at(version);
913  }
914  catch(...)
915  {
916  __SS__ << "Table '" << tableName_ << "' does not have version v" << version
917  << " in the cache." << __E__;
918  __SS_THROW__;
919  }
920 
921  if(!activeTableView_)
922  {
923  __SS__ << "There is no active table view setup! Please check your system "
924  "configuration."
925  << __E__;
926  __SS_ONLY_THROW__;
927  }
928  return activeTableView_;
929 } //end getViewP()
930 
931 //==============================================================================
932 TableView* TableBase::getMockupViewP(void) { return &mockupTableView_; }
933 
934 //==============================================================================
935 void TableBase::setTableName(const std::string& tableName) { tableName_ = tableName; }
936 
937 //==============================================================================
938 void TableBase::setTableDescription(const std::string& tableDescription)
939 {
940  tableDescription_ = tableDescription;
941 } //end setTableDescription()
942 
943 //==============================================================================
946 void TableBase::deactivate() { activeTableView_ = 0; }
947 
948 //==============================================================================
950 bool TableBase::isActive() { return activeTableView_ ? true : false; }
951 
952 //==============================================================================
953 bool TableBase::setActiveView(TableVersion version)
954 {
955  if(!isStored(version))
956  { // we don't call else load for the user, because the table manager would lose
957  // track.. (I think?)
958  // so load new versions for the first time through the table manager only. (I
959  // think??)
960  __SS__ << "\nsetActiveView() ERROR: View with version " << version
961  << " has never been stored before!" << __E__;
962  __SS_THROW__;
963  return false;
964  }
965  activeTableView_ = &tableViews_.at(version);
966 
967  if(tableViews_.at(version).getVersion() != version)
968  {
969  __SS__ << "Something has gone very wrong with the version handling!" << __E__;
970  __SS_THROW__;
971  }
972 
973  return true;
974 } //end setActiveView()
975 
976 //==============================================================================
985  const TableView& sourceViewA,
986  const TableView& sourceViewB,
987  TableVersion destinationVersion,
988  const std::string& author,
989  const std::string& mergeApproach /*Rename,Replace,Skip*/,
990  std::map<std::pair<std::string /*original table*/, std::string /*original uidB*/>,
991  std::string /*converted uidB*/>& uidConversionMap,
992  std::map<
993  std::pair<std::string /*original table*/,
994  std::pair<std::string /*group linkid*/, std::string /*original gidB*/>>,
995  std::string /*converted gidB*/>& groupidConversionMap,
996  bool fillRecordConversionMaps,
997  bool applyRecordConversionMaps,
998  bool generateUniqueDataColumns /*=false*/,
999  std::stringstream* mergeReport /*=nullptr*/)
1000 {
1001  __COUT__ << "mergeViews starting..." << __E__;
1002 
1003  // clang-format off
1004  // There 3 modes:
1005  // rename -- All records from both groups are maintained, but conflicts from B are renamed.
1006  // Must maintain a map of UIDs that are remapped to new name for
1007  // because linkUID fields must be preserved.
1008  // replace -- Any UID conflicts for a record are replaced by the record from group B.
1009  // skip -- Any UID conflicts for a record are skipped so that group A record remains
1010  // clang-format on
1011 
1012  // check valid mode
1013  if(!(mergeApproach == "Rename" || mergeApproach == "Replace" ||
1014  mergeApproach == "Skip"))
1015  {
1016  __SS__ << "Error! Invalid merge approach '" << mergeApproach << ".'" << __E__;
1017  __SS_THROW__;
1018  }
1019 
1020  // check that column sizes match
1021  if(sourceViewA.getNumberOfColumns() != mockupTableView_.getNumberOfColumns())
1022  {
1023  __SS__ << "Error! Number of Columns of source view A must match destination "
1024  "mock-up view."
1025  << "Dimension of source is [" << sourceViewA.getNumberOfColumns()
1026  << "] and of destination mockup is ["
1027  << mockupTableView_.getNumberOfColumns() << "]." << __E__;
1028  __SS_THROW__;
1029  }
1030  // check that column sizes match
1031  if(sourceViewB.getNumberOfColumns() != mockupTableView_.getNumberOfColumns())
1032  {
1033  __SS__ << "Error! Number of Columns of source view B must match destination "
1034  "mock-up view."
1035  << "Dimension of source is [" << sourceViewB.getNumberOfColumns()
1036  << "] and of destination mockup is ["
1037  << mockupTableView_.getNumberOfColumns() << "]." << __E__;
1038  __SS_THROW__;
1039  }
1040 
1041  // fill conversion map based on merge approach
1042 
1043  sourceViewA.print();
1044  sourceViewB.print();
1045 
1046  if(mergeReport)
1047  (*mergeReport) << "\n'" << mergeApproach << "'-Merging table '" << getTableName()
1048  << "' A=v" << sourceViewA.getVersion() << " with B=v"
1049  << sourceViewB.getVersion() << __E__;
1050 
1051  if(fillRecordConversionMaps && mergeApproach == "Rename")
1052  {
1053  __COUT__ << "Filling record conversion map." << __E__;
1054 
1055  // rename -- All records from both groups are maintained, but conflicts from
1056  // B are renamed.
1057  // Must maintain a map of UIDs that are remapped to new name for
1058  // groupB, because linkUID fields must be preserved.
1059 
1060  // for each B record
1061  // if there is a conflict, rename
1062 
1063  unsigned int uniqueId;
1064  std::string uniqueIdString, uniqueIdBase;
1065  char indexString[1000];
1066  unsigned int ra;
1067  unsigned int numericStartIndex;
1068  bool found;
1069 
1070  for(unsigned int cb = 0; cb < sourceViewB.getNumberOfColumns(); ++cb)
1071  {
1072  // skip columns that are not UID or GroupID columns
1073  if(!(sourceViewA.getColumnInfo(cb).isUID() ||
1074  sourceViewA.getColumnInfo(cb).isGroupID()))
1075  continue;
1076 
1077  __COUT__ << "Have an ID column: " << cb << " "
1078  << sourceViewA.getColumnInfo(cb).getType() << __E__;
1079 
1080  // at this point we have an ID column, verify B and mockup are the same
1081  if(sourceViewA.getColumnInfo(cb).getType() !=
1082  sourceViewB.getColumnInfo(cb).getType() ||
1083  sourceViewA.getColumnInfo(cb).getType() !=
1084  mockupTableView_.getColumnInfo(cb).getType())
1085  {
1086  __SS__ << "Error! " << sourceViewA.getColumnInfo(cb).getType()
1087  << " column " << cb
1088  << " of source view A must match source B and destination mock-up "
1089  "view."
1090  << " Column of source B is ["
1091  << sourceViewA.getColumnInfo(cb).getType()
1092  << "] and of destination mockup is ["
1093  << mockupTableView_.getColumnInfo(cb).getType() << "]." << __E__;
1094  __SS_THROW__;
1095  }
1096 
1097  // getLinkGroupIDColumn(childLinkIndex)
1098 
1099  std::vector<std::string /*converted uidB*/>
1100  localConvertedIds; // used for conflict completeness check
1101 
1102  if(sourceViewA.getColumnInfo(cb).isGroupID())
1103  {
1104  std::set<std::string> aGroupids = sourceViewA.getSetOfGroupIDs(cb);
1105  std::set<std::string> bGroupids = sourceViewB.getSetOfGroupIDs(cb);
1106 
1107  for(const auto& bGroupid : bGroupids)
1108  {
1109  if(aGroupids.find(bGroupid) == aGroupids.end())
1110  continue;
1111 
1112  // if here, found conflict
1113  __COUT__ << "found conflict: " << getTableName() << "/" << bGroupid
1114  << __E__;
1115 
1116  // extract starting uniqueId number
1117  {
1118  const std::string& str = bGroupid;
1119  numericStartIndex = str.size();
1120 
1121  // find first non-numeric character
1122  while(numericStartIndex - 1 < str.size() &&
1123  str[numericStartIndex - 1] >= '0' &&
1124  str[numericStartIndex - 1] <= '9')
1125  --numericStartIndex;
1126 
1127  if(numericStartIndex < str.size())
1128  {
1129  uniqueId = atoi(str.substr(numericStartIndex).c_str()) + 1;
1130  uniqueIdBase = str.substr(0, numericStartIndex);
1131  }
1132  else
1133  {
1134  uniqueId = 0;
1135  uniqueIdBase = str;
1136  }
1137 
1138  __COUTV__(uniqueIdBase);
1139  __COUTV__(uniqueId);
1140  } // end //extract starting uniqueId number
1141 
1142  // find unique id string
1143  {
1144  sprintf(indexString, "%u", uniqueId);
1145  uniqueIdString = uniqueIdBase + indexString;
1146  __COUTV__(uniqueIdString);
1147 
1148  found = false;
1149  // check converted records and source A and B for conflicts
1150  if(aGroupids.find(uniqueIdString) != aGroupids.end())
1151  found = true;
1152  if(!found && bGroupids.find(uniqueIdString) != bGroupids.end())
1153  found = true;
1154  if(!found && bGroupids.find(uniqueIdString) != bGroupids.end())
1155  found = true;
1156  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
1157  if(localConvertedIds[ra] == uniqueIdString)
1158  found = true;
1159 
1160  while(found) // while conflict, change id
1161  {
1162  ++uniqueId;
1163  sprintf(indexString, "%u", uniqueId);
1164  uniqueIdString = uniqueIdBase + indexString;
1165  __COUTV__(uniqueIdString);
1166 
1167  found = false;
1168  // check converted records and source A and B for conflicts
1169  if(aGroupids.find(uniqueIdString) != aGroupids.end())
1170  found = true;
1171  if(!found &&
1172  bGroupids.find(uniqueIdString) != bGroupids.end())
1173  found = true;
1174  if(!found &&
1175  bGroupids.find(uniqueIdString) != bGroupids.end())
1176  found = true;
1177  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
1178  if(localConvertedIds[ra] == uniqueIdString)
1179  found = true;
1180  }
1181  } // end find unique id string
1182 
1183  // have unique id string now
1184  __COUTV__(uniqueIdString);
1185 
1186  groupidConversionMap
1187  [std::pair<std::string /*original table*/,
1188  std::pair<std::string /*group linkid*/,
1189  std::string /*original gidB*/>>(
1190  getTableName(),
1191  std::pair<std::string /*group linkid*/,
1192  std::string /*original gidB*/>(
1193  sourceViewB.getColumnInfo(cb).getChildLinkIndex(),
1194  bGroupid))] = uniqueIdString;
1195  localConvertedIds.push_back(uniqueIdString); // save to vector for
1196  // future conflict
1197  // checking within table
1198 
1199  if(mergeReport)
1200  (*mergeReport)
1201  << "\t"
1202  << "Found conflicting B groupID for linkIndex '"
1203  << sourceViewB.getColumnInfo(cb).getChildLinkIndex()
1204  << "' and renamed '" << bGroupid << "' to '" << uniqueIdString
1205  << "'" << __E__;
1206 
1207  } // end row find unique id string loop for groupid
1208 
1209  // done creating conversion map
1210  __COUTV__(StringMacros::mapToString(groupidConversionMap));
1211 
1212  } // end group id conversion map fill
1213  else // start uid conversion map fill
1214  {
1215  for(unsigned int rb = 0; rb < sourceViewB.getNumberOfRows(); ++rb)
1216  {
1217  found = false;
1218 
1219  for(ra = 0; ra < sourceViewA.getDataView().size(); ++ra)
1220  if(sourceViewA.getValueAsString(ra, cb) ==
1221  sourceViewB.getValueAsString(rb, cb))
1222  {
1223  found = true;
1224  break;
1225  }
1226 
1227  if(!found)
1228  continue;
1229 
1230  // found conflict
1231  __COUT__ << "found conflict: " << getTableName() << "/"
1232  << sourceViewB.getDataView()[rb][cb] << __E__;
1233 
1234  // extract starting uniqueId number
1235  {
1236  const std::string& str = sourceViewB.getDataView()[rb][cb];
1237  numericStartIndex = str.size();
1238 
1239  // find first non-numeric character
1240  while(numericStartIndex - 1 < str.size() &&
1241  str[numericStartIndex - 1] >= '0' &&
1242  str[numericStartIndex - 1] <= '9')
1243  --numericStartIndex;
1244 
1245  if(numericStartIndex < str.size())
1246  {
1247  uniqueId = atoi(str.substr(numericStartIndex).c_str()) + 1;
1248  uniqueIdBase = str.substr(0, numericStartIndex);
1249  }
1250  else
1251  {
1252  uniqueId = 0;
1253  uniqueIdBase = str;
1254  }
1255 
1256  __COUTV__(uniqueIdBase);
1257  __COUTV__(uniqueId);
1258  } // end //extract starting uniqueId number
1259 
1260  // find unique id string
1261  {
1262  sprintf(indexString, "%u", uniqueId);
1263  uniqueIdString = uniqueIdBase + indexString;
1264  __COUTV__(uniqueIdString);
1265 
1266  found = false;
1267  // check converted records and source A and B for conflicts
1268  for(ra = 0; !found && ra < sourceViewA.getDataView().size(); ++ra)
1269  if(sourceViewA.getValueAsString(ra, cb) == uniqueIdString)
1270  found = true;
1271  for(ra = 0; !found && ra < sourceViewB.getDataView().size(); ++ra)
1272  if(ra == rb)
1273  continue; // skip record in question
1274  else if(sourceViewB.getValueAsString(ra, cb) ==
1275  uniqueIdString)
1276  found = true;
1277  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
1278  if(localConvertedIds[ra] == uniqueIdString)
1279  found = true;
1280 
1281  while(found) // while conflict, change id
1282  {
1283  ++uniqueId;
1284  sprintf(indexString, "%u", uniqueId);
1285  uniqueIdString = uniqueIdBase + indexString;
1286  __COUTV__(uniqueIdString);
1287 
1288  found = false;
1289  // check converted records and source A and B for conflicts
1290  for(ra = 0; !found && ra < sourceViewA.getDataView().size();
1291  ++ra)
1292  if(sourceViewA.getValueAsString(ra, cb) == uniqueIdString)
1293  found = true;
1294  for(ra = 0; !found && ra < sourceViewB.getDataView().size();
1295  ++ra)
1296  if(ra == rb)
1297  continue; // skip record in question
1298  else if(sourceViewB.getValueAsString(ra, cb) ==
1299  uniqueIdString)
1300  found = true;
1301  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
1302  if(localConvertedIds[ra] == uniqueIdString)
1303  found = true;
1304  }
1305  } // end find unique id string
1306 
1307  // have unique id string now
1308  __COUTV__(uniqueIdString);
1309 
1310  uidConversionMap[std::pair<std::string /*original table*/,
1311  std::string /*original uidB*/>(
1312  getTableName(), sourceViewB.getValueAsString(rb, cb))] =
1313  uniqueIdString;
1314  localConvertedIds.push_back(uniqueIdString); // save to vector for
1315  // future conflict
1316  // checking within table
1317 
1318  if(mergeReport)
1319  (*mergeReport) << "\t"
1320  << "Found conflicting B UID and renamed '"
1321  << sourceViewB.getValueAsString(rb, cb) << "' to '"
1322  << uniqueIdString << "'" << __E__;
1323  } // end row find unique id string loop
1324 
1325  // done creating conversion map
1326  __COUTV__(StringMacros::mapToString(uidConversionMap));
1327  }
1328 
1329  } // end column find unique id string loop
1330 
1331  } // end rename conversion map create
1332  else
1333  __COUT__ << "Not filling record conversion map." << __E__;
1334 
1335  if(!applyRecordConversionMaps)
1336  {
1337  __COUT__ << "Not applying record conversion map." << __E__;
1338  return TableVersion(); // return invalid
1339  }
1340  else
1341  {
1342  __COUT__ << "Applying record conversion map." << __E__;
1343  __COUTV__(StringMacros::mapToString(uidConversionMap));
1344  __COUTV__(StringMacros::mapToString(groupidConversionMap));
1345  }
1346 
1347  // if destinationVersion is INVALID, creates next available temporary version
1348  destinationVersion = createTemporaryView(TableVersion(), destinationVersion);
1349 
1350  __COUT__ << "Merging from (A) " << sourceViewA.getTableName() << "_v"
1351  << sourceViewA.getVersion() << " and (B) " << sourceViewB.getTableName()
1352  << "_v" << sourceViewB.getVersion() << " to " << getTableName() << "_v"
1353  << destinationVersion << " with approach '" << mergeApproach << ".'"
1354  << __E__;
1355 
1356  // if the merge fails then delete the destinationVersion view
1357  try
1358  {
1359  // start with a copy of source view A
1360 
1361  tableViews_.emplace(
1362  std::make_pair(destinationVersion, TableView(getTableName())));
1363  TableView* destinationView =
1364  &(tableViews_.at(destinationVersion)
1365  .copy(sourceViewA, destinationVersion, author));
1366 
1367  unsigned int destRow, destSize = destinationView->getDataView().size();
1368  unsigned int cb;
1369  bool found;
1370  std::map<std::pair<std::string /*original table*/, std::string /*original uidB*/>,
1371  std::string /*converted uidB*/>::iterator uidConversionIt;
1372  std::map<std::pair<std::string /*original table*/,
1373  std::pair<std::string /*group linkid*/,
1374  std::string /*original gidB*/>>,
1375  std::string /*converted uidB*/>::iterator groupidConversionIt;
1376 
1377  bool linkIsGroup;
1378  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
1379  std::string strb;
1380  size_t stri;
1381 
1382  unsigned int colUID = mockupTableView_.getColUID(); // setup UID column
1383 
1384  // handle merger with conflicts consideration
1385  for(unsigned int rb = 0; rb < sourceViewB.getNumberOfRows(); ++rb)
1386  {
1387  if(mergeApproach == "Rename")
1388  {
1389  // rename -- All records from both groups are maintained, but
1390  // conflicts from B are renamed. Must maintain a map of
1391  // UIDs that are remapped to new name for groupB,
1392  // because linkUID fields must be preserved.
1393 
1394  // conflict does not matter (because record conversion map is already
1395  // created, always take and append the B record copy row from B to new
1396  // row
1397  destRow = destinationView->copyRows(
1398  author,
1399  sourceViewB,
1400  rb,
1401  1 /*srcRowsToCopy*/,
1402  -1 /*destOffsetRow*/,
1403  generateUniqueDataColumns /*generateUniqueDataColumns*/);
1404 
1405  // check every column and remap conflicting names
1406 
1407  for(cb = 0; cb < sourceViewB.getNumberOfColumns(); ++cb)
1408  {
1409  if(sourceViewB.getColumnInfo(cb).isChildLink())
1410  continue; // skip link columns that have table name
1411  else if(sourceViewB.getColumnInfo(cb).isChildLinkUID())
1412  {
1413  __COUT__ << "Checking UID link... col=" << cb << __E__;
1414  sourceViewB.getChildLink(cb, linkIsGroup, linkPair);
1415 
1416  // if table and uid are in conversion map, convert
1417  if((uidConversionIt = uidConversionMap.find(
1418  std::pair<std::string /*original table*/,
1419  std::string /*original uidB*/>(
1420  sourceViewB.getValueAsString(rb, linkPair.first),
1421  sourceViewB.getValueAsString(
1422  rb, linkPair.second)))) != uidConversionMap.end())
1423  {
1424  __COUT__ << "Found entry to remap: "
1425  << sourceViewB.getDataView()[rb][linkPair.second]
1426  << " ==> " << uidConversionIt->second << __E__;
1427 
1428  if(mergeReport)
1429  (*mergeReport)
1430  << "\t\t"
1431  << "Found entry to remap [r,c]=[" << rb << "," << cb
1432  << "]"
1433  << ": "
1434  << sourceViewB.getDataView()[rb][linkPair.second]
1435  << " ==> [" << destRow << "," << linkPair.second
1436  << uidConversionIt->second << __E__;
1437  destinationView->setValueAsString(
1438  uidConversionIt->second, destRow, linkPair.second);
1439  }
1440  }
1441  else if(sourceViewB.getColumnInfo(cb).isChildLinkGroupID())
1442  {
1443  __COUT__ << "Checking GroupID link... col=" << cb << __E__;
1444  sourceViewB.getChildLink(cb, linkIsGroup, linkPair);
1445 
1446  // if table and uid are in conversion map, convert
1447  if((groupidConversionIt = groupidConversionMap.find(
1448  std::pair<std::string /*original table*/,
1449  std::pair<std::string /*group linkid*/,
1450  std::string /*original gidB*/>>(
1451  sourceViewB.getValueAsString(rb, linkPair.first),
1452  std::pair<std::string /*group linkid*/,
1453  std::string /*original gidB*/>(
1454  sourceViewB.getColumnInfo(cb).getChildLinkIndex(),
1455  sourceViewB.getValueAsString(
1456  rb, linkPair.second))))) !=
1457  groupidConversionMap.end())
1458  {
1459  __COUT__ << "Found entry to remap: "
1460  << sourceViewB.getDataView()[rb][linkPair.second]
1461  << " ==> " << groupidConversionIt->second << __E__;
1462 
1463  if(mergeReport)
1464  (*mergeReport)
1465  << "\t\t"
1466  << "Found entry to remap [r,c]=[" << rb << "," << cb
1467  << "]"
1468  << ": "
1469  << sourceViewB.getDataView()[rb][linkPair.second]
1470  << " ==> [" << destRow << "," << linkPair.second
1471  << "] " << groupidConversionIt->second << __E__;
1472  destinationView->setValueAsString(
1473  groupidConversionIt->second, destRow, linkPair.second);
1474  }
1475  }
1476  else if(sourceViewB.getColumnInfo(cb).isUID())
1477  {
1478  __COUT__ << "Checking UID... col=" << cb << __E__;
1479  if((uidConversionIt = uidConversionMap.find(
1480  std::pair<std::string /*original table*/,
1481  std::string /*original uidB*/>(
1482  getTableName(),
1483  sourceViewB.getValueAsString(rb, cb)))) !=
1484  uidConversionMap.end())
1485  {
1486  __COUT__ << "Found entry to remap: "
1487  << sourceViewB.getDataView()[rb][cb] << " ==> "
1488  << uidConversionIt->second << __E__;
1489 
1490  if(mergeReport)
1491  (*mergeReport)
1492  << "\t\t"
1493  << "Found entry to remap [r,c]=[" << rb << "," << cb
1494  << "]"
1495  << ": " << sourceViewB.getDataView()[rb][cb]
1496  << " ==> [" << destRow << "," << cb << "] "
1497  << uidConversionIt->second << __E__;
1498  destinationView->setValueAsString(
1499  uidConversionIt->second, destRow, cb);
1500  }
1501  }
1502  else if(sourceViewB.getColumnInfo(cb).isGroupID())
1503  {
1504  __COUT__ << "Checking GroupID... col=" << cb << __E__;
1505  if((groupidConversionIt = groupidConversionMap.find(
1506  std::pair<std::string /*original table*/,
1507  std::pair<std::string /*group linkid*/,
1508  std::string /*original gidB*/>>(
1509  getTableName(),
1510  std::pair<std::string /*group linkid*/,
1511  std::string /*original gidB*/>(
1512  sourceViewB.getColumnInfo(cb).getChildLinkIndex(),
1513  sourceViewB.getValueAsString(rb, cb))))) !=
1514  groupidConversionMap.end())
1515  {
1516  __COUT__ << "Found entry to remap: "
1517  << sourceViewB.getDataView()[rb][cb] << " ==> "
1518  << groupidConversionIt->second << __E__;
1519 
1520  if(mergeReport)
1521  (*mergeReport)
1522  << "\t\t"
1523  << "Found entry to remap [r,c]=[" << rb << "," << cb
1524  << "]" << sourceViewB.getDataView()[rb][cb]
1525  << " ==> [" << destRow << "," << cb << "] "
1526  << groupidConversionIt->second << __E__;
1527  destinationView->setValueAsString(
1528  groupidConversionIt->second, destRow, cb);
1529  }
1530  }
1531  else
1532  {
1533  // look for text link to a Table/UID in the map
1534  strb = sourceViewB.getValueAsString(rb, cb);
1535  if(strb.size() > getTableName().size() + 2 && strb[0] == '/')
1536  {
1537  // check for linked name
1538  __COUT__ << "Checking col" << cb << " " << strb << __E__;
1539 
1540  // see if there is an entry in p
1541  for(const auto& mapPairToPair : uidConversionMap)
1542  {
1543  if((stri = strb.find(mapPairToPair.first.first + "/" +
1544  mapPairToPair.first.second)) !=
1545  std::string::npos)
1546  {
1547  __COUT__ << "Found a text link match (stri=" << stri
1548  << ")! "
1549  << (mapPairToPair.first.first + "/" +
1550  mapPairToPair.first.second)
1551  << " ==> " << mapPairToPair.second << __E__;
1552 
1553  // insert mapped substitution into string
1554  destinationView->setValueAsString(
1555  strb.substr(0, stri) +
1556  (mapPairToPair.first.first + "/" +
1557  mapPairToPair.first.second) +
1558  strb.substr(stri +
1559  (mapPairToPair.first.first + "/" +
1560  mapPairToPair.first.second)
1561  .size()),
1562  destRow,
1563  cb);
1564 
1565  __COUT__
1566  << "Found entry to remap: "
1567  << sourceViewB.getDataView()[rb][cb] << " ==> "
1568  << destinationView->getDataView()[destRow][cb]
1569  << __E__;
1570 
1571  if(mergeReport)
1572  (*mergeReport)
1573  << "\t\t"
1574  << "Found entry to remap [r,c]=[" << rb << ","
1575  << cb << "] "
1576  << sourceViewB.getDataView()[rb][cb]
1577  << " ==> [" << destRow << "," << cb << "] "
1578  << destinationView->getDataView()[destRow][cb]
1579  << __E__;
1580  break;
1581  }
1582  } // end uid conversion map loop
1583  }
1584  }
1585  } // end column loop over B record
1586 
1587  continue;
1588  } // end rename, no-conflict handling
1589 
1590  // if here, then not doing rename, so conflicts matter
1591 
1592  found = false;
1593 
1594  for(destRow = 0; destRow < destSize; ++destRow)
1595  if(destinationView->getValueAsString(destRow, colUID) ==
1596  sourceViewB.getValueAsString(rb, colUID))
1597  {
1598  found = true;
1599  break;
1600  }
1601  if(!found) // no conflict
1602  {
1603  __COUT__ << "No " << mergeApproach << " conflict: " << __E__;
1604 
1605  if(mergeApproach == "replace" || mergeApproach == "skip")
1606  {
1607  // no conflict so append the B record
1608  // copy row from B to new row
1609  destinationView->copyRows(
1610  author, sourceViewB, rb, 1 /*srcRowsToCopy*/);
1611  }
1612  else
1613 
1614  continue;
1615  } // end no-conflict handling
1616 
1617  // if here, then there was a conflict
1618 
1619  __COUT__ << "found " << mergeApproach
1620  << " conflict: " << sourceViewB.getDataView()[rb][colUID] << __E__;
1621 
1622  if(mergeApproach == "replace")
1623  {
1624  if(mergeReport)
1625  (*mergeReport)
1626  << "\t\t"
1627  << "Found UID conflict, replacing A with B record row=" << rb
1628  << " " << sourceViewB.getDataView()[rb][colUID] << __E__;
1629  // replace -- Any UID conflicts for a record are replaced by the
1630  // record from group B.
1631 
1632  // delete row in destination
1633  destinationView->deleteRow(destRow--); // delete row and back up pointer
1634  --destSize;
1635 
1636  // append the B record now
1637  // copy row from B to new row
1638  destinationView->copyRows(author, sourceViewB, rb, 1 /*srcRowsToCopy*/);
1639  }
1640  else if(mergeApproach == "skip") // then do nothing with conflicting B record
1641  {
1642  if(mergeReport)
1643  (*mergeReport)
1644  << "\t\t"
1645  << "Found UID conflict, skipping B record row=" << rb << " "
1646  << sourceViewB.getDataView()[rb][colUID] << __E__;
1647  }
1648  }
1649 
1650  destinationView->print();
1651  }
1652  catch(...) // if the copy fails then delete the destinationVersion view
1653  {
1654  __COUT_ERR__ << "Failed to merge " << sourceViewA.getTableName() << "_v"
1655  << sourceViewA.getVersion() << " and " << sourceViewB.getTableName()
1656  << "_v" << sourceViewB.getVersion() << " into " << getTableName()
1657  << "_v" << destinationVersion << __E__;
1658  __COUT_WARN__ << "Deleting the failed destination version " << destinationVersion
1659  << __E__;
1660  eraseView(destinationVersion);
1661  throw; // and rethrow
1662  }
1663 
1664  return destinationVersion;
1665 } // end mergeViews
1666 
1667 //==============================================================================
1676  TableVersion destinationVersion,
1677  const std::string& author,
1678  bool looseColumnMatching /* = false */)
1679 {
1680  // check that column sizes match
1681  if(!looseColumnMatching &&
1682  sourceView.getNumberOfColumns() != mockupTableView_.getNumberOfColumns())
1683  {
1684  __SS__ << "Error! Number of Columns of source view must match destination "
1685  "mock-up view."
1686  << "Dimension of source is [" << sourceView.getNumberOfColumns()
1687  << "] and of destination mockup is ["
1688  << mockupTableView_.getNumberOfColumns() << "]." << __E__;
1689  __SS_THROW__;
1690  }
1691 
1692  // check for destination version confict
1693  if(!destinationVersion.isInvalid() &&
1694  tableViews_.find(destinationVersion) != tableViews_.end())
1695  {
1696  __SS__ << "Error! Asked to copy a view with a conflicting version: "
1697  << destinationVersion << __E__;
1698  __SS_THROW__;
1699  }
1700 
1701  // if destinationVersion is INVALID, creates next available temporary version
1702  destinationVersion = createTemporaryView(TableVersion(), destinationVersion);
1703 
1704  __COUT__ << "Copying from " << sourceView.getTableName() << "_v"
1705  << sourceView.getVersion() << " to " << getTableName() << "_v"
1706  << destinationVersion << __E__;
1707 
1708  try
1709  {
1710  tableViews_.emplace(std::make_pair(destinationVersion, TableView(tableName_)));
1711  tableViews_.at(destinationVersion).copy(sourceView, destinationVersion, author);
1712  }
1713  catch(...) // if the copy fails then delete the destinationVersion view
1714  {
1715  __COUT_ERR__ << "Failed to copy from " << sourceView.getTableName() << "_v"
1716  << sourceView.getVersion() << " to " << getTableName() << "_v"
1717  << destinationVersion << __E__;
1718  __COUT_WARN__ << "Deleting the failed destination version " << destinationVersion
1719  << __E__;
1720  eraseView(destinationVersion);
1721  throw; // and rethrow
1722  }
1723 
1724  return destinationVersion;
1725 } // end copyView()
1726 
1727 //==============================================================================
1735  TableVersion destTemporaryViewVersion)
1736 {
1737  __COUTT__ << "Table: " << getTableName() << __E__
1738  << "Num of Views: " << tableViews_.size()
1739  << " (Temporary Views: " << (tableViews_.size() - getNumberOfStoredViews())
1740  << ")" << __E__;
1741 
1742  TableVersion tmpVersion = destTemporaryViewVersion;
1743  if(tmpVersion.isInvalid())
1745  while(isStored(tmpVersion) && // find a new valid temporary version
1746  !(tmpVersion = TableVersion::getNextTemporaryVersion(tmpVersion)).isInvalid())
1747  ;
1748  if(isStored(tmpVersion) || tmpVersion.isInvalid())
1749  {
1750  __SS__ << "Invalid destination temporary version: " << destTemporaryViewVersion
1751  << ". Expected next temporary version < " << tmpVersion << __E__;
1752  __SS_THROW__;
1753  }
1754 
1755  if(sourceViewVersion ==
1756  TableVersion::INVALID || // use mockup if sourceVersion is -1 or not found
1757  tableViews_.find(sourceViewVersion) == tableViews_.end())
1758  {
1759  if(sourceViewVersion != -1)
1760  {
1761  __SS__ << "ERROR: sourceViewVersion " << sourceViewVersion << " not found. "
1762  << "Invalid source version. Version requested is not stored (yet?) or "
1763  "does not exist."
1764  << __E__;
1765  __SS_THROW__;
1766  }
1767  __COUTT__ << "Using Mock-up view" << __E__;
1768  tableViews_.emplace(std::make_pair(tmpVersion, TableView(tableName_)));
1769  tableViews_.at(tmpVersion)
1770  .copy(mockupTableView_, tmpVersion, mockupTableView_.getAuthor());
1771  }
1772  else
1773  {
1774  try // do not allow init to throw an exception here..
1775  { // it's ok to copy invalid data, the user may be trying to change it
1776  tableViews_.emplace(std::make_pair(tmpVersion, TableView(tableName_)));
1777  tableViews_.at(tmpVersion)
1778  .copy(tableViews_.at(sourceViewVersion),
1779  tmpVersion,
1780  tableViews_.at(sourceViewVersion).getAuthor());
1781  }
1782  catch(...)
1783  {
1784  __COUT_WARN__
1785  << "createTemporaryView() Source view failed init(). "
1786  << "This is being ignored (hopefully the new copy is being fixed)."
1787  << __E__;
1788  }
1789  }
1790 
1791  return tmpVersion;
1792 } // end createTemporaryView()
1793 
1794 //==============================================================================
1799 {
1800  TableVersion tmpVersion;
1801 
1802  // std::map guarantees versions are in increasing order!
1803  if(tableViews_.size() != 0 && tableViews_.begin()->first.isTemporaryVersion())
1804  tmpVersion = TableVersion::getNextTemporaryVersion(tableViews_.begin()->first);
1805  else
1807 
1808  // verify tmpVersion is ok
1809  if(isStored(tmpVersion) || tmpVersion.isInvalid() || !tmpVersion.isTemporaryVersion())
1810  {
1811  __SS__ << "Invalid destination temporary version: " << tmpVersion << __E__;
1812  __SS_THROW__;
1813  }
1814  return tmpVersion;
1815 } //end getNextTemporaryVersion()
1816 
1817 //==============================================================================
1822 {
1823  TableVersion tmpVersion;
1824 
1825  // std::map guarantees versions are in increasing order!
1826  if(tableViews_.size() != 0 && !tableViews_.rbegin()->first.isTemporaryVersion())
1827  tmpVersion = TableVersion::getNextVersion(tableViews_.rbegin()->first);
1828  else
1829  tmpVersion = TableVersion::getNextVersion();
1830 
1831  // verify tmpVersion is ok
1832  if(isStored(tmpVersion) || tmpVersion.isInvalid() || tmpVersion.isTemporaryVersion())
1833  {
1834  __SS__ << "Invalid destination next version: " << tmpVersion << __E__;
1835  __SS_THROW__;
1836  }
1837  return tmpVersion;
1838 } //end getNextVersion()
1839 
1840 //==============================================================================
1846 {
1847  if(!temporaryVersion.isTemporaryVersion() || !isStored(temporaryVersion))
1848  {
1849  __SS__ << getTableName() << ":: Error! Temporary version not found!" << __E__;
1850  __SS_THROW__;
1851  }
1852  return &tableViews_.at(temporaryVersion);
1853 } //end getTemporaryView()
1854 
1855 //==============================================================================
1859 std::string TableBase::convertToCaps(std::string& str, bool isTableName)
1860 {
1861  // append Table to be nice to user
1862  unsigned int tablePos = (unsigned int)std::string::npos;
1863  if(isTableName &&
1864  ((tablePos = str.find("Table")) != str.size() - strlen("Table") ||
1865  tablePos ==
1866  (unsigned int)std::string::npos)) //avoid case when tableName is length 4
1867  str += "Table";
1868 
1869  // create all caps name and validate
1870  // only allow alpha names with Table at end
1871  std::string capsStr = "";
1872  for(unsigned int c = 0; c < str.size(); ++c)
1873  if(str[c] >= 'A' && str[c] <= 'Z')
1874  {
1875  // add _ before table and if lower case to uppercase
1876  if(c == tablePos ||
1877  (c && str[c - 1] >= 'a' &&
1878  str[c - 1] <= 'z') || // if this is a new start of upper case
1879  (c && str[c - 1] >= 'A' &&
1880  str[c - 1] <= 'Z' && // if this is a new start from running caps
1881  c + 1 < str.size() && str[c + 1] >= 'a' && str[c + 1] <= 'z') ||
1882  (c && str[c - 1] >= '0' &&
1883  str[c - 1] <= '9' && // if this is a new start from numbers
1884  c + 1 < str.size() && str[c + 1] >= 'a' && str[c + 1] <= 'z'))
1885  capsStr += "_";
1886  capsStr += str[c];
1887  }
1888  else if(str[c] >= 'a' && str[c] <= 'z')
1889  capsStr += char(str[c] - 32); // capitalize
1890  else if(str[c] >= '0' && str[c] <= '9')
1891  capsStr += str[c]; // allow numbers
1892  else // error! non-alpha
1893  {
1894  //allow underscores for group cache document name
1895  if((str.substr(0, TableBase::GROUP_CACHE_PREPEND.length()) ==
1896  TableBase::GROUP_CACHE_PREPEND ||
1897  str.substr(0, TableBase::JSON_DOC_PREPEND.length()) ==
1898  TableBase::JSON_DOC_PREPEND) &&
1899  str[c] == '_')
1900  {
1901  capsStr += '-';
1902  continue;
1903  }
1904 
1905  std::stringstream ss;
1906  ss << __COUT_HDR_FL__
1907  << "TableBase::convertToCaps: Invalid character found in name (allowed: "
1908  "A-Z, a-z, 0-9) '"
1909  << str << "'" << __E__;
1910  TLOG(TLVL_ERROR) << ss.str();
1911  __SS_ONLY_THROW__;
1912  }
1913 
1914  return capsStr;
1915 } //end convertToCaps()
const std::string & getTableName(void) const
Getters.
Definition: TableBase.cc:814
std::map< TableVersion, TableView > tableViews_
Definition: TableBase.h:123
TableVersion createTemporaryView(TableVersion sourceViewVersion=TableVersion(), TableVersion destTemporaryViewVersion=TableVersion::getNextTemporaryVersion())
source of -1, from MockUp, else from valid view version
Definition: TableBase.cc:1734
void setupMockupView(TableVersion version)
Definition: TableBase.cc:292
bool diffTwoVersions(TableVersion v1, TableVersion v2, std::stringstream *diffReport=0, std::map< std::string, std::vector< std::string >> *v1ModifiedRecords=0) const
Definition: TableBase.cc:542
TableBase(bool specialTable, const std::string &specialTableName)
Definition: TableBase.cc:140
void trimTemporary(TableVersion targetVersion=TableVersion())
Definition: TableBase.cc:362
TableVersion mergeViews(const TableView &sourceViewA, const TableView &sourceViewB, TableVersion destinationVersion, const std::string &author, const std::string &mergeApproach, std::map< std::pair< std::string, std::string >, std::string > &uidConversionMap, std::map< std::pair< std::string, std::pair< std::string, std::string > >, std::string > &groupidConversionMap, bool fillRecordConversionMaps, bool applyRecordConversionMaps, bool generateUniqueDataColumns=false, std::stringstream *mergeRepoert=nullptr)
Definition: TableBase.cc:984
unsigned int getNumberOfStoredViews(void) const
Definition: TableBase.cc:856
TableVersion checkForDuplicate(TableVersion needleVersion, TableVersion ignoreVersion=TableVersion()) const
Definition: TableBase.cc:404
bool isActive(void)
isActive
Definition: TableBase.cc:950
static std::string convertToCaps(std::string &str, bool isConfigName=false)
Definition: TableBase.cc:1859
TableView * getTemporaryView(TableVersion temporaryVersion)
Definition: TableBase.cc:1845
const unsigned int MAX_VIEWS_IN_CACHE
Definition: TableBase.h:22
TableVersion getNextVersion(void) const
Definition: TableBase.cc:1821
const TableVersion & getViewVersion(void) const
always the active one
Definition: TableBase.cc:823
TableVersion copyView(const TableView &sourceView, TableVersion destinationVersion, const std::string &author, bool looseColumnMatching=false)
Definition: TableBase.cc:1675
bool latestAndMockupColumnNumberMismatch(void) const
Definition: TableBase.cc:831
void setTableName(const std::string &tableName)
Setters.
Definition: TableBase.cc:935
virtual ~TableBase(void)
Definition: TableBase.cc:254
void print(std::ostream &out=std::cout) const
always prints active view
Definition: TableBase.cc:277
void deactivate(void)
Definition: TableBase.cc:946
void specialMetaTableConstructor(void)
Methods.
Definition: TableBase.cc:172
TableVersion getNextTemporaryVersion(void) const
Definition: TableBase.cc:1798
void trimCache(unsigned int trimSize=-1)
Definition: TableBase.cc:322
bool isInvalid(void) const
isInvalid
static TableVersion getNextVersion(const TableVersion &version=TableVersion())
bool isTemporaryVersion(void) const
static TableVersion getNextTemporaryVersion(const TableVersion &version=TableVersion())
static const std::string DATATYPE_NUMBER
std::string getChildLinkIndex(void) const
getChildLinkIndex
bool isUID(void) const
isUID
static const std::string TYPE_UID
NOTE: Do NOT put '-' in static const TYPEs because it will mess up javascript handling in the web gui...
bool isChildLinkGroupID(void) const
void setValueAsString(const std::string &value, unsigned int row, unsigned int col)
Definition: TableView.cc:1090
void deleteRow(int r)
Definition: TableView.cc:3602
bool getChildLink(const unsigned int &col, bool &isGroup, std::pair< unsigned int, unsigned int > &linkPair) const
Definition: TableView.cc:3632
unsigned int copyRows(const std::string &author, const TableView &src, unsigned int srcOffsetRow=0, unsigned int srcRowsToCopy=(unsigned int) -1, unsigned int destOffsetRow=(unsigned int) -1, unsigned char generateUniqueDataColumns=false, const std::string &baseNameAutoUID="")
Definition: TableView.cc:126
void init(void)
Definition: TableView.cc:195
std::string getValueAsString(unsigned int row, unsigned int col, bool convertEnvironmentVariables=true) const
Definition: TableView.cc:975
std::set< std::string > getSetOfGroupIDs(const std::string &childLinkIndex, unsigned int row=-1) const
Definition: TableView.cc:1736
unsigned int getColUID(void) const
Definition: TableView.cc:1322
unsigned int addRow(const std::string &author="", unsigned char incrementUniqueData=false, const std::string &baseNameAutoUID="", unsigned int rowToAdd=(unsigned int) -1, std::string childLinkIndex="", std::string groupId="")
Definition: TableView.cc:3517
static std::string mapToString(const std::map< std::string, T > &mapToReturn, const std::string &primaryDelimeter=", ", const std::string &secondaryDelimeter=": ")
static std::string stackTrace(void)