diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c38f56968aed1f151139f7f15990ec993d94057e..885b791421f5ddef4014692409e9358d7e6c507d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,6 +39,10 @@ variables: CPPINT: "" MYSQLBACKEND: "" +workflow: + rules: + - if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_REF_NAME != $CI_COMMIT_TAG + image: $CI_REGISTRY_IMAGE stages: - info @@ -70,8 +74,11 @@ build-testenv: image: docker:20.10 stage: setup timeout: 3h - only: - - schedules + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_PIPELINE_SOURCE != "schedule" + changes: + - src/test/docker/Dockerfile script: - cd src/test/docker - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY @@ -136,9 +143,8 @@ trigger_inttest: pages_prepare: &pages_prepare tags: [ cached-dind ] stage: deploy - only: - refs: - - /^release-.*$/i + rules: + - if: $CI_COMMIT_REF_NAME =~ /^release-.*$/i script: - echo "Deploying..." - make doc @@ -148,6 +154,5 @@ pages_prepare: &pages_prepare - public pages: <<: *pages_prepare - only: - refs: - - main + rules: + - if: $CI_COMMIT_REF_NAME == 'main' diff --git a/.gitlab/issue_templates/Default.md b/.gitlab/issue_templates/Default.md new file mode 100644 index 0000000000000000000000000000000000000000..aa1a65aca363b87aff50280e1a86824009d2098b --- /dev/null +++ b/.gitlab/issue_templates/Default.md @@ -0,0 +1,28 @@ +## Summary + +*Please give a short summary of what the issue is.* + +## Expected Behavior + +*What did you expect how the software should behave?* + +## Actual Behavior + +*What did the software actually do?* + +## Steps to Reproduce the Problem + +*Please describe, step by step, how others can reproduce the problem. Please try these steps for yourself on a clean system.* + +1. +2. +3. + +## Specifications + +- Version: *Which version of this software?* +- Platform: *Which operating system, which other relevant software versions?* + +## Possible fixes + +*Do you have ideas how the issue can be resolved?* diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md new file mode 100644 index 0000000000000000000000000000000000000000..35c6d01c5904289b77fc7f1de9419ef91a1510e9 --- /dev/null +++ b/.gitlab/merge_request_templates/Default.md @@ -0,0 +1,54 @@ +# Summary + +*Insert a meaningful description for this merge request here: What is the new/changed behavior? +Which bug has been fixed? Are there related issues?* + + +# Focus + +*Point the reviewer to the core of the code change. Where should they start reading? What should +they focus on (e.g. security, performance, maintainability, user-friendliness, compliance with the +specs, finding more corner cases, concrete questions)?* + + +# Test Environment + +*How to set up a test environment for manual testing?* + + +# Check List for the Author + +Please, prepare your MR for a review. Be sure to write a summary and a focus and create gitlab +comments for the reviewer. They should guide the reviewer through the changes, explain your changes +and also point out open questions. For further good practices have a look at [our review +guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md) + +- [ ] All automated tests pass +- [ ] Reference related issues +- [ ] Up-to-date CHANGELOG.md (or not necessary) +- [ ] Up-to-date JSON schema (or not necessary) +- [ ] Appropriate user and developer documentation (or not necessary) + - How do I use the software? Assume "stupid" users. + - How do I develop or debug the software? Assume novice developers. +- [ ] Annotations in code (Gitlab comments) + - Intent of new code + - Problems with old code + - Why this implementation? + + +# Check List for the Reviewer + +- [ ] I understand the intent of this MR +- [ ] All automated tests pass +- [ ] Up-to-date CHANGELOG.md (or not necessary) +- [ ] Appropriate user and developer documentation (or not necessary) +- [ ] The test environment setup works and the intended behavior is reproducible in the test + environment +- [ ] In-code documentation and comments are up-to-date. +- [ ] Check: Are there specifications? Are they satisfied? + +For further good practices have a look at [our review guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md). + + +/assign me +/target_branch dev diff --git a/CHANGELOG.md b/CHANGELOG.md index d8101a5439db6c78dcdeed2a93b7735bac324c65..0c5decad3d8be81a49dccdd4a748dc667994c587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,16 +11,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### +* 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. Issue: [#192](https://gitlab.com/caosdb/caosdb-server/-/issues/192) +* The InsertFilesInDir FlagJob now creates File entities without a name. The previous behavior + caused severe performance problems for very large numbers of files. Issue: [#197](https://gitlab.com/caosdb/caosdb-server/-/issues/197) + ### Deprecated ### ### Removed ### ### Fixed ### +* Denying a role permission has no effect + [#196](https://gitlab.com/caosdb/caosdb-server/-/issues/196). See security + notes below. +* Missing RecordType leads to unexpected server error + [#166](https://gitlab.com/caosdb/caosdb-server/-/issues/166) + ### Security ### +* Fixed [#196](https://gitlab.com/caosdb/caosdb-server/-/issues/196). This was + an error in the authorization procedure which allowed unprivileged users + execute insert, update or delete transactions on entities. However, the + unprivileged users would also need the correct entity permissions to do that. + + Without backup, this means possible data loss. Also there was the possibility + to spam the database by creating unwanted entities. + ### Documentation ### +- Nested queries. +- Global entity permissions. + ## [0.9.0] - 2023-01-19 ### Added diff --git a/CITATION.cff b/CITATION.cff index ff126ff3b04d1baec72a6c6d5cb7c4f8aff77e3b..d6e80aaa203bf09b085514ff25aa5927b7965f32 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -13,6 +13,9 @@ authors: - family-names: tom Wörden given-names: Henrik orcid: https://orcid.org/0000-0002-5549-578X + - family-names: Spreckelsen + given-names: Florian + orcid: https://orcid.org/0000-0002-6856-2910 - family-names: Parlitz given-names: Ulrich orcid: https://orcid.org/0000-0003-3058-1435 @@ -22,4 +25,4 @@ authors: title: "CaosDB - Server" version: 0.8.1 doi: 10.3390/data4020083 -date-released: 2022-11-07 \ No newline at end of file +date-released: 2022-11-07 diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index d772f4b392b782e7bcb9a82f77898402218660c1..fbcb186e45ff5bff20ca7ff20fa321f547048527 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -7,6 +7,7 @@ * `>=Java 11` * `>=Apache Maven 3.6.0` * `>=Make 4.2` +* `>=gcc 8` (if PAM authentication is required) * `libpam` (if PAM authentication is required) * More dependencies are being pulled and installed automatically by Maven. See the complete list of dependencies in the [pom.xml](pom.xml) diff --git a/README_SETUP.md b/README_SETUP.md index ce6f03d7c287f3842eac6af2d017eed5a7d8bcb5..acb3a79ad256fbba6a0d0a69887bb7c7ba7b28b9 100644 --- a/README_SETUP.md +++ b/README_SETUP.md @@ -15,12 +15,18 @@ See [DEPENDENCIES.md](DEPENDENCIES.md). On Debian, the required packages can be installed with: - apt-get install git make mariadb-server maven openjdk-11-jdk-headless \ - python3-pip screen libpam0g-dev unzip + apt-get install make mariadb-server maven openjdk-11-jdk-headless \ + python3-pip libpam0g-dev unzip Note that installing MariaDB will uninstall existing MySQL packages and vice versa. +#### Install the requirements on Fedora + +On Fedora, the required packages can be installed with: + + sudo dnf install make pam-devel mariadb-server mariadb python3 java-17-openjdk-headless unzip gcc + ### System * `>=Linux 4.0.0`, `x86_64`, e.g. Ubuntu 18.04 @@ -160,20 +166,47 @@ sources (if you called `make run` previously). ## Setup Eclipse -1. Open Eclipse (recommended version: Oxygen.1a Release (4.7.1a)) -2. `File > New > Java Project`: Choose a project name and specify the location - of this repo. The JRE and Project layout should be configured automatically. - Now, the project should initially have two source-folders: `./src/main/java` - and `./src/test/java`. After a build, another one, - `./target/generated-sources/antlr4` should be generated. If there are more - than these three source-folders, reconfigure the projects source folders - appropriately with `Project > Properties > Java Build Path > Source`. -3. In the `Package Explorer` view, right-click on the project and `Configure > - Convert to Maven Project`. -4. In the `Package Explorer` view, right-click on the project and `Maven > - Update Project`. -5. Usually a build of the project is started automatically. Otherwise `Project > - Build Project`. +1. Open Eclipse (tested with 2022-R12) +2. File > Import > Maven > Existing Maven Projects: Specify location. +3. You will most likely encounter "Plugin execution not covered by lifecycle + configuration: ..." errors. Adapt the file + `<eclipse-workspace>/.metadata/.plugins/org.eclipse.m2e.core/lifecycle-mapping-metadata.xml`. + + Example: + + ```xml + <?xml version="1.0" encoding="UTF-8"?> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>com.coveo</groupId> + <artifactId>fmt-maven-plugin</artifactId> + <versionRange>2.5.1</versionRange> + <goals> + <goal>format</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.codehaus.mojo</groupId> + <artifactId>buildnumber-maven-plugin</artifactId> + <versionRange>1.4</versionRange> + <goals> + <goal>create-metadata</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + ``` Done! diff --git a/conf/core/server.conf b/conf/core/server.conf index 57744250a7ef88285b343aa5c037de9cbbf97097..c030d6eb63d61ad2d9adc6a174877b1006396537 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -67,7 +67,7 @@ MYSQL_DATABASE_NAME=caosdb # User name for connecting to mysql MYSQL_USER_NAME=caosdb # Password for the user -MYSQL_USER_PASSWORD=caosdb +MYSQL_USER_PASSWORD=random1234 # Schema of mysql procedures and tables which is required by this CaosDB instance MYSQL_SCHEMA_VERSION=v5.0 @@ -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/doc/Query.md b/doc/Query.md index 1d7748438cd7f1a4a84f0ab66215564cbc5ad36d..1a875bf3008be0ef2b078ccae2b713d533acfaf4 100644 --- a/doc/Query.md +++ b/doc/Query.md @@ -219,6 +219,12 @@ The following query returns entities which have a _pname1_ property with any val `FIND ename WITH pname1` +`FIND ename WITH A pname1` + +`FIND ename WITH A PROPERTY pname1` + +`FIND ename WITH PROPERTY pname1` + `FIND ename . pname1` `FIND ename.pname1` @@ -338,7 +344,7 @@ Any result set can be filtered by logically combining POV filters or back refere * 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:: The marker for the beginning of the filters. Equivalent expressions: `WHICH, WHICH HAS A, WHICH HAS A PROPERTY, WHERE, WITH, .` +* WHICH:: The marker for the beginning of the filters. 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. diff --git a/src/doc/CaosDB-Query-Language.md b/src/doc/CaosDB-Query-Language.md index 0e05a56b259cb7f99cba7ad4d1dd6d1856117050..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|DELETED) (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* @@ -252,12 +266,13 @@ The following query returns entities which have a _pname1_ property with any val `FIND ename WHICH HAS BEEN CREATED BY erwin ON 2014-12-24` -`FIND ename WHICH HAS BEEN CREATED BY SOMEONE ELSE BUT erwin ON 2014-12-24` +`FIND ename WHICH HAS BEEN UPDATED BY SOMEONE ELSE BUT erwin ON 2014-12-24` -`FIND ename WHICH HAS BEEN CREATED BY erwin` +`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` @@ -310,6 +325,13 @@ One may omit the property specification: * `FIND ename1 WITH @ ename2` * `FIND ename1 . @ ename2` +### Nested queries, or filtering by sub-properties ### + +Nested queries can easily be searched by simply concatenating `WHICH` or `WITH` expressions: + +* `FIND ename WHICH HAS A pname WHICH HAS A subpname=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 logically combining POV filters or back reference filters: @@ -356,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: @@ -391,14 +413,22 @@ 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. -### Future +## Configuration -* Add `(LATEST|LAST|OLDEST|NEWEST|FIRST) VERSION OF` modifiers. -* Add `(ANY|LATEST|LAST|OLDEST|NEWEST|FIRST) VERSION (BEFORE|AFTER) (<timestamp>|<transaction id>|<entity@version>) OF` modifier. -* Add support for subproperties, e.g. `FIND ANY VERSION OF ENTITY WHICH IS REFERENCED BY ename WITH ...`. +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 - * *Sub Queries* (or *Sub Properties*): `FIND ename WHICH HAS A pname WHICH HAS A subpname=val`. This is like: `FIND AN experiment WHICH HAS A camera WHICH HAS A 'serial number'= 1234567890` - * *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.` - +* Additional versioning queries: + * Add `(LATEST|LAST|OLDEST|NEWEST|FIRST) VERSION OF` modifiers. + * Add `(ANY|LATEST|LAST|OLDEST|NEWEST|FIRST) VERSION (BEFORE|AFTER) (<timestamp>|<transaction + id>|<entity@version>) OF` modifier. + * Add support for subproperties, e.g. `FIND ANY VERSION OF ENTITY WHICH IS REFERENCED BY ename + 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/permissions.rst b/src/doc/permissions.rst index 6b07f48fa469e81069b2f01559957fb76ef27f5a..3d9c5ed7c26349ca0a20dec4e89b8889d4e10bf1 100644 --- a/src/doc/permissions.rst +++ b/src/doc/permissions.rst @@ -124,8 +124,8 @@ How to set permissions ---------------------- There are multiple ways to set role and entity permissions. The most -common and best tested way currently is to set global default entity permissions -in the ``global_entity_permissions.xml`` config file, and role-based role +common and best tested way currently is to set global default *entity* permissions +in the ``global_entity_permissions.xml`` config file, and role-based *role* permissions with the ``caosdb_admin.py`` `utility script <https://gitlab.com/caosdb/caosdb-pylib/-/blob/main/src/caosdb/utils/caosdb_admin.py>`__ of CaosDB's Python library which is also used to `manage users and @@ -138,8 +138,8 @@ find a more detailed description of the possible ways of setting permissions. you can set the default permissions that every entity on the server has. The global default permissions can **only** be set in this file; all other ways below can only change the permissions of individual entities. Note that you - can add more rules but you can never remove rules set in the - ``global_entity_permissions.xml``. Thus, it might not be possible to overrule + can add more rules in the ``global_entity_permissions.xml``, but you can not remove rules by + writing to this file. Thus, it might not be possible to overrule permissions defined here (see :ref:`Permission calculation<Calculation>`). Note also that, as the name suggests, only :ref:`entity permissions<entity-permissions>` can be set this way. The 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/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java index 0e9d7ef79377884853caa2198c84d428aed38e01..9da74edd0db86f6d81a8636020afcdaf3faa9e09 100644 --- a/src/main/java/org/caosdb/server/jobs/Job.java +++ b/src/main/java/org/caosdb/server/jobs/Job.java @@ -555,7 +555,7 @@ public abstract class Job { * * @param entity the entity to be resolved. * @return the resolved entity. - * @throws EntityWasNotUniqueException if the resolution failed due to ambuiguity of the name. + * @throws EntityWasNotUniqueException if the resolution failed due to ambiguity of the name. */ protected EntityInterface resolve(final EntityInterface entity) throws EntityWasNotUniqueException { diff --git a/src/main/java/org/caosdb/server/jobs/core/AccessControl.java b/src/main/java/org/caosdb/server/jobs/core/AccessControl.java index 4217c55e1153dafc302630823873fc75640ca9be..ede7658580811671f19f955f36faa59db4df922f 100644 --- a/src/main/java/org/caosdb/server/jobs/core/AccessControl.java +++ b/src/main/java/org/caosdb/server/jobs/core/AccessControl.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2023 IndiScale <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,17 +18,17 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.caosdb.server.accessControl.ACMPermissions; -import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity; +import org.caosdb.server.entity.DeleteEntity; import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.entity.wrapper.Parent; +import org.caosdb.server.entity.InsertEntity; +import org.caosdb.server.entity.Role; +import org.caosdb.server.entity.UpdateEntity; import org.caosdb.server.jobs.ContainerJob; import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.TransactionStage; @@ -35,6 +36,14 @@ import org.caosdb.server.transaction.Retrieve; import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; +/** + * Checks the TRANSACTION:* permissions before a transaction begins. + * + * <p>Users need TRANSACTION:INSERT:?ENTITY_ROLE? permission to insert an entity of the particular + * entity role. Likewise, they need the TRANSACTION:UPDATE or TRANSACTION:DELETE permissions. + * + * @author Timm Fitschen <f.fitschen@indiscale.com> + */ @JobAnnotation(stage = TransactionStage.INIT) public class AccessControl extends ContainerJob { @@ -46,12 +55,15 @@ public class AccessControl extends ContainerJob { super(permission, description); } - public final String toString(String entityRole) { - return toString().replace(ENTITY_ROLE_PARAMETER, entityRole); + public final String toString(Role entityRole) { + String roleString = entityRole == null ? "" : entityRole.toString(); + return toString().replace(ENTITY_ROLE_PARAMETER, roleString); } - public final String toString(String transaction, String entityRole) { - return "TRANSACTION:" + transaction + (entityRole != null ? (":" + entityRole) : ""); + public final String toString(String transaction, Role entityRole) { + return "TRANSACTION:" + + transaction + + (entityRole != null ? (":" + entityRole.toString()) : ""); } public static String init() { @@ -80,38 +92,26 @@ public class AccessControl extends ContainerJob { protected void run() { final Subject subject = SecurityUtils.getSubject(); - // subject has complete permissions for this kind of transaction - if (subject.isPermitted( - TRANSACTION_PERMISSIONS.toString(getTransaction().getClass().getSimpleName(), null))) { - return; - } - if (getTransaction() instanceof Retrieve) { return; } for (final EntityInterface e : getContainer()) { - // per role permission - if (subject.isPermitted( - TRANSACTION_PERMISSIONS.toString( - getTransaction().getClass().getSimpleName(), e.getRole().toString()))) { - continue; - } - - // special annotations permission - if (e.hasParents() && e.getParents().size() == 1) { - final Parent par1 = e.getParents().get(0); - if (par1.hasId() && !par1.getId().isTemporary()) { - execute(new RetrieveSparseEntity(par1)); + if (e instanceof InsertEntity) { + if (subject.isPermitted(INSERT.toString(e.getRole()))) { + continue; } - if (par1.hasName() - && par1.getName().equals("CommentAnnotation") - && subject.isPermitted( - getTransaction().getClass().getSimpleName() + ":CommentAnnotation")) { + } else if (e instanceof DeleteEntity) { + if (subject.isPermitted(DELETE.toString(e.getRole()))) { + continue; + } + } else if (e instanceof UpdateEntity) { + if (subject.isPermitted(UPDATE.toString(e.getRole()))) { continue; } } + e.setEntityStatus(EntityStatus.UNQUALIFIED); e.addMessage(ServerMessages.AUTHORIZATION_ERROR); } diff --git a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java index 33c7772811d36a5b9430556fc8926e88e2cd302c..a1bd800c7ca40b28d7b9ff27dd8f828ed112891f 100644 --- a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java +++ b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java @@ -119,7 +119,7 @@ public class Inheritance extends EntityJob { } // implement properties - if (getEntity().hasProperties()) { + if (getEntity().getEntityStatus() == EntityStatus.QUALIFIED && getEntity().hasProperties()) { propertyLoop: for (final Property property : getEntity().getProperties()) { final ArrayList<Property> transfer = new ArrayList<>(); diff --git a/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java b/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java index 5b5418286a81d442822aed0e7252fa4557fb21ff..6390ad7cf6da2483a1589432b2c82ba7dfd1dfae 100644 --- a/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java +++ b/src/main/java/org/caosdb/server/jobs/core/InsertFilesInDir.java @@ -60,7 +60,7 @@ import org.caosdb.server.utils.Utils; loadOnDefault = false, stage = TransactionStage.INIT, description = - "For expert users only! Risk of creating spam records!\nValue of this flag might be any directory on the servers local file system which is part of the server's back-end file storage. This job will insert every readable, nonhidden file in said directory into the database and link the file with a symlink. This is useful to add a huge amount of files without actully copying them to the back-end file storage. If you call this job on a directory more than once every file that was recently added to the source directory is inserted. Every yet known file is left untouched. \nOptional parameter -e EXCLUDE: A regular expression of files which are to be ignored. \n Optional parameter -i INCLUDE: a regular expression of files which are to be included. By default, all files are included. The -e takes precedence. \nOptional parameter -p PREFIX: Stores all new files into the directory PREFIX in the server's file system.\nOptional parameter --force-allow-symlinks: Simlinks in your data are a source of problems for the database. Therefore, simlinks are ignored by default. This option allows symlinks (but still generates simlink warnings). \nPrepend/Dry run: Call this flag with a retrieve transaction (HTTP GET) and it will only count all files and list them without actually inserting them.") + "For expert users only! Risk of creating spam records!\nValue of this flag may be any directory on the servers local file system which is part of the server's back-end file storage. This job will insert every readable, nonhidden file in said directory into the database and link the file with a symlink. This is useful to add a huge amount of files without actually copying them to the back-end file storage. If you call this job on a directory more than once every file that was recently added to the source directory is inserted. Every already known file is left untouched. \nOptional parameter -e EXCLUDE: A regular expression of files which are to be ignored. \n Optional parameter -i INCLUDE: a regular expression of files which are to be included. By default, all files are included. The -e takes precedence. \nOptional parameter -p PREFIX: Stores all new files into the directory PREFIX in the server's file system.\nOptional parameter --force-allow-symlinks: Symlinks in your data are a source of problems for the database. Therefore, simlinks are ignored by default. This option allows symlinks (but still generates simlink warnings). \nPrepend/Dry run: Call this flag with a retrieve transaction (HTTP GET) and it will only count all files and list them without actually inserting them.") public class InsertFilesInDir extends FlagJob { private File tmp = null; @@ -95,6 +95,11 @@ public class InsertFilesInDir extends FlagJob { return ret; } + /** + * Parse the value string and store the content in this Job. + * + * @return The path to the source directory as given by the flag parameter. + */ public String parseValue(String value) { String ret = value; @@ -217,7 +222,7 @@ public class InsertFilesInDir extends FlagJob { } else { i++; final String targetPath = root + sub.getName(); - final EntityInterface newFileEntity = createInsertFileEntity(sub.getName()); + final EntityInterface newFileEntity = createInsertFileEntity(); final long size = sub.length(); final FileProperties fp = new FileProperties(null, targetPath, size); newFileEntity.setFileProperties(fp); @@ -266,14 +271,13 @@ public class InsertFilesInDir extends FlagJob { * Create a new InsertEntity (if this is an actual run) or a new RetrieveEntity (in dry-run mode) * with {@link Role.File}. * - * @param name the file name * @return new File entity */ - private EntityInterface createInsertFileEntity(String name) { + private EntityInterface createInsertFileEntity() { if (getTransaction() instanceof WriteTransactionInterface) { - return new InsertEntity(name, Role.File); + return new InsertEntity((String) null, Role.File); } - EntityInterface result = new RetrieveEntity(name); + EntityInterface result = new RetrieveEntity((String) null); result.setRole(Role.File); return result; } diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4 index 99c9879de7a772c6032decc51486fe7485d869e5..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: - [Ww][Ii][Tt][Hh] 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? ; +/** */ 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 d44674b29d5c35fab2db9f4a8395e064219b79c9..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,14 +147,20 @@ entity_filter returns [EntityFilterInterface filter] )? ; +/** + * WHICH keyword and syntactic sugar. + */ which_exp: WHICH (HAS_A (PROPERTY)?)? | HAS_A (PROPERTY)? - | WITH (A (PROPERTY)?)? + | WITH_A (PROPERTY)? | WHERE | 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,14 +615,20 @@ 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 | WHERE | DOT | AND | OR | RPAREN )) + (~(WHITE_SPACE | WHICH | HAS_A | WITH_A | WHERE | DOT | AND | OR | RPAREN )) (~(WHITE_SPACE))* | 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 cf71903e1182ed41bbc8bea1233981290cf87ff4..f1abe03ef1fc1becd8b495c81c02a37c916bb12a 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..19cf9dcd5cae9431ae0dc265870acbdfb1b3e650 100644 --- a/src/test/docker/Dockerfile +++ b/src/test/docker/Dockerfile @@ -3,10 +3,13 @@ RUN apt-get update && \ apt-get install -y \ git make mariadb-server maven openjdk-11-jdk-headless \ plantuml \ + libtiff5-dev libjpeg-dev libopenjp2-7-dev zlib1g-dev \ + libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ + libharfbuzz-dev libfribidi-dev libxcb1-dev \ python3-pip screen libpam0g-dev unzip curl shunit2 \ - python3-sphinx \ - && \ - pip3 install javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml + python3-sphinx + +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()); } /**