ODL Script comments
The sample schema demonstrates different ways of defining schema elements. In order to define persistent type information, complex data types have to defined as CLASS rather than as STRUCT . In order to distinguish better between keywords and names, we used capital letters for keywords in the scripts. Keywords can be written in lower and upper case letters. More details, you will find in OSI - ODABA Script Interface .
The example defines three global and one local complex data type. Person , Car and Company are global types. The sequence of definition is not important since the semantic check is performed after loading the schema updates for the complete schema stored in the dictionary.
The dictionary location might be passed as parameter when calling ODL or within the script. Usually, it is more flexible passing the dictionary location as parameter when calling ODL. For the example, we used the internal specification for the dictionary as shown below.
In order to get proper error messages, it is suggested to call ODL with an ini-file, that refers to the system dictionary in the system section. Then, one may refer to the dictionary in the script of by defining a DATASOURCE in the script, which refers to a section in the ini-file.
DICTIONARY = 'Sample/Sample.dev'; // sample resources
When defining a scheme, one may update ( UPDATE ) the schema or create a completely new one ( NEW ). We always suggest using UPDATE , since NEW would delete all project resources completely (type definitions but also implemented classes and functions), which is not always desired. The name for the schema is turned internally into a project name. Thus, one may define a number of schemata within a dictionary.
ODABA supports namespaces. Each project is a module, which is a namespace. In order to define an active namespace, i.e. a namespace that really uses its own names for types and extents, the namespace (module or project) has to be marked as ACTIVE . Using active namespaces for persistent data may cause a lot of trouble, since we suggest using active namespaces only, when those are urgently required.
Adding other SCHEMA , MODULE or NAMESPACE statements allow you creating project, module or namespace hierarchies of any level.
UPDATE SCHEMA Sample {
....
The schema definition in ODL corresponds to the project definition in ODE, i.e. each schema project is a schema definition, but contains additional development resource objects.
Defining a class supports several extensions as inheritance specifications ( EXTENDS ), key definitions ( KEY ) or extent definitions ( EXTENT ).
Keys are defined in order to be used in index defintions for any type of collections, later on. One key may be marked as IDENT_KEY (primary key), which is usually used as preferred key when defining multiple indexes for a collection. Keys as such do not influence database performance but are just structure types defining a projection from the class instance to the key instance. Thus one may define as many keys as required. Keys are context dependent and can be defined in the context of a class. Referring to a key as a structure always requires a scoped name, except you are in the context of the class.
Extent specifications are supported in the context of a class definition. When defining an extent in the scope of a class, it becomes element of the class namespace. Thus, referring to such extents requires scoped name, except you are in the context of the class.
SET<Person> &pers_collection = Person::Persons; // OSI
CLASS Person PERSISTENT
( KEY { IDENT_KEY pk (pid); sk (name); };
EXTENT MULTIPLE_KEY owner Persons ORDERED_BY (pk UNIQUE NOT_EMPTY, sk); )
{
.....
};
ODABA supports two different conceptual types of inheritance. One is exclusive inheritance, which is that what programmers expect, when inheriting from another type. In this case, the base type instance (e.g. Person in Employee ) becomes an integrated part of the extended instance ( Employee ). Shared inheritance, however, allows sharing an instance by different specializations, i.e. the same Person might be specialized to an Employee , but also to a Student or Patient .
ODABA supports multiple inheritance, which is introduced by colon ':' or the EXTENDS keyword:
CLASS Employee PERSISTENT : Person ...
CLASS Employee PERSISTENT EXTENDS Person ...
The two statements above are identical. Since base classes are considered as specific relationships, there are many additional options, which might be defined for inherited types. In the example, we defined a super-set for the base type, which causes ODABA to use shared inheritance. Practically, it means, that any time when creating a new Employee instance a new Person instance will be created as well or an existing Person instance with the required pid will be linked to the employee instance.
The number of Employee instances to be created for a Person instance depends on the cardinality defined for the inverse reference employee, which refers to all Employee instances extending the Person instance. In the example, the dimension is 1 , i.e. a Person may participate in only one employment relation.
CLASS Employee PERSISTENT : Person person BASED_ON Person::Persons INVERSE employee
( ..... )
{
.....
};
The example contains a local extent definition for Person and a global one for Company . In the example, Car does not has got an extent, since cars are of interest in the context of the Company , only. Since extents in ODABA define sort of global object collections rather than the collection of all instances of a given type, one may define any number of extents for a complex data type.
Even though one may define extents in the class context, which seems to be the most appropriate place for defining extents, we suggest defining extents in a global scope (see Company extent). In order to benefit from default setting provided by ODE tools, you should define an extent with the same name as the complex data type for each class that requires an extent definition.
Regardless whether an extent has been defined in local or global scope, the extent defines a global collection in an object space. Set relations as super-set/subset relation, intersect or union might be defined between extents, but this is not shown in the example.
// Global Company extent definition
EXTENT Company UPDATE MULTIPLE_KEY OWNER Company
ORDERED_BY ( pk UNIQUE NOT_EMPTY );
EXTENT Employee UPDATE MULTIPLE_KEY OWNER Employee
ORDERED_BY ( pk UNIQUE NOT_EMPTY );
In the example below, an extent Persons has been defined with two order keys (indexes). The one is ordered and unique ( LIST ), the other is ordered with duplicates (missing in ODMG 3.0 but sometimes called RANKING . It becomes obvious that being a list, bag, set or ranking is not property of the collection, but property of the index. Thus, within ODABA ODL one may use any of those keywords without difference.
CLASS Person PERSISTENT
( .....
EXTENT MULTIPLE_KEY owner Persons ORDERED_BY (pk UNIQUE NOT_EMPTY, sk); )
{
.....
};
ODABA ODL supports local data types as structures or enumerations, but no class definitions within class definitions. The Sample ODL contains a local enumeration and structure definition in the Person class. In order to refer to those definitions from outside the Person class, the type names have to be scoped.
// Person class definition
CLASS Person PERSISTENT ( ... )
{
ENUM Sex {
male = 1,
female = 2,
undefined = 0
};
STRUCT Address PERSISTENT {
STRING(6) zip;
STRING(40) city;
STRING(80) street;
STRING(6) number;
};
.....
};
Attribute definitions provide a number of extensions. Basic data types may get type qualifiers (e.g. for size and precision). Attributes may get array dimensions as in first_name (default is 1). In order to deny empty values, the attribute might be marked as NO_EMPTY , which denies storing Person instances, when the pid attribute is empty (blank). The attributes sex and married got initial values in order to initialize the attributes when creating an instance. By default attributes are initialized to empty values.
Initialization values are set when creating a new instance. This differs from the SOURCE option, that provides a rule to evaluate an attribute or reference value. Typically, sources are defined for transient ( TRANSIENT ) attributes or references. Since the source expression is evaluated when accessing the attribute or reference value explicitly (and not when reading the instance), this is the most efficient way of providing derived information.
CLASS Person PERSISTENT ( ... )
{
.....
ATTRIBUTE {
NOT_EMPTY CHAR(16) pid;
STRING(40) name;
STRING(40) first_name[3];
DATE birth_date;
Sex sex = male;
bool married = false;
TRANSIENT INT(3) age SOURCE( (Date() - birth_date)/365.25 );
};
.....
};
References in the example use an alternate way of defining class properties. Here, each element is explicitly preceded by the property type keyword REFERENCE . This is also possible for attributes and relationships. References define partitive relationships, i.e. the objects referenced are part of the object, i.e. they belong to the object and will be deleted when deleting the owning object instance ( Person ).
In the example there is a single reference to an Address object and another one to a STRING ( MEMO ) object. String references are always stored as MEMO fields with a maximum length. MEMO and BLOB references are conceptually attributes, but ODABA handles those technically as references. Thus, you cannot define MEMO or BLOB data type for an attribute.
CLASS Person PERSISTENT ( ... )
{
.....
REFERENCE Address location;
REFERENCE STRING notes[4000];
.....
};
BLOB types are not yet fully implemented, i.e. one may define them but you cannot access them in the current ODABA version.
Relationship in ODABA allow setting many generic business features and constraints. The cardinality of the collection can be restricted to a maximum number by setting a relationship dimension. The default is 1. For defining unlimited number of elements, one may use the collection keywords SET , BAG or LIST or pass 0 as dimension:
SET<Person> children BASED_ON Persons
Person children[0] BASED_ON Persons
Thus, the two statements above are identical. Whether a collection is a bag, list, set or ranking is not property of the collection but of the assigned indexes (see Collections and indices above).
An essential feature is the superset definition ( BASED_ON ) for relationships. Defining super-set relation ships allows linking objects from other collections to the current relationship. The database automatically maintains super-set relationships. Super-sets might refer to extents, but also to other relationships relative to the current instance. Thus, used_cars refers to the pool of cars owned by the company where the person is employed ( .company.cars ).
Inverse relationships are maintained by the system. They are optional, but it is suggested always using inverse relationships, which are necessary in order to maintain indexes for local collections when key component attributes change in a referenced instance. In order to avoid infinite loops when copying databases, one side of the relationship will be marked as SECONDARY . Secondary relationships are not copied when copying a database. When SECONDARY has not been set for any of both sides, the schema checker sets the option for one relationship in the relationship pair.
The NO_CREATE option for cars prevents you from creating company cars implicitly by adding a new car to an employee.
CLASS Person PERSISTENT ( ... ) {
.....
RELATIONSHIP {
SET<Person> children BASED_ON Persons
ORDERED_BY (pk UNIQUE)
INVERSE parents;
Person SECONDARY parents[2] BASED_ON Persons
INVERSE children;
Employee SECONDARY employee INVERSE person;
}; .....
};
CLASS Employee PERSISTENT ..... ( ..... ) {
.....
RELATIONSHIP {
Company SECONDARY company BASED_ON Company
ORDERED_BY (pk UNIQUE)
INVERSE employees;
Car NO_CREATE used_cars[2] BASED_ON .company.cars
ORDERED_BY (pk UNIQUE)
INVERSE users;
};
};
Since we are in the context of the Person class, there is no explanation why the dot is required in the super-set definition for cars ( .company.cars ). This is an old mistake when we started to use relative paths. We will remove this in one of the next versions (which will still accept the dot at the beginning but will not require it anymore). So, just write it and do not think so much about it.
Ownership is an important concept in ODABA. In an ODABA database, there is always exactly one collection, which is owner of an instance. When removing an instance from its owning collection, the instance will be deleted completely, which is not the case when removing an instance from a not owning collection.
Typically, extents are owning the instances ( OWNER ), at least as long as they do not have got a super-set. In most cases, one can say, that always the topmost super-set , the root collection, is the owner of its instances, i.e. collections, which do not have got a super-set must be marked as owning collections. But there is one exception. Secondary inverse relationships do not need e super-set when they are not owning. In this case, however, one cannot add instances directly to this relationship.
The cars relationship in the Company class has been defined as owning collection, since cars in the example are of interest only in the context of a company. Hence, there is no Car extent and all Car instances are owned by the one or the other Company . When deleting a Company instance, all Car instances will be deleted, too.
CLASS Company PERSISTENT ( ... ) {
.....
RELATIONSHIP {
.....
SET<Car> OWNER cars ORDERED_BY (ik_cid UNIQUE)
INVERSE company;
};
};
// Company extent definition
EXTENT Company UPDATE MULTIPLE_KEY OWNER Company
ORDERED_BY ( ik_name UNIQUE NOT_EMPTY );