From 5a985c53c142a8658bf2e4f3d13c7beb3bea1677 Mon Sep 17 00:00:00 2001 From: Giuseppe Villani Date: Thu, 26 Jan 2023 17:36:48 +0100 Subject: [PATCH] [hZww9vSJ] Make the type output of apoc.schema.* procedures more specific --- core/src/main/java/apoc/schema/Schemas.java | 88 +++++++------- .../schema/SchemasEnterpriseFeaturesTest.java | 35 ++++++ .../test/java/apoc/schema/SchemasTest.java | 112 +++++++++++++----- 3 files changed, 162 insertions(+), 73 deletions(-) diff --git a/core/src/main/java/apoc/schema/Schemas.java b/core/src/main/java/apoc/schema/Schemas.java index b57307be3..40a9dde6e 100644 --- a/core/src/main/java/apoc/schema/Schemas.java +++ b/core/src/main/java/apoc/schema/Schemas.java @@ -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; @@ -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; @@ -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; @@ -336,6 +335,7 @@ private Boolean constraintsExistsForRelationship(String type, List prope * @return */ private Stream indexesAndConstraintsForNode(Map config) { + Schema schema = tx.schema(); SchemaConfig schemaConfig = new SchemaConfig(config); Set includeLabels = schemaConfig.getLabels(); @@ -346,9 +346,10 @@ private Stream indexesAndConstraintsForNode(Map indexesIterator; - Iterable constraintsIterator; - - final Predicate isNodeConstraint = Util::isNodeCategory; + Iterable constraintsIterator; + final Predicate isNodeConstraint = constraintDefinition -> + Util.isNodeCategory(constraintDefinition.getConstraintType()); + if (includeLabels.isEmpty()) { Iterator allIndex = schemaRead.indexesGetAll(); @@ -363,23 +364,18 @@ private Stream indexesAndConstraintsForNode(Map allConstraints = () -> schemaRead.constraintsGetAll(); + Iterable 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 indexesForLabel = () -> schemaRead.constraintsGetForLabel(tokenRead.nodeLabel(label)); - return StreamSupport.stream(indexesForLabel.spliterator(), false); + Iterable constraintsForType = schema.getConstraints(Label.label(label)); + return StreamSupport.stream(constraintsForType.spliterator(), false) + .filter(isNodeConstraint); }) .collect(Collectors.toList()); @@ -393,8 +389,7 @@ private Stream indexesAndConstraintsForNode(Map 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 indexNodeInfoStream = StreamSupport.stream(indexesIterator.spliterator(), false) @@ -452,7 +447,7 @@ private Stream indexesAndConstraintsForRelation Iterable 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 allIndex = schemaRead.indexesGetAll(); @@ -472,28 +467,27 @@ private Stream 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 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 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) ); } @@ -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(), @@ -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 ); } } @@ -560,27 +555,28 @@ private IndexConstraintRelationshipInfo relationshipInfoFromIndexDescription(Ind if (length == 0) { relName = TOKEN_REL_TYPE; } else { - final List labels = IntStream.of(relIds) + final List 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 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); } /** @@ -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 properties) { diff --git a/core/src/test/java/apoc/schema/SchemasEnterpriseFeaturesTest.java b/core/src/test/java/apoc/schema/SchemasEnterpriseFeaturesTest.java index d1f33f4c5..7ffffa2b8 100644 --- a/core/src/test/java/apoc/schema/SchemasEnterpriseFeaturesTest.java +++ b/core/src/test/java/apoc/schema/SchemasEnterpriseFeaturesTest.java @@ -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; @@ -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; @@ -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 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 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() { diff --git a/core/src/test/java/apoc/schema/SchemasTest.java b/core/src/test/java/apoc/schema/SchemasTest.java index ae869e59a..d497faeee 100644 --- a/core/src/test/java/apoc/schema/SchemasTest.java +++ b/core/src/test/java/apoc/schema/SchemasTest.java @@ -46,7 +46,9 @@ * @since 12.05.16 */ public class SchemasTest { - + public static final String CALL_SCHEMA_NODES_ORDERED = "CALL apoc.schema.nodes() " + + "YIELD label, type, properties, status, userDescription, name " + + "RETURN * ORDER BY label, type"; @Rule public DbmsRule db = new ImpermanentDbmsRule() .withSetting(GraphDatabaseSettings.procedure_unrestricted, Collections.singletonList("apoc.*")) @@ -58,7 +60,7 @@ private static void accept(Result result) { assertEquals(":Foo(bar)", r.get("name")); assertEquals("ONLINE", r.get("status")); assertEquals("Foo", r.get("label")); - assertEquals("INDEX", r.get("type")); + assertEquals("RANGE", r.get("type")); assertEquals("bar", ((List) r.get("properties")).get(0)); assertEquals("NO FAILURE", r.get("failure")); assertEquals(100d, r.get("populationProgress")); @@ -74,7 +76,7 @@ private static void accept2(Result result) { assertEquals(":Foo(bar)", r.get("name")); assertEquals("ONLINE", r.get("status")); assertEquals("Foo", r.get("label")); - assertEquals("INDEX", r.get("type")); + assertEquals("RANGE", r.get("type")); assertEquals("bar", ((List) r.get("properties")).get(0)); assertEquals("NO FAILURE", r.get("failure")); assertEquals(100d, r.get("populationProgress")); @@ -86,7 +88,7 @@ private static void accept2(Result result) { assertEquals(":Person(name)", r.get("name")); assertEquals("ONLINE", r.get("status")); assertEquals("Person", r.get("label")); - assertEquals("INDEX", r.get("type")); + assertEquals("TEXT", r.get("type")); assertEquals("name", ((List) r.get("properties")).get(0)); assertEquals("NO FAILURE", r.get("failure")); assertEquals(100d, r.get("populationProgress")); @@ -336,7 +338,7 @@ public void testIndexes() { assertEquals(":Foo(bar)", r.get("name")); assertEquals("ONLINE", r.get("status")); assertEquals("Foo", r.get("label")); - assertEquals("INDEX", r.get("type")); + assertEquals("RANGE", r.get("type")); assertEquals("bar", ((List) r.get("properties")).get(0)); assertEquals("NO FAILURE", r.get("failure")); assertEquals(100d, r.get("populationProgress")); @@ -355,7 +357,7 @@ public void testRelIndex() { assertEquals(":KNOWS(id,since)", row.get("name")); assertEquals("ONLINE", row.get("status")); assertEquals("KNOWS", row.get("relationshipType")); - assertEquals("INDEX", row.get("type")); + assertEquals("RANGE", row.get("type")); assertEquals(List.of("id", "since"), row.get("properties")); }); } @@ -396,36 +398,89 @@ public void testUniquenessConstraintOnNode() { db.executeTransactionally("CREATE CONSTRAINT FOR (bar:Bar) REQUIRE bar.foo IS UNIQUE"); awaitIndexesOnline(); - testResult(db, "CALL apoc.schema.nodes()", (result) -> { + testResult(db, CALL_SCHEMA_NODES_ORDERED, (result) -> { Map r = result.next(); - assertEquals(":Bar(foo)", r.get("name")); - assertEquals("Bar", r.get("label")); + assertionsBarFooUniqueCons(r); + + assertEquals("RANGE", r.get("type")); + assertEquals("ONLINE", r.get("status")); + final String expectedUserDescBarIdx = "name='constraint_4791de3e', type='RANGE', schema=(:Bar {foo}), indexProvider='range-1.0', owningConstraint=4"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarIdx); + r = result.next(); + + assertionsBarFooUniqueCons(r); + assertEquals("UNIQUENESS", r.get("type")); - assertEquals("foo", ((List) r.get("properties")).get(0)); + assertEquals("", r.get("status")); + final String expectedUserDescBarCons = "name='constraint_4791de3e', type='UNIQUENESS', schema=(:Bar {foo}), ownedIndex=3"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); assertFalse(result.hasNext()); }); } + private static void assertionsBarFooUniqueCons(Map r) { + assertEquals(":Bar(foo)", r.get("name")); + assertEquals("Bar", r.get("label")); + assertEquals(List.of("foo"), r.get("properties")); + } + @Test public void testIndexAndUniquenessConstraintOnNode() { - db.executeTransactionally("CREATE INDEX FOR (n:Foo) ON (n.foo)"); - db.executeTransactionally("CREATE CONSTRAINT FOR (bar:Bar) REQUIRE bar.bar IS UNIQUE"); + db.executeTransactionally("CREATE INDEX foo_idx FOR (n:Foo) ON (n.foo)"); + db.executeTransactionally("CREATE CONSTRAINT bar_unique FOR (bar:Bar) REQUIRE bar.bar IS UNIQUE"); awaitIndexesOnline(); - - testResult(db, "CALL apoc.schema.nodes()", (result) -> { + + testResult(db, CALL_SCHEMA_NODES_ORDERED, (result) -> { Map r = result.next(); - assertEquals("Bar", r.get("label")); + assertionsBarUniqueCons(r); + assertEquals("RANGE", r.get("type")); + assertEquals("ONLINE", r.get("status")); + final String expectedUserDescBarIdx = "name='bar_unique', type='RANGE', schema=(:Bar {bar}), indexProvider='range-1.0', owningConstraint"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarIdx); + + r = result.next(); + + assertionsBarUniqueCons(r); assertEquals("UNIQUENESS", r.get("type")); - assertEquals("bar", ((List) r.get("properties")).get(0)); + assertEquals("", r.get("status")); + final String expectedUserDescBarCons = "name='bar_unique', type='UNIQUENESS', schema=(:Bar {bar}), ownedIndex"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescBarCons); r = result.next(); assertEquals("Foo", r.get("label")); - assertEquals("INDEX", r.get("type")); + assertEquals("RANGE", r.get("type")); assertEquals("foo", ((List) r.get("properties")).get(0)); assertEquals("ONLINE", r.get("status")); + assertEquals(":Foo(foo)", r.get("name")); + final String expectedUserDescFoo = "name='foo_idx', type='RANGE', schema=(:Foo {foo}), indexProvider='range-1.0' )"; + Assertions.assertThat(r.get("userDescription").toString()).contains(expectedUserDescFoo); + + assertFalse(result.hasNext()); + }); + } + + private static void assertionsBarUniqueCons(Map r) { + assertEquals("Bar", r.get("label")); + assertEquals(":Bar(bar)", r.get("name")); + assertEquals(List.of("bar"), r.get("properties")); + } + + @Test + public void testSchemaNodesPointIndex() { + db.executeTransactionally("CREATE POINT INDEX pointIdx FOR (n:Baz) ON (n.baz)"); + + testResult(db, "CALL apoc.schema.nodes()", (result) -> { + Map r = result.next(); + + assertEquals("Baz", r.get("label")); + assertEquals("POINT", r.get("type")); + assertEquals("baz", ((List) r.get("properties")).get(0)); + assertEquals("ONLINE", r.get("status")); + final String expectedUserDesc = "name='pointIdx', type='POINT', schema=(:Baz {baz}), indexProvider='point-1.0'"; + Assertions.assertThat( r.get( "userDescription").toString() ).contains(expectedUserDesc); assertFalse(result.hasNext()); }); @@ -731,7 +786,7 @@ public void testUniqueRelationshipConstraint() { r = result.next(); assertEquals(":SINCE(year)", r.get("name")); - assertEquals("INDEX", r.get("type")); + assertEquals("RANGE", r.get("type")); assertEquals("SINCE", r.get("relationshipType")); assertEquals("year", ((List) r.get("properties")).get(0)); assertTrue(!result.hasNext()); @@ -756,7 +811,7 @@ public void testMultipleUniqueRelationshipConstraints() { )); relConstraints.add(new IndexConstraintRelationshipInfo( ":SINCE(year)", - "INDEX", + "RANGE", List.of("year"), "ONLINE", "SINCE" @@ -770,7 +825,7 @@ public void testMultipleUniqueRelationshipConstraints() { )); relConstraints.add(new IndexConstraintRelationshipInfo( ":LIKED(when)", - "INDEX", + "RANGE", List.of("when"), "ONLINE", "LIKED" @@ -784,16 +839,17 @@ public void testMultipleUniqueRelationshipConstraints() { )); relConstraints.add(new IndexConstraintRelationshipInfo( ":KNOW(how)", - "INDEX", + "RANGE", List.of("how"), "ONLINE", "KNOW" )); - testResult(db, "CALL apoc.schema.relationships({})", + testResult(db, "CALL apoc.schema.relationships", result -> { while (result.hasNext()) { Map r = result.next(); + System.out.println("r = " + r); assertEquals(1, relConstraints.stream().filter( c -> c.name.equals(r.get("name")) && @@ -816,17 +872,19 @@ public void testLookupIndexes() { assertEquals(":" + TOKEN_LABEL + "()", row.get("name")); assertEquals("ONLINE", row.get("status")); assertEquals(TOKEN_LABEL, row.get("label")); - assertEquals("INDEX", row.get("type")); + assertEquals("LOOKUP", row.get("type")); assertTrue(((List)row.get("properties")).isEmpty()); assertEquals("NO FAILURE", row.get("failure")); assertEquals(100d, row.get("populationProgress")); assertEquals(1d, row.get("valuesSelectivity")); - assertTrue(row.get("userDescription").toString().contains("name='node_label_lookup_index', type='LOOKUP', schema=(:), indexProvider='token-lookup-1.0' )")); + final String expectedUserDesc = "name='node_label_lookup_index', type='LOOKUP', schema=(:), indexProvider='token-lookup-1.0'"; + Assertions.assertThat( row.get( "userDescription").toString() ).contains(expectedUserDesc); }); + testCall(db, "CALL apoc.schema.relationships()", (row) -> { assertEquals(":" + TOKEN_REL_TYPE + "()", row.get("name")); assertEquals("ONLINE", row.get("status")); - assertEquals("INDEX", row.get("type")); + assertEquals("LOOKUP", row.get("type")); assertEquals(TOKEN_REL_TYPE, row.get("relationshipType")); assertTrue(((List)row.get("properties")).isEmpty()); }); @@ -853,7 +911,7 @@ public void testIndexesWithMultipleLabelsAndRelTypes() { assertEquals(":[Blah, Moon],(weightProp,anotherProp)", r.get("name")); assertEquals("ONLINE", r.get("status")); assertEquals(List.of("Blah", "Moon"), r.get("label")); - assertEquals("INDEX", r.get("type")); + assertEquals("FULLTEXT", r.get("type")); assertEquals(List.of("weightProp", "anotherProp"), r.get("properties")); assertEquals("NO FAILURE", r.get("failure")); assertEquals(100d, r.get("populationProgress")); @@ -870,7 +928,7 @@ public void testIndexesWithMultipleLabelsAndRelTypes() { assertEquals("ONLINE", r.get("status")); assertEquals(List.of("TYPE_1", "TYPE_2"), r.get("relationshipType")); assertEquals(List.of("alpha", "beta"), r.get("properties")); - assertEquals("INDEX", r.get("type")); + assertEquals("FULLTEXT", r.get("type")); }); }