A Brief Example (again)!
So, we're trying to build a crossword puzzle. We've got a list of words appropriate for our difficulty level, and a list of the lengths of the gaps of in the puzzle:ArrayList<String> words = dict.getWords(Puzzle.MEDIUM);
ArrayList<Integer> gaplengths = puzzle.getGapLengths();
And we outlined a query to find pairings of words with their lengths:
List<Object[]> matches = selectAll(String w : words,
Integer i : gaplengths |
w.length() == i);
To break the query down into its components - firstly,
selectAll is a keyword we have added; it indicates that what follows in brackets is a JQL query. Following that, we come to:
String w : words, Integer i : gaplengthsFinally, separated from the domain variable declarations by a vertical bar '|', we find our query expression:
w.length() == iQueries can be far more complex than this - an arbitrary number of domain variables can be declared, and query expressions can consist of many boolean expressions conjoined with "&&", the boolean AND. To build on our example, now our crossword's target audience has a superstition about words that start with the letter 'R' - none of these are allowed in our crossword. Easily accounted for!
List<Object[]> matches = selectAll(String w : words,
Integer i : gaplengths |
w.length() == i &&
w.charAt(0) != 'R' );
Join Types
When talking about how we evaluate queries, we swipe terminology from the world of databases and refer to operations between pairs of sets as joins between sets. One way that we can accomplish the above query is using a simple nested loop join. This is pretty much what it sounds like - a loop throughwords, and a loop through gaplengths in the body of that loop. Here's one now:
List<Object[]> matches = new ArrayList<Object[]>();
for(String w : words){
for(Integer i : gaplengths){
if(w.length() == i)
matches.add(new Object[i,w]);
}
}
The Hash Join is a less obvious, less easily programmed but far more efficient join methodology that can be extremely beneficial in the situations in which it can be used. The basic principle is, for joins on equality, we can build a hash table which has as keys the attribute we are joing on, and for each key is a list of objects which have that attribute. A possible hashtable built for a hash join for our crossword example could hash from an integer to a list of words which are as long as that integer. Once we've built our hashtable, we can then loop through the set of objects we didn't build the hashtable from, looking up in the hash each value we are testing for equality. Code-wise this might look a bit like this (generic parameters omitted for clarity):
List matches = new ArrayList(); Hashtable index = new Hashtable();
for(String w : words){
List l = index.get(w.length());
if(l==null){
l = new ArrayList();
index.put(w.length(),l));
}
l.add(w);
}
for(Integer i : words){
for(String w : index.get(i))
matches.add(new Object[i,w]);
}
JQLMail
For a more substantive example, we created JQLMail - a mail client written using JQL. It's a simple proof of concept - not much to look at, but it demonstrates the usability of JQL in a reasonable sized application.
JQLMail stores emails internally as a completely flat collection of Message objects - all the folders listed in the screenshot are triggers for queries. Messages can be tagged as well, and this allows for folder-like functionality. This is very similar to the functionality offered by Google's GMail service.
Folders on the left hand side are stored queries - mostly they simply select all messages tagged with a certain tag (clicking on the "Sent" folder simply selects all messages with the "Sent" tag, for example), but they can be any query you like. One could store a query for messages tagged with a certain tag, from certain senders, before a certain date, for example.