Domain Queries Traversal Expressions - Wolfgang-Schuetzelhofer/jcypher GitHub Wiki

Previous Next Table of Contents

Domain Queries

Traversal Expressions

Traversal expressions provide a means to navigate the graph of domain objects. Traversal expressions start with a DomainObjectMatch which identifies a set of domain objects. These objects serve as root elements of paths through the object graph which are defined by the traversal expression. A Traversal expression then consist of one or more steps. Each step specifies a hop from object via attribute to object as part of the path through the object graph.
A Traversal expression produces another DomainObjectMatch. It yields a set of domain objects which are the endpoints of the specified paths.

Given, that an object of type Person has an attribute addresses which references a list of objects of type Address, then you can formulate the following traversal expression in order to retrieve these addresses:

// create a DomainQuery object
DomainQuery q = domainAccess.createQuery();
// create a DomainObjectMatch for objects of type Person
DomainObjectMatch<Person> j_smithMatch = q.createMatch(Person.class);

// Constrain the set of Persons to contain
// 'John Smith' only
q.WHERE(j_smithMatch.atttribute("lastName")).EQUALS("Smith");
q.WHERE(j_smithMatch.atttribute("firstName")).EQUALS("John");
		
// Traverse forward, start with 'John Smith'
// navigate attribute 'addresses',
// end matching objects of type Address.
DomainObjectMatch<Address> j_smith_AddressesMatch =
   q.TRAVERSE_FROM(j_smithMatch).FORTH("addresses").TO(Address.class);
		
// execute the query
DomainQueryResult result = q.execute();

// retrieve the list of matching domain objects
// (i.e. all addresses of 'John Smith')
List<Address> j_smith_Addresses = result.resultOf(j_smith_AddressesMatch);

You can specifiy longer traversal paths. Imagine that objects of type Address reference objects of type Area (representing e.g. cities, districts, countries, continents, ...) via attribute area. Then you can formulate:

// A set of Persons containing
// 'John Smith' only - see query above
DomainObjectMatch<Person> j_smithMatch ...

// Start with the set containing 'John Smith',
// navigate forward via attribute 'addresses'
// navigate forward via attribute 'area',
// end matching objects of type Area
// (these are the immediate areas referenced by addresses
// of 'John Smith' e.g. cities, urban districts, villages, ...).
DomainObjectMatch<Area> immediateAreasMatch =
   q.TRAVERSE_FROM(j_smithMatch).FORTH("addresses")
      .FORTH("area").TO(Area.class);

For every step in a traversal expression you can define the distance in terms of how many hops to take when navigating the domain graph along a given attribute. You can define a minimum number of hops (minDistance) and a maximum number of hops (maxDistance). This is optional and it defaults to one hop (minDistance = maxDistance = 1). If an object of type Area references another object of type Area (the area it is located in; eg. a city is located in a country) via attribute partOf, then a hierarchy of areas can exist in the domain object graph. E.g. area San Francisco is 'part of' area California is 'part of' Area USA is... You can formulate the following query:

// A set of Persons containing
// 'John Smith' only - see previous query
DomainObjectMatch<Person> j_smithMatch ...

// Start with the set containing 'John Smith',
// navigate forward via attribute 'addresses'
// navigate forward via attribute 'area',
// navigate forward via attribute 'partOf'.
// ( DISTANCE(1, -1) means that all areas reachable
// from one hop up to an arbitrary number of hops
// via attribute 'partOf' will be collected),
// end matching objects of type Area
// you have collected the entire areas hierarchy
// starting at one area above the immediate areas
// (see previous query)
DomainObjectMatch<Area> areasMatch =
   q.TRAVERSE_FROM(j_smithMatch).FORTH("addresses")
      .FORTH("area")
      .FORTH("partOf").DISTANCE(1, -1).TO(Area.class);

You can also specify backward taversals. Imagine you have an address and you want to know all persons which live at that particular address. In a traditional approach you would have to collect all persons and then select those who reference the given address. In JCypher instead you specify a backward traversal. The big advantage, besides the simplicity of the expression, is that backward traversals are executed with the same performance as forward traversals.

// create a DomainObjectMatch for objects of type Address
DomainObjectMatch<Address> addressMatch = q.createMatch(Address.class);
		
// Constrain the set of Addresses to contain
// 'Market Street 20' only
q.WHERE(addressMatch.atttribute("street")).EQUALS("Market Street");
q.WHERE(addressMatch.atttribute("number")).EQUALS(20);
		
// Traverse backward, start with 'Market Street 20',
// navigate attribute 'addresses',
// end matching objects of type Person.
DomainObjectMatch<Person> residentsMatch =
   q.TRAVERSE_FROM(addressMatch).BACK("addresses")
      .TO(Person.class);

Of course you can concatenate multiple backward steps and you can use DISTANCE(.., ..) on any of these steps.
You also can combine forward and backward steps within a single traversal expression. Given that an object of type Person references another object of type Person via attribute friend. If you have selected a person and you want to know which other persons live at that person's friend's addresses, you can formulate:

// A set of Persons containing
// 'John Smith' only
DomainObjectMatch<Person> j_smithMatch ...

// Start with the set containing 'John Smith',
// navigate forward via attribute 'friend'
// navigate forward via attribute 'addresses'
// (now you have reached the friend's addresses)
// navigate backward via attribute 'addresses'
// end matching objects of type Person
// you have collected all persons
// living at the friend's addresses
DomainObjectMatch<Person> residentsMatch =
   q.TRAVERSE_FROM(j_smithMatch).FORTH("friend")
      .FORTH("addresses")
      .BACK("addresses").TO(Person.class);

You can specify additional perdicate expressions on a DomainObjectMatch which was produced by a traversal expression.

DomainObjectMatch<Person> residentsMatch =
   q.TRAVERSE_FROM(j_smithMatch).FORTH("friend")
      .FORTH("addresses")
      .BACK("addresses").TO(Person.class);
// constrain residentsMatch to contain only persons
// who's first name starts with 'J' 
q.WHERE(residentsMatch.attribute("firstName")).LIKE("J.*");

Besides being able to specify arbitrarily complex predicate expressions on DomainObjectMatch(es) produced by traversal expressions, such DomainObjectMatch(es) can of course serve as starting points for other traversal expressions in turn.


Previous Next Table of Contents
⚠️ **GitHub.com Fallback** ⚠️