The schema evolution process has two phases:
In most cases, new fields are initialized with zeros. There is one useful exception to this, however. In the case where a field has changed type, and the old and new types are assignment compatible, the new field is initialized by assignment from the old field value. See os_schema_evolution::evolve() for the initialization rules.
During the initialization phase, the address of an instance being migrated generally changes. The reason for this is that migration actually consists of making a copy of the old, unmigrated instance, and then modifying this copy. The copy and the old instance will be in the same segment, but their offsets within the segment will be different.
In such a case, you can provide a special handler function (see os_schema_evolution::set_illegal_pointer_handler()) to process the illegal pointer (for example, by changing it to null or simply reporting its presence). Each time an illegal pointer is detected, the handler function is executed on the pointer, and then schema evolution is resumed. If you do not provide a handler function, an exception is signaled when an illegal pointer is encountered.
C++ references are treated as a kind of pointer. References to migrated instances are adjusted just as described above. And illegal references are detected and can be handled as described.
For some schema changes, the instance initialization phase is all that is needed. But in other cases, further modification of class instances or associated data structures is required to complete the schema evolution. This further modification is generally application dependent, so ObjectStore allows you to define your own functions, transformer functions, to perform the task (see os_schema_evolution::augment_post_evol_transformers()).
You associate exactly one transformer with each class whose instances you want to be transformed. During the transformation phase of instance migration, the schema evolution facility invokes each transformer function on each instance of the function's associated class, including instances that are subobjects of other objects.
You can also use a transformer function to adjust local references. A transformer, associated with the class os_reference_local or os_reference_protected_local, could perform the adjustment by retrieving the new version of each local reference's referent (see os_schema_evolution::get_evolved_object()), and assigning it to the reference.
In addition, transformers are useful for updating data structures that depend on the addresses of migrated instances. A hash table, for example, that hashes on addresses should be rebuilt using a transformer. Note that you do not need to rebuild a data structure if the position of an entry in the structure does not depend on the address of an object pointed to by the entry, but depends instead, for example, on the value of some field of the object pointed to. Such data structures will still be correct after the instance initialization phase.
The schema evolution facility allows for one special form of instance migration, which allows you to reclassify instances of a given class as instances of a class derived from the given class. This form of migration is special because it is not, strictly speaking, a case of modifying instances to conform to a new class definition - you could even reclassify instances without changing the schema at all. However, instance reclassification is typically desirable when new subclasses are added to a schema. Instances of the base class can be given a more specialized representation by being classified as instances of one of the derived classes.
To perform schema evolution, you make and execute an application that invokes the static member function os_schema_evolution::evolve(). The function must be called outside the dynamic scope of a transaction.
os_schema_evolution::augment_classes_to_be_recycled()
static void augment_classes_to_be_recycled( const char *class_name );Adds the class with the specified name to the set of classes whose old, unevolved instances are to be deleted during the instance initialization phase of schema evolution. Applies to evolutions initiated in the current process after the call to this function. The old, unevolved instances of a recycled class will not be accessible during the instance transformation phase.
static void augment_classes_to_be_recycled( const os_Collection<const char*> &class_names );Adds the classes named by the elements of the specified collection to the set of classes whose old, unevolved instances are to be deleted during the instance initialization phase of schema evolution. Applies to evolutions initiated in the current process after the call to this function. The old, unevolved instances of recycled classes will not be accessible during the instance transformation phase.
static void augment_classes_to_be_removed( const char *class_name );Adds the class with the specified name to the set of classes to be removed from the schema during subsequent evolutions. This applies to evolutions initiated in the current process. If the indicated class is actually part of the new schema, err_se_cannot_delete_class is signaled. Note that when you remove a class, C, you must also remove or modify any class that mentions C in its definition. Otherwise err_se_cannot_delete_class is signaled.
static void augment_classes_to_be_removed( const os_collection &class_names );Adds the classes named by the elements of the specified collection to the set of classes to be removed from the schema during schema evolution. This applies to evolutions initiated during the current process after the call to this function. If the indicated class is actually part of the new schema, err_se_cannot_delete_class is signaled. Note that when you remove a class, C, you must also remove or modify any class that mentions C in its definition. Otherwise err_se_cannot_delete_class is signaled.
static void augment_post_evol_transformers( const os_transformer_binding& );Adds the specified transformer binding to the set of transformer bindings to be used during subsequent evolutions. This applies to evolutions initiated in the current process. A transformer binding associates a class with a function so that the function is executed on each instance of the class during the instance transformation phase of evolution - see os_transformer_binding.
static void augment_post_evol_transformers( const os_Collection<os_transformer_binding*>& );Adds the elements of the specified collection to the set of transformer bindings to be used during subsequent evolutions. This applies to evolutions initiated in the current process. A transformer binding associates a class with a function so that the function is executed on each instance of the class during the instance transformation phase of evolution - see os_transformer_binding.
os_schema_evolution::augment_subtype_selectors()
static void augment_subtype_selectors( const os_evolve_subtype_fun_binding& );Adds the specified subtype function binding to the set of subtype function bindings to be used during subsequent evolutions. This applies to evolutions initiated in the current process. A subtype function binding associates a class with a function. The function is used to reclassify instances of the class as instances of a subclass of the class. The string returned by the function for a given instance indicates the instance's new class - see os_evolve_subtype_fun_binding.
static void augment_subtype_selectors( const os_Collection<os_evolve_subtype_fun_binding*>& );Adds the elements of the specified collection to the set of subtype function bindings to be used during subsequent evolutions. This applies to evolutions initiated in the current process. A subtype function binding associates a class with a function. The function is used to reclassify instances of the class as instances of a subclass of the class. The string returned by the function for a given instance indicates the instance's new class - see os_evolve_subtype_fun_binding.
static void evolve ( const char *work_db_name, const char *db_to_evolve );Invokes schema evolution on the database named by db_to_evolve. The function must be called outside the dynamic scope of a transaction. If there is no database named by work_db_name, one with that name is created and used as the work database. If there is a database named by work_db_name, it must be a work database from a prior interrupted evolution performed on the database named by db_to_evolve. In this case, evolution resumes from the latest consistent state prior to the last interruption.
The new schema is determined by the schema of the database being evolved together with the modification schema.
The modification schema is the schema for the compilation or application schema database specified in the most recent call in the current process to os_schema_evolution::set_evolved_schema_db_name() - see below. If there is no prior call to set_evolved_schema_db_name(), the modification schema is the schema for the application calling evolve().
The new schema is the result of merging the schema of the database(s) being evolved with the modification schema; that is, the new schema is the union of the old schema and the modification schema minus the definitions in the old schema of classes that are also defined in the modification schema.
If there are any classes present in the old schema but not in the new schema, they must be specified prior to the call to evolve() using os_schema_evolution::augment_classes_to_be_removed().
During the instance initialization phase, the instances of modified classes are modified to conform to the layouts imposed by the new classes.
Data members whose value type has changed are initialized by assignment with the old data member value, if old and new value types are assignment compatible. That is, ObjectStore assigns the value of the old data member to the storage associated with the new member, applying only standard conversions defined by the C++ language.
In some cases schema evolution considers types assignment compatible when C++ would not. For example, if D is derived from B, schema evolution will assign a B* to a D* if it knows that the B is also an instance of D.
If the new and old value types are not assignment compatible, then, where the new value type is a class, the new members are initialized as if by a constructor that sets each field to the appropriate representation of 0, and where the new value type is not a class, they are initialized with the appropriate representation of 0.
Data members added to the schema whose value type is a class are initialized as if by a constructor that sets each field to 0. Other new data members are initialized with 0.
Array-valued members are initialized, using the above rules, as if the ith array element were a separate data member corresponding to the ith element of the old data member value. If there is no ith element of the old data member value (either because the old value is not an array, or because the old value is an array but does not have an ith element), the new element is initialized as if by a constructor that sets each field to 0, or with 0.
Bit fields are evolved according to the default signed or unsigned rules of the implementation that built the evolution application. This can lead to unexpected results when an evolution application built with one default rule evolves a database originally populated by an application built by an implementation whose default rule differs. The unexpected results occur when the evolution application attempts to increase the width of a bit field.
Schema evolution cannot evolve a pointer-to-member that points to a member in a virtual base class.
When a class is modified to inherit from a base class, subobjects corresponding to the base class are initialized as if by a constructor that sets each field to 0.
Subobjects corresponding to removed data members or base classes are deleted, as are instances of classes removed from the schema.
Changing inheritance from virtual to nonvirtual is treated as removal of the virtual base class and addition of nonvirtual base classes. Changing inheritance from nonvirtual to virtual is treated as removal of the nonvirtual base classes and addition of a virtual base class. In each case, subobjects corresponding to the added classes are initialized as if by a constructor that sets each field to 0.
All pointers and C++ references to instances that require modification, including cross-database pointers, are adjusted to point to the new, evolved instances. All nonlocal ObjectStore references are similarly adjusted. Local references are also similarly adjusted, provided a nonzero os_boolean (true) is supplied as argument in the last call to set_local_references_are_db_relative() prior to the call to evolve(). Otherwise, local references are left unchanged.
Pointers, C++ references, and nonlocal ObjectStore references to deleted subobjects are detected as illegal, as are pointers and references to transient or freed memory, as well as type-mismatched pointers and references. Local ObjectStore references are also detected as illegal under the same circumstances, provided a nonzero os_boolean (true) is supplied as argument in the last call to set_local_references_are_db_relative() prior to the call to evolve(). Pointers of type void* are detected as illegal if the set of preevolution objects whose memory begins at the indicated location is changed after evolution.
Illegal pointers and references can be processed by illegal-pointer handlers supplied by the user (see os_schema_evolution::set_illegal_pointer_handler()).
Illegal pointers and C++ references for which there is no handler provoke the exception err_illegal_pointer or one of its child exceptions, unless ignore_illegal_pointers mode is on (see os_schema_evolution::set_ignore_illegal_pointers()).
When the selection criterion of a query or the path of an index makes reference to a removed class or data member, or makes incorrect type assumptions in light of a schema change, the query or index becomes obsolete. ObjectStore detects all obsolete queries and indexes. In the case of an obsolete query, ObjectStore internally marks the query so that subsequent attempts to use it result in the exception err_os_query_evaluation_error.
Each instance of a class with an associated subtyping function is reclassified according to the class name returned by the function for the instance (see os_schema_evolution::augment_subtype_selectors()).
During the instance initialization phase, unevolved instances of classes to be recycled (see os_schema_evolution::augment_classes_to_be_recycled()) are deleted.
Subsequent to instance initialization, each transformer function (see os_schema_evolution::augment_post_evol_transformers()) is executed on each instance of its associated class. The order of execution of transformers on embedded objects follows the same pattern as constructors. When the transformer for a given class is invoked, the transformers for base classes of the given class are executed first (in declaration order), followed by the transformers for class-valued members of the given class (in declaration order), after which the transformer for the given class itself is executed.
static void evolve( const char *work_db_name, const os_Collection<const char*> &dbs_to_evolve );Invokes schema evolution on the databases named by the elements of dbs_to_evolve. The rest of the behavior for this function is as described for the previous overloading of evolve(), above.
static void evolve( const char *work_db_name, const os_Collection<const char*> &dbs_to_evolve, os_schema &new_schema );Invokes schema evolution on the databases named by the elements of dbs_to_evolve. The rest of the behavior for this function is as described for the first overloading of evolve(), above, except that the modification schema is specified by new_schema.
static void evolve( const char *work_db_name, const char *db_to_evolve, os_schema &new_schema );Invokes schema evolution on the database specified by db_to_evolve. The rest of the behavior for this function is as described for the first overloading of evolve(), above, except that the modification schema is specified by new_schema.
static os_typed_pointer_void get_evolved_address(void*);Returns an os_typed_pointer_void to the evolved object corresponding to the unevolved object pointed to by the specified void*. The os_typed_pointer_void encapsulates a void* pointer to the evolved object. The pointer is null if the object was not evolved. Assumes that only the address of the evolved object is desired, not the object itself, and consequently does not check the validity of the object.
static os_typed_pointer_void get_evolved_object(void*);Returns an os_typed_pointer_void to the evolved object corresponding to the unevolved object pointed to by the specified void*. The os_typed_pointer_void encapsulates a void* pointer to the evolved object. The pointer is null if the object was not evolved. An exception is raised if this pointer is illegal (see os_schema_evolution::evolve()).
os_schema_evolution::get_ignore_illegal_pointers()
static os_boolean get_ignore_illegal_pointers();Returns nonzero if ignore_illegal_pointers mode is on. Returns 0 otherwise. See also os_schema_evolution::set_ignore_illegal_pointers().
os_schema_evolution::get_unevolved_address()
static os_typed_pointer_void get_unevolved_address(void*);Returns an os_typed_pointer_void to the unevolved object corresponding to the evolved object pointed to by the specified void*. Assumes that only the address of this object is desired, not the object itself, and consequently does not check the validity of the object.
static os_typed_pointer_void get_unevolved_object(void*);Returns an os_typed_pointer_void to the unevolved object corresponding to the evolved object pointed to by the specified void*. An exception is raised if the unevolved object was deleted during instance initialization (see os_schema_evolution::augment_classes_to_be_recycled()).
os_schema_evolution::get_work_database()
static os_database *get_work_database();This function returns a pointer to the work database for the current evolution.
static void set_evolved_schema_db_name(const char*);Specifies the name of an application schema database used to determine the new schema in subsequent evolutions during the current process.
static void set_ignore_illegal_pointers( os_boolean) );If the argument is nonzero, turns on ignore_illegal_pointers mode, causing ObjectStore to ignore illegal pointers and references encountered during evolution. If the argument is 0, turns off ignore_illegal_pointers mode. See also os_schema_evolution::get_ignore_illegal_pointers().
os_schema_evolution::set_illegal_pointer_handler()
static void set_illegal_pointer_handler( void (*f)(objectstore_exception_r, os_char_p msg, os_void_pr illegalp) );Specifies f as the handler function for illegal pointers. Applies to evolutions initiated in the current process after the call to this function. The function f must be defined by the user. The objectstore_exception is the exception that would have been signaled had a handler not been supplied, the char* points to the error message that would have been generated, and illegalp is a reference to the illegal pointer.
static void set_illegal_pointer_handler( void (*f)(objectstore_exception_r, os_char_p msg, os_canonical_ptom_r) );Specifies f as the handler function for illegal pointers to members. Applies to evolutions initiated in the current process after the call to this function. The function f must be defined by the user. The objectstore_exception_r is the exception that would have been signaled had a handler not been supplied, the os_char_p points to the error message that would have been generated, and the os_canonical_ptom_r is a reference to the illegal pointer-to-member.
static void set_illegal_pointer_handler( void (*f)(objectstore_exception_r, os_char_p*msg, os_reference_local_r) );Specifies f as the handler function for illegal ObjectStore local references. Applies to evolutions initiated in the current process after the call to this function. The function f must be defined by the user. The objectstore_exception_r is the exception that would have been signaled had a handler not been supplied, the os_char_p* points to the error message that would have been generated, and the os_reference_local_r is a C++ reference to the illegal ObjectStore reference.
static void set_illegal_pointer_handler( void (*f)(objectstore_exception_r, os_char_p msg, os_reference_r) );Specifies f as the handler function for illegal ObjectStore nonlocal references. Applies to evolutions initiated in the current process after the call to this function. The function f must be defined by the user. The objectstore_exception_r is the exception that would have been signaled had a handler not been supplied, the os_char_p points to the error message that would have been generated, and the os_reference_r is a C++ reference to the illegal ObjectStore reference.
static void set_illegal_pointer_handler( void (*f)(objectstore_exception_r, os_char_p msg, os_database_root_r) );Specifies f as the handler function for illegal database root values. Applies to evolutions initiated in the current process after the call to this function. The function f must be defined by the user. The objectstore_exception_r is the exception that would have been signaled had a handler not been supplied, the os_char_p* points to the error message that would have been generated, and the os_database_root_r is a C++ reference to the illegal database root.
static void set_local_references_are_db_relative(os_boolean);If a nonzero os_boolean (true) is supplied as argument, local references will be resolved using the database in which the reference itself resides. Otherwise local references will not be adjusted during the instance initialization phase. Applies to evolutions initiated in the current process after the call to this function.
static void set_obsolete_index_handler( void (*f)(const os_collection&, const char *path_string) );Specifies f as the handler function for obsolete indexes. Applies to evolutions initiated in the current process after the call to this function. The function f must be defined by the user. The os_collection& is a reference to the collection whose index is obsolete, and the char* points to a string expressing the index's path (key).
static void set_obsolete_query_handler_handler( void (*f)(os_coll_query_r, os_char_const_p query_string) );Specifies f as the handler function for obsolete queries. Applies to evolutions initiated in the current process after the call to this function. The function f must be defined by the user. The os_coll_query_r is a reference to the obsolete query, and the os_char_const_p points to a string expressing the query's selection criterion.
static void set_task_list_file_name(const char *file_name);Specifies the file named file_name as the file to which a task list should be sent. Applies to task lists generated in the current process after the call to this function.
static void task_list( const char *work_db_name, const char *db_to_evolve );Generates a task list for the evolution that would have taken place had evolve() been called with the same arguments. The task list is sent to the file specified by the most recent call to os_schema_evolution::set_task_list_file_name(), or if there is no such call, to standard output. Once the task list is generated, this function exits, terminating the current process.
The task list contains a function definition for each class whose instances will be migrated. Each function has a name of the form
class-name@[1]::initializer()where class-name names the function's associated class. Each class-name@[1]::initializer() function definition contains a statement or comment for each data member of its associated class. For a member with value type T, this statement or comment is either
static void task_list ( const char *work_db_name, const os_Collection<const char*> &dbs_to_evolve );Generates a task list for the evolution that would have taken place had evolve() been called with the same arguments. The task list is sent to the file specified by the most recent call to os_schema_evolution::set_task_list_file_name(). The contents of the task list are as described for the previous overloading of task_list(), above.
Updated: 03/31/98 17:25:09