otsdaq  v2_04_01
TableBase.cc
1 #include "otsdaq-core/TableCore/TableBase.h"
2 
3 #include <iostream> // std::cout
4 #include <typeinfo>
5 
6 #include "otsdaq-core/TableCore/TableInfoReader.h"
7 
8 using namespace ots;
9 
10 #undef __MF_SUBJECT__
11 #define __MF_SUBJECT__ "TableBase-" + getTableName()
12 
13 //==============================================================================
14 // TableBase
15 // If a valid string pointer is passed in accumulatedExceptions
16 // then allowIllegalColumns is set for InfoReader
17 // If accumulatedExceptions pointer = 0, then illegal columns throw std::runtime_error
18 // exception
19 TableBase::TableBase(std::string tableName,
20  std::string* accumulatedExceptions)
21  : MAX_VIEWS_IN_CACHE(20) // This is done, so that inheriting table classes could have
22  // varying amounts of cache
23  , tableName_(tableName)
24  , activeTableView_(0)
25 {
26  // info reader fills up the mockup view
27  TableInfoReader tableInfoReader(accumulatedExceptions);
28  try // to read info
29  {
30  std::string returnedExceptions = tableInfoReader.read(this);
31 
32  if(returnedExceptions != "")
33  __COUT_ERR__ << returnedExceptions << __E__;
34 
35  if(accumulatedExceptions)
36  *accumulatedExceptions += std::string("\n") + returnedExceptions;
37  }
38  catch(...) // if accumulating exceptions, continue to and return, else throw
39  {
40  __SS__ << "Failure in tableInfoReader.read(this). "
41  << "Perhaps you need to run otsdaq_convert_config_to_table ?" << __E__;
42  __COUT_ERR__ << "\n" << ss.str();
43  if(accumulatedExceptions)
44  *accumulatedExceptions += std::string("\n") + ss.str();
45  else
46  throw;
47  return; // do not proceed with mockup check if this failed
48  }
49 
50  // call init on mockup view to verify columns
51  try
52  {
53  getMockupViewP()->init();
54  }
55  catch(std::runtime_error&
56  e) // if accumulating exceptions, continue to and return, else throw
57  {
58  if(accumulatedExceptions)
59  *accumulatedExceptions += std::string("\n") + e.what();
60  else
61  throw;
62  }
63 }
64 
65 //==============================================================================
66 // TableBase
67 // Default constructor is only used to create special tables
68 // not based on an ...Info.xml file
69 // e.g. the TableGroupMetadata table in ConfigurationManager
70 TableBase::TableBase(void)
71  : MAX_VIEWS_IN_CACHE(1) // This is done, so that inheriting table classes could have
72  // varying amounts of cache
73  , tableName_("")
74  , activeTableView_(0)
75 {
76 }
77 
78 //==============================================================================
79 TableBase::~TableBase(void) {}
80 
81 //==============================================================================
82 std::string TableBase::getTypeId() { return typeid(this).name(); }
83 
84 //==============================================================================
85 void TableBase::init(ConfigurationManager* tableManager)
86 {
87  //__COUT__ << "Default TableBase::init() called." << __E__;
88 }
89 
90 //==============================================================================
91 void TableBase::reset(bool keepTemporaryVersions)
92 {
93  // std::cout << __COUT_HDR_FL__ << "resetting" << __E__;
94  deactivate();
95  if(keepTemporaryVersions)
96  trimCache(0);
97  else // clear all
98  tableViews_.clear();
99 }
100 
101 //==============================================================================
102 void TableBase::print(std::ostream& out) const
103 {
104  // std::cout << __COUT_HDR_FL__ << "activeVersion_ " << activeVersion_ << "
105  // (INVALID_VERSION:=" << INVALID_VERSION << ")" << __E__;
106  if(!activeTableView_)
107  {
108  __COUT_ERR__ << "ERROR: No active view set" << __E__;
109  return;
110  }
111  activeTableView_->print(out);
112 }
113 
114 //==============================================================================
115 // makes active version the specified table view version
116 // if the version is not already stored, then creates a mockup version
117 void TableBase::setupMockupView(TableVersion version)
118 {
119  if(!isStored(version))
120  {
121  tableViews_[version].copy(
122  mockupTableView_, version, mockupTableView_.getAuthor());
123  trimCache();
124  if(!isStored(version)) // the trim cache is misbehaving!
125  {
126  __SS__ << __COUT_HDR_P__
127  << "IMPOSSIBLE ERROR: trimCache() is deleting the "
128  "latest view version "
129  << version << "!" << __E__;
130  __SS_THROW__;
131  }
132  }
133  else
134  {
135  __SS__ << __COUT_HDR_P__ << "View to fill with mockup already exists: " << version
136  << ". Cannot overwrite!" << __E__;
137  ss << StringMacros::stackTrace() << __E__;
138  __SS_THROW__;
139  }
140 }
141 
142 //==============================================================================
143 // trimCache
144 // if there are more views than MAX_VIEWS_IN_CACHE, erase them.
145 // choose wisely the view to delete
146 // (by access time)
147 void TableBase::trimCache(unsigned int trimSize)
148 {
149  // delete cached views, if necessary
150 
151  if(trimSize == (unsigned int)-1) // if -1, use MAX_VIEWS_IN_CACHE
152  trimSize = MAX_VIEWS_IN_CACHE;
153 
154  int i = 0;
155  while(getNumberOfStoredViews() > trimSize)
156  {
157  TableVersion versionToDelete;
158  time_t stalestTime = -1;
159 
160  for(auto& viewPair : tableViews_)
161  if(!viewPair.first.isTemporaryVersion())
162  {
163  if(stalestTime == -1 || viewPair.second.getLastAccessTime() < stalestTime)
164  {
165  versionToDelete = viewPair.first;
166  stalestTime = viewPair.second.getLastAccessTime();
167  if(!trimSize)
168  break; // if trimSize is 0, then just take first found
169  }
170  }
171 
172  if(versionToDelete.isInvalid())
173  {
174  __SS__ << "Can NOT have a stored view with an invalid version!" << __E__;
175  __SS_THROW__;
176  }
177 
178  eraseView(versionToDelete);
179  }
180 }
181 
182 //==============================================================================
183 // trimCache
184 // if there are more views than MAX_VIEWS_IN_CACHE, erase them.
185 // choose wisely the view to delete
186 // (by access time)
187 void TableBase::trimTemporary(TableVersion targetVersion)
188 {
189  if(targetVersion.isInvalid()) // erase all temporary
190  {
191  for(auto it = tableViews_.begin(); it != tableViews_.end(); /*no increment*/)
192  {
193  if(it->first.isTemporaryVersion())
194  {
195  __COUT__ << "Trimming temporary version: " << it->first << __E__;
196  if(activeTableView_ &&
197  getViewVersion() == it->first) // if activeVersion is being erased!
198  deactivate(); // deactivate active view, instead of guessing at next
199  // active view
200  tableViews_.erase(it++);
201  }
202  else
203  ++it;
204  }
205  }
206  else if(targetVersion.isTemporaryVersion()) // erase target
207  {
208  __COUT__ << "Trimming temporary version: " << targetVersion << __E__;
209  eraseView(targetVersion);
210  }
211  else
212  {
213  // else this is a persistent version!
214  __SS__ << "Temporary trim target was a persistent version: " << targetVersion
215  << __E__;
216  __COUT_ERR__ << "\n" << ss.str();
217  __SS_THROW__;
218  }
219 }
220 
221 //==============================================================================
222 // checkForDuplicate
223 // look for a duplicate of the needleVersion in the haystack
224 // which is the cached views in tableViews_
225 //
226 // Note: ignoreVersion is useful if you know another view is already identical
227 // like when converting from temporary to persistent
228 //
229 // Return invalid if no matches
230 TableVersion TableBase::checkForDuplicate(TableVersion needleVersion,
231  TableVersion ignoreVersion) const
232 {
233  auto needleIt = tableViews_.find(needleVersion);
234  if(needleIt == tableViews_.end())
235  {
236  // else this is a persistent version!
237  __SS__ << "needleVersion does not exist: " << needleVersion << __E__;
238  __COUT_ERR__ << "\n" << ss.str();
239  __SS_THROW__;
240  }
241 
242  const TableView* needleView = &(needleIt->second);
243  unsigned int rows = needleView->getNumberOfRows();
244  unsigned int cols = needleView->getNumberOfColumns();
245 
246  bool match;
247  unsigned int potentialMatchCount = 0;
248 
249  // needleView->print();
250 
251  // for each table in cache
252  // check each row,col
253  auto viewPairReverseIterator = tableViews_.rbegin();
254  for(; viewPairReverseIterator != tableViews_.rend(); ++viewPairReverseIterator)
255  {
256  if(viewPairReverseIterator->first == needleVersion)
257  continue; // skip needle version
258  if(viewPairReverseIterator->first == ignoreVersion)
259  continue; // skip ignore version
260  if(viewPairReverseIterator->first.isTemporaryVersion())
261  continue; // skip temporary versions
262 
263  if(viewPairReverseIterator->second.getNumberOfRows() != rows)
264  continue; // row mismatch
265 
266  if(viewPairReverseIterator->second.getDataColumnSize() != cols ||
267  viewPairReverseIterator->second.getSourceColumnMismatch() != 0)
268  continue; // col mismatch
269 
270  ++potentialMatchCount;
271  __COUT__ << "Checking version... " << viewPairReverseIterator->first << __E__;
272 
273  // viewPairReverseIterator->second.print();
274 
275  // match = true;
276 
277  // if column source names do not match then skip
278  // source names are potentially different from
279  // getColumnsInfo()/getColumnStorageNames
280 
281  match = viewPairReverseIterator->second.getSourceColumnNames().size() ==
282  needleView->getSourceColumnNames().size();
283  if(match)
284  {
285  for(auto& haystackColName :
286  viewPairReverseIterator->second.getSourceColumnNames())
287  if(needleView->getSourceColumnNames().find(haystackColName) ==
288  needleView->getSourceColumnNames().end())
289  {
290  __COUT__ << "Found column name mismach for '" << haystackColName
291  << "'... So allowing same data!" << __E__;
292 
293  match = false;
294  break;
295  }
296  }
297 
298  // checking columnsInfo seems to be wrong approach, use getSourceColumnNames
299  // (above) auto viewColInfoIt =
300  // viewPairReverseIterator->second.getColumnsInfo().begin(); for(unsigned
301  // int col=0; match && //note column size must already match
302  // viewPairReverseIterator->second.getColumnsInfo().size() > 3 &&
303  // col<viewPairReverseIterator->second.getColumnsInfo().size()-3;++col,viewColInfoIt++)
304  // if(viewColInfoIt->getName() !=
305  // needleView->getColumnsInfo()[col].getName())
306  // {
307  // match = false;
311  // }
312 
313  for(unsigned int row = 0; match && row < rows; ++row)
314  {
315  for(unsigned int col = 0; col < cols - 2;
316  ++col) // do not consider author and timestamp
317  if(viewPairReverseIterator->second.getDataView()[row][col] !=
318  needleView->getDataView()[row][col])
319  {
320  match = false;
321 
322  // __COUT__ << "Value name mismatch " << col << ":"
323  //<<
324  // viewPairReverseIterator->second.getDataView()[row][col]
325  //<< "[" <<
326  // viewPairReverseIterator->second.getDataView()[row][col].size()
327  //<< "]" << " vs " <<
328  // needleView->getDataView()[row][col] << "["
329  //<<
330  // needleView->getDataView()[row][col].size()
331  //<<
332  //"]"
333  //<<
334  // __E__;
335 
336  break;
337  }
338  }
339  if(match)
340  {
341  __COUT_INFO__ << "Duplicate version found: " << viewPairReverseIterator->first
342  << __E__;
343  return viewPairReverseIterator->first;
344  }
345  }
346 
347  __COUT__ << "No duplicates found in " << potentialMatchCount << " potential matches."
348  << __E__;
349  return TableVersion(); // return invalid if no matches
350 }
351 
352 //==============================================================================
353 void TableBase::changeVersionAndActivateView(TableVersion temporaryVersion,
354  TableVersion version)
355 {
356  if(tableViews_.find(temporaryVersion) == tableViews_.end())
357  {
358  __SS__ << "ERROR: Temporary view version " << temporaryVersion
359  << " doesn't exists!" << __E__;
360  __COUT_ERR__ << "\n" << ss.str();
361  __SS_THROW__;
362  }
363  if(version.isInvalid())
364  {
365  __SS__ << "ERROR: Attempting to create an invalid version " << version
366  << "! Did you really run out of versions? (this should never happen)"
367  << __E__;
368  __COUT_ERR__ << "\n" << ss.str();
369  __SS_THROW__;
370  }
371 
372  if(tableViews_.find(version) != tableViews_.end())
373  __COUT_WARN__ << "WARNING: View version " << version
374  << " already exists! Overwriting." << __E__;
375 
376  tableViews_[version].copy(tableViews_[temporaryVersion],
377  version,
378  tableViews_[temporaryVersion].getAuthor());
379  setActiveView(version);
380  eraseView(temporaryVersion); // delete temp version from tableViews_
381 }
382 
383 //==============================================================================
384 bool TableBase::isStored(const TableVersion& version) const
385 {
386  return (tableViews_.find(version) != tableViews_.end());
387 }
388 
389 //==============================================================================
390 bool TableBase::eraseView(TableVersion version)
391 {
392  if(!isStored(version))
393  return false;
394 
395  if(activeTableView_ &&
396  getViewVersion() == version) // if activeVersion is being erased!
397  deactivate(); // deactivate active view, instead of guessing at next active view
398 
399  tableViews_.erase(version);
400 
401  return true;
402 }
403 
404 //==============================================================================
405 const std::string& TableBase::getTableName(void) const { return tableName_; }
406 
407 //==============================================================================
408 const std::string& TableBase::getTableDescription(void) const
409 {
410  return tableDescription_;
411 }
412 
413 //==============================================================================
414 const TableVersion& TableBase::getViewVersion(void) const
415 {
416  return getView().getVersion();
417 }
418 
419 //==============================================================================
420 // latestAndMockupColumnNumberMismatch
421 // intended to check if the column count was recently changed
422 bool TableBase::latestAndMockupColumnNumberMismatch(void) const
423 {
424  std::set<TableVersion> retSet = getStoredVersions();
425  if(retSet.size() && !retSet.rbegin()->isTemporaryVersion())
426  {
427  return tableViews_.find(*(retSet.rbegin()))->second.getNumberOfColumns() !=
428  mockupTableView_.getNumberOfColumns();
429  }
430  // there are no latest non-temporary tables so there is a mismatch (by default)
431  return true;
432 }
433 
434 //==============================================================================
435 std::set<TableVersion> TableBase::getStoredVersions(void) const
436 {
437  std::set<TableVersion> retSet;
438  for(auto& configs : tableViews_)
439  retSet.emplace(configs.first);
440  return retSet;
441 }
442 
443 //==============================================================================
444 // getNumberOfStoredViews
445 // count number of stored views, not including temporary views
446 // (invalid views should be impossible)
447 unsigned int TableBase::getNumberOfStoredViews(void) const
448 {
449  unsigned int sz = 0;
450  for(auto& viewPair : tableViews_)
451  if(viewPair.first.isTemporaryVersion())
452  continue;
453  else if(viewPair.first.isInvalid())
454  {
455  //__SS__ << "Can NOT have a stored view with an invalid version!" << __E__;
456  //__SS_THROW__;
457 
458  // NOTE: if this starts happening a lot, could just auto-correct and remove
459  // the invalid version
460  // but it would be better to fix the cause.
461 
462  // FIXME... for now just auto correcting
463  __COUT__ << "There is an invalid version now!.. where did it come from?"
464  << __E__;
465  }
466  else
467  ++sz;
468  return sz;
469 }
470 
471 //==============================================================================
472 const TableView& TableBase::getView(void) const
473 {
474  if(!activeTableView_)
475  {
476  __SS__ << "activeTableView_ pointer is null! (...likely the active view was not "
477  "setup properly. Check your system setup.)"
478  << __E__;
479  __SS_THROW__;
480  }
481  return *activeTableView_;
482 }
483 
484 //==============================================================================
485 TableView* TableBase::getViewP(void)
486 {
487  if(!activeTableView_)
488  {
489  __SS__ << "activeTableView_ pointer is null! (...likely the active view was not "
490  "setup properly. Check your system setup.)"
491  << __E__;
492  __SS_THROW__;
493  }
494  return activeTableView_;
495 }
496 
497 //==============================================================================
498 TableView* TableBase::getMockupViewP(void) { return &mockupTableView_; }
499 
500 //==============================================================================
501 void TableBase::setTableName(const std::string& tableName) { tableName_ = tableName; }
502 
503 //==============================================================================
504 void TableBase::setTableDescription(const std::string& tableDescription)
505 {
506  tableDescription_ = tableDescription;
507 }
508 
509 //==============================================================================
510 // deactivate
511 // reset the active view
512 void TableBase::deactivate() { activeTableView_ = 0; }
513 
514 //==============================================================================
515 // isActive
516 bool TableBase::isActive() { return activeTableView_ ? true : false; }
517 
518 //==============================================================================
519 bool TableBase::setActiveView(TableVersion version)
520 {
521  if(!isStored(version))
522  { // we don't call else load for the user, because the table manager would lose
523  // track.. (I think?)
524  // so load new versions for the first time through the table manager only. (I
525  // think??)
526  __SS__ << "\nsetActiveView() ERROR: View with version " << version
527  << " has never been stored before!" << __E__;
528  __SS_THROW__;
529  return false;
530  }
531  activeTableView_ = &tableViews_[version];
532 
533  if(tableViews_[version].getVersion() != version)
534  {
535  __SS__ << "Something has gone very wrong with the version handling!" << __E__;
536  __SS_THROW__;
537  }
538 
539  return true;
540 }
541 
542 //==============================================================================
543 // mergeViews
544 // merges source view A and B and places in
545 // destination temporary version.
546 // if destination version is invalid, then next available temporary version is chosen
547 // one error, throw exception
548 //
549 // Returns version of new temporary view that was created.
551  const TableView& sourceViewA,
552  const TableView& sourceViewB,
553  TableVersion destinationVersion,
554  const std::string& author,
555  const std::string& mergeApproach /*Rename,Replace,Skip*/,
556  std::map<std::pair<std::string /*original table*/, std::string /*original uidB*/>,
557  std::string /*converted uidB*/>& uidConversionMap,
558  std::map<std::pair<
559  std::string /*original table*/,
560  std::pair<std::string /*group linkid*/, std::string /*original gidB*/> >,
561  std::string /*converted gidB*/>& groupidConversionMap,
562  bool fillRecordConversionMaps,
563  bool applyRecordConversionMaps,
564  bool generateUniqueDataColumns)
565 {
566  __COUT__ << "mergeViews starting..." << __E__;
567 
568  // There 3 modes:
569  // rename -- All records from both groups are maintained, but conflicts from B
570  // are renamed.
571  // Must maintain a map of UIDs that are remapped to new name for
572  // groupB, because linkUID fields must be preserved. replace --
573  // Any UID conflicts for a record are replaced by the record from group B.
574  // skip -- Any UID conflicts for a record are skipped so that group A record
575  // remains
576 
577  // check valid mode
578  if(!(mergeApproach == "Rename" || mergeApproach == "Replace" ||
579  mergeApproach == "Skip"))
580  {
581  __SS__ << "Error! Invalid merge approach '" << mergeApproach << ".'" << __E__;
582  __SS_THROW__;
583  }
584 
585  // check that column sizes match
586  if(sourceViewA.getNumberOfColumns() != mockupTableView_.getNumberOfColumns())
587  {
588  __SS__ << "Error! Number of Columns of source view A must match destination "
589  "mock-up view."
590  << "Dimension of source is [" << sourceViewA.getNumberOfColumns()
591  << "] and of destination mockup is ["
592  << mockupTableView_.getNumberOfColumns() << "]." << __E__;
593  __SS_THROW__;
594  }
595  // check that column sizes match
596  if(sourceViewB.getNumberOfColumns() != mockupTableView_.getNumberOfColumns())
597  {
598  __SS__ << "Error! Number of Columns of source view B must match destination "
599  "mock-up view."
600  << "Dimension of source is [" << sourceViewB.getNumberOfColumns()
601  << "] and of destination mockup is ["
602  << mockupTableView_.getNumberOfColumns() << "]." << __E__;
603  __SS_THROW__;
604  }
605 
606  // fill conversion map based on merge approach
607 
608  sourceViewA.print();
609  sourceViewB.print();
610 
611  if(fillRecordConversionMaps && mergeApproach == "Rename")
612  {
613  __COUT__ << "Filling record conversion map." << __E__;
614 
615  // rename -- All records from both groups are maintained, but conflicts from
616  // B are renamed.
617  // Must maintain a map of UIDs that are remapped to new name for
618  // groupB, because linkUID fields must be preserved.
619 
620  // for each B record
621  // if there is a conflict, rename
622 
623  unsigned int uniqueId;
624  std::string uniqueIdString, uniqueIdBase;
625  char indexString[1000];
626  unsigned int ra;
627  unsigned int numericStartIndex;
628  bool found;
629 
630  for(unsigned int cb = 0; cb < sourceViewB.getNumberOfColumns(); ++cb)
631  {
632  // skip columns that are not UID or GroupID columns
633  if(!(sourceViewA.getColumnInfo(cb).isUID() ||
634  sourceViewA.getColumnInfo(cb).isGroupID()))
635  continue;
636 
637  __COUT__ << "Have an ID column: " << cb << " "
638  << sourceViewA.getColumnInfo(cb).getType() << __E__;
639 
640  // at this point we have an ID column, verify B and mockup are the same
641  if(sourceViewA.getColumnInfo(cb).getType() !=
642  sourceViewB.getColumnInfo(cb).getType() ||
643  sourceViewA.getColumnInfo(cb).getType() !=
644  mockupTableView_.getColumnInfo(cb).getType())
645  {
646  __SS__ << "Error! " << sourceViewA.getColumnInfo(cb).getType()
647  << " column " << cb
648  << " of source view A must match source B and destination mock-up "
649  "view."
650  << " Column of source B is ["
651  << sourceViewA.getColumnInfo(cb).getType()
652  << "] and of destination mockup is ["
653  << mockupTableView_.getColumnInfo(cb).getType() << "]." << __E__;
654  __SS_THROW__;
655  }
656 
657  // getColLinkGroupID(childLinkIndex)
658 
659  std::vector<std::string /*converted uidB*/>
660  localConvertedIds; // used for conflict completeness check
661 
662  if(sourceViewA.getColumnInfo(cb).isGroupID())
663  {
664  std::set<std::string> aGroupids = sourceViewA.getSetOfGroupIDs(cb);
665  std::set<std::string> bGroupids = sourceViewB.getSetOfGroupIDs(cb);
666 
667  for(const auto& bGroupid : bGroupids)
668  {
669  if(aGroupids.find(bGroupid) == aGroupids.end())
670  continue;
671 
672  // if here, found conflict
673  __COUT__ << "found conflict: " << getTableName() << "/" << bGroupid
674  << __E__;
675 
676  // extract starting uniqueId number
677  {
678  const std::string& str = bGroupid;
679  numericStartIndex = str.size();
680 
681  // find first non-numeric character
682  while(numericStartIndex - 1 < str.size() &&
683  str[numericStartIndex - 1] >= '0' &&
684  str[numericStartIndex - 1] <= '9')
685  --numericStartIndex;
686 
687  if(numericStartIndex < str.size())
688  {
689  uniqueId = atoi(str.substr(numericStartIndex).c_str()) + 1;
690  uniqueIdBase = str.substr(0, numericStartIndex);
691  }
692  else
693  {
694  uniqueId = 0;
695  uniqueIdBase = str;
696  }
697 
698  __COUTV__(uniqueIdBase);
699  __COUTV__(uniqueId);
700  } // end //extract starting uniqueId number
701 
702  // find unique id string
703  {
704  sprintf(indexString, "%u", uniqueId);
705  uniqueIdString = uniqueIdBase + indexString;
706  __COUTV__(uniqueIdString);
707 
708  found = false;
709  // check converted records and source A and B for conflicts
710  if(aGroupids.find(uniqueIdString) != aGroupids.end())
711  found = true;
712  if(!found && bGroupids.find(uniqueIdString) != bGroupids.end())
713  found = true;
714  if(!found && bGroupids.find(uniqueIdString) != bGroupids.end())
715  found = true;
716  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
717  if(localConvertedIds[ra] == uniqueIdString)
718  found = true;
719 
720  while(found) // while conflict, change id
721  {
722  ++uniqueId;
723  sprintf(indexString, "%u", uniqueId);
724  uniqueIdString = uniqueIdBase + indexString;
725  __COUTV__(uniqueIdString);
726 
727  found = false;
728  // check converted records and source A and B for conflicts
729  if(aGroupids.find(uniqueIdString) != aGroupids.end())
730  found = true;
731  if(!found &&
732  bGroupids.find(uniqueIdString) != bGroupids.end())
733  found = true;
734  if(!found &&
735  bGroupids.find(uniqueIdString) != bGroupids.end())
736  found = true;
737  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
738  if(localConvertedIds[ra] == uniqueIdString)
739  found = true;
740  }
741  } // end find unique id string
742 
743  // have unique id string now
744  __COUTV__(uniqueIdString);
745 
746  groupidConversionMap
747  [std::pair<std::string /*original table*/,
748  std::pair<std::string /*group linkid*/,
749  std::string /*original gidB*/> >(
750  getTableName(),
751  std::pair<std::string /*group linkid*/,
752  std::string /*original gidB*/>(
753  sourceViewB.getColumnInfo(cb).getChildLinkIndex(),
754  bGroupid))] = uniqueIdString;
755  localConvertedIds.push_back(uniqueIdString); // save to vector for
756  // future conflict
757  // checking within table
758 
759  } // end row find unique id string loop for groupid
760 
761  // done creating conversion map
762  __COUTV__(StringMacros::mapToString(groupidConversionMap));
763 
764  } // end group id conversion map fill
765  else // start uid conversion map fill
766  {
767  for(unsigned int rb = 0; rb < sourceViewB.getNumberOfRows(); ++rb)
768  {
769  found = false;
770 
771  for(ra = 0; ra < sourceViewA.getDataView().size(); ++ra)
772  if(sourceViewA.getValueAsString(ra, cb) ==
773  sourceViewB.getValueAsString(rb, cb))
774  {
775  found = true;
776  break;
777  }
778 
779  if(!found)
780  continue;
781 
782  // found conflict
783  __COUT__ << "found conflict: " << getTableName() << "/"
784  << sourceViewB.getDataView()[rb][cb] << __E__;
785 
786  // extract starting uniqueId number
787  {
788  const std::string& str = sourceViewB.getDataView()[rb][cb];
789  numericStartIndex = str.size();
790 
791  // find first non-numeric character
792  while(numericStartIndex - 1 < str.size() &&
793  str[numericStartIndex - 1] >= '0' &&
794  str[numericStartIndex - 1] <= '9')
795  --numericStartIndex;
796 
797  if(numericStartIndex < str.size())
798  {
799  uniqueId = atoi(str.substr(numericStartIndex).c_str()) + 1;
800  uniqueIdBase = str.substr(0, numericStartIndex);
801  }
802  else
803  {
804  uniqueId = 0;
805  uniqueIdBase = str;
806  }
807 
808  __COUTV__(uniqueIdBase);
809  __COUTV__(uniqueId);
810  } // end //extract starting uniqueId number
811 
812  // find unique id string
813  {
814  sprintf(indexString, "%u", uniqueId);
815  uniqueIdString = uniqueIdBase + indexString;
816  __COUTV__(uniqueIdString);
817 
818  found = false;
819  // check converted records and source A and B for conflicts
820  for(ra = 0; !found && ra < sourceViewA.getDataView().size(); ++ra)
821  if(sourceViewA.getValueAsString(ra, cb) == uniqueIdString)
822  found = true;
823  for(ra = 0; !found && ra < sourceViewB.getDataView().size(); ++ra)
824  if(ra == rb)
825  continue; // skip record in question
826  else if(sourceViewB.getValueAsString(ra, cb) ==
827  uniqueIdString)
828  found = true;
829  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
830  if(localConvertedIds[ra] == uniqueIdString)
831  found = true;
832 
833  while(found) // while conflict, change id
834  {
835  ++uniqueId;
836  sprintf(indexString, "%u", uniqueId);
837  uniqueIdString = uniqueIdBase + indexString;
838  __COUTV__(uniqueIdString);
839 
840  found = false;
841  // check converted records and source A and B for conflicts
842  for(ra = 0; !found && ra < sourceViewA.getDataView().size();
843  ++ra)
844  if(sourceViewA.getValueAsString(ra, cb) == uniqueIdString)
845  found = true;
846  for(ra = 0; !found && ra < sourceViewB.getDataView().size();
847  ++ra)
848  if(ra == rb)
849  continue; // skip record in question
850  else if(sourceViewB.getValueAsString(ra, cb) ==
851  uniqueIdString)
852  found = true;
853  for(ra = 0; !found && ra < localConvertedIds.size(); ++ra)
854  if(localConvertedIds[ra] == uniqueIdString)
855  found = true;
856  }
857  } // end find unique id string
858 
859  // have unique id string now
860  __COUTV__(uniqueIdString);
861 
862  uidConversionMap[std::pair<std::string /*original table*/,
863  std::string /*original uidB*/>(
864  getTableName(), sourceViewB.getValueAsString(rb, cb))] =
865  uniqueIdString;
866  localConvertedIds.push_back(uniqueIdString); // save to vector for
867  // future conflict
868  // checking within table
869 
870  } // end row find unique id string loop
871 
872  // done creating conversion map
873  __COUTV__(StringMacros::mapToString(uidConversionMap));
874  }
875 
876  } // end column find unique id string loop
877 
878  } // end rename conversion map create
879  else
880  __COUT__ << "Not filling record conversion map." << __E__;
881 
882  if(!applyRecordConversionMaps)
883  {
884  __COUT__ << "Not applying record conversion map." << __E__;
885  return TableVersion(); // return invalid
886  }
887  else
888  {
889  __COUT__ << "Applying record conversion map." << __E__;
890  __COUTV__(StringMacros::mapToString(uidConversionMap));
891  __COUTV__(StringMacros::mapToString(groupidConversionMap));
892  }
893 
894  // if destinationVersion is INVALID, creates next available temporary version
895  destinationVersion = createTemporaryView(TableVersion(), destinationVersion);
896 
897  __COUT__ << "Merging from (A) " << sourceViewA.getTableName() << "_v"
898  << sourceViewA.getVersion() << " and (B) " << sourceViewB.getTableName()
899  << "_v" << sourceViewB.getVersion() << " to " << getTableName() << "_v"
900  << destinationVersion << " with approach '" << mergeApproach << ".'"
901  << __E__;
902 
903  // if the merge fails then delete the destinationVersion view
904  try
905  {
906  // start with a copy of source view A
907 
908  TableView* destinationView = &(tableViews_[destinationVersion].copy(
909  sourceViewA, destinationVersion, author));
910 
911  unsigned int destRow, destSize = destinationView->getDataView().size();
912  unsigned int cb;
913  bool found;
914  std::map<std::pair<std::string /*original table*/, std::string /*original uidB*/>,
915  std::string /*converted uidB*/>::iterator uidConversionIt;
916  std::map<std::pair<std::string /*original table*/,
917  std::pair<std::string /*group linkid*/,
918  std::string /*original gidB*/> >,
919  std::string /*converted uidB*/>::iterator groupidConversionIt;
920 
921  bool linkIsGroup;
922  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
923  std::string strb;
924  size_t stri;
925 
926  unsigned int colUID = mockupTableView_.getColUID(); // setup UID column
927 
928  // handle merger with conflicts consideration
929  for(unsigned int rb = 0; rb < sourceViewB.getNumberOfRows(); ++rb)
930  {
931  if(mergeApproach == "Rename")
932  {
933  // rename -- All records from both groups are maintained, but
934  // conflicts from B are renamed. Must maintain a map of
935  // UIDs that are remapped to new name for groupB,
936  // because linkUID fields must be preserved.
937 
938  // conflict does not matter (because record conversion map is already
939  // created, always take and append the B record copy row from B to new
940  // row
941  destRow = destinationView->copyRows(
942  author,
943  sourceViewB,
944  rb,
945  1 /*srcRowsToCopy*/,
946  -1 /*destOffsetRow*/,
947  generateUniqueDataColumns /*generateUniqueDataColumns*/);
948 
949  // check every column and remap conflicting names
950 
951  for(cb = 0; cb < sourceViewB.getNumberOfColumns(); ++cb)
952  {
953  if(sourceViewB.getColumnInfo(cb).isChildLink())
954  continue; // skip link columns that have table name
955  else if(sourceViewB.getColumnInfo(cb).isChildLinkUID())
956  {
957  __COUT__ << "Checking UID link... col=" << cb << __E__;
958  sourceViewB.getChildLink(cb, linkIsGroup, linkPair);
959 
960  // if table and uid are in conversion map, convert
961  if((uidConversionIt = uidConversionMap.find(
962  std::pair<std::string /*original table*/,
963  std::string /*original uidB*/>(
964  sourceViewB.getValueAsString(rb, linkPair.first),
965  sourceViewB.getValueAsString(
966  rb, linkPair.second)))) != uidConversionMap.end())
967  {
968  __COUT__ << "Found entry to remap: "
969  << sourceViewB.getDataView()[rb][linkPair.second]
970  << " ==> " << uidConversionIt->second << __E__;
971  destinationView->setValueAsString(
972  uidConversionIt->second, destRow, linkPair.second);
973  }
974  }
975  else if(sourceViewB.getColumnInfo(cb).isChildLinkGroupID())
976  {
977  __COUT__ << "Checking GroupID link... col=" << cb << __E__;
978  sourceViewB.getChildLink(cb, linkIsGroup, linkPair);
979 
980  // if table and uid are in conversion map, convert
981  if((groupidConversionIt = groupidConversionMap.find(
982  std::pair<std::string /*original table*/,
983  std::pair<std::string /*group linkid*/,
984  std::string /*original gidB*/> >(
985  sourceViewB.getValueAsString(rb, linkPair.first),
986  std::pair<std::string /*group linkid*/,
987  std::string /*original gidB*/>(
988  sourceViewB.getColumnInfo(cb).getChildLinkIndex(),
989  sourceViewB.getValueAsString(
990  rb, linkPair.second))))) !=
991  groupidConversionMap.end())
992  {
993  __COUT__ << "Found entry to remap: "
994  << sourceViewB.getDataView()[rb][linkPair.second]
995  << " ==> " << groupidConversionIt->second << __E__;
996  destinationView->setValueAsString(
997  groupidConversionIt->second, destRow, linkPair.second);
998  }
999  }
1000  else if(sourceViewB.getColumnInfo(cb).isUID())
1001  {
1002  __COUT__ << "Checking UID... col=" << cb << __E__;
1003  if((uidConversionIt = uidConversionMap.find(
1004  std::pair<std::string /*original table*/,
1005  std::string /*original uidB*/>(
1006  getTableName(),
1007  sourceViewB.getValueAsString(rb, cb)))) !=
1008  uidConversionMap.end())
1009  {
1010  __COUT__ << "Found entry to remap: "
1011  << sourceViewB.getDataView()[rb][cb] << " ==> "
1012  << uidConversionIt->second << __E__;
1013  destinationView->setValueAsString(
1014  uidConversionIt->second, destRow, cb);
1015  }
1016  }
1017  else if(sourceViewB.getColumnInfo(cb).isGroupID())
1018  {
1019  __COUT__ << "Checking GroupID... col=" << cb << __E__;
1020  if((groupidConversionIt = groupidConversionMap.find(
1021  std::pair<std::string /*original table*/,
1022  std::pair<std::string /*group linkid*/,
1023  std::string /*original gidB*/> >(
1024  getTableName(),
1025  std::pair<std::string /*group linkid*/,
1026  std::string /*original gidB*/>(
1027  sourceViewB.getColumnInfo(cb).getChildLinkIndex(),
1028  sourceViewB.getValueAsString(rb, cb))))) !=
1029  groupidConversionMap.end())
1030  {
1031  __COUT__ << "Found entry to remap: "
1032  << sourceViewB.getDataView()[rb][cb] << " ==> "
1033  << groupidConversionIt->second << __E__;
1034  destinationView->setValueAsString(
1035  groupidConversionIt->second, destRow, cb);
1036  }
1037  }
1038  else
1039  {
1040  // look for text link to a Table/UID in the map
1041  strb = sourceViewB.getValueAsString(rb, cb);
1042  if(strb.size() > getTableName().size() + 2 && strb[0] == '/')
1043  {
1044  // check for linked name
1045  __COUT__ << "Checking col" << cb << " " << strb << __E__;
1046 
1047  // see if there is an entry in p
1048  for(const auto& mapPairToPair : uidConversionMap)
1049  {
1050  if((stri = strb.find(mapPairToPair.first.first + "/" +
1051  mapPairToPair.first.second)) !=
1052  std::string::npos)
1053  {
1054  __COUT__ << "Found a text link match (stri=" << stri
1055  << ")! "
1056  << (mapPairToPair.first.first + "/" +
1057  mapPairToPair.first.second)
1058  << " ==> " << mapPairToPair.second << __E__;
1059 
1060  // insert mapped substitution into string
1061  destinationView->setValueAsString(
1062  strb.substr(0, stri) +
1063  (mapPairToPair.first.first + "/" +
1064  mapPairToPair.first.second) +
1065  strb.substr(stri +
1066  (mapPairToPair.first.first + "/" +
1067  mapPairToPair.first.second)
1068  .size()),
1069  destRow,
1070  cb);
1071 
1072  __COUT__
1073  << "Found entry to remap: "
1074  << sourceViewB.getDataView()[rb][cb] << " ==> "
1075  << destinationView->getDataView()[destRow][cb]
1076  << __E__;
1077  break;
1078  }
1079  } // end uid conversion map loop
1080  }
1081  }
1082  } // end column loop over B record
1083 
1084  continue;
1085  } // end rename, no-conflict handling
1086 
1087  // if here, then not doing rename, so conflicts matter
1088 
1089  found = false;
1090 
1091  for(destRow = 0; destRow < destSize; ++destRow)
1092  if(destinationView->getValueAsString(destRow, colUID) ==
1093  sourceViewB.getValueAsString(rb, colUID))
1094  {
1095  found = true;
1096  break;
1097  }
1098  if(!found) // no conflict
1099  {
1100  __COUT__ << "No " << mergeApproach << " conflict: " << __E__;
1101 
1102  if(mergeApproach == "replace" || mergeApproach == "skip")
1103  {
1104  // no conflict so append the B record
1105  // copy row from B to new row
1106  destinationView->copyRows(
1107  author, sourceViewB, rb, 1 /*srcRowsToCopy*/);
1108  }
1109  else
1110 
1111  continue;
1112  } // end no-conflict handling
1113 
1114  // if here, then there was a conflict
1115 
1116  __COUT__ << "found " << mergeApproach
1117  << " conflict: " << sourceViewB.getDataView()[rb][colUID] << __E__;
1118 
1119  if(mergeApproach == "replace")
1120  {
1121  // replace -- Any UID conflicts for a record are replaced by the
1122  // record from group B.
1123 
1124  // delete row in destination
1125  destinationView->deleteRow(destRow--); // delete row and back up pointer
1126  --destSize;
1127 
1128  // append the B record now
1129  // copy row from B to new row
1130  destinationView->copyRows(author, sourceViewB, rb, 1 /*srcRowsToCopy*/);
1131  }
1132  // else if (mergeApproach == "skip") then do nothing with conflicting B record
1133  }
1134 
1135  destinationView->print();
1136  }
1137  catch(...) // if the copy fails then delete the destinationVersion view
1138  {
1139  __COUT_ERR__ << "Failed to merge " << sourceViewA.getTableName() << "_v"
1140  << sourceViewA.getVersion() << " and " << sourceViewB.getTableName()
1141  << "_v" << sourceViewB.getVersion() << " into " << getTableName()
1142  << "_v" << destinationVersion << __E__;
1143  __COUT_WARN__ << "Deleting the failed destination version " << destinationVersion
1144  << __E__;
1145  eraseView(destinationVersion);
1146  throw; // and rethrow
1147  }
1148 
1149  return destinationVersion;
1150 } // end mergeViews
1151 
1152 //==============================================================================
1153 // copyView
1154 // copies source view (including version) and places in self
1155 // as destination temporary version.
1156 // if destination version is invalid, then next available temporary version is chosen
1157 // if conflict, throw exception
1158 //
1159 // Returns version of new temporary view that was created.
1160 TableVersion TableBase::copyView(const TableView& sourceView,
1161  TableVersion destinationVersion,
1162  const std::string& author)
1163 {
1164  // check that column sizes match
1165  if(sourceView.getNumberOfColumns() != mockupTableView_.getNumberOfColumns())
1166  {
1167  __SS__ << "Error! Number of Columns of source view must match destination "
1168  "mock-up view."
1169  << "Dimension of source is [" << sourceView.getNumberOfColumns()
1170  << "] and of destination mockup is ["
1171  << mockupTableView_.getNumberOfColumns() << "]." << __E__;
1172  __SS_THROW__;
1173  }
1174 
1175  // check for destination version confict
1176  if(!destinationVersion.isInvalid() &&
1177  tableViews_.find(destinationVersion) != tableViews_.end())
1178  {
1179  __SS__ << "Error! Asked to copy a view with a conflicting version: "
1180  << destinationVersion << __E__;
1181  __SS_THROW__;
1182  }
1183 
1184  // if destinationVersion is INVALID, creates next available temporary version
1185  destinationVersion = createTemporaryView(TableVersion(), destinationVersion);
1186 
1187  __COUT__ << "Copying from " << sourceView.getTableName() << "_v"
1188  << sourceView.getVersion() << " to " << getTableName() << "_v"
1189  << destinationVersion << __E__;
1190 
1191  try
1192  {
1193  tableViews_[destinationVersion].copy(sourceView, destinationVersion, author);
1194  }
1195  catch(...) // if the copy fails then delete the destinationVersion view
1196  {
1197  __COUT_ERR__ << "Failed to copy from " << sourceView.getTableName() << "_v"
1198  << sourceView.getVersion() << " to " << getTableName() << "_v"
1199  << destinationVersion << __E__;
1200  __COUT_WARN__ << "Deleting the failed destination version " << destinationVersion
1201  << __E__;
1202  eraseView(destinationVersion);
1203  throw; // and rethrow
1204  }
1205 
1206  return destinationVersion;
1207 } // end copyView()
1208 
1209 //==============================================================================
1210 // createTemporaryView
1211 // -1, from MockUp, else from valid view version
1212 // destTemporaryViewVersion is starting point for search for available temporary
1213 // versions. if destTemporaryViewVersion is invalid, starts search at
1214 // TableVersion::getNextTemporaryVersion().
1215 // returns new temporary version number (which is always negative)
1216 TableVersion TableBase::createTemporaryView(TableVersion sourceViewVersion,
1217  TableVersion destTemporaryViewVersion)
1218 {
1219  __COUT__ << "Table: " << getTableName() << __E__;
1220 
1221  __COUT__ << "Num of Views: " << tableViews_.size()
1222  << " (Temporary Views: " << (tableViews_.size() - getNumberOfStoredViews())
1223  << ")" << __E__;
1224 
1225  TableVersion tmpVersion = destTemporaryViewVersion;
1226  if(tmpVersion.isInvalid())
1227  tmpVersion = TableVersion::getNextTemporaryVersion();
1228  while(isStored(tmpVersion) && // find a new valid temporary version
1229  !(tmpVersion = TableVersion::getNextTemporaryVersion(tmpVersion)).isInvalid())
1230  ;
1231  if(isStored(tmpVersion) || tmpVersion.isInvalid())
1232  {
1233  __SS__ << "Invalid destination temporary version: " << destTemporaryViewVersion
1234  << ". Expected next temporary version < " << tmpVersion << __E__;
1235  __COUT_ERR__ << ss.str();
1236  __SS_THROW__;
1237  }
1238 
1239  if(sourceViewVersion ==
1240  TableVersion::INVALID || // use mockup if sourceVersion is -1 or not found
1241  tableViews_.find(sourceViewVersion) == tableViews_.end())
1242  {
1243  if(sourceViewVersion != -1)
1244  {
1245  __SS__ << "ERROR: sourceViewVersion " << sourceViewVersion << " not found. "
1246  << "Invalid source version. Version requested is not stored (yet?) or "
1247  "does not exist."
1248  << __E__;
1249  __COUT_ERR__ << ss.str();
1250  __SS_THROW__;
1251  }
1252  __COUT__ << "Using Mock-up view" << __E__;
1253  tableViews_[tmpVersion].copy(
1254  mockupTableView_, tmpVersion, mockupTableView_.getAuthor());
1255  }
1256  else
1257  {
1258  try // do not allow init to throw an exception here..
1259  { // it's ok to copy invalid data, the user may be trying to change it
1260  tableViews_[tmpVersion].copy(tableViews_[sourceViewVersion],
1261  tmpVersion,
1262  tableViews_[sourceViewVersion].getAuthor());
1263  }
1264  catch(...)
1265  {
1266  __COUT_WARN__
1267  << "createTemporaryView() Source view failed init(). "
1268  << "This is being ignored (hopefully the new copy is being fixed)."
1269  << __E__;
1270  }
1271  }
1272 
1273  return tmpVersion;
1274 } // end createTemporaryView()
1275 
1276 //==============================================================================
1277 // getNextAvailableTemporaryView
1278 // TableVersion::INVALID is always MockUp
1279 // returns next available temporary version number (which is always negative)
1280 TableVersion TableBase::getNextTemporaryVersion() const
1281 {
1282  TableVersion tmpVersion;
1283 
1284  // std::map guarantees versions are in increasing order!
1285  if(tableViews_.size() != 0 && tableViews_.begin()->first.isTemporaryVersion())
1286  tmpVersion = TableVersion::getNextTemporaryVersion(tableViews_.begin()->first);
1287  else
1288  tmpVersion = TableVersion::getNextTemporaryVersion();
1289 
1290  // verify tmpVersion is ok
1291  if(isStored(tmpVersion) || tmpVersion.isInvalid() || !tmpVersion.isTemporaryVersion())
1292  {
1293  __SS__ << "Invalid destination temporary version: " << tmpVersion << __E__;
1294  __COUT_ERR__ << ss.str();
1295  __SS_THROW__;
1296  }
1297  return tmpVersion;
1298 }
1299 
1300 //==============================================================================
1301 // getNextVersion
1302 // returns next available new version
1303 // the implication is any version number equal or greater is available.
1304 TableVersion TableBase::getNextVersion() const
1305 {
1306  TableVersion tmpVersion;
1307 
1308  // std::map guarantees versions are in increasing order!
1309  if(tableViews_.size() != 0 && !tableViews_.rbegin()->first.isTemporaryVersion())
1310  tmpVersion = TableVersion::getNextVersion(tableViews_.rbegin()->first);
1311  else
1312  tmpVersion = TableVersion::getNextVersion();
1313 
1314  // verify tmpVersion is ok
1315  if(isStored(tmpVersion) || tmpVersion.isInvalid() || tmpVersion.isTemporaryVersion())
1316  {
1317  __SS__ << "Invalid destination next version: " << tmpVersion << __E__;
1318  __COUT_ERR__ << ss.str();
1319  __SS_THROW__;
1320  }
1321  return tmpVersion;
1322 }
1323 
1324 //==============================================================================
1325 // getTemporaryView
1326 // must be a valid temporary version, and the view must be stored in table.
1327 // temporary version indicates it has not been saved to database and assigned a version
1328 // number
1329 TableView* TableBase::getTemporaryView(TableVersion temporaryVersion)
1330 {
1331  if(!temporaryVersion.isTemporaryVersion() || !isStored(temporaryVersion))
1332  {
1333  __SS__ << getTableName() << ":: Error! Temporary version not found!" << __E__;
1334  __COUT_ERR__ << ss.str();
1335  __SS_THROW__;
1336  }
1337  return &tableViews_[temporaryVersion];
1338 }
1339 
1340 //==============================================================================
1341 // convertToCaps
1342 // static utility for converting table and column names to the caps version
1343 // throw std::runtime_error if not completely alpha-numeric input
1344 std::string TableBase::convertToCaps(std::string& str, bool isConfigName)
1345 {
1346  // append Table to be nice to user
1347  unsigned int configPos = (unsigned int)std::string::npos;
1348  if(isConfigName && (configPos = str.find("Table")) != str.size() - strlen("Table"))
1349  str += "Table";
1350 
1351  // create all caps name and validate
1352  // only allow alpha names with Table at end
1353  std::string capsStr = "";
1354  for(unsigned int c = 0; c < str.size(); ++c)
1355  if(str[c] >= 'A' && str[c] <= 'Z')
1356  {
1357  // add _ before table and if lower case to uppercase
1358  if(c == configPos ||
1359  (c && str[c - 1] >= 'a' &&
1360  str[c - 1] <= 'z') || // if this is a new start of upper case
1361  (c && str[c - 1] >= 'A' &&
1362  str[c - 1] <= 'Z' && // if this is a new start from running caps
1363  c + 1 < str.size() && str[c + 1] >= 'a' && str[c + 1] <= 'z'))
1364  capsStr += "_";
1365  capsStr += str[c];
1366  }
1367  else if(str[c] >= 'a' && str[c] <= 'z')
1368  capsStr += char(str[c] - 32); // capitalize
1369  else if(str[c] >= '0' && str[c] <= '9')
1370  capsStr += str[c]; // allow numbers
1371  else // error! non-alpha
1372  __THROW__(std::string("TableBase::convertToCaps::") +
1373  "Invalid character found in name (allowed: A-Z, a-z, 0-9):" + str);
1374 
1375  return capsStr;
1376 }
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)
Definition: TableBase.cc:550