Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

VIATRA/Query/UserDocumentation/Examples/school

Introductory example: school

The school example features a simple EMF model and some simple graph patterns to show the very basics of using EMF-IncQuery. Read this first before anything else!


Overview

The aim of this page is to provide an easy-to-understand, step-by-step guide to EMF-IncQuery. For this purpose, the school example features a simple EMF model and some simple graph patterns to show the very basics of the query language and the core add-ons.


Obtaining the example

In the simple case, you can install the School example from the IncQuery Extra update site.

Alternatively (for advanced users): you will need two Eclipse workspaces to try this example.

You can launch the (school, school.edit, school.editor) projects in a Runtime Eclipse application (which will use the runtime workspace), or you can install them into your host Eclipse (in that case, no separate runtime workspace is necessary).


Source projects

  • host workspace / installed plugins: school, school.edit, school.editor

The school project contains the EMF metamodel (school.ecore) for representing schools, years, classes, students, teachers, and courses. The .edit and .editor projects are automatically generated by EMF (no manual tweaking on the school.genmodel was performed.)

EMFIncQuery-Examples-school-Slide30.png

Example query and instance model contents

  • runtime workspace: school.instancemodel/

This project contains two school instance models with a school, years, classes, students, teachers, and courses. You can create your own model using the standard generated EMF features: New/Other/Example EMF Model Creation Wizards/School Model. BUTE.school contains the following configuration (some details, such as attribute values, have been omitted for the sake of clarity):


EMFIncQuery-Examples-school-Slide31.png

EMFIncQuery-Examples-school-Slide32.png


Hogwarts.school is another simple instance model example, borrowing some characters from a famous novel.

  • runtime workspace: school.incquery/

This project is an EMF-IncQuery Project (created with New/Other/EMF-IncQuery/EMF-IncQuery Project). It also contains the tutorial queries (patterns) in the src/school/schoolqueries.eiq file, which has been created using the ""New/Other/EMF-IncQuery/EMF-IncQuery Query Definition"" wizard.


Jump right in - try the patterns!

  1. Make sure you are in the runtime workspace.
  2. Make sure the "Query Explorer" view of EMF-IncQuery is open (Window/Show view/EMF-IncQuery/Query Explorer).
  3. In the school.incquery project, right click in the schoolqueries.eiq file and choose EMF-IncQuery/Register patterns in Query Explorer.

Alternatively, you can use the magical green button of the Query Explorer view (green triangle in the view toolbar) when the schoolqueries.eiq file is the active editor.

  1. Observe that the patterns have been registered by opening the left-side panel (Pattern registry) of the Query Explorer.
  2. Open BUTE.school file (either with the generated School editor, or the Sample Reflective Ecore model editor, both of them should work).
  3. Import the instance model into the Query Explorer by using the magical green button again (while the instance model editor is the active editor).
  4. Observe that the instance model has been loaded; expand the tree to see each pattern and their matches.

You should see something like this:


EMFIncQuery-Examples-school-iqexplorer.png


Observe that both the Pattern Registry and the Details/Filters panels can be toggled, depending on whether you prefer to see their contents or not. The Details/Filters panel is also useful for parameterized (parameter-bound) queries (see later). If you double-click on a match (green dot icons), the selection corresponding to the match (containing all model elements referenced by pattern parameters) will be applied to the host editor (revealing the corresponding model elements automatically). This is useful if you want to locate the model elements in the editor.


Detailed step-by-step guide

Now we describe step-by-step how the sample queries/patterns have been created and what they do. The 7-step tutorial is intended to serve as a quick overview of the language and tooling features. To ease understanding, it is always a good idea to check the language overview and the FAQ. For performance considerations, be sure to check the performance guide.


Step 0 - header

package school
 
import "http://school.ecore"

When you first create an IncQuery query definition, an empty file will be created, with only a package declaration. The package declaration is primarily used to determine the Java packages of the code that IncQuery will generate.

In order to start working with IncQuery, you need to import an Ecore namespace, by typing the import keyword and then activating content assist (ctrl-space) and selecting a registered EPackage. Just after you have entered an import declaration, it is a good idea to include an appropriate plugin dependency to the contributing EMF .model plugin (in this case, school) in the MANIFEST.MF of the IncQuery project, and also take care to re-export this dependency.

Workspace EPackages will be usable with the EMF-IncQuery Generator Model (generator.eiqgen) by pointing to the genmodel file of the used metamodels. Once you add the resource URI to the eiqgen file, you can import with the nsUri in the eiq file.


Step 1 - simple queries

// Step 1: simple queries
 
 /* 
  * all schools
  */
 pattern schools(Sch) = {
 	School(Sch);
 }
 
 /*
  *  all teachers
  */
 pattern teachers(T) = {
 	Teacher(T);
 }
 
 /* 
  * a sample query along a reference
  * 
  * Teacher T teaches in school Sch.
  */
 pattern teachersOfSchool(T:Teacher,Sch:School) = {
 	School.teachers(Sch,T); 
 	// note that there is no restriction on the direction of navigation:
 	// all "Sch"s can be queried from Ts and vice-versa
 }


The most simple queries in IncQuery are graph patterns that identify nodes and edges of the EMF instance model.

  • nodes (EObjects) can be identified by their Ecore types, such as School or Teacher. A simple pattern consisting of a single node type constraint will yield all instances of that type in the query results, called match set in IncQuery terminology.
  • edges (EReference "instances", i.e. Java references) can be added as additional structural constraints by their Ecore types, such as School.teachers.
    • in the teachersOfSchool pattern above, we are looking for pairs of School-Teacher instances that are actually connected by a teachers edge.
    • Notice that IncQuery has type inference: as the types of the edges endpoints can be inferred from the metamodel, there is no need to include additional Teacher(T) and School(Sch) constraints. The types are defined in the pattern header only for the sake of end-user readability.
    • Notice that IncQuery implicitly makes all EReference navigation bidirectional: while you have to formulate the pattern in a syntactially correct way (specifying that the teachers edge is defined for the School EClass), the query evaluates all connected School-Teacher pairs and thus can be used to look for the Schools belonging to a particular Teacher instance (thus navigating backward along the teachers edge). This works regardless whether there is an EOpposite relationship defined or not.


Step 2 - pattern calls, negative application conditions

// Step 2: pattern calls, negative application conditions
 
/* 
 * Courses of a teacher.
 * 
 * Teacher T teaches in Course C
 */
 pattern coursesOfTeacher(T:Teacher, C:Course) = {
 	Teacher.courses(T,C);
 } 
 
/*
 * Teacher T teaches a course which is being taught to SchoolClass SC 
 * Note: header type constraints can be ommitted due to automatic type inference.
 */
 pattern classesOfTeacher(T, SC) = {
 	find coursesOfTeacher(T,C);
 	Course.schoolClass(C,SC);
 }
 
 /*
  * Teacher T is a teacher who doesn't teach any schoolclass.
  */
 pattern teacherWithoutClass(T:Teacher) = {
	neg find classesOfTeacher(T,SC); 	
 }

One of the most powerful reusability features of the IncQuery pattern language is the find keyword. You can use it to call patterns from other patterns, giving you the ability to factor out commonly used code segments and reuse them whereever necessary. The find keyword uses an intuitive parameter passing semantics, identifying (bound) objects by the parameter they are assigned to (i.e., in the example, wherever you write C, it means the same object).

The find keyword can be preceded by the neg modifier to invert the meaning, i.e. forbid the match of the called pattern with the given parameterization. In that case, unbound parameters (SC) will be "running parameters", i.e. they are not allowed to be matched by anything.


Step 3 - path expressions

// Step 3: path expressions
 
 /*
  * Student S attends school Sch
  */
 pattern studentOfSchool(S:Student,Sch:School) = {
 	Student.schoolClass.courses.school(S,Sch);
 	// logically equivalent: School.courses.schoolClass.students(Sch,S);
 }

Path expressions are useful to compacting patterns, wherever intermediate nodes along a path are not important. Notice that in this particular case, the direction of navigation is unimportant in the sense that both ways of connecting S and Sch yield the same result, because all the included EReferences have EOpposites.


Step 4 - check conditions

// Step 4: check conditions
 
 /*
  * Attribute value constraint with a constant:
  * 
  * Course C has weight 30
  */
 pattern courseWithWeightThirty(C:Course) = {
 	Course.weight(C,30);
 }
 
 /*
  *  a bit more complicated check expression:
  * 
  * Course C has a subject name which is longer than C's weight.
  */
 pattern courseWithNameLongerThanWeight(C) {
	Course.subject(C, CName);
	Course.weight(C, W);
	check(CName.length > W); // no type casts necessary inside check expressions!
}
 
  /*
   * A check condition with some Xbase magic:
   * (this must be the most primitive prime tester ever :-)
   *
   * for the limitations on check() expressions, see https://viatra.inf.mit.bme.hu/incquery/language#Limitations
   * for details on Xbase expressions, see http://www.eclipse.org/Xtext/documentation/2_1_0/199h-xbase-language-reference-expressions.php 
   * 
   * RULES:
   * - ensure that a boolean value will be returned on all execution paths
   * - write side-effect free code
   * - only access pattern variables and their members, no navigation inside check() expression
   * Note: For v.0.6, the last two constraints are not hard-enforced and not checked, but this will change for v0.7.
   *
   * Course C has a weight which is a prime number
   */
pattern courseWithPrimeWeight(C) {
	Course.weight(C, W);
	check( { // make sure the check() expression returns a boolean value on all execution paths
		if(W % 2 == 0)
		false else {
			var Integer maxValue = Math:: round(new Float(Math:: sqrt(W)))
			var Integer divisor = 3 while(divisor <= maxValue) {
				if(W % divisor == 0)
				return false else divisor = divisor + 2;
			}
 
			true // we have found a prime number	
		}
 
	});
}

Check expressions are useful for specifying value constraints, most frequently for EAttributes. Technically, you write Xbase into a check() expression, making it possible to include complex (imperative) logic right inside the query definition.

  • For the limitations on check() expressions, see the language reference
    • 0.6 limitation: the basic rule of thumb is that you should never include navigation inside a check expression; only write (side effect free) code that accesses a single object from the pattern body and make sure your expression returns a boolean value in a deterministic and idempotent way (that is, the same value for the same input, independently of when it is invoked). Some of these limitations will be lifted for v0.7+.
    • 0.6 limitation: once IncQuery will have full type inference, the type casts (as) inside check() expressions will not be necessary.
  • For details on Xbase expressions, see the Xbase reference


Step 5 - counting

// Step 5: counting (match cardinalities)
 
  /*
   * Teacher T1 teaches more classes than teacher T2
   */
  @QueryExplorer(display = false) // this removes it from the Query Explorer
  pattern teachesMoreClasses(T1 : Teacher, T2 : Teacher) {
	N == count find classesOfTeacher(T1, _SC1);
	M == count find classesOfTeacher(T2, _SC2);
	check(N > M);
  }
 
  /*
   * Teacher T teaches the most courses.
   * 
   * Note: there can be multiple matches to T as there might be cases
   * where multiple teachers teach the same and maximum amount of courses.
   */
  pattern teachesTheMostCourses(T:Teacher) = {
  	neg find teachesMoreClasses(_Tx,T); // there is noone who would teach more courses
  }

Counting can be used to refer to the size of the match set inside a query. This comes handy e.g. when you want to identify the model element with the minimum/maximum of something. Similarly to neg, cound find involves (a) running (unbound) variable(s) that act(s) as the objects (that)/(whose combinations) will be counted.


Step 6 - disjunction, transitive closure

// Step 6: disjunction, transitive closure
 
   /*
    * Student S1 is friendly to student S2, i.e. they are friends in either direction
    * 
    */
   @QueryExplorer(display = false)
   pattern friendlyTo(S1:Student, S2:Student) = {
   	Student.friendsWith(S1,S2);
   } or {
   	Student.friendsWith(S2,S1);
   }
 
   /*
    *  Student Someone is in the circle of student S1's friends:
    *  - they are either friendly directly
    *  - or there is a common friend who is in both student's circle of friends
    * 
    * Student S1 is transitively friendly to student Someone
    */
   pattern inTheCircleOfFriends(S1:Student,Someone:Student) = {
   	find friendlyTo+(S1,Someone);
   	S1!=Someone; // we do not allow self loops
   }
 
   /*
    * Student S1 has more transitive friends than student S2
    */
   @QueryExplorer(display = false)
   pattern moreFriendsThan(S1 : Student, S2 : Student) {
	N == count find inTheCircleOfFriends(S1, _Sx1);
	M == count find inTheCircleOfFriends(S2, _Sx2);
        check(N > M);
   }
 
   /*
    * Student S has the largest circle of friends.
    * 
    * Note: again, there can be multiple matches in case there are
    * equally large friend circles.
    */
   pattern theOnesWithTheBiggestCircle(S:Student) = {
   	neg find moreFriendsThan(Sx,S);
   }


By default, all clauses inside the pattern body are interpreted as a conjunction (i.e. AND). IncQuery also supports disjunction by OR patterns, whereby multiple pattern bodies can be assigned.

IncQuery includes a specific language construct to define transitive closures, i.e. the computation of reachability relationships where a single step is defined by a (binary) pattern (friendlyTo in the example). This construct is supported by a much more efficient evaluation back-end (evaluated for a recent ICGT conference paper). In order to try the transitive closure, we suggest you to edit the students inside the model, add/remove friendsWith relationships and observe how the match set of the inTheCircleOfFriends patterns change!


Step 7 - combining everything

// Step 7: combine everything, @PatternUI, @ObservableValue, @Handler
 
   /*
    * 
    * We want to find those years, which had courses taught by the busiest teacher
    * and included the most sociable students
    * 
    */
    @QueryExplorer(message="The busiest teacher $T.name$ taught the most sociable student $S.name$ in $Y.startingDate$")
    @ObservableValue(name = "Year", expression="Y.startingDate")
    @ObservableValue(name = "Teacher", expression="T.name")
    @ObservableValue(name = "Student", expression="S.name")
    @Handler(fileExtension = "school")
    pattern finalPattern(Y:Year,C:Course,T:Teacher,S:Student) = {
    	Year.schoolClasses.courses(Y,C);
    	Course.teacher(C,T);
    	Student.schoolClass.courses(S,C);
 
    	find theOnesWithTheBiggestCircle(S);
    	find teachesTheMostCourses(T);
    }


In the final step of the walkthrough, we combine the previously defined patterns to define a multi-parameter query. It is important to note that in IncQuery, patterns can have an arbitrary number of parameters, any of which can be bound/unbound freely. The Query Explorer supports parameterized queries (parameter binding) through the Details/Filters panel on the right: by selecting a pattern in the tree, you have the ability to specify Filters for each parameter separately.

  • as pattern parameters can be scalars, such values can be entered by typing the string value or number, boolean literal etc.
  • object pattern parameters can be specified by the model element picker cell editor.

The finalPattern example is intended to highlight the most important advantage of the IncQuery pattern language over other (EMF) query languages: it has been designed to make the specification of complex graph queries involving multiple objects and inter-relationships compact and easy-to-understand.

This example also demonstrates the usage of IncQuery add-ons by query annotations:

  • @PatternUI allows the developer to customize the appearance of a pattern match in the Query Explorer tree. Inside the message, simple path expressions (only allowing direct EAttribute access) can be used inside $$ sections to include match-specific details.
  • @ObservableValue allows the developer to customize the appearance of a match inside the Details panel. It defines an observable value (in the JFace databinding sense) which can be bound to an Eclipse/JFace UI. This annotation will also trigger the generation of an additional .databinding side-project next to your IncQuery project, which includes some helper glue code that you can use to ease the integration of IncQuery into your application.
  • @Handler triggers the generation of a .ui side-project that contains a command handler class that can be used on instance model files inside e.g. the Package Explorer view (invokable from the right-click context menu, under the EMF-IncQuery submenu). The contents of this class illustrate the usage of the pattern matcher interface code that is generated by IncQuery inside your IncQuery project (inside the src-gen folder). This interface is used to invoke queries from Java code, and they are designed to make the integration of IncQuery features into any EMF application easy and straightforward.

Another annotation add-on, @Constraint (which is used to specify IncQuery-based well-formedness rules) is discussed in a separate example page: BPMN Validation.


Generated code overview

Outdated content

The contents of this section have been superseded by the following page on the Eclipse Wiki:

http://wiki.eclipse.org/EMFIncQuery/UserDocumentation/API

We now briefly overview the source code generated by the IncQuery generator (inside the src-gen folder of your IncQuery project). This is not meant to be a very detailed description; it is recommended to check the code yourself for specific details and useful tidbits not discussed here.


Match and Matcher

package school.classesofteacher;
 
public final class ClassesOfTeacherMatch extends BasePatternMatch implements IPatternMatch {
  private Teacher fT;
  private SchoolClass fSC;
 
  public String prettyPrint() { /* ... */ }
  public int hashCode() { /* ... */ }
  public boolean equals(final Object obj) { /* ... */  }
 
  /* ... */
}
 
public class ClassesOfTeacherMatcher extends BaseGeneratedMatcher<ClassesOfTeacherMatch> implements IncQueryMatcher<ClassesOfTeacherMatch> {
  /**
   * Initializes the pattern matcher over a given EMF model root (recommended: Resource or ResourceSet).
   */
  public ClassesOfTeacherMatcher(final Notifier notifier) throws IncQueryRuntimeException { /* ... */ }
 
  /**
   * Returns the set of all matches 
   */
  public Collection<ClassesOfTeacherMatch> getAllMatches(final Teacher pT, final SchoolClass pSC) { /* ... */ }
 
  /* other getters: arbitrary match, hasMatch, countMatches */
 
  /**
   * Executes the given processor on each match of the pattern
   */
  public void forEachMatch(final Teacher pT, final SchoolClass pSC, final IMatchProcessor<? super ClassesOfTeacherMatch> processor) { /* ... */  }
 
  /**
   * Registers a new filtered delta monitor on this pattern matcher.
   * The DeltaMonitor can be used to track changes (delta) in the set of filtered pattern matches from now on.
   */
  public DeltaMonitor<ClassesOfTeacherMatch> newFilteredDeltaMonitor(final boolean fillAtStart, final Teacher pT, final SchoolClass pSC) { /* ... */ }
 
}


The two most important components of the IncQuery Java integration interface are the _Match and _Matcher classes:

  • The _Match class represents a data transfer object, corresponding to a single match of your pattern. It is basically a tuple of objects that are identified by the header parameters of the pattern.
  • The _Matcher class is the main accessor interface to IncQuery's pattern matching engine:
    • it provides means to initialize a pattern matcher for a given EMF instance model (which can either be a Resource, a ResourceSet, or an EObject -- in this latter case, the scope of the matching will be the containment tree under the passed EObject).
    • it provides getter methods to retrieve the contents of the match set anytime.
    • it provides a convenience method (forEachMatch) for easy iteration over the match set -- the most frequent usecase.


Using the Matcher

public class FinalPatternHandler extends AbstractHandler {
 
 public Object execute(ExecutionEvent event) throws ExecutionException {
  //returns the selected element
  IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event);
  Object firstElement = selection.getFirstElement();
  //the filter is set in the command declaration no need for type checking
  IFile file = (IFile)firstElement;
  //Loads the resource
  ResourceSet resourceSet = new ResourceSetImpl();
  URI fileURI = URI.createPlatformResourceURI(file.getFullPath().toString(), false);
  Resource resource = resourceSet.getResource(fileURI, true);
 
  String matches = getMatches(resource);
  //prints the match set to a dialog window 
  MessageDialog.openInformation(Display.getCurrent().getActiveShell(), "Match set of the \"finalPattern\" pattern", matches);
  return null;
}
 
 /**
  * Returns the match set of the finalPattern pattern on the input EMF resource
  * @param emfRoot the container of the EMF model on which the pattern matching is invoked
  * @return The serialized form of the match set
  */
  private String getMatches(Notifier emfRoot){
   //the match set will be serialized into a string builder
   StringBuilder builder = new StringBuilder();
   if(emfRoot != null) {
 
    // initializing matcher and applying it to emfRoot	
    FinalPatternMatcher matcher = FinalPatternMatcher.FACTORY.getMatcher(emfRoot); 
    //get all matches of the pattern
    Collection<FinalPatternMatch> matches = matcher.getAllMatches();
 
 
    //serializes the current match into the string builder
    if(matches.size() > 0)
     for(FinalPatternMatch match: matches) {
      builder.append(match.toString()); builder.append("\n");
     }
    else builder.append("The finalPattern pattern has an empty match set.");	
   }
   //returns the match set in a serialized form
   return builder.toString();
}
}


This example illustrates the most basic usage of the IncQuery API. In fact, this code is generated by the @Handler annotation add-on, and shows how to programmatically load and EMF instance model, initialize a matcher for it, and retrieve/process the results.

Back to the top