--- last_review: "2025-01-01" last_reviewer: "-" documented_code: [ ] --- ```{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 - the [introduction to LQL](/explanation/general/linkahead_query_language.md) - the web interface tutorials [Finding data](/tutorial/webinterface/query_find.md) and [Get tabular data](/tutorial/webinterface/query_select.md). ## Query structure ## LQL queries are structured in the following way: ```{syntax:diagram} :end-class: simple - group: - comment: "Query type" - group: - comment: "Entity expression" - group: - comment: "Filters" ``` ## Formal grammar specification ## The exact definition of these parts is best reflected by these ANTLR grammars, both from the server source code: - `CQLLexer.g4` for the tokenization of the input. ([file on gitlab.com](https://gitlab.com/linkahead/linkahead-server/-/blob/main/src/main/java/org/caosdb/server/query/CQLLexer.g4?ref_type=heads)) - `CQLParser.g4` for the construction of the syntax elements ([file on gitlab.com](https://gitlab.com/linkahead/linkahead-server/-/blob/main/src/main/java/org/caosdb/server/query/CQLParser.g4?ref_type=heads)) ## 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`. ```lql 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](/explanation/general/linkahead_query_language.md), 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: ```lql 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 {term}`POV filters `, like atomic properties. This means ```lql 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: ```lql 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](/explanation/general/Datatype.md#utcdatetime) * `True` if the time of `datetime_1` is after the time of `datetime_2` according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). * `False` otherwise. ###### [SemiCompleteDateTime](/explanation/general/Datatype.md#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](/explanation/general/Datatype.md#utcdatetime) * `True` if the time of `datetime_1` is before the time of `datetime_2` according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) * `False` otherwise. ###### [SemiCompleteDateTime](/explanation/general/Datatype.md#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](/explanation/general/Datatype.md#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](/explanation/general/Datatype.md#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 {term}`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`. ```lql FIND entityname WHICH HAS A PROPERTY > value1 FIND entityname . > value1 FIND entityname.>value1 ``` And for references... ```lql 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. ```lql 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 #### ```lql 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: ```lql 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`: ```lql 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`: ```lql FIND FILE WHICH IS STORED AT /data/*/*.acq ``` Find files stored in `/data/`, ending with `.acq`: ```lql 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`: ```lql FIND FILE WHICH IS STORED AT /data/**.acq ``` Find any file in a directory which begins with `2016-02`: ```lql 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`. ```lql 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: ```lql 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: ```lql FIND entityname WHICH HAS A property WHICH HAS A subproperty=val ``` For example: ```lql 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 {term}`POV filters ` or back reference filters using logical operators: #### Conjunction (AND) As mentioned above, users can combine conditions: ```lql FIND MusicalAnalysis WHICH HAS quality_factor>0.5 AND date IN 2019 ``` Notes: - This operator can be used as `FIND WHICH AND `. - 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 WHICH ( AND ) OR `. `FIND Guitar WHICH REFERENCES Manufacturer AND price` is a combination of a reference filter and a {term}`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. ```lql 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 `!`: ```lql FIND WHICH NOT ``` There are many syntactic sugar alternatives which are treated the same as `NOT`: - `DOES NOT HAVE` - `ISN'T` - and many more ```lql 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. ```lql 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)`. ```lql FIND entityname1 WHICH HAS A property1=value1 AND DOESN'T HAVE A property2 FROM`: ```lql 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: ```lql 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 `Person`s from this property, as `helper` is not a child of `Person`. :::