NEQL

Introduction

The main design goal of NEQL is to provide a simple mechanism for querying taggable entities from Niagara applications. BQL does not meet the need because it was designed specifically with the Niagara component space and component API in mind. BQL could not be modified to meet the goal because of the risk of breaking existing applications based on BQL.

Primary differences between NEQL and BQL

  1. NEQL only queries for tags using the Niagara 4 Taggable and Entity APIs.
  2. NEQL supports traversing defined entity relationships.
  3. NEQL supports parameterized queries.
  4. NEQL does not support the tree semantics and pathing of the Niagara component space. (ex. parent.parent)
  5. NEQL does not support projection and only returns entities so the projection if any in the NEQL are not used.
  6. NEQL does not support BFormat operations.

NEQL Grammer

<statement> := <full select> | <filter select> | <traverse>

<full select> := select <tag list> where <predicate>

<filter select> := <predicate>

<traverse> := traverse <relation> (where <predicate)

<tag list> := <tag> (, <tag>)*

<tag> := (<namespace>:)<key>

<relation> := (<namespace>:)<key><direction>

<namespace> := <word>

<key> := <word>

<direction> := ->  |  <-

<predicate> := <condOr>

<condOr> := <condAnd> (or <condAnd>)*

<condAnd> := <term> (and <term>)* 

<term> := <cmp> | <tagPath> | <not>

<cmp> := <comparable> <cmpOp> <comparable> | <like>

<like> := <tagPath> like <regex>

<cmpOp> :=  =  |  !=  |  <  |  <=  |  >  |  >=

<comparable> := <val> | <tagPath>

<val> := <number> | <bool> | <str>

<tagPath> := (<relation>)*<tag>

<not> := not <negatable> | !<negatable>

<negatable> := (<predicate>) | <tag> // note: parens around <predicate> signify actual paren characters, NOT optional syntax

<number> := <int> | <double>

<bool> := true | false

<str> := "<chars>"

<word> := <chars>

<regex> := "<chars>" // note: some regex edge cases not supported

Examples

// returns all points, in namespace "n"
n:point

// Projection is not supported in NEQL, note that results are same as n:point.
select n:name, n:displayName where n:point  

// both return all the points in namespace "n", restricted by yearBuilt tag in namespace "hs".
hs:yearBuilt >2015
select n:name, n:displayName where hs:yearBuilt > 2015

// returns all the points which was either built later than 2015 or the primary function is backup.
hs:yearBuilt >2015 or hs:primaryFunction = "backup" 

// returns only the points which are greater than 150 and less than or equal to 400.
hs:area > 150 and hs:area <= 400

// returns all the matches.
true

// no matches are returned.
false

// returns only the point whose primary function is backup.
hs:primaryFunction = "backup" 	

// matches everything.
"something"="something" 
10 < 12

// returns all entities whose grandparent's name contains the word "Basement"
// (refer to the Pattern class in the java.util.regex package for regex help)
n:parent->n:parent->n:name like ".*Basement.*"

// Known issue: if the relation path is not unique, then only first match is checked
// (e.g. if an entity has many children, this query will only return the entity if its first child has n:name = "OutsideTemp")
n:child->n:name="OutsideTemp"

Tag Expressions in NEQL

Tags are in the form of subject-predicate-object expressions. The subject denotes the resource, and the predicate denotes traits or aspects of the resource and expresses a relationship between the subject and the object. In the Niagara Component Space, BComponents are subjects, the Tag Id is the predicate and the Tag value is the object. The Id is generally displayed in the form dictionaryNamespace:name. If the dictionary is an empty string, default dictionary defined in the tag dictionary service is used. The dictionary namespace is typically a very short string, i.e., one or two characters. The Tag value is just that, i.e., the value of the tag. In Resource Description Framework terminology the thing that the Tag is applied to is the subject, the Id is the predicate and the value is the object.

** Marker tags **
Some tags may not require a value.  In this case, the fact that the subject has the tag applied
is sufficient to convey the semantic information. These tags are referred to as Marker tags.	

Example:
hs:equip is an example of marker tag. Apply this tag to all the equipment - Pump, boiler, heater etc.
hs:equip, will returns all the equipment which have this tag associated with them.


**String**
"=","!=", these operators apply here.Note that the match is case sensitive	

// list all items tagged with this implied tag
n:name

// returns component with name MyComponent
n:name = "MyComponent"

//returns all strings which contain Xing
neql:n:name like \".*Xing.*\"


**Double**
"=","!=","<","<=",">",">=", these operators apply here.

Assign hs:area tag with value 400.00 to HotWaterPump and 150.00 to CoolingTower

// returns both HotWaterPump and CoolingTower.
hs:area, returns both HotWaterPump and CoolingTower.

// returns both HotWaterPump and CoolingTower
hs:area >= 150

// returns HotWaterPump
neql:hs:area > 190

**Long**
Same as Double above.

**Integer**
Same as Double above.

Note More information about tagging and dictionaries can be found in the Tag Dictionaries documentation.

Select vs Traverse

A Select statement is a NEQL statement for selecting a collection of Entities, whereas a Traverse statement is a NEQL statement for selecting a collection of Entities by traversing a relation from either end of the relation.

** Select statement example:**
// returns everything other than Services
select name where  n:name != "Services"

// returns Cooling tower and HotWaterPump 
select name where hs:area >= 150, 	


** Traverse Statement example:**
This example returns all the outbound child relations going from parent node to all
the child nodes under it.

traverse n:child-> 

The following returns all the outbound child relations going from parent node to all
the child nodes under it while restricting the child nodes to component name with NumericWritable.

traverse n:child-> where n:name = 'NumericWritable'

NEQL Scope

Suppose that we have a station's NiagaraNetwork configured as follows:
Two stations - supervisor and another subordinate both up and running. The subordinate station
has BooleanPoint and NumericPoint. Next on the supervisor discover subordinate and add it to
supervisor station. Also discover and add the two Points - the Boolean and NumericPoint as well.
So the supervisor looks like the following:

supervisor
--Drivers
---NiagaraNetwork
//subordinate is a device
-----subordinate
-------Points
----------NumericPoint
----------BooleanPoint

// Following example limits scope to all BDevices associated with supervisor.
BOrd nDeviceSearchOrd = BOrd.make("neql: n:device");

// supervisor below, refers to the handle to the supervisor station. 
BQueryResult r = (BQueryResult)nDeviceSearchOrd.get(supervisor); 

The above result set has all the devices under the NiagaraNetwork, here in this case the
result has device, subordinate.

// Now applying "traverse n:childPoint->" on subordinate(at subordinate scope)
BOrd nTraverseChildOrd = BOrd.make("neql:traverse n:childPoint->");

//subordinate, refers to the handle to the discovered subordinate station under supervisor station.
BQueryResult r = (BQueryResult)nTraverseChildOrd.get(subordinate);

The above result has two child outbound relations, one each to NumericPoint 
and BooleanPoint from the subordinate station.

Scalar and Aggregate Functions

Not supported.

NEQL using Java

**Searching for the AHU using Java code:**
// handle to system out.
Consumer<Object> print = (output) -> System.out.println(output);

For example let's say we have a Heating System component - HSComponent. This HSComponent has lot
of things including AHU unit tagged with "ahu" and "equip".  

// only one unit with both ahu and equip tag
BOrd query = BOrd.make("neql: hs:equip and hs:ahu"); 
BQueryResult result = (BQueryResult)query.resolve(HSComponentInstance).get();
Iterator<Entity> results = result.getResults();

// print out all the entities it finds tagged with ahu and equip.
results.stream().forEach(entity -> System.out.println(((BComponent)entity).getName()));


** Facet Context Expression **
BFacets is a map of name/value pairs used to annotate a BComplex's Slot or to just provide additional metadata about something. A Context Expression is an expression that is evaluated
against the query context.  Typically the query context is the base object from the ord that
the query is part of.

//create a facet name/value integerValue50 with value of 50
BFacets facets = BFacets.make("integerValue50", 50);

BComponent root = new BComponent();
// add two childValue's with value of 50
for (int i = 0; i < 2; i++)
{
  BComponent child1 = new BComponent();
  child1.add("childValue50", BInteger.make(50));
  root.add("child?", child1);
}

// add 3 childValue's with value 100
for (int i = 0; i < 3; i++)
{
  BComponent child1 = new BComponent();
  child1.add("integerValue100", BInteger.make(100));
  root.add("child?", child1);
}

// use facet integerValue50 as context expression
BOrd query = BOrd.make("neql:childValue50 = {integerValue50}");

// search under root to see if there are any component(s) with same property value as integerValue50.
BQueryResult result = (BQueryResult)query.resolve(root, facets).get();

// Number of matching components printed will be 2, which is the number of components with value 50
print.accept("Number of matching components : " + result.stream().count());


** Traverse Out With Predicate **
// create a root component
BComponent root = new BComponent();

// add 3 child components with foo Marker to the root above
for (int i = 0; i < 3; i++)
{
  BComponent child = new BComponent();
  child.tags().set(Id.newId("n:foo"), BMarker.MARKER);
  root.add("child?", child);
  root.relations().add(Id.newId("n:child"), child, false);
}

// add 3 child components with bar Marker to the root above
for (int i = 0; i < 3; i++)
{
  BComponent child = new BComponent();
  child.tags().set(Id.newId("n:bar"), BMarker.MARKER);
  root.add("child?", child);
  root.relations().add(Id.newId("n:child"), child, false);
}

// traverse for 3 components with foo marker tag
BOrd query = BOrd.make("neql:traverse n:child -> where n:foo");
BQueryResult result = (BQueryResult)query.resolve(root).get();
print.accept("Number of components with foo marker tag:  " + result.stream().count());

// traverse for the 6 components with either foo or bar marker tag
query = BOrd.make("neql:traverse n:child -> where n:foo or n:bar");
result = (BQueryResult)query.resolve(root).get();
print.accept("Number of components with foo or bar marker tag:  " + result.stream().count());