[Nectar-devel] Query and Record overhaul...
Status: Alpha
Brought to you by:
skander
|
From: Kai S. <sch...@cs...> - 2005-05-03 15:43:36
|
hey,
I'm in the middle of a pretty massive Query and Record overhaul... Here
are the main changes:
- Record classes no longer define table names explicitly. The
SQLDataAdapter infers them from the Record's class (ie, table in SQL
will be 'nectar_record_PersonRecord', instead of 'person')
- the Query class is now abstract, and can be instantiated through
RecordQuery and ArbitraryQuery. RecordQuery is much more restrictive,
and only allows queries on Records, while ArbitraryQuery is much more
loose and can query any table and field. Actions will only be allowed to
execute RecordQuery's, and while Services may do either, but really
should only execute ArbitraryQuery's.
- A Record's field can now be referenced through a static final
Record.RecordField object. One can use this straight in the definition
of Query objects as a query field, as these RecordFields know which
Class they belong to, even in a long hierarchy. Data loading and
unloading from Record objects is done by Java reflection on these static
RecordFields.
- RecordDataElement classes in nectar.record.datatypes have been cut
down from around 20 to 5: RecordInteger, RecordString, RecordSet,
RecordBoolean and RecordDate. Validation of these RecordDataElements
will be done more generically with constructs of a DataElementCondition
class, which will define ranges, etc for a specific Record's field.
- the number of Query methods will grow greatly, to provide many many
shortcuts. What was previously:
Query q = new Query(new nectar.record.RecordDescriptor(project,
ForumRecord.TABLE, owner));
q.addDataCondition(DataConditions.AND, new OperatorCondition(new
FieldElement(ForumRecord.TABLE, ForumRecord.FIELD_CATEGORY,
OperatorCondition.EQUAL, new ValueElement(category)));
q.addPostElement(new OrderByPostElement(new
FieldElement(ForumRecord.TABLE, ForumRecord.FIELD_FORUM_NAME)));
List rows =
nectar.ServicesUtil.getDataAdapter().readSingleFieldMultipleRow(q);
will now be:
RecordQuery q = new RecordQuery();
q.addOp(ForumRecord.OWNER, owner);
q.addOp(ForumRecord.CATEGORY, category);
q.addOrderBy(ForumRecord.FORUM_NAME);
RecordList list = query(q);
Less typing, same funcationality! (if this is too much shortcuts for
you, read at the bottom of this mail for a full explanation)
These changes pave the way to making an internal OODBMS possible, which
would have been very difficult with the current Query / Record API. It
also should simplify auto-generation of Record class code from XML
pre-definitions. The changes mainly focus on ease of use, and magically
simplify both the Record class code as well as using the Query code.
Much of the complexity has been pushed to the MySQLAdapter, while the
Query object itself has only grown in it's number of methods, it hasn't
fattened up in actual code.
While I'm not looking forward to modifying existing package code EVEN
MORE, this will definetly make Query writing and Record handling easier,
while making some parts safer, and others finally functional (inherited
records!!).
The subversion revision implementing these changes will be committed
within a day or two...
-kai
---------------
For the afficionados here's a more explicit version of the above example
code without the shortcuts to the shortcuts:
RecordQuery q = new RecordQuery();
q.addOperatorCondition(Query.Binary.AND, ForumRecord.OWNER,
Query.Operator.EQUAL, owner);
q.addOperatorCondition(Query.Binary.AND, ForumRecord.CATEGORY,
Query.Operator.EQUAL, category);
q.addOrderBy(Query.Order.ASCENDING, ForumRecord.FORUM_NAME);
RecordList list = query(q);
Q: How does this Query know which project I'm running?
A: This piece of sample code is what would be written within an Action.
the 'query(q)' method passes your Query to the Action superclass. This
superclass knows which project your Action implementation is tied to,
and will add or forcefully replace the project ID number for any record
you are querying.
Q: How does this Query know which tables I'm querying?
A: Forget about tables. It is true that when you're developping Record
classes, you have to think about organizing your data in sections as if
they where in different tables (Record Classes) for efficiency purposes.
The RecordQuery, and how it executes in an Action makes a point of
blurring the line between tables. The DataAdapter will automatically
figure out how to join SQL tables over their ID numbers. Stop thinking
about tables, start thinking about Records, in the way that Java thinks
of Object from a variety of Classes.
Q: How do I do a Query that spans multiple Records?
A: Let's say you want to execute a Query for ForumPostRecord's in all
ForumRecord's that were created by a certain PersonRecord whose ID
number is a variable personId.
RecordQuery q = new RecordQuery();
q.addOp(ForumPostRecord.class); // set the type of Record you want to
retrieve.
q.addOp(ForumPostRecord.OWNER, ForumRecord.RECORD_ID); // link owner
ID to a record
OperatorCondition oc = new OperatorConditon(PersonRecord.RECORD_ID,
personId); // find PersonRecord with the given ID
q.addOp(ForumPostRecord.POSTER, oc); // link personId to the POSTER
field.
Q: How do I run queries over multiple types of Records?
A: Queries can be executed over Records that have been inherited, and
the query's result will allow you to get a mixed set. For example,
consider your project has AdminPersonRecord's and ClientPersonRecord's,
that both inherit from the PersonRecord. To make it simple, Clients have
a credit card number field, and Admin's have a mailbox number field,
where they payment checks go to. Your action wants to query all
PersonRecord's for a login name, All you have to do is run your query on
the PersonRecord instead of the ClientPersonRecord or the AdminPersonRecord:
q.addOp(PersonRecord.LOGIN, searchLogin);
RecordList list = executeQuery(q);
The RecordList object, once you load them fully with the loadRecords()
method or the like, will give you a heterogenous Collection of Records:
Collection<Record> records = loadRecords(list);
for (Record r : records) {
if (r instanceof (ClientPersonRecord)) {
[...]
} else if (r instanceof (AdminPersonRecord)) {
[...]
}
}
|