diff --git a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInSchemaProceduresIT.java b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInSchemaProceduresIT.java index f7ef697181bb..a61fa198dd05 100644 --- a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInSchemaProceduresIT.java +++ b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInSchemaProceduresIT.java @@ -43,8 +43,8 @@ public class BuiltInSchemaProceduresIT extends KernelIntegrationTest { - private final String prefix = "okapi"; - private final String procedure = "schema"; + private final String[] nodesProcedureName = {"db", "schema", "nodeProperties"}; + private final String[] relsProcedureName = {"db", "schema", "edgeProperties"}; @Test public void testNodePropertiesRegardlessOfCreationOrder1() throws Throwable @@ -59,13 +59,13 @@ public void testNodePropertiesRegardlessOfCreationOrder1() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), containsInAnyOrder( - equalTo( entry("Node", Arrays.asList( "A" ), "color", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "A" ), "size", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "A" ), "origin", Arrays.asList( "String" ), true) ) ) ); + equalTo( entry("A", "color", Arrays.asList( "String" ), false) ), + equalTo( entry("A", "size", Arrays.asList( "String" ), false) ), + equalTo( entry("A", "origin", Arrays.asList( "String" ), false) ) ) ); // printStream( stream ); } @@ -82,13 +82,13 @@ public void testNodePropertiesRegardlessOfCreationOrder2() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), containsInAnyOrder( - equalTo( entry("Node", Arrays.asList( "B" ), "color", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "B" ), "size", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "B" ), "origin", Arrays.asList( "String" ), true) ) ) ); + equalTo( entry("B", "color", Arrays.asList( "String" ), false) ), + equalTo( entry("B", "size", Arrays.asList( "String" ), false) ), + equalTo( entry("B", "origin", Arrays.asList( "String" ), false) ) ) ); // printStream( stream ); } @@ -106,14 +106,14 @@ public void testNodePropertiesRegardlessOfCreationOrder3() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), containsInAnyOrder( - equalTo( entry("Node", Arrays.asList( "C" ), "color", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "C" ), "size", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "C" ), "origin", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "C" ), "active", Arrays.asList( "Boolean" ), true) ) ) ); + equalTo( entry("C", "color", Arrays.asList( "String" ), false) ), + equalTo( entry("C", "size", Arrays.asList( "String" ), false) ), + equalTo( entry("C", "origin", Arrays.asList( "String" ), false) ), + equalTo( entry("C", "active", Arrays.asList( "Boolean" ), false) ) ) ); // printStream( stream ); } @@ -134,14 +134,13 @@ public void testRelsPropertiesRegardlessOfCreationOrder1() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), containsInAnyOrder( - equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "color", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "size", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "origin", Arrays.asList( "String" ), true) ) ) ); + equalTo( entry("R", "color", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "size", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "origin", Arrays.asList( "String" ), false) ) ) ); // printStream( stream ); } @@ -161,14 +160,13 @@ public void testRelsPropertiesRegardlessOfCreationOrder2() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), containsInAnyOrder( - equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "color", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "size", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "origin", Arrays.asList( "String" ), true) ) ) ); + equalTo( entry("R", "color", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "size", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "origin", Arrays.asList( "String" ), false) ) ) ); // printStream( stream ); } @@ -190,15 +188,14 @@ public void testRelsPropertiesRegardlessOfCreationOrder3() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), containsInAnyOrder( - equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "color", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "size", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "origin", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "active", Arrays.asList( "Boolean" ), true))) ); + equalTo( entry("R", "color", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "size", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "origin", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "active", Arrays.asList( "Boolean" ), false))) ); // printStream( stream ); } @@ -216,11 +213,11 @@ public void testNodesShouldNotDependOnOrderOfCreationWithOverlap() throws Throwa // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( new Object[]{"Node", Arrays.asList( "B" ), "type", Arrays.asList( "String" ), false} ), - equalTo( entry("Node", Arrays.asList( "B" ), "size", Arrays.asList( "Integer" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( equalTo( new Object[]{"B", "type", Arrays.asList( "String" ), true} ), + equalTo( entry("B", "size", Arrays.asList( "Integer" ), false) ) ) ); // printStream( stream ); } @@ -238,11 +235,12 @@ public void testNodesShouldNotDependOnOrderOfCreationWithOverlap2() throws Throw // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( new Object[]{"Node", Arrays.asList( "B" ), "type", Arrays.asList( "String" ), false} ), - equalTo( entry("Node", Arrays.asList( "B" ), "size", Arrays.asList( "Integer" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( new Object[]{"B", "type", Arrays.asList( "String" ), true} ), + equalTo( entry("B", "size", Arrays.asList( "Integer" ), false) ) ) ); // printStream( stream ); } @@ -262,12 +260,12 @@ public void testRelsShouldNotDependOnOrderOfCreationWithOverlap() throws Throwab // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "B" ), "type", Arrays.asList( "String" ), false) ), - equalTo( entry("Relationship", Arrays.asList( "B" ), "size", Arrays.asList( "Integer" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("B", "type", Arrays.asList( "String" ), true) ), + equalTo( entry("B", "size", Arrays.asList( "Integer" ), false) ) ) ); // printStream( stream ); } @@ -287,12 +285,12 @@ public void testRelsShouldNotDependOnOrderOfCreationWithOverlap2() throws Throwa // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "B" ), "type", Arrays.asList( "String" ), false) ), - equalTo( entry("Relationship", Arrays.asList( "B" ), "size", Arrays.asList( "Integer" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("B", "type", Arrays.asList( "String" ), true) ), + equalTo( entry("B", "size", Arrays.asList( "Integer" ), false) ) ) ); // printStream( stream ); } @@ -314,14 +312,15 @@ public void testWithNodes() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList( "A", "B" ), "prop1", Arrays.asList( "String" ), false) ), - equalTo( entry("Node", Arrays.asList( "A", "B" ), "prop2", Arrays.asList( "Integer" ), false) ), - equalTo( entry("Node", Arrays.asList( "B" ), "prop1", Arrays.asList( "Boolean" ), false) ), - equalTo( entry("Node", Arrays.asList( "C" ), "prop1", Arrays.asList( "StringArray" ), false) ), - equalTo( entry("Node", Arrays.asList(), null, null, true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("A:B", "prop1", Arrays.asList( "String" ), true) ), + equalTo( entry("A:B", "prop2", Arrays.asList( "Integer" ), true) ), + equalTo( entry("B", "prop1", Arrays.asList( "Boolean" ), true) ), + equalTo( entry("C", "prop1", Arrays.asList( "StringArray" ), true) ), + equalTo( entry("", null, null, false) ) ) ); // printStream( stream ); } @@ -339,10 +338,10 @@ public void testWithSimilarNodes() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), contains( equalTo( entry("Node", Arrays.asList( "A" ), "prop1", Arrays.asList( "String" ), false) ) ) ); + assertThat( asList( stream ), contains( equalTo( entry("A", "prop1", Arrays.asList( "String" ), true) ) ) ); // printStream( stream ); } @@ -364,12 +363,13 @@ public void testWithSimilarNodesHavingDifferentPropertyValueTypes() throws Throw // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList(), "prop1", Arrays.asList( "String" ), false) ), - equalTo( entry("Node", Arrays.asList(), "prop2", Arrays.asList( "Integer", "Float" ), true) ), - equalTo( entry("Node", Arrays.asList(), "prop3", Arrays.asList( "String", "Boolean" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("", "prop1", Arrays.asList( "String" ), true) ), + equalTo( entry("", "prop2", Arrays.asList( "Integer", "Float" ), false) ), + equalTo( entry("", "prop3", Arrays.asList( "String", "Boolean" ), false) ) ) ); // printStream( stream ); } @@ -391,12 +391,13 @@ public void testWithSimilarNodesShouldNotDependOnOrderOfCreation() throws Throwa // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList(), "prop1", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList(), "prop2", Arrays.asList( "Integer", "Float" ), true) ), - equalTo( entry("Node", Arrays.asList(), "prop3", Arrays.asList( "String", "Boolean" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("", "prop1", Arrays.asList( "String" ), false) ), + equalTo( entry("", "prop2", Arrays.asList( "Integer", "Float" ), false) ), + equalTo( entry("", "prop3", Arrays.asList( "String", "Boolean" ), false) ) ) ); // printStream( stream ); } @@ -418,15 +419,15 @@ public void testWithRelationships() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), - containsInAnyOrder( equalTo( entry("Relationship", Arrays.asList( "R" ), "prop1", Arrays.asList( "String" ), false) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop2", Arrays.asList( "Integer" ), false) ), - equalTo( entry("Relationship", Arrays.asList( "X" ), "prop1", Arrays.asList( "Boolean" ), false) ), - equalTo( entry("Relationship", Arrays.asList( "Z" ), null, null, true) ), - equalTo( entry("Node", Arrays.asList(), null, null, true) ) ) ); + containsInAnyOrder( + equalTo( entry("R", "prop1", Arrays.asList( "String" ), true) ), + equalTo( entry("R", "prop2", Arrays.asList( "Integer" ), true) ), + equalTo( entry("X", "prop1", Arrays.asList( "Boolean" ), true) ), + equalTo( entry("Z", null, null, false) ) ) ); // printStream( stream ); } @@ -446,12 +447,12 @@ public void testWithSimilarRelationships() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then assertThat( asList( stream ), - containsInAnyOrder( equalTo( entry("Relationship", Arrays.asList( "R" ), "prop1", Arrays.asList( "String" ), false) ), - equalTo( entry("Node", Arrays.asList(), null, null, true) ) ) ); + containsInAnyOrder( + equalTo( entry("R", "prop1", Arrays.asList( "String" ), true ) ) ) ); //printStream( stream ); } @@ -472,13 +473,13 @@ public void testSchemaWithRelationshipWithoutProperties() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop1", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop2", Arrays.asList( "Integer" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop3", Arrays.asList( "Boolean" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("R", "prop1", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "prop2", Arrays.asList( "Integer" ), false) ), + equalTo( entry("R", "prop3", Arrays.asList( "Boolean" ), false) ) ) ); //printStream( stream ); } @@ -502,13 +503,13 @@ public void testWithSimilarRelationshipsHavingDifferentPropertyValueTypes() thro // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop1", Arrays.asList( "String" ), false) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop2", Arrays.asList( "Integer", "Float" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop3", Arrays.asList( "String", "Boolean" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("R", "prop1", Arrays.asList( "String" ), true) ), + equalTo( entry("R", "prop2", Arrays.asList( "Integer", "Float" ), false) ), + equalTo( entry("R", "prop3", Arrays.asList( "String", "Boolean" ), false) ) ) ); //printStream( stream ); } @@ -533,13 +534,13 @@ public void testWithSimilarRelationshipsShouldNotDependOnOrderOfCreation() throw // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( relsProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList(), null, null, true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop1", Arrays.asList( "String" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop2", Arrays.asList( "Integer", "Float" ), true) ), - equalTo( entry("Relationship", Arrays.asList( "R" ), "prop3", Arrays.asList( "String", "Boolean" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("R", "prop1", Arrays.asList( "String" ), false) ), + equalTo( entry("R", "prop2", Arrays.asList( "Integer", "Float" ), false) ), + equalTo( entry("R", "prop3", Arrays.asList( "String", "Boolean" ), false) ) ) ); //printStream( stream ); } @@ -564,21 +565,22 @@ public void testWithNullableProperties() throws Throwable // When RawIterator stream = - procs().procedureCallRead( procs().procedureGet( procedureName( prefix, procedure ) ).id(), new Object[0] ); + procs().procedureCallRead( procs().procedureGet( procedureName( nodesProcedureName ) ).id(), new Object[0] ); // Then - assertThat( asList( stream ), containsInAnyOrder( equalTo( entry("Node", Arrays.asList( "A" ), "prop1", Arrays.asList( "String" ), false) ), - equalTo( entry("Node", Arrays.asList( "A" ), "prop2", Arrays.asList( "Integer" ), true) ), - equalTo( entry("Node", Arrays.asList( "A" ), "prop3", Arrays.asList( "Boolean" ), true) ), - equalTo( entry("Node", Arrays.asList( "B" ), "prop1", Arrays.asList( "String" ), true) ), - equalTo( entry("Node", Arrays.asList( "B" ), "prop2", Arrays.asList( "Integer" ), true) ) ) ); + assertThat( asList( stream ), containsInAnyOrder( + equalTo( entry("A", "prop1", Arrays.asList( "String" ), true) ), + equalTo( entry("A", "prop2", Arrays.asList( "Integer" ), false) ), + equalTo( entry("A", "prop3", Arrays.asList( "Boolean" ), false) ), + equalTo( entry("B", "prop1", Arrays.asList( "String" ), false) ), + equalTo( entry("B", "prop2", Arrays.asList( "Integer" ), false) ) ) ); //printStream( stream ); } - private Object[] entry( String entityType, List labelsOrRelType, String propertyName, List propertyValueTypes, Boolean nullable ) + private Object[] entry( String labelsOrRelType, String propertyName, List propertyValueTypes, Boolean mandatory ) { - return new Object[]{entityType, labelsOrRelType, propertyName, propertyValueTypes, nullable}; + return new Object[]{labelsOrRelType, propertyName, propertyValueTypes, mandatory}; } private long createEmptyNode() throws Throwable diff --git a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInProceduresIT.java b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInProceduresIT.java index 0d99879ffec5..f40c2871eeaf 100644 --- a/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInProceduresIT.java +++ b/community/community-it/kernel-it/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInProceduresIT.java @@ -149,10 +149,14 @@ public void listProcedures() throws Throwable "Show the schema of the data.", "READ" ), proc( "db.schema.visualization","() :: (nodes :: LIST? OF NODE?, relationships :: LIST? OF RELATIONSHIP?)", "Visualize the schema of the data. Replaces db.schema.", "READ" ), - proc( "okapi.schema", "() :: (type :: STRING?, nodeLabelsOrRelType :: LIST? OF STRING?, property :: STRING?, " + - "cypherTypes :: LIST? OF STRING?, nullable :: BOOLEAN?)", "Show the derived property schema of the data in tabular form.", + proc( "db.schema.nodeProperties", "() :: (nodeType :: STRING?, propertyName :: STRING?, " + + "propertyTypes :: LIST? OF STRING?, mandatory :: BOOLEAN?)", "Show the derived property schema of the nodes in tabular form.", "READ" ), - proc( "db.relationshipTypes", "() :: (relationshipType :: " + "STRING?)", "List all relationship types in the database.", "READ" ), + proc( "db.schema.edgeProperties", "() :: (relationshipType :: STRING?, propertyName :: STRING?, " + + "propertyTypes :: LIST? OF STRING?, mandatory :: BOOLEAN?)", + "Show the derived property schema of the relationships in tabular form.", "READ" ), + proc( "db.relationshipTypes", "() :: (relationshipType :: " + "STRING?)", + "List all relationship types in the database.", "READ" ), proc( "dbms.procedures", "() :: (name :: STRING?, signature :: " + "STRING?, description :: STRING?, mode :: STRING?)", "List all procedures in the DBMS.", "DBMS" ), proc( "dbms.functions", "() :: (name :: STRING?, signature :: " + "STRING?, description :: STRING?)", diff --git a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/BuiltInProcedures.java b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/BuiltInProcedures.java index 3c1e9c9721d0..42657c8f8c2f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/BuiltInProcedures.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/BuiltInProcedures.java @@ -215,11 +215,18 @@ public void resampleOutdatedIndexes() } } - @Procedure( name = "okapi.schema", mode = Mode.READ ) - @Description( "Show the derived property schema of the data in tabular form." ) - public Stream propertySchema() + @Procedure( name = "db.schema.nodeProperties", mode = Mode.READ ) + @Description( "Show the derived property schema of the nodes in tabular form." ) + public Stream nodePropertySchema() { - return new SchemaCalculator( tx ).calculateTabularResultStream(); + return new SchemaCalculator( tx ).calculateTabularResultStreamForNodes(); + } + + @Procedure( name = "db.schema.edgeProperties", mode = Mode.READ ) + @Description( "Show the derived property schema of the relationships in tabular form." ) + public Stream relationshipPropertySchema() + { + return new SchemaCalculator( tx ).calculateTabularResultStreamForRels(); } @Deprecated diff --git a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/SchemaInfoResult.java b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/NodePropertySchemaInfoResult.java similarity index 50% rename from community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/SchemaInfoResult.java rename to community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/NodePropertySchemaInfoResult.java index 73799828f1da..a7fd81fe867c 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/SchemaInfoResult.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/NodePropertySchemaInfoResult.java @@ -21,39 +21,33 @@ import java.util.List; -public class SchemaInfoResult +public class NodePropertySchemaInfoResult { /** - * Indicates whether the entry is a node or a relationship + * A combination of labels interleaved by ":" */ - public final String type; + public final String nodeType; /** - * A combination of labels or a relationship + * A property name that occurs on the given label combination or null */ - public final List nodeLabelsOrRelType; + public final String propertyName; /** - * A property that occurs on the given label combination / relationship type or null + * A List containing all types of the given property on the given label combination or null */ - public final String property; + public final List propertyTypes; /** - * A List containing all CypherTypes of the given property on the given label combination / relationship type or null + * Indicates whether the property is present on all similar nodes (= true) or not (= false) */ - public final List cypherTypes; + public final boolean mandatory; - /** - * Indicates whether the property is present on all similar nodes / relationships (= false) or not (= true) - */ - public final boolean nullable; - - public SchemaInfoResult( String type, List nodeLabelsOrRelType, String property, List cypherTypes, boolean nullable ) + public NodePropertySchemaInfoResult( String nodeLabels, String propertyName, List cypherTypes, boolean mandatory ) { - this.type = type; - this.nodeLabelsOrRelType = nodeLabelsOrRelType; - this.property = property; - this.cypherTypes = cypherTypes; - this.nullable = nullable; + this.nodeType = nodeLabels; + this.propertyName = propertyName; + this.propertyTypes = cypherTypes; + this.mandatory = mandatory; } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/RelationshipPropertySchemaInfoResult.java b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/RelationshipPropertySchemaInfoResult.java new file mode 100644 index 000000000000..a7bf69740b81 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/RelationshipPropertySchemaInfoResult.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2002-2018 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.kernel.builtinprocs; + +import java.util.List; + +public class RelationshipPropertySchemaInfoResult +{ + /** + * A relationship type + */ + public final String relationshipType; + + /** + * A property name that occurs on the given relationship type or null + */ + public final String propertyName; + + /** + * A List containing all types of the given property on the given relationship type or null + */ + public final List propertyTypes; + + /** + * Indicates whether the property is present on all similar relationships (= true) or not (= false) + */ + public final boolean mandatory; + + public RelationshipPropertySchemaInfoResult( String relType, String propertyName, List cypherTypes, boolean mandatory ) + { + this.relationshipType = relType; + this.propertyName = propertyName; + this.propertyTypes = cypherTypes; + this.mandatory = mandatory; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/SchemaCalculator.java b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/SchemaCalculator.java index 752cb498e715..b0a5a785fad2 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/SchemaCalculator.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/builtinprocs/SchemaCalculator.java @@ -46,19 +46,12 @@ public class SchemaCalculator { - private Map labelSetToPropertyKeysMapping; - private Map,ValueTypeListHelper> labelSetANDNodePropertyKeyIdToValueTypeMapping; - private Set nullableLabelSets; // used for label combinations without properties -> all properties are viewed as nullable - private Map labelIdToLabelNameMapping; - private Map propertyIdToPropertylNameMapping; - private Map relationshipTypIdToRelationshipNameMapping; - private Map relationshipTypeIdToPropertyKeysMapping; - private Map,ValueTypeListHelper> relationshipTypeIdANDPropertyTypeIdToValueTypeMapping; - private Set nullableRelationshipTypes; // used for types without properties -> all properties are viewed as nullable + private Map propertyIdToPropertyNameMapping; + + private NodeMappings nodeMappings; + private RelationshipMappings relMappings; private final MutableIntSet emptyPropertyIdSet = IntSets.mutable.empty(); - private static final String NODE = "Node"; - private static final String RELATIONSHIP = "Relationship"; private final Read dataRead; private final TokenRead tokenRead; @@ -70,60 +63,75 @@ public class SchemaCalculator this.tokenRead = ktx.tokenRead(); this.cursors = ktx.cursors(); - // setup mappings + // the only one that is common for both nodes and rels so thats why we can do it here + propertyIdToPropertyNameMapping = new HashMap<>( tokenRead.propertyKeyCount() ); + addNamesToCollection( tokenRead.propertyKeyGetAllTokens(), propertyIdToPropertyNameMapping ); + } + + private void initializeMappingsForNodes() + { int labelCount = tokenRead.labelCount(); + nodeMappings = new NodeMappings( labelCount ); + } + + private void initializeMappingsForRels() + { int relationshipTypeCount = tokenRead.relationshipTypeCount(); - labelSetToPropertyKeysMapping = new HashMap<>( labelCount ); - labelIdToLabelNameMapping = new HashMap<>( labelCount ); - propertyIdToPropertylNameMapping = new HashMap<>( tokenRead.propertyKeyCount() ); - relationshipTypIdToRelationshipNameMapping = new HashMap<>( relationshipTypeCount ); - relationshipTypeIdToPropertyKeysMapping = new HashMap<>( relationshipTypeCount ); - labelSetANDNodePropertyKeyIdToValueTypeMapping = new HashMap<>(); - relationshipTypeIdANDPropertyTypeIdToValueTypeMapping = new HashMap<>(); - nullableLabelSets = new HashSet<>( ); - nullableRelationshipTypes = new HashSet<>( ); + relMappings = new RelationshipMappings( relationshipTypeCount ); } - public Stream calculateTabularResultStream() + // If we would have this schema information in the count store (or somewhere), this could be super fast + public Stream calculateTabularResultStreamForNodes() { - calculateSchema(); + initializeMappingsForNodes(); + scanEverythingBelongingToNodes(); - List results = new ArrayList<>(); - results.addAll( produceResultsForNodes() ); - results.addAll( produceResultsForRelationships() ); + // go through all labels to get actual names + addNamesToCollection( tokenRead.labelsGetAllTokens(), nodeMappings.labelIdToLabelName ); - return results.stream(); + return produceResultsForNodes().stream(); } - private List produceResultsForRelationships() + public Stream calculateTabularResultStreamForRels() { - List results = new ArrayList<>(); - for ( Integer typeId : relationshipTypeIdToPropertyKeysMapping.keySet() ) + initializeMappingsForRels(); + scanEverythingBelongingToRelationships( ); + + // go through all relationshipTypes to get actual names + addNamesToCollection( tokenRead.relationshipTypesGetAllTokens(), relMappings.relationshipTypIdToRelationshipName ); + + return produceResultsForRelationships().stream(); + } + + private List produceResultsForRelationships() + { + List results = new ArrayList<>(); + for ( Integer typeId : relMappings.relationshipTypeIdToPropertyKeys.keySet() ) { // lookup typ name - String name = relationshipTypIdToRelationshipNameMapping.get( typeId ); + String name = relMappings.relationshipTypIdToRelationshipName.get( typeId ); // lookup property value types - MutableIntSet propertyIds = relationshipTypeIdToPropertyKeysMapping.get( typeId ); + MutableIntSet propertyIds = relMappings.relationshipTypeIdToPropertyKeys.get( typeId ); if ( propertyIds.size() == 0 ) { - results.add( new SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), null, null, true ) ); + results.add( new RelationshipPropertySchemaInfoResult( name, null, null, false ) ); } else { propertyIds.forEach( propId -> { // lookup propId name and valueGroup - String propName = propertyIdToPropertylNameMapping.get( propId ); - ValueTypeListHelper valueTypeListHelper = relationshipTypeIdANDPropertyTypeIdToValueTypeMapping.get( Pair.of( typeId, propId ) ); - if ( nullableRelationshipTypes.contains( typeId ) ) + String propName = propertyIdToPropertyNameMapping.get( propId ); + ValueTypeListHelper valueTypeListHelper = relMappings.relationshipTypeIdANDPropertyTypeIdToValueType.get( Pair.of( typeId, propId ) ); + if ( relMappings.nullableRelationshipTypes.contains( typeId ) ) { - results.add( new SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), propName, valueTypeListHelper.getCypherTypesList(), - true ) ); + results.add( new RelationshipPropertySchemaInfoResult( name, propName, valueTypeListHelper.getCypherTypesList(), + false ) ); } else { - results.add( new SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), propName, valueTypeListHelper.getCypherTypesList(), - valueTypeListHelper.isNullable() ) ); + results.add( new RelationshipPropertySchemaInfoResult( name, propName, valueTypeListHelper.getCypherTypesList(), + valueTypeListHelper.isMandatory() ) ); } } ); } @@ -131,39 +139,53 @@ private List produceResultsForRelationships() return results; } - private List produceResultsForNodes() + private List produceResultsForNodes() { - List results = new ArrayList<>(); - for ( SortedLabels labelSet : labelSetToPropertyKeysMapping.keySet() ) + List results = new ArrayList<>(); + for ( SortedLabels labelSet : nodeMappings.labelSetToPropertyKeys.keySet() ) { - // lookup label names and produce list of names + // lookup label names and produce list of names and produce String out of them List labelNames = new ArrayList<>(); for ( int i = 0; i < labelSet.numberOfLabels(); i++ ) { - String name = labelIdToLabelNameMapping.get( labelSet.label( i ) ); + String name = nodeMappings.labelIdToLabelName.get( labelSet.label( i ) ); labelNames.add( name ); } + Collections.sort( labelNames ); // this is optional but waaaaay nicer + StringBuilder labelsConcatenator = new StringBuilder(); + for ( String item : labelNames ) + { + if ( labelsConcatenator.toString().equals( "" ) ) + { + labelsConcatenator.append( item ); + } + else + { + labelsConcatenator.append( ":" ).append( item ); + } + } + String labels = labelsConcatenator.toString(); // lookup property value types - MutableIntSet propertyIds = labelSetToPropertyKeysMapping.get( labelSet ); + MutableIntSet propertyIds = nodeMappings.labelSetToPropertyKeys.get( labelSet ); if ( propertyIds.size() == 0 ) { - results.add( new SchemaInfoResult( NODE, labelNames, null, null, true ) ); + results.add( new NodePropertySchemaInfoResult( labels, null, null, false ) ); } else { propertyIds.forEach( propId -> { // lookup propId name and valueGroup - String propName = propertyIdToPropertylNameMapping.get( propId ); - ValueTypeListHelper valueTypeListHelper = labelSetANDNodePropertyKeyIdToValueTypeMapping.get( Pair.of( labelSet, propId ) ); - if ( nullableLabelSets.contains( labelSet ) ) + String propName = propertyIdToPropertyNameMapping.get( propId ); + ValueTypeListHelper valueTypeListHelper = nodeMappings.labelSetANDNodePropertyKeyIdToValueType.get( Pair.of( labelSet, propId ) ); + if ( nodeMappings.nullableLabelSets.contains( labelSet ) ) { - results.add( new SchemaInfoResult( NODE, labelNames, propName, valueTypeListHelper.getCypherTypesList(), true ) ); + results.add( new NodePropertySchemaInfoResult( labels, propName, valueTypeListHelper.getCypherTypesList(), false ) ); } else { - results.add( new SchemaInfoResult( NODE, labelNames, propName, valueTypeListHelper.getCypherTypesList(), - valueTypeListHelper.isNullable() ) ); + results.add( new NodePropertySchemaInfoResult( labels, propName, valueTypeListHelper.getCypherTypesList(), + valueTypeListHelper.isMandatory() ) ); } } ); } @@ -171,21 +193,6 @@ private List produceResultsForNodes() return results; } - //TODO: If we would have this schema information in the count store (or somewhere), this could be super fast - private void calculateSchema() - { - scanEverythingBelongingToNodes( ); - scanEverythingBelongingToRelationships( ); - - // OTHER: - // go through all labels - addNamesToCollection( tokenRead.labelsGetAllTokens(), labelIdToLabelNameMapping ); - // go through all propertyKeys - addNamesToCollection( tokenRead.propertyKeyGetAllTokens(), propertyIdToPropertylNameMapping ); - // go through all relationshipTypes - addNamesToCollection( tokenRead.relationshipTypesGetAllTokens(), relationshipTypIdToRelationshipNameMapping ); - } - private void scanEverythingBelongingToRelationships( ) { try ( RelationshipScanCursor relationshipScanCursor = cursors.allocateRelationshipScanCursor(); @@ -204,13 +211,13 @@ private void scanEverythingBelongingToRelationships( ) Value currentValue = propertyCursor.propertyValue(); Pair key = Pair.of( typeId, propertyKey ); - updateValueTypeInMapping( currentValue, key, relationshipTypeIdANDPropertyTypeIdToValueTypeMapping ); + updateValueTypeInMapping( currentValue, key, relMappings.relationshipTypeIdANDPropertyTypeIdToValueType ); propertyIds.add( propertyKey ); } propertyCursor.close(); - MutableIntSet oldPropertyKeySet = relationshipTypeIdToPropertyKeysMapping.getOrDefault( typeId, emptyPropertyIdSet ); + MutableIntSet oldPropertyKeySet = relMappings.relationshipTypeIdToPropertyKeys.getOrDefault( typeId, emptyPropertyIdSet ); // find out which old properties we did not visited and mark them as nullable if ( oldPropertyKeySet == emptyPropertyIdSet ) @@ -218,7 +225,7 @@ private void scanEverythingBelongingToRelationships( ) if ( propertyIds.size() == 0 ) { // Even if we find property key on other rels with this type, set all of them nullable - nullableRelationshipTypes.add( typeId ); + relMappings.nullableRelationshipTypes.add( typeId ); } propertyIds.addAll( oldPropertyKeySet ); @@ -233,13 +240,13 @@ private void scanEverythingBelongingToRelationships( ) propertyIds.addAll( oldPropertyKeySet ); propertyIds.forEach( id -> { Pair key = Pair.of( typeId, id ); - relationshipTypeIdANDPropertyTypeIdToValueTypeMapping.get( key ).setNullable(); + relMappings.relationshipTypeIdANDPropertyTypeIdToValueType.get( key ).setNullable(); } ); propertyIds.addAll( currentPropertyIdsHelperSet ); } - relationshipTypeIdToPropertyKeysMapping.put( typeId, propertyIds ); + relMappings.relationshipTypeIdToPropertyKeys.put( typeId, propertyIds ); } relationshipScanCursor.close(); } @@ -263,13 +270,13 @@ private void scanEverythingBelongingToNodes( ) Value currentValue = propertyCursor.propertyValue(); int propertyKeyId = propertyCursor.propertyKey(); Pair key = Pair.of( labels, propertyKeyId ); - updateValueTypeInMapping( currentValue, key, labelSetANDNodePropertyKeyIdToValueTypeMapping ); + updateValueTypeInMapping( currentValue, key, nodeMappings.labelSetANDNodePropertyKeyIdToValueType ); propertyIds.add( propertyKeyId ); } propertyCursor.close(); - MutableIntSet oldPropertyKeySet = labelSetToPropertyKeysMapping.getOrDefault( labels, emptyPropertyIdSet ); + MutableIntSet oldPropertyKeySet = nodeMappings.labelSetToPropertyKeys.getOrDefault( labels, emptyPropertyIdSet ); // find out which old properties we did not visited and mark them as nullable if ( oldPropertyKeySet == emptyPropertyIdSet ) @@ -277,7 +284,7 @@ private void scanEverythingBelongingToNodes( ) if ( propertyIds.size() == 0 ) { // Even if we find property key on other nodes with those labels, set all of them nullable - nullableLabelSets.add( labels ); + nodeMappings.nullableLabelSets.add( labels ); } propertyIds.addAll( oldPropertyKeySet ); @@ -292,13 +299,13 @@ private void scanEverythingBelongingToNodes( ) propertyIds.addAll( oldPropertyKeySet ); propertyIds.forEach( id -> { Pair key = Pair.of( labels, id ); - labelSetANDNodePropertyKeyIdToValueTypeMapping.get( key ).setNullable(); + nodeMappings.labelSetANDNodePropertyKeyIdToValueType.get( key ).setNullable(); } ); propertyIds.addAll( currentPropertyIdsHelperSet ); } - labelSetToPropertyKeysMapping.put( labels, propertyIds ); + nodeMappings.labelSetToPropertyKeys.put( labels, propertyIds ); } nodeCursor.close(); } @@ -330,7 +337,7 @@ private void addNamesToCollection( Iterator labelIterator, Map seenValueTypes; - private boolean isNullable; + private boolean isMandatory = true; ValueTypeListHelper( Value v ) { @@ -338,14 +345,14 @@ private class ValueTypeListHelper updateValueTypesWith( v ); } - private void setNullable( ) + private void setNullable() { - isNullable = true; + isMandatory = false; } - public boolean isNullable() + public boolean isMandatory() { - return isNullable; + return isMandatory; } List getCypherTypesList() @@ -362,4 +369,42 @@ void updateValueTypesWith( Value newValue ) seenValueTypes.add( newValue.getTypeName() ); } } + + /* + All mappings needed to describe Nodes except for property infos + */ + private class NodeMappings + { + final Map labelSetToPropertyKeys; + final Map,ValueTypeListHelper> labelSetANDNodePropertyKeyIdToValueType; + final Set nullableLabelSets; // used for label combinations without properties -> all properties are viewed as nullable + final Map labelIdToLabelName; + + NodeMappings( int labelCount ) + { + labelSetToPropertyKeys = new HashMap<>( labelCount ); + labelIdToLabelName = new HashMap<>( labelCount ); + labelSetANDNodePropertyKeyIdToValueType = new HashMap<>(); + nullableLabelSets = new HashSet<>(); + } + } + + /* + All mappings needed to describe Rels except for property infos + */ + private class RelationshipMappings + { + final Map relationshipTypIdToRelationshipName; + final Map relationshipTypeIdToPropertyKeys; + final Map,ValueTypeListHelper> relationshipTypeIdANDPropertyTypeIdToValueType; + final Set nullableRelationshipTypes; // used for types without properties -> all properties are viewed as nullable + + RelationshipMappings( int relationshipTypeCount ) + { + relationshipTypIdToRelationshipName = new HashMap<>( relationshipTypeCount ); + relationshipTypeIdToPropertyKeys = new HashMap<>( relationshipTypeCount ); + relationshipTypeIdANDPropertyTypeIdToValueType = new HashMap<>(); + nullableRelationshipTypes = new HashSet<>(); + } + } } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java b/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java index 5e721d3b1b6b..1145ca8d8a89 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/builtinprocs/BuiltInProceduresTest.java @@ -289,10 +289,14 @@ public void shouldListCorrectBuiltinProcedures() throws Throwable record( "db.schema.visualization", "db.schema.visualization() :: (nodes :: LIST? OF NODE?, relationships :: LIST? OF RELATIONSHIP?)", "Visualize the schema of the data. Replaces db.schema.", "READ" ), - record( "okapi.schema", - "okapi.schema() :: (type :: STRING?, nodeLabelsOrRelType :: LIST? OF STRING?, property :: STRING?," + - " cypherTypes :: LIST? OF STRING?, nullable :: BOOLEAN?)", - "Show the derived property schema of the data in tabular form.", "READ" ), + record( "db.schema.nodeProperties", + "db.schema.nodeProperties() :: (nodeType :: STRING?, propertyName :: STRING?," + + " propertyTypes :: LIST? OF STRING?, mandatory :: BOOLEAN?)", + "Show the derived property schema of the nodes in tabular form.", "READ" ), + record( "db.schema.edgeProperties", + "db.schema.edgeProperties() :: (relationshipType :: STRING?, propertyName :: STRING?, " + + "propertyTypes :: LIST? OF STRING?, mandatory :: BOOLEAN?)", + "Show the derived property schema of the relationships in tabular form.", "READ" ), record( "db.index.explicit.searchNodes", "db.index.explicit.searchNodes(indexName :: STRING?, query :: ANY?) :: (node :: NODE?, weight :: FLOAT?)", "Search nodes in explicit index. Replaces `START n=node:nodes('key:foo*')`", "READ" ),