diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java index 7ea1dd4d7..2c4aa084b 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoader.java @@ -85,6 +85,8 @@ class AttributeBasedRowsToAxiomsQueryResultLoader implements QueryResultLoade private Set> currentEntityAxioms = Set.of(); private NamedResource currentSubject; + private boolean typesProjected = true; + AttributeBasedRowsToAxiomsQueryResultLoader(UnitOfWork uow, Class resultType, Descriptor descriptor, EntityGraph fetchGraph) { this(uow, resultType, descriptor, fetchGraph, () -> null); @@ -96,7 +98,8 @@ class AttributeBasedRowsToAxiomsQueryResultLoader implements QueryResultLoade this.uow = uow; this.resultType = resultType; this.descriptor = descriptor; - this.mappingSupplier = mappingSupplier != null ? mappingSupplier : () -> null; + assert mappingSupplier != null; + this.mappingSupplier = mappingSupplier; this.entityType = uow.getMetamodel().entity(resultType); this.fetchGraph = fetchGraph; } @@ -113,6 +116,7 @@ private void createProjectionMappings(List projectedVars) { if (mappings != null) { return; } + this.typesProjected = false; assert !projectedVars.isEmpty(); final String subjectVar = projectedVars.get(0); this.mappings = new ArrayList<>(projectedVars.size() - 1); @@ -126,9 +130,11 @@ private void createProjectionMappings(List projectedVars) { } else if (atts.containsKey(projectedVar.substring(subjectVar.length() + 1))) { // ?x_stringAttribute mappings.add(new QueryVariableMapping(subjectVar, projectedVar, atts.get(projectedVar.substring(subjectVar.length() + 1)))); - } else if (projectedVar.endsWith(AttributeEnumeratingSparqlAssemblyModifier.TYPES_VAR_NAME)) { + } else if (projectedVar.endsWith(AttributeEnumeratingSparqlAssemblyModifier.TYPES_VAR_NAME) || (entityType.getTypes() != null && projectedVar.endsWith(entityType.getTypes() + .getName()))) { // ?x_types or ?types mappings.add(new QueryVariableMapping(subjectVar, projectedVar, entityType.getTypes())); + this.typesProjected = true; } else { LOG.warn("Variable '{}' projected from the query cannot be mapped to any attributes in entity class {}.", projectedVar, entityType); } @@ -209,6 +215,10 @@ private Stream> extractValue(ResultRow row, } private T loadEntity() { + if (!typesProjected) { + currentEntityAxioms.add(new AxiomImpl<>(currentSubject, + Assertion.createClassAssertion(false), new Value<>(entityType.getIRI().toURI()))); + } try { return uow.readObjectFromAxioms(resultType, currentEntityAxioms, new AxiomBasedLoadingConfigGroup<>(currentSubject.getIdentifier(), descriptor, fetchGraph)); } catch (CardinalityConstraintViolatedException e) { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java index cb47d4ef7..878ef2776 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/sparql/loader/AttributeBasedRowsToAxiomsQueryResultLoaderTest.java @@ -19,6 +19,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassA; import cz.cvut.kbss.jopa.environment.OWLClassD; +import cz.cvut.kbss.jopa.environment.Phone; import cz.cvut.kbss.jopa.environment.Vocabulary; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; @@ -32,6 +33,7 @@ import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; import cz.cvut.kbss.jopa.sessions.UnitOfWork; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingConfigGroup; +import cz.cvut.kbss.ontodriver.ResultSet; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.iteration.ResultRow; import cz.cvut.kbss.ontodriver.model.Assertion; @@ -154,7 +156,8 @@ void loadLastPendingLoadsLastPendingEntity() throws Exception { verify(uow).readObjectFromAxioms(eq(OWLClassA.class), anyCollection(), eq(new AxiomBasedLoadingConfigGroup<>(instance.getUri(), descriptor, fetchGraph))); } - private AttributeBasedRowsToAxiomsQueryResultLoader createOwlClassASut(EntityGraph fetchGraph) { + private AttributeBasedRowsToAxiomsQueryResultLoader createOwlClassASut( + EntityGraph fetchGraph) { final List mappings = List.of( new QueryVariableMapping("x", "x_stringAttribute", metamodelMocks.forOwlClassA().stringAttribute()), new QueryVariableMapping("x", "x_types", metamodelMocks.forOwlClassA().typesSpec()) @@ -300,4 +303,32 @@ void loadResultSupportsGroupConcatenatedTypes() throws Exception { .anyMatch(ax -> ax.getValue().stringValue().equals(instance.getStringAttribute()))); } + @Test + void loadResultAutomaticallyAddsClassAssertionForDirectEntityMappingWithoutProjectedTypes() throws Exception { + final AttributeBasedRowsToAxiomsQueryResultLoader sut = new AttributeBasedRowsToAxiomsQueryResultLoader<>(uow, Phone.class, descriptor, null); + final Phone phone = new Phone(Generators.createIndividualIdentifier(), "123456789", "Nokia"); + final List columnNames = List.of("x", "x_number", "x_brand"); + final ResultSet resultSet = mock(ResultSet.class); + when(resultSet.getColumnNames()).thenReturn(columnNames); + final ResultRow row = mock(ResultRow.class); + when(row.getColumnCount()).thenReturn(3); + when(row.getColumnNames()).thenReturn(columnNames); + when(row.isBound("x")).thenReturn(true); + when(row.getObject(0, URI.class)).thenReturn(phone.getUri()); + when(row.getObject("x", URI.class)).thenReturn(phone.getUri()); + when(row.isBound("x_number")).thenReturn(true); + when(row.getObject("x_number")).thenReturn(phone.getNumber()); + when(row.isBound("x_brand")).thenReturn(true); + when(row.getObject("x_brand")).thenReturn(phone.getBrand()); + when(uow.readObjectFromAxioms(eq(Phone.class), anyCollection(), any(AxiomBasedLoadingConfigGroup.class))).thenReturn(phone); + + sut.init(resultSet); + final Optional intermediate = sut.loadResult(row); + assertFalse(intermediate.isPresent()); + final Optional result = sut.loadLastPending(); + assertTrue(result.isPresent()); + final ArgumentCaptor>> captor = ArgumentCaptor.forClass(Collection.class); + verify(uow).readObjectFromAxioms(eq(Phone.class), captor.capture(), any(AxiomBasedLoadingConfigGroup.class)); + assertThat(captor.getValue(), hasItem(new AxiomImpl<>(NamedResource.create(phone.getUri()), Assertion.createClassAssertion(false), new Value<>(URI.create(Vocabulary.c_Phone))))); + } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java index a942c5ac7..185b891e5 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java @@ -470,7 +470,8 @@ public void querySupportsFetchGraphs() { .getResultList(); assertEquals(entities.size(), result.size()); result.forEach(c -> { - final Optional expected = entities.stream().filter(v -> v.getUri().equals(c.getUri())).findFirst(); + final Optional expected = entities.stream().filter(v -> v.getUri().equals(c.getUri())) + .findFirst(); assertTrue(expected.isPresent()); assertEquals(expected.get().getName(), c.getName()); assertEquals(expected.get().getDescription(), c.getDescription()); @@ -482,4 +483,24 @@ public void querySupportsFetchGraphs() { }); }); } + + @Test + public void querySupportsMappingResultsDirectlyToEntityWithoutProjectingTypes() { + final List entities = QueryTestEnvironment.getData(OWLClassE.class); + final List result = getEntityManager().createNativeQuery(""" + SELECT ?x ?stringAttribute WHERE { + ?x a ?type ; + ?hasString ?stringAttribute . + }""", OWLClassE.class) + .setParameter("type", URI.create(Vocabulary.C_OWL_CLASS_E)) + .setParameter("hasString", URI.create(Vocabulary.P_E_STRING_ATTRIBUTE)) + .getResultList(); + entities.sort(Comparator.comparing(OWLClassE::getUri)); + result.sort(Comparator.comparing(OWLClassE::getUri)); // Sort here instead of in the query due to OWL2Query lacking proper sorting support + assertEquals(entities.size(), result.size()); + for (int i = 0; i < entities.size(); i++) { + assertEquals(entities.get(i).getUri(), result.get(i).getUri()); + assertEquals(entities.get(i).getStringAttribute(), result.get(i).getStringAttribute()); + } + } }