diff --git a/CHANGELOG.md b/CHANGELOG.md index bdedc677864430fc61ca4c06e86574c6e0765fcc..04f6d43ad5e61befeae5c1441eec7f61126bde94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed ### +* `NullPointerException` in GRPC API converters when executing SELECT query on + NULL values. + ### Security ### ### Documentation ### diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java index 48367508112c2c3267abd771fa84eb21ea16a534..e1acfbdc1cecf7eeb54f778be01d79ffc9b2cb53 100644 --- a/src/main/java/org/caosdb/server/entity/Entity.java +++ b/src/main/java/org/caosdb/server/entity/Entity.java @@ -609,10 +609,11 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa @Override public void parseValue() throws Message { - if (!this.isParsed) { - this.isParsed = true; - setValue(getDatatype().parseValue(getValue())); + if (this.isParsed) { + return; } + this.isParsed = true; + if (getValue() != null) setValue(getDatatype().parseValue(getValue())); } @Override diff --git a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java index 6fef590746bbf2c3ff802baec73385e4b061ec95..1b70a729e4950f8aaf838100397b0338e7d798c0 100644 --- a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java +++ b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java @@ -298,6 +298,8 @@ public class CaosDBToGrpcConverters { } else if (value instanceof GenericValue) { return convertGenericValue((GenericValue) value); + } else if (value == null) { + return ScalarValue.newBuilder().setSpecialValue(SpecialValue.SPECIAL_VALUE_UNSPECIFIED); } return null; } @@ -704,7 +706,7 @@ public class CaosDBToGrpcConverters { return null; } - private org.caosdb.api.entity.v1.Value.Builder getSelectedValue(Selection s, EntityInterface e) { + 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); @@ -735,8 +737,9 @@ public class CaosDBToGrpcConverters { 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(); + Value wrapped = ((IndexedSingleValue) i).getWrapped(); + if (wrapped instanceof ReferenceValue) { + EntityInterface referenced_entity = ((ReferenceValue) wrapped).getEntity(); result = getSelectedValue(s.getSubselection(), referenced_entity); results.add(result); } else { diff --git a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java index d39a803f9cfe3ce0a4cf23265683d121b3bc688d..d46c187324b76e179ef8d562cd5a150ab7968a45 100644 --- a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java +++ b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java @@ -1,12 +1,14 @@ package org.caosdb.server.grpc; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.TimeZone; +import org.caosdb.api.entity.v1.Value.Builder; import org.caosdb.datetime.DateTimeFactory2; +import org.caosdb.server.datatype.CollectionValue; import org.caosdb.server.datatype.GenericValue; +import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.datatype.Value; import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.FileProperties; @@ -17,6 +19,7 @@ import org.caosdb.server.entity.StatementStatus; import org.caosdb.server.entity.wrapper.Parent; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.IdAndServerMessagesOnlyStrategy; +import org.caosdb.server.query.Query.Selection; import org.junit.jupiter.api.Test; public class CaosDBToGrpcConvertersTest { @@ -27,7 +30,9 @@ public class CaosDBToGrpcConvertersTest { DateTimeFactory2 factory = new DateTimeFactory2(timeZone); CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(timeZone); Value value = null; - assertNull(converters.convertScalarValue(value)); + assertEquals( + converters.convertScalarValue(value).toString(), + "special_value: SPECIAL_VALUE_UNSPECIFIED\n"); value = factory.parse("2022"); assertEquals(converters.convertScalarValue(value).toString(), "string_value: \"2022\"\n"); value = factory.parse("2022-12"); @@ -101,4 +106,68 @@ public class CaosDBToGrpcConvertersTest { converters.convert(entity).toString(), "entity {\n id: \"1234\"\n}\nerrors {\n code: 1\n description: \"error\"\n}\nwarnings {\n code: 1\n description: \"warning\"\n}\ninfos {\n code: 1\n description: \"info\"\n}\n"); } + + @Test + public void testGetSelectedValueWithNullValue() { + Property p = new Property(new RetrieveEntity()); + p.setName("p0"); + p.setDatatype("DOUBLE"); + RetrieveEntity entity = new RetrieveEntity(); + entity.addProperty(p); + + CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(null); + Builder value = converters.getSelectedValue(new Selection("p0"), entity); + assertEquals( + "scalar_value {\n" + " special_value: SPECIAL_VALUE_UNSPECIFIED\n" + "}\n", + value.toString()); + } + + @Test + public void testGetSelectedValueWithListOfReferenceValue() { + CollectionValue col = new CollectionValue(); + Property p = new Property(new RetrieveEntity()); + p.setName("Person"); + p.setDatatype("List<Person>"); + + Property fullName1 = new Property(new RetrieveEntity()); + fullName1.setName("full name"); + fullName1.setDatatype("TEXT"); + fullName1.setValue(new GenericValue("Harry Belafonte")); + + RetrieveEntity person1 = new RetrieveEntity(); + person1.addProperty(fullName1); + ReferenceValue val1 = new ReferenceValue(new EntityID(1234)); + val1.setEntity(person1, false); + col.add(val1); + + Property fullName2 = new Property(new RetrieveEntity()); + fullName2.setName("full name"); + fullName2.setDatatype("TEXT"); + fullName2.setValue(new GenericValue("Louis Prima")); + + RetrieveEntity person2 = new RetrieveEntity(); + person2.addProperty(fullName2); + ReferenceValue val2 = new ReferenceValue(new EntityID(1234)); + val2.setEntity(person2, false); + col.add(val2); + p.setValue(col); + + RetrieveEntity entity = new RetrieveEntity(); + entity.addProperty(p); + + CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(null); + Builder value = + converters.getSelectedValue( + new Selection("Person").setSubSelection(new Selection("full name")), entity); + assertEquals( + "list_values {\n" + + " values {\n" + + " string_value: \"Harry Belafonte\"\n" + + " }\n" + + " values {\n" + + " string_value: \"Louis Prima\"\n" + + " }\n" + + "}\n", + value.toString()); + } }