The idea of a Value in InfinityDB corresponds not to a suffix of a single Item but to all the suffixes of a set of Items having a common prefix. Such a set of suffixes is called a subspace, and there are very many situations where subspaces are important.
In the case of the the Entity-Attribute-Value model, the common prefix of a Value is comprised of the EntityClass component, the Entity components, and the Attribute component. However, any common prefix can be used.
Each of these 'complex' Value structures can be added to an existing database on the fly for backwards compatability of new application versions with existing databases. Sometimes older applications can work with the extended structure as well.
EntityClass PERSON = new EntityClass(0); // 0 is the unique id for people Attribute CREDIT_CARD = new Attribute(29); //29 is the unique id for people .. Cu cu = Cu.alloc().append(PERSON).append(732) .append(CREDIT_CARD).append(9193013132); db.insert(cu);
Above, the EntityClass (like a Relation Table) is PERSON, the Entity (like a Relational primary Key) is 732, the Attribute (like a Relational column) is CREDIT_CARD, and the Value (like a Relational column value) is 9193013132. So far, the semantics map to a single value in the relational model.
However, if person 732 gets more credit cards, we just insert one Item for each new card, changing only the Value part, and we have a multi-value Attribute. So, it is simple to convert a single-value Attribute into a multi-value, i.e. set-valued Attribute at runtime, providing compatability with existing databases. Older applications will usually not be compatible with the altered databases, but new applications can extend and use existing databases.
Now in order to enumerate the cards, we use a prefix of everything up to but not including the Value:
cu.clear().append(PERSON).append(personId).append(CREDIT_CARD);
int protectedPrefixLength = cu.length();
while (db.next(cu, protectedPrefixLength)) {
System.out.println(cu.longAt(protectedPrefixLength));
}
The above code is typical for enumerating the suffixes of
any set of Items. The multiple Values can be considered
a set, and there are many operations that can work
with generic sets of suffixes.
Multi-value Attributes are important in
Inversion.
cu.clear().append(PERSON).append(personId).append(CREDIT_CARD)
.append(9193013132).append("MC");
The Value is now two components long: 9193013132 "MC". The "MC" could be put first if desired.
This composite Attribute can be created and stored into an existing database having only the simple numeric credit card numbers, so the number collision problem can be overcome even if it is discovered after databases have been created. Enhanced application code can watch for the composite card-number/card-issuer Values and use the card-type info as it is available:
cu.clear().append(PERSON).append(personId).append(CREDIT_CARD);
int offsetOfCreditCard = cu.length();
while (db.next(cu, offsetOfCreditCard)) {
long creditCard = cu.longAt(offsetOfCreditCard);
int offsetAfterCreditCard = cu.skipComponent(offsetOfCreditCard);
String issuer = "MC"; // default in older db's
if (offsetAfterCreditCard < cu.length()) { // got a "MC"/"Visa" component
issuer = cu.stringAt(offsetAfterCreditCard);
}
System.out.println("personId: " + personId +
" card: " + creditCard +
" issuer" + issuer);
}
Storing these new string components is easy: just append the Strings to the Cu in place of the usual long component as the Value before inserting. For retrieval:
cu.clear().append(SUPPLIER).append(supplierId).append(S_PART_NO);
int offsetOfPartNo = cu.length();
while (db.next(cu, offsetOfPartNo)) {
Object partId = cu.componentAt(offsetOfPartNo);
System.out.println("supplerId: " + supplierId +
" partNo: " + partNo);
// output for numeric partNo's is like
// supplierId: 183 partNo: 99311
// output for string partNo's is like:
// supplierId: 183 partNo: "93-A-00015"
}
Above we have simply replaced the cu.longAt(offsetOfPartNo)
with cu.componentAt(offsetOfPartNo). This method
is completely generic, and will parse out any component
at the given offset. If the component is a long or other
primitive, the result will be a primitive wrapper, e.g. Long.
If the component is an Attribute, an instance of Attribute is returned.
This generic form is, of course, not type-safe to the invoker,
and will require instanceof and casting in some situations,
so the strongly-typed methods like cu.longAt(int offset)
are usually preferred. The Objects returned can also be
directly appended to a Cu: the Cu.append(Object) will append
the primitive wrappers with the appropriate primitive component types.
There is additional Object construction, however,
while longAt(offset) does none.