Tags: advanced-user, developer, reference, query

The LinkAhead Query Language#

Users and client applications send search queries to the LinkAhead server as plain text that follows the LinkAhead Query Language (LQL) syntax. This page gives a high to medium level overview over the syntax.

For more information, see

Query structure#

LQL queries are structured in the following way:

Query typeEntity expressionFilters

Formal grammar specification#

The exact definition of these parts is best reflected by these ANTLR grammars, both from the server source code:

Conditions / Filters#

Property filters#

The result set can be restricted using Property-Operator-Value (POV) filters, which filter results based on the value of a property. The following queries are equivalent, and return records which have a property named property1 with the value value1.

FIND entityname.property1=value1
FIND entityname WITH                 property1=value1
FIND entityname WHICH HAS A          property1=value1
FIND entityname WHICH HAS A PROPERTY property1=value1

As described in the LQL Explanation, the result set can be restricted to any other entity role by modifying the entity expression:

FIND RECORDTYPE entityname WHICH HAS A property1=value1

Available basic operators: =, !=, <=, <, >=, > (see the following sections for LIKE, date‑time operators, etc.).

Special operator: LIKE#

The operator LIKE can be used with wildcards. The asterisk * is a wildcard for any (possibly empty) sequence of characters.

Examples:

FIND entityname WHICH HAS A property1 LIKE va*
FIND entityname WHICH HAS A property1 LIKE va*1
FIND entityname WHICH HAS A property1 LIKE *al1

Note

The LIKE operator is only well-defined for text properties.

Special case: references#

Reference properties can also be used for POV filters, like atomic properties. This means

FIND entityname1 WITH property1=entityname2

will return any record named entityname1 which fulfills one of these conditions:

  • It has a reference property named property1 which references an entity with name or id entityname2.

  • It has a text property property1 with the string value entityname2.

To restrict the result set to reference properties, reference operators can be used:

Reference operators: ->, REFERENCES, REFERENCE TO

The query looks like this:

FIND entityname1 WHICH HAS A property1 REFERENCE TO entityname2
FIND entityname1 WHICH HAS A property1->entityname2

Special case: DateTime#

DateTime operators: =, !=, <, >, IN, NOT IN

= Equivalence relation#

datetime_1 = datetime_2

  • True if datetime_1 and datetime_2 are equal in every respect, which means they use the same DateTime type, the same fields are defined/undefined, and all defined fields are equal respectively.

  • False if they have the same DateTime flavor, but have diferent fields defined, or corresponding defined fields have difering values.

  • Undefined otherwise.

Examples:

  • 2015-04-03=2015-04-03T00:00:00 is undefined.

  • 2015-04-03T00:00:00=2015-04-03T00:00:00.0 is undefined (second precision vs. nanosecond precision).

  • 2015-04-03T00:00:00.0=2015-04-03T00:00:00.0 is true.

  • 2015-04-03T00:00:00=2015-04-03T00:00:00 is true.

  • 2015-04=2015-05 is false.

  • 2015-04=2015-04 is true.

!= Intransitive, symmetric relation#

Inverse of equivalence: datetime_1 != datetime_2

  • True if datetime_1=datetime_2 is false.

  • False if datetime_1=datetime_2 is true.

  • Undefined otherwise.

Examples:

  • 2015-04-03!=2015-04-03T00:00:00 is undefined.

  • 2015-04-03T00:00:00!=2015-04-03T00:00:00.0 is undefined.

  • 2015-04-03T00:00:00.0!=2015-04-03T00:00:00.0 is false.

  • 2015-04-03T00:00:00!=2015-04-03T00:00:00 is false.

  • 2015-04!=2015-05 is true.

  • 2015-04!=2015-04 is false.

> Transitive, non-symmetric relation#

datetime_1 > datetime_2

Semantics depend on the flavors of datetime_1 and datetime_2. If both operands are instances of:

UTCDateTime#
  • True if the time of datetime_1 is after the time of datetime_2 according to UTC.

  • False otherwise.

SemiCompleteDateTime#

Datetime data of flavor SemiCompleteDateTime has inclusive lower bounds (ILB) and exclusive upper bounds (EUB) defined.

  • True if datetime_1.ILB > datetime_2.EUB or datetime_1.ILB = datetime_2.EUB.

  • False if datetime_1.EUB < datetime_2.ILB or datetime_1.EUB = datetime_2.ILB.

  • Undefined otherwise.

Examples:

  • 2015>2014 is true.

  • 2015-04>2014 is true.

  • 2015-01-01T20:15.00>2015-01-01T20:14 is true.

  • 2015-04>2015 is undefined.

  • 2015>2015-04 is undefined.

  • 2015-01-01T20:15>2015-01-01T20:15:15 is undefined.

  • 2014>2015 is false.

  • 2014-04>2015 is false.

  • 2014-01-01>2015-01-01T20:15:30 is false.

< Transitive, non-symmetric relation#

datetime_1 < datetime_2

Semantics depend on the flavors of datetime_1 and datetime_2 and are similar to those of >. If both operands are instances of:

UTCDateTime#
  • True if the time of datetime_1 is before the time of datetime_2 according to UTC

  • False otherwise.

SemiCompleteDateTime#

Datetime data of flavor SemiCompleteDateTime has inclusive lower bounds (ILB) and exclusive upper bounds (EUB) defined.

  • True if datetime_1.EUB < datetime_2.ILB or datetime_1.EUB = datetime_2.ILB.

  • False if datetime_1.ILB > datetime_2.EUB or datetime_1.ILB = datetime_2.EUB.

  • Undefined otherwise.

Examples:

  • 2014<2015 is true.

  • 2014-04<2015 is true.

  • 2014-01-01<2015-01-01T20:15:30 is true.

  • 2015-04<2015 is undefined.

  • 2015<2015-04 is undefined.

  • 2015-01-01T20:15<2015-01-01T20:15:15 is undefined.

  • 2015<2014 is false.

  • 2015-04<2014 is false.

  • 2015-01-01T20:15.00<2015-01-01T20:14 is false.

IN Transitive, non-symmetric relation.#

datetime_1 IN datetime_2

Semantics depend on the flavors of datetime_1 and datetime_2. If both are instances of:

SemiCompleteDateTime#
  • True if (datetime_1.ILB > datetime_2.ILB or datetime_1.ILB = datetime_2.ILB) and (datetime_1.EUB < datetime_2.EUB or datetime_1.EUB = datetime_2.EUB).

  • False otherwise.

Examples:

  • 2015-01-01 IN 2015 is true.

  • 2015-01-01T20:15:30 IN 2015-01-01 is true.

  • 2015-01-01T20:15:30 IN 2015-01-01T20:15:30 is true.

  • 2015 IN 2015-01-01 is false.

  • 2015-01-01 IN 2015-01-01T20:15:30 is false.

NOT IN Transitive, non-symmetric relation.#

datetime_1 NOT IN datetime_2

Semantics depend on the flavors of datetime_1 and datetime_2. If both are instances of:

SemiCompleteDateTime#
  • True if datetime_1.ILB IN datetime_2.ILB is false.

  • False otherwise.

Examples:

  • 2015 NOT IN 2015-01-01 is true.

  • 2015-01-01 NOT IN 2015-01-01T20:15:30 is true.

  • 2015-01-01 NOT IN 2015 is false.

  • 2015-01-01T20:15:30 NOT IN 2015-01-01 is false.

  • 2015-01-01T20:15:30 NOT IN 2015-01-01T20:15:30 is false.

Note on three-valued logic (undefined truth values)#

LQL uses three‑valued logic (true, false, undefined) for these DateTime operators. Only true is truth preserving, this means that only expressions which evaluate to true pass the POV filter.

Example: FIND ... WHICH HAS A somedate=2015-01 only returns entities for which somedate=2015-01 is true. On the other hand, FIND ... WHICH DOESN'T HAVE A somedate=2015-01 returns entities for which somedate=2015-01 is false or undefined.

In summary, NOT datetime_1 = datetime_2 is not equivalent to datetime_1 != datetime_2. The latter assertion is stronger.

Omitting the Property or the Value#

The property or the value may also be omitted. The following equivalent queries filter the result set for records which have any property with a value greater than value1.

FIND entityname WHICH HAS A PROPERTY > value1
FIND entityname . > value1
FIND entityname.>value1

And for references…

FIND entityname1 WHICH HAS A REFERENCE TO entityname2
FIND entityname1 WHICH REFERENCES entityname2
FIND entityname1 . -> entityname2
FIND entityname1.->entityname2

The following query returns records which have a property1 property with any value.

FIND entityname WHICH HAS A PROPERTY property1
FIND entityname WHICH HAS A property1
FIND entityname WITH property1
FIND entityname WITH A property1
FIND entityname WITH A PROPERTY property1
FIND entityname WITH PROPERTY property1
FIND entityname . property1
FIND entityname.property1

Transaction filters#

Transaction filters specify metadata concerning the creation or modification of entities.

Query structure#

FIND entityname WHICH (sugar|negated_sugar)? (NOT)? (CREATED|INSERTED|UPDATED) (by_clause time_clause?| time_clause by_clause?)

Definition#

sugar

HAS BEEN | HAVE BEEN | HAD BEEN | WAS | IS

negated_sugar

HAS NOT BEEN | HASN'T BEEN | WAS NOT | WASN'T | IS NOT | ISN'T | HAVN'T BEEN | HAVE NOT BEEN | HADN'T BEEN | HAD NOT BEEN

by_clause

BY (ME | username | SOMEONE ELSE (BUT ME)? | SOMEONE ELSE BUT username)

datetime

A datetime string of the form YYYY[-MM[-DD(T| )[hh[:mm[:ss[.nnn][(+|-)zzzz]]]]]] or TODAY.

time_clause

[AT|ON|IN|BEFORE|AFTER|UNTIL|SINCE] (datetime)

Examples#

FIND entityname WHICH HAS BEEN CREATED BY ME ON 2014-12-24
FIND entityname WHICH HAS BEEN CREATED BY SOMEONE ELSE ON 2014-12-24
FIND entityname WHICH HAS BEEN CREATED BY erwin ON 2014-12-24
FIND entityname WHICH HAS BEEN UPDATED BY SOMEONE ELSE BUT erwin ON 2014-12-24
FIND entityname WHICH HAS BEEN INSERTED BY erwin
FIND entityname WHICH HAS BEEN INSERTED SINCE 2021-04

Note that ON, SINCE, UNTIL are inclusive, while AT, BEFORE and AFTER are not.

File Location#

Search for file objects by their location:

FIND FILE WHICH IS STORED AT a/certain/path/

Wildcards#

STORED AT accepts Unix-style wildcards.

  • * matches any characters or none at all, except the directory separator /

  • ** matches any characters or none at all, including /.

  • A leading * is a shortcut for /**

  • Asterisks directly between two other asterisks are ignored: *** is the same as **.

  • Escape character: \ (E.g. \\ is a literal backslash. \* is a literal star. But \\* is a literal backslash followed by a wildcard.)

Examples#

Find any files ending with .acq:

FIND FILE WHICH IS STORED AT *.acq
FIND FILE WHICH IS STORED AT **.acq
FIND FILE WHICH IS STORED AT /**.acq

Find files stored one directory below /data/, ending with .acq:

FIND FILE WHICH IS STORED AT /data/*/*.acq

Find files stored in /data/, ending with .acq:

FIND FILE WHICH IS STORED AT /data/*.acq

Find files stored in a directory at any depth in the tree below /data/, ending with .acq:

FIND FILE WHICH IS STORED AT /data/**.acq

Find any file in a directory which begins with 2016-02:

FIND FILE WHICH IS STORED AT */2016-02*/*

Backwards references#

The backwards reference filters for entities that are referenced by another entity. The following query returns records of the type entityname1 which are referenced by entityname2 entities via the reference property property1.

FIND entityname1 WHICH IS REFERENCED BY entityname2 AS A property1
FIND entityname1 WITH @ entityname2 / property1
FIND entityname1 . @ entityname2 / property1

The property specification may be omitted:

FIND entityname1 WHICH IS REFERENCED BY entityname2
FIND entityname1 WHICH HAS A PROPERTY @ entityname2
FIND entityname1 WITH @ entityname2
FIND entityname1 . @ entityname2

Nested queries, or filtering by sub-properties#

Nested queries can be searched by concatenating WHICH or WITH expressions:

FIND entityname WHICH HAS A property WHICH HAS A subproperty=val

For example:

FIND AN experiment WHICH HAS A camera WHICH HAS A 'serial number'= 1234567890

Combining filters with propositional logic#

Any result set can be filtered by combining POV filters or back reference filters using logical operators:

Conjunction (AND)#

As mentioned above, users can combine conditions:

FIND MusicalAnalysis WHICH HAS quality_factor>0.5 AND date IN 2019

Notes:

  • This operator can be used as FIND <entity_expr> WHICH <filter1> AND <filter2>.

  • The character & has the same meaning and can be used instead of AND.

  • Several conditions can be chained together.

  • Mixed conjunctions and disjunctions require parentheses to define the priority. For example:
    FIND <entity_expr> WHICH (<filter1> AND <filter2>) OR <filter3>.

FIND Guitar WHICH REFERENCES Manufacturer AND price is a combination of a reference filter and a POV filter. For readability, this could also be written as FIND Guitar WHICH REFERENCES Manufacturer AND WHICH HAS A price. However, the additional WHICH HAS A is syntactic sugar, which means it is purely cosmetic and does not change the meaning of the query.

Disjunction (OR)#

The rules for disjunctions (OR or |) are the same as for conjunctions, see above.

FIND entityname1 WHICH HAS A PROPERTY property1=value1 OR A PROPERTY property2=val2 Or A PROPERTY...
FIND entityname1 WHICH HAS A PROPERTY property1=value1 OR A property2=val2 OR ...
FIND entityname1 . property1=value1 | property2=val2 | ...

Negation (NOT)#

Users can negate any filter by prefixing the filter with NOT or !:

FIND <entity_expr> WHICH NOT <filter1>

There are many syntactic sugar alternatives which are treated the same as NOT:

  • DOES NOT HAVE

  • ISN'T

  • and many more

FIND entityname1 WHICH DOES NOT HAVE A PROPERTY property1=value1
FIND entityname1 WHICH DOESN'T HAVE A property1=value1
FIND entityname1 . NOT property2=val2
FIND entityname1 . !property2=val2

Parentheses#

Users can add parentheses around filter expressions, conjunctions and disjunctions.

FIND Guitar WHICH (REFERENCES Manufacturer AND WHICH HAS A price)
FIND Guitar WHICH (REFERENCES Manufacturer) AND (WHICH HAS A price)

For better readability, the above query can be written as: FIND Guitar WHICH (REFERENCES Manufacturer AND HAS A price).

FIND entityname1
    WHICH HAS A property1=value1 AND DOESN'T HAVE A property2<val2
      AND (
            (WHICH HAS A property3=val3 AND A property4=val4)
            OR DOES NOT HAVE A
            (property5=val5 AND property6=val6)
          )
FIND entityname1 . property1=value1 & !property2<val2
    & (
        (property3=val3 & property4=val4)
        | !
        (property5=val5 & property6=val6)
      )
FIND entityname1.property1=value1&!property2<val2&((property3=val3&property4=val4)|!(property5=val5&property6=val6))

Whitespace was added for readability, but is not necessary.

A few important keywords#

A

The indistinct article. This is only syntactic sugar. Equivalent expressions: A, AN

AND

The logical and. Equivalent expressions: AND, &

FIND

The beginning of the query.

NOT

The logical negation. Equivalent expressions: NOT, DOESN'T HAVE A PROPERTY, DOES NOT HAVE A PROPERTY, DOESN'T HAVE A, DOES NOT HAVE A, DOES NOT, DOESN'T, IS NOT, ISN'T, !

OR

The logical or. Equivalent expressions: OR, |

RECORD,RECORDTYPE,FILE,PROPERTY

Role expression for restricting the result set to a specific role.

WHICH

Starts the filters section. Equivalent expressions: WHICH, WHICH HAS A, WHICH HAS A PROPERTY, WHERE, WITH (A), .

REFERENCE

This one is tricky: REFERENCE TO expresses a the state of having a reference property. REFERENCED BY expresses the state of being referenced by another entity.

COUNT

COUNT works like FIND but doesn’t return the entities.

Select queries#

In contrast to FIND queries, which always return the complete entity, SELECT queries return the entity with only those properties which are specified in the query.

The SELECT syntax is very similar to FIND queries - replace the FIND by SELECT <comma separated list of selectors> FROM:

SELECT prop1, prop2, prop3 FROM entityname

However, the SELECT query can also return properties of referenced entities and thereby may join entities together and return a custom view or projection:

SELECT Responsible.Last Name FROM Experiment

This returns the responsible person’s last name, if Responsible is a reference property of Experiment and Last Name is a property of the Responsible records.

Selectors#

Selectors are dot‑separated entity name identifiers: Responsible.Address, Responsible.Last Name or Experiment.Responsible.Last name.

Multiple selectors comma-separated: Responsible.First Name, Responsible.Last Name.

Evaluation of selectors#

The query will return all those properties which have the same name as specified by the selectors (case-insensitive). However, SELECT queries are also capable of sub-typing in the selectors:

SELECT Person FROM Experiment returns all Person properties, and also all Responsible properties, if Responsible is a child RecordType of Person.

Note

If Experiment instead has a property responsible_person with data type Person, the above SELECT statement does not include Persons from this property, as helper is not a child of Person.