Making sure that all entities are linked to their immediately accessible ones is tedious, but not unmanageable.
But I am now going to explore the possibilities that are opened if we devise a way to link to indirectly accessible
entities, and how this, extremely more difficult, task can be automated. Moving more than one step away in the data
space is akin to a hyperjump in the physical space, so I named this practice “Data Hyperjump Linking”.
First of all, let me present an example of Data Hyperjump interaction. Don't stick to the form, because the way a user
will be able to choose a hyperjump is irrelevant to its essense, although a hierarchical menu is a strong contender, due
to the large number of available options (easily more than one hundred).
This example is a small part of the length-4 hyperjumps that start from the Customer entity of a real application.
Hyperjumps are nothing more than paths in the graph whose nodes are the entities and whose arcs are relationships based on
properties. If entity Contract
has one or more properties of type Customer
, then there are one or
more arcs linking the two entities. In database terms, arcs are equivalent to foreign key relationships. Arcs can be traversed
both ways; the directionality is only useful to direct code generation and inferencing of the multiplicity. When you go from the
referencing entity to the referenced entity, I call it a forward link, else I call it a reverse
link.
A reverse link cannot be followed by the corresponding forward link (because we would go back to the same entity). Other than
that, all paths are valid and potentially useful. In fact, hyperjump links go beyond shortcutting a series of plain data
hyperlinks. Sometimes they produce the union of all results from data hyperlinks along a specific path.
I will now introduce a minimal example for purposes of demonstration of the concepts, but also in order to introduce a
tool that can produce hyperjump paths and their queries in HQL (for Hibernate/NHibernate). The formulation will be in Prolog,
as the tool is written in this succint language that is very suited to similar tasks.
- Concepts are introduced with the
concept
predicate. Concepts correspond to classes or tables.
- Arcs and their directionality are introduced with the
link
predicate, with arguments the originating concept,
the destination concept, and the linking property. The linking property corresponds to a class property or a database column.
- Predicate
significant
lists all concepts that can appear as the origin or the destination of a path. These
are concepts that can be shown in the UI. Non-significant concepts can be used only along a path.
% concept(concept_id)
concept('Person').
concept('Building').
concept('Floor').
concept('Manager').
concept('Dog').
concept('Pack').
concept('Flee').
% link(from_concept_id, to_concept_id,property_name)
link('Dog','Person',master1).
link('Dog','Person',master2).
link('Dog','Pack',pack).
link('Flee','Dog',dog).
link('Person','Building',building).
link('Person','Building',work_building).
link('Floor','Building',building).
link('Building','Manager',manager).
% significant(list of concepts that can serve as endpoints)
% concepts that are not "significant" can only be used along a path
significant(['Person','Building','Floor','Manager','Dog','Flee']).
The corresponding graph is shown below.
The exclusion of paths that contain a reverse link followed by a forward link between the same entities should be evident, if
one looks at a specific case: going from Person to Dog and then back to Person, we land on the same Person. Any path that includes
such a construct is redundant, as there is a corresponding path that omits it and arrives at the same results.
Based on this concrete example, one should be able to see how Hyperjump linking allows access to results not immediately
retrievable by plain hyperlinking. With a series of hyperlinks, one cannot arrive at a single results page listing all flees of
all dogs of a person, for example.
Program navigate.pl, in SWI-Prolog, a distribution which can be freely downloaded for
many platforms, can be used to process such a graph and output the possible paths. It is possible to drive it from a shell script,
as shown below (for Windows; 1st parameter is the originating concept, 2nd parameter is the max length):
"\Program Files\SWI-Prolog\bin\plcon" -g consult(navigate),consult(persongraph),allaccesspathsfrom('%1',L,%2),significant(S),include(significant_endpoints(S),L,L1),include(not_retrogressive,L1,L2),print_paths(L2) -t 'halt'
The specified command outputs paths that are not retrogressive (don't look it up in your graph theory book), meaning
those paths that go back to a concept after having visiting it once. Retrogressive paths are more complicated for the user to
understand, but they are not without value, and you may choose to include them.
An example from the output of the program is the following path. The first line is the path rendered in English, and the
second line is the corresponding HQL query (containing a parameter to plug in the ID of the originating entity).
The program can be extended to output the equivalent SQL query as well.
From Person, go to Building using work_building, go to Person having it as building, go to Dog having it as master2, go to Flee having it as dog
from Flee a21291 where a21291.dog in (select a10708.id from Dog a10708 where a10708.master2 in (select a21280.id from Person a21280 where a21280.building in (select a58985.work_building from Person a58985 where a58985.id in (:id ))))
Creating the graph by hand is tedious and error-prone. XSLT template
hbm2prolog.xslt can be run on
a Hibernate mapping file to extract the relevant predicates as text output. Run it on all mappings and concatenate outputs to
arrive at a Prolog file containing the entire graph. Be sure to check the Hibernate contstructs that are covered, as you may be
using others that are not taken into account.
Why not P#, to produce actual, linkable code?
To be frank, there is also a P# version, navigate_psharp.pl.
I was halfway debugging it, which meant compiling it to uncover faults not flagged during interpretation, when I hit some critical
size, after which compilation thrashes and I kill it after it crosses 1.5 Gb memory usage... However, using it as input to the
interpreter from inside a .NET program should work and this is how I am planning to use it.