From 8e9ccd20aa0d34bb97533253c70252f57b43655a Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Sat, 1 Feb 2014 23:15:43 +0100 Subject: [PATCH] DATAGRAPH-388 Support for Labels (indexes, queries etc) * @Indexed(indexType=LABEL) * added template.isLabelBased(), indexInfo.isLabelBased() and TRS.isLabelBased() * already reworked some of the derived finders to consistently use a start-less syntax wherever possible * first stab at dynamic Labels with @Labels and LabelFieldAccessorFactory --- ...odeTypeRepresentationStrategyTestBase.java | 8 +- ...edNodeTypeRepresentationStrategyTests.java | 3 +- ...edNodeTypeRepresentationStrategyTests.java | 8 +- .../Neo4jGraphPersistenceTests-context.xml | 4 + .../data/neo4j/annotation/Labels.java | 36 +++++ .../data/neo4j/config/Neo4jConfiguration.java | 8 +- .../core/TypeRepresentationStrategy.java | 2 + .../LabelFieldAccessorFactory.java | 111 ++++++++++++++ .../NodeDelegatingFieldAccessorFactory.java | 1 + .../data/neo4j/mapping/IndexInfo.java | 31 +++- .../repository/AbstractGraphRepository.java | 135 ++-------------- .../neo4j/repository/LegacyIndexSearcher.java | 145 ++++++++++++++++++ .../neo4j/repository/query/CypherQuery.java | 25 +-- .../repository/query/CypherQueryBuilder.java | 3 +- .../query/IdPropertyWhereClause.java | 58 +++++++ .../neo4j/repository/query/MatchClause.java | 7 + .../repository/query/QueryTemplates.java | 2 + .../neo4j/repository/query/WhereClause.java | 19 ++- .../data/neo4j/support/Infrastructure.java | 5 + .../neo4j/support/MappingInfrastructure.java | 15 +- .../MappingInfrastructureFactoryBean.java | 7 +- .../data/neo4j/support/Neo4jTemplate.java | 4 + .../support/index/IndexProviderImpl.java | 1 + .../data/neo4j/support/index/IndexType.java | 12 +- .../IndexCreationMappingEventListener.java | 14 +- .../mapping/Neo4jPersistentEntityImpl.java | 20 +-- .../support/mapping/StoredEntityType.java | 9 ++ .../support/schema/SchemaIndexProvider.java | 65 ++++++++ ...tIndexBasedTypeRepresentationStrategy.java | 5 + ...elBasedNodeTypeRepresentationStrategy.java | 5 + .../NoopNodeTypeRepresentationStrategy.java | 5 + ...elationshipTypeRepresentationStrategy.java | 5 + ...ferenceNodeTypeRepresentationStrategy.java | 5 + .../TypeRepresentationStrategies.java | 5 + .../AbstractCypherQueryBuilderTestBase.java | 8 +- .../AbstractDerivedFinderMethodTestBase.java | 10 +- ...QueryBuilderForIndexBasedTRSUnitTests.java | 4 - ...QueryBuilderForLabelBasedTRSUnitTests.java | 12 +- ...ivedFinderMethodForLabelBasedTRSTests.java | 13 +- 39 files changed, 652 insertions(+), 183 deletions(-) create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Labels.java create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/LabelFieldAccessorFactory.java create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/LegacyIndexSearcher.java create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/IdPropertyWhereClause.java create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java index a89082149..b3786455c 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java @@ -45,6 +45,7 @@ import java.util.Arrays; import java.util.HashSet; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; public abstract class AbstractNodeTypeRepresentationStrategyTestBase extends EntityTestBase { @@ -96,7 +97,12 @@ public abstract class AbstractNodeTypeRepresentationStrategyTestBase extends Ent IteratorUtil.addToCollection(allThings, new HashSet())); } - @Test + @Test + public void testAssertLabelIndexOrNot() throws Exception { + assertFalse("not label based", nodeTypeRepresentationStrategy.isLabelBased()); + } + + @Test @Transactional public void testCountOfSuperTypeIncludesSubTypes() throws Exception { final int EXPECTED_NUM_THINGS = 1; diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/IndexBasedNodeTypeRepresentationStrategyTests.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/IndexBasedNodeTypeRepresentationStrategyTests.java index 753fc1083..ed08df719 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/IndexBasedNodeTypeRepresentationStrategyTests.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/IndexBasedNodeTypeRepresentationStrategyTests.java @@ -48,6 +48,7 @@ import java.util.HashSet; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; /** @@ -76,7 +77,7 @@ public class IndexBasedNodeTypeRepresentationStrategyTests extends AbstractNodeT instanceOf(IndexBasedNodeTypeRepresentationStrategy.class)); } - @Test + @Test @Transactional @Override public void testPostEntityCreation() throws Exception { diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategyTests.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategyTests.java index 1f82cb775..079d5353d 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategyTests.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategyTests.java @@ -30,6 +30,8 @@ import org.springframework.transaction.annotation.Transactional; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Tests to ensure that all scenarios involved in entity creation / reading etc @@ -69,5 +71,9 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends AbstractNodeT // preEntityRemoval is a no op method, so nothing to test here! } - + @Test + @Override + public void testAssertLabelIndexOrNot() throws Exception { + assertTrue("label based", nodeTypeRepresentationStrategy.isLabelBased()); + } } diff --git a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml index fe5cfdfef..444a6a1c0 100644 --- a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml +++ b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml @@ -32,6 +32,7 @@ + @@ -60,6 +61,9 @@ + + + diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Labels.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Labels.java new file mode 100644 index 000000000..76918fe3b --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/annotation/Labels.java @@ -0,0 +1,36 @@ +/** + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.neo4j.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to explcitely declare a property handled by datastore-graph. Automatically indexes the property. + * Only required in partial mode. Otherwise properties are handled by default if they are primitive or convertible to + * a String using the built in conversion services. + * + * @author Michael Hunger + * @since 27.08.2010 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) +public @interface Labels { + String[] defaultValue() default {}; +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java index a01550d5f..887042769 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java @@ -47,6 +47,7 @@ import org.springframework.data.neo4j.support.node.NodeEntityInstantiator; import org.springframework.data.neo4j.support.node.NodeEntityStateFactory; import org.springframework.data.neo4j.support.relationship.RelationshipEntityInstantiator; import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; import org.springframework.data.neo4j.support.typerepresentation.ClassValueTypeInformationMapper; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy; @@ -238,7 +239,7 @@ public abstract class Neo4jConfiguration { @Bean public IndexCreationMappingEventListener indexCreationMappingEventListener() throws Exception { - return new IndexCreationMappingEventListener(indexProvider()); + return new IndexCreationMappingEventListener(indexProvider(),schemaIndexProvider()); } @Bean @@ -264,6 +265,11 @@ public abstract class Neo4jConfiguration { return new IndexProviderImpl(graphDatabase()); } + @Bean + public SchemaIndexProvider schemaIndexProvider() throws Exception { + return new SchemaIndexProvider(graphDatabase()); + } + @Bean public TypeSafetyPolicy typeSafetyPolicy() throws Exception { return new TypeSafetyPolicy(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/TypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/TypeRepresentationStrategy.java index 4f64a77a5..2d29c7f28 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/TypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/TypeRepresentationStrategy.java @@ -70,4 +70,6 @@ public interface TypeRepresentationStrategy { * @param state Backing state of entity being removed */ void preEntityRemoval(S state); + + boolean isLabelBased(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/LabelFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/LabelFieldAccessorFactory.java new file mode 100644 index 000000000..7235aae8d --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/LabelFieldAccessorFactory.java @@ -0,0 +1,111 @@ +/** + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.neo4j.fieldaccess; + + +import org.neo4j.graphdb.*; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.neo4j.annotation.Labels; +import org.springframework.data.neo4j.mapping.MappingPolicy; +import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; +import org.springframework.data.neo4j.support.Neo4jTemplate; + +import java.util.ArrayList; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import static org.springframework.data.neo4j.support.DoReturn.doReturn; + +/** + * @author Michael Hunger + * @since 12.09.2010 + */ +public class LabelFieldAccessorFactory implements FieldAccessorFactory { + private final Neo4jTemplate template; + + public LabelFieldAccessorFactory(Neo4jTemplate template) { + this.template = template; + } + + @Override + public boolean accept(final Neo4jPersistentProperty property) { + return property.isAnnotationPresent(Labels.class); + } + + @Override + public FieldAccessor forField(final Neo4jPersistentProperty property) { + return new LabelFieldAccessor(property, template); + } + + public static class LabelFieldAccessor implements FieldAccessor { + protected final Neo4jPersistentProperty property; + private final Neo4jTemplate template; + + public LabelFieldAccessor(final Neo4jPersistentProperty property, Neo4jTemplate template) { + this.property = property; + this.template = template; + } + + @Override + public boolean isWriteable(Object entity) { + return true; + } + + @Override + public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) { + if (entity==null) return entity; + final PropertyContainer state = template.getPersistentState(entity); + if (state instanceof Node) { + Node node = (Node) state; + Set oldLabels = getLabels(node); + for (String newLabel : (Iterable) entity) { + if (oldLabels.remove(newLabel)) continue; + node.addLabel(DynamicLabel.label(newLabel)); + } + for (String removedLabels : oldLabels) { + node.removeLabel(DynamicLabel.label(removedLabels)); + } + return doReturn(newVal); + } + throw new MappingException("Error setting labels on "+entity); + } + + @Override + public Object getValue(final Object entity, MappingPolicy mappingPolicy) { + final PropertyContainer state = template.getPersistentState(entity); + if (state instanceof Node) { + return doReturn(getLabels((Node) state)); + } + throw new MappingException("Error retrieving labels from "+entity); + } + + private Set getLabels(Node state) { + Set labels = new TreeSet<>(); + for (Label label : state.getLabels()) { + labels.add(label.name()); + } + return labels; + } + + @Override + public Object getDefaultValue() { + return null; + } + + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java index f83b8e97f..c21f10e21 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java @@ -48,6 +48,7 @@ public class NodeDelegatingFieldAccessorFactory extends DelegatingFieldAccessorF return Arrays.asList( new IdFieldAccessorFactory(template), new TransientFieldAccessorFactory(), +//TODO Labels new LabelFieldAccessorFactory(template), new TraversalFieldAccessorFactory(template), new QueryFieldAccessorFactory(template), new PropertyFieldAccessorFactory(template), diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java index b4c6d50af..19697d159 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java @@ -15,8 +15,10 @@ */ package org.springframework.data.neo4j.mapping; +import org.springframework.data.mapping.model.MappingException; import org.springframework.data.neo4j.annotation.Indexed; import org.springframework.data.neo4j.support.index.IndexType; +import org.springframework.data.neo4j.support.mapping.StoredEntityType; /** * @author mh @@ -32,13 +34,36 @@ public class IndexInfo { private boolean numeric; public IndexInfo(Indexed annotation, Neo4jPersistentProperty property) { - this.indexName = determineIndexName(annotation, property); this.indexType = annotation.indexType(); + this.indexName = isLabelBased() ? determineLabelIndexName(annotation, property) : determineIndexName(annotation, property); fieldName = annotation.fieldName(); this.indexKey = fieldName.isEmpty() ? property.getNeo4jPropertyName() : fieldName; unique = annotation.unique(); level = annotation.level(); numeric = annotation.numeric(); + verify(property); + } + + private void verify(Neo4jPersistentProperty property) { + if (isLabelBased() && numeric) { + throw new MappingException("No numeric indexing and range queries currently supported for label based indexes, property: " + property.getOwner().getName()+"."+property.getName()); + } + } + + private String determineLabelIndexName(Indexed annotation, Neo4jPersistentProperty property) { + if (!annotation.indexName().isEmpty()) throw new MappingException("No index name allowed on label based indexes"); + Neo4jPersistentEntity entity = property.getOwner(); + StoredEntityType entityType = entity.getEntityType(); + switch (annotation.level()) { + case CLASS: + Class declaringClass = property.getField().getDeclaringClass(); + StoredEntityType classType = entityType.findByTypeClass(declaringClass); + return classType.getAlias().toString(); + case INSTANCE: + return entityType.getAlias().toString(); + case GLOBAL: throw new MappingException("No global index for label based indexes"); + } + return entityType.getAlias().toString(); } @@ -49,6 +74,10 @@ public class IndexInfo { return Indexed.Name.get(annotation.level(), declaringClass, providedIndexName, instanceType); } + public boolean isLabelBased() { + return indexType.isLabelBased(); + } + public String getIndexName() { return indexName; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java index 50cdc48af..411cd5cdf 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java @@ -16,15 +16,12 @@ package org.springframework.data.neo4j.repository; -import org.apache.lucene.search.NumericRangeQuery; import org.neo4j.cypherdsl.grammar.Execute; import org.neo4j.cypherdsl.grammar.Skip; -import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.PropertyContainer; import org.neo4j.graphdb.index.IndexHits; import org.neo4j.graphdb.index.ReadableIndex; import org.neo4j.helpers.collection.ClosableIterable; -import org.neo4j.helpers.collection.IterableWrapper; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -32,15 +29,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.neo4j.annotation.QueryType; import org.springframework.data.neo4j.conversion.EndResult; -import org.springframework.data.neo4j.conversion.Result; -import org.springframework.data.neo4j.core.TypeRepresentationStrategy; -import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.repository.query.CypherQuery; import org.springframework.data.neo4j.support.Neo4jTemplate; -import org.springframework.data.neo4j.support.index.NoSuchIndexException; -import org.springframework.data.neo4j.support.index.NullReadableIndex; import org.springframework.data.neo4j.support.query.QueryEngine; -import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy; import org.springframework.transaction.annotation.Transactional; import java.util.*; @@ -57,6 +48,7 @@ import static org.neo4j.helpers.collection.MapUtil.map; */ @Transactional(readOnly = true) public abstract class AbstractGraphRepository implements GraphRepository, NamedIndexRepository, SpatialRepository, CypherDslRepository { + private final LegacyIndexSearcher legacyIndexSearcher; /* index.query( LayerNodeIndex.WITHIN_WKT_GEOMETRY_QUERY, @@ -70,22 +62,25 @@ public abstract class AbstractGraphRepository im @Override public EndResult findWithinWellKnownText( final String indexName, String wellKnownText) { - return geoQuery(indexName, "withinWKTGeometry", wellKnownText); + return legacyIndexSearcher.geoQuery(indexName, "withinWKTGeometry", wellKnownText); } @Override public EndResult findWithinDistance( final String indexName, final double lat, double lon, double distanceKm) { - return geoQuery(indexName, "withinDistance", map("point", new Double[] { lon, lat}, "distanceInKm", distanceKm)); + return legacyIndexSearcher.geoQuery(indexName, "withinDistance", map("point", new Double[] { lon, lat}, "distanceInKm", distanceKm)); } @Override public EndResult findWithinBoundingBox(final String indexName, final double lowerLeftLat, final double lowerLeftLon, final double upperRightLat, final double upperRightLon) { - return geoQuery(indexName, "bbox", format("[%s, %s, %s, %s]", lowerLeftLon, upperRightLon, lowerLeftLat, upperRightLat)); + return legacyIndexSearcher.geoQuery(indexName, "bbox", format("[%s, %s, %s, %s]", lowerLeftLon, upperRightLon, lowerLeftLat, upperRightLat)); } - private Result geoQuery(String indexName, String geoQuery, Object params) { - final IndexHits indexHits = getIndex(indexName,null).query(geoQuery, params); - return template.convert(new IndexHitsWrapper(indexHits)); + interface Query { + IndexHits query(ReadableIndex index); + } + + protected T createEntity(S node) { + return template.createEntityFromState(node, clazz, template.getMappingPolicy(clazz)); } public static final ClosableIterable EMPTY_CLOSABLE_ITERABLE = new ClosableIterable() { @@ -107,6 +102,7 @@ public abstract class AbstractGraphRepository im public AbstractGraphRepository(final Neo4jTemplate template, final Class clazz) { this.template = template; this.clazz = clazz; + legacyIndexSearcher = new LegacyIndexSearcher<>(template,clazz); } @Override @@ -175,40 +171,10 @@ public abstract class AbstractGraphRepository im */ @Override public T findByPropertyValue(final String indexName, final String property, final Object value) { - try { - S result = getIndexHits(indexName, property, value).getSingle(); - if (result == null) return null; - return createEntity(result); - } catch (NotFoundException e) { - return null; - } + return legacyIndexSearcher.findByPropertyValue(indexName, property, value); } - private IndexHits getIndexHits(String indexName, String propertyName, Object value) { - final Neo4jPersistentProperty property = template.getPersistentProperty(clazz, propertyName); - if (value instanceof Number && (property==null || property.getIndexInfo().isNumeric())) { - Number number = (Number) value; - return getIndex(indexName, propertyName).query(propertyName, createInclusiveRangeQuery(propertyName, number,number)); - } - return getIndex(indexName, propertyName).get(propertyName, value); - } - - protected ReadableIndex getIndex(String indexName, String property) { - try { - if (indexName!=null) { - return template.getIndex(indexName,clazz); - } - return template.getIndex(clazz,property); - } catch(NoSuchIndexException nsie) { - return new NullReadableIndex(nsie.getIndex(),template.getGraphDatabaseService()); - } - } - - protected T createEntity(S node) { - return template.createEntityFromState(node, clazz, template.getMappingPolicy(clazz)); - } - /** * Index based exact finder. * @@ -219,12 +185,9 @@ public abstract class AbstractGraphRepository im */ @Override public EndResult findAllByPropertyValue(final String indexName, final String property, final Object value) { - return queryResult(indexName, new Query() { - public IndexHits query(ReadableIndex index) { - return getIndexHits(indexName, property, value); - } - }); + return legacyIndexSearcher.findAllByPropertyValue(indexName, property, value); } + /** * Index based exact finder, uses the default index name for this type (short class name). * @param property @@ -255,39 +218,7 @@ public abstract class AbstractGraphRepository im */ @Override public EndResult findAllByQuery(final String indexName, final String property, final Object query) { - return queryResult(indexName, new Query() { - public IndexHits query(ReadableIndex index) { - return getIndex(indexName, property).query(property, query); - } - }); - } - - interface Query { - IndexHits query(ReadableIndex index); - } - - private ClosableIterable query(String indexName, Query query) { - try { - final IndexHits indexHits = query.query(getIndex(indexName, null)); - if (indexHits == null) return emptyClosableIterable(); - return new IndexHitsWrapper(indexHits); - } catch (NotFoundException e) { - return null; - } - } - - private EndResult queryResult(String indexName, Query query) { - try { - final IndexHits indexHits = query.query(getIndex(indexName, null)); - return template.convert(indexHits).to(clazz); - } catch (NotFoundException e) { - return null; - } - } - - @SuppressWarnings({"unchecked"}) - private ClosableIterable emptyClosableIterable() { - return EMPTY_CLOSABLE_ITERABLE; + return legacyIndexSearcher.findAllByQuery(indexName, property, query); } @Override @@ -296,21 +227,9 @@ public abstract class AbstractGraphRepository im } @Override public EndResult findAllByRange(final String indexName, final String property, final Number from, final Number to) { - return queryResult(indexName, new Query() { - public IndexHits query(ReadableIndex index) { - return index.query(property, createInclusiveRangeQuery(property, from, to)); - } - }); + return legacyIndexSearcher.findAllByRange(indexName, property, from, to); } - @SuppressWarnings("unchecked") - protected NumericRangeQuery createInclusiveRangeQuery(String property, Number from, Number to) { - if (from instanceof Long) return (NumericRangeQuery) NumericRangeQuery.newLongRange(property, from.longValue(),to.longValue(),true,true); - if (from instanceof Integer) return (NumericRangeQuery) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true); - if (from instanceof Double) return (NumericRangeQuery) NumericRangeQuery.newDoubleRange(property, from.doubleValue(), to.doubleValue(), true, true); - if (from instanceof Float) return (NumericRangeQuery) NumericRangeQuery.newFloatRange(property, from.floatValue(), to.floatValue(), true, true); - return (NumericRangeQuery) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true); - } protected abstract S getById(long id); @@ -356,8 +275,7 @@ public abstract class AbstractGraphRepository im @Override public EndResult findAll(Sort sort) { - TypeRepresentationStrategy nodeTypeRepresentationStrategy = template.getInfrastructure().getNodeTypeRepresentationStrategy(); - CypherQuery cq = new CypherQuery(template.getEntityType(clazz).getEntity(),template,nodeTypeRepresentationStrategy); + CypherQuery cq = new CypherQuery(template.getEntityType(clazz).getEntity(),template, template.isLabelBased()); return query(cq.toQueryString(sort), Collections.EMPTY_MAP); } @@ -427,25 +345,6 @@ public abstract class AbstractGraphRepository im return count; } - private class IndexHitsWrapper extends IterableWrapper implements ClosableIterable { - private final IndexHits indexHits; - - public IndexHitsWrapper(IndexHits indexHits) { - super(indexHits); - this.indexHits = indexHits; - } - - @SuppressWarnings({"unchecked"}) - protected T underlyingObjectToObject(final S result) { - return createEntity(result); - } - - @Override - public void close() { - this.indexHits.close(); - } - } - @SuppressWarnings("unchecked") @Override public Page query(Execute query, Execute countQuery, Map params, Pageable page) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/LegacyIndexSearcher.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/LegacyIndexSearcher.java new file mode 100644 index 000000000..2a8b8efe4 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/LegacyIndexSearcher.java @@ -0,0 +1,145 @@ +package org.springframework.data.neo4j.repository; + +import org.apache.lucene.search.NumericRangeQuery; +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.graphdb.PropertyContainer; +import org.neo4j.graphdb.index.IndexHits; +import org.neo4j.graphdb.index.ReadableIndex; +import org.neo4j.helpers.collection.ClosableIterable; +import org.neo4j.helpers.collection.IterableWrapper; +import org.springframework.data.neo4j.conversion.EndResult; +import org.springframework.data.neo4j.conversion.Result; +import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; +import org.springframework.data.neo4j.support.Neo4jTemplate; +import org.springframework.data.neo4j.support.index.NoSuchIndexException; +import org.springframework.data.neo4j.support.index.NullReadableIndex; + +/** +* @author mh +* @since 01.02.14 +*/ +public class LegacyIndexSearcher { + private final Neo4jTemplate template; + private final Class clazz; + + LegacyIndexSearcher(Neo4jTemplate template, Class clazz) { + this.template = template; + this.clazz = clazz; + } + public Result geoQuery(String indexName, String geoQuery, Object params) { + final IndexHits indexHits = getIndex(indexName,null).query(geoQuery, params); + Iterable wrapper = (Iterable) new IndexHitsWrapper(indexHits); + return template.convert(wrapper); + } + + private ReadableIndex getIndex(String indexName, String property) { + try { + if (indexName!=null) { + return template.getIndex(indexName,clazz); + } + return template.getIndex(clazz,property); + } catch(NoSuchIndexException nsie) { + return new NullReadableIndex(nsie.getIndex(),template.getGraphDatabaseService()); + } + } + + private T createEntity(S node) { + return template.createEntityFromState(node, clazz, template.getMappingPolicy(clazz)); + } + + private class IndexHitsWrapper extends IterableWrapper implements ClosableIterable { + private final IndexHits indexHits; + + public IndexHitsWrapper(IndexHits indexHits) { + super(indexHits); + this.indexHits = indexHits; + } + + @SuppressWarnings({"unchecked"}) + protected T underlyingObjectToObject(final S result) { + return createEntity(result); + } + + @Override + public void close() { + this.indexHits.close(); + } + } + + private IndexHits getIndexHits(String indexName, String propertyName, Object value) { + final Neo4jPersistentProperty property = template.getPersistentProperty(clazz, propertyName); + if (value instanceof Number && (property==null || property.getIndexInfo().isNumeric())) { + Number number = (Number) value; + return getIndex(indexName, propertyName).query(propertyName, createInclusiveRangeQuery(propertyName, number,number)); + } + return getIndex(indexName, propertyName).get(propertyName, value); + } + + private ClosableIterable query(String indexName, AbstractGraphRepository.Query query) { + try { + final IndexHits indexHits = query.query(getIndex(indexName, null)); + if (indexHits == null) return emptyClosableIterable(); + return new IndexHitsWrapper(indexHits); + } catch (NotFoundException e) { + return null; + } + } + + @SuppressWarnings({"unchecked"}) + private ClosableIterable emptyClosableIterable() { + return AbstractGraphRepository.EMPTY_CLOSABLE_ITERABLE; + } + + @SuppressWarnings("unchecked") + protected NumericRangeQuery createInclusiveRangeQuery(String property, Number from, Number to) { + if (from instanceof Long) return (NumericRangeQuery) NumericRangeQuery.newLongRange(property, from.longValue(),to.longValue(),true,true); + if (from instanceof Integer) return (NumericRangeQuery) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true); + if (from instanceof Double) return (NumericRangeQuery) NumericRangeQuery.newDoubleRange(property, from.doubleValue(), to.doubleValue(), true, true); + if (from instanceof Float) return (NumericRangeQuery) NumericRangeQuery.newFloatRange(property, from.floatValue(), to.floatValue(), true, true); + return (NumericRangeQuery) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true); + } + + public EndResult findAllByRange(String indexName, final String property, final Number from, final Number to) { + return queryResult(indexName, new AbstractGraphRepository.Query() { + public IndexHits query(ReadableIndex index) { + return index.query(property, createInclusiveRangeQuery(property, from, to)); + } + }); + } + + public EndResult findAllByQuery(final String indexName, final String property, final Object query) { + return queryResult(indexName, new AbstractGraphRepository.Query() { + public IndexHits query(ReadableIndex index) { + return getIndex(indexName, property).query(property, query); + } + }); + } + + public T findByPropertyValue(String indexName, String property, Object value) { + try { + S result = getIndexHits(indexName, property, value).getSingle(); + if (result == null) return null; + return createEntity(result); + } catch (NotFoundException e) { + return null; + } + } + + + public EndResult findAllByPropertyValue(final String indexName, final String property, final Object value) { + return queryResult(indexName, new AbstractGraphRepository.Query() { + public IndexHits query(ReadableIndex index) { + return getIndexHits(indexName, property, value); + } + }); + } + + private EndResult queryResult(String indexName, AbstractGraphRepository.Query query) { + try { + final IndexHits indexHits = query.query(getIndex(indexName, null)); + return template.convert(indexHits).to(clazz); + } catch (NotFoundException e) { + return null; + } + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQuery.java index 2cc60d4fb..b7b63621f 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQuery.java @@ -18,11 +18,9 @@ package org.springframework.data.neo4j.repository.query; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.context.PersistentPropertyPath; -import org.springframework.data.neo4j.core.TypeRepresentationStrategy; import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.Neo4jTemplate; -import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.parser.Part; @@ -43,10 +41,10 @@ public class CypherQuery implements CypherQueryDefinition { private boolean isCountQuery = false; private boolean useLabels = false; - public CypherQuery(final Neo4jPersistentEntity entity, Neo4jTemplate template, TypeRepresentationStrategy nodeTypeRepresentationStrategy) { + public CypherQuery(final Neo4jPersistentEntity entity, Neo4jTemplate template, boolean useLabels) { this.entity = entity; this.template = template; - this.useLabels = nodeTypeRepresentationStrategy instanceof LabelBasedNodeTypeRepresentationStrategy; + this.useLabels = useLabels; } private String getEntityName(Neo4jPersistentEntity entity) { @@ -69,19 +67,25 @@ public class CypherQuery implements CypherQueryDefinition { String variable = variableContext.getVariableFor(path); final PartInfo partInfo = new PartInfo(path, variable, part, index); + MatchClause matchClause = new MatchClause(path); // index("a:foo AND b:bar") // a=index1(a="foo"), b=index2(b="bar") where a=b - not good b/c of cross product // index1(a=foo) where a.foo=bar Neo4jPersistentProperty leafProperty = partInfo.getLeafProperty(); - if (partInfo.isPrimitiveProperty() && !leafProperty.isIdProperty()) { + boolean isIdProperty = leafProperty.isIdProperty(); + boolean addedMatchClause = false; + if (partInfo.isPrimitiveProperty() && !isIdProperty) { if (!addedStartClause(partInfo)) { whereClauses.add(new WhereClause(partInfo,template)); } - } else if (leafProperty.isRelationship() || leafProperty.isIdProperty()) { - startClauses.add(new GraphIdStartClause(partInfo)); + } else if (leafProperty.isRelationship() || isIdProperty) { if (useLabels) { + whereClauses.add(new IdPropertyWhereClause(new PartInfo(path, variable, part, index), template)); whereClauses.add(new LabelBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template)); + matchClauses.add(matchClause); + addedMatchClause = true; } else { + startClauses.add(new GraphIdStartClause(partInfo)); whereClauses.add(new IndexBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template)); } } else { @@ -89,9 +93,7 @@ public class CypherQuery implements CypherQueryDefinition { } index += 1; - MatchClause matchClause = new MatchClause(path); - - if (matchClause.hasRelationship()) { + if (!addedMatchClause && matchClause.hasRelationship()) { matchClauses.add(matchClause); } } @@ -106,8 +108,7 @@ public class CypherQuery implements CypherQueryDefinition { for (Sort.Order o : sorts) { entityAwareOrders.add( getEntityAwareOrderRef(o) ); } - Sort entityAwareSort = new Sort(entityAwareOrders); - return entityAwareSort; + return new Sort(entityAwareOrders); } private Sort.Order getEntityAwareOrderRef(Sort.Order o) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java index 833586836..eda1e1c3b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java @@ -20,7 +20,6 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.Neo4jTemplate; -import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy; import org.springframework.data.repository.query.parser.Part; /** @@ -36,7 +35,7 @@ class CypherQueryBuilder { public CypherQueryBuilder(MappingContext, Neo4jPersistentProperty> context, Class type, Neo4jTemplate template) { this.context = context; Neo4jPersistentEntity entity = context.getPersistentEntity(type); - this.query = new CypherQuery(entity, template, template.getInfrastructure().getNodeTypeRepresentationStrategy()); + this.query = new CypherQuery(entity, template, template.isLabelBased()); } public CypherQueryBuilder asCountQuery() { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/IdPropertyWhereClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/IdPropertyWhereClause.java new file mode 100644 index 000000000..9b1051732 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/IdPropertyWhereClause.java @@ -0,0 +1,58 @@ +/** + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.repository.query; + +import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; +import org.springframework.data.neo4j.support.Neo4jTemplate; +import org.springframework.data.repository.query.parser.Part; + +import java.util.EnumSet; + +import static org.springframework.data.neo4j.repository.query.QueryTemplates.*; + +/** + * Representation of a Cypher {@literal where} clause specifically for + * use to narrow the results based on particular entity types, where + * those entities can be identified via specific Labels (as per the + * Label Based Type Representation Strategy) + * + * @author Nicki Watt + */ +public class IdPropertyWhereClause extends WhereClause { + + + + public IdPropertyWhereClause(PartInfo partInfo, Neo4jTemplate template) { + super(partInfo, template); + } + + + @Override + public String toString() { + final String operator = SYMBOLS.get(type); + String variable = partInfo.getIdentifier(); + String result = String.format(WHERE_CLAUSE_ID, variable, operator, partInfo.getParameterIndex()); + if (EnumSet.of(Part.Type.NOT_IN).contains(type)) { + result = "not( "+result+" )"; + } + return result; + } + + @Override + protected Object convertValue(PartInfo partInfo, Object value) { + return value; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java index cc78cb41b..e76822e91 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java @@ -63,6 +63,10 @@ class MatchClause { } private String matchPattern(VariableContext variableContext, PersistentPropertyPath relPath) { + if (!relPath.getLeafProperty().isRelationship()) { + final Neo4jPersistentProperty property = relPath.getBaseProperty(); + return formatMatch(variableContext.getVariableFor(property.getOwner())); + } if (relPath.getLength() == 1) { final Neo4jPersistentProperty property = relPath.getBaseProperty(); return formatMatch(variableContext.getVariableFor(property.getOwner()), @@ -75,6 +79,9 @@ class MatchClause { variableContext.getVariableFor(relPath)); } + private String formatMatch(String single) { + return String.format(QueryTemplates.MATCH_CLAUSE_SINGLE, single); + } private String formatMatch(String first, String arrow, String second) { return String.format(QueryTemplates.MATCH_CLAUSE, first, arrow, second); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java index aa0b0e4bc..eec3dbf89 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java @@ -42,6 +42,7 @@ public abstract class QueryTemplates { private static final String DIRECTION_INCOMING = "<-[:`%s`]-"; private static final String DIRECTION_OUTGOING = "-[:`%s`]->"; private static final String DIRECTION_BOTH = "-[:`%s`]-"; + static final String MATCH_CLAUSE_SINGLE = "(`%s`)"; static final String MATCH_CLAUSE = "(`%s`)%s(`%s`)"; static final String MATCH_CLAUSE2 = "%s%s(`%s`)"; @@ -53,6 +54,7 @@ public abstract class QueryTemplates { static final String START_CLAUSE_INDEX_LOOKUP = "`%s`=node:`%s`(`%s`=" + PLACEHOLDER + ")"; static final String START_CLAUSE_INDEX_QUERY = "`%s`=node:`%s`(" + PLACEHOLDER + ")"; static final String WHERE_CLAUSE_1 = "`%1$s`.`%2$s` %3$s {%4$d}"; + static final String WHERE_CLAUSE_ID = "id(`%1$s`) %2$s {%3$d}"; static final String INDEXBASED_WHERE_TYPE_CHECK = "`%1$s`.__type__ IN [%2$s]"; static final String LABELBASED_WHERE_TYPE_CHECK = "`%1$s`:%2$s"; static final String WHERE_CLAUSE_0 = "`%1$s`.`%2$s` %3$s "; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java index 61534fb22..8db3dee5d 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java @@ -67,7 +67,7 @@ class WhereClause { } protected final PartInfo partInfo; - private final Type type; + protected final Type type; private PropertyConverter propertyConverter; public WhereClause(PartInfo partInfo, Neo4jTemplate template) { @@ -108,15 +108,18 @@ class WhereClause { public Map resolveParameters(Map parameters) { for (Map.Entry entry : parameters.entrySet()) { if (partInfo.getParameterIndex() == entry.getKey().getIndex()) { - Object value = entry.getValue(); - if (EnumSet.of(Type.CONTAINING,Type.STARTING_WITH,Type.ENDING_WITH).contains(type)) - value = QueryTemplates.formatExpression(partInfo, value); - else if (propertyConverter!=null) { - value = propertyConverter.serializePropertyValue(value); - } - entry.setValue(value); + entry.setValue(convertValue(partInfo, entry.getValue())); } } return parameters; } + + protected Object convertValue(PartInfo partInfo, Object value) { + if (EnumSet.of(Type.CONTAINING, Type.STARTING_WITH, Type.ENDING_WITH).contains(type)) + return QueryTemplates.formatExpression(this.partInfo, value); + else if (propertyConverter!=null) { + return propertyConverter.serializePropertyValue(value); + } + return value; + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java index 698402d6a..65136cbc5 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java @@ -30,6 +30,7 @@ import org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister; import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.support.node.EntityStateFactory; import org.springframework.data.neo4j.support.query.CypherQueryExecutor; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy; @@ -71,4 +72,8 @@ public interface Infrastructure { TypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(); TypeSafetyPolicy getTypeSafetyPolicy(); + + SchemaIndexProvider getSchemaIndexProvider(); + + CypherQueryExecutor getCypherQueryExecutor(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java index dd420fdb9..e0487a734 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java @@ -30,6 +30,7 @@ import org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister; import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.support.node.EntityStateFactory; import org.springframework.data.neo4j.support.query.CypherQueryExecutor; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy; @@ -58,11 +59,12 @@ public class MappingInfrastructure implements Infrastructure { private final PlatformTransactionManager transactionManager; private final ResultConverter resultConverter; private final IndexProvider indexProvider; + private final SchemaIndexProvider schemaIndexProvider; private final GraphDatabaseService graphDatabaseService; private final GraphDatabase graphDatabase; private final TypeSafetyPolicy typeSafetyPolicy; - public MappingInfrastructure(GraphDatabase graphDatabase, GraphDatabaseService graphDatabaseService, IndexProvider indexProvider, ResultConverter resultConverter, PlatformTransactionManager transactionManager, TypeRepresentationStrategies typeRepresentationStrategies, EntityRemover entityRemover, Neo4jEntityPersister entityPersister, EntityStateHandler entityStateHandler, CypherQueryExecutor cypherQueryExecutor, Neo4jMappingContext mappingContext, TypeRepresentationStrategy relationshipTypeRepresentationStrategy, TypeRepresentationStrategy nodeTypeRepresentationStrategy, Validator validator, ConversionService conversionService, TypeSafetyPolicy typeSafetyPolicy) { + public MappingInfrastructure(GraphDatabase graphDatabase, GraphDatabaseService graphDatabaseService, IndexProvider indexProvider, ResultConverter resultConverter, PlatformTransactionManager transactionManager, TypeRepresentationStrategies typeRepresentationStrategies, EntityRemover entityRemover, Neo4jEntityPersister entityPersister, EntityStateHandler entityStateHandler, CypherQueryExecutor cypherQueryExecutor, Neo4jMappingContext mappingContext, TypeRepresentationStrategy relationshipTypeRepresentationStrategy, TypeRepresentationStrategy nodeTypeRepresentationStrategy, Validator validator, ConversionService conversionService, SchemaIndexProvider schemaIndexProvider, TypeSafetyPolicy typeSafetyPolicy) { this.graphDatabase = graphDatabase; this.graphDatabaseService = graphDatabaseService; this.indexProvider = indexProvider; @@ -78,6 +80,7 @@ public class MappingInfrastructure implements Infrastructure { this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy; this.validator = validator; this.conversionService = conversionService; + this.schemaIndexProvider = schemaIndexProvider; this.typeSafetyPolicy = typeSafetyPolicy; } @@ -155,4 +158,14 @@ public class MappingInfrastructure implements Infrastructure { public TypeSafetyPolicy getTypeSafetyPolicy() { return typeSafetyPolicy; } + + @Override + public SchemaIndexProvider getSchemaIndexProvider() { + return schemaIndexProvider; + } + + @Override + public CypherQueryExecutor getCypherQueryExecutor() { + return cypherQueryExecutor; + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java index 8184f49f5..6aed54850 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java @@ -44,6 +44,7 @@ import org.springframework.data.neo4j.support.node.NodeEntityStateFactory; import org.springframework.data.neo4j.support.query.CypherQueryExecutor; import org.springframework.data.neo4j.support.relationship.RelationshipEntityInstantiator; import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy; @@ -77,6 +78,7 @@ public class MappingInfrastructureFactoryBean implements FactoryBean T save(T entity) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java index b3b3c85b3..3515aa8fb 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexProviderImpl.java @@ -23,6 +23,7 @@ import org.springframework.data.neo4j.annotation.Indexed; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; import static org.springframework.data.neo4j.support.ParameterCheck.notNull; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexType.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexType.java index 04ec9d7cf..5e36a0825 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexType.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/index/IndexType.java @@ -23,11 +23,15 @@ import java.util.Map; public enum IndexType { - SIMPLE{ public Map getConfig() { return LuceneIndexImplementation.EXACT_CONFIG; } }, + SIMPLE { public Map getConfig() { return LuceneIndexImplementation.EXACT_CONFIG; } }, + LABEL { public Map getConfig() { return null; } public boolean isLabelBased() { return true; }}, FULLTEXT { public Map getConfig() { return LuceneIndexImplementation.FULLTEXT_CONFIG; } }, - POINT { public Map getConfig() { return MapUtil.stringMap( - IndexManager.PROVIDER, "spatial", "geometry_type" , "point","wkt","wkt") ; } }, - UNIQUE(){ public Map getConfig() { return LuceneIndexImplementation.EXACT_CONFIG; } }; + POINT { public Map getConfig() { return MapUtil.stringMap( + IndexManager.PROVIDER, "spatial", "geometry_type" , "point","wkt","wkt") ; } } + + ; public abstract MapgetConfig(); + + public boolean isLabelBased() { return false; } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/IndexCreationMappingEventListener.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/IndexCreationMappingEventListener.java index c6a994cd3..35e864d69 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/IndexCreationMappingEventListener.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/IndexCreationMappingEventListener.java @@ -15,7 +15,6 @@ */ package org.springframework.data.neo4j.support.mapping; -import org.neo4j.graphdb.index.Index; import org.springframework.context.ApplicationListener; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.MappingContextEvent; @@ -23,6 +22,7 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.index.IndexProvider; import org.springframework.data.neo4j.support.index.IndexType; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; /** * @author mh @@ -30,8 +30,11 @@ import org.springframework.data.neo4j.support.index.IndexType; */ public class IndexCreationMappingEventListener implements ApplicationListener, Neo4jPersistentProperty>> { private IndexProvider indexProvider; - public IndexCreationMappingEventListener(IndexProvider indexProvider) { + private SchemaIndexProvider schemaIndexProvider; + + public IndexCreationMappingEventListener(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider) { this.indexProvider = indexProvider; + this.schemaIndexProvider = schemaIndexProvider; } @Override @@ -43,11 +46,14 @@ public class IndexCreationMappingEventListener implements ApplicationListener entity) { final Class entityType = entity.getType(); - indexProvider.getIndex(entity, null, IndexType.SIMPLE); + indexProvider.getIndex(entity, null, IndexType.SIMPLE); // TODO only when TRS is non-label? entity.doWithProperties(new PropertyHandler() { @Override public void doWithPersistentProperty(Neo4jPersistentProperty property) { - if (property.isIndexed()) { + if (!property.isIndexed()) return; + if (property.getIndexInfo().isLabelBased()) { + schemaIndexProvider.createIndex(property); + } else { indexProvider.getIndex(property, entityType); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java index b11dfd917..cd05f7594 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java @@ -29,11 +29,7 @@ import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.neo4j.annotation.NodeEntity; import org.springframework.data.neo4j.annotation.RelationshipEntity; -import org.springframework.data.neo4j.mapping.ManagedEntity; -import org.springframework.data.neo4j.mapping.MappingPolicy; -import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; -import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; -import org.springframework.data.neo4j.mapping.RelationshipProperties; +import org.springframework.data.neo4j.mapping.*; import org.springframework.data.util.TypeInformation; /** @@ -75,10 +71,10 @@ public class Neo4jPersistentEntityImpl extends BasicPersistentEntity() { Neo4jPersistentProperty unique = null; - public void doWithPersistentProperty(Neo4jPersistentProperty persistentProperty) { - if (persistentProperty.isUnique()) { - if (unique!=null) throw new MappingException("Duplicate unique property " + persistentProperty.getName()+ ", " + unique.getName() + " has already been defined. Only one unique property is allowed per type"); - unique = persistentProperty; + public void doWithPersistentProperty(Neo4jPersistentProperty property) { + if (property.isUnique()) { + if (unique!=null) throw new MappingException("Duplicate unique property " + qualifiedPropertyName(property)+ ", " + qualifiedPropertyName(uniqueProperty) + " has already been defined. Only one unique property is allowed per type"); + unique = property; } } }); @@ -87,7 +83,11 @@ public class Neo4jPersistentEntityImpl extends BasicPersistentEntity cypher; + + + public SchemaIndexProvider(GraphDatabase gd) { + this.gd = gd; + cypher = gd.queryEngineFor(QueryType.Cypher); + + } + + public void createIndex(Neo4jPersistentProperty property) { + IndexInfo indexInfo = property.getIndexInfo(); + String label = indexInfo.getIndexName(); + String prop = property.getNeo4jPropertyName(); + String query = indexQuery(label, prop, indexInfo.isUnique()); + Result result = cypher.query(query, null); + } + + public EndResult findAll(Neo4jPersistentEntity entity) { + String label = entity.getTypeAlias().toString(); + String query = findByLabelQuery(label); + return cypher.query(query, null).to(entity.getType()); + } + + public EndResult findAll(Neo4jPersistentProperty property, Object value) { + IndexInfo indexInfo = property.getIndexInfo(); + String label = indexInfo.getIndexName(); + String prop = property.getNeo4jPropertyName(); + String query = findByLabelAndPropertyQuery(label, prop); + return cypher.query(query, map("value", value)).to((Class)property.getOwner().getType()); + } + + private String findByLabelQuery(String label) { + return "MATCH (n:`"+label+"`) RETURN n"; + } + + private String findByLabelAndPropertyQuery(String label, String prop) { + return "MATCH (n:`"+label+"` {`"+prop+"`:{value}}) RETURN n"; + } + + private String indexQuery(String label, String prop, boolean unique) { + if (unique) { + return "CREATE CONSTRAINT ON (n:`"+ label +"`) ASSERT n.`"+ prop +"` IS UNIQUE"; + } + return "CREATE INDEX ON :`"+ label +"`(`"+ prop +"`)"; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java index 58ac4382d..be1d0b76f 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java @@ -101,6 +101,11 @@ public abstract class AbstractIndexBasedTypeRepresentationStrategy readAliasFrom(Node state) { return null; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/NoopRelationshipTypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/NoopRelationshipTypeRepresentationStrategy.java index 544cbbcfd..c558b415a 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/NoopRelationshipTypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/NoopRelationshipTypeRepresentationStrategy.java @@ -41,6 +41,11 @@ public class NoopRelationshipTypeRepresentationStrategy implements RelationshipT public void preEntityRemoval(Relationship state) { } + @Override + public boolean isLabelBased() { + return false; + } + @Override public Object readAliasFrom(Relationship state) { return null; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java index 64c1f34e7..803d4a32b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java @@ -174,6 +174,11 @@ public class SubReferenceNodeTypeRepresentationStrategy implements NodeTypeRepre } } + @Override + public boolean isLabelBased() { + return false; + } + @Override public ClosableIterable findAll(final StoredEntityType type) { final Node subrefNode = findSubreferenceNode(type); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategies.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategies.java index 2b317cc3d..cb89508ed 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategies.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategies.java @@ -96,6 +96,11 @@ public class TypeRepresentationStrategies implements TypeRepresentationStrategy< getTypeRepresentationStrategy(state).preEntityRemoval(state); } + @Override + public boolean isLabelBased() { + return nodeTypeRepresentationStrategy.isLabelBased(); + } + public TypeRepresentationStrategy getNodeTypeRepresentationStrategy() { return nodeTypeRepresentationStrategy; } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractCypherQueryBuilderTestBase.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractCypherQueryBuilderTestBase.java index 9eb416757..07f69befd 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractCypherQueryBuilderTestBase.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractCypherQueryBuilderTestBase.java @@ -54,14 +54,14 @@ public abstract class AbstractCypherQueryBuilderTestBase { public void setUp() { Neo4jMappingContext context = new Neo4jMappingContext(); Neo4jTemplate template = Mockito.mock(Neo4jTemplate.class); - Infrastructure inf = Mockito.mock(Infrastructure.class); - when (template.getInfrastructure()).thenReturn(inf); - when (inf.getNodeTypeRepresentationStrategy()).thenReturn(getNodeTypeRepresentationStrategy()); + finishMock(template); this.query = new CypherQueryBuilder(context, Person.class, template); this.trsSpecificExpectedQuery = null; } - abstract NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(); + protected void finishMock(Neo4jTemplate template) { + } + @Test public void createsQueryForSimplePropertyReference() { diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java index d85ce6aaf..dee1ce54a 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java @@ -42,6 +42,7 @@ import java.util.concurrent.TimeUnit; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Defines the tests for the various finder method based scenarios with @@ -84,6 +85,7 @@ public abstract class AbstractDerivedFinderMethodTestBase { } } + protected final static String THING_NAME = Thing.class.getName(); @Autowired ThingRepository repository; @Autowired @@ -391,7 +393,7 @@ public abstract class AbstractDerivedFinderMethodTestBase { * This method will either return the trs specific query string if * this was set, otherwise the default value passed in. */ - private String getExpectedQuery(String defaultQueryString) { + protected String getExpectedQuery(String defaultQueryString) { return (this.trsSpecificExpectedQuery != null) ? this.trsSpecificExpectedQuery : defaultQueryString; @@ -404,7 +406,7 @@ public abstract class AbstractDerivedFinderMethodTestBase { * This method will either return the trs specific query params if * this was set, otherwise the default value passed in. */ - private Object[] getExpectedParams(Object... defaultVals) { + protected Object[] getExpectedParams(Object... defaultVals) { return (this.trsSpecificExpectedParams != null) ? this.trsSpecificExpectedParams : (defaultVals == null) ? new Object[0] : defaultVals; @@ -418,7 +420,9 @@ public abstract class AbstractDerivedFinderMethodTestBase { String query = derivedCypherRepositoryQuery.createQueryWithPagingAndSorting(accessor); Map params = derivedCypherRepositoryQuery.resolveParams(accessor); String firstWord = expectedQuery.split("\\s+")[0]; - String actual = query.substring(query.indexOf(firstWord)); + int beginIndex = query.indexOf(firstWord); + assertTrue("didn't find word "+firstWord+" in "+query,beginIndex != -1); + String actual = query.substring(beginIndex); actual = actual.substring(0, Math.min(expectedQuery.length(),actual.length())); assertEquals(expectedQuery, actual); assertEquals(expectedParam.length,params.size()); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForIndexBasedTRSUnitTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForIndexBasedTRSUnitTests.java index 746668a0f..9453acb0c 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForIndexBasedTRSUnitTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForIndexBasedTRSUnitTests.java @@ -39,10 +39,6 @@ public class CypherQueryBuilderForIndexBasedTRSUnitTests extends AbstractCypherQ super.setUp(); } - protected NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() { - return Mockito.mock(IndexBasedNodeTypeRepresentationStrategy.class); - } - @Override @Test public void createsQueryForLikeProperty() { diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForLabelBasedTRSUnitTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForLabelBasedTRSUnitTests.java index 54741d41f..6e4d38923 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForLabelBasedTRSUnitTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderForLabelBasedTRSUnitTests.java @@ -19,6 +19,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy; +import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy; import static org.hamcrest.CoreMatchers.is; @@ -39,8 +40,9 @@ public class CypherQueryBuilderForLabelBasedTRSUnitTests extends AbstractCypherQ super.setUp(); } - protected NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() { - return Mockito.mock(LabelBasedNodeTypeRepresentationStrategy.class); + @Override + protected void finishMock(Neo4jTemplate template) { + Mockito.when(template.isLabelBased()).thenReturn(true); } @Override @@ -101,7 +103,7 @@ public class CypherQueryBuilderForLabelBasedTRSUnitTests extends AbstractCypherQ @Override @Test public void createsSimpleTraversalClauseCorrectly() { - this.trsSpecificExpectedQuery = "START `person_group`=node({0}) MATCH (`person`)<-[:`members`]-(`person_group`) WHERE `person`:`Person` RETURN `person`"; + this.trsSpecificExpectedQuery = " MATCH (`person`)<-[:`members`]-(`person_group`) WHERE id(`person_group`) = {0} AND `person`:`Person` RETURN `person`"; super.createsSimpleTraversalClauseCorrectly(); } @@ -120,14 +122,14 @@ public class CypherQueryBuilderForLabelBasedTRSUnitTests extends AbstractCypherQ @Override @Test public void shouldFindByNodeEntity() throws Exception { - this.trsSpecificExpectedQuery = "START `person_pet`=node({0}) MATCH (`person`)-[:`owns`]->(`person_pet`) WHERE `person`:`Person` RETURN `person`"; + this.trsSpecificExpectedQuery = " MATCH (`person`)-[:`owns`]->(`person_pet`) WHERE id(`person_pet`) = {0} AND `person`:`Person` RETURN `person`"; super.shouldFindByNodeEntity(); } @Override @Test public void shouldFindByNodeEntityForIncomingRelationship() { - this.trsSpecificExpectedQuery = "START `person_group`=node({0}) MATCH (`person`)<-[:`members`]-(`person_group`) WHERE `person`:`Person` RETURN `person`"; + this.trsSpecificExpectedQuery = " MATCH (`person`)<-[:`members`]-(`person_group`) WHERE id(`person_group`) = {0} AND `person`:`Person` RETURN `person`"; super.shouldFindByNodeEntityForIncomingRelationship(); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java index 1a05bbe8d..600e97200 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java @@ -44,7 +44,7 @@ import static org.hamcrest.Matchers.instanceOf; @TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class DerivedFinderMethodForLabelBasedTRSTests extends AbstractDerivedFinderMethodTestBase { - private static final String DEFAULT_MATCH_CLAUSE = "MATCH (`thing`:`org.springframework.data.neo4j.repository.query.AbstractDerivedFinderMethodTestBase$Thing`)"; + private static final String DEFAULT_MATCH_CLAUSE = "MATCH (`thing`:`"+THING_NAME+"`)"; @Autowired NodeTypeRepresentationStrategy strategy; @@ -61,10 +61,19 @@ public class DerivedFinderMethodForLabelBasedTRSTests extends AbstractDerivedFin @Override public void testQueryWithEntityGraphId() throws Exception { // findByOwnerId - this.trsSpecificExpectedQuery = "START `thing_owner`=node({0}) MATCH (`thing`)-[:`owner`]->(`thing_owner`) WHERE `thing`:`org.springframework.data.neo4j.repository.query.AbstractDerivedFinderMethodTestBase$Thing` "; + this.trsSpecificExpectedQuery = "MATCH (`thing`)-[:`owner`]->(`thing_owner`) WHERE id(`thing_owner`) = {0} AND `thing`:`"+THING_NAME+"` RETURN `thing`"; super.testQueryWithEntityGraphId(); } + @Test + public void testQueryWithGraphId() throws Exception { + assertRepositoryQueryMethod(ThingRepository.class, + "findById", + new Object[]{123}, + getExpectedQuery("MATCH (`thing`) WHERE id(`thing`) = {0}"), + getExpectedParams(123)); + } + @Test @Override public void testIndexQueryWithTwoParams() throws Exception {