Chapter 8. Tolog Query Engine

Table of Contents

Querying A Topic Map
Using TologResultsSet
Extending The Tolog Engine
Custom Predicates Implementing org.tm4j.tologx.parser.Predicate
Custom Predicates Extending org.tm4j.tologx.predicates.PredicateBase
Efficient Tolog Querying

The tolog query engine which is part of the TM4J distribution allows prolog-like queries and scripts to be evaluated against a topic map. The interface to the engine is somewhat similar to the basic JDBC interface. A query can be passed in to the engine as a string and is evaluated to produce a results set which can then be iterated over to extract all the results.

In addition to supporting most of the basic tolog language facilities, TM4J's implementation allows additional complex predicates to be added to the system by implementing a simple Java interface.

Details of the tolog query language itself are beyond the scope of this document. However, for a tutorial on the language, please see the Ontopia Query Language Tutorial.

Note

From release 0.9.5 of TM4J, a new implementation of the tolog Query Engine is provided in the package org.tm4j.tologx. This package implements all of the tolog 1.0 query language. The old package org.tm4j.tolog is now deprecated and so this document refers only to the newer package.

Querying A Topic Map

The procedure for querying a topic map is as follows:

  1. Create a QueryEvaluator

    The main interface to the Tolog query engine is org.tm4j.tologx.QueryEvaluator. This is an interface only. Although there is currently only one implementation of this interface, it is intended that future release of TM4J will support specialised implementations that can take advantage of the query support of the underlying backend (e.g. translating tolog queries to SQL for the Hibernate backend). For this reason, you should NEVER create an instance of a class that implements this interface directly. Instead you should use the org.tm4j.tologx.QueryEvaluatorFactory class which provides a single static method newQueryEvaluator(TopicMap tm). This method returns the most appropriate implementation of the QueryEvaluator interface for querying the topic map specified in the the tm parameter.

  2. [OPTIONAL] Register any extension predicate handlers

    Predicate extensions are classes which implement additional processing functionality to the basic Tolog query engine. Predicate extensions are described in more detail in the section called “Extending The Tolog Engine”

  3. [OPTIONAL] Add any rules for the QueryEvaluator to consider

    You may add rules to the query engine either one at a time, using the method addRule(String ruleString) or in bulk from a file using the method addRulesModule(InputStream rules, String prefix). For the latter method, the prefix parameter specifies the prefix that you must use in your query to refer to the rules defined in the module. For example if the module defines a rule "descendant-of($A,$B)" and you import that module with the prefix "family", you must refer to the rule in your query as "family:descendant-of(...)". In either case, the rule(s) will be parsed when they are loaded and any errors will be reported by throwing a org.tm4j.tolog.ParserException exception.

  4. [OPTIONAL] Precompile the query string(s) to be used

    If you intend to use the same query strings repeatedly, it can be more performant to precompile the queries. The compilation process parses the query and ensures that all referenced topics and other topic map objects are found and that (as far as possible) the parameter requirements of the predicates are satisfied, although in some cases, this cannot be determined until runtime. Any query optimisation supported by the QueryEvaluator implementation may also be performed at this stage.

    To compile a query string, use the method prepareQuery(String queryString). The return result from this method is an instance of the org.tm4j.tologx.PreparedQuery interface.

    In some cases you may want to have a query that is used repeatedly with one or two of the variables in it being assigned to values from your code. To do this, you should write your query string using references of the form "%n" where n is an integer value greater than 0. These references are evaluated at query time against parameters provided to the execute() as an array. For a reference "%n", the n'th item of the parameters array will be used as a replacement value. Note that in this case, arrays are numbered starting from 1, not 0.

    Example 8.1. Example of using replacement values

    	    QueryEvaluator qe = QueryEvaluatorFactory.newQueryEvaluator(tm);
    	    PreparedQuery pq = qe.prepareQuery("select $DESC from descendant-of($DESC, %1)?");
    
    	    // Then later in the code, assume that we want to find the descendant-of the topic t
    	    TologResultsSet rs = pq.execute(new Object[] { t });
    	  
  5. Evaluate the query

    If you are using a precompiled query, then you execute it by invoking either the execute() method or the execute(Object[] params) of the PreparedQuery interface, the latter method provides replacement value for % references (see above for details).

    If you are not using a precompiled query, then you can still execute any query string by using the execute(String queryString) or execute(String queryString, Object []params) method of the QueryEvaluator interface. The latter method allows you to use the %n references in the query string mapped to replacement values in the params parameter.

    If the query is syntactically incorrect, a org.tm4j.tologx.TologParserExceptionwill be thrown. If the query is syntactically correct but cannot be evaluated for some other reason, then a org.tm4j.tologx.TologProcessingException will be thrown. The typical causes of an TologProcessingException being thrown are a mis-spelt predicate name (when the engine encounters a predicate which is not in its rules base, it will try and locate it as an extension, if the extension is not found, then this exception will be thrown); or a not() clause being encountered in which one or more of the variables in the not() clause has not been assigned a value during the evaluation of preceeding clauses.

    If evaluation is successful, the evaluate() method will return an object implementing the org.tm4j.tologx.TologResultsSet interface. This is described in more detail in the section called “Using TologResultsSet”

  6. [OPTIONAL] Process the results

    The org.tm4j.tologx.TologResultsSet object returned from any of the evaluate() methods represents the results of the query as a collection of "rows". Each row represents one set of values which match the query criteria. Each of the variables in the query are mapped to a "column". the section called “Using TologResultsSet” describes how to iterate through the results in the results set and how to convert this results set into another TopicMap.

The code listing below shows these basic steps. The methods dumpResults() and extractResults() are shown in the section called “Using TologResultsSet”.

Example 8.2. Invoking the tolog Query Engine

private void doQuery(TopicMap tm)
{
  try
  {
    // Create a QueryEvaluator to query the loaded topic map
    QueryEvaluator queryEvaluator = new QueryEvaluator(tm);
	    
    // Add inference rules to the query engine
    // Note that one inference rule may make use of another rule, allowing
    // some pretty complex inference chains to be built.
	    
    queryEvaluator.addRule(
          "direct-descendant-of($A, $B) :- " + 
          "  { child-of($B:father, $A:son) | " + 
          "    child-of($B:mother, $A:son) | " + 
          "    child-of($B:father, $A:daughter) |" + 
          "    child-of($B:mother, $A:daughter) }.");

    queryEvaluator.addRule(
	"descendant-of($A, $B) :- { " + 
        "  direct-descendant-of($A, $B) | " +
        "  direct-descendant-of($A, $C), descendant-of($C, $B) }.");
	    
    TologResultsSet results = queryEvaluator.evaluate(
        "select $A from descendant-of($A, henry-i) ?");
	    
    // Print the results in ASCII form to the standard output
    dumpResults(results);
	    
    // Create a topic map to receive the results as a topic map fragment
    TopicMap resultsTopicMap = createTopicMap(
          "http://www.tm4j.org/examples/tolog/tologqueryexample.xtm");

    // Extract the results from the queried topic map into
    // the new topic map fragment.
    extractResults(results, resultsTopicMap);

    writeTopicMap(resultsTopicMap, System.out);

  }
  catch(IndexException ex)
  {
    throw new RuntimeException(
       "Could not initialise the indexes needed to query using tolog.",
       ex);
  }
  catch(IndexManagerException ex)
  {
    throw new RuntimeException(
       "Could not open the indexes needed to query using tolog.",
       ex);
  }
  catch(org.tm4j.tolog.parser.ParseException ex)
  {
    throw new RuntimeException("Error parsing rule/query", ex);
  }
  catch(TologEvaluationException ex)
  {
    throw new RuntimeException("Error evaluating query.", ex);
  }
  catch(TologParserException ex)
  {
    throw new RuntimeException("Error parsing rules/query", ex);
  }
}