Symbolic Logic:Programming:ORM Attributes

From Knowino
Jump to: navigation, search

The heart of an Object to Relational Mapping (ORM) is the implementation of Attributes.

Attributes map to columns in the database. Using Renaming Inheritance all of the functionality for implementing an attribute is implemented in one class, and shared out for use by the ORM Persistence classes.

Once the Attribute class has done its job all the ORM has to do is combine the results.

Contents

[edit] Attributes

To add an attribute to a Persistent object, inherit from the Attribute class like,

inherit Attribute("name", String) as Name;

for example to create a persistent class with attributes name, age, sex,

class Person
{
inherit Persist("person", Person)
inherit Attribute("name", String) as Name;
inherit Attribute("age", long) as Age;
inherit Attribute("sex", EnumSex) as Sex;
List(PersistBase) GetInheritingClasses()
{
return [new Person, new Lawyer];
}
}

A class inheriting from Person automatically inherits all the attributes of the person. For example,

class Lawyer
{
inherit Persist("lawyer", Lawyer)
inherit Person
inherit Attribute("title", String) as Title;
List(PersistBase) GetInheritingClasses()
{
return [new Lawyer];
}
}

When a database upgrade is requested the "lawyer" table will automatically be updated to include all the columns of the Person, as well as the attributes of Lawyer.

[edit] Accessing the attribute value

An attribute value changes with time. This is discussed in Applied Constraint Logic Programming.

The functions Get and Set may be defined as,

class Attribute(attributeName, type)
{
inherit PersistBase;
private:
TimeList(type) m_ValueList
protected:
// Get the attribute value (at a particular time).
type Get(role Time transactionalTime) rename as Get%
{
precondition !IsQueryTemplate();
return m_ValueList.Find(transactionTime);
}
// Set the attribute value (at a particular time).
type Set(type newValue, role Time transactionalTime) rename as Get%;
{
precondition !IsQueryTemplate();
m_ValueList.Add(transactionTime, newValue);
OnChange();
}
virtual Boolean Validate(role Time transactionalTime) rename as Validate {};
virtual void OnChange(type oldValue, type, newValue, role Time transactionalTime) rename as OnChange% {};
}

The above Get and Set functions are run only if Persist.CreateObject is used to create the object. This makes IsQueryTemplate() return true, which satisfies the precondition.

The transactionTime is the time at which the transaction occurred. Using roles it is not necessary to explicitly pass this time to each call.

The TimeList implementation may be expensive in performance. However as long as certain restrictions are allowed on the treatment of times it is not actually necessary to record the full list. Only the current and the original value are likely to be accessed. This functionality can all be hidden inside the TimeList service.

The IsDirty function plays says if the attribute value has changed since being retrieved from the database. The combine operator || is used to create an IsDirty function in the inheriting class that ors together the results of IsDirty on each attribute. This calculates if the row has been modified since being retrieved from the database.

[edit] Selection of Records

A select query on an object looks like,

List(Person) oldies = Person.Select(x : x.GetAge() > 55);

The Select statement creates SQL which retrieves rows from the database table. To implement this the expression x.GetAge() > 55 returns a WhereCondition. The Select function uses the WhereCondition and other information from object to generate SQL like,

SELECT id, timestamp, update_user,person, name, age, sex
FROM age = 55

Another example is,

Person p = new Person();
List(Person) sociablePetOwners = Person.Select(x : x.IsSociable() and x.GetPetList().Count() > 0);

IsSociable is a function like,

any IsSociable()
{
return GetFriendList().Count() > 20;
}

A function like IsSociable has a dual interpretation,

The keyword "any" supports this dual meaning by allowing us not to define the return type of the function. Instead the compiler must decide the return type from the context.

[edit] Implementation

Select is implemented in the PersistBase class as,

class PersistBase
{
// Get a comma separate string of field names.
abstract String GetAttributeNames();
}

The attribute class has the following code for supporting selection,

class Attribute(String attributeName, class type)
{
public:
String GetAttributeName() rename as GetAttributeNames combine CommaCombine;
{
return attributeName;
}
protected:
void Read(DBRow row)
{
Set(row.GetField(attributeName));
}
}

GetAttributeNames is used to retrieve a list of attribute names separated by commas. CommaCombine is defined in the PersistBase class.

Read is used to read a row of data from the query result set into a business object.

[edit] Construction of where conditions to retrieve objects

The Get function is overloaded in the attribute class,

class Attribute(String attributeName, class type)
{
// Retrieve the table attribute name for use in constructring SQL queries.
StringField(type) Get() rename as Get%
{
precondition IsQueryTemplate();
return new StringField(type, this, attributeName);
}
}

The precondition IsQueryTemplate() enables the above implementation of the function only when the object is constructed using the Person.CreateTemplate() method. The Get method for retrieving attribute values is enabled when the object is created using Person.CreateObject().

The two Get functions have different signatures. Both signature are matched by the GetAge call, because the transactionTime is a role. Each call to GetAge has code to invoke both methods, but which method is invoked depends on the IsQueryTemplate() function.

[edit] Order by

TODO

Somebody must have thought of a really good way of building up order by conditions.

I want the duality of being able to create the nested inequalities, and also generating the order by condition.

[edit] Saving

class Attribute(String attributeName, class type)
{
Boolean IsDirty(role Time transactionalTime) rename as IsDirty combine ||;
{
return m_ValueList.OriginalValue() != transactionalTime;
}
void Bind(DBBind bind)
{
bind.SetParameter(GetAttributeName(), Get());
}
}

[edit] Upgrading the Database

For the table the Attribute class needs to be able to add or modify a column.

class Attribute(String attributeName, class type, bool nullable=true, long stringLength=2000)
{
protected:
void UpgradeColumn(DBTable table, role Time currentTime) rename as UpgradeColumns
{
if (not table.ColumnExists(GetAttributeName())
{
table.AddColumn(GetAttributeName(), type.GetDatabaseTypeName(), nullable, length);
}
else
{
DBColumn column = table.GetColumn(GetAttributeName());
if (column.GetType() != type.GetDatabaseTypeName())
{
column.SetType(type.GetDatabaseTypeName());
}
if (column.GetNullable() != nullable)
{
column.SetNullable(nullable);
}
if (column.GetLength() != length)
{
column.SetLength(length);
}
}
}
}

[edit] Links

Personal tools
Variants
Actions
Navigation
Community
Toolbox