diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/SoqlException.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/SoqlException.java new file mode 100644 index 000000000..792495192 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/exception/SoqlException.java @@ -0,0 +1,13 @@ +package cz.cvut.kbss.jopa.exception; + +import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; + +/** + * Indicates an error during parsing and translation of SOQL to SPARQL. + */ +public class SoqlException extends OWLPersistenceException { + + public SoqlException(String message) { + super(message); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/MemberOfOperator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/MemberOfOperator.java new file mode 100644 index 000000000..dfe96d051 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/MemberOfOperator.java @@ -0,0 +1,32 @@ +package cz.cvut.kbss.jopa.query.soql; + +/** + * SOQL ({@code NOT}) {@code MEMBER OF} operator. + */ +public class MemberOfOperator implements FilterableExpression { + + private final boolean isNot; + + private MemberOfOperator(boolean isNot) { + this.isNot = isNot; + } + + @Override + public String toFilterExpression(String parameter, String value) { + // TODO FILTER NOT EXISTS + return ""; + } + + @Override + public boolean requiresFilterExpression() { + return isNot; + } + + static MemberOfOperator memberOf() { + return new MemberOfOperator(false); + } + + static MemberOfOperator notMemberOf() { + return new MemberOfOperator(true); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java index a53e5495b..372b8a0c0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlAttribute.java @@ -14,6 +14,7 @@ */ package cz.cvut.kbss.jopa.query.soql; +import cz.cvut.kbss.jopa.query.sparql.SparqlConstants; import cz.cvut.kbss.jopa.utils.IdentifierTransformer; import java.util.Collections; @@ -162,6 +163,7 @@ public String getBasicGraphPattern(String rootVariable) { } private static String toIri(SoqlNode node) { - return IdentifierTransformer.stringifyIri(node.getIri()); + final String nodeIri = node.getIri(); + return SparqlConstants.RDF_TYPE_SHORTCUT.equals(nodeIri) ? nodeIri : IdentifierTransformer.stringifyIri(nodeIri); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java index d68e21f53..7e28f0dd3 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java @@ -14,6 +14,7 @@ */ package cz.cvut.kbss.jopa.query.soql; +import cz.cvut.kbss.jopa.exception.SoqlException; import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.model.metamodel.Attribute; import cz.cvut.kbss.jopa.model.metamodel.EntityType; @@ -21,6 +22,7 @@ import cz.cvut.kbss.jopa.model.metamodel.PluralAttribute; import cz.cvut.kbss.jopa.model.metamodel.SingularAttribute; import cz.cvut.kbss.jopa.model.metamodel.Type; +import cz.cvut.kbss.jopa.query.sparql.SparqlConstants; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.ParseTree; @@ -366,6 +368,26 @@ public void exitLikeExpression(SoqlParser.LikeExpressionContext ctx) { this.isInObjectIdentifierExpression = false; } + @Override + public void enterMemberOfExpression(SoqlParser.MemberOfExpressionContext ctx) { + + } + + @Override + public void exitMemberOfExpression(SoqlParser.MemberOfExpressionContext ctx) { + if (ctx.getChildCount() > 2 && ctx.getChild(1).getText().equals(SoqlConstants.NOT)) { + // TODO + attrPointer.setOperator(MemberOfOperator.notMemberOf()); + ParseTree whereClauseValue = ctx.getChild(3); + attrPointer.setValue(whereClauseValue.getText()); + } else { + attrPointer.setOperator(MemberOfOperator.memberOf()); + ParseTree whereClauseValue = ctx.getChild(0); + attrPointer.setValue(whereClauseValue.getText()); + } + this.isInObjectIdentifierExpression = false; + } + @Override public void enterComparisonExpression(SoqlParser.ComparisonExpressionContext ctx) { } @@ -689,10 +711,19 @@ private IdentifiableEntityType getEntityType(String name) { } private void setAllNodesIris(EntityType entityType, SoqlNode node) { - if (entityType.getIdentifier().getName().equals(node.getValue())) { + final String nodeName = node.getValue(); + if (entityType.getIdentifier().getName().equals(nodeName)) { return; } final Attribute abstractAttribute = entityType.getAttribute(node.getValue()); + if (abstractAttribute == null) { + if (entityType.getTypes() != null && entityType.getTypes().getName().equals(node.getValue())) { + node.setIri(SparqlConstants.RDF_TYPE_SHORTCUT); + return; + } else { + throw new SoqlException("No matching attribute with name '" + node.getValue() + "' found on entity type '" + entityType.getName() + "'."); + } + } //not implemented case of 3 or more fragments (chained SoqlNodes) node.setIri(abstractAttribute.getIRI().toString()); if (node.hasChild()) { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlConstants.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlConstants.java new file mode 100644 index 000000000..874fa1e39 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlConstants.java @@ -0,0 +1,26 @@ +package cz.cvut.kbss.jopa.query.sparql; + +/** + * Constants of SPARQL. + */ +public class SparqlConstants { + + /** + * The {@literal SELECT} keyword. + */ + public static final String SELECT = "SELECT"; + + /** + * The {@literal WHERE} keyword. + */ + public static final String WHERE = "WHERE"; + + /** + * The {@literal a} keyword representing the rdf:type IRI. + */ + public static final String RDF_TYPE_SHORTCUT = "a"; + + private SparqlConstants() { + throw new AssertionError(); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryParser.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryParser.java index 67ae3a8f1..b24e3f16a 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryParser.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/SparqlQueryParser.java @@ -34,9 +34,6 @@ */ public class SparqlQueryParser implements QueryParser { - private static final String SELECT = "SELECT"; - private static final String WHERE = "WHERE"; - private final ParameterValueFactory parameterValueFactory; private String query; @@ -195,9 +192,9 @@ private QueryParameter getQueryParameter(Integer position) { } private void wordEnd() { - if (SELECT.equalsIgnoreCase(currentWord.toString())) { + if (SparqlConstants.SELECT.equalsIgnoreCase(currentWord.toString())) { this.inProjection = true; - } else if (inProjection && WHERE.equalsIgnoreCase(currentWord.toString())) { + } else if (inProjection && SparqlConstants.WHERE.equalsIgnoreCase(currentWord.toString())) { this.inProjection = false; } currentWord = new StringBuilder(); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java index 23b1e61b8..1c1fe6904 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java @@ -14,6 +14,7 @@ import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; +import cz.cvut.kbss.jopa.exception.SoqlException; import cz.cvut.kbss.jopa.model.MetamodelImpl; import cz.cvut.kbss.jopa.query.QueryHolder; import cz.cvut.kbss.jopa.query.QueryParser; @@ -31,8 +32,11 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -767,4 +771,11 @@ void parseQuerySupportsCountById() { final String expectedSparql = "SELECT (COUNT(?id) AS ?count) WHERE { ?id a " + strUri(Vocabulary.c_OwlClassA) + " . }"; parseAndAssertEquality(soql, expectedSparql); } + + @Test + void parseQueryThrowsSoqlExceptionWhenUnknownAttributeNameIsUsed() { + final String soql = "SELECT p FROM Person p WHERE p.unknownAttribute = :param"; + final SoqlException ex = assertThrows(SoqlException.class, () -> sut.parseQuery(soql)); + assertThat(ex.getMessage(), containsString("No matching attribute")); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/IdentifierTransformerTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/IdentifierTransformerTest.java index 1064fde8e..306343bb0 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/IdentifierTransformerTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/utils/IdentifierTransformerTest.java @@ -14,13 +14,16 @@ */ package cz.cvut.kbss.jopa.utils; +import cz.cvut.kbss.jopa.environment.utils.Generators; import org.junit.jupiter.api.Test; import java.net.URI; import java.net.URL; import java.util.Date; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class IdentifierTransformerTest { @@ -53,4 +56,10 @@ void transformThrowsIllegalArgumentForUnsupportedTargetType() { assertThrows(IllegalArgumentException.class, () -> IdentifierTransformer.transformToIdentifier(IDENTIFIER, Date.class)); } + + @Test + void stringifyIriReturnsSpecifiedIriInAngledBrackets() { + final URI uri = Generators.createIndividualIdentifier(); + assertEquals("<" + uri + ">", IdentifierTransformer.stringifyIri(uri)); + } }