Table of Contents Previous Next
Logo
FreezeScript : 37.3 Database Migration
Copyright © 2003-2008 ZeroC, Inc.

37.3 Database Migration

The FreezeScript tool transformdb migrates a database created by a Freeze map or evictor. It accomplishes this by comparing the "old" Slice definitions (i.e., the ones that describe the current contents of the database) with the "new" Slice definitions, and making whatever modifications are necessary to ensure that the transformed database is compatible with the new definitions.
This would be difficult to achieve by writing a custom transformation program because that program would require static knowledge of the old and new types, which frequently define many of the same symbols and would therefore prevent the program from being loaded. The transformdb tool avoids this issue using an interpretive approach: the Slice definitions are parsed and used to drive the migration of the database records.
The tool supports two modes of operation:
1. automatic migration, in which the database is migrated in a single step using only the default set of transformations, and
2. custom migration, in which you supply a script to augment or override the default transformations.

37.3.1 Default Transformations

The default transformations performed by transformdb preserve as much information as possible. However, there are practical limits to the tool’s capabilities, since the only information it has is obtained by performing a comparison of the Slice definitions.
For example, suppose our old definition for a structure is the following:
struct AStruct {
    int i;
};
We want to migrate instances of this struct to the following revised definition:
struct AStruct {
    int j;
};
As the developers, we know that the int member has been renamed from i to j, but to transformdb it appears that member i was removed and member j was added. The default transformation results in exactly that behavior: the value of i is lost, and j is initialized to a default value. If we need to preserve the value of i and transfer it to j, then we need to use custom migration (see Section 37.3.5).
The changes that occur as a type system evolves can be grouped into three categories:
• Data members
The data members of class and structure types are added, removed, or renamed. As discussed above, the default transformations initialize new and renamed data members to default values (see Section 37.3.3).
• Type names
Types are added, removed, or renamed. New types do not pose a problem for database migration when used to define a new data member; the member is initialized with default values as usual. On the other hand, if the new type replaces the type of an existing data member, then type compatibility becomes a factor (see the following item).
Removed types generally do not cause problems either, because any uses of that type must have been removed from the new Slice definitions (e.g., by removing data members of that type). There is one case, however, where removed types become an issue, and that is for polymorphic classes (see Section 37.5.10).
Renamed types are a concern, just like renamed data members, because of the potential for losing information during migration. This is another situation for which custom migration is recommended.
• Type content
Examples of changes of type content include the key type of a dictionary, the element type of a sequence, or the type of a data member. If the old and new types are not compatible (as defined in Section 37.3.2), then the default transformation emits a warning, discards the current value, and reinitializes the value as described in Section 37.3.3.

37.3.2 Type Compatibility

Changes in the type of a value are restricted to certain sets of compatible changes. This section describes the type changes supported by the default transformations. All incompatible type changes result in a warning indicating that the current value is being discarded and a default value for the new type assigned in its place. Additional flexibility is provided by custom migration, as described in Section 37.3.5.

Boolean

A value of type bool can be transformed to and from string. The legal string values for a bool value are "true" and "false".

Integer

The integer types byte, short, int, and long can be transformed into each other, but only if the current value is within range of the new type. These integer types can also be transformed into string.

Floating Point

The floating-point types float and double can be transformed into each other, as well as to string. No attempt is made to detect a loss of precision during transformation.

String

A string value can be transformed into any of the primitive types, as well as into enumeration and proxy types, but only if the value is a legal string representation of the new type. For example, the string value "Pear" can be transformed into the enumeration Fruit, but only if Pear is an enumerator of Fruit.

Enum

An enumeration can be transformed into an enumeration with the same type id, or into a string. Transformation between enumerations is performed symbolically. For example, consider our old type below:
enum Fruit { Apple, Orange, Pear };
Suppose the enumerator Pear is being transformed into the following new type:
enum Fruit { Apple, Pear };
The transformed value in the new enumeration is also Pear, despite the fact that Pear has changed positions in the new type. However, if the old value had been Orange, then the default transformation emits a warning because that enumerator no longer exists, and initializes the new value to Apple (the default value).
If an enumerator has been renamed, then custom migration is required to convert enumerators from the old name to the new one.

Sequence

A sequence can be transformed into another sequence type, even if the new sequence type does not have the same type id as the old type, but only if the element types are compatible. For example, sequence<short> can be transformed into sequence<int>, regardless of the names given to the sequence types.

Dictionary

A dictionary can be transformed into another dictionary type, even if the new dictionary type does not have the same type id as the old type, but only if the key and value types are compatible. For example, dictionary<int, string> can be transformed into dictionary<long, string>, regardless of the names given to the dictionary types.
Caution is required when changing the key type of a dictionary, because the default transformation of keys could result in duplication. For example, if the key type changes from int to short, any int value outside the range of short results in the key being initialized to a default value (namely zero). If zero is already used as a key in the dictionary, or another out-of-range key is encountered, then a duplication occurs. The transformation handles key duplication by removing the duplicate element from the transformed dictionary. (Custom migration can be useful in these situations if the default behavior is not acceptable.)

Structure

A struct type can only be transformed into another struct type with the same type id. Data members are transformed as appropriate for their types.

Proxy

A proxy value can be transformed into another proxy type, or into string. Transformation into another proxy type is done with the same semantics as in a language mapping: if the new type does not match the old type, then the new type must be a base type of the old type (that is, the proxy is widened).

Class

A class type can only be transformed into another class type with the same type id. A data member of a class type is allowed to be widened to a base type. Data members are transformed as appropriate for their types. See Section 37.5.10 for more information on transforming classes.

37.3.3 Default Values

Data types are initialized with default values, as shown in Table 37.1.

37.3.4 Running an Automatic Transformation

In order to use automatic transformation, we need to supply the following information to transformdb:
• The old and new Slice definitions
• The old and new types for the database key and value
• The database environment directory, the database file name, and the name of a new database environment directory to hold the transformed database
Here is an example of a transformdb command:
$ transformdb old old/MyApp.ice new new/MyApp.ice \
key int,string value ::Employee db emp.db newdb
Briefly, the old and new options specify the old and new Slice definitions, respectively. These options can be specified as many times as necessary in order to load all of the relevant definitions. The key option indicates that the database key is evolving from int to string. The value option specifies that ::Employee is used as the database value type in both old and new type definitions, and therefore only needs to be specified once. Finally, we provide the pathname of the database environment directory (db), the file name of the database (emp.db), and the pathname of the database environment directory for the transformed database (newdb).
See Section 37.5 for more information on using transformdb.

37.3.5 Custom Migration

Custom migration is useful when your types have changed in ways that make automatic migration difficult or impossible. It is also convenient to use custom migration when you have complex initialization requirements for new types or new data members, because custom migration enables you to perform many of the same tasks that would otherwise require you to write a throwaway program.
Custom migration operates in conjunction with automatic migration, allowing you to inject your own transformation rules at well-defined intercept points in the automatic migration process. These rules are called transformation descriptors, and are written in XML.

A Simple Example

We can use a simple example to demonstrate the utility of custom migration. Suppose our application uses a Freeze map whose type is string and whose value is an enumeration, defined as follows:
enum BigThree { Ford, DaimlerChrysler, GeneralMotors };
We now wish to rename the enumerator DaimlerChrysler, as shown in our new definition:
enum BigThree { Ford, Daimler, GeneralMotors };
As explained in Section 37.3.2, the default transformation results in all occurrences of the DaimlerChrysler enumerator being transformed into Ford, because Chrysler no longer exists in the new definition and therefore the default value Ford is used instead.
To remedy this situation, we use the following transformation descriptors:
<transformdb>
    <database key="string" value="::BigThree">
        <record>
            <if test="oldvalue == ::Old::DaimlerChrysler>
                <set target="newvalue"
                    value="::New::Daimler"/>
            </if>
        </record>
    </database>
</transformdb>
When executed, these descriptors convert occurrences of DaimlerChrysler in the old type system into Daimler in the transformed database’s new type system. Transformation descriptors are described in detail in Section 37.4.
Table of Contents Previous Next
Logo