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