![]() |
|
Published 1999-11-23 Printer-friendly version
Having dealt with the FileManager and
RelationManager classes the final part of the file
system I need to deal with is the ViewManager. It
could be argued that having three different objects dealing with
files is a little excessive. In a sense this is true; logically the
ViewManager brings fairly little to the table over a
RelationManager (it "just" deals with a bunch of
files). However, the ViewManager does handle the very
important special case where a bunch of files are being used to
retrieve a series of records (including child lookups) in a
nominated sequence. This was so vital I felt it deserved a special
object. It turns out that the ViewManager is almost
never used as a ViewManager, but it is the base
class used for many of the higher level data access objects.
The ViewManager is to a View structure
what a FileManager is to a File
structure. Specifically, the ViewManager is there to
act as an OOP front end to the view. It is also there to provide
some logical sophistication not present in the native view. The
main logical elements brought to the table are:
ViewManager is to support multiple sort
orders (simply).ViewManager provides for multiple filters
active at once.ViewManager watches the
commands being sent to the view structure and translates these
commands (where required) into a more intelligent sequence.The initialisation section of the ViewManager is
quite large, and it also offends the OOP purists as it contains
state; the order in which the methods are called is significant.
These two are tied together. A significant procedure (say five
browses and 15 drop combos) will contain 20 view initialisation
sequences and possibly 100 sort order creation sequences. We felt
that having these sequences concise, readable and efficient was
more important than sheer hygiene.
The call sequence is:
Init
[ Repeat 1 or more times
AddSortOrder
AppendOrder ! Optional
AddRange ! Optional
]
UseView
AddRange PROCEDURE(*? Field)
AddRange PROCEDURE(*? Field,*? Limit)
AddRange PROCEDURE(*? Field,*? Low,*? High)
The three AddRange methods set a range limit upon
the current sort order (defined by the preceding
AddSortOrder or SetOrder). They
correspond to Current Value, Single Value and High/Low Value range
limits respectively. The code for each is similar. The aim of the
code is to produce a RangeLimit queue with a queue record defined
for each element of the key that is range limited.
The first line of code sets the type of the range limit. The
second calls LimitMajorComponents. It is defined in
ABC that if the range-limited field of a sort order is not the most
major component then all the more major components are implicitly
current-value limited irrespective of the limit-type of the more
minor component. The call to LimitMajorComponents
implements this detail.
The field(s) passed in is then added to the range-limit queue.
Finally SetFreeElement is called to compute the free
element of the sort order.
AddRange PROCEDURE(*? Field,RelationManagerMyFile,RelationManager RelatedFile)
The purpose of this AddRange is the same as the
other three, but the code is somewhat different because the
information required to construct the range limit is not publicly
available (it is hidden in the RelationManager).
Essentially if two files are related by keys with, say, three
components, then a FileLimit range limit corresponds
to a single-value limit upon three different elements! The queue is
thus filled in using the ListLinkingFields capability
of the RelationManager.
AddSortOrder PROCEDURE(<KEY K>),BYTE,PROC
The AddSortOrder method actually performs the
action of creating a new logical sort order. Each logical sort
order can have a different range limit (and thus free element), and
a different filter. This is stored in a queue (or
Order queue) with a record pertaining to each sort
order.
This method clears the queue record (it has to as it contains an
ANY), stores the key, creates a new range-limit list
and uses the first component of the key as the free element.
The sort order itself is computed by passing the key (if
present) as a comma delimited list of components to the
SetOrder method.
As it is impossible to remove sort orders it is okay to return
the record number of the queue record added as the "unique
identifier" of the sort order within the queue (for later use by
SetOrder).
AppendOrder PROCEDURE(STRING Order)
Each time I look at the AddSortOrder/AppendOrder
relationship I oscillate between deciding it is a beautifully
elegant engineering solution and fretting that it is a complete
hack. The issue is this: Proper database theory will tell
you that sort orders should be defined in terms of fields and
ascending/descending flags so that the logic of the program is nice
and clean, and the ugliness of physical database design and
actually getting performance out of the system can be left to some
other poor schmuck.
The problem is that with many programmers coding furiously and requiring many different sort orders, the "poor schmuck" (typically the DBA) actually may not be able to get the system to perform at all! Worse yet he may be sufficiently senior that you can't boss him around to make sure your program runs ok.
Thus the programmers have to get pragmatic and they start building keys into their program logic. Performance improves greatly; unfortunately applications have to use a restricted set of sort sequences or the number of keys explodes.
So along comes ABC. What do we do, "restrictive" or "slow"? Legacy took the restrictive approach, which I didn't want, but the alternative wasn't that nice either. What we settled on in ABC was an interface that can be used in a fully, logically pure way but that encourages the writing of efficient queries. It does this by defining a sort order in two parts. The first (optional) part is a key which defines the main part of the sort sequence. The second (also optional) part is the fields used to sort duplicates within the first key. Combining this with some special technology within the view driver we have the panacea of full flexibility that is usually fast.
To put some meat on the bones; assume you have an invoice file
containing a customer id, and an invoice date (amongst other
things). There is a key on customerid. You want to
list invoices in customer order with invoices for a customer listed
in date order. You do an AddSortOrder(CustomerKey) and
then an AppendOrder('INV:Dateordered'). The view
driver will then read in the records for the first customer, sort
them, then the second customer etc. Given that sorting is at least
an NlogN process this "bucket sorting" can produce a massive time
savings.
An additional tweak that should be available by C6 is that AppendOrder may start with a "*" character, meaning the specified order replaces the order specified by the key after and including the free element. This is useful for specifying range-limit information using a key but then ignoring any trailing key components.
Init PROCEDURE(VIEW V,RelationManager
RM,<SortOrder SO>)
Much of the init code is straightforward; note though that the
order property may be set up in one of two ways. If passed in then
that version is used, otherwise one is created. This allows a
derived class to construct a larger queue (more fields) than the
ViewManager requires while still having the
ViewManager perform the administration required.
The order queue is central to the whole ViewManager
operation and stores all the information pertaining to a given sort
order. When the ViewManager is derived by a browse it
stores all the information for one tab of the browse.
Note also the default values provided to the view driver to enable the SQL buffering technology.
The Init method also ensures the UseView method is
called, for reasons I'll discuss under that method name.
Kill PROCEDURE,VIRTUAL
This is one of those messy little procedures that only really exists to ensure that there aren't any memory leaks.
Essentially Kill just loops through the records of
the order queue, and for each element of that queue it cycles
through each element of the filter queue freeing up each filter
element. Then it disposes of the filter and order-clause queue and
nulls out the free element (which is an any).
Finally if the order queue needs freeing (meaning it wasn't passed in from outside) then it's disposed of. Finally the order queue is freed.
UseView PROCEDURE,PROTECTED
UseView has a simple task, to call
UseFile on all the files a view references.
(UseFile technology is explained fully in my article
on the FileManager
class.) The requirement of calling UseFile is a little
subtle.
Logically when the template uses a view (or a browse) it only
knows about the primary file: the lookups are an implicit part of
view functionality. But the implementation of the VIEW (within the
Clarion language) has one or two legacy throwbacks. One of these is
that the files have to be opened independently before the view will
work. So the ViewManager "dresses" the view structure
to tidy this up, making sure all of the underlying files have been
used at an ABC level and are therefore likely to be open, thus
satisfying the view.
A list of file references is available from the view driver
itself. The FileManager reference is obtained from the
internal lists so that UseFile can be called.
That takes care of the initialization and cleanup code. In Part II I'll look at the
ViewManager methods used in typical browse
operations.
David Bayliss is a Systems Architect for The TopSpeed Development Center. He has worked upon TopSpeed's compiler and was the chief architect of the Application Builder Classes.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $159
(reg $189, save $30)
(includes all back issues since '99)
Renewals from $109
Two years: $249
(reg $289, save $40)
Renewals from $199