Defining Data Type Classes
This topic describes how data type classes work and describes how to define them.
Overview of Data Type Classes
The purpose of a data type class is to be used as the type of a literal property in an object class. Data type classes provide the following features:
-
They provide for SQL and client interoperability by providing SQL logical operation, client data type, and translation information.
-
They provide validation for literal data values, which you can extend or customize by using data type class parameters.
-
They manage the translation of literal data for its stored (on disk), logical (in memory), and display formats.
See Property Methods for information on how the compiler uses a data type class to generate code for a property.
Data type classes differ from other classes in a number of ways:
-
They cannot be instantiated or stored independently.
-
They cannot contain properties.
-
They support a specific set of methods (called the data type interface), which is described below.
Because it is useful to be aware of some internal details, this section briefly discusses how data type classes work.
As noted previously, the purpose of a data type class is to be used as the type of a property, particularly within a class that extends one of the core object classes. The following shows an example object class that has three properties. Each property uses a data type class as its type.
Class Datatypes.Container Extends %RegisteredObject
{
Property P1 As %String;
Property P2 As %Integer;
Property P3 As %Boolean;
}
Property Methods
When you add literal properties to a class and compile the class, InterSystems IRIS® data platform adds property methods to that class. For reference, let us use the term container class to refer to the class that contains the properties. The property methods control how the container class handles the data for those properties. This system works as follows:
-
Each data type class provides a set of methods, more specifically method generators, that InterSystems IRIS uses when it compiles a class that uses them. A method generator is a method that generates its own runtime code.
In the example shown here, when we compile the Datatypes.Container, the compiler uses the method generators of the %StringOpens in a new tab, %IntegerOpens in a new tab, and %BooleanOpens in a new tab data type classes. These method generators create methods for each property and add these methods to the container class. As noted above, these methods are called property methods. Their names start with the name of the property to which they apply. For example, for the P1 property, the compiler generates methods such as P1IsValid(), P1Normalize(), P1LogicalToDisplay(), P1DisplayToLogical(), and others.
-
The container class automatically calls the property methods at suitable points in processing. For example, when you call the %ValidateObject() instance method for an instance of the class shown above, the method in turn calls P1IsValid(), P2IsValid(), and P3IsValid() — that is, it calls the IsValid() method for each property. For another example, if the container class is persistent, and you use InterSystems SQL to access all the fields in the associated table, and the SQL runtime mode is ODBC, InterSystems IRIS calls the LogicalToOdbc() method for each property, so that the query returns results in ODBC format.
Note that the property methods are not visible in the class definition.
All these methods are class methods and all of them require an argument: the value to check for validity, to normalize, to convert, and so on.
Data Formats
Many of the property methods translate data from one format to another, for example when displaying data in a human-readable format or when accessing data via ODBC. The formats are:
-
Display — The format in which data can be input and displayed. For instance, a date in the form of “April 3, 1998” or “23 November, 1977.”
-
Logical — The in-memory format of data, which is the format upon which operations are performed. While dates have the display format described above, their logical format is integer; for the sample dates above, their values in logical format are 57436 and 50000, respectively.
-
Storage — The on-disk format of data — the format in which data is stored to the database. Typically this is identical to the logical format.
-
ODBC — The format in which data can be presented via ODBC or JDBC. This format is used when data is exposed to ODBC/SQL. The available formats correspond to those defined by ODBC.
-
XSD — SOAP-encoded format. This format is used when you export to or import from XML. This applies only to classes that are XML-enabled.
Parameters in Data Type Classes
Class parameters have a special behavior when used with data type classes. With data type classes, class parameters are used to provide a way to customize the behavior of any properties based on the data type.
For example, the %IntegerOpens in a new tab data type class has a class parameter, MAXVAL, which specifies the maximum valid value for a property of type %IntegerOpens in a new tab. If you define a class with the property NumKids as follows:
Property NumKids As %Integer(MAXVAL=10);
This specifies that the MAXVAL parameter for the %IntegerOpens in a new tab class will be set to 10 for the NumKids property.
Internally, this works as follows: the validation methods for the standard data type classes are all implemented as method generators and use their various class parameters to control the generation of these validation methods. In this example, this property definition generates content for a NumKidsIsValid() method that tests whether the value of NumKids exceeds 10. Without the use of class parameters, creating this functionality would require the definition of an IntegerLessThanTen class.
Defining a Data Type Class
To easily define a data type class, first identify an existing data type class that is close to your needs. Create a subclass of this class. In your subclass:
-
Specify suitable values for the keywords SqlCategory, ClientDataType, and OdbcType.
-
Override any class parameters as needed. For example, you might override the MAXLEN parameter so that there is no length limit for the property.
If needed, add your own class parameters as well.
-
Override the methods of the data type class as needed. In your implementations, refer to the parameters of this class as needed.
If you do not base your data type class on an existing data type class, be sure to add [ ClassType=datatype ] to the class definition. This declaration is not needed if you do base your class on another data type class.
When defining a data type class, do not extend %PersistentOpens in a new tab, %RegisteredObjectOpens in a new tab, or other object classes, as data type classes cannot contain properties.
Defining Class Methods in Data Type Classes
Depending on your needs, you should define some or all of the following class methods in your data type classes:
-
IsValid() — performs validation of data for the property, using property parameters if appropriate. As noted earlier, the %ValidateObject() instance method of any object class invokes the IsValid() method for each property. This method has the following signature:
ClassMethod IsValid(%val) As %Status
Where %val is the value to be validated. This method should return an error status if the value is invalid and should otherwise return $$$OK.
Note:It is standard practice in InterSystems IRIS not to invoke validation logic for null values.
-
Normalize() — converts the data for the property into a standard form or format. The %NormalizeObject() instance method of any object class invokes the Normalize() method for each property. This method has the following signature:
ClassMethod Normalize(%val) As Type
Where %val is the value to be validated and Type is a suitable type class.
-
DisplayToLogical() — converts a display value into a logical value. (For information on formats, see Data Formats.) This method has the following signature:
ClassMethod DisplayToLogical(%val) As Type
Where %val is the value to be converted and Type is a suitable type class.
The other format conversion methods have the same general form.
-
LogicalToDisplay() — converts a logical value to a display value.
-
LogicalToOdbc() — converts a logical value into an ODBC value.
Note that the ODBC value must be consistent with the ODBC type specified by the OdbcType class keyword of the data type class.
-
LogicalToStorage() — converts a logical value into a storage value.
-
LogicalToXSD() — converts a logical value into the appropriate SOAP-encoded value.
-
OdbcToLogical() — converts an ODBC value into a logical value.
-
StorageToLogical() — converts a database storage value into a logical value.
-
XSDToLogical() — converts a SOAP-encoded value into a logical value.
All these methods are class methods and all of them must accept an argument: the value to check for validity, to normalize, to convert, and so on.
If the data type class includes the DISPLAYLIST and VALUELIST parameters, these methods must first check for the presence of these class parameters and include code to process these lists if present. The logic is similar for other methods.
In most cases, many of these methods are method generators.
Note that the data format and translation methods cannot include embedded SQL. If you need to call embedded SQL within this logic, then you can place the embedded SQL in a separate routine, and the method can call this routine.
Defining Instance Methods in Data Type Classes
You can also add instance methods to the data type class, and these methods can use the variable %val, which contains the current value of the property. The compiler uses these to generate the associated property methods in any class that uses the data type class. For example, consider the following example data type class:
Class Datatypes.MyDate Extends %Date
{
Method ToMyDate() As %String [ CodeMode = expression ]
{
$ZDate(%val,4)
}
}
Suppose that we have another class as follows:
Class Datatypes.Container Extends %Persistent
{
Property DOB As Datatypes.MyDate;
/// additional class members
}
When we compile these classes, InterSystems IRIS adds the instance method DOBToMyDate() to the container class. Then when we create an instance of the container class, we can invoke this method. For example:
TESTNAMESPACE>set instance=##class(Datatypes.Container).%New()
TESTNAMESPACE>set instance.DOB=+$H
TESTNAMESPACE>write instance.DOBToMyDate()
30/10/2014