ObjectStore provides facilities to help deal with two of the most common integrity maintenance problems.
Illegal Pointers
Another integrity control problem concerns illegal pointers. ObjectStore can detect two kinds of illegal pointers:
After the object referred to by an os_Reference_protected is deleted, resolution of the os_Reference_protected causes an err_reference_not_found exception to be signaled. If the referent database has been deleted, err_database_not_found is signaled.
Inverse Data Members
ObjectStore allows you to model binary relationships with pointer-valued (or collection-of-pointer-valued) data members that maintain the referential integrity of their inverse data members. You implement this inverse maintenance by defining an embedded relationship class, which encapsulates the pointer (or collection-of-pointers) so that it can intercept updates to the encapsulated value and perform the necessary inverse maintenance tasks.
ostore/ostore.hh, ostore/coll.hh, ostore/relat.hh
otherpart = somepart->container; /* simple data member */
otherpart = somepart->container.getvalue(); /* relationship *
otherpart = somepart->get_container(); /*functional interface */
somepart->container = otherpart; /* simple data member */
somepart->container.setvalue(otherpart); /* relationship */
somepart->set_container(otherpart); /* functional interface */
Note that it is completely up to the class definer to decide which of these interfaces to export to the class's end users. The underlying ObjectStore library interface to relationships supports all of them and, in fact, a class definer could choose to export more than one (for example, so that the end user could do either
p->set_container(q)or
p->container.setvalue(q))Similarly, for the many-valued relationship contents, which lists a part's subparts, any of the following interfaces could be presented to the end user:
os_collection* subparts;
subparts = somepart->contents; /* simple data member */
subparts = somepart->contents.getvalue(); /* relationship */
subparts = somepart->get_contents(); /* functional interface */
somepart->contents.insert(otherpart); /* simple data member */
somepart->contents.getvalue().insert(otherpart); /* relationship */
somepart->insert_contents(otherpart); /* functional interface */
Again, deciding which of these interfaces to export to the end user is under the control of the class definer. The ObjectStore library interface to relationships supports all three.
The collection for an m side of an os_relationship data member is created upon the first insertion into the collection.
You control the size and placement of the collection by calling os_relationship::create_coll() in the constructor of the class that contains the os_relationship m data member.
Presizing the collection yields the best performance in terms of eliminating mutations as the collection grows and in terms of clustering.
The relationship macros wrap a class around the data member; this adds no additional storage to the data member. The wrapper simply implements the functions to perform the inverse operations. The m side of a relationship is an embedded collection that is 8 bytes. It mutates to an out-of-line representation automatically upon the insertion of the first element.
Note that these macros always come in fours. Each use of a member macro to define one side of a relationship must be paired with another member macro to define the other side of the relationship, and each member macro must have a corresponding body macro to provide the implementations for the relationship's accessor functions. This means that a one-to-many relationship member must also have a one-to-many relationship body and a many-to-one inverse member, which itself must have a many-to-one relationship body.
Macro Arguments
The member macros always have five arguments:
os_relationship_1_m (person,employer,company,employees,
company*) employer;
defines a company* employer data member, which is part of a relationship.The function body macros have just four arguments. For each function body macro, the arguments are the same as those of the corresponding member macro, but without the last argument, as shown in the examples that follow.
See Chapter 9, Macros and User-Defined Functions Reference, of the C++ Collections Guide and Reference for descriptions of the os_relationship_1_1() and os_rel_1_1_body() macros.
Example:
os_relationship_1_1 and os_rel_1_1_body macros
/* C++ Note Program - Header File */
#include <fstream.h>
#include <string.h>
#include <ostore/ostore.hh>
#include <ostore/coll.hh>
#include <ostore/relat.hh>
class author;
/* A simple class that records a note entered by the user. */
class note {
public:
/* Public Member functions */
note(const char*, int);
~note();
void display(ostream& = cout);
static os_typespec* get_os_typespec();
/* Public Data members */
os_backptr bkptr;
char* user_text;
os_indexable_member(note,priority,int) priority;
os_relationship_1_m(
note,the_author,author,notes,author*)
the_author;
};
#include <ostore/relat.hh>
class node {
public:
os_relationship_1_1(node,next,node,previous,node*) next;
os_relationship_1_1(node,previous,node,next,
node*) previous;
node() {};
};
os_rel_1_1_body(node,next,node,previous);
os_rel_1_1_body(node,previous,node,next);
main() {
OS_ESTABLISH_FAULT_HANDLER
/* show the end users use of these relationships */
objectstore::initialize();
os_collection::initialize();
node* n1 = new node();
node* n2 = new node();
n1->next = n2;
/* this also automatically updates n2->previous */
printf("n1 (%x) --> (%x)\n",
n1, n1->next.getvalue());
printf("n2 (%x) --> (%x)\n",
n2, n2->previous.getvalue());
OS_END_FAULT_HANDLER
}
n1->next = n2->next;actually is interpreted by the C++ compiler as
n1->next.operator=( n2->next.operator node* () );
printf("The value of the relationship is %x \n", n1->next );
because printf() does not have prototype information for its arguments, so the compiler does not know to apply a coercion. In this case, either of the following would be a suitable alternative:
printf("The value of the relationship is %x \n",
n1->next.getvalue() );
printf("The value of the relationship is %x \n",
(node*)n1->next );
#include <ostore/ostore.hh>
#include <ostore/coll.hh>
#include <ostore/relat.hh>
class node {
private:
os_relationship_1_1(node,next,node,previous,
node*) next;
os_relationship_1_1(node,previous,node,next,
node*) previous;
public:
node* get_next() {return next.getvalue();};
void set_next(node* val) {next.setvalue(val);};
node* get_previous() {
return previous.getvalue();};
void set_previous(node* val) {
previous.setvalue(val);};
node() {};
};
os_rel_1_1_body(node,next,node,previous);
os_rel_1_1_body(node,previous,node,next);
main() {
OS_ESTABLISH_FAULT_HANDLER
/* show the end users use of these relationships */
objectstore::initialize();
os_collection::initialize();
node* n1 = new node();
node* n2 = new node();
n1->set_next(n2);
/* this automatically also updates n2->prev */
printf("n1 (%x) --> (%x)\n",n1, n1->get_next());
printf("n2 (%x) --> (%x)\n",n2, n2->get_prev());
OS_END_FAULT_HANDLER
}
See Chapter 9, Macros and User-Defined Functions Reference, of the C++ Collections Guide and Reference for descriptions of the os_rel_m_m_body(), os_rel_m_1_body(), and os_relationship_m_m() macros.
Example:
Following is an example in which a class node is defined with a pair of many-to-many relationships, ancestors and descendents (as in a node in a graph structure):
os_relationship_m_m and
os_rel_m_m_body macros
#include <ostore/ostore.hh>
#include <ostore/coll.hh>
#include <ostore/relat.hh>
class node {
public:
os_relationship_m_m(node,ancestors,node,descendents,
os_collection) ancestors;
os_relationship_m_m(node,descendents,node,ancestors,
os_collection) descendents;
node() {};
};
os_rel_m_m_body(node,ancestors,node,descendents);
os_rel_m_m_body(node,descendents,node,ancestors);
main() {
OS_ESTABLISH_FAULT_HANDLER
/* show the end users use of these relationships */
objectstore::initialize(); os_collection::initialize();
node* n1 = new node(); node* n2 = new node();
n1->ancestors.insert(n2);
/* this also updates n2->descendents */
node* n;
printf("n1 (%x)\n",n1);
printf(" has %d descendents: ", n1->descendents->size ()); {
os_cursor c(n1->descendents);
for (n = (node*) c.first(); n; n = (node*) c.next())
printf("(%x) ",n);
printf("\n");
}
printf(" and %d ancestors: ", n1->ancestors->size ()) {
os_cursor c(n1->ancestors);
for (n = (node*) c.first(); n; n = (node*) c.next())
printf("(%x) ", n);
printf("\n");
}
printf("n2 (%x)\n",n2);
printf(" has %d descendents: ",
n2->descendents->size ()); {
os_cursor c(n2->descendents);
for (n = (node*) c.first(); n; n = (node*) c.next())
printf("(%x) ", n);
printf("\n");
}
printf(" and %d ancestors: ",
n2->ancestors->size ()); {
os_cursor c(n2->ancestors);
for (n = (node*) c.first(); n; n = (node*) c.next())
printf("(%x) ", n);
printf("\n");
}
OS_END_FAULT_HANDLER
}
See Chapter 9, Macros and User-Defined Functions Reference, of the C++ Collections Guide and Reference for descriptions of the os_relationship_1_m(), os_relationship_m_1(), os_rel_1_m_body(), and os_rel_m_1_body() macros.
Example:
os_relationship_1_m, os_relationship_m_1, os_rel_1_m_body, and
os_rel_m_1_body macros
#include <ostore/ostore.hh>
#include <ostore/coll.hh>
#include <ostore/relat.hh>
class node {
public:
os_relationship_1_m(node,parent,node,children,
node*) parent;
os_relationship_m_1(node,children,node,parent,
os_collection) children;
node() {};
};
os_rel_1_m_body(node,parent,node,children);
os_rel_m_1_body(node,children,node,parent);
main() {
OS_ESTABLISH_FAULT_HANDLER
/* show the end users use of these relationships */
objectstore::initialize();
os_collection::initialize();
node* n1 = new node();
node* n2 = new node();
n1->children.insert(n2);
/* this also updates n2->parent */
/* NOTE: "n2->parent = n1;" would have had */
/* identical effect */
/* etc */
OS_ESTABLISH_FAULT_HANDLER
}
#include <ostore/relat.hh>
class person {
public:
os_relationship_1_m(person,employer,company,
employees, company*) employer;
char* name;
};
class company {
public:
os_relationship_m_1(company,employees,person,
employer, os_collection) employees;
int gross_revenue;
};
os_rel_1_m_body(person,employer,company,employees);
os_rel_m_1_body(company,employees,person,employer);
Suppose a complex part keeps track of the primitive parts it uses, as well as the number of times each primitive part is used. (For example, a wheel might be a primitive part, and be used four times in a complex part like a car.) Suppose also that each primitive part is used in only one complex part. This can be modeled with the following classes:
class complex_part {
os_relationship_m_1(
complex_part,
components,
primitive_part,
used_by,
os_Bag<primitive_part*> ) components ;
}
class primitive_part {
os_relationship_1_m(
primitive_part,
used_by,
complex_part,
components,
complex_part* ) used_by ;
}
Suppose that a certain primitive_part, a_wheel, is used by a particular complex_part, the_car. If you do
a_wheel->used_by = 0;ObjectStore removes all occurrences of a_wheel from the_car's components, because setting used_by to 0 implies that the wheel is not used by the car at all.
Suppose you do
the_car->components.remove(a_wheel)If the car uses four wheels at first, afterward it uses three wheels. a_wheel->used_by still points to the car, because the car still uses the wheel at least once.
Now suppose each primitive part can be used by multiple complex parts.
class complex_part {
os_relationship_m_1(
complex_part,
components,
primitive_part,
used_by,
os_Bag<primitive_part*>
) components ;
}
class primitive_part {
os_relationship_1_m(
primitive_part,
used_by,
complex_part,
components,
os_Set<complex_part*>
) used_by ;
}
And suppose you do
a_wheel->used_by.remove(the_car);This causes all occurrences of a_wheel to be removed from the_car's components, because it implies that the wheel is not used by the car at all.
If you do
the_car->components.remove(a_wheel);ObjectStore removes the_car from the wheel's used_by set only if it removes the last occurrence of the wheel from the car's components, that is, only if the car no longer uses the wheel at all.
class node {
public:
os_relationship_m_m(node,ancestors,node,descendents,
os_Collection<node*>) ancestors;
os_relationship_m_m(
node,descendents,node,ancestors,
os_Collection<node*>) descendents;
node() {};
};
os_rel_m_m_body(node,ancestors,node,descendents);
os_rel_m_m_body(node,descendents,node,ancestors);
In this case, the functions that perform a get value (that is, getvalue()) and the coercion operator return an os_Collection<node*>& rather than an os_collection& only.
These macros are like the body macros already discussed, except that they have three extra arguments, used for specifying various options. The fifth argument (the first extra argument) can be either os_rel_propagate_delete or os_rel_dont_propagate_delete, as in
Example
os_rel_m_1_body_options(part,subparts,part,container,
os_rel_propagate_delete, os_auto_index, os_no_index)
The last two arguments are used to indicate whether the current member and its inverse are indexable. These are described in the next section.
These macros are like the body macros discussed earlier, except that they have three extra arguments, used for specifying various options.
Form of the call
os_index( class, member)where class is the name of the class defining the indexable member and member is the name of the os_backptr-valued data member appearing before indexable members of the class. Following is an example:
os_rel_m_1_body_options(part,subparts,part,container,
os_propagate_delete,
os_auto_index, os_index(part,b))
Many-valued members that have an inverse need not be indexable to be used in a path. For an indexable many-valued relationship, specify os_auto_index.
Updated: 03/10/99 12:27:04