diff --git a/CHANGELOG.md b/CHANGELOG.md index ddaf607e7586d27099da61004dd1f911569d5b91..3567f09ce83e8e25452e6a71e1af54be2735a2ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0] - 2023-01-19 + +### Added + +* SELECT query support for the GRPC API + ## [0.8.1] - 2022-11-07 (Timm Fitschen) diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 0f380b923c39129a7f29e380436857a3e9c6d8f6..d772f4b392b782e7bcb9a82f77898402218660c1 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -2,7 +2,7 @@ ## For Building and Running the Server -* `>=caosdb-proto 0.2.0` +* `>=caosdb-proto 0.3.0` * `>=caosdb-mysqlbackend 5.0.0` * `>=Java 11` * `>=Apache Maven 3.6.0` diff --git a/FEATURES.md b/FEATURES.md index 485172f0dd8c930e36a7c216d0d8a9dd7e8086f3..2c046daee0518eb12e4de807a7ffe165c74d8664 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -1,6 +1,6 @@ # Features -* The CaosDB Server implements a CaosDB GRPC API Endpoint (v0.2.0) +* The CaosDB Server implements a CaosDB GRPC API Endpoint (v0.3.0) Authentication is supported via a Basic scheme, using the well-known "authentication" header. Notable limitations of the current implementation of the API: diff --git a/README.md b/README.md index 62da38e86df18a34deddf7c31a651dd12d81b7e8..3aac5df2153281a9ef67a203956114aad1641b18 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ when creating the merge request. This allows our team to work with you on your r - If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-server/), the preferred way is also a merge request as describe above (the documentation resides in `src/doc`). However, you can also create an issue for it. -- You can also contact us at **info (AT) caosdb.de** and join the +- You can also contact us at **info (AT) caosdb.org** and join the CaosDB community on [#caosdb:matrix.org](https://matrix.to/#/!unwwlTfOznjEnMMXxf:matrix.org). diff --git a/README_SETUP.md b/README_SETUP.md index ad4dc1bfd087d235aa903dee9183b78bc6ff3094..ce6f03d7c287f3842eac6af2d017eed5a7d8bcb5 100644 --- a/README_SETUP.md +++ b/README_SETUP.md @@ -211,7 +211,7 @@ Stand-alone documentation is built using Sphinx: `make doc` (`l_` not found): ```sh -git clone git@github.com:simgrid/javasphinx.git +git clone https://github.com/simgrid/javasphinx.git cd javasphinx git checkout 659209069603a pip3 install . diff --git a/RELEASE_GUIDELINES.md b/RELEASE_GUIDELINES.md index fff7dac583213ca4d1b79c68dbd66cdee83ed78b..58484d51565a87d98929a37e842c7040fc08cabf 100644 --- a/RELEASE_GUIDELINES.md +++ b/RELEASE_GUIDELINES.md @@ -43,5 +43,5 @@ guidelines of the CaosDB Project ```md # Changelog -[See full changelog](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/v0.7.3/CHANGELOG.md) +[See full changelog](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/${TAG}/CHANGELOG.md) ``` diff --git a/caosdb-proto b/caosdb-proto index c439aa40a30c9214db315018b84fa50112c9251e..c6405e538c179d2a8af952f85d9e9dc51fbadb92 160000 --- a/caosdb-proto +++ b/caosdb-proto @@ -1 +1 @@ -Subproject commit c439aa40a30c9214db315018b84fa50112c9251e +Subproject commit c6405e538c179d2a8af952f85d9e9dc51fbadb92 diff --git a/caosdb-webui b/caosdb-webui index a3767124c9400da1418d87fe7efdb09a630acca4..421b2dce5199a5a2c96bcc638543c1fd51d48870 160000 --- a/caosdb-webui +++ b/caosdb-webui @@ -1 +1 @@ -Subproject commit a3767124c9400da1418d87fe7efdb09a630acca4 +Subproject commit 421b2dce5199a5a2c96bcc638543c1fd51d48870 diff --git a/pom.xml b/pom.xml index bf7e8c795a447e76de06a413a14efde55badf5eb..ea47a9e516122019c06146a61538b5957d1d5b0f 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.caosdb</groupId> <artifactId>caosdb-server</artifactId> - <version>0.8.1</version> + <version>0.9.0</version> <packaging>jar</packaging> <name>CaosDB Server</name> <scm> diff --git a/src/doc/CaosDB-Query-Language.md b/src/doc/CaosDB-Query-Language.md index ffa889c6b8acd0cd635049ed2aaceb748d56f2a9..0e05a56b259cb7f99cba7ad4d1dd6d1856117050 100644 --- a/src/doc/CaosDB-Query-Language.md +++ b/src/doc/CaosDB-Query-Language.md @@ -120,11 +120,11 @@ Examples: ##### `d1>d2`: Transitive, non-symmetric relation. Semantics depend on the flavors of d1 and d2. If both are... -###### [UTCDateTime](Datatype#datetime) +###### [UTCDateTime](specification/Datatype.html#datetime) * ''True'' iff the time of d1 is after the the time of d2 according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). * ''False'' otherwise. -###### [SemiCompleteDateTime](Datatype#datetime) +###### [SemiCompleteDateTime](specification/Datatype.html#datetime) * ''True'' iff `d1.ILB>d2.EUB` is true or `d1.ILB=d2.EUB` is true. * ''False'' iff `d1.EUB<d2.ILB}} is true or {{{d1.EUB=d2.ILB` is true. * ''Undefined'' otherwise. @@ -142,11 +142,11 @@ Examples: ##### `d1<d2`: Transitive, non-symmetric relation. Semantics depend on the flavors of d1 and d2. If both are... -###### [UTCDateTime](Datatype#datetime) +###### [UTCDateTime](specification/Datatype.html#datetime) * ''True'' iff the time of d1 is before the the time of d2 according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) * ''False'' otherwise. -###### [SemiCompleteDateTime](Datatype#datetime) +###### [SemiCompleteDateTime](specification/Datatype.html#datetime) * ''True'' iff `d1.EUB<d2.ILB` is true or `d1.EUB=d2.ILB` is true. * ''False'' iff `d1.ILB>d2.EUB}} is true or {{{d1.ILB=d2.EUB` is true. * ''Undefined'' otherwise. @@ -164,7 +164,7 @@ Examples: ##### `d1 IN d2`: Transitive, non-symmetric relation. Semantics depend on the flavors of d1 and d2. If both are... -###### [SemiCompleteDateTime](Datatype#datetime) +###### [SemiCompleteDateTime](specification/Datatype.html#datetime) * ''True'' iff (`d1.ILB>d2.ILB` is true or `d1.ILB=d2.ILB` is true) and (`d1.EUB<d2.EUB` is true or `d1.EUB=d2.EUB` is true). * ''False'' otherwise. @@ -177,7 +177,7 @@ Examples: ##### `d1 NOT IN d2`: Non-symmetric relation. Semantics depend on the flavors of d1 and d2. If both are... -###### [SemiCompleteDateTime](Datatype#datetime) +###### [SemiCompleteDateTime](specification/Datatype.html#datetime) * ''True'' iff `d1.ILB IN d2.ILB` is false. * ''False'' otherwise. diff --git a/src/doc/conf.py b/src/doc/conf.py index 315c47a024cc0db50870256c2ccdd0cde2a22a81..b0c958c03bbb23b9642a36f91a4d62cbe373f6e6 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -25,9 +25,9 @@ copyright = '2022, IndiScale GmbH' author = 'Daniel Hornung, Timm Fitschen' # The short X.Y version -version = '0.8.1' +version = '0.9.0' # The full version, including alpha/beta/rc tags -release = '0.8.1' +release = '0.9.0' # -- General configuration --------------------------------------------------- diff --git a/src/doc/entities.png b/src/doc/entities.png index ba3e111a78ddec275636ff0b7e8b324edb13f7be..9f0247eb98ca33d4b62168c34ae7d8564f6fc394 100644 Binary files a/src/doc/entities.png and b/src/doc/entities.png differ diff --git a/src/doc/entities.svg b/src/doc/entities.svg new file mode 100644 index 0000000000000000000000000000000000000000..63251e31297f4ef480b497a089987e79296f9534 --- /dev/null +++ b/src/doc/entities.svg @@ -0,0 +1,310 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="257.60461mm" + height="92.070786mm" + viewBox="0 0 257.60462 92.070786" + version="1.1" + id="svg8" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + sodipodi:docname="entities.svg" + inkscape:export-filename="/home/daniel/indiscale/software/linkahead/caosdb-server/doc/entities.png" + inkscape:export-xdpi="138.03999" + inkscape:export-ydpi="138.03999"> + <defs + id="defs2"> + <marker + style="overflow:visible" + id="marker1306" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="EmptyTriangleOutM" + inkscape:isstock="true"> + <path + transform="matrix(0.4,0,0,0.4,-1.8,0)" + style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 5.77,0 -2.88,5 V -5 Z" + id="path1304" /> + </marker> + <marker + style="overflow:visible" + id="marker1244" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="EmptyTriangleOutM" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="matrix(0.4,0,0,0.4,-1.8,0)" + style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 5.77,0 -2.88,5 V -5 Z" + id="path1242" /> + </marker> + <marker + style="overflow:visible" + id="marker1184" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="EmptyTriangleOutM" + inkscape:isstock="true"> + <path + transform="matrix(0.4,0,0,0.4,-1.8,0)" + style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 5.77,0 -2.88,5 V -5 Z" + id="path1182" /> + </marker> + <marker + style="overflow:visible" + id="EmptyTriangleOutM" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="EmptyTriangleOutM" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="matrix(0.4,0,0,0.4,-1.8,0)" + style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 5.77,0 -2.88,5 V -5 Z" + id="path1029" /> + </marker> + <marker + style="overflow:visible" + id="Arrow1Lstart" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Lstart" + inkscape:isstock="true"> + <path + transform="matrix(0.8,0,0,0.8,10,0)" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path866" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.98994949" + inkscape:cx="456.88236" + inkscape:cy="211.12615" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1135" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:document-rotation="0" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(200.30478,-69.702334)"> + <path + style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#EmptyTriangleOutM)" + d="m -164.37076,125.68023 c 0,0 7.98157,-12.50783 14.18443,-16.03726 16.03406,-9.12339 40.17589,5.10656 52.013732,-9.04239 2.255094,-2.695361 2.46672,-11.843345 2.46672,-11.843345" + id="path962" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cssc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker1244)" + d="m -96.257355,126.19649 c 0.965381,-4.15542 1.084194,-8.26873 7.5598,-11.26854 6.807025,-3.15335 7.474053,-2.81327 8.712136,-12.65522 0.438632,-3.486838 0.252106,-13.515495 0.252106,-13.515495" + id="path1240" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cssc" /> + <g + id="g41" + transform="translate(0,-26.458334)"> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.39511;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect835" + width="54.226971" + height="16.809242" + x="-98.980247" + y="96.858223" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="-87.690086" + y="108.18256" + id="text817"><tspan + sodipodi:role="line" + id="tspan815" + x="-87.690086" + y="108.18256" + style="stroke-width:0.264583">Entity</tspan></text> + </g> + <rect + style="fill:#5bb374;fill-opacity:1;stroke:#000000;stroke-width:1.39511;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect835-3" + width="69.995872" + height="16.809242" + x="-199.60722" + y="125.42733" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="-196.9794" + y="136.75168" + id="text821"><tspan + sodipodi:role="line" + id="tspan819" + x="-196.9794" + y="136.75168" + style="stroke-width:0.264583">Record Type</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker1306)" + d="m -35.372097,126.19649 c -0.965381,-4.15542 -1.084194,-8.26873 -7.5598,-11.26854 -6.807025,-3.15335 -19.11572,-2.81327 -20.353803,-12.65522 -0.438632,-3.486838 -0.475048,-13.515495 -0.475048,-13.515495" + id="path1302" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cssc" /> + <rect + style="fill:#e25b58;fill-opacity:1;stroke:#000000;stroke-width:1.39511;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect835-5" + width="49.950661" + height="16.809242" + x="-115.86278" + y="125.42733" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="-109.21966" + y="137.77745" + id="text825"><tspan + sodipodi:role="line" + id="tspan823" + x="-109.21966" + y="137.77745" + style="stroke-width:0.264583">Record</tspan></text> + <rect + style="fill:#5c6de1;fill-opacity:1;stroke:#000000;stroke-width:1.39511;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect835-7" + width="52.356087" + height="16.809242" + x="-52.16354" + y="125.42733" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="-48.754131" + y="136.5889" + id="text829"><tspan + sodipodi:role="line" + id="tspan827" + x="-48.754131" + y="136.5889" + style="stroke-width:0.264583">Property</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker1184)" + d="m 33.047535,126.20395 c 0,0 -7.981575,-12.50783 -14.184434,-16.03726 -16.0340604,-9.12339 -52.346724,5.10656 -64.184564,-9.04239 -2.25509,-2.695361 -2.46672,-12.221321 -2.46672,-12.221321" + id="path1180" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cssc" /> + <rect + style="fill:#d87ac8;fill-opacity:1;stroke:#000000;stroke-width:1.39511;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect835-6" + width="41.932575" + height="16.809235" + x="13.941126" + y="125.42734" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="25.812365" + y="137.77745" + id="text833"><tspan + sodipodi:role="line" + id="tspan831" + x="25.812365" + y="137.77745" + style="stroke-width:0.264583">File</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.35px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="-164.62788" + y="152.51477" + id="text1001"><tspan + sodipodi:role="line" + id="tspan1003" + x="-164.62788" + y="152.51477">"blue print" </tspan><tspan + sodipodi:role="line" + id="tspan1005" + x="-164.62788" + y="160.45227">of data objects</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.35px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="-90.906052" + y="152.51477" + id="text1001-5"><tspan + sodipodi:role="line" + id="tspan843" + x="-90.906052" + y="152.51477">actual instances</tspan><tspan + sodipodi:role="line" + id="tspan845" + x="-90.906052" + y="160.45227">of data objects</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.35px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="-26.117271" + y="152.51477" + id="text1001-6"><tspan + sodipodi:role="line" + id="tspan1050" + x="-26.117271" + y="152.51477">Records can </tspan><tspan + sodipodi:role="line" + id="tspan1052" + x="-26.117271" + y="160.45227">have Properties</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.35px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="35.023685" + y="153.13023" + id="text1001-9"><tspan + sodipodi:role="line" + id="tspan1118" + x="35.023685" + y="153.13023">representation </tspan><tspan + sodipodi:role="line" + id="tspan1120" + x="35.023685" + y="161.06773">of files</tspan></text> + </g> +</svg> diff --git a/src/doc/roles.md b/src/doc/roles.md index a036b8e4ee39290fcf4b82634bde799464e04c2e..838b3d9ec92626676c2ca16535496620bedd5842 100644 --- a/src/doc/roles.md +++ b/src/doc/roles.md @@ -32,4 +32,4 @@ There are some special roles, which are automatically assigned to users: Except for the `anonymous` role, these special roles are not returned by the server, but can nevertheless be used to define -[permissions](permissions). +[permissions](permissions.html#permissions). diff --git a/src/doc/specification/AbstractProperty.md b/src/doc/specification/AbstractProperty.md index dee88e665c1609224bdab185ea147b91c49dc8f3..3a2fe7583480c7467bccb0a2bf94850ea30d7be4 100644 --- a/src/doc/specification/AbstractProperty.md +++ b/src/doc/specification/AbstractProperty.md @@ -132,14 +132,13 @@ Any xml representation of an `AbstractProperty` that is to be posted to the Caos #### Get all *Request:* - GET http://localhost:8122/mpidsserver/AbstractProperty/ - GET http://localhost:8122/mpidsserver/AbstractProperty + GET http://localhost:8122/server/AbstractProperty/ + GET http://localhost:8122/server/AbstractProperty *Response:* <?xml version="1.0" encoding="UTF-8"?> <Response /> -[View Ticket #2](http://dev.bmp.ds.mpg.de/heartdb/ticket/2) #### Erroneous Requests ##### Non-existing *Request:* diff --git a/src/doc/specification/Datatype.md b/src/doc/specification/Datatype.md index ffc9794ea36cfe748e8c11cee2bcd32adc8ebe0a..6a169042dce2be2e6dc939d0935f3336de264308 100644 --- a/src/doc/specification/Datatype.md +++ b/src/doc/specification/Datatype.md @@ -43,7 +43,7 @@ ---- ## DATETIME -The DateTime data type exists in (currently) three flavors which are dynamically chosen during parsing on the the serverside. The flavors have different ranges, support of time zones and intended use cases. Only the first two flavors are actually implemented for storage and queries. The third one is implemented for queries exclusively. +The DateTime data type exists in (currently) three flavors which are dynamically chosen during parsing on the serverside. The flavors have different ranges, support of time zones and intended use cases. Only the first two flavors are actually implemented for storage and queries. The third one is implemented for queries exclusively. ### UTCDateTime * Description: This DATETIME flavor stores values which represent a single point of time according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) with the format specified by [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) (Combined date and time). It does support [UTC Leap Seconds](https://en.wikipedia.org/wiki/Leap_second) and time zones. @@ -55,7 +55,7 @@ The DateTime data type exists in (currently) three flavors which are dynamically * It is allowed to ommit the nanosecond part of a UTCDateTime (`2016-01-01T13:23:00CEST`). This indicates a precision of seconds for a UTCDateTime value. ### Date - Description:: This DATETIME flavor stores values which represent a single date, month or year according to the [gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_Calendar). A month/year is conceived as a single date with the presion of a month/year. This concept is useful if you try to understand the query semantics which are explained [elsewhere](./QueryLanguage#POVDateTime). + Description:: This DATETIME flavor stores values which represent a single date, month or year according to the [gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_Calendar). A month/year is conceived as a single date with the presion of a month/year. This concept is useful if you try to understand the query semantics which are explained [elsewhere](../CaosDB-Query-Language.html#pov-property-operator-value). Format:: `Y[YYY][-MM[-dd]]` (where square brackets mean that the expression is optional). Range:: Any valid date according to the gregorian calendar from `-9999-01-01` to `9999-12-31` (and respective dates with lower precision. E.g. the year `-9999`). There is no year `0`. * Note: Date is a specialization of [#SemiCompleteDateTime]. @@ -73,7 +73,7 @@ Please file a new feature request as soon as you need them. ---- ## REFERENCE -* Description: REFERENCE values store the [Valid ID](./Glossary#valid-id) of an existing entity. The are useful to establish links between two entities. +* Description: REFERENCE values store the [Valid ID](../Glossary#valid-id) of an existing entity. The are useful to establish links between two entities. * Accepted Values: Any [Valid ID](./Glossary#valid-id) or [Valid Unique Existing Name](./Glossary#valid-unique-existing-name) or [Valid Unique Temporary ID](./Glossary#valid-unique-temporary-id) or [Valid Unique Prospective Name](./Glossary#valid-unique-prospective-pame). * Note: * After beeing processed successfully by the server the REFERENCE value is normalized to a [Valid ID](./Glossary#valid-id). I.e. it is guaranteed that a REFERENCE value of a valid property is a positive integer. diff --git a/src/doc/specification/RecordType.rst b/src/doc/specification/RecordType.rst index 0c196fea86e74f1014e34c0bd1e5b096acd1a5d9..53e8e1613dddeea901e1b8404f186d67ebc2be23 100644 --- a/src/doc/specification/RecordType.rst +++ b/src/doc/specification/RecordType.rst @@ -190,8 +190,6 @@ Get all <?xml version="1.0" encoding="UTF-8"?> <Response /> -[View Ticket #2](http://caosdb.example.com/caosdb/ticket/2) - POST Requests ~~~~~~~~~~~~~ diff --git a/src/main/java/org/caosdb/server/datatype/BooleanDatatype.java b/src/main/java/org/caosdb/server/datatype/BooleanDatatype.java index 92c3440aeab6ef1cc86426c4e5d2356aef6720aa..366b77937d3dcf020d98c035821bed0aedaadf8d 100644 --- a/src/main/java/org/caosdb/server/datatype/BooleanDatatype.java +++ b/src/main/java/org/caosdb/server/datatype/BooleanDatatype.java @@ -32,6 +32,8 @@ public class BooleanDatatype extends AbstractDatatype { public SingleValue parseValue(final Object value) throws Message { if (value instanceof BooleanValue) { return (SingleValue) value; + } else if (value instanceof CollectionValue) { + throw ServerMessages.DATA_TYPE_DOES_NOT_ACCEPT_COLLECTION_VALUES; } else if (value instanceof SingleValue) { return parse(((SingleValue) value).toDatabaseString()); } else { diff --git a/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java b/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java index e9e2526521d044fc89746eb0e3204de97f473e0c..f3f17ace28802cdb045a4cd66a7ca2a78cde2d1c 100644 --- a/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java +++ b/src/main/java/org/caosdb/server/datatype/DoubleDatatype.java @@ -34,6 +34,8 @@ public class DoubleDatatype extends AbstractDatatype { try { if (value instanceof GenericValue) { return parse(((GenericValue) value).toDatabaseString()); + } else if (value instanceof CollectionValue) { + throw ServerMessages.DATA_TYPE_DOES_NOT_ACCEPT_COLLECTION_VALUES; } else { return parse(value.toString()); } diff --git a/src/main/java/org/caosdb/server/datatype/IntegerDatatype.java b/src/main/java/org/caosdb/server/datatype/IntegerDatatype.java index 0b1116ed5cbc135f846b5c7fbc0e1d497576380f..f3a58a4a52b888e00ec80f71793cd452c4b98910 100644 --- a/src/main/java/org/caosdb/server/datatype/IntegerDatatype.java +++ b/src/main/java/org/caosdb/server/datatype/IntegerDatatype.java @@ -33,6 +33,8 @@ public class IntegerDatatype extends AbstractDatatype { try { if (value instanceof GenericValue) { return new GenericValue(Long.parseLong(((GenericValue) value).toDatabaseString())); + } else if (value instanceof CollectionValue) { + throw ServerMessages.DATA_TYPE_DOES_NOT_ACCEPT_COLLECTION_VALUES; } else { return new GenericValue(Long.parseLong(value.toString())); } diff --git a/src/main/java/org/caosdb/server/datatype/TextDatatype.java b/src/main/java/org/caosdb/server/datatype/TextDatatype.java index af06ed6de1f0f49b481b2a706f529037e555785e..3b32fadcb3467528ac377e67db1081295f85c7ec 100644 --- a/src/main/java/org/caosdb/server/datatype/TextDatatype.java +++ b/src/main/java/org/caosdb/server/datatype/TextDatatype.java @@ -23,6 +23,7 @@ package org.caosdb.server.datatype; import org.caosdb.server.entity.Message; +import org.caosdb.server.utils.ServerMessages; @DatatypeDefinition(name = "Text") public class TextDatatype extends AbstractDatatype { @@ -31,6 +32,8 @@ public class TextDatatype extends AbstractDatatype { public SingleValue parseValue(final Object value) throws Message { if (value instanceof GenericValue) { return (GenericValue) value; + } else if (value instanceof CollectionValue) { + throw ServerMessages.DATA_TYPE_DOES_NOT_ACCEPT_COLLECTION_VALUES; } return new GenericValue(value.toString()); } diff --git a/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java b/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java index 289287c82fcd32092d15cb1f10a9dbbd83e77fde..d396345508a028700db6fd8e973390fd634e54d9 100644 --- a/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java +++ b/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java @@ -34,6 +34,7 @@ import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Message.MessageType; import org.caosdb.server.entity.xml.ToElementable; import org.caosdb.server.jobs.JobTarget; +import org.caosdb.server.query.Query; import org.caosdb.server.utils.EntityStatus; import org.jdom2.Element; @@ -120,6 +121,7 @@ public class TransactionContainer extends Container<EntityInterface> private HashMap<String, FileProperties> files = new HashMap<String, FileProperties>(); private TransactionBenchmark benchmark; + private Query query; public void setFiles(final HashMap<String, FileProperties> files) { this.files = files; @@ -183,4 +185,12 @@ public class TransactionContainer extends Container<EntityInterface> addMessage(new Message(MessageType.Error, m.getCode(), m.getDescription(), m.getBody())); } } + + public void setQuery(Query query) { + this.query = query; + } + + public Query getQuery() { + return query; + } } diff --git a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java index fc3c26f664cd81ec27139c2591f52be7af29c3bd..6fef590746bbf2c3ff802baec73385e4b061ec95 100644 --- a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java +++ b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java @@ -45,9 +45,14 @@ import org.caosdb.api.entity.v1.MessageCode; import org.caosdb.api.entity.v1.Parent; import org.caosdb.api.entity.v1.ReferenceDataType; import org.caosdb.api.entity.v1.ScalarValue; +import org.caosdb.api.entity.v1.SelectQueryColumn; +import org.caosdb.api.entity.v1.SelectQueryHeader; +import org.caosdb.api.entity.v1.SelectQueryResult; +import org.caosdb.api.entity.v1.SelectQueryRow; import org.caosdb.api.entity.v1.SpecialValue; import org.caosdb.api.entity.v1.Version; import org.caosdb.datetime.DateTimeInterface; +import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.AbstractDatatype; import org.caosdb.server.datatype.BooleanDatatype; @@ -71,10 +76,13 @@ import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Role; import org.caosdb.server.entity.StatementStatus; import org.caosdb.server.entity.container.ParentContainer; +import org.caosdb.server.entity.container.RetrieveContainer; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.SerializeFieldStrategy; import org.caosdb.server.permissions.EntityACI; import org.caosdb.server.permissions.EntityPermission; +import org.caosdb.server.query.Query; +import org.caosdb.server.query.Query.Selection; public class CaosDBToGrpcConverters { @@ -588,4 +596,200 @@ public class CaosDBToGrpcConverters { } return result; } + + public org.caosdb.api.entity.v1.SelectQueryResult.Builder convertSelectResult( + RetrieveContainer container) { + SelectQueryResult.Builder result = SelectQueryResult.newBuilder(); + result.setHeader(convertSelectQueryHeader(container)); + for (EntityInterface e : container) { + result.addDataRows(convert(container.getQuery().getSelections(), e)); + } + return result; + } + + private SelectQueryRow.Builder convert(List<Selection> selections, EntityInterface e) { + + SelectQueryRow.Builder result = SelectQueryRow.newBuilder(); + for (Selection s : selections) { + org.caosdb.api.entity.v1.Value.Builder value = getSelectedValue(s, e); + result.addCells(value); + } + return result; + } + + /** + * Handle special selectors like "id", "name", "value", "parent",... + * + * @return a {@link org.caosdb.api.entity.v1.Value.Builder}, if the selector has been handled. + * Return null when the caller must handle the selector otherwise. + */ + private org.caosdb.api.entity.v1.Value.Builder handleSpecialSelectors( + String selector, EntityInterface e) { + org.caosdb.api.entity.v1.Value.Builder result = null; + switch (selector) { + case "value": + try { + e.parseValue(); + } catch (Message m) { + throw new TransactionException(m); + } + if (e.hasValue()) { + return convert(e.getValue()); + } + break; + case "version": + if (e.hasVersion()) { + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setScalarValue(convertStringValue(e.getVersion().getId())); + return result; + } + break; + case "name": + if (e.hasName()) { + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setScalarValue(convertStringValue(e.getName())); + return result; + } + break; + + case "id": + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setScalarValue(convertStringValue(e.getId().toString())); + return result; + + case "description": + if (e.hasDescription()) { + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setScalarValue(convertStringValue(e.getDescription())); + return result; + } + break; + + case "unit": + final String unit = getStringUnit(e); + if (unit != null) { + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setScalarValue(convertStringValue(unit)); + return result; + } + break; + case "datatype": + if (e.hasDatatype()) { + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setScalarValue(convertStringValue(e.getDatatype().toString())); + return result; + } + break; + case "path": + if (e.hasFileProperties() && e.getFileProperties().hasPath()) { + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setScalarValue(convertStringValue(e.getFileProperties().getPath())); + return result; + } + break; + case "parent": + if (e.hasParents()) { + result = org.caosdb.api.entity.v1.Value.newBuilder(); + CollectionValues.Builder parents = CollectionValues.newBuilder(); + for (org.caosdb.server.entity.wrapper.Parent p : e.getParents()) { + parents.addValues(ScalarValue.newBuilder().setStringValue(p.getName())); + } + result.setListValues(parents); + return result; + } + break; + default: + break; + } + return null; + } + + private org.caosdb.api.entity.v1.Value.Builder getSelectedValue(Selection s, EntityInterface e) { + org.caosdb.api.entity.v1.Value.Builder result = null; + String selector = s.getSelector(); + result = handleSpecialSelectors(selector, e); + if (result == null) { + // selector for a normal property + List<org.caosdb.api.entity.v1.Value.Builder> results = new LinkedList<>(); + for (Property p : e.getProperties()) { + if (p.getName() != null && p.getName().equals(selector)) { + if (s.getSubselection() == null) { + // no subselection -> just return the actual value + try { + p.parseValue(); + } catch (Message m) { + throw new TransactionException(m); + } + + results.add(convert(p.getValue())); + } else { + // with subselection, e.g. p1.unit, site.geolocation.longitude + String subselection = s.getSubselection().getSelector(); + result = handleSpecialSelectors(subselection, p); + if (result == null) { + // normal property + Value v = p.getValue(); + + if (v instanceof ReferenceValue) { + EntityInterface referenced_entity = ((ReferenceValue) v).getEntity(); + result = getSelectedValue(s.getSubselection(), referenced_entity); + } else if (v instanceof CollectionValue) { + for (Value i : (CollectionValue) v) { + if (i instanceof ReferenceValue) { + EntityInterface referenced_entity = ((ReferenceValue) i).getEntity(); + result = getSelectedValue(s.getSubselection(), referenced_entity); + results.add(result); + } else { + // Non-reference scalar value + result = getSelectedValue(s.getSubselection(), p); + results.add(result); + } + } + } else { + // Actual sub-property + result = getSelectedValue(s.getSubselection(), p); + results.add(result); + } + + } else { + results.add(result); + } + } + } + } + + if (results.size() > 1) { + // There have been multiple matching properties + CollectionValues.Builder values = CollectionValues.newBuilder(); + for (org.caosdb.api.entity.v1.Value.Builder v : results) { + // Concatenate all found values to a list + if (v.hasScalarValue()) { + values.addValues(v.getScalarValueBuilder()); + } else { + values.addAllValues(v.getListValuesBuilder().getValuesList()); + } + } + result = org.caosdb.api.entity.v1.Value.newBuilder(); + result.setListValues(values); + } else if (results.size() == 1) { + // There has been exactly one matching property + result = results.get(0); + } + } + + if (result == null) { + // no matching property found + result = org.caosdb.api.entity.v1.Value.newBuilder(); + } + return result; + } + + private SelectQueryHeader.Builder convertSelectQueryHeader(RetrieveContainer container) { + SelectQueryHeader.Builder result = SelectQueryHeader.newBuilder(); + Query query = container.getQuery(); + for (Selection s : query.getSelections()) { + result.addColumns(SelectQueryColumn.newBuilder().setName(s.toString())); + } + return result; + } } diff --git a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java index 315358deacf2d77828add722d29a6c4643acd283..030b52544fec858ef3c62c531e9627baf5500551 100644 --- a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java @@ -33,6 +33,7 @@ import org.caosdb.api.entity.v1.Entity; import org.caosdb.api.entity.v1.EntityRequest; import org.caosdb.api.entity.v1.EntityResponse; import org.caosdb.api.entity.v1.EntityTransactionServiceGrpc.EntityTransactionServiceImplBase; +import org.caosdb.api.entity.v1.FindQueryResult; import org.caosdb.api.entity.v1.IdResponse; import org.caosdb.api.entity.v1.InsertRequest; import org.caosdb.api.entity.v1.InsertResponse; @@ -43,6 +44,7 @@ import org.caosdb.api.entity.v1.MultiTransactionResponse; import org.caosdb.api.entity.v1.MultiUpdateEntityACLRequest; import org.caosdb.api.entity.v1.MultiUpdateEntityACLResponse; import org.caosdb.api.entity.v1.RetrieveResponse; +import org.caosdb.api.entity.v1.SelectQueryResult; import org.caosdb.api.entity.v1.TransactionRequest; import org.caosdb.api.entity.v1.TransactionRequest.WrappedRequestsCase; import org.caosdb.api.entity.v1.TransactionResponse; @@ -59,6 +61,7 @@ import org.caosdb.server.entity.UpdateEntity; import org.caosdb.server.entity.container.RetrieveContainer; import org.caosdb.server.entity.container.WritableContainer; import org.caosdb.server.permissions.EntityPermission; +import org.caosdb.server.query.Query; import org.caosdb.server.transaction.Retrieve; import org.caosdb.server.transaction.RetrieveACL; import org.caosdb.server.transaction.UpdateACL; @@ -77,6 +80,24 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa this.fileTransmissionService = fileTransmissionService; } + private void prepareDownload( + EntityResponse.Builder entityResponse, EntityInterface entity, FileDownload fileDownload) + throws Exception { + try { + entity.checkPermission(EntityPermission.RETRIEVE_FILE); + if (fileDownload == null) { + fileDownload = fileTransmissionService.registerFileDownload(null); + } + entity.getFileProperties().retrieveFromFileSystem(); + entityResponse.setDownloadId( + fileTransmissionService.registerFileDownload( + fileDownload.getId(), entity.getFileProperties())); + } catch (AuthenticationException exc) { + entityResponse.addErrors(caosdbToGrpc.convert(ServerMessages.AUTHORIZATION_ERROR)); + entityResponse.addInfos(caosdbToGrpc.convert(new Message(exc.getMessage()))); + } + } + /** * Handle read-only transactions. Of these only one may be a query at the moment, the others must * be ID retrieves. @@ -129,30 +150,42 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa final Retrieve transaction = new Retrieve(container); transaction.execute(); - if (container.getFlags().containsKey("query_count_result")) { - final int count = Integer.parseInt(container.getFlags().get("query_count_result")); + if (container.getQuery() != null && container.getQuery().getType() == Query.Type.COUNT) { + // this was a count query + final int count = container.getQuery().getCount(); builder .addResponsesBuilder() .setRetrieveResponse(RetrieveResponse.newBuilder().setCountResult(count)); + } else if (container.getQuery() != null + && container.getQuery().getType() == Query.Type.SELECT) { + // this was a select query + SelectQueryResult.Builder selectResult = caosdbToGrpc.convertSelectResult(container); + builder + .addResponsesBuilder() + .setRetrieveResponse(RetrieveResponse.newBuilder().setSelectResult(selectResult)); + } else if (container.getQuery() != null && container.getQuery().getType() == Query.Type.FIND) { + // this was a find query + final boolean download_files_container = container.getFlags().containsKey("download_files"); + FindQueryResult.Builder findResult = FindQueryResult.newBuilder(); + for (final EntityInterface entity : container) { + final EntityResponse.Builder entityResponse = caosdbToGrpc.convert(entity); + if ((download_files_container || entity.getFlags().containsKey("download_files")) + && entity.hasFileProperties()) { + prepareDownload(entityResponse, entity, fileDownload); + } + findResult.addResultSet(entityResponse); + } + builder + .addResponsesBuilder() + .setRetrieveResponse(RetrieveResponse.newBuilder().setFindResult(findResult)); } else { + // normal retrieval via id final boolean download_files_container = container.getFlags().containsKey("download_files"); for (final EntityInterface entity : container) { final EntityResponse.Builder entityResponse = caosdbToGrpc.convert(entity); if ((download_files_container || entity.getFlags().containsKey("download_files")) && entity.hasFileProperties()) { - try { - entity.checkPermission(EntityPermission.RETRIEVE_FILE); - if (fileDownload == null) { - fileDownload = fileTransmissionService.registerFileDownload(null); - } - entity.getFileProperties().retrieveFromFileSystem(); - entityResponse.setDownloadId( - fileTransmissionService.registerFileDownload( - fileDownload.getId(), entity.getFileProperties())); - } catch (AuthenticationException exc) { - entityResponse.addErrors(caosdbToGrpc.convert(ServerMessages.AUTHORIZATION_ERROR)); - entityResponse.addInfos(caosdbToGrpc.convert(new Message(exc.getMessage()))); - } + prepareDownload(entityResponse, entity, fileDownload); } builder .addResponsesBuilder() diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckQueryTemplate.java b/src/main/java/org/caosdb/server/jobs/core/CheckQueryTemplate.java index 1b5ccb41f58b92d2230fb99e5635ad02bd9922f5..42dccf552d69bbba6bee2ab909eaf31b5159041f 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckQueryTemplate.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckQueryTemplate.java @@ -26,8 +26,6 @@ import org.caosdb.server.entity.Role; import org.caosdb.server.jobs.EntityJob; import org.caosdb.server.query.Query; import org.caosdb.server.query.Query.ParsingException; -import org.caosdb.server.query.Query.Type; -import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; public class CheckQueryTemplate extends EntityJob { @@ -40,20 +38,22 @@ public class CheckQueryTemplate extends EntityJob { final Query q = new Query(getEntity().getQueryTemplateDefinition()); try { q.parse(); - if (q.getType() == Type.COUNT) { - getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - getEntity().addError(ServerMessages.QUERY_TEMPLATE_WITH_COUNT); - } - if (q.getSelections() != null && !q.getSelections().isEmpty()) { - getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - getEntity().addError(ServerMessages.QUERY_TEMPLATE_WITH_SELECT); + switch (q.getType()) { + case COUNT: + getEntity().addError(ServerMessages.QUERY_TEMPLATE_WITH_COUNT); + break; + + case SELECT: + getEntity().addError(ServerMessages.QUERY_TEMPLATE_WITH_SELECT); + break; + + default: + break; } } catch (final ParsingException e) { - getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); getEntity().addError(ServerMessages.QUERY_PARSING_ERROR); } } else { - getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); getEntity().addError(ServerMessages.QUERY_TEMPLATE_HAS_NO_QUERY_DEFINITION); } } diff --git a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java index 0a19977b199195610d96f887c4c538a75b491511..352a7428f2007fc59cbf0b3fb7f067d1198857c1 100644 --- a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java +++ b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java @@ -31,8 +31,6 @@ import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.TransactionStage; import org.caosdb.server.query.Query; import org.caosdb.server.query.Query.ParsingException; -import org.caosdb.server.query.Query.Type; -import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; @JobAnnotation(flag = "query", stage = TransactionStage.INIT) @@ -41,27 +39,23 @@ public class ExecuteQuery extends FlagJob { @Override protected void job(final String value) { - final Query queryInstance = new Query(value, getTransaction().getTransactor(), getContainer()); - try { - if (value != null) { + if (value != null) { + final Query queryInstance = + new Query(value, getTransaction().getTransactor(), getContainer()); + getContainer().setQuery(queryInstance); + try { queryInstance.execute(getTransaction().getAccess()); + } catch (final ParsingException e) { + getContainer().addError(ServerMessages.QUERY_PARSING_ERROR); + } catch (final UnsupportedOperationException e) { + getContainer().addError(ServerMessages.QUERY_EXCEPTION); + getContainer() + .addMessage(new Message(MessageType.Info, (MessageCode) null, e.getMessage())); + } + getContainer().addMessage(queryInstance); + for (final EntityInterface entity : getContainer()) { + getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); } - } catch (final ParsingException e) { - getContainer().addMessage(ServerMessages.QUERY_PARSING_ERROR); - getContainer().setStatus(EntityStatus.UNQUALIFIED); - } catch (final UnsupportedOperationException e) { - getContainer().addMessage(ServerMessages.QUERY_EXCEPTION); - getContainer().setStatus(EntityStatus.UNQUALIFIED); - getContainer().addMessage(new Message(MessageType.Info, (MessageCode) null, e.getMessage())); - } - getContainer().addMessage(queryInstance); - if (queryInstance.getQuery().getType() == Type.COUNT) { - getContainer() - .getFlags() - .put("query_count_result", Integer.toString(queryInstance.getCount())); - } - for (final EntityInterface entity : getContainer()) { - getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); } } } diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4 index c4a68e5fbd3b4ad6cf61e5a7bd07b02ff1085095..d44674b29d5c35fab2db9f4a8395e064219b79c9 100644 --- a/src/main/java/org/caosdb/server/query/CQLParser.g4 +++ b/src/main/java/org/caosdb/server/query/CQLParser.g4 @@ -40,7 +40,7 @@ cq returns [Query.Type t, List<Query.Selection> s, Query.Pattern e, Query.Role r : ( - SELECT prop_sel {$s = $prop_sel.s;} FROM {$t = Query.Type.FIND;} + SELECT prop_sel {$s = $prop_sel.s;} FROM {$t = Query.Type.SELECT;} | FIND {$t = Query.Type.FIND;} | COUNT {$t = Query.Type.COUNT;}) (version {$v = $version.v;})? diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index 7da810c3d8f50ab739dd72a65551b45449b4d183..88da39bce24f1cabf651eebf1532cab14bddb82c 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -165,7 +165,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac public enum Type { FIND, - COUNT + COUNT, + SELECT, }; public static final class Pattern { @@ -722,7 +723,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } /** Fill entities from `resultSet` into `container`. */ private void fillContainerWithResult() { - if (this.container != null && this.type == Type.FIND) { + if (this.container != null && (this.type == Type.FIND || this.type == Type.SELECT)) { for (final IdVersionAclTriplet t : this.resultSet) { final Entity e = new RetrieveEntity(new EntityID(t.id), t.version); diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java index cfd53f94f305088f04f9a5eb0a2e443ad65f9c6c..fe73e2b2c33749fac6d72a4c5b478eca5d154465 100644 --- a/src/test/java/org/caosdb/server/query/TestCQL.java +++ b/src/test/java/org/caosdb/server/query/TestCQL.java @@ -278,6 +278,10 @@ public class TestCQL { "SELECT 'name with spaces.and dot', 'name with spaces'.name, name with spaces.name, name with\\,comma and\\.dot and \\'single_quote.sub FROM ENTITY"; String issue130b = "SELECT 'Wrapper' FROM RECORD TestRT"; + // quotation marks gone rogue + String quotation1 = + "FIND ENTITY WHICH HAS A PROPERTY LIKE '*with double*' AND A PROPERTY LIKE '*and single*' AND A PROPERTY LIKE '*what\\'s wrong?*' AND A PROPERTY LIKE '*\\'*' AND A PROPERTY LIKE '*nothin\\'*' AND A PROPERTY LIKE '*\"\\'bla*'"; + @Test public void testQuery1() throws InterruptedException, SQLException, ConnectionException, QueryException { @@ -6082,7 +6086,7 @@ public class TestCQL { assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type); assertEquals("ename", sfq.e.toString()); assertEquals(Query.Role.RECORD, sfq.r); - assertEquals(Query.Type.FIND, sfq.t); + assertEquals(Query.Type.SELECT, sfq.t); assertNotNull(sfq.s); assertFalse(sfq.s.isEmpty()); assertEquals(1, sfq.s.size()); @@ -6105,7 +6109,7 @@ public class TestCQL { assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type); assertEquals("ename", sfq.e.toString()); assertEquals(Query.Role.RECORD, sfq.r); - assertEquals(Query.Type.FIND, sfq.t); + assertEquals(Query.Type.SELECT, sfq.t); assertNotNull(sfq.s); assertFalse(sfq.s.isEmpty()); assertEquals(1, sfq.s.size()); @@ -6128,7 +6132,7 @@ public class TestCQL { assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type); assertEquals("ename", sfq.e.toString()); assertEquals(Query.Role.RECORD, sfq.r); - assertEquals(Query.Type.FIND, sfq.t); + assertEquals(Query.Type.SELECT, sfq.t); assertNotNull(sfq.s); assertFalse(sfq.s.isEmpty()); assertEquals(3, sfq.s.size()); @@ -6156,7 +6160,7 @@ public class TestCQL { assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type); assertEquals("ename", sfq.e.toString()); assertEquals(Query.Role.RECORD, sfq.r); - assertEquals(Query.Type.FIND, sfq.t); + assertEquals(Query.Type.SELECT, sfq.t); assertNotNull(sfq.s); assertFalse(sfq.s.isEmpty()); assertEquals(4, sfq.s.size()); @@ -6619,7 +6623,7 @@ public class TestCQL { assertEquals(Query.Pattern.TYPE_NORMAL, sfq.e.type); assertEquals("ename", sfq.e.toString()); assertNull(sfq.r); - assertEquals(Query.Type.FIND, sfq.t); + assertEquals(Query.Type.SELECT, sfq.t); assertNotNull(sfq.s); assertFalse(sfq.s.isEmpty()); assertEquals(1, sfq.s.size()); @@ -6989,4 +6993,24 @@ public class TestCQL { assertEquals("Wrapper", sfq.s.get(0).getSelector()); assertNull(sfq.s.get(0).getSubselection()); } + + @Test + public void testQuotation1() { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.quotation1)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + assertTrue(sfq.filter instanceof Conjunction); + Conjunction conj = (Conjunction) sfq.filter; + assertEquals(6, conj.getFilters().size()); + assertEquals("POV(null,LIKE ,%with double%)", conj.getFilters().get(0).toString()); + assertEquals("POV(null,LIKE ,%and single%)", conj.getFilters().get(1).toString()); + assertEquals("POV(null,LIKE ,%what's wrong?%)", conj.getFilters().get(2).toString()); + assertEquals("POV(null,LIKE ,%'%)", conj.getFilters().get(3).toString()); + assertEquals("POV(null,LIKE ,%nothin'%)", conj.getFilters().get(4).toString()); + assertEquals("POV(null,LIKE ,%\"'bla%)", conj.getFilters().get(5).toString()); + } }