diff --git a/CHANGELOG.md b/CHANGELOG.md index b10d028facf3449cdd68908ac2079d5dbc7aa1c1..b91d1dcac52b8b1d4596570507fce17d75d77dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### -- CQL now treats `WITH` and `WITH A` equivalently. This is for [#192](https://gitlab.com/caosdb/caosdb-server/-/issues/192). +* The default behavior of the query FIND SomeName [...] (as well as COUNT and SELECT) is being made configurable and changes + * `FIND SomeName` will be interpreted as `FIND <FIND_QUERY_DEFAULT_ROLE> + SomeName` from now on where `FIND_QUERY_DEFAULT_ROLE` is a newly introduced + server property. + * The new `FIND_QUERY_DEFAULT_ROLE` server property defaults to `RECORD` + which is why the behavior of the server api has a **breaking change**. + * The semantics of `FIND *` are affected as well. `FIND *` is equivalent to + `FIND <FIND_QUERY_DEFAULT_ROLE>`. + * Of course, administrators can choose to retain the old behavior by setting + `FIND_QUERY_DEFAULT_ROLE=ENTITY`. +* CQL now treats `WITH` and `WITH A` equivalently. This is for [#192](https://gitlab.com/caosdb/caosdb-server/-/issues/192). ### Deprecated ### diff --git a/conf/core/server.conf b/conf/core/server.conf index 57744250a7ef88285b343aa5c037de9cbbf97097..46c0f83d4b7327d84c9009bdcba10b54ee0df70f 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -211,6 +211,15 @@ USER_NAME_INVALID_MESSAGE=User names must have a length from 1 to 32 characters. PASSWORD_VALID_REGEX=^((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\\p{Punct}]).{8,128})$ PASSWORD_INVALID_MESSAGE=Passwords must have a length from 8 to 128 characters. THe must contain at least one [A-Z], one [a-z], one [0-9] and a special character e.g. [!§$%&/()=?.;,:#+*+~]. +# -------------------------------------------------- +# Query Settings +# -------------------------------------------------- + +# FIND blablabla is short for FIND $FIND_QUERY_DEFAULT_ROLE blablabla. Should be +# either ENTITY or RECORD. RECORDTYPE, FILE, and PROPERTY will work as well but +# are rather unexpected for human users. +FIND_QUERY_DEFAULT_ROLE=RECORD + # -------------------------------------------------- # Extensions # -------------------------------------------------- @@ -220,4 +229,4 @@ ENTITY_VERSIONING_ENABLED=true # Enabling the state machine extension -# EXT_STATE_ENTITY=ENABLE +# EXT_STATE_ENTITY=ENABLE \ No newline at end of file diff --git a/src/doc/CaosDB-Query-Language.md b/src/doc/CaosDB-Query-Language.md index fbccdc5554c64f4a51ed34e86798d169af37d601..a34191d051fec9907a6cbb47f11f97d60ac972ac 100644 --- a/src/doc/CaosDB-Query-Language.md +++ b/src/doc/CaosDB-Query-Language.md @@ -1,49 +1,66 @@ -# CaosDB Query Language -**WIP This is going to be the specification. CQL tutorials are in the webui** +# CaosDB Query Language Examples -## Example queries +See syntax specification in [CaosDB Query Language Syntax](query-syntax). -### Simple FIND Query -The following query will return any entity which has the name _ename_ and all its children. -`FIND ename` +## Simple FIND Query -The following queries are equivalent and will return any entity which has the name _ename_ and all its children, but only if they are genuin records. Of course, the returned set of entities (henceforth referred to as _resultset_) can also be restricted to recordtypes, properties and files. +The following query will return any record which has the name _somename_ and all +record children of any entity with that name. -`FIND RECORD ename` +`FIND somename` -`FIND RECORDS ename` +On server in the default configuration, the following queries are equivalent to this one. -Wildcards use `*` for any characters or none at all. Wildcards for single characters (like the '_' wildcard from mysql) are not implemented yet. +`FIND RECORD somename` -`FIND RECORD en*` returns any entity which has a name beginning with _en_. +`FIND RECORDS somename` -Regular expressions must be surrounded by _<<_ and '>>': +Of course, the returned set of entities (henceforth referred to as _resultset_) can also be +restricted to RecordTypes (`FIND RECORDTYPE ...`), Properties (`FIND PROPERTY ...`) and Files (`FIND +FILE ...`). -`FIND RECORD <<e[aemn]{2,5}>>` +You can include all entities (Records, RecordTypes, Properties, ...) into the results by using the +`ENTITY` keyword: -`FIND RECORD <<[cC]am_[0-9]*>>` +`FIND ENTITY somename` -*TODO* (Timm): -Describe escape sequences like `\\ `, `\*`, `\<<` and `\>>`. +Wildcards use `*` for any characters or none at all. Wildcards for single characters (like the `_` wildcard from mysql) are not implemented yet. -Currently, wildcards and regular expressions are only available for the _simple-find-part_ of the query, i. e. no wildcards/regexps for filters. +`FIND en*` returns any record which has a name beginning with _en_. -### Simple COUNT Query +Regular expressions must be surrounded by _<<_ and _>>_: -This query counts entities which have certain properties. +`FIND <<e[aemn]{2,5}>>` -`COUNT ename` -will return the number of entities which have the name _ename_ and all their children. +`FIND <<[cC]amera_[0-9]*>>` -The syntax of the COUNT queries is equivalent to the FIND queries in any respect (this also applies to wildcards and regular expressions) but one: The prefix is to be `COUNT` instead of `FIND`. +*TODO*: +Describe escape sequences like `\\\\ `, `\*`, `\<<` and `\>>`. -Unlike the FIND queries, the COUNT queries do not return any entities. The result of the query is the number of entities which _would be_ returned if the query was a FIND query. +Currently, wildcards and regular expressions are only available for the _simple-find-part_ of the +query, i.e. not for property-operator-value filters (see below). + +## Simple COUNT Query + +COUNT queries count entities which have certain properties. + +`COUNT ... rname ...` + +will return the number of records which have the name _rname_ and all record +children of any entity with that name. + +The syntax of the COUNT queries is equivalent to the FIND queries in any +respect (this also applies to wildcards and regular expressions) but one: The +prefix is to be `COUNT` instead of `FIND`. + +Unlike the FIND queries, the COUNT queries do not return any entities. The result of the query is +the number of entities which _would be_ returned if the query was a FIND query. ## Filters ### POV - Property-Operator-Value -The following queries are equivalent and will restrict the result set to entities which have a property named _pname1_ that has a value _val1_. +The following queries are equivalent and will restrict the result set to records which have a property named _pname1_ that has a value _val1_. `FIND ename.pname1=val1` @@ -53,31 +70,33 @@ The following queries are equivalent and will restrict the result set to entitie `FIND ename WHICH HAS A pname1=val1` -Again, the resultset can be restricted to records: +Again, the resultset can be restricted to any other entity role as well: -`FIND RECORD ename WHICH HAS A pname1=val1` +`FIND RECORDTYPE ename WHICH HAS A pname1=val1` _currently known operators:_ `=, !=, <=, <, >=, >` (and cf. next paragraphes!) #### Special Operator: LIKE -The _LIKE_ can be used with wildcards. The `*` is a wildcard for any (possibly empty) sequence of characters. Examples: +The _LIKE_ can be used with wildcards. The `*` is a wildcard for any (possibly empty) sequence of characters. + +Examples: -`FIND RECORD ename WHICH HAS A pname1 LIKE va*` +`FIND ename WHICH HAS A pname1 LIKE va*` -`FIND RECORD ename WHICH HAS A pname1 LIKE va*1` +`FIND ename WHICH HAS A pname1 LIKE va*1` -`FIND RECORD ename WHICH HAS A pname1 LIKE *al1` +`FIND ename WHICH HAS A pname1 LIKE *al1` -_Note:_ The _LIKE_ operator is will only produce expectable results with text properties. +_Note:_ The _LIKE_ operator will only produce expectable results with text properties. #### Special Case: References -In general a reference can be addressed just like a POV filter. So +In general a reference can be addressed just like a POV filter. So -`FIND ename1.pname1=ename2` +`FIND ename1 WITH pname1=ename2` -will also return any entity named _ename1_ which references the entity with name or id _ename2_ via a reference property named _pname1_. However, it will also return any entity with a text property of that name with the string value _ename2_. In order to restrict the result set to reference properties one may make use of special reference operators: +will also return any record named _ename1_ which references the entity with name or id _ename2_ via a reference property named _pname1_. However, it will also return any entity with a text property of that name with the string value _ename2_. In order to restrict the result set to reference properties one may make use of special reference operators: _reference operators:_ `->, REFERENCES, REFERENCE TO` @@ -88,12 +107,12 @@ The query looks like this: `FIND ename1 WHICH HAS A pname1->ename2` -#### Time Special Case: DateTime +#### Special Case: DateTime _DateTime operators:_ `=, !=, <, >, IN, NOT IN` -##### `d1=d2`: Equivalence relation. -* ''True'' iff d1 and d2 are equal in every respect (same DateTime flavor, same fields are defined/undefined and all defined fields are equal respectively). +##### `d1=d2`: Equivalence relation. +* ''True'' iff d1 and d2 are equal in every respect (same DateTime flavor, same fields are defined/undefined and all defined fields are equal respectively). * ''False'' iff they have the same DateTime flavor but have different fields defined or fields with differing values. * ''Undefined'' otherwise. @@ -103,11 +122,11 @@ Examples: * `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. +* `2015-04=2015-04` is true. -##### `d1!=d2`: Intransitive, symmetric relation. -* ''True'' iff `d1=d2` is false. -* ''False'' iff `d1=d2` is true. +##### `d1!=d2`: Intransitive, symmetric relation. +* ''True'' iff `d1=d2` is false. +* ''False'' iff `d1=d2` is true. * ''Undefined'' otherwise. Examples: @@ -116,7 +135,7 @@ Examples: * `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. +* `2015-04!=2015-04` is false ##### `d1>d2`: Transitive, non-symmetric relation. Semantics depend on the flavors of d1 and d2. If both are... @@ -175,7 +194,7 @@ Examples: * `2015 IN 2015-01-01` is false. * `2015-01-01 IN 2015-01-01T20:15:30` is false. -##### `d1 NOT IN d2`: Non-symmetric relation. +##### `d1 NOT IN d2`: Transitive, non-symmetric relation. Semantics depend on the flavors of d1 and d2. If both are... ###### [SemiCompleteDateTime](specification/Datatype.html#datetime) * ''True'' iff `d1.ILB IN d2.ILB` is false. @@ -193,7 +212,7 @@ These semantics follow a three-valued logic with ''true'', ''false'' and ''undef #### Omitting the Property or the Value -One doesn't have to specify the property or the value at all. The following query filters the result set for entities which have any property with a value greater than _val1_. +One doesn't have to specify the property or the value at all. The following query filters the result set for records which have any property with a value greater than _val1_. `FIND ename WHICH HAS A PROPERTY > val1` @@ -213,7 +232,7 @@ And for references... `FIND ename1.->ename2` -The following query returns entities which have a _pname1_ property with any value. +The following query returns records which have a _pname1_ property with any value. `FIND ename WHICH HAS A PROPERTY pname1` @@ -227,22 +246,17 @@ The following query returns entities which have a _pname1_ property with any val ### TransactionFilter +`FIND ename 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` | + 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` + 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)` - - date:: A date string of the form `YYYY-MM-DD` - - datetime:: A datetime string of the form `YYYY-MM-DD hh:mm:ss` - - time_clause:: `ON ($date|$datetime) ` Here is plenty of room for more syntactic sugar, e.g. a `TODAY` keyword, and more funcionality, e.g. ranges. - -`FIND ename WHICH ($sugar|$negated_sugar)? (NOT)? (CREATED|INSERTED|UPDATED) (by_clause time_clause?| time_clause by_clause?)` + 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* @@ -256,8 +270,9 @@ The following query returns entities which have a _pname1_ property with any val `FIND ename WHICH HAS BEEN INSERTED BY erwin` -`FIND ename . CREATED BY erwin ON ` +`FIND ename WHICH HAS BEEN INSERTED SINCE 2021-04` +Note that `SINCE` and `UNTIL` are inclusive, while `BEFORE` and `AFTER` are not. ### File Location @@ -297,7 +312,7 @@ Find any file in a directory which begins with `2016-02`: ### Back References -The back reference filters for entities that are referenced by another entity. The following query returns entities of the type _ename1_ which are referenced by _ename2_ entities via the reference property _pname1_. +The back reference filters for entities that are referenced by another entity. The following query returns records of the type _ename1_ which are referenced by _ename2_ entities via the reference property _pname1_. * `FIND ename1 WHICH IS REFERENCED BY ename2 AS A pname1` * `FIND ename1 WITH @ ename2 / pname1` @@ -363,7 +378,7 @@ Any result set can be filtered by logically combining POV filters or back refere In contrast to `FIND` queries, which always return the complete entity, there are `SELECT` queries which only return the entity with only those properties which are specified in the query. The syntax is very similar to `FIND` queries - just replace the `FIND` by `SELECT <comma separated list of selectors> FROM`: -`SELECT p1, p2, p3 FROM Record ename` +`SELECT p1, p2, p3 FROM ename` However, the `SELECT` query can also return properties of referenced entities and thereby are a means of joining entities together and return a custom view or projection: @@ -398,6 +413,15 @@ Since Caosdb 0.2 entities are optionally version controlled. The query language * The `ANY VERSION OF` modifier currently the only expression for taking the versioning into account when using the query language. * Subproperties are not supported yet, e.g. `FIND ANY VERSION OF ENTITY WHICH IS REFERENCED BY ename WITH ...`. This applies to all cases where you specify properties of *referenced* entities or *referencing* entities. +## Configuration + +In CaosDB Server implementations before version 0.9.0, the `FIND ename` query +would return any entity with that name and all children, regardless of the +entity's role. Basically, `FIND ename` *was* equivalent to `FIND ENTITY ename`. +Since 0.9.0 the default behavior has changed and now `FIND ename` is equivalent +to `FIND RECORD ename`. This default is, however, configurable via the +`FIND_QUERY_DEFAULT_ROLE` server property. See [Server Configuration](./administration/configuration.rst). + ## Future * Additional versioning queries: @@ -408,4 +432,3 @@ Since Caosdb 0.2 entities are optionally version controlled. The query language WITH ...`. * Find deleted entities: `FIND ename WHICH WAS DELETED (BY ME | ON 2014-12-24)` * *More Logic*, especially `ANY`, `ALL`, `NONE`, and `SUCH THAT` key words (and equivalents) for logical quantisation: `FIND ename1 SUCH THAT ALL ename2 WHICH HAVE A REFERENCE TO ename1 HAVE A pname=val`. This is like `FIND experiment SUCH THAT ALL person WHICH ARE REFERENCED BY THIS experiment AS conductor HAVE AN 'academic title'=professor.` - diff --git a/src/doc/conf.py b/src/doc/conf.py index c7ff480a90f6d8d7aca14bd61bfcc4afc8d79c6b..700a009370e0925a209efe66da04f18324beea69 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -17,6 +17,7 @@ # sys.path.insert(0, os.path.abspath('../caosdb')) import sphinx_rtd_theme +from os.path import dirname, abspath # -- Project information ----------------------------------------------------- @@ -48,6 +49,7 @@ extensions = [ "sphinx.ext.autosectionlabel", # Allow reference sections using its title "sphinx_rtd_theme", "sphinxcontrib.plantuml", # PlantUML diagrams + "sphinx_a4doc", # antl4 ] # Add any paths that contain templates here, relative to this directory. @@ -223,3 +225,7 @@ autodoc_default_options = { # -- Options for autosectionlabel -------------------------------------------- autosectionlabel_prefix_document = True + +# -- Options for sphinx_a4doc ------------------------------------------------ + +a4_base_path = abspath(dirname(__file__) + '/../main/java/org/caosdb/server/query') diff --git a/src/doc/index.rst b/src/doc/index.rst index b5ce9f3235277b613b357dca5dfc3334d80aeb3f..1a5e4134ef7f934ff5018cc8847603f1165ab16e 100644 --- a/src/doc/index.rst +++ b/src/doc/index.rst @@ -18,7 +18,7 @@ Welcome to caosdb-server's documentation! Changelog <CHANGELOG> specification/index.rst Glossary - API documentation<_apidoc/packages> + Server Internals<_apidoc/packages> Welcome to the CaosDB, the flexible semantic data management toolkit! diff --git a/src/doc/query-syntax.rst b/src/doc/query-syntax.rst new file mode 100644 index 0000000000000000000000000000000000000000..00df440fb9cf2a175e371c3038b164643d89a84a --- /dev/null +++ b/src/doc/query-syntax.rst @@ -0,0 +1,15 @@ +CaosDB Query Language Syntax +============================ + +This is the documentation of the CaosDB Query Language Syntax. The +authoritative specification of the syntax is the ANTLR4 grammar you can find in +the source code: +`CQLParser.g4 <https://gitlab.com/caosdb/caosdb-server/-/blob/main/src/main/java/org/caosdb/server/query/CQLParser.g4>`__ +and +`CQLLexer.g4 <https://https://gitlab.com/caosdb/caosdb-server/-/blob/main/src/main/java/org/caosdb/server/query/CQLLexer.g4>`__ + +See examples in :doc:`Query Language<CaosDB-Query-Language>`. + +.. a4:autogrammar:: CQLParser.g4 + +.. a4:autogrammar:: CQLLexer.g4 diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java index 87f5b22d55dfc11a8ae0158df85fa69d2b6cdc9d..d58250242f691498288d765dd252986eda7fb09f 100644 --- a/src/main/java/org/caosdb/server/ServerProperties.java +++ b/src/main/java/org/caosdb/server/ServerProperties.java @@ -31,6 +31,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Properties; +import org.caosdb.server.query.Query; import org.caosdb.server.utils.AbstractObservable; import org.caosdb.server.utils.Observable; import org.caosdb.server.utils.Observer; @@ -114,6 +115,7 @@ public class ServerProperties extends Properties implements Observable { public static final String KEY_QUERY_FILTER_ENTITIES_WITHOUT_RETRIEVE_PERMISSIONS = "QUERY_FILTER_ENTITIES_WITHOUT_RETRIEVE_PERMISSIONS"; + public static final String KEY_FIND_QUERY_DEFAULT_ROLE = "FIND_QUERY_DEFAULT_ROLE"; public static final String KEY_SERVER_NAME = "SERVER_NAME"; @@ -199,9 +201,25 @@ public class ServerProperties extends Properties implements Observable { logger.info(name + "=" + val); } } + + sanityCheck(serverProperties); + return serverProperties; } + private static void sanityCheck(ServerProperties serverProperties) { + try { + Query.Role.valueOf(serverProperties.getProperty(KEY_FIND_QUERY_DEFAULT_ROLE)); + } catch (IllegalArgumentException e) { + logger.error( + "Unsupported value for server property " + + KEY_FIND_QUERY_DEFAULT_ROLE + + ": " + + serverProperties.getProperty(KEY_FIND_QUERY_DEFAULT_ROLE)); + System.exit(1); + } + } + private static void loadConfigFile(final Properties serverProperties, final File confFile) throws IOException { if (confFile.exists() && confFile.isFile()) { diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4 index eba7f773a7e83360671d80f0bc3dde54e72ff625..518d1628b16ab9d0d66816b88e9b9115b765b6d4 100644 --- a/src/main/java/org/caosdb/server/query/CQLLexer.g4 +++ b/src/main/java/org/caosdb/server/query/CQLLexer.g4 @@ -21,93 +21,115 @@ */ lexer grammar CQLLexer; +/** */ AS_A: [Aa][Ss] (WHITE_SPACE_f? A)? WHITE_SPACE_f? ; +/** */ IS_REFERENCED: (IS_f WHITE_SPACE_f?)? [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee][Dd] WHITE_SPACE_f? ; +/** */ BY: [Bb][Yy] WHITE_SPACE_f? ; +/** */ fragment OF_f: [Oo][Ff] ; +/** */ fragment ANY_f: [Aa][Nn][Yy] ; +/** */ fragment VERSION_f: [Vv][Ee][Rr][Ss][Ii][Oo][Nn] ; +/** */ ANY_VERSION_OF: (ANY_f WHITE_SPACE_f VERSION_f WHITE_SPACE_f OF_f) WHITE_SPACE_f? ; +/** */ SELECT: [Ss][Ee][Ll][Ee][Cc][Tt] WHITE_SPACE_f? -> pushMode(SELECT_MODE) ; +/** */ INSERTED: [Ii][Nn][Ss][Ee][Rr][Tt][Ee][Dd] WHITE_SPACE_f? ; +/** */ CREATED: [Cc][Rr][Ee][Aa][Tt][Ee][Dd] WHITE_SPACE_f? ; +/** */ UPDATED: [Uu][Pp][Dd][Aa][Tt][Ee][Dd] WHITE_SPACE_f? ; +/** */ ON: [Oo][Nn] WHITE_SPACE_f? ; +/** */ IN: [Ii][Nn] WHITE_SPACE_f? ; +/** */ AFTER: [Aa][Ff][Tt][Ee][Rr] WHITE_SPACE_f? ; +/** */ BEFORE: [Bb][Ee][Ff][Oo][Rr][Ee] WHITE_SPACE_f? ; +/** */ UNTIL: [Uu][Nn][Tt][Ii][Ll] WHITE_SPACE_f? ; +/** */ SINCE: [Ss][Ii][Nn][Cc][Ee] WHITE_SPACE_f? ; +/** */ IS_STORED_AT: (IS_f WHITE_SPACE_f?)? [Ss][Tt][Oo][Rr][Ee][Dd] (WHITE_SPACE_f? AT)? WHITE_SPACE_f? ; +/** */ AT: [Aa][Tt] WHITE_SPACE_f? ; +/** */ FIND: [Ff][Ii][Nn][Dd] WHITE_SPACE_f? ; +/** */ COUNT: [Cc][Oo][Uu][Nn][Tt] WHITE_SPACE_f? ; +/** */ AND: ( ( @@ -117,6 +139,7 @@ AND: ) WHITE_SPACE_f? ; +/** */ OR: ( ( @@ -126,22 +149,27 @@ OR: ) WHITE_SPACE_f? ; +/** */ LPAREN: '(' WHITE_SPACE_f? ; +/** */ RPAREN: ')' WHITE_SPACE_f? ; +/** */ SINGLE_QUOTE_START: '\'' -> pushMode(SINGLE_QUOTE_MODE) ; +/** */ DOUBLE_QUOTE_START: '"' -> pushMode(DOUBLE_QUOTE_MODE) ; +/** */ OPERATOR: '=' | '<' @@ -153,115 +181,138 @@ OPERATOR: | [Rr][Ee][Ff][Ee][Rr][Ee][Nn][Cc][Ee]([Ss]|WHITE_SPACE_f? [Tt][Oo]) (WHITE_SPACE_f? A {_input.LA(1) == ' '}?)? {setText("->");} ; +/** */ LIKE: [Ll][Ii][Kk][Ee] WHITE_SPACE_f? ; +/** */ IS_NULL: IS_f WHITE_SPACE_f NULL_f WHITE_SPACE_f? ; +/** */ IS_NOT_NULL: IS_f WHITE_SPACE_f NOT_f WHITE_SPACE_f NULL_f WHITE_SPACE_f? ; +/** */ fragment NULL_f: [Nn][Uu][Ll][Ll] ; +/** */ fragment DOES_f: [Dd][Oo][Ee][Ss] ; +/** */ fragment NOT_f: [Nn][Oo][Tt] ; +/** */ fragment DOESNT_f: DOES_f WHITE_SPACE_f? NOT_f | DOES_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ fragment ISNT_f: IS_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ fragment WERE_f: [Ww][Ee][Rr][Ee] ; +/** */ fragment WERENT_f: WERE_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ fragment HAVENT_f: HAVE_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ fragment HADNT_f: HAD_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ fragment HAD_f: [Hh][Aa][Dd] ; +/** */ fragment HAVE_f: [Hh][Aa][Vv][Ee] ; +/** */ fragment HAS_f: [Hh][Aa][Ss] ; +/** */ fragment HASNT_f: HAS_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ fragment BEEN_f: [Bb][Ee][Ee][Nn] ; +/** */ fragment HAVE_A_f: HAVE_f (WHITE_SPACE? A)? ; +/** */ fragment DO_f: [Dd][Oo] ; +/** */ fragment DONT_f: DO_f NOT_f | DO_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ fragment WAS_f: [Ww][Aa][Ss] ; +/** */ fragment WASNT_f: WAS_f [Nn] SINGLE_QUOTE [Tt] ; +/** */ NEGATION: ( '!' @@ -277,46 +328,57 @@ NEGATION: ) WHITE_SPACE_f? ; +/** */ +WITH_A: + [Ww][Ii][Tt][Hh] (WHITE_SPACE_f? A)? WHITE_SPACE_f? +; + +/** */ THE: [Tt][Hh][Ee] WHITE_SPACE_f? ; +/** */ GREATEST: [Gg][Rr][Ee][Aa][Tt][Ee][Ss][Tt] WHITE_SPACE_f? ; +/** */ SMALLEST: [Ss][Mm][Aa][Ll][Ll][Ee][Ss][Tt] WHITE_SPACE_f? ; +/** */ A: [Aa][Nn]? WHITE_SPACE_f? ; +/** */ ME: [Mm][Ee] WHITE_SPACE_f? ; +/** */ SOMEONE: [Ss][Oo][Mm][Ee][Oo][Nn][Ee] WHITE_SPACE_f? ; +/** */ ELSE: [Ee][Ll][Ss][Ee] WHITE_SPACE_f? ; -WITH_A: - [Ww][Ii][Tt][Hh] (WHITE_SPACE_f? A)? WHITE_SPACE_f? -; - +/** */ WHERE: [Ww][Hh][Ee][Rr][Ee] WHITE_SPACE_f? ; +/** */ WHICH: [Ww][Hh][Ii][Cc][Hh] WHITE_SPACE_f? ; +/** */ HAS_A: ( (HAS_f | HAD_f | HAVE_f | WERE_f | WAS_f | IS_f) @@ -327,236 +389,293 @@ HAS_A: ) WHITE_SPACE_f? ; +/** */ PROPERTY: [Pp][Rr][Oo][Pp][Ee][Rr][Tt]([Yy]|[Ii][Ee][Ss]) WHITE_SPACE_f? ; +/** */ RECORDTYPE: [Rr][Ee][Cc][Oo][Rr][Dd][Tt][Yy][Pp][Ee]([Ss])? WHITE_SPACE_f? ; +/** */ RECORD: [Rr][Ee][Cc][Oo][Rr][Dd]([Ss])? WHITE_SPACE_f? ; +/** */ FILE: [Ff][Ii][Ll][Ee]([Ss])? WHITE_SPACE_f? ; +/** */ ENTITY: [Ee][Nn][Tt][Ii][Tt]([Yy]|[Ii][Ee][Ss]) WHITE_SPACE_f? ; +/** */ QUERYTEMPLATE: [Qq][Uu][Ee][Rr][yY][Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee] WHITE_SPACE_f? ; +/** */ fragment IS_f: [Ii][Ss] ; +/** */ fragment WHITE_SPACE_f: [ \t\n\r]+ ; +/** */ WHITE_SPACE: [ \t\n\r]+ ; +/** */ fragment DOUBLE_QUOTE: '"' ; +/** */ fragment SINGLE_QUOTE: '\'' ; +/** */ REGEXP_MARKER: '#' ; +/** */ REGEXP_BEGIN: '<<' ; +/** */ REGEXP_END: '>>' ; +/** */ ID: [Ii][Dd] WHITE_SPACE_f? ; +/** */ SLASH: '/' ; +/** */ STAR: '*' ; +/** */ DOT: '.' ; +/** */ QMARK: '?' WHITE_SPACE_f? ; +/** */ BUT: [Bb][Uu][Tt] WHITE_SPACE_f? ; +/** */ ESC_REGEXP_END: ESC_MARKER '>>' WHITE_SPACE_f? ; +/** */ ESC_STAR: ESC_MARKER '*' WHITE_SPACE_f? ; +/** */ ESC_BS: ESC_MARKER '\\' WHITE_SPACE_f? ; +/** */ fragment ESC_MARKER: '\\' ; +/** */ TODAY: [Tt][Oo][Dd][Aa][Yy] WHITE_SPACE_f? ; +/** */ HYPHEN: '-' ; +/** */ COLON: ':' ; +/** */ NUM: NUM_f | DOT NUM_f | NUM_f DOT NUM_f ; +/** */ NUM_f: ('0'..'9')+ ; +/** */ TXT: ('a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-' {_input.LA(1) != '>'}? | '+' | '&' | ';' | ',' | '$' | ':' | '%' | '^' | '~' {_input.LA(1) != '='}? | '`' | '´' | 'ö' | 'ä' | 'ß' | 'ü' | 'Ö' | 'Ä' | 'Ü' | '@' | '[' | ']' | '{' | '}' )+ ; +/** */ UNKNOWN_CHAR: . ; +/** */ mode SINGLE_QUOTE_MODE; + /** */ SINGLE_QUOTE_ESCAPED_CHAR: ESC_MARKER ( '\'' | '\\' | '*' ) ; + /** */ SINGLE_QUOTE_END: '\'' -> mode(DEFAULT_MODE) ; + /** */ SINGLE_QUOTE_STAR: '*' ; + /** */ SINGLE_QUOTE_ANY_CHAR: ~('\''|'\\'|'*')+ ; +/** */ mode DOUBLE_QUOTE_MODE; + /** */ DOUBLE_QUOTE_ESCAPED_CHAR: ESC_MARKER ( '"' | '\\' | '*' ) ; + /** */ DOUBLE_QUOTE_END: '"' -> mode(DEFAULT_MODE) ; + /** */ DOUBLE_QUOTE_STAR: '*' ; + /** */ DOUBLE_QUOTE_ANY_CHAR: ~('"'|'\\'|'*')+ ; +/** */ mode SELECT_DOUBLE_QUOTED; + /** */ SELECT_DOUBLE_QUOTE_ESCAPED: ESC_MARKER '"' ; + /** */ SELECT_DOUBLE_QUOTE_END: '"' WHITE_SPACE_f? {setText("");} -> mode(SELECT_MODE) ; + /** */ SELECT_DOUBLE_QUOTE_TXT: . ; +/** */ mode SELECT_SINGLE_QUOTED; + /** */ SELECT_SINGLE_QUOTE_ESCAPED: ESC_MARKER '\'' ; + /** */ SELECT_SINGLE_QUOTE_END: '\'' WHITE_SPACE_f? {setText("");} -> mode(SELECT_MODE) ; + /** */ SELECT_SINGLE_QUOTE_TXT: . ; +/** */ mode SELECT_MODE; + /** */ FROM: [Ff][Rr][Oo][Mm]([ \t\n\r])* -> mode(DEFAULT_MODE) ; + /** */ SELECT_ESCAPED: ESC_MARKER ( '"' | '\\' | '\'' | ',' | '.' ) {setText(getText().substring(1));} ; + /** */ SELECT_DOT: '.' WHITE_SPACE_f? ; + /** */ SELECT_DOUBLE_QUOTE: '"' {setText("");} -> mode(SELECT_DOUBLE_QUOTED) ; + /** */ SELECT_SINGLE_QUOTE: '\'' {setText("");} -> mode(SELECT_SINGLE_QUOTED) ; + /** */ SELECT_COMMA: ',' WHITE_SPACE_f? ; + /** */ SELECTOR_TXT: . ; diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4 index cf16baef48b5be4af25522fcf9075710a0a835a3..bcb4645ad7019d61f933793989e5d0afa5521f09 100644 --- a/src/main/java/org/caosdb/server/query/CQLParser.g4 +++ b/src/main/java/org/caosdb/server/query/CQLParser.g4 @@ -29,6 +29,9 @@ options { tokenVocab = CQLLexer; } import java.util.List; } +/** + * This is the root of the CQL grammar. + */ cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r, EntityFilterInterface filter, VersionFilter v] @init{ $s = null; @@ -56,6 +59,9 @@ cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r EOF ; +/** + * For versioned queries. + */ version returns [VersionFilter v] @init{ $v = null; @@ -64,6 +70,9 @@ version returns [VersionFilter v] ANY_VERSION_OF {$v = VersionFilter.ANY_VERSION;} ; +/** + * The comma-separated list of selected (sub)properties of a SELECT query. + */ prop_sel returns [List<Query.Selection> s] @init{ $s = new LinkedList<Query.Selection>(); @@ -73,6 +82,9 @@ prop_sel returns [List<Query.Selection> s] (SELECT_COMMA prop_subsel {$s.add($prop_subsel.sub);})* ; +/** + * The (sub-)selections (e.g. geolocation.longitude) of a SELECT query. + */ prop_subsel returns [Query.Selection sub]: selector_txt {$sub = new Query.Selection($selector_txt.text);} ( @@ -80,6 +92,9 @@ prop_subsel returns [Query.Selection sub]: )? ; +/** + * Rule for the allowed characters of a prop_subsel. + */ selector_txt: ( SELECT_DOUBLE_QUOTE @@ -94,6 +109,9 @@ selector_txt: ( SELECTOR_TXT | SELECT_ESCAPED )+ ; +/** + * The entity role. + */ role returns [Query.Role r]: RECORDTYPE {$r = Query.Role.RECORDTYPE;} | RECORD {$r = Query.Role.RECORD;} @@ -103,6 +121,9 @@ role returns [Query.Role r]: | ENTITY {$r = Query.Role.ENTITY;} ; +/** + * The filters of a FIND, SELECT, or COUNT query. + */ entity_filter returns [EntityFilterInterface filter] @init{ $filter = null; @@ -126,6 +147,9 @@ entity_filter returns [EntityFilterInterface filter] )? ; +/** + * WHICH keyword and syntactic sugar. + */ which_exp: WHICH (HAS_A (PROPERTY)?)? | HAS_A (PROPERTY)? @@ -134,6 +158,9 @@ which_exp: | DOT WHITE_SPACE? ; +/** + * Collection of different filters. + */ filter_expression returns [EntityFilterInterface efi] : backreference (subproperty {((Backreference) $backreference.ref).setSubProperty($subproperty.subp);})? {$efi = $backreference.ref;} @@ -145,6 +172,9 @@ filter_expression returns [EntityFilterInterface efi] | negation {$efi=$negation.n;} ; +/** + * ID Filter (e.g. id > 10123). + */ idfilter returns [IDFilter filter] locals [String o, String v, String a] @init{ $a = null; @@ -164,6 +194,9 @@ idfilter returns [IDFilter filter] locals [String o, String v, String a] )? ; +/** + * Transaction filter (e.g INSERTED BY ME). + */ transaction returns [TransactionFilter filter] locals [String type, TransactionFilter.Transactor user, String time, String time_op] @init{ $time = null; @@ -186,6 +219,9 @@ transaction returns [TransactionFilter filter] locals [String type, TransactionF ) ; +/** + * The transactor (e.g. "user1", or "ME"). + */ transactor returns [TransactionFilter.Transactor t] : BY @@ -200,6 +236,9 @@ transactor returns [TransactionFilter.Transactor t] ) ; +/** + * A user name or a user name pattern. + */ username returns [Query.Pattern ep] locals [int type] @init{ $type = Query.Pattern.TYPE_NORMAL; @@ -211,6 +250,9 @@ username returns [Query.Pattern ep] locals [int type] ( STAR {$type = Query.Pattern.TYPE_LIKE;} | ~(STAR | WHITE_SPACE) )+ ; +/** + * Time or timeframe of a transaction (for the transaction filter). + */ transaction_time returns [String tqp, String op] @init { $op = "("; @@ -232,9 +274,11 @@ transaction_time returns [String tqp, String op] ) ; -/* -* not fully compliant with iso 8601 (TODO) -*/ +/** + * A date time or a fragment of a date time. + * + * Not fully compliant with iso 8601 (TODO). + */ datetime : NUM // year @@ -259,6 +303,10 @@ datetime )? ; +/** + * The property-operator-value filter (e.g. temperator > 240°C) and + * (optionally) subfilters. + */ pov returns [POV filter] locals [Query.Pattern p, String o, String v, String a] @init{ $p = null; @@ -300,6 +348,9 @@ pov returns [POV filter] locals [Query.Pattern p, String o, String v, String a] ; +/** + * Wrapper for a filter on referenced entities. + */ subproperty returns [SubProperty subp] @init{ $subp = null; @@ -308,6 +359,9 @@ subproperty returns [SubProperty subp] subproperty_filter {$subp = new SubProperty($subproperty_filter.filter);} ; +/** + * The actual filter on referenced entities. + */ subproperty_filter returns [EntityFilterInterface filter] @init{ $filter = null; @@ -329,7 +383,9 @@ subproperty_filter returns [EntityFilterInterface filter] )? ; - +/** + * The backreference filter (e.g. REFERENCED BY ...). + */ backreference returns [Backreference ref] locals [Query.Pattern e, Query.Pattern p] @init{ $e = null; @@ -349,6 +405,9 @@ backreference returns [Backreference ref] locals [Query.Pattern e, Query.Pattern WHITE_SPACE? ; +/** + * Stored-at filter for the path of files. + */ storedat returns [StoredAt filter] locals [String loc] @init{ $loc = null; @@ -362,6 +421,9 @@ storedat returns [StoredAt filter] locals [String loc] WHITE_SPACE? ; +/** + * Combine other filters with a logical AND. + */ conjunction returns [Conjunction c] @init{ $c = new Conjunction(); @@ -399,6 +461,9 @@ conjunction returns [Conjunction c] )+ ; +/** + * Combine other filter with a logical OR. + */ disjunction returns [Disjunction d] @init{ $d = new Disjunction(); @@ -435,6 +500,9 @@ disjunction returns [Disjunction d] )+ ; +/** + * Negation of another filter. + */ negation returns [Negation n] @init{ } @@ -454,6 +522,9 @@ negation returns [Negation n] ) ; +/** + * An entity's full name, id, or a pattern for a name. + */ entity returns [Query.Pattern ep] : regexp_pattern {$ep = $regexp_pattern.ep;} @@ -464,6 +535,10 @@ entity returns [Query.Pattern ep] | ( ~(ENTITY |WHITE_SPACE | DOT) )+ {$ep = new Query.Pattern((String) $text.trim(), Query.Pattern.TYPE_NORMAL);} ; + +/** + * A regexp pattern. + */ regexp_pattern returns [Query.Pattern ep] locals [StringBuffer sb] @init{ $sb = new StringBuffer(); @@ -474,6 +549,9 @@ regexp_pattern returns [Query.Pattern ep] locals [StringBuffer sb] REGEXP_END {$ep = new Query.Pattern((String) $sb.toString(), Query.Pattern.TYPE_REGEXP);} ; +/** + * A like pattern (e.g. "*oxide"). + */ like_pattern returns [Query.Pattern ep] locals [StringBuffer sb] @init{ $sb = new StringBuffer(); @@ -485,6 +563,9 @@ like_pattern returns [Query.Pattern ep] locals [StringBuffer sb] {$ep = new Query.Pattern((String) $text, Query.Pattern.TYPE_LIKE);} ; +/** + * A Property's name, id or a pattern of a name. + */ property returns [Query.Pattern pp, String agg]locals [StringBuffer sb] @init{ $sb = new StringBuffer(); @@ -502,6 +583,9 @@ property returns [Query.Pattern pp, String agg]locals [StringBuffer sb] WHITE_SPACE? ; +/** + * Expression for minumum or maximum. + */ minmax returns [String agg] : (THE?? ( @@ -510,6 +594,9 @@ minmax returns [String agg] )) ; +/** + * A property value. + */ value returns [String str] : number_with_unit {$str = $number_with_unit.text;} @@ -518,6 +605,9 @@ value returns [String str] WHITE_SPACE? ; +/** + * A number with a unit (e.g. 20m). + */ number_with_unit : HYPHEN?? @@ -525,6 +615,9 @@ number_with_unit (WHITE_SPACE?? unit)? ; +/** + * A unit from a physical system of units or any other kind of system. + */ unit : (~(WHITE_SPACE | WHICH | HAS_A | WITH_A | WHERE | DOT | AND | OR | RPAREN )) @@ -533,6 +626,9 @@ unit NUM SLASH (~(WHITE_SPACE))+ ; +/** + * A files path. + */ location returns [String str] : atom {$str = $atom.ep.str;} @@ -540,6 +636,9 @@ location returns [String str] (~WHITE_SPACE)+ {$str = $text; } ; +/** + * An atomic string or pattern. + */ atom returns [Query.Pattern ep] : double_quoted {$ep = $double_quoted.ep;} @@ -547,6 +646,9 @@ atom returns [Query.Pattern ep] | (~(WHITE_SPACE | DOT | RPAREN | LPAREN ))+ {$ep = new Query.Pattern($text, Query.Pattern.TYPE_NORMAL);} ; +/** + * A single-quoted string or pattern. + */ single_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternType] @init{ $sb = new StringBuffer(); @@ -567,6 +669,9 @@ single_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternTyp SINGLE_QUOTE_END ; +/** + * A double-quoted string or pattern. + */ double_quoted returns [Query.Pattern ep] locals [StringBuffer sb, int patternType] @init{ $sb = new StringBuffer(); diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index 88da39bce24f1cabf651eebf1532cab14bddb82c..c4a458630456d057ccddf26c48def0e72ccf3789 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -55,6 +55,7 @@ import org.caosdb.server.caching.Cache; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException; import org.caosdb.server.database.backend.implementation.MySQL.MySQLHelper; +import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction; import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity; import org.caosdb.server.database.misc.DBHelper; import org.caosdb.server.database.misc.TransactionBenchmark; @@ -596,14 +597,27 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac * </ol> */ public void optimize() { - // basic optimization + // "FIND Person" is interpreted as "FIND RECORD Person" + if (this.role == null) { + try { + this.role = + Role.valueOf( + CaosDBServer.getServerProperty(ServerProperties.KEY_FIND_QUERY_DEFAULT_ROLE)); + } catch (IllegalArgumentException e) { + addError( + "Unsupportet value of the server property " + + ServerProperties.KEY_FIND_QUERY_DEFAULT_ROLE + + ": " + + CaosDBServer.getServerProperty(ServerProperties.KEY_FIND_QUERY_DEFAULT_ROLE)); + throw new UnsupportedOperationException(e); + } + } + + // "FIND *" is interpreted as "FIND RECORD", "FIND <ROLE> *" as "FIND <ROLE>" if (this.entity != null && this.entity.type == Pattern.TYPE_LIKE && this.entity.str.equals("*")) { this.entity = null; - if (this.role == null) { - this.role = Role.ENTITY; - } } } @@ -830,6 +844,10 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac this.messages.add(new Message(MessageType.Warning, MessageCode.MESSAGE_CODE_UNKNOWN, w)); } + private void addError(final String e) { + this.messages.add(new Message(MessageType.Error, MessageCode.MESSAGE_CODE_UNKNOWN, e)); + } + private void cleanUp() { if (getConnection() != null) { ResultSet rs = null; diff --git a/src/test/docker/Dockerfile b/src/test/docker/Dockerfile index f3c8b98fdab34400cccfc11f0c9211eeca37f845..ec670b7d4b43709054e9794f501aea111a059a0a 100644 --- a/src/test/docker/Dockerfile +++ b/src/test/docker/Dockerfile @@ -5,8 +5,8 @@ RUN apt-get update && \ plantuml \ python3-pip screen libpam0g-dev unzip curl shunit2 \ python3-sphinx \ - && \ - pip3 install javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml + +RUN pip3 install javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml sphinx-a4doc # Alternative, if javasphinx fails because python3-sphinx is too recent: # (`_l` not found): diff --git a/src/test/java/org/caosdb/server/query/QueryTest.java b/src/test/java/org/caosdb/server/query/QueryTest.java index 1827ceaf576bfba79059d557fa1422839cad8d28..986122ca46307173c6c9f80b2085db3f1776904a 100644 --- a/src/test/java/org/caosdb/server/query/QueryTest.java +++ b/src/test/java/org/caosdb/server/query/QueryTest.java @@ -61,20 +61,23 @@ public class QueryTest { @Test public void testGetKey() { - assertEquals("E_enameF_POV(pname,=,val1)", getCacheKey("FIND ename WITH pname = val1")); - assertEquals("E_enameF_POV(pname,=,val1)", getCacheKey("COUNT ename WITH pname = val1")); + assertEquals("R_RECORDE_enameF_POV(pname,=,val1)", getCacheKey("FIND ename WITH pname = val1")); assertEquals( - "E_enameF_POV(pname,=,val1)", getCacheKey("SELECT bla FROM ename WITH pname = val1")); - assertEquals("E_enameF_POV(pname,null,null)", getCacheKey("SELECT bla FROM ename WITH pname")); + "R_RECORDE_enameF_POV(pname,=,val1)", getCacheKey("COUNT ename WITH pname = val1")); assertEquals( - "E_enameF_maxPOV(pname,null,null)", + "R_RECORDE_enameF_POV(pname,=,val1)", + getCacheKey("SELECT bla FROM ename WITH pname = val1")); + assertEquals( + "R_RECORDE_enameF_POV(pname,null,null)", getCacheKey("SELECT bla FROM ename WITH pname")); + assertEquals( + "R_RECORDE_enameF_maxPOV(pname,null,null)", getCacheKey("SELECT bla FROM ename WITH THE GREATEST pname")); assertEquals( "R_RECORDE_enameF_POV(pname,=,val1)", getCacheKey("FIND RECORD ename WITH pname = val1")); assertEquals("R_ENTITYF_POV(pname,=,val1)", getCacheKey("COUNT ENTITY WITH pname = val1")); assertEquals( - "E_enameF_Conj(POV(pname,=,val1)POV(ename2,=,val2))", + "R_RECORDE_enameF_Conj(POV(pname,=,val1)POV(ename2,=,val2))", getCacheKey("SELECT bla FROM ename WITH pname = val1 AND ename2 = val2")); assertEquals("V_R_ENTITYF_ID(,>,2)", getCacheKey("FIND ANY VERSION OF ENTITY WITH ID > 2")); @@ -83,14 +86,14 @@ public class QueryTest { assertEquals( "R_ENTITYF_SAT(asdf/asdf)", getCacheKey("FIND ENTITY WHICH IS STORED AT asdf/asdf")); assertEquals( - "E_enameF_POV(ref1,null,null)SUB(POV(pname,>,val1)", + "R_RECORDE_enameF_POV(ref1,null,null)SUB(POV(pname,>,val1)", getCacheKey("FIND ename WITH ref1 WITH pname > val1 ")); assertEquals( - "E_enameF_@(ref1,null)SUB(POV(pname,>,val1)", + "R_RECORDE_enameF_@(ref1,null)SUB(POV(pname,>,val1)", getCacheKey("FIND ename WHICH IS REFERENCED BY ref1 WITH pname > val1 ")); assertEquals( - "U_anonymous@anonymousE_enameF_POV(pname,=,val1)", + "U_anonymous@anonymousR_RECORDE_enameF_POV(pname,=,val1)", getCacheKeyWithUser("FIND ename WITH pname = val1")); } @@ -103,7 +106,7 @@ public class QueryTest { q.optimize(); assertNull(q.getEntity()); - assertEquals(Query.Role.ENTITY, q.getRole()); + assertEquals(Query.Role.RECORD, q.getRole()); } /**