OOFILE  1.9
oofrel.cpp
Go to the documentation of this file.
1 // COPYRIGHT 1994 A.D. Software, All rights reserved
2 
3 // public layer of OOFILE database
4 // implementing relations
5 
6 #include "oofpch_c.h" // for precompilation of core files
7 
8 #ifndef H_OOFIOS
9  #include "oofios.h"
10 #endif
11 #ifndef H_OOF3
12  #include "oof3.h"
13 #endif
14 #ifndef H_OOFREL
15  #include "oofrel.h"
16 #endif
17 #ifndef H_OOFRELX
18  #include "oofrelx.h"
19 #endif
20 #ifndef OOF_EXCEP
21  #include "oofexcep.h"
22 #endif
23 
24 #ifdef OOF_MEM_DEBUG_LAST_INCLUDE
25  #include OOF_MEM_DEBUG_LAST_INCLUDE
26 #endif
27 
28 #ifndef OOF_NO_STDLIB
29  #ifndef std
30  using namespace std;
31  #endif
32 #endif
33 
34 
35 // statics
36 OOF_RelMN* OOF_RelMN::sCurrentlyBuilding = 0;
37 dbRelRefBase* OOF_RelMN::sCurrentRHSref = 0;
38 
39 
40 
41 // -------------------------------------------------------
42 // o o f R e l M N
43 // -------------------------------------------------------
44 OOF_RelMN::OOF_RelMN(dbRelRefBase& lhs, dbRelRefBase& rhs) :
45  mProtoLHS(lhs),
46  mProtoRHS(rhs),
47  mReferences(0),
48  mRelToLHS(0),
49  mRelToRHS(0),
50  mMaintainer(0)
51 {
52  // either side usually would propagate deletes to the intermediate MN table
53  // but they don't go past that table
54  lhs.propagateRelatedDeletes(false);
55  rhs.propagateRelatedDeletes(false);
56 }
57 
58 
59 OOF_RelMN::OOF_RelMN(const OOF_RelMN& rhs) :
60  mProtoLHS(rhs.mProtoLHS),
61  mProtoRHS(rhs.mProtoRHS),
62  mReferences(0),
63  mRelToLHS(rhs.mRelToLHS), // maybe 0
64  mRelToRHS(rhs.mRelToRHS),
65  mMaintainer(0) // won't be listening to new rhs table, so want to recreate
66 {
67 }
68 
69 
70 void
72 {
73  ++mReferences;
74 }
75 
76 
77 void
79 {
80  if (mReferences)
81  --mReferences;
82  if (mReferences==0)
83  delete this;
84 }
85 
86 
87 bool
89 {
90  if (sCurrentlyBuilding==this) {
91  assert(sCurrentRHSref==0); // should never be called twice while building
92  sCurrentRHSref = inRel;
93  return true;
94  }
95  else
96  return false;
97 }
98 
99 
100 bool
101 OOF_RelMN::RelateMN()
102 {
103  assert(mRelToLHS);
104  assert(mRelToRHS);
105  mRelToLHS->relateFromRecord();
106  bool ret = mRelToRHS->selectAllRelated();
107  return ret;
108 }
109 
110 
111 dbTable*
112 OOF_RelMN::BuildRelatedTable(dbRelRefBase* cloner)
113 {
114  sCurrentlyBuilding = this;
115 
116 // cope with the fact that this link may have been declared "backwards"
117 // as far as the calling relationship is concerned. (ie: one link is used to describe
118 // the two directions of traversal)
119  dbRelRefBase *leftProto, *rightProto;
120  if (cloner->fieldTable()->tableNumber()==mProtoLHS.relatedTableNumber()) {
121  leftProto = &mProtoLHS;
122  rightProto = &mProtoRHS;
123  }
124  else { // backwards!
125  leftProto = &mProtoRHS;
126  rightProto = &mProtoLHS;
127  }
128 
129 
130 // do some of the work in the normal dbRelRefBase::BuildRelatedTable()
131 // the BIG difference is that nobody from the LHS points to this object,
132 // unlike the dbRelRefBase's on each side of a relationship, which point to each other
133  dbTable* middleClone = leftProto->fieldTable()->cloneTableWithoutSelection(); // could be either lhs or rhs
135 
136  mRelToLHS = (dbRelRefBase*) middleClone->field(leftProto->fieldNumber());
137  mRelToLHS->completeCloneOfInverse(cloner->fieldTable());
138 
139 // we now have a viable cloned table to manage the middle of the MN relationship
140 // so transition to the RHS is not so hard
141 
142  mRelToRHS = (dbRelRefBase*) middleClone->field(rightProto->fieldNumber());
143  dbTable* ret = mRelToRHS->relatedTable(); // as if normal relationship
144 
145 /*
146 At this point, the middle clone and far rhs clone tables have been constructed,
147 the middle clone's two relationship fields are initialised as is the far rhs
148 relationship field (which points back to the middle).
149 
150 This MNLink is the "prototypical" one in that it was pointed to by the rhs table
151 being cloned to manage the relationship. Thus, it is inappropriate for us to contain
152 the 'real' relationship pointers.
153 
154 As part of the cloning procedure, the dbRelRefBase on the rhs will tell each MN link
155 that it is being cloned. Thus, we need a mechanism to track exactly which MN link
156 should bother to listen. We use sCurrentlyBuilding to tell us this, and cloningRelIs()
157 will record the relref in sCurrentRHSref. Thus as a final step, we need to cleanup these
158 statics, and clone ourself for the rhs.
159 
160 Note that the copy will only be pointed to by the rhs of the relationship (the clone
161 managing the related selection). The lhs will continue to point to this prototypical
162 instance.
163 */
164  assert(sCurrentRHSref); // when rhs table is cloned above, it points to us
165  OOF_RelMN* ourClone = new OOF_RelMN(*this);
166  sCurrentRHSref->setMNlink(ourClone); // this ref is the mRelToLHS of the far rhs clone
167 
168 // cleanup time!
169  sCurrentRHSref = 0;
170  sCurrentlyBuilding = 0;
171  mRelToLHS = 0; // we don't need to know fields - our clone does!
172  mRelToRHS = 0;
173 
174  return ret;
175 }
176 
177 
178 void
180 {
181 // ASSERT - by the time we are called we are guaranteed that the selection is NOT
182 // already related, so avoid some checks to see if middle record exists
183  dbTable* middleTable = mRelToLHS->fieldTable();
184  dbTable* rhsTable = mRelToRHS->relatedTable();
185  assert(rhsTable);
186 
187  dbField* rhsJoinField = mRelToRHS->joinField();
188  OOF_recordSelection* addingSel = (OOF_recordSelection*) inSel->internalSelection(); // unsafe downcast, safe only as long as we don't have another selection type
189  unsigned long numAdding = addingSel->count();
190 
191  if (rhsJoinField) {
192  stSaveSelection preserveRHS(*rhsTable);
193  for (unsigned long i=0; i<numAdding; i++) {
194  oidT rhsOID = addingSel->value(i);
195  rhsTable->selectJustOID(rhsOID);
196  if (rhsTable->count()==1) {
197  middleTable->newRecord();
198  mRelToLHS->updateRelValue();
199  mRelToRHS->updateRelValue();
200  }
201  else {
202  // the rhs has disappeared - do we care?
203  } // get rhs record to copy join value
204  // left unsaved as it should be cached until the parent is saved
205  } // loop rhs selection we're adding links to
206  } // rel to rhs is join
207 
208  else {
209  for (unsigned long i=0; i<numAdding; i++) {
210  middleTable->newRecord();
211  mRelToLHS->updateRelValue();
212  oidT rhsOID = addingSel->value(i);
213  mRelToRHS->setOID(rhsOID);
214  } // loop rhs selection we're adding links to
215  } // rel to rhs is pointer, so don't need to go and change rhs selectoin
216 }
217 
218 
224 void
226 {
227  dbTable* middleTable = mRelToLHS->fieldTable(); // the table CONTAINING the rel, not to which it relates
228  dbTable* rhsTable = mRelToRHS->relatedTable();
229  assert(rhsTable);
230 
231  dbField* rhsJoinField = mRelToRHS->joinField();
232  OOF_recordSelection* removingSel = (OOF_recordSelection*) inSel->internalSelection(); // unsafe downcast, safe only as long as we don't have another selection type
233  unsigned long numRemoving = removingSel->count();
234 
235  if (rhsJoinField) {
236  if (mRelToLHS->relateFromRecord()) { // first get a selection from LHS
237  dbSelection narrowingSel = middleTable->currentSelection();
238  stSaveSelection preserveRHS(*rhsTable);
239  for (unsigned long i=0; i<numRemoving; i++) {
240  oidT rhsOID = removingSel->value(i);
241  rhsTable->selectJustOID(rhsOID);
242  if (rhsTable->count()==1) {
243  if (mRelToRHS->relateFromRecord()) // ALL related record links, not just for lhs
244  // now intersect against the lhs set that we got in advance and are whittling down
245  narrowingSel.intersection_with(*middleTable);
246  }
247  else {
248  // the rhs has disappeared already so don't care about link record
249  } // get rhs record to copy join value
250  } // loop rhs selection we're removing links from
251  if (narrowingSel.count()) {
252  middleTable->setSelection(narrowingSel); // finally, just the set to delete
253  middleTable->deleteSelection();
254  } // got a set to delete
255  } // have any middle recs from lhs
256  } // rel to rhs is join
257 
258  else {
259  assert(!"Removing MN links with pointers not yet supported"); //lint !e506 constant Boolean
260  } // rel to rhs is pointer, so don't need to go and change rhs selectoin
261 }
262 
263 
264 void
266 {
267  dbTable* middleTable = mRelToLHS->fieldTable();
268  middleTable->deleteSelection();
269 }
270 
271 
272 void
274 {
275 // an OOF_RelMN doesn't need to know the dbRelRefBase that directly
276 // owns it, so that object is passed in here
277  if (!mMaintainer)
278  mMaintainer = new dbRelMaintainer(mRelToRHS, this);
279 }
280 
281 
282 // -------------------------------------------------------
283 // d b R e l R e f B a s e
284 // -------------------------------------------------------
285 dbRelRefBase::dbRelRefBase(const char* fieldName, unsigned long maxLinks) :
286  dbField(fieldName),
287  mMinLinks(0),
288  mMaxLinks(maxLinks),
289  mRelatedTableProto(0),
290  mActualRelatedTable(0),
291  mInverseFieldNumber(0),
292  mJoinField(0),
293  mPropagatesDeletes(false),
294  mInverseField(0),
295  mMNlink(0)
296 {
297 }
298 
300  dbField(rhs),
301  mMinLinks(rhs.mMinLinks),
302  mMaxLinks(rhs.mMaxLinks),
303  mRelatedTableProto(rhs.mRelatedTableProto),
304  mActualRelatedTable(0),
305  mInverseFieldNumber(rhs.mInverseFieldNumber),
306  mJoinField(rhs.mJoinField), // see postCloneTableCleanup
307  mPropagatesDeletes(rhs.mPropagatesDeletes),
308  mInverseField(0),
309  mMNlink(0)
310 {
311  if (rhs.mMNlink) {
312  bool thisIsTheRelForWhichWeAreBeingCloned = rhs.mMNlink->cloningRelIs(this); // link will later fix us up
313  if (!thisIsTheRelForWhichWeAreBeingCloned)
314  setMNlink(rhs.mMNlink);
315  }
316 }
317 
318 
320 {
321  if (mMNlink)
322  mMNlink->decRefs();
323 }
324 
325 
326 dbField*
328 {
329  return new dbRelRefBase(*this);
330 }
331 
332 
333 void
335 {
336  if (mJoinField) {
337  // by this stage we are guaranteed that all fields in the table exist and
338  // have been registered with the table, so can pull out our real field from
339  // the dictionary.
341  }
342 
343 }
344 
345 
346 void
348 {
349  assert(!mInverseField); // good check to see multiple calls
350  mInverseField = &rhs; // will be changed later to point to actual inverse but useful for lookups
353  if (!rhs.isRef() && !mJoinField)
354  index(kIndexed); // unless a join field later specified
355 #ifdef OOF_DEBUG
356  if ((mJoinField==0) != (rhs.mJoinField==0))
357  dbConnect::raise(stringstream() << flush << "dbRelRefBase::SetCommonRelationshipFields\n"
358  << "Relationship specified with join field on only one side, between:\n"
359  << fieldName() << " and " << rhs.fieldName()
360  );
361 #else
362  assert((mJoinField==0) == (rhs.mJoinField==0));
363 #endif
364 }
365 
366 
387 void
389 {
391  const bool wasLHS = !rhs.CompletelySpecified();
392  if (wasLHS) {
393  rhs.relatesTo(*this); // recurse to setup RHS "rhs" in this call will be the original left
394  }
395  // finish init now both sides of relationship are pointing at each other
396  if (wasLHS) {
397  if (isOneToMany())
398  propagateRelatedDeletes(true); // ie: 1:N
399  }
400  else {
402  dbConnect::raise(stringstream() << flush << "dbRelRefBase::SetCommonRelationshipFields\n"
403  << "incorrectly specified relationship, check the table type on both sides of\n"
404  << fieldTable()->defaultName()
405  << " to "
406  << rhs.fieldTable()->defaultName()
407  );
408 
409  }
410  } // checks for !wasLHS
411 }
412 
413 
414 void
416 {
417  assert(joinBy.fieldTable() == mTable);
418  mJoinField = &joinBy;
419  index(kNotIndexed); // only indexed if an N-ary side of an oid relationship
420 }
421 
422 
423 void
425 {
426  #ifdef OOF_DEBUG
427  if (!mRelatedTableProto)
428  dbConnect::raise(stringstream() << flush << "Relationship field No " << fieldNumber()
429  << " in table " << tableName() << " lacks a rhs");
430  #else
431  assert(mRelatedTableProto);
432  #endif
433  assert(mActualRelatedTable==0); // should only be called in this circumstance
434  if (mMNlink)
435  mActualRelatedTable = mMNlink->BuildRelatedTable(this);
436  else
438  assert(mActualRelatedTable);
440  mActualRelatedTable->setTableValid(false); // only validates selection when referred to, lazy evalution of actual relationship
441 
443  assert(mInverseField);
444 
445  assert( (mJoinField==0) == (mInverseField->joinField()==0) ); // either both zero or both pointers
447 }
448 
449 
450 void
452 {
453  mActualRelatedTable = cloner->fieldTable();
454 
455  if (mMNlink) { // we have another rel that links to a middle table used to manage the MN sets
456  // it is our caller's responsibility to make sure this is after the other rel has been linked
457  assert(mTable->mRelToLHS); // the other rel
458  mTable->mRelToLHS->unsubscribe(); // tell the other rel not to react (to changes in the middle table)
459  // a table only needs to know mRelToLHS when the table is a clone managing a related selection
460  }
461  mTable->mRelToLHS = this;
463  // depend on the lhs of the rel for things like context changes, selection changes
464  mInverseField = cloner;
465 }
466 
467 
468 void
470 {
471  mActualRelatedTable = clonerTable;
472  mTable->mRelToLHS = this;
473 
474  if (!mMNlink) {
476  }
478 // also make the inverse point at us
479  // assert(!mInverseField->mActualRelatedTable);
482 }
483 
484 
495 void
497 {
498  if (mMNlink)
499  mMNlink->decRefs();
500  mMNlink = inLink;
501  inLink->incRefs();
503 }
504 
505 
506 bool
507 dbRelRefBase::relateRecord(bool broadcastChange)
508 {
509  assert(mInverseField);
510  assert(mActualRelatedTable); // can't get here without being complete
511  bool ret = mInverseField->relateFromRecord(false);
512  if (broadcastChange) {
514  }
515  return ret;
516 }
517 
518 
519 bool
520 dbRelRefBase::selectAllRelated(bool broadcastChange)
521 {
522  if (!mActualRelatedTable)
524 
525  mTable->setTableValid(true); // don't care if loaded or not, we're using it
526  unsigned long numRecs = mTable->count();
527 
528  bool ret = false;
529  if (numRecs==0)
531  else {
532  mTable->start();
533  relateRecord(false); // get rhs selection for 1st record
534  if (numRecs>1) {
535  assert(mActualRelatedTable);
537  numRecs--;
538  for (unsigned long i=0; i < numRecs ; i++) {
539  mTable->next();
540  if (relateRecord(false)) { // updates the clone in mActualRelatedTable
541  combiningSels.union_with(*mActualRelatedTable);
542  }
543  }
544  mActualRelatedTable->setSelection(combiningSels);
545  }
546  ret = !mActualRelatedTable->isEmpty();
547  } // any recs
548 
549  if (broadcastChange)
551  return ret;
552 }
553 
554 
579 bool
580 dbRelRefBase::relateFromRecord(bool broadcastChange)
581 {
582  mTable->setTableValid(true);
583 OOF_DEBUG_LOG(stringstream() << flush << "dbRelRefBase::relateFromRecord\t"
584  << hex << (void*)this
585  << "\tfrom table: " << mActualRelatedTable->tableName() << " (" << (void*)mActualRelatedTable << ')'
586  << "\tour table: " << mTable->tableName() << " (" << (void*)mTable << ')'
587 );
588 
589  bool ret = false;
590  if (mMNlink) {
591  ret = mMNlink->RelateMN();
592  }
593  else {
594  if (mJoinField) // is a join relationship
595  {
597  }
598  else {
599  if (mInverseField->isRef()) {
600  oidT theOID = mInverseField->OID(); // reading value from a field ensures rec loaded
601  if (theOID)
602  ret = mBackend->selectJustOID(theOID);
603  else {
604  mBackend->selectNone();
605  ret = false;
606  }
607  } // 1:1 or N:1
608  else {
609  oidT oidToFind = mInverseField->fieldTable()->currentOID();
610  ret = mBackend->searchEqual(this, &oidToFind);
611  } // 1:N
612  } // join vs pointer 1:1, 1:N or N:1
613  } // M:N relationship
614  mTable->setTableValid(true); // should be redundant but possibly invalidated by above searches
615  if (broadcastChange)
617  return ret;
618 }
619 
620 
621 
622 unsigned long
624 {
625  unsigned long numRecs = mTable->count();
626 
627  if (numRecs==0)
628  return 0;
629 
630  // NOT YET IMPLEMENTED - optimise these - just need counts, not selections
631  // BUT may not be that simple - must NOT have duplicates
632  mTable->start();
633  if (!mActualRelatedTable)
635  relateRecord(false); // get rhs selection for 1st record
636  if (numRecs==1)
637  return mActualRelatedTable->count();
638 
640  numRecs--;
641  for (unsigned long i=0; i < numRecs ; i++) {
642  mTable->next();
643  relateRecord(false); // updates the clone in mActualRelatedTable
644  combiningSels.union_with(*mActualRelatedTable);
645  }
646  return combiningSels.count();
647 }
648 
649 
650 unsigned long
652 {
653  unsigned long numRecs = mTable->count();
654 
655  if (numRecs==0)
656  return 0;
657 
658  mTable->start();
659  if (!mActualRelatedTable)
661  relateRecord(false); // get rhs selection for 1st record
662  if (numRecs==1)
663  return mActualRelatedTable->count();
664 
666  if (numRecs>1) {
667  numRecs--;
668  for (unsigned long i=0; i < numRecs ; i++) {
669  mTable->next();
670  relateRecord(false); // updates the clone in mActualRelatedTable
671  combiningSels.union_with(*mActualRelatedTable);
672  }
673  }
674  combiningSels.intersection_with(*rhs);
675  return combiningSels.count();
676 }
677 
678 
681 {
682  return relationshipField;
683 }
684 
685 
688 {
689  if (mJoinField)
690  return fieldType();
691  else
692  return uLongField;
693 }
694 
695 
696 bool
698 {
699  if (mJoinField)
700  return true;
701  else
702  return false; // stores an OID
703 }
704 
705 
706 unsigned long dbRelRefBase::fieldStorageLen() const
707 {
708  if (mJoinField)
709  return 0; // no storage in the record
710  else
711  return sizeof(oidT); // stores an OID
712 }
713 
714 
715 oidT
717 {
718  assert(!mJoinField); // shouldn't be called this is a join relationship
719  assert(mBackend);
720  const oidT currentOID = mBackend->readLong(this);
721  return currentOID;
722 }
723 
724 
725 void
727 {
728  oidT currentOID = OID();
729  if (oid!=currentOID) {
730  assert(sizeof(oidT)==sizeof(long));
731  assert(mBackend);
732  mBackend->writeLong(oid, this);
733  }
734 }
735 
736 
746 void
748 {
749  assert(mTable);
750 /*
751 This test is not sufficient as we can also be called whilst cloning of a table
752 is occurring. We need to be able to tell that cloning is complete to enforce this
753 check.
754  if (!mTable->inDeclarativePhase()) {
755 #ifdef OOF_DEBUG
756  dbConnect::raise(
757  stringstream() << flush << "propagateRelatedDeletes called after opening, on "
758  << mTable->tableName() << ':' << fieldName()
759  );
760 #endif
761  return; // can't change once open
762  }
763 */
764 
765  if (prop) {
766  if (isOneToMany() || isOneToOne())
767  mPropagatesDeletes = true;
768  else {
769  if (isJoin())
770  mPropagatesDeletes = true;
771  else {
772  #ifdef OOF_DEBUG
774  stringstream() << flush << "propagateRelatedDeletes can't handle pointer relationships for N:1 or N:M "
775  << mTable->tableName() << ':' << fieldName()
776  );
777  #endif
778  }
779  } // may not handle this kind
780  }
781  else
782  mPropagatesDeletes = false; // always accept them turning it off
783 }
784 
785 
786 bool
787 dbRelRefBase::receiveMsg(OOFmsgT msg, unsigned long senderDefined)
788 {
789 // we are on the RHS of a relationship listening to broadcasts from the immediate LHS table
790  switch (msg) {
791  case (OOFmsg_ChangeSelection) :
792  case (OOFmsg_ChangeContext) :
793  case (OOFmsg_UnloadRecord) :
794  #ifdef FIX_FOR_GABY_MULTIPLE_LEVEL_AUTO_SAVE
795  mTable->ContextChange();
796  mTable->setTableValid(false);
797  #else
798  mTable->setTableValid(false); // don't just unload the record - there are NO related recs!
799  mTable->unloadRecord();
801  #endif
802  break;
803 
804 
805  case (OOFmsg_DeleteRecord) :
806  if (mInverseField->propagatesDeletes()) // it propagates to us
808  break;
809 
810  case (OOFmsg_SaveRecord) :
811  // senderDefined is isNewRecord()
812  if (mTable->isTableValid() && mTable->isRecordLoaded() && mTable->isDirty()) {
813  mTable->saveRecord(); // triggers probably save of multiple cached related records
814  if (!mJoinField) {
815  if (mInverseField->isRef()) // rhs of something:1, almost certainly 1:1
816  // tell our inverse to point straight at us, now we have an oid
818  else // lhs of N:something
819  assert (isRef()); // lhs of N:1
820  // other side's OID is itself, used to search us so it can be left set at 0
821  // Can't end up here with N:N as we handle them with a separate pair
822  // of relationships, through intermediate table, which has it's own
823  // traversal path that catches this save message
824  }
825  }
826  break;
827 
828  case (OOFmsg_BroadcasterClosing) :
829  mListensTo = 0; // won't be around to tell it that we're being deleted
830  delete mTable; // yes, delete the table! it's a clone on the heap, managing this relationship
831  break;
832 
833  case (OOFmsg_SuspendSorting) :
834  mTable->suspendSorting(); // our LHS has just told us
835  break;
836 
837  case (OOFmsg_ResumeSorting) :
838  mTable->resumeSorting(); // our LHS has just told us
839  break;
840 
841  default :
842  oofSingleListener::receiveMsg(msg, senderDefined);
843  };
844  return true;
845 }
846 
847 
854 void
856 {
857  assert(mActualRelatedTable); // we are on RHS of relationship
858  if (mJoinField) {
859  if (!mMNlink) // if this is the direct rel to the lhs we don't copy the ID, only the rel to the MN link file in the middle copies
861  }
862  else { // grab an OID
863  if (isRef()) // rhs of something:1 rel
864  OID(mInverseField->fieldTable()->currentOID()); // we point straight at our inverse
865  else // rhs of something:N rel
866  if (mInverseField->isRef()) // rhs of 1:N rel
867  OID(mTable->currentOID()); // store our own table's OID to search inverse
868  else // N:N - need independent OID
870  oofE_General("N:N pointer relationships not yet supported") // later get a relationship OID
871  );
872  }
873 }
874 
875 
876 void
878 {
879  assert (!mJoinField); // NOT a join
880  assert (isRef()); // rhs of something:1 rel
881  OID(inOID);
882 }
883 
884 
898 void
900 {
901  if (!mJoinField) {
902  if (mInverseField->isRef()) // we are rhs of 1:1 or N:1 rel
903  mInverseField->OID(0); // stop lhs pointing to us
904  else
905  if (!isRef())// M:N - need independent OID
907  oofE_General("M:N relationships not yet supported")
908  ); // later get a relationship OID
909  }
910 }
911 
912 
919 void
921 {
922  assert(&newRhs != this);
923  if (!mJoinField) {
924  if (!mActualRelatedTable)
926  if (mInverseField->isRef()) { // rhs of 1:1 or N:1 rel
927  // newRhs.OID( fieldTable()->currentOID() ); // rhs points to US
928  // OID( newRhs.fieldTable()->currentOID() ); // we point to RHS
929  assert(!mTable->isNewRecord()); // only work on a saved record
930  // begin transaction here, end it when caller calls saveRecord()
931  if (OID()) {
932  relateRecord(false);
933  mInverseField->OID(0); // related no longer points to us
934  mInverseField->fieldTable()->saveRecord(); // is this OK?
935  }
936  OID(newRhs.OID()); // we point to new related
937  if (newRhs.OID()) {
938  newRhs.OID(0); // old parent no longer points there
939  newRhs.mTable->saveRecord();
940  }
941  if (relateRecord(false))
942  mInverseField->OID(mTable->currentOID()); // new related points to us
943  }
944  else
945  if (!isRef())// M:N - need independent OID
947  oofE_General("M:N relationships not yet supported") // later get a relationship OID
948  );
949  }
950 }
951 
952 
953 
954 void
955 dbRelRefBase::describe(ostream& os) const
956 {
957  if (CompletelySpecified()) {
958  os << "field: " << fieldName() << " is a ";
959  dbRelRefBase* inverseProto = (dbRelRefBase* )mRelatedTableProto->field(mInverseFieldNumber); // safe downcast
960  // always exists regardless of relationship status
961 
962  if (inverseProto->isRef())
963  os << "1";
964  else {
965  if (isRef())
966  os << "N";
967  else
968  os << "M"; // is M:N by convention, not N:N
969  }
970  if (isRef())
971  os << ":1";
972  else
973  os << ":N";
974 
975  os << " relationship ";
976 
977  if (mJoinField)
978  os << "joining across field: " << mJoinField->fieldName();
979 
980  if (propagatesDeletes())
981  os << " and propagating deletes to related records";
982  }
983  else {
984  os << "incompletely specified relationship";
985  }
986  os << endl;
987 }
988 
989 
1001 bool
1003 {
1004  return true;
1005 }
1006 
1007 
1008 bool
1010 {
1011  if (dbField::fieldIsSameTypeAs(rhs)) {
1012  const dbRelRefBase* rhsField = (const dbRelRefBase*) rhs; // safe downcast
1013  return ( mRelatedTableProto && rhsField->mRelatedTableProto
1016  );
1017  }
1018  else
1019  return false;
1020 }
1021 
1022 
1023 void
1024 dbRelRefBase::extract(ostream& os) const
1025 {
1026  if (mJoinField) {
1027  mJoinField->extract(os);
1028  }
1029  else {
1030  if (isRef())
1031  os << mRelatedTableProto->tableName() << "->[" << OID() << ']'; // if we are the destination of 1:something link then will contain parent OID
1032  // if we are the 1 side then output parent OID
1033  else
1034  os << '[' << mTable->currentOID() << "]->" << mRelatedTableProto->tableName();
1035  // thus dumps of 1-1 or 1-N links should point at each other!
1036  }
1037 }
1038 
1039 
1049 const OOF_String&
1051 {
1052 // override as the table name could change at a later date
1053 // than our initialisation - it's very messy getting the ultimate name
1054 // and this is NOT a function that needs to be particularly efficient
1055  const oofString& tempName = dbField::fieldName();
1056  if (CompletelySpecified() && tempName.isEmpty()) {
1057  return mRelatedTableProto->tableName();
1058  }
1059  else
1060  return tempName; // returns empty name if not completely specified
1061  // so that we have an empty oofString to safely return via reference
1062 }
1063 
1064 
1065 tableNumT
1067 {
1068  assert(mRelatedTableProto);
1069  return mRelatedTableProto->tableNumber();
1070 }
1071 
1072 
1073 dbTable*
1075 {
1076  if (!mActualRelatedTable)
1078  return mActualRelatedTable;
1079 }
1080 
1081 
1082 // -------------------------------------------------------
1083 // d b R e l R e f
1084 // -------------------------------------------------------
1085 
1090 dbRelRef::dbRelRef(const char* fieldName) :
1091  dbRelRefBase(fieldName, 1 /* max related record */),
1092  mCachedSearchTable(0)
1093 {}
1094 
1095 
1097 {
1098  delete mCachedSearchTable;
1099 }
1100 
1101 
1103  dbRelRefBase(rhs),
1104  mCachedSearchTable(0)
1105 {
1106 }
1107 
1108 
1109 const dbRelRef&
1111 {
1112  delete mCachedSearchTable;
1113  mCachedSearchTable = 0;
1115  return *this;
1116 }
1117 
1118 
1135 {
1136  assert(!mJoinField); // only pointer relationships right now
1138 }
1139 
1140 
1146 bool
1148 {
1149  OID(inOID);
1150  if (mActualRelatedTable)
1151  mActualRelatedTable->setTableValid(false); // so next time we want something related will get correct
1152  return inOID!=0;
1153 }
1154 
1155 
1160 bool
1162 {
1163  dbTable* inverseTable = relatedTable(); // force instantiation if never before traversed relationship
1164  assert(rhsTable && inverseTable->tableNumber()==rhsTable->tableNumber());
1165  if (rhsTable->isEmpty())
1166  return false;
1167 
1168  bool settoRHS = false;
1169  if (isJoin()) {
1170  dbField* inverseJoinKey;
1171  if (inverseTable==rhsTable)
1172  inverseJoinKey = mInverseField->joinField();
1173  else
1174  inverseJoinKey = mInverseField->equivalentFieldFromTable(rhsTable);
1175  mJoinField->copyValueIfDifferent(inverseJoinKey); // simply update key fields
1176  }
1177  else {
1178  const oidT rhsOID = rhsTable->currentOID();
1179  OID(rhsOID);
1180  }
1181  inverseTable->setTableValid(false); // so next time we want something related will re-load record
1182  return true; // there must have been something on rhs
1183 }
1184 
1185 
1193 bool
1195 {
1196  bool ret = false;
1197  if (!mCachedSearchTable)
1199 
1200  dbQueryClause* convertedSearch = 0;
1201  try {
1202  const dbQueryClause* searchBy = 0;
1203  convertedSearch = inSearch.cloneReplacingTable(relatedTable(), mCachedSearchTable);
1204  if (convertedSearch)
1205  searchBy = convertedSearch;
1206  else
1207  searchBy = &inSearch; // use original if didn't change
1208  if (mCachedSearchTable->search(searchBy)) {
1209  mCachedSearchTable->start(); // load record to get the OID!
1210  if (mJoinField) {
1211  dbField* cachedJoinField = mCachedSearchTable->field( mInverseField->joinField()->fieldNumber() );
1212  mJoinField->copyValueIfDifferent(cachedJoinField);
1213  }
1214  else
1216  ret = true;
1217  }
1218  else
1219  if (mJoinField)
1220  mJoinField->clear();
1221  else
1222  OID(0); // if not found, clear current OID!
1223  }
1224  catch (...) {
1225  delete convertedSearch;
1226  throw;
1227  }
1228  delete convertedSearch;
1229  return ret;
1230 }
1231 
1232 
1233 // -------------------------------------------------------
1234 // d b R e l a t i o n s h i p
1235 // -------------------------------------------------------
1237  mLHS(lhs),
1238  mRHS(rhs)
1239 {
1240  lhs.relatesTo(rhs);
1241 }
1242 
1243 
1245  mLHS(*lhs),
1246  mRHS(*rhs)
1247 {
1248  lhs->relatesTo(*rhs);
1249 }
1250 
1251 
1253  mLHS(lhs),
1254  mRHS(rhs)
1255 {
1256  lhs.relatesTo(rhs);
1257  lhs.joinField(lhsJoin);
1258  rhs.joinField(rhsJoin);
1259 }
1260 
1261 
1263  mLHS(*lhs),
1264  mRHS(*rhs)
1265 {
1266  lhs->relatesTo(*rhs);
1267  lhs->joinField(*lhsJoin);
1268  rhs->joinField(*rhsJoin);
1269 }
1270 
1271 
1279 void
1281 {
1282 #ifdef OOF_DEBUG
1283  if (lhsLink.fieldTable() != rhsLink.fieldTable()) {
1284  dbConnect::raise(stringstream() << flush << "dbRelationship::linkMNvia\n"
1285  << "incorrectly specified relationship, should be same table on both sides, NOT\n"
1286  << lhsLink.fieldTable()->tableName()
1287  << " to "
1288  << rhsLink.fieldTable()->tableName()
1289  );
1290  }
1291 #else
1292  assert(lhsLink.fieldTable() == rhsLink.fieldTable());
1293 #endif
1294  OOF_RelMN* link = new OOF_RelMN(lhsLink, rhsLink); // ref counted, deleted by the following
1295  mLHS.setMNlink(link);
1296  mRHS.setMNlink(link);
1297 
1298 // SEE dbRelRefBase::SetCommonRelationshipFields for test that ensures both
1299 // sides of relationship are joins or not
1300 
1301 /* 96/08/14 not sure this is necessary. If the link file is via join, there is no
1302  need for the direct link to be specified as a join
1303 
1304 // ENSURE both the rel to the rhs and the rel to the link file are joins/not
1305 #ifdef OOF_DEBUG
1306  if ((mLHS.joinField()==0) != (lhsLink.joinField()==0))
1307  dbConnect::raise(stringstream() << flush << "dbRelationship::linkMNvia\n"
1308  << "M:N Relationship specified with join field on only one of the link and the actual relationship:\n"
1309  << mLHS.fieldName()
1310  );
1311 
1312  if ((mRHS.joinField()==0) != (rhsLink.joinField()==0))
1313  dbConnect::raise(stringstream() << flush << "dbRelationship::linkMNvia\n"
1314  << "M:N Relationship specified with join field on only one of the link and the actual relationship:\n"
1315  << mRHS.fieldName()
1316  );
1317 #else
1318  assert((mLHS.joinField()==0) == (lhsLink.joinField()==0));
1319  assert((mRHS.joinField()==0) == (rhsLink.joinField()==0));
1320 #endif
1321 */
1322 }
1323 
1324 
1325 void
1327 {
1328  linkMNvia(*lhsLink, *rhsLink);
1329 }
1330 
1331 
1332 // -------------------------------------------------------
1333 // d b R e l M a i n t a i n e r
1334 // -------------------------------------------------------
1336  oofSingleListener(relToRHS->relatedTable()),
1337  mLink(linkWeAlert)
1338 {
1339 }
1340 
1341 
1343 {
1344 }
1345 
1346 
1347 bool
1348 dbRelMaintainer::receiveMsg(OOFmsgT msg, unsigned long senderDefined)
1349 {
1350  switch (msg) {
1351  case (OOFmsg_AppendSelection) :
1352  mLink->appendSelection((dbSelection*) senderDefined);
1353  break;
1354 
1355  case (OOFmsg_RemoveSelection) :
1356  mLink->removeSelection((dbSelection*) senderDefined);
1357  break;
1358 
1359  case (OOFmsg_ClearSelection) :
1360  mLink->clearSelection();
1361  break;
1362 
1363  default :
1364  oofSingleListener::receiveMsg(msg, senderDefined);
1365 
1366  };
1367  return true;
1368 }
1369 
virtual void describe(std::ostream &) const
Provide human-readable information about object.
Definition: oofrel.cpp:955
virtual bool fieldIsSameTypeAs(const dbField *) const
Definition: oofrel.cpp:1009
virtual dbField * clone() const
Definition: oofrel.cpp:327
Definition: oof1.h:236
void incRefs()
Definition: oofrel.cpp:71
dbRelMaintainer(dbRelRefBase *relToRHS, OOF_RelMN *linkWeAlert)
Definition: oofrel.cpp:1335
void completeCloneOfInverse(dbRelRefBase *cloner)
Definition: oofrel.cpp:451
static void raise(std::ostream &, bool terminateAfterMsg=true)
const char * tableName() const
Definition: oof3.cpp:202
unsigned long count()
Count records in current selection.
Definition: oof1.h:2017
void union_with(const dbSelection &)
Definition: oof2.cpp:698
fieldNumT fieldNumber() const
Definition: oof3.h:754
virtual const oofString & fieldName() const
Definition: oof3.h:769
void intersection_with(const dbSelection &)
Definition: oof2.cpp:635
precompilation header.
tableNumT tableNumber() const
Definition: oof1.h:2376
dbField * equivalentFieldFromTable(dbTable *) const
Definition: oof3.cpp:352
Tries to hide the different platforms and version issues with standard IO.
void clearSelection()
Definition: oofrel.cpp:265
bool mPropagatesDeletes
Definition: oofrel.h:103
dbField * mJoinField
Definition: oofrel.h:102
void resumeSorting()
Definition: oof1.cpp:2508
dbRelRefBase * mInverseField
Definition: oofrel.h:104
bool isRef() const
Is "ref" to just one instance.
Definition: oofrel.h:261
virtual bool selectJustOID(oidT)=0
Highest level used to assemble queries.
Definition: oofquery.h:46
void appendSelection(dbSelection *)
Definition: oofrel.cpp:179
const OOFmsgT OOFmsg_ChangeSelection
Definition: oofmsg.h:33
void relatesTo(dbRelRefBase &)
Called by dbRelationship to tie two instances of dbRelRefBase together.
Definition: oofrel.cpp:388
unsigned long count() const
Definition: oof2.cpp:770
bool isCopyCompatibleWith(const dbTable *) const
Definition: oof1.cpp:1994
dbRelRefBase * inverse() const
Definition: oofrel.h:242
Envelope class to contain an abstract selection apart from its dbTable.
Definition: oof1.h:316
dbTable * mCachedSearchTable
Definition: oofrel.h:127
virtual unsigned long fieldStorageLen() const
Definition: oofrel.cpp:706
dbTable * relatedTable()
Definition: oofrel.cpp:1074
void start()
Definition: oof1.cpp:2057
OOF_tableBackend * mBackend
Definition: oof3.h:155
const OOFmsgT OOFmsg_SuspendSorting
Definition: oofmsg.h:50
Special limited oofReceiver which will only ever listen to one oofBroadcaster at a time...
Definition: oofmsg.h:133
virtual bool receiveMsg(OOFmsgT msg, unsigned long senderDefined)
The default receiveMsg behaviour is to delete yourself when the broadcaster closes.
Definition: oofmsg.cpp:275
void index(const OOF_IndexOptions=kIndexed)
Definition: oof3.cpp:212
virtual void newRecord()
Definition: oof1.cpp:2090
RHS long argument to queries on fields like dbLong.
Definition: oofquery.h:480
virtual const OOF_String & fieldName() const
Override to return related table name as sensible default.
Definition: oofrel.cpp:1050
OOF_tableBackend * mBackend
Definition: oof1.h:788
dbQueryBinary nullRelationship() const
Search expression to find records which have a relationship pointing nowhere.
Definition: oofrel.cpp:1134
bool setRelatedRecord(oidT)
Set our link to a single record to the specified OID.
Definition: oofrel.cpp:1147
virtual dbQueryClause * cloneReplacingTable(const dbTable *inTable, const dbTable *repTable) const
Definition: oofquery.cpp:48
bool propagatesDeletes() const
Definition: oofrel.h:235
const OOFmsgT OOFmsg_BroadcasterClosing
Definition: oofmsg.h:40
bool isJoin() const
If we don't have mJoinField then we are a pointer relationship.
Definition: oofrel.h:304
bool selectAllRelated(bool broadcastChange=true)
Definition: oofrel.cpp:520
bool search(const dbQueryClause &query)
Definition: oof1.cpp:2348
const dbRelRef & operator=(const dbRelRef &)
Definition: oofrel.cpp:1110
void updateRelValue()
Propagate values to maintain a relationship.
Definition: oofrel.cpp:855
void SetCommonRelationshipFields(dbRelRefBase &)
Definition: oofrel.cpp:347
void decRefs()
Definition: oofrel.cpp:78
virtual bool receiveMsg(OOFmsgT msg, unsigned long senderDefined)
The default receiveMsg behaviour is to delete yourself when the broadcaster closes.
Definition: oofrel.cpp:1348
virtual ~dbRelMaintainer()
Definition: oofrel.cpp:1342
virtual void copyValueIfDifferent(const dbField *)
Definition: oof3.h:141
void linkMNvia(dbRelRefBase &, dbRelRefBase &)
Finish specifying an M:N relationship by pointing out from middle table to both sides.
Definition: oofrel.cpp:1280
const OOF_Selection * internalSelection() const
Definition: oof2.h:317
bool isEmpty()
Definition: oof1.cpp:2107
const OOFmsgT OOFmsg_ClearSelection
Definition: oofmsg.h:47
Common binary query for field, eg: People.Salary > 90000.
Definition: oofquery.h:165
Created by dbRelationship::linkMNvia to maintain an MN relationship.
Definition: oofrel.h:181
void breakRelLink()
Break the link from another table pointing at us.
Definition: oofrel.cpp:899
virtual void unsubscribe(oofBroadcaster *from=0)
Definition: oofmsg.cpp:304
Parent for any field that is a relationship to another table.
Definition: oofrel.h:19
bool isTableValid() const
Definition: oof1.h:1795
bool isDirty() const
Definition: oof1.h:2334
LHS argument to queries on fields.
Definition: oofquery.h:572
unsigned long CountAllRelatedIn(dbTable *)
Definition: oofrel.cpp:651
virtual unsigned long count() const
Definition: oofrecs.h:853
unsigned short tableNumT
Definition: oof1.h:275
#define OOF_DEBUG_LOG(x)
Definition: oof0.h:243
void deleteSelection()
Definition: oof1.cpp:1765
void setMNlink(OOF_RelMN *)
Point this relationship at the object which maintains the link.
Definition: oofrel.cpp:496
void removeSelection(dbSelection *)
Remove the selected records from the intermedate link file, immediately.
Definition: oofrel.cpp:225
const OOFmsgT OOFmsg_ResumeSorting
Definition: oofmsg.h:51
Selection of records in context of a single dbTable instance.
Definition: oofrecs.h:173
virtual void extract(std::ostream &) const
Definition: oofrel.cpp:1024
virtual bool searchEqual(const dbField *, const char *, bool matchEntireKey=true)=0
void joinField(dbField &)
Definition: oofrel.cpp:415
bool isEmpty() const
Test based on either length or null pointer.
Definition: oofstr.h:413
virtual bool fieldIsVirtual() const
Definition: oofrel.cpp:697
virtual bool receiveMsg(OOFmsgT msg, unsigned long senderDefined)
The default receiveMsg behaviour is to delete yourself when the broadcaster closes.
Definition: oofrel.cpp:787
unsigned long oidT
type we pass around pretending we have a real OID.
Definition: oof1.h:229
void selectNone()
Change the current selection to no records.
Definition: oof1.h:2321
bool relateRecord(bool broadcastChange=true)
Definition: oofrel.cpp:507
dbTable * mTable
Definition: oof3.h:156
void operator=(const char *)
Definition: oof3.h:671
const OOFmsgT OOFmsg_ChangeContext
Definition: oofmsg.h:34
virtual bool fieldIsSameTypeAs(const dbField *) const
Definition: oof3.cpp:557
bool cloningRelIs(dbRelRefBase *)
Definition: oofrel.cpp:88
const oofString & tableName() const
Definition: oof1.h:2369
void suspendSorting()
Definition: oof1.cpp:2492
virtual void selectNone()=0
dbSelection currentSelection()
Definition: oof1.cpp:2674
void setSelection(const dbSelection &)
Definition: oof1.cpp:2725
tableNumT relatedTableNumber() const
Definition: oofrel.cpp:1066
const OOFmsgT OOFmsg_UnloadRecord
Definition: oofmsg.h:35
OOF_fieldTypes
Definition: oof3.h:26
oidT OID() const
Definition: oofrel.cpp:716
virtual void writeLong(long, const dbField *)=0
Exception for unclassified use when nothing else is better.
Definition: oofexcep.h:325
virtual long readLong(const dbField *) const =0
virtual OOF_fieldTypes fieldType() const
Definition: oofrel.cpp:680
bool setRelatedRecordMatching(const dbQueryClause &)
Set our link to a single record found by the search expression.
Definition: oofrel.cpp:1194
oofBroadcaster * mListensTo
Definition: oofmsg.h:149
#define RAISE_EXCEPTION(E)
Macro to allow us to either throw an exception or call dbConnect::raise.
Definition: oofexcep.h:31
Save & restore current database selection on the stack.
Definition: oof1.h:392
dbField * field(fieldNumT) const
Definition: oof1.h:2355
virtual bool pointsToCorrectTableType() const
Default base method.
Definition: oofrel.cpp:1002
virtual dbTable * cloneTableWithoutSelection() const
Definition: oof1.cpp:1423
bool isOneToOne() const
Definition: oofrel.h:284
Base class for persistent tables.
Definition: oof1.h:452
virtual void extract(std::ostream &) const
Definition: oof3.cpp:506
dbTable * fieldTable() const
Definition: oof3.cpp:308
const OOFmsgT OOFmsg_DeleteRecord
Definition: oofmsg.h:37
virtual void clear()
Definition: oof3.h:131
void next()
Next record in the selection becomes current.
Definition: oof1.h:1970
Portable highly capable string class.
Definition: oofstr.h:101
OOF_RelMN * mMNlink
Definition: oofrel.h:105
void makeRelMaintainer()
factory conditionally creates a dbRelMaintainer listening to rhs table
Definition: oofrel.cpp:273
bool CompletelySpecified() const
Definition: oofrel.h:228
dbTable * mActualRelatedTable
Definition: oofrel.h:100
void setSaveOption(const saveOptionsT)
Definition: oof1.cpp:2752
virtual ~dbRelRef()
Definition: oofrel.cpp:1096
const OOFmsgT OOFmsg_AppendSelection
Definition: oofmsg.h:45
listener for an MN link that adds and deletes records.
Definition: oof1.h:1456
void changeRelationshipTo(dbRelRefBase &newRhs)
Change an existing relationship to point to a different field.
Definition: oofrel.cpp:920
dbRelationship(dbRelRefBase &, dbRelRefBase &)
Definition: oofrel.cpp:1236
virtual void saveRecord()
Definition: oof1.cpp:1689
virtual const char * defaultName() const
Definition: oof1.h:467
virtual void postCloneTableCleanup()
Definition: oofrel.cpp:334
bool relateFromRecord(bool broadcastChange=true)
Used to load a related selection.
Definition: oofrel.cpp:580
virtual ~dbRelRefBase()
Definition: oofrel.cpp:319
const OOFmsgT OOFmsg_RemoveSelection
Definition: oofmsg.h:46
unsigned long OOFmsgT
Definition: oofmsg.h:31
bool selectJustOID(oidT)
Try to change the current selection to just the matching record.
Definition: oof1.cpp:2705
unsigned long value(unsigned long) const
Definition: oofrecs.h:967
void propagateRelatedDeletes(bool prop=true)
Set relationship to propagate deletes, ie: owns related records.
Definition: oofrel.cpp:747
bool isNewRecord() const
Definition: oof1.h:2341
fieldNumT mInverseFieldNumber
Definition: oofrel.h:101
dbField * joinField() const
Definition: oofrel.h:250
dbTable * mRelatedTableProto
Definition: oofrel.h:99
virtual void unloadCache()=0
unsigned long countAllRelated()
Definition: oofrel.cpp:623
bool isOneToMany() const
Definition: oofrel.h:268
void BuildRelatedTable()
Definition: oofrel.cpp:424
const OOFmsgT OOFmsg_SaveRecord
Definition: oofmsg.h:38
oidT currentOID()
Absolute record address of current record.
Definition: oof1.h:1938
virtual bool loadRelatedContextJoiningFromTo(const dbField *, const dbField *)=0
dbRelRef(const char *fieldName=0)
ctor allowing you to pass field name for relationship.
Definition: oofrel.cpp:1090
void setTableValid(bool isValid=true)
Force current table state to valid.
Definition: oof1.h:1875
void unloadRecord()
Definition: oof1.cpp:2536
void broadcast(OOFmsgT msg, unsigned long senderDefined=0, const oofReceiver *skipping=0)
Broadcast a message to all anonymous subscribers.
Definition: oofmsg.cpp:82
Base class for persistent fields in dbTable's.
Definition: oof3.h:63
bool isRecordLoaded() const
Definition: oof1.h:1823
Relationship field to 1 or 0 instances.
Definition: oofrel.h:113
virtual OOF_fieldTypes nativeType() const
Definition: oofrel.cpp:687
void setOID(oidT)
Definition: oofrel.cpp:877
dbRelRefBase(const char *fieldName=0, unsigned long maxLinks=0)
Definition: oofrel.cpp:285
virtual void subscribeTo(oofBroadcaster *)
Definition: oofmsg.cpp:293