diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d94ff5f6d730050d2b3925f8bad96df01de01eb..e6f200e107cdee3aa2382c99166b921650a8eb5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* SELECT query support for the GRPC API + ### Changed ### Deprecated diff --git a/caosdb-proto b/caosdb-proto index c439aa40a30c9214db315018b84fa50112c9251e..13d083b84400507f6f1967a099e2af006af2a231 160000 --- a/caosdb-proto +++ b/caosdb-proto @@ -1 +1 @@ -Subproject commit c439aa40a30c9214db315018b84fa50112c9251e +Subproject commit 13d083b84400507f6f1967a099e2af006af2a231 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()); + } }