Skip to content

Commit

Permalink
GH-948: Single/composite index support for relationships.
Browse files Browse the repository at this point in the history
  • Loading branch information
iamyuchen committed Jul 13, 2023
1 parent 4bfa280 commit 62909e9
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 25 deletions.
77 changes: 62 additions & 15 deletions core/src/main/java/org/neo4j/ogm/autoindex/AutoIndex.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class AutoIndex {
private static final Set<String> ENTITIES_IN_LOOKUP_INDIZES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList("node", "relationship")));

private static final String REL_ENTITY_TYPE = "RELATIONSHIP";

private final String[] properties;

/**
Expand All @@ -68,6 +70,8 @@ class AutoIndex {
*/
private final String description;

private String indexName;

AutoIndex(IndexType type, String owningType, String[] properties) {
this.properties = properties;
this.owningType = owningType;
Expand All @@ -80,18 +84,25 @@ private static String createDescription(IndexType type, String owningType, Strin
String name = owningType.toLowerCase();

switch (type) {
case SINGLE_INDEX:
validatePropertiesLength(properties, SINGLE_INDEX);
case NODE_SINGLE_INDEX:
validatePropertiesLength(properties, NODE_SINGLE_INDEX);
return "INDEX ON :`" + owningType + "`(`" + properties[0] + "`)";

case REL_SINGLE_INDEX:
validatePropertiesLength(properties, REL_SINGLE_INDEX);
return "INDEX FOR ()-[`" + name + "`:`" + owningType + "`]-() ON (`" + name + "`.`" + properties[0] + "`)";

case UNIQUE_CONSTRAINT:
validatePropertiesLength(properties, UNIQUE_CONSTRAINT);
return "CONSTRAINT ON (`" + name + "`:`" + owningType + "`) ASSERT `" + name + "`.`" + properties[0]
+ "` IS UNIQUE";

case COMPOSITE_INDEX:
case NODE_COMPOSITE_INDEX:
return buildCompositeIndex(name, owningType, properties);

case REL_COMPOSITE_INDEX:
return buildRelCompositeIndex(name, owningType, properties);

case NODE_KEY_CONSTRAINT:
return buildNodeKeyConstraint(name, owningType, properties);

Expand Down Expand Up @@ -130,6 +141,18 @@ private static String buildCompositeIndex(String name, String owningType, String
return sb.toString();
}

private static String buildRelCompositeIndex(String name, String owningType, String[] properties) {
StringBuilder sb = new StringBuilder();
sb.append("INDEX FOR ()-[`")
.append(name)
.append("`:`")
.append(owningType)
.append("`]-() ON (");
appendPropertiesWithNode(sb, name, properties);
sb.append(")");
return sb.toString();
}

private static String buildNodeKeyConstraint(String name, String owningType, String[] properties) {

StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -179,12 +202,26 @@ public IndexType getType() {
return type;
}

public String getIndexName() {
return indexName;
}

public void setIndexName(String indexName) {
this.indexName = indexName;
}

Statement getCreateStatement() {
return new RowDataStatement("CREATE " + this.description, emptyMap());
}

public Statement getDropStatement() {
return new RowDataStatement("DROP " + this.description, emptyMap());
switch (type) {
case REL_SINGLE_INDEX:
case REL_COMPOSITE_INDEX:
return new RowDataStatement("DROP INDEX " + this.indexName, emptyMap());
default:
return new RowDataStatement("DROP " + this.description, emptyMap());
}
}

String getDescription() {
Expand Down Expand Up @@ -342,9 +379,9 @@ static Optional<AutoIndex> parseIndex(Map<String, Object> indexRow, String versi
properties[i] = properties[i].trim();
}
if (properties.length > 1) {
return Optional.of(new AutoIndex(IndexType.COMPOSITE_INDEX, label, properties));
return Optional.of(new AutoIndex(IndexType.NODE_COMPOSITE_INDEX, label, properties));
} else {
return Optional.of(new AutoIndex(SINGLE_INDEX, label, properties));
return Optional.of(new AutoIndex(NODE_SINGLE_INDEX, label, properties));
}
}
}
Expand All @@ -360,10 +397,20 @@ static Optional<AutoIndex> parseIndex(Map<String, Object> indexRow, String versi

if (indexRow.containsKey("properties") && indexRow.containsKey("labelsOrTypes") && indexRow.get("labelsOrTypes") instanceof String[]) {
String[] indexProperties = (String[]) indexRow.get("properties");
String indexLabel = ((String[]) indexRow.get("labelsOrTypes"))[0];
String indexLabelOrType = ((String[]) indexRow.get("labelsOrTypes"))[0];
String entityType = (String) indexRow.get("entityType");
AutoIndex autoIndex;
if (REL_ENTITY_TYPE.equalsIgnoreCase(entityType)) {
String indexName = (String) indexRow.get("name");
autoIndex = new AutoIndex(indexProperties.length > 1 ? REL_COMPOSITE_INDEX : REL_SINGLE_INDEX,
indexLabelOrType, indexProperties);
autoIndex.setIndexName(indexName);
} else {
autoIndex = new AutoIndex(indexProperties.length > 1 ? NODE_COMPOSITE_INDEX : NODE_SINGLE_INDEX,
indexLabelOrType, indexProperties);
}

return Optional.of(new AutoIndex(indexProperties.length > 1 ? COMPOSITE_INDEX : SINGLE_INDEX,
indexLabel, indexProperties));
return Optional.of(autoIndex);
}

logger.warn("Could not parse index of type {} with description {}", indexType, description);
Expand Down Expand Up @@ -400,8 +447,8 @@ public String toString() {

public boolean hasOpposite() {
switch (type) {
case SINGLE_INDEX:
case COMPOSITE_INDEX:
case NODE_SINGLE_INDEX:
case NODE_COMPOSITE_INDEX:
case UNIQUE_CONSTRAINT:
case NODE_KEY_CONSTRAINT:
return true;
Expand All @@ -413,17 +460,17 @@ public boolean hasOpposite() {

public AutoIndex createOppositeIndex() {
switch (type) {
case SINGLE_INDEX:
case NODE_SINGLE_INDEX:
return new AutoIndex(UNIQUE_CONSTRAINT, owningType, properties);

case UNIQUE_CONSTRAINT:
return new AutoIndex(SINGLE_INDEX, owningType, properties);
return new AutoIndex(NODE_SINGLE_INDEX, owningType, properties);

case COMPOSITE_INDEX:
case NODE_COMPOSITE_INDEX:
return new AutoIndex(NODE_KEY_CONSTRAINT, owningType, properties);

case NODE_KEY_CONSTRAINT:
return new AutoIndex(COMPOSITE_INDEX, owningType, properties);
return new AutoIndex(NODE_COMPOSITE_INDEX, owningType, properties);

default:
throw new IllegalStateException("Can not create opposite index for type=" + type);
Expand Down
24 changes: 21 additions & 3 deletions core/src/main/java/org/neo4j/ogm/autoindex/AutoIndexManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
import java.util.stream.Stream;

import org.neo4j.ogm.annotation.CompositeIndex;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.config.AutoIndexMode;
import org.neo4j.ogm.config.Configuration;
import org.neo4j.ogm.metadata.AnnotationInfo;
import org.neo4j.ogm.metadata.ClassInfo;
import org.neo4j.ogm.metadata.FieldInfo;
import org.neo4j.ogm.metadata.MetaData;
Expand Down Expand Up @@ -263,11 +265,21 @@ private static Set<AutoIndex> initialiseAutoIndex(MetaData metaData) {

if (needsToBeIndexed(classInfo)) {

AnnotationInfo relationshipEntityAnnotation = classInfo.annotationsInfo().get(RelationshipEntity.class);
boolean isRelationship = relationshipEntityAnnotation != null;

// We build the composite index first, to find out whether an @Id or @Index annotated field
// is actually decomposed by a MapCompositeConverter AND has a defined composite index.
Set<String> decomposedFields = new HashSet<>();
for (CompositeIndex index : classInfo.getCompositeIndexes()) {
IndexType type = index.unique() ? IndexType.NODE_KEY_CONSTRAINT : IndexType.COMPOSITE_INDEX;
IndexType type;
if (index.unique()) {
type = IndexType.NODE_KEY_CONSTRAINT;
} else if (isRelationship) {
type = IndexType.REL_COMPOSITE_INDEX;
} else {
type = IndexType.NODE_COMPOSITE_INDEX;
}
List<String> properties = new ArrayList<>();
Stream.of(index.value().length > 0 ? index.value() : index.properties())
.forEach(p -> {
Expand All @@ -286,8 +298,14 @@ private static Set<AutoIndex> initialiseAutoIndex(MetaData metaData) {
}

for (FieldInfo fieldInfo : getIndexFields(classInfo)) {

IndexType type = fieldInfo.isConstraint() ? IndexType.UNIQUE_CONSTRAINT : IndexType.SINGLE_INDEX;
IndexType type;
if (fieldInfo.isConstraint()) {
type = IndexType.UNIQUE_CONSTRAINT;
} else if (isRelationship) {
type = IndexType.REL_SINGLE_INDEX;
} else {
type = IndexType.NODE_SINGLE_INDEX;
}

if (fieldInfo.hasCompositeConverter()) {
if (!decomposedFields.contains(fieldInfo.getName())) {
Expand Down
20 changes: 15 additions & 5 deletions core/src/main/java/org/neo4j/ogm/autoindex/IndexType.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,27 @@
enum IndexType {

/**
* Single property index
* Node single property index
*/
SINGLE_INDEX,
NODE_SINGLE_INDEX,

/**
* Composite index
* Relationship single property index
*/
COMPOSITE_INDEX,
REL_SINGLE_INDEX,

/**
* Unique constraint
* Node composite index
*/
NODE_COMPOSITE_INDEX,

/**
* Relationship composite index
*/
REL_COMPOSITE_INDEX,

/**
* Node unique constraint
*/
UNIQUE_CONSTRAINT,

Expand Down
32 changes: 30 additions & 2 deletions core/src/test/java/org/neo4j/ogm/autoindex/AutoIndexTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,49 @@ public void parseIndex() {
AutoIndex index = AutoIndex.parseIndex(indexRow, "3.5").get();
assertThat(index.getOwningType()).isEqualTo("Person");
assertThat(index.getProperties()).containsOnly("name");
assertThat(index.getType()).isEqualTo(IndexType.SINGLE_INDEX);
assertThat(index.getType()).isEqualTo(IndexType.NODE_SINGLE_INDEX);
assertThat(index.getDescription()).isEqualTo("INDEX ON :`Person`(`name`)");
}

@Test
public void parseRelationshipIndex() {
indexRow.put("entityType", "RELATIONSHIP");
indexRow.put("properties", new String[]{"stars"});
indexRow.put("labelsOrTypes", new String[]{"LIKED"});


AutoIndex index = AutoIndex.parseIndex(indexRow, "4.3").get();
assertThat(index.getOwningType()).isEqualTo("LIKED");
assertThat(index.getProperties()).containsOnly("stars");
assertThat(index.getType()).isEqualTo(IndexType.REL_SINGLE_INDEX);
assertThat(index.getDescription()).isEqualTo("INDEX FOR ()-[`liked`:`LIKED`]-() ON (`liked`.`stars`)");
}

@Test
public void parseCompositeIndex() {
indexRow.put("description", "INDEX ON :Person(name,id)");

AutoIndex index = AutoIndex.parseIndex(indexRow, "3.5").get();
assertThat(index.getOwningType()).isEqualTo("Person");
assertThat(index.getProperties()).containsOnly("name", "id");
assertThat(index.getType()).isEqualTo(IndexType.COMPOSITE_INDEX);
assertThat(index.getType()).isEqualTo(IndexType.NODE_COMPOSITE_INDEX);
assertThat(index.getDescription()).isEqualTo("INDEX ON :`Person`(`name`,`id`)");
}

@Test
public void parseRelationshipCompositeIndex() {
indexRow.put("entityType", "RELATIONSHIP");
indexRow.put("properties", new String[]{"stars", "movies"});
indexRow.put("labelsOrTypes", new String[]{"LIKED"});


AutoIndex index = AutoIndex.parseIndex(indexRow, "4.3").get();
assertThat(index.getOwningType()).isEqualTo("LIKED");
assertThat(index.getProperties()).contains("stars", "movies");
assertThat(index.getType()).isEqualTo(IndexType.REL_COMPOSITE_INDEX);
assertThat(index.getDescription()).isEqualTo("INDEX FOR ()-[`liked`:`LIKED`]-() ON (`liked`.`stars`,`liked`.`movies`)");
}

@Test
public void parseUniqueConstraint() {
constraintRow.put("description", "CONSTRAINT ON ( person:Person ) ASSERT person.name IS UNIQUE");
Expand Down

0 comments on commit 62909e9

Please sign in to comment.