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 43626f9c08702..5030b3bfff954 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,6 +149,10 @@ public void listProcedures() throws Throwable equalTo( new Object[]{"db.schema", "db.schema() :: (nodes :: LIST? OF NODE?, relationships :: LIST? " + "OF " + "RELATIONSHIP?)", "Show the schema of the data.", "READ"} ), + equalTo( new Object[]{"db.propertySchema", + "db.propertySchema() :: (type :: STRING?, nodeLabelsOrRelType :: LIST? OF STRING?, property :: STRING?, cypherType :: STRING?)", + "Show the derived property schema of the data in tabular form.", + "READ"} ), equalTo( new Object[]{"db.relationshipTypes", "db.relationshipTypes() :: (relationshipType :: " + "STRING?)", "List all relationship types in the database.", "READ"} ), 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 aceec198cd6a5..af4ed81a6cff5 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 @@ -19,16 +19,15 @@ */ package org.neo4j.kernel.builtinprocs; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; +import org.eclipse.collections.api.set.primitive.MutableIntSet; +import org.eclipse.collections.impl.factory.primitive.IntSets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Stream; import org.neo4j.helpers.collection.Pair; @@ -39,13 +38,15 @@ import org.neo4j.internal.kernel.api.PropertyCursor; import org.neo4j.internal.kernel.api.Read; import org.neo4j.internal.kernel.api.RelationshipScanCursor; -import org.neo4j.internal.kernel.api.SchemaRead; import org.neo4j.internal.kernel.api.TokenRead; import org.neo4j.internal.kernel.api.Transaction; +import org.neo4j.values.storable.IntegralArray; +import org.neo4j.values.storable.IntegralValue; import org.neo4j.values.storable.Value; import org.neo4j.values.storable.ValueGroup; import static org.neo4j.kernel.builtinprocs.SchemaCalculator.ValueStatus.ANY; +import static org.neo4j.kernel.builtinprocs.SchemaCalculator.ValueStatus.CONSISTENT_NUMBER_VALUE; import static org.neo4j.kernel.builtinprocs.SchemaCalculator.ValueStatus.VALUE; import static org.neo4j.kernel.builtinprocs.SchemaCalculator.ValueStatus.VALUE_GROUP; @@ -53,18 +54,26 @@ public class SchemaCalculator { private org.neo4j.internal.kernel.api.Transaction ktx; - private Map> labelSetToPropertyKeysMapping; + private Map labelSetToPropertyKeysMapping; private Map,ValueTypeDecider> labelSetANDNodePropertyKeyIdToValueTypeMapping; private Map labelIdToLabelNameMapping; private Map propertyIdToPropertylNameMapping; private Map relationshipTypIdToRelationshipNameMapping; - private Map> relationshipTypeIdToPropertyKeysMapping; + private Map relationshipTypeIdToPropertyKeysMapping; private Map,ValueTypeDecider> relationshipTypeIdANDPropertyTypeIdToValueTypeMapping; - private final Set emptyPropertyIdSet = Collections.unmodifiableSet( Collections.emptySet() ); + private final MutableIntSet emptyPropertyIdSet = IntSets.mutable.empty(); private final String ANYVALUE = "ANY"; + private final String INTEGRAL = "INTEGRAL"; + private final String INTEGRAL_ARRAY = "INTEGRALARRAY"; + private final String FLOATING_POINT = "FLOATINGPOINT"; + private final String FLOATING_POINT_ARRAY = "FLOATINGPOINTARRAY"; private final String NULLABLE = "?"; private final String NULLABLE_ANYVALUE = ANYVALUE + NULLABLE; + private final String NULLABLE_INTEGRAL = INTEGRAL + NULLABLE; + private final String NULLABLE_INTEGRAL_ARRAY = INTEGRAL_ARRAY + NULLABLE; + private final String NULLABLE_FLOATING_POINT_ARRAY = FLOATING_POINT_ARRAY + NULLABLE; + private final String NULLABLE_FLOATING_POINT = FLOATING_POINT + NULLABLE; private final String NODE = "Node"; private final String RELATIONSHIP = "Relationship"; @@ -93,21 +102,19 @@ private List produceResultsForRelationships() String name = relationshipTypIdToRelationshipNameMapping.get( typeId ); // lookup property value types - Set propertyIds = relationshipTypeIdToPropertyKeysMapping.get( typeId ); + MutableIntSet propertyIds = relationshipTypeIdToPropertyKeysMapping.get( typeId ); if ( propertyIds.size() == 0 ) { results.add( new SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), null, null ) ); } else { - for ( Integer propId : propertyIds ) - { + propertyIds.forEach( propId -> { // lookup propId name and valueGroup String propName = propertyIdToPropertylNameMapping.get( propId ); ValueTypeDecider valueTypeDecider = relationshipTypeIdANDPropertyTypeIdToValueTypeMapping.get( Pair.of( typeId, propId ) ); - results.add( new SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), propName, - valueTypeDecider.getCypherTypeString() ) ); - } + results.add( new SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), propName, valueTypeDecider.getCypherTypeString() ) ); + } ); } } return results; @@ -127,40 +134,30 @@ private List produceResultsForNodes() } // lookup property value types - Set propertyIds = labelSetToPropertyKeysMapping.get( labelSet ); + MutableIntSet propertyIds = labelSetToPropertyKeysMapping.get( labelSet ); if ( propertyIds.size() == 0 ) { results.add( new SchemaInfoResult( NODE, labelNames, null, null ) ); } else { - for ( Integer propId : propertyIds ) - { + propertyIds.forEach( propId -> { // lookup propId name and valueGroup String propName = propertyIdToPropertylNameMapping.get( propId ); ValueTypeDecider valueTypeDecider = labelSetANDNodePropertyKeyIdToValueTypeMapping.get( Pair.of( labelSet, propId ) ); results.add( new SchemaInfoResult( NODE, labelNames, propName, valueTypeDecider.getCypherTypeString() ) ); - } + } ); } } return results; } - public Stream calculateGraphResultStream() - { - calculateSchema(); - //TODO finish this - throw new NotImplementedException(); - } - - //TODO: Parallelize this: (Nodes | Rels) and/or more //TODO: If we would have this schema information in the count store (or somewhere), this could be super fast private void calculateSchema() { // this one does most of the work Read dataRead = ktx.dataRead(); TokenRead tokenRead = ktx.tokenRead(); - SchemaRead schemaRead = ktx.schemaRead(); CursorFactory cursors = ktx.cursors(); // setup mappings @@ -195,7 +192,7 @@ private void scanEverythingBelongingToRelationships( Read dataRead, CursorFactor { int typeId = relationshipScanCursor.type(); relationshipScanCursor.properties( propertyCursor ); - Set propertyIds = new HashSet<>(); // is Set really the best fit here? + MutableIntSet propertyIds = IntSets.mutable.empty(); while ( propertyCursor.next() ) { @@ -209,7 +206,7 @@ private void scanEverythingBelongingToRelationships( Read dataRead, CursorFactor } propertyCursor.close(); - Set oldPropertyKeySet = relationshipTypeIdToPropertyKeysMapping.getOrDefault( typeId, emptyPropertyIdSet ); + MutableIntSet oldPropertyKeySet = relationshipTypeIdToPropertyKeysMapping.getOrDefault( typeId, emptyPropertyIdSet ); // find out which old properties we did not visited and mark them as nullable if ( !(oldPropertyKeySet == emptyPropertyIdSet) ) @@ -238,7 +235,7 @@ private void scanEverythingBelongingToNodes( Read dataRead, CursorFactory cursor // each node LabelSet labels = nodeCursor.labels(); nodeCursor.properties( propertyCursor ); - Set propertyIds = new HashSet<>(); // is Set really the best fit here? + MutableIntSet propertyIds = IntSets.mutable.empty(); while ( propertyCursor.next() ) { @@ -251,7 +248,7 @@ private void scanEverythingBelongingToNodes( Read dataRead, CursorFactory cursor } propertyCursor.close(); - Set oldPropertyKeySet = labelSetToPropertyKeysMapping.getOrDefault( labels, emptyPropertyIdSet ); + MutableIntSet oldPropertyKeySet = labelSetToPropertyKeysMapping.getOrDefault( labels, emptyPropertyIdSet ); // find out which old properties we did not visited and mark them as nullable if ( !(oldPropertyKeySet == emptyPropertyIdSet) ) @@ -298,7 +295,8 @@ private class ValueTypeDecider private Value concreteValue; private ValueGroup valueGroup; private ValueStatus valueStatus; - private Boolean isNullable = false; + private boolean isNullable; + private Boolean isIntegral; // this is only important if we have a NumberValue or NumberArray. In those cases false means FloatingPoint ValueTypeDecider( Value v ) { @@ -309,6 +307,8 @@ private class ValueTypeDecider this.concreteValue = v; this.valueGroup = v.valueGroup(); this.valueStatus = VALUE; + + this.isIntegral = isIntegral( v ); } private void setNullable( ) @@ -316,6 +316,26 @@ private void setNullable( ) isNullable = true; } + /*** + * Checks if the given value is an Integral value, a Floating Point value or none + * @param v the given value + * @return true, if v is an IntegralValue or -Array (e.g. Long), false if v is a FloatingPoint or -Array (e.g. Double) + * or null if v is not neither NUMBER nor NUMBER_ARRAY + */ + + private Boolean isIntegral( Value v ) + { + if ( v.valueGroup() == ValueGroup.NUMBER_ARRAY ) + { + return v instanceof IntegralArray; + } + else if ( v.valueGroup() == ValueGroup.NUMBER ) + { + return v instanceof IntegralValue; + } + return null; + } + /* This method translates an ValueTypeDecider into the correct String */ @@ -326,6 +346,36 @@ String getCypherTypeString() case VALUE: return isNullable ? concreteValue.getTypeName().toUpperCase() + NULLABLE : concreteValue.getTypeName().toUpperCase(); + case CONSISTENT_NUMBER_VALUE: + if ( isIntegral == null ) + { + throw new IllegalStateException( "isIntegral should have been set in this state" ); + } + if ( valueGroup == ValueGroup.NUMBER ) + { + if ( isIntegral ) + { + return isNullable ? NULLABLE_INTEGRAL + : INTEGRAL; + } + else + { + return isNullable ? NULLABLE_FLOATING_POINT + : FLOATING_POINT; + } + } + // NUMBER_ARRAY + if ( isIntegral ) + { + return isNullable ? NULLABLE_INTEGRAL_ARRAY + : INTEGRAL_ARRAY; + } + else + { + return isNullable ? NULLABLE_FLOATING_POINT_ARRAY + : FLOATING_POINT_ARRAY; + } + case VALUE_GROUP: return isNullable ? valueGroup.name() + NULLABLE : valueGroup.name(); @@ -340,8 +390,9 @@ String getCypherTypeString() /* This method is needed to handle conflicting property values and sets valueStatus accordingly to: A) VALUE if current and new value match on class level - B) VALUE_GROUP, if at least the ValueGroups of the current and new values match - C) ANY, if nothing matches + B) CONSISTENT_NUMBER_VALUE if current and new value are NUMBER or NUMBER_ARRAY and both are integral or both are floating point values + C) VALUE_GROUP if at least the ValueGroups of the current and new values match + D) ANY if nothing matches */ void compareAndPutValueType( Value newValue ) { @@ -353,14 +404,29 @@ void compareAndPutValueType( Value newValue ) switch ( valueStatus ) { case VALUE: - // check if classes match + // check if classes match -> if so, do nothing if ( !concreteValue.getClass().equals( newValue.getClass() ) ) { // Clases don't match -> update needed if ( valueGroup.equals( newValue.valueGroup() ) ) { - // same valueGroup -> set that + // same valueGroup -> set that (default, can be overriden if they are Numbers and consistency checks out) valueStatus = VALUE_GROUP; + + if ( valueGroup == ValueGroup.NUMBER_ARRAY || valueGroup == ValueGroup.NUMBER ) + { + Boolean newValueIsIntegral = isIntegral( newValue ); + if ( isIntegral == null || newValueIsIntegral == null ) + { + throw new IllegalStateException( + "isIntegral should have been set in this state and method should have returned non null for new value" ); + } + // test consistency + if ( isIntegral == newValueIsIntegral ) + { + valueStatus = CONSISTENT_NUMBER_VALUE; + } + } } else { @@ -369,6 +435,29 @@ void compareAndPutValueType( Value newValue ) } } break; + + case CONSISTENT_NUMBER_VALUE: + if ( !valueGroup.equals( newValue.valueGroup() ) ) + { + // not same valueGroup -> update to AnyValue + valueStatus = ANY; + } + else + { + // same value-group + // -> update to VALUE_GROUP if new value brakes consistency + Boolean newValueIsIntegral = isIntegral( newValue ); + if ( isIntegral == null || newValueIsIntegral == null ) + { + throw new IllegalStateException( + "isIntegral should have been set in this state and method should have returned non null for new value" ); + } + if ( ! (isIntegral == newValueIsIntegral) ) + { + valueStatus = VALUE_GROUP; + } + } + break; case VALUE_GROUP: if ( !valueGroup.equals( newValue.valueGroup() ) ) { @@ -388,6 +477,7 @@ void compareAndPutValueType( Value newValue ) enum ValueStatus { VALUE, + CONSISTENT_NUMBER_VALUE, VALUE_GROUP, ANY } 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 7e89f35fb51f5..5de357890bb51 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 @@ -234,6 +234,9 @@ public void shouldListCorrectBuiltinProcedures() throws Throwable record( "db.schema", "db.schema() :: (nodes :: LIST? OF NODE?, relationships :: LIST? OF RELATIONSHIP?)", "Show the schema of the data.", "READ" ), + record( "db.propertySchema", + "db.propertySchema() :: (type :: STRING?, nodeLabelsOrRelType :: LIST? OF STRING?, property :: STRING?, cypherType :: STRING?)", + "Show the derived property schema of the data 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" ), diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInSchemaProceduresIT.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInSchemaProceduresIT.java index 44b39b4b47c6b..dab37b06cf0b2 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInSchemaProceduresIT.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/integrationtest/BuiltInSchemaProceduresIT.java @@ -19,9 +19,7 @@ */ package org.neo4j.kernel.impl.api.integrationtest; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import java.util.Arrays; import java.util.Iterator; @@ -29,8 +27,6 @@ import org.neo4j.collection.RawIterator; import org.neo4j.internal.kernel.api.Transaction; import org.neo4j.internal.kernel.api.exceptions.ProcedureException; -import org.neo4j.kernel.api.ResourceTracker; -import org.neo4j.kernel.api.StubResourceManager; import org.neo4j.kernel.api.security.AnonymousContext; import org.neo4j.values.storable.Values; @@ -43,11 +39,6 @@ public class BuiltInSchemaProceduresIT extends KernelIntegrationTest { - @Rule - public ExpectedException exception = ExpectedException.none(); - - private final ResourceTracker resourceTracker = new StubResourceManager(); - @Test public void testSchemaTableWithNodes() throws Throwable { @@ -90,8 +81,7 @@ public void testSchemaTableWithNodes() throws Throwable equalTo( new Object[]{"Node", Arrays.asList( "C" ), "prop1", "STRINGARRAY"} ), equalTo( new Object[]{"Node", Arrays.asList(), null, null} )) ); - // Just for printing out the result if needed -// printStream( stream ); + // printStream( stream ); } @Test @@ -121,8 +111,7 @@ public void testSchemaTableWithSimilarNodes() throws Throwable assertThat( asList( stream ), contains( equalTo( new Object[]{"Node", Arrays.asList("A"), "prop1", "STRING"} )) ); - // Just for printing out the result if needed - //printStream( stream ); + // printStream( stream ); } @Test @@ -159,8 +148,7 @@ public void testSchemaTableWithSimilarNodesHavingDifferentPropertyValueTypes() t equalTo( new Object[]{"Node", Arrays.asList(), "prop2", "NUMBER"} ), equalTo( new Object[]{"Node", Arrays.asList(), "prop3", "ANY"} ) ) ); - // Just for printing out the result if needed - //printStream( stream ); + // printStream( stream ); } @Test @@ -199,8 +187,7 @@ public void testSchemaTableWithRelationships() throws Throwable equalTo( new Object[]{"Relationship", Arrays.asList( "Z" ), null, null} ), equalTo( new Object[]{"Node", Arrays.asList(), null, null} ) ) ); - // Just for printing out the result if needed -// printStream( stream ); + // printStream( stream ); } @Test @@ -231,7 +218,6 @@ public void testSchemaTableWithSimilarRelationships() throws Throwable equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop1", "STRING"} ), equalTo( new Object[]{"Node", Arrays.asList(), null, null} ) ) ); - // Just for printing out the result if needed //printStream( stream ); } @@ -273,7 +259,6 @@ public void testSchemaTableWithSimilarRelationshipsHavingDifferentPropertyValueT equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop2", "NUMBER?"} ), equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop3", "ANY?"} ) ) ); - // Just for printing out the result if needed //printStream( stream ); } @@ -329,10 +314,113 @@ public void testSchemaTableWithNullableProperties() throws Throwable equalTo( new Object[]{"Node", Arrays.asList( "B" ), "prop1", "STRING?"} ), equalTo( new Object[]{"Node", Arrays.asList( "B" ), "prop2", "INTEGER?"} ) ) ); - // Just for printing out the result if needed //printStream( stream ); } + @Test + public void testSchemaTablesValueStatusUpgradeForNumbers() throws Throwable + { + // Given (not valid cypher) + + // Node1: (:A{prop1:(long), prop2: (float), prop3:(long), prop4:(long)}) + // Node2: (:A{prop1:(int),prop2: (double), prop3:(float), prop4:(string)}) + // Node3: (:A{prop1:(byte)}) + // Node4: (:A{prop1:(short)}) + + Transaction transaction = newTransaction( AnonymousContext.writeToken() ); + long nodeId1 = transaction.dataWrite().nodeCreate(); + long nodeId2 = transaction.dataWrite().nodeCreate(); + long nodeId3 = transaction.dataWrite().nodeCreate(); + long nodeId4 = transaction.dataWrite().nodeCreate(); + int labelA = transaction.tokenWrite().labelGetOrCreateForName( "A" ); + transaction.dataWrite().nodeAddLabel( nodeId1, labelA ); + transaction.dataWrite().nodeAddLabel( nodeId2, labelA ); + transaction.dataWrite().nodeAddLabel( nodeId3, labelA ); + transaction.dataWrite().nodeAddLabel( nodeId4, labelA ); + int prop1 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop1" ); + int prop2 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop2" ); + int prop3 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop3" ); + int prop4 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop4" ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop1, Values.longValue( 1 ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop1, Values.intValue( 1 ) ); + transaction.dataWrite().nodeSetProperty( nodeId3, prop1, Values.byteValue( (byte) 1 ) ); + transaction.dataWrite().nodeSetProperty( nodeId4, prop1, Values.shortValue( (short) 1 ) ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop2, Values.floatValue( 2.1f ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop2, Values.doubleValue( 2.1d ) ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop3, Values.longValue( 1 ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop3, Values.floatValue( 2.1f ) ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop4, Values.longValue( 1 ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop4, Values.stringValue( "Text" ) ); + commit(); + + // When + RawIterator stream = + procs().procedureCallRead( procs().procedureGet( procedureName( "db", "propertySchema" ) ).id(), new Object[0] ); + + // Then + assertThat( asList( stream ), containsInAnyOrder( + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop1", "INTEGRAL"} ), + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop2", "FLOATINGPOINT?"} ), + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop3", "NUMBER?"} ), + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop4", "ANY?"} ) ) ); + + //printStream( stream ); + } + + @Test + public void testSchemaTablesValueStatusUpgradeForNumberArrays() throws Throwable + { + // Given (not valid cypher) + + // Node1: (:A{prop1:(long array), prop2: (float array)}, prop3:(long array), prop4:(long array)}) + // Node2: (:A{prop1:(int array),prop2: (double array), prop3:(float array), prop4:(string array)}) + // Node3: (:A{prop1:(byte array)}) + // Node4: (:A{prop1:(short array)}) + + Transaction transaction = newTransaction( AnonymousContext.writeToken() ); + long nodeId1 = transaction.dataWrite().nodeCreate(); + long nodeId2 = transaction.dataWrite().nodeCreate(); + long nodeId3 = transaction.dataWrite().nodeCreate(); + long nodeId4 = transaction.dataWrite().nodeCreate(); + int labelA = transaction.tokenWrite().labelGetOrCreateForName( "A" ); + transaction.dataWrite().nodeAddLabel( nodeId1, labelA ); + transaction.dataWrite().nodeAddLabel( nodeId2, labelA ); + transaction.dataWrite().nodeAddLabel( nodeId3, labelA ); + transaction.dataWrite().nodeAddLabel( nodeId4, labelA ); + int prop1 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop1" ); + int prop2 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop2" ); + int prop3 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop3" ); + int prop4 = transaction.tokenWrite().propertyKeyGetOrCreateForName( "prop4" ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop1, Values.longArray( new long[]{1L} ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop1, Values.intArray( new int[]{1} ) ); + transaction.dataWrite().nodeSetProperty( nodeId3, prop1, Values.byteArray( new byte[]{(byte) 1} ) ); + transaction.dataWrite().nodeSetProperty( nodeId4, prop1, Values.shortArray( new short[]{(short) 1} ) ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop2, Values.floatArray( new float[]{2.1f} ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop2, Values.doubleArray( new double[]{2.1d} ) ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop3, Values.longArray( new long[]{1L} ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop3, Values.floatArray( new float[]{2.1f} ) ); + transaction.dataWrite().nodeSetProperty( nodeId1, prop4, Values.longArray( new long[]{1L} ) ); + transaction.dataWrite().nodeSetProperty( nodeId2, prop4, Values.stringArray( "Text" ) ); + commit(); + + // When + RawIterator stream = + procs().procedureCallRead( procs().procedureGet( procedureName( "db", "propertySchema" ) ).id(), new Object[0] ); + + // Then + assertThat( asList( stream ), containsInAnyOrder( + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop1", "INTEGRALARRAY"} ), + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop2", "FLOATINGPOINTARRAY?"} ), + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop3", "NUMBER_ARRAY?"} ), + equalTo( new Object[]{"Node", Arrays.asList( "A" ), "prop4", "ANY?"} ) ) ); + + // printStream( stream ); + } + + /* + This method can be used to print to result stream to System.out -> Useful for debugging + */ + @SuppressWarnings( "unused" ) private void printStream( RawIterator stream ) throws Throwable { Iterator iterator = asList( stream ).iterator();