Table of Contents Previous Next
Logo
FreezeScript : 41.4 Transformation Descriptors
Copyright © 2003-2009 ZeroC, Inc.

41.4 Transformation Descriptors

This section describes the XML elements comprising the FreezeScript transformation descriptors.

41.4.1 Overview

A transformation descriptor file has a well-defined structure. The top-level descriptor in the file is <transformdb>. A <database> descriptor must be present within <transformdb> to define the key and value types used by the database. Inside <database>, the <record> descriptor triggers the transformation process. See Section 41.3.5 for an example that demonstrates the structure of a minimal descriptor file.
During transformation, type-specific actions are supported by the <transform> and <init> descriptors, both of which are children of <transformdb>. One <transform> descriptor and one <init> descriptor may be defined for each type in the new Slice definitions. Each time transformdb creates a new instance of a type, it executes the <init> descriptor for that type, if one is defined. Similarly, each time transformdb transforms an instance of an old type into a new type, the <transform> descriptor for the new type is executed.
The <database>, <record>, <transform>, and <init> descriptors may contain general-purpose action descriptors such as <if>, <set>, and <echo>. These actions resemble statements in programming languages like C++ and Java, in that they are executed in the order of definition and their effects are cumulative. Actions make use of the expression language described in Section 41.8.

41.4.2 Flow of Execution

The transformation descriptors are executed as described below.
• <database> is executed first. Each child descriptor of <database> is executed in the order of definition. If a <record> descriptor is present, database transformation occurs at that point. Any child descriptors of <database> that follow <record> are not executed until transformation completes.
• During transformation of each record, transformdb creates instances of the new key and value types, which includes the execution of the <init> descriptors for those types. Next, the old key and value are transformed into the new key and value, in the following manner:
1. Locate the <transform> descriptor for the type.
2. If no descriptor is found, or the descriptor exists and it does not preclude default transformation, then transform the data as described in Section 41.3.1.
3. If the <transform> descriptor exists, execute it.
4. Finally, execute the child descriptors of <record>.
See Section 41.4.4 for detailed information on the transformation descriptors.

41.4.3 Scopes

The <database> descriptor creates a global scope, allowing child descriptors of <database> to define symbols that are accessible in any descriptor1. Furthermore, certain other descriptors create local scopes that exist only for the duration of the descriptor’s execution. For example, the <transform> descriptor creates a local scope and defines the symbols old and new to represent a value in its old and new forms. Child descriptors of <transform> can also define new symbols in the local scope, as long as those symbols do not clash with an existing symbol in that scope. It is legal to add a new symbol with the same name as a symbol in an outer scope, but the outer symbol will not be accessible during the descriptor’s execution.
The global scope is useful in many situations. For example, suppose you want to track the number of times a certain value was encountered during transformation. This can be accomplished as shown below:
<transformdb>
    <database key="string" value="::Ice::Identity">
        <define name="categoryCount" type="int" value="0"/>
        <record/>
        <echo message="categoryCount = " value="categoryCount"/>
    </database>
    <transform type="::Ice::Identity">
        <if test="new.category == 'Accounting'">
            <set target="categoryCount"
                value="categoryCount + 1"/>
        </if>
    </transform>
</transformdb>
In this example, the <define> descriptor introduces the symbol categoryCount into the global scope, defining it as type int with an initial value of zero. Next, the <record> descriptor causes transformation to proceed. Each occurrence of the type Ice::Identity causes its <transform> descriptor to be executed, which examines the category member and increases categoryCount if necessary. Finally, after transformation completes, the <echo> descriptor displays the final value of categoryCount.
To reinforce the relationships between descriptors and scopes, consider the diagram in Figure 41.1. Several descriptors are shown, including the symbols they define in their local scopes. In this example, the <iterate> descriptor has a dictionary target and therefore the default symbol for the element value, value, hides the symbol of the same name in the parent <init> descriptor’s scope2. In addition to symbols in the <iterate> scope, child descriptors of <iterate> can also refer to symbols from the <init> and <database> scopes.
Figure 41.1. Relationship between descriptors and scopes.

41.4.4 Descriptor Reference

<transformdb>

The top-level descriptor in a descriptor file. It requires at least one <database> descriptor, and supports any number of <transform> and <init> descriptors. This descriptor has no attributes.

<database>

The attributes of this descriptor define the old and new key and value types for the database to be transformed, and optionally the name of the database to which these types apply. It supports any number of child descriptors, but at most one <record> descriptor. The <database> descriptor also creates a global scope for user-defined symbols (see Section 41.4.3).
The attributes supported by the <database> descriptor are described in Table 41.2.
Table 41.2. Attributes for <database> descriptor.
Specifies the Slice types of the old and new keys. If the types are the same, only one needs to be specified. Otherwise, the types are separated by a comma.
Specifies the Slice types of the old and new values. If the types are the same, only one needs to be specified. Otherwise, the types are separated by a comma.
As an example, consider the following <database> descriptor. In this case, the Freeze map to be transformed currently has key type int and value type ::Employee, and is migrating to a key type of string:
<database key="int,string" value="::Employee">

<record>

Commences the transformation. Child descriptors are executed for each record in the database, providing the user with an opportunity to examine the record's old key and value, and optionally modify the new key and value. Default transformations, as well as <transform> and <init> descriptors, are executed before the child descriptors. The <record> descriptor introduces the following symbols into a local scope: oldkey, newkey, oldvalue, newvalue, facet. These symbols are accessible to child descriptors, but not to <transform> or <init> descriptors. The oldkey and oldvalue symbols are read-only. The facet symbol is a string indicating the facet name of the object in the current record, and is only relevant for Freeze evictor databases.
Use caution when modifying database keys to ensure that duplicate keys do not occur. If a duplicate database key is encountered, transformation fails immediately.
Note that database transformation only occurs if a <record> descriptor is present.

<transform>

Customizes the transformation for all instances of a type in the new Slice definitions. The children of this descriptor are executed after the optional default transformation has been performed, as described in Section 41.3.1. Only one <transform> descriptor can be specified for a type, but a <transform> descriptor is not required for every type. The symbols old and new are introduced into a local scope and represent the old and new values, respectively. The old symbol is read-only. The attributes supported by this descriptor are described in Table 41.3.
Table 41.3. Attributes for <transform> descriptor.
If false, no default transformation is performed on values of this type. If not specified, the default value is true.
This attribute determines whether <transform> descriptors of base class types are executed. If true, the <transform> descriptor of the immediate base class is invoked. If no descriptor is found for the immediate base class, the class hierarchy is searched until a descriptor is found. The execution of any base class descriptors occurs after execution of this descriptor’s children. If not specified, the default value is true.
Indicates that a type in the old Slice definitions has been renamed to the new type identified by the type attribute. The value of this attribute is the type id of the old Slice definition. Specifying this attribute relaxes the strict compatibility rules defined in Section 41.3.2 for enum, struct and class types.
Below is an example of a <transform> descriptor that initializes a new data member:
<transform type="::Product">
    <set target="new.salePrice"
         value="old.listPrice * old.discount"/>
</transform>
For class types, transformdb first attempts to locate a <transform> descriptor for the object’s most-derived type. If no descriptor is found, transformdb proceeds up the class hierarchy in an attempt to find a descriptor. The base object type, Object, is the root of every class hierarchy and is included in the search for descriptors. It is therefore possible to define a <transform> descriptor for type Object, which will be invoked for every class instance.
Note that <transform> descriptors are executed recursively. For example, consider the following Slice definitions:
struct Inner {
    int sum;
};
struct Outer {
    Inner i;
};
When transformdb is performing the default transformation on a value of type Outer, it recursively performs the default transformation on the Inner member, then executes the <transform> descriptor for Inner, and finally executes the <transform> descriptor for Outer. However, if default transformation is disabled for Outer, then no transformation is performed on the Inner member and therefore the <transform> descriptor for Inner is not executed.

<init>

Defines custom initialization rules for all instances of a type in the new Slice definitions. Child descriptors are executed each time the type is instantiated. The typical use case for this descriptor is for types that have been introduced in the new Slice definitions and whose instances require default values different than what transformdb supplies. The symbol value is introduced into a local scope to represent the instance. The attributes supported by this descriptor are described in Table 41.4.
Table 41.4. Attributes for <init> descriptor.
Here is a simple example of an <init> descriptor:
<init type="::Player">
    <set target="value.currency" value="100"/>
</init>
Note that, like <transform>, <init> descriptors are executed recursively. For example, if an <init> descriptor is defined for a struct type, the <init> descriptors of the struct’s members are executed before the struct’s descriptor.

<iterate>

Iterates over a dictionary or sequence, executing child descriptors for each element. The symbol names selected to represent the element information may conflict with existing symbols in the enclosing scope, in which case those outer symbols are not accessible to child descriptors. The attributes supported by this descriptor are described in Table 41.5.
Table 41.5. Attributes for <iterate> descriptor.
Shown below is an example of an <iterate> descriptor that sets the new data member reviewSalary to true if the employee’s salary is greater than $3000.
<iterate target="new.employeeMap" key="id" value="emp">
    <if test="emp.salary > 3000">
        <set target="emp.reviewSalary" value="true"/>
    </if>
</iterate>

<if>

Conditionally executes child descriptors. The attributes supported by this descriptor are described in Table 41.6.
Table 41.6. Attributes for <if> descriptor.
See Section 41.8 for more information on the descriptor expression language.

<set>

Modifies a value. The value and type attributes are mutually exclusive. If target denotes a dictionary element, that element must already exist (i.e., <set> cannot be used to add an element to a dictionary). The attributes supported by this descriptor are described in Table 41.7.
Table 41.7. Attributes for <set> descriptor.
If specified, set the target to be an instance of the given Slice class. The value is a type id from the new Slice definitions. The class must be compatible with the target’s type.
An integer expression representing the desired new length of a sequence. If the new length is less than the current size of the sequence, elements are removed from the end of the sequence. If the new length is greater than the current size, new elements are added to the end of the sequence. If value or type is also specified, it is used to initialize each new element.
If true, additional type conversions are supported: between integer and floating point, and between integer and enumeration. Transformation fails immediately if a range error occurs. If not specified, the default value is false.
The <set> descriptor below modifies a member of a dictionary element:
<set target="new.parts['P105J3'].cost"
      value="new.parts['P105J3'].cost * 1.05"/>
This <set> descriptor adds an element to a sequence and initializes its value:
<set target="new.partsList" length="new.partsList.length + 1"
     value="'P105J3'"/>
As another example, the following <set> descriptor changes the value of an enumeration. Notice that the value refers to a symbol in the new Slice definitions (see Section 41.8.3 for more information).
<set target="new.ingredient" value="::New::Apple"/>

<add>

Adds a new element to a sequence or dictionary. It is legal to add an element while traversing the sequence or dictionary using <iterate>, however the traversal order after the addition is undefined. The key and index attributes are mutually exclusive, as are the value and type attributes. If neither value nor type is specified, the new element is initialized with a default value. The attributes supported by this descriptor are described in Table 41.8.
Table 41.8. Attributes for <add> descriptor.
An expression that must evaluate to an integer value representing the insertion position. The new element is inserted before index. The value must not exceed the length of the target sequence.
An expression that must evaluate to a value compatible with the target dictionary’s value type, or the target sequence’s element type.
If specified, set the target value or element to be an instance of the given Slice class. The value is a type id from the new Slice definitions. The class must be compatible with the target dictionary’s value type, or the target sequence’s element type.
If true, additional type conversions are supported: between integer and floating point, and between integer and enumeration. Transformation fails immediately if a range error occurs. If not specified, the default value is false.
Below is an example of an <add> descriptor that adds a new dictionary element and then initializes its member:
<add target="new.parts" key="'P105J4'"/>
<set target="new.parts['P105J4'].cost" value="3.15"/>

<define>

Defines a new symbol in the current scope. The attributes supported by this descriptor are described in Table 41.9.
Table 41.9. Attributes for <define> descriptor.
The name of the new symbol. An error occurs if the name matches an existing symbol in the current scope.
The name of the symbol’s formal Slice type. For user-defined types, the name should be prefixed with ::Old or ::New to indicate the source of the type. The prefix can be omitted for primitive types.
If true, additional type conversions are supported: between integer and floating point, and between integer and enumeration. Execution fails immediately if a range error occurs. If not specified, the default value is false.
Below are two examples of the <define> descriptor. The first example defines the symbol identity to have type Ice::Identity, and proceeds to initialize its members using <set>:
<define name="identity" type="::New::Ice::Identity"/>
<set target="identity.name" value="steve"/>
<set target="identity.category" value="Admin"/>
The second example uses the enumeration we first saw in Section 41.3.5 to define the symbol manufacturer and assign it a default value:
<define name="manufacturer" type="::New::BigThree"
        value="::New::Daimler"/>

<remove>

Removes an element from a sequence or dictionary. It is legal to remove an element while traversing a sequence or dictionary using <iterate>, however the traversal order after removal is undefined. The attributes supported by this descriptor are described in Table 41.10.
Table 41.10. Attributes for <remove> descriptor.
An expression that must evaluate to an integer value representing the index of the sequence element to be removed.

<fail>

Causes transformation to fail immediately. If test is specified, transformation fails only if the expression evaluates to true. The attributes supported by this descriptor are described in Table 41.11.
Table 41.11. Attributes for <fail> descriptor.
The following <fail> descriptor terminates the transformation if a range error is detected:
<fail message="range error occurred in ticket count!"
      test="old.ticketCount > 32767"/>

<delete>

Causes transformation of the current database record to cease, and removes the record from the transformed database. This descriptor has no attributes.

<echo>

Displays values and informational messages. If no attributes are specified, only a newline is printed. The attributes supported by this descriptor are described in Table 41.12.
Table 41.12. Attributes for <echo> descriptor.
Shown below is an <echo> descriptor that uses both message and value attributes:
<if test="old.ticketCount > 32767">
    <echo message="deleting record with invalid ticket count: "
          value="old.ticketCount"/>
    <delete/>
</if>

41.4.5 Descriptor Guidelines

There are three points at which you can intercept the transformation process: when transforming a record (<record>), when transforming an instance of a type (<transform>), and when creating an instance of a type (<init>).
In general, <record> is used when your modifications require access to both the key and value of the record. For example, if the database key is needed as a factor in an equation, or to identify an element in a dictionary, then <record> is the only descriptor in which this type of modification is possible. The <record> descriptor is also convenient to use when the number of changes to be made is small, and does not warrant the effort of writing separate <transform> or <init> descriptors.
The <transform> descriptor has a more limited scope than <record>. It is used when changes must potentially be made to all instances of a type (regardless of the context in which that type is used) and access to the old value is necessary. The <transform> descriptor does not have access to the database key and value, therefore decisions can only be made based on the old and new instances of the type in question.
Finally, the <init> descriptor is useful when access to the old instance is not required in order to properly initialize a type. In most cases, this activity could also be performed by a <transform> descriptor that simply ignored the old instance, so <init> may seem redundant. However, there is one situation where <init> is required: when it is necessary to initialize an instance of a type that is introduced by the new Slice definitions. Since there are no instances of this type in the current database, a <transform> descriptor for that type would never be executed.

1
In order for a global symbol to be available to a <transform> or <init> descriptor, the symbol must be defined before the <record> descriptor is executed.

2
This situation can be avoided by assigning a different symbol name to the element value.

Table of Contents Previous Next
Logo