In the Presentation Layer, developers create (and modify) queries against a Model. This allows the client (UI, usually the Presenter or View) to filter, sort and otherwise manipulate the data contained in the Model without necessarily having an impact on how that data was populated. This separation of queries (and data) is an important element of the OERA. It is also necessary for the client to be able to pass query definitions from the client to the server in order to populate the client data set.
The reference components provide a mechanism for this in two parts: a query definition mechanism, which allows for the storage of the query definition, and a query manipulation mechanism.
The QueryDefinition object contains meta-data about a single query: buffers, joins (between those buffers), filters (non-buffer), sorting and the like. It allows the use of named buffers. The QueryDefinition does not perform any operations on the query itself (such as open, close or reposition); these actions are left to the Query object.
This object exists mainly because having an object allows us to extend or modify the kinds of data associated with a query without having to change any of the parameters. This is similar to the intent of using EventArgs. We can also change the implementation of the (say) buffers without any external changes (to use objects instead of strings, for instance).
The QueryDefinition object implements the IQueryDefinition interface. The QueryDefinition class and IQueryDefinition interface can be found in the OpenEdge.Core.System package.
The QueryDefinition object implements OpenEdge.Base.Interfaces.ISerializable, and so can be serialized to and from a LONGCHAR, which can be passed across session boundaries (ie an AppServer) if need be.
The IQueryDefinition object defines a single event to allow objects that use it to react to any changes in the definition.
|QueryDefinitionChanged||void (IQueryDefinition, QueryDefinitionEventArgs)||Fires whenever any of the the query definition elements change. Note that the QueryDefinition doesn't do anything based on whether it's changed or not, it merely reports the fact to its Subscribers.|
Query and IQuery
The Query object contains a single QueryDefinition object, which it uses to define the query. The Query object creates and operates on an ABL query. The Query object has a number of properties that are restricted to having a PUBLIC GET, and a restricted set (protected or private accessors). These properties are either derived from the ABL query or are set via the constructor (or derived therein).
|QueryHandle||Handle||The query handle is exposed since it is used by a ProBindingSource.|
|QueryDefinition||OpenEdge.Core.System.IQueryDefinition||A Query only has one QueryDefinition associated with it. Once it's created, we can manipulate to our hearts' content, but the object instance stays the same. Note that the Query object subscribes to the QueryDefinitionChanged event.|
|NumRows||integer||Number of result rows; derived from the underlying query|
|CurrentPosition||integer||The current ordinal position; derived from the underlying query|
|RowPosition||OpenEdge.Core.System.RowPositionEnum||Relative position of current row|
|TableOwner||OpenEdge.Core.System.ITableOwner||Defines where the Query object can call to get a handle to the tables which will be used to create buffers for the query|
The Query methods are mostly wrappers around corresponding ABL query methods. The following lists some more specialized Query methods:
|GetBufferTableName(char:BufferName)||character||Returns the physical table name of the query buffer|
|GetCurrentTableKey||character||Returns the current key (expression) that corresponds to a particular buffer|
|GetCurrentRowKey||character extent||Returns the current row key as an array with an extent for each buffer in the query. It is not defined as a property because it is used as a whole, typically passed into other APIs that needs the full key. More info at OERI Query Result Row Identification|
The Query object has two constructors.
|handle:QueryHandle, IQueryDefinition||If an ABL query has already been created, the handle of the query should be passed in together with the QueryDefinition. In this case, the buffers associated with the query are considered fixed. In addition, when the Query object is destroyed, the QueryHandle is left alone.|
|ITableOwner, ITableDefinition||The ITableOwner argument defines where the Query object can call to get a handle to the tables which will be used to create buffers for the query.|
Query Result Row Identification
Rows in a query can be uniquely identified by means of a Row Key Array, as described here.
The fact that the CurrentRowKey array order is defined by the actual buffer order in the query makes the key volatile. The buffer order of a query can change. It is thus not unlikely that there are cases that uses the key to reposition that might allow the buffer orders to change after the get and before the reposition. The key is currently used to set position for an appending batch, but in that case the buffer order cannot change between the requests.
One solution to this problem is to flatten the RowKey into a single character field. This can be done without loosing any information by adding table qualifiers to the field references. The drawback is that some parsing is required, but it is consistent with how field references are managed elsewhere, for example in databinding where qualifed fields are used when the query/resultlist have multiple buffers.
The ITableOwner interface is implemented by the class that defines a table handle from which to create buffers for the query.