Queries
Intro
How
to query a set
Building
and using query specifications
Stroking
the query optimizer
Types,
polymorphism, and queries
Query
methods generated by PTXX
Sample
queries
A query creates a set of objects which meet certain conditions. For instance, if you have a database which contains shapes, you may want to find all of the grey circles. The following diagram illustrates the three main components of this query. The CircleAllSet is the set which is queried. Only the objects in this set will be examined. The CircleQuery tells us which circles we are interested in - in this case, the grey circles. The CircleSet is the result set; the query fills the result set with references to all the circles in the CircleAllSet which meet the conditions stated in the CircleQuery.
![]() |
POET now has many ways of querying the database. This chapter describes queries, and the next two chapters cover filters and OQL. Filters are useful when you want to retrieve your results one at a time. OQL provides a very good query language which is standardized by the Object Database Management Group. In addition, the Generic Interfaces Guide contains a section on generic queries, which can be used when you have no advance knowledge of the classes in a database. There are a few other functions which also have similar functionality; if speed is essential and you just want to find an object based on its value or its identity, you may want to look at PtAllSet::FindKey() or PtObjectSet::Find(); if you just want to set a sort order without actually sorting, you may want to look at PtAllSet ::SortByIndex().
In POET, every query is done by asking a set to query itself. In the previous example, the query would be done by asking the CircleAllSet to examine itself to see if it contains any grey circles, and the result set would hold all grey circles.
Like all POET sets, CircleAllSet has a function called Query() which actually performs the query - in our example we did the query by calling CircleAllSet:: Query(). This function takes two parameters, the address of the query specification and the address of the result set. The first parameter, the query specification, is a CircleQuery object. The CircleQuery class is generated by PTXX, which always creates a query specification class for each persistent class. It does this because the semantics of a query depend on the attributes of the class. In our example, we are looking for grey circles; we can do this only if circles have a data member for color which can contain the value grey. If the class Circle has a member called Color which can contain the value GREY, then CircleQuery contains a member function called CircleQuery:: SetColor(), and you can specify the condition like this:
In a good class design, Color is probably a member in the Shape class which is inherited by the Circle class. Therefore, PTXX derives CircleQuery from ShapeQuery to allow you to specify conditions for those data members which Circle inherits from Shape.
The second parameter is the result set. PTXX does not automatically generate a result set type for each persistent class. Use a typedef in your .HCD file to create result set types for your queries. For instance, in our example, we may want an ondemand set of circles to hold the result of our query. We can declare this type after our Circle class declaration like this:
We strongly recommend that you use ondemand sets for result sets - POET can place an ondemand reference into a set much faster than it can build objects, so queries using ondemand result sets are significantly faster. Once we have declared our result set type and run PTXX, we can create a CircleSet in our program using the type declared by the typedef. Here is the code for the query in our example:
If Circle has a Display() function, we can display the contents of the result set like this:
Intro
Conditions
Boolean
Operators
Precedence
Sorting
the result set
Clearing
a query
Query specifications can be used to formulate queries which are arbitrarily complex. For instance, you may want to find all grey circles created in 1994 with a radius between one and three whose coordinates lie in a certain range which are part of a particular diagram. The rest of this chapter will show you how to formulate these kinds of queries based on the structure of your class declarations and references, but to understand this discussion, you must first know the basic building blocks of a query specification: conditions, boolean operators, precedence, and sorting conditions. These are each discussed in this section.
In our Circle example, we said that we are looking for grey circles using CircleQuery:: SetColor() like this:
Every call to SetColor() specifies one condition for the color of the circle. We just specified circles whose color is equal to grey. We could also specify circles whose color is not equal to grey by using the PtNEQ comparision operator, which stands for "is not equal to":
A condition consists of a value and a comparison operator, and the parameters for SetColor() allow you to specify each. The default comparison operator is PtEQ, which means "is equal to", so POET tests for equality if you do not specify a comparison operator.
The following comparison operators are available:
| PtEQ | Equal to |
| PtLT | Less than |
| PtGT | Greater than |
| PtGTE | Greater than or equal to |
| PtLTE | Less than or equal to |
| PtNEQ | Not equal to |
Conditions are grouped into expressions using boolean operators. The function PtQuery:: SetBoolOp() allows you to specify a boolean operator - every query specification class is derived from PtQuery and has this function. For instance, if you want circles which are grey or red, you could use the following expression:
The following boolean operators are available:
| PtAnd | and |
| PtOR | or |
| PtXOR | exclusive or |
| PtNOT | not |
| PtNAND | not and |
| PtNXOR | not exclusive or |
If two conditions are set without explicitly stating a boolean operator, POET combines them using a PtAND.
POET allows you to specify the precedence for your expressions. This is sometimes essential to get the result you expect. Suppose you want to find the circles whose radius is greater than two and less than four, or which are blue. We will apply this query to the following table:
| Color | Radius |
| RED | 5 |
| GREY | 2 |
| BLUE | 1 |
| GREEN | 10 |
There are two ways to interpret the query. The first way, which is probably what you are expecting, is to interpret it like this:
If the query is interpreted this way, it first looks for circles whose radiuses are between two and four. There are no circles with a radius in this range. Next, the query looks for blue circles, and adds these to the result set. One object is found, the blue circle.
However, the query could also be interpreted like this:
If the query is interpreted like this, it starts by looking at the circles which have a radius less than four or which are blue. Both the grey and the blue circles satisfy these conditions. Next, the query restricts these circles to those which have a radius greater than two. Neither circle satisfies this condition. You see, then, that the precedence affects the results of a query. POET allows you to set precedence using PtQuery::OpenBracket(), which is equivalent to a left parenthesis in the previous examples, and PtQuery::CloseBracket(), which is equivalent to a right parenthesis. By default, POET uses the same precedence rules as C++, which means that PtAND has a higher precedence than PtOR, and POET would use the precedence shown in the first interpretation. If you want POET to use the second interpretation, you can specify the precedence like this:
You can specify the sort order of the result set using the SortByMember() functions generated by PTXX. You can sort on multiple members - the sort orders nest from left to right. For instance, if you want to sort a set of people by last name and then by first name (as in a telephone book) then you would add these two lines:
In the current version of POET you must specify sort orders before setting any conditions for the query. Enclose the rest of the query in brackets:
Incidentally, if you just want to use a different sort order for an AllSet, do not do this with a query. PtAllSet::SortByIndex() is much more efficient.
Before you use a query specification, you should clear it using PtQuery::Clear(). If you do not do this, POET continues to accumulate your conditions, assuming that each condition is part of the same query - even if you do a query, POET does not assume that you are starting the query specification from scratch when you add the next condition.
Clearing a query specification looks like this:
There are often many ways that POET could process a query to get the same answer. POET uses a query optimizer to determine which of these ways is the most efficient. This query optimizer is currently somewhat dependent on the form of your query, especially if your query specifies sort orders or ranges of values.
You can speed up a query which specifies a sort order by following two simple rules: specify the sort order before specifying your search conditions, and place parentheses around the search conditions. Suppose you want to look for Persons which satisfy the condition:
The results should be sorted by name, then by first name. We can help the query optimizer by specifying our query like this:
A real example for which this particular query is useful is left as an exercise for the reader.
Query specific ations are typed, and the type is part of the semantics of the query. If we use a PersonQuery to query a set, then POET will apply the query to all persons. Suppose we have a class called Weirdo which is derived from Person. Query specification classes are derived using the same inheritance structure as the classes they apply to, so WierdoQuery is derived from PersonQuery. Since a WierdoQuery is a PersonQuery you can use it as a parameter to the Query() methods of Book sets. Consider the previous query; if we perform exactly the same query on the same set using a WeirdoQuery instead of a PersonQuery, then POET will apply the search criteria only to members of the class Weirdo:
Intro
Conditions
for embedded data or objects
Query
methods for sets of data or objects
Query
methods for arrays of data or objects
We have already seen that PTXX generates a query class and methods for conditions involving the members of each persistent class it encounters. We will now see which methods will be generated for each member of a persistent class; this depends on the type of the member (C++ base type, POET Type Manager type, persistent object, or non-persistent object). Make sure you understand these distinctions:
| C++ base type | int, char, float, double, or other types which are atomic in the C++ language. |
| POET Type Manager type | PtString, PtBLOB, PtDate, PtTime |
| persistent object | any object whose class declaration uses the persistent or _persistent keyword |
| non-persistent object | any object whose class declaration does not use the persistent or _persistent keyword |
Which query methods will be generated also depends on whether the member embedded class, a pointer, an ondemand, a set, or an array. The following declaration illustrates the combinations which are possible with POET:
The next sections will show how to formulate queries for each member type. Each of these possibilities will be illustrated with sample class declarations and the query methods that are generated for them.
Intro
Query
methods for embedded C++ base types
Query
methods for embedded POET Type Manager types
Query
methods for embedded persistent objects
Nesting
query specifications
Query
methods for embedded non-persistent objects
Query
methods for referenced persistent objects
Nesting
query specifications
Identity
queries
Embedded data or objects are the direct members of the class. They may be C++ base types, POET Type Manager types, non-persistent objects, or persistent objects. The names of the variables are used in the headings for the next several sections; for instance, an integer is an EmbeddedBaseType. To see which query methods are generated for embedded base types, look at the heading "Embedded Base Types," below. Here is a class which illustrates the embedded types which may occur in a class:
Suppose your persistent class has an embedded C++ base type; e.g., an integer, a float, a long, a character, or something like that:
PTXX will generate a query specification class for Demo which contains methods for searching and sorting on that member:
You use this DemoQuery to specify query conditions for the Demo class. Let's look for all Demo objects in the database whose EmbeddedBaseType is greater than 10 and less than or equal to 17. We'll also sort the result set by EmbeddedBaseType:
For queries, POET Type Manager types are handled just like C++ base types. Suppose your persistent class has an embedded POET Type Manager type; e.g., a PtString, PtDate, PtTime, or PtBlob:
PTXX will generate a query specification class for Demo which contains methods for searching and sorting on that member:
You use this DemoQuery to specify query conditions for the Demo class. Our embedded type is a string; let's look for all Demo objects in the database whose EmbeddedPOETType is between "Apples" and "Bananas". We'll also sort the result set by EmbeddedPOETType:
Embedded objects are handled the same whether or not they are persistent; however, the types of parameters in the generated query classes will differ. Suppose your persistent class has an embedded persistent object:
PTXX will generate a query specification class for Demo which contains methods for searching on that member:
You use this DemoQuery to specify query conditions for the Demo class. But you need some way to specify conditions for the embedded object; therefore, the parameter for SetEmbeddedPersistent() is a pointer to a query specification for the persistent object. Here is the PersClassQuery class which PTXX generated for our PersClass:
Now let's look for the Demo objects whose embedded PersClass contain strings between "Apples" and "Bananas":
Embedded objects are handled the same whether or not they are persistent; however, the types of parameters in the generated query classes will differ. Suppose your persistent class has an embedded non-persistent object:
PTXX will generate a query specification class for Demo which contains methods for searching and sorting on that member:
You use this DemoQuery to specify query conditions for the Demo class. But you need some way to specify conditions for the embedded object; therefore, the parameter for SetEmbeddedNonPersistent is a pointer to a query specification for the non-persistent object. Here is the NonPersClassQuery class which PTXX generated for our NonPersClass:
Now let's look for the Demo objects whose embedded NonPersClass contain integers between 10 and 17:
This section covers query specification methods for pointers, references, or ondemands to persistent objects. Since all three are handled very similarly we will discuss them together here.
Suppose your persistent class contains pointers, references, and ondemands to persistent objects:
PTXX will generate a query specification class for Demo which contains methods for searching on those members:
The DemoQuery contains three pairs of functions: one pair for the pointer, one pair for the reference, and one pair for the ondemand. The first function in each pair is for setting query conditions based on the referenced object; the parameter for each of these methods is a pointer to a query specification for the persistent object. The second function in each pair is for identity queries.
Three of the functions in our DemoQuery take a pointer to a PersClassQuery:
These functions are used to create nested query specifications. Here is the PersClassQuery class which PTXX generated for our PersClass:
Now let's look for the Demo objects which have pointers, references, or ondemands to a PersClass which contains strings between "Apples" and "Bananas":
Three of the member functions for this DemoQuery take a PersClass pointer or a PersClassOnDemand pointer as a parameter:
All three are used to formulate identity queries; we would use them if we want to know which Demos contain a pointer, reference, or ondemand to a particular PersClass object. Let's suppose we have a PersClass which is called SomeParticularPersClass and we want to know which Demos contain pointers to it. We can do this with the following code:
Note that identity queries are not generated for non-persistent objects. Since they have no persistent identity, non-persistent objects may never be used to specify identity queries.
Intro
Counting
conditions
Counting
conditions with value conditions
Counting
conditions with nested query specifications
Counting
conditions with identity conditions
This section discusses queries based on the contents of sets in your persistent objects. These sets might contain C++ base types, POET Type manager types, non-persistent objects, pointers to persistent objects, or ondemand references to persistent objects:
PTXX will generate a query specification class for Demo which contains methods for searching on its members. Searching on a set always means searching on the members of that set. Here is the query specification class for the above persistent class:
The parameter lists for all query specification methods for sets begin with the same two parameters, which are known as counting conditions. If no further conditions are specified, then the method sets a condition on the total number of elements in the set. For instance, the following calls specify how many elements are in various sets:
If the method specifies a further condition, then the first two parameters specify the number of elements in the set which meet a condition, and the rest of the parameters specify the condition which the elements should meet. The following condition looks for Demo objects with at least 10 items with the string "Apples" in the SetOfPOETType set:
If the set contains C++ base types or POET Type manager types, then the last two parameters of its query specification method specify the values of the data contained in the set. For instance, we could look for Demo objects which contain sets with at least 10 integers which are less than 20. The name of the set containing integers in our example is SetOfBaseType, so the corresponding method is SetSetOfBaseType():
Or we could look for Demo objects with at least 10 items with the string "Apples" in the SetOfPOETType set:
Note: previous versions of this manual called counting conditions valence conditions. The change in terminology is intended to relieve the synaptic strain caused by gratuitous use of Latin.
If the last parameter of the query specification method is a pointer to a query specification then the corresponding set contains either non-persistent objects, pointers to persistent objects, or ondemand references to persistent objects. In all three cases the method is used for nested query specifications. In our example the following methods can be used for nested query specifications:
To demonstrate these methods, we need a DemoQuery, a NonPersClassQuery, and a PersClassQuery:
Consider the following two conditions: the first specifies Demo objects whose SetOfNonPersistents contain exactly 10 items; the second specifies Demo objects with exactly 10 items which meet the conditions specified in the NonPersClassQuery:
Here are the corresponding statements for SetOfPersistents, which are identical except that the nested query uses a PersClassQuery instead of a NonPersClassQuery:
These are precisely the same parameters we would use for SetOfOnDemandToPersistent:
If the last parameter of the query specification method is a pointer to a persistent object, then the corresponding set contains contains either non-persistent objects, pointers to persistent objects, or ondemand references to persistent objects. In all three cases the method is used for identity queries. In our example the following methods can be used for identity queries:
The last parameter is a pointer to the object for the identity query; the query will look for those objects whose sets contain references to this particular object. The first two parameters contain the counting conditions. Why do we specify the number of times that the object is contained in the set? In some kinds of data the same object may occur several times in a set. You may want to find roads that intersect with a particular road at least twice, or highways that cross a particular river more than twice. To demonstrate the above functions we need a DemoQuery and a pointer to a PersClass object:
The following statement specifies Demo objects whose SetOfPointersToPersistents contain this particular PersClass object at least twice:
We can use the following statement to find all Demo objects whose SetOfPointersToPersistents contain any references to the given object:
The syntax for identity conditions is the same if the set contains ondemands to persistent objects:
Intro
Whole
array conditions
Counting
conditions
Counting
conditions with value conditions
Counting
conditions with nested query specifications
This section discusses queries based on the contents of arrays in your persistent objects. These arrays might contain C++ base types, POET Type manager types, non-persistent objects, pointers to persistent objects, or ondemand references to persistent objects:
PTXX will generate a query specification class for Demo which contains methods for searching on its members. Searching on an array always means searching on the members of that array. Here is the query specification class for the above persistent class:
Query specification functions which take an array as a parameter are used for whole array conditions; the first parameter is an array which has the same type and size as the array in your object, and this condition is used to set conditions based on a whole array. Here are the corresponding methods in our above example:
Now let's use these methods to look for objects containing an ArrayOfBaseType array with the values 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 and an ArrayOfPOETType array with the values "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten":
Several of the query specification methods for arrays begin with the same two parameters:
These are used to set counting conditions. The first two parameters specify the number of elements in the set which meet a condition, and the rest of the parameters specify the condition which the elements should meet. The first two methods set counting conditions with value conditions, the second set counting conditions with nested query specifications. Unlike sets, there are now queries which just test the number of elements in an array - after all, the size of an array is fixed!
If the array contains C++ base types or POET Type manager types, then the last two parameters of its query specification method specify the values of the data contained in the array. For instance, we could look for Demo objects which contain arrays with at least 10 integers which are less than 20. The name of the array containing integers in our example is ArrayOfBaseType, so the corresponding method is SetArrayOfBaseType():
We also have an array of PtStrings called ArrayOfPOETType, and here is its counting condition:
Now let's look for Demo objects with at least 3 integers in ArrayOfBaseType that are greater than 10 or at least one string in ArrayOfPOETType which is equal to "flummery":
If the last parameter of the query specification method is a pointer to a query specification, then the corresponding array contains either non-persistent objects, or pointers to persistent objects. In either case the method is used for nested query specifications. In our example the following methods can be used for nested query specifications:
To demonstrate the above functions we need a DemoQuery, a NonPersClassQuery and a PersClassQuery:
The following statement would search for Demo objects whose ArrayOfNonPersistents contains no more than two objects which meet the conditions specified in the NonPersistentQuery:
The following statement would search for Demo objects whose ArrayOfPersistents contains at least 5 objects which meet the conditions specified in the PersistentQuery:
Intro
Example
1: Husbands of people named "Esther"
Example
2: husbands of this particular Esther
Example
3: sheiks, wives, and children
Example
4: weather data
Example
5: Bigamist queries
Example
6: Weather data (for arrays)
Example
7: Trigraphs
At this point we have introduced a lot of concepts and terminology. A few examples may help you get used to translating English-language statements into POET queries. By now you are probably comfortable with simple queries, so these sample queries are relatively complex.
Suppose we want to know which people have a spouse named "Esther." This implies that a person has a spouse:
Both the person and the spouse are Person objects. Our result set will contain Persons - the men with wives named "Esther". We must declare the result set type with a typedef in the .HCD file:
Now we need to build the query specification. Let's translate our English sentence into our C++ data structures. We want all persons for whom the following condition is true:
Spouse is a pointer to a persistent object, which means that the following kinds of methods will be generated:
The actual names used by PTXX depend on the names in our declaration, so we must change "Spouse" to "PointerToPersistent." Because Spouse is a pointer to Person we must also change "PersClass" to "Person." Therefore, the actual methods will look like this:
The first is a nested query, the second is an identity query. In our case we want to search for spouses with a particular name, so we want a nested query. Here is the actual code:
The above query will give you all husbands whose wives are named Esther. But you might have one particular Esther, and you want to know who her husband is. In the above example this is trivial, since her Spouse pointer presumably points to her husband. If this pointer did not exist you could still find her husband using a query with an identity condition. We might also use a query to check for inconsistencies in our data, for instance, if we suspect that more than one husband may be listing the same Esther as his wife. An identity condition is a way of saying "Give me all objects that point to this object". It is called an identity query because it is based on the object's identity and not on its values.
In example 1 we showed the class declaration for Person. Spouse is a pointer to a persistent object, so PTXX generates the following query methods:
The second one is used for identity conditions. Let us assume that we have already found Esther somehow. Now we can perform our identity query:
Suppose our application permits people to have more than one wife. This would be represented using a set. Wives, in turn, may have many children. In this example we will use the following classes:
Now we want to ask how many sheiks have more than 5 wives. Because wives is a set of persistent objects, PTXX will generate the following kind of query method:
In our case the exact name and parameters will be:
We are only interested in the number of wives, so we can omit the last parameter:
The last parameter is a WifeQuery. We can use this to construct a nested query. For instance, if we want to find all sheiks who have at least five wives who have at least five children named Fred we can use the following query:
Identity queries can also be done for sets. In our example the generated method looks like this:
jealous sheik wanted to make sure that no other sheik has Esther as one of his wives. He could use the following code:
If your set does not contain objects, then you may want to do direct comparisons. This can be done for any C++ base type or for any other type known to the type manager.
Suppose we have an application which measures the temperature every hour during the course of a day and stores the measurements. The measurements for a day might be stored in a set:
Now we may want to know on which days the temperature was at least 37 degrees or no more than 0 degrees for at least 4 hours:
PTXX also generates query methods for fixed-sized arrays contained within objects. If an array contains pointers to persistent objects, then its queries take the same form as they would for a set, but there is no default parameter for the query specification (since we always know how many elements are in an array, there is no need to be able to generate queries to determine the size of the array). In the following example a bigamist is analogous to our sheik, but a bigamist has an array with precisely two wives.
The code is also analogous to our sheik example:
Identity queries also use the same form as for sets:
If the array contains types known to the type manager, then the above methods are not generated. Instead, one method will be generated to test for the number of elements meeting a condition and one method will be generated to compare whole arrays.
The former has the same form as direct comparisons for sets. If our weather example had the following persistent class declaration, then our application code would be identical to that given above:
Finally, you can use an entire array as the basis for a query. Array elements are compared sequentially just as characters in a string are compared in strcmp(), but the entire array is always compared- a zero does not terminate the array.
If we have this in our .HCD file:
Then we can search for tokens based on trigraphs:
Copyright (c) 1996 POET Software, Inc. All rights reserved. Reproduction in whole or in part in any form or medium without the express permission of POET Software, Inc. is prohibited.