The information about data integrity considerations is organized in the following manner:
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:
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 eight bytes. It automatically mutates to an out-of-line representation 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, as well as 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 exactly the same as those of the corresponding member macro, but without the last argument, as illustrated in the examples that follow.
See Chapter 4, System-Supplied Macros, of the ObjectStore C++ API 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 which 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() { /* 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()); }
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() { /* 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()); }
See Chapter 4, System-Supplied Macros, of the ObjectStore C++ API Reference for descriptions of the os_rel_m_m_body(), os_rel_m_1_body(), and os_relationship_m_m() macros.
Example:
Here 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() { /* 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"); } }
See Chapter 4, System-Supplied Macros, of the ObjectStore C++ API 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() { /* 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 */ }
#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 how many 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, since 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, since 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, since 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 will return an os_Collection<node*>& rather than just an os_collection&.
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. Here 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 do not need to be indexable to be used in a path. For an indexable many-valued relationship, specify os_auto_index.
By default, ObjectStore sometimes checks for illegal pointers, but other times the checking is optimized out. However, you can instruct ObjectStore always to check for illegal pointers in a given segment or database on transaction commit. Keep in mind that this can be expensive and should really be done for development and stress testing, but not in production. You can also direct ObjectStore never to check. See Controlling Illegal Pointer Checking.
You can also control the action taken when ObjectStore detects an illegal pointer, as described in Controlling the Consequences of Illegal Pointer Detection.
Note that these functions concern checking for pointers to transient memory and cross-database pointers. There are other kinds of database pointers that can cause database integrity problems. Prominent among these is the dangling reference, the pointer to a deleted object, as well as the incorrectly typed pointer. The integrity functions described in the following sections do not involve checking for such pointers. However the inverse member and schema evolution facilities do provide integrity control support in these areas.
Controlling Illegal Pointer Checking
You control whether ObjectStore always detects illegal pointers with the following member of os_segment:
void set_check_illegal_pointers(os_boolean) ;
The results of using this function do not remain in effect after the current process ends, and are invisible to other processes. See Illegal Pointer Modes Are Process Local.
Finding illegal pointers in a given segment
You can use the following member of os_segment to determine the current mode of a given segment:
os_boolean get_check_illegal_pointers();If the segment is in check_illegal_pointers mode, the function returns 1; otherwise, it returns 0.
void set_default_check_illegal_pointers(os_boolean) ;
os_boolean get_default_check_illegal_pointers() ;
void set_check_illegal_pointers(os_boolean) ;Passing 0 disables default_check_illegal_pointers mode for the database and disables check_illegal_pointers mode for each segment in the database.
static void set_check_illegal_pointers(os_boolean) ;This also enables check_illegal_pointers mode for each database currently retrieved by the current process.
static os_boolean get_check_illegal_pointers() ;
static void always_ignore_illegal_pointers(os_boolean) ;Supplying a nonzero value specifies that illegal pointers should always be ignored by ObjectStore during the current process, provided the process is not in always_null_illegal_pointers mode. This includes illegal pointers detected during database reads as well as database writes.
void set_null_illegal_pointers(os_boolean) ;
The results of using this function do not remain in effect after the current process ends, and they are invisible to other processes. See Illegal Pointer Modes Are Process Local.
Find the current signal mode of a given segment
You can use the following member of os_segment to determine the current mode of a given segment:
os_boolean get_null_illegal_pointers() ;If the segment is in null_illegal_pointers mode, the function returns nonzero; otherwise, it returns 0.
void set_default_null_illegal_pointers(os_boolean) ;
os_boolean get_default_null_illegal_pointers() ;
void set_null_illegal_pointers(os_boolean) ;Passing 0 disables default_null_illegal_pointers mode for the database and disables null_illegal_pointers mode for each segment in the database.
static void set_null_illegal_pointers(os_boolean) ;This also enables null_illegal_pointers mode for each database currently retrieved by the current process.
static os_boolean get_null_illegal_pointers() ;
static void set_always_ignore_illegal_pointers(os_boolean) ;Supplying a nonzero value specifies that illegal pointers should always be ignored by ObjectStore during the current process, provided the process is not in always_null_illegal_pointers mode. This includes illegal pointers detected during database reads as well as database writes.
static void set_always_null_illegal_pointers(os_boolean) ;Supplying a nonzero value specifies that illegal pointers should always be set to 0 when detected by ObjectStore during the current process. This includes illegal pointers detected during database reads as well as database writes.
For example, if one process enables null_illegal_pointers mode for a given segment, another concurrent process can disable null_illegal_pointers mode for that same segment. The first process will change illegal pointers to 0, and the second process will signal an error when it finds an illegal pointer. Moreover, the modes are transient; they remain in effect only until the process terminates. So these modes actually determine the nature of illegal pointer checking and handling for the current process.
Updated: 03/31/98 17:02:43