Skip to content

Commit

Permalink
[hZww9vSJ] Make the type output of apoc.schema.* procedures more spec…
Browse files Browse the repository at this point in the history
…ific
  • Loading branch information
vga91 committed Jan 30, 2023
1 parent d986a04 commit 5a985c5
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 73 deletions.
88 changes: 42 additions & 46 deletions core/src/main/java/apoc/schema/Schemas.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.schema.ConstraintDefinition;
import org.neo4j.graphdb.schema.ConstraintType;
import org.neo4j.graphdb.schema.IndexDefinition;
import org.neo4j.graphdb.schema.IndexType;
import org.neo4j.graphdb.schema.Schema;
Expand All @@ -21,7 +20,6 @@
import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.internal.kernel.api.exceptions.LabelNotFoundKernelException;
import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotFoundKernelException;
import org.neo4j.internal.schema.ConstraintDescriptor;
import org.neo4j.internal.schema.IndexDescriptor;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.Statement;
Expand Down Expand Up @@ -56,6 +54,7 @@
import static org.neo4j.internal.schema.SchemaUserDescription.TOKEN_REL_TYPE;

public class Schemas {
private static final String IDX_NOT_FOUND = "NOT_FOUND";

@Context
public Transaction tx;
Expand Down Expand Up @@ -336,6 +335,7 @@ private Boolean constraintsExistsForRelationship(String type, List<String> prope
* @return
*/
private Stream<IndexConstraintNodeInfo> indexesAndConstraintsForNode(Map<String,Object> config) {
Schema schema = tx.schema();

SchemaConfig schemaConfig = new SchemaConfig(config);
Set<String> includeLabels = schemaConfig.getLabels();
Expand All @@ -346,9 +346,10 @@ private Stream<IndexConstraintNodeInfo> indexesAndConstraintsForNode(Map<String,

SchemaRead schemaRead = ktx.schemaRead();
Iterable<IndexDescriptor> indexesIterator;
Iterable<ConstraintDescriptor> constraintsIterator;

final Predicate<ConstraintDescriptor> isNodeConstraint = Util::isNodeCategory;
Iterable<ConstraintDefinition> constraintsIterator;
final Predicate<ConstraintDefinition> isNodeConstraint = constraintDefinition ->
Util.isNodeCategory(constraintDefinition.getConstraintType());

if (includeLabels.isEmpty()) {

Iterator<IndexDescriptor> allIndex = schemaRead.indexesGetAll();
Expand All @@ -363,23 +364,18 @@ private Stream<IndexConstraintNodeInfo> indexesAndConstraintsForNode(Map<String,
}
}));

Iterable<ConstraintDescriptor> allConstraints = () -> schemaRead.constraintsGetAll();
Iterable<ConstraintDefinition> allConstraints = schema.getConstraints();
constraintsIterator = StreamSupport.stream(allConstraints.spliterator(),false)
.filter(isNodeConstraint)
.filter(constraint -> Arrays.stream(constraint.schema().getEntityTokenIds()).noneMatch(id -> {
try {
return excludeLabels.contains(tokenRead.nodeLabelName(id));
} catch (LabelNotFoundKernelException e) {
return false;
}
}))
.filter(constraint -> !excludeLabels.contains(constraint.getLabel().name()))
.collect(Collectors.toList());
} else {
constraintsIterator = includeLabels.stream()
.filter(label -> !excludeLabels.contains(label) && tokenRead.nodeLabel(label) != -1)
.flatMap(label -> {
Iterable<ConstraintDescriptor> indexesForLabel = () -> schemaRead.constraintsGetForLabel(tokenRead.nodeLabel(label));
return StreamSupport.stream(indexesForLabel.spliterator(), false);
Iterable<ConstraintDefinition> constraintsForType = schema.getConstraints(Label.label(label));
return StreamSupport.stream(constraintsForType.spliterator(), false)
.filter(isNodeConstraint);
})
.collect(Collectors.toList());

Expand All @@ -393,8 +389,7 @@ private Stream<IndexConstraintNodeInfo> indexesAndConstraintsForNode(Map<String,
}

Stream<IndexConstraintNodeInfo> constraintNodeInfoStream = StreamSupport.stream(constraintsIterator.spliterator(), false)
.filter(ConstraintDescriptor::isNodePropertyExistenceConstraint)
.map(constraintDescriptor -> this.nodeInfoFromConstraintDescriptor(constraintDescriptor, tokenRead))
.map(constraintDescriptor -> nodeInfoFromConstraintDefinition(constraintDescriptor, tokenRead))
.sorted(Comparator.comparing(i -> i.label.toString()));

Stream<IndexConstraintNodeInfo> indexNodeInfoStream = StreamSupport.stream(indexesIterator.spliterator(), false)
Expand Down Expand Up @@ -452,7 +447,7 @@ private Stream<IndexConstraintRelationshipInfo> indexesAndConstraintsForRelation
Iterable<ConstraintDefinition> allConstraints = schema.getConstraints();
constraintsIterator = StreamSupport.stream(allConstraints.spliterator(),false)
.filter(isRelConstraint)
.filter(index -> !excludeRelationships.contains(index.getRelationshipType().name()))
.filter(constraint -> !excludeRelationships.contains(constraint.getRelationshipType().name()))
.collect(Collectors.toList());

Iterator<IndexDescriptor> allIndex = schemaRead.indexesGetAll();
Expand All @@ -472,28 +467,27 @@ private Stream<IndexConstraintRelationshipInfo> indexesAndConstraintsForRelation
}

/**
* ConstraintInfo info from ConstraintDescriptor
* ConstraintInfo info from ConstraintDefinition
*
* @param constraintDescriptor
* @param constraintDefinition
* @param tokens
* @return
*/
private IndexConstraintNodeInfo nodeInfoFromConstraintDescriptor(ConstraintDescriptor constraintDescriptor, TokenNameLookup tokens) {
String labelName = tokens.labelGetName(constraintDescriptor.schema().getLabelId());
List<String> properties = new ArrayList<>();
Arrays.stream(constraintDescriptor.schema().getPropertyIds()).forEach((i) -> properties.add(tokens.propertyKeyGetName(i)));
private IndexConstraintNodeInfo nodeInfoFromConstraintDefinition(ConstraintDefinition constraintDefinition, TokenNameLookup tokens) {
String labelName = constraintDefinition.getLabel().name();
List<String> properties = Iterables.asList(constraintDefinition.getPropertyKeys());
return new IndexConstraintNodeInfo(
// Pretty print for index name
String.format(":%s(%s)", labelName, StringUtils.join(properties, ",")),
labelName,
properties,
StringUtils.EMPTY,
ConstraintType.NODE_PROPERTY_EXISTENCE.toString(),
constraintDefinition.getConstraintType().name(),
"NO FAILURE",
0,
0,
0,
constraintDescriptor.userDescription(tokens)
ktx.schemaRead().constraintGetForName(constraintDefinition.getName()).userDescription(tokens)
);
}

Expand Down Expand Up @@ -523,10 +517,12 @@ private IndexConstraintNodeInfo nodeInfoFromIndexDefinition(IndexDescriptor inde
.mapToObj(tokens::propertyKeyGetName)
.collect(Collectors.toList());

// Pretty print for index name
final String schemaInfoName = getSchemaInfoName(labelName, properties);
final String userDescription = indexDescriptor.userDescription(tokens);
try {
return new IndexConstraintNodeInfo(
// Pretty print for index name
getSchemaInfoName(labelName, properties),
schemaInfoName,
labelName,
properties,
schemaRead.indexGetState(indexDescriptor).toString(),
Expand All @@ -535,19 +531,18 @@ private IndexConstraintNodeInfo nodeInfoFromIndexDefinition(IndexDescriptor inde
schemaRead.indexGetPopulationProgress(indexDescriptor).getCompleted() / schemaRead.indexGetPopulationProgress(indexDescriptor).getTotal() * 100,
schemaRead.indexSize(indexDescriptor),
schemaRead.indexUniqueValuesSelectivity(indexDescriptor),
indexDescriptor.userDescription(tokens)
userDescription
);
} catch(IndexNotFoundKernelException e) {
return new IndexConstraintNodeInfo(
// Pretty print for index name
getSchemaInfoName(labelName, properties),
schemaInfoName,
labelName,
properties,
"NOT_FOUND",
IDX_NOT_FOUND,
getIndexType(indexDescriptor),
"NOT_FOUND",
IDX_NOT_FOUND,
0,0,0,
indexDescriptor.userDescription(tokens)
userDescription
);
}
}
Expand All @@ -560,27 +555,28 @@ private IndexConstraintRelationshipInfo relationshipInfoFromIndexDescription(Ind
if (length == 0) {
relName = TOKEN_REL_TYPE;
} else {
final List<String> labels = IntStream.of(relIds)
final List<String> rels = IntStream.of(relIds)
.mapToObj(tokens::relationshipTypeGetName)
.sorted()
.collect(Collectors.toList());
relName = labels.size() > 1 ? labels : labels.get(0);
relName = rels.size() > 1 ? rels : rels.get(0);
}
final List<String> properties = Arrays.stream(indexDescriptor.schema().getPropertyIds())
.mapToObj(tokens::propertyKeyGetName)
.collect(Collectors.toList());

// Pretty print for index name
final String name = getSchemaInfoName(relName, properties);
final String schemaType = getIndexType(indexDescriptor);

String indexStatus;
try {
return new IndexConstraintRelationshipInfo(getSchemaInfoName(relName, properties), "INDEX", properties, schemaRead.indexGetState(indexDescriptor).toString(), relName);
indexStatus = schemaRead.indexGetState(indexDescriptor).toString();
} catch (IndexNotFoundKernelException e) {
return new IndexConstraintRelationshipInfo(
// Pretty print for index name
getSchemaInfoName(relName, properties),
getIndexType(indexDescriptor),
properties,
"NOT_FOUND",
relName
);
indexStatus = IDX_NOT_FOUND;
}

return new IndexConstraintRelationshipInfo(name, schemaType, properties, indexStatus, relName);
}

/**
Expand All @@ -600,7 +596,7 @@ private IndexConstraintRelationshipInfo relationshipInfoFromConstraintDefinition
}

private static String getIndexType(IndexDescriptor indexDescriptor) {
return indexDescriptor.isUnique() ? "UNIQUENESS" : "INDEX";
return indexDescriptor.getIndexType().name();
}

private String getSchemaInfoName(Object labelOrType, List<String> properties) {
Expand Down
35 changes: 35 additions & 0 deletions core/src/test/java/apoc/schema/SchemasEnterpriseFeaturesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import apoc.util.TestContainerUtil.ApocPackage;
import junit.framework.TestCase;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.api.Assertions;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
Expand All @@ -20,6 +21,7 @@
import java.util.Map;
import java.util.stream.Collectors;

import static apoc.schema.SchemasTest.CALL_SCHEMA_NODES_ORDERED;
import static apoc.util.TestContainerUtil.createEnterpriseDB;
import static apoc.util.TestContainerUtil.testCall;
import static apoc.util.TestContainerUtil.testResult;
Expand Down Expand Up @@ -110,6 +112,39 @@ public void testKeptNodeKeyAndUniqueConstraintIfExists() {
});
}

@Test
public void testSchemaNodesWithNodeKey() {
session.writeTransaction(tx -> {
tx.run("CREATE CONSTRAINT node_key_movie FOR (m:Movie) REQUIRE (m.first, m.second) IS NODE KEY");
tx.commit();
return null;
});

testResult(session, CALL_SCHEMA_NODES_ORDERED, (result) -> {
Map<String, Object> r = result.next();
schemaNodeKeyAssertions(r);
assertEquals("", r.get("status"));
assertEquals("NODE_KEY", r.get("type"));
final String expectedUserDescConstraint = "name='node_key_movie', type='NODE KEY', schema=(:Movie {first, second}), ownedIndex=11 )";
Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescConstraint);

r = result.next();
schemaNodeKeyAssertions(r);
assertEquals("ONLINE", r.get("status"));
assertEquals("RANGE", r.get("type"));
final String expectedUserDescIdx = "name='node_key_movie', type='RANGE', schema=(:Movie {first, second}), indexProvider='range-1.0', owningConstraint=12";
Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescIdx);

assertFalse(result.hasNext());
});
}

private static void schemaNodeKeyAssertions(Map<String, Object> r) {
assertEquals("Movie", r.get("label"));
assertEquals(List.of("first", "second"), r.get("properties"));
assertEquals(":Movie(first,second)", r.get("name"));
}


@Test
public void testRelKeyConstraintIsKeptAndDropped() {
Expand Down
Loading

0 comments on commit 5a985c5

Please sign in to comment.