Skip to content

Commit

Permalink
Refactoring that removes the need of pushing around Objects and casting
Browse files Browse the repository at this point in the history
  • Loading branch information
SaschaPeukert committed Jul 26, 2018
1 parent 78da235 commit 7d4e24d
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 71 deletions.
Expand Up @@ -42,13 +42,13 @@ public class BuiltInSchemaProcedures
@Description( "Show the schema of the data in tabular form." ) @Description( "Show the schema of the data in tabular form." )
public Stream<SchemaInfoResult> schemaAsTable() public Stream<SchemaInfoResult> schemaAsTable()
{ {
return new SchemaCalculator( db, tx ).calculateTabularResultStream(); return new SchemaCalculator( tx ).calculateTabularResultStream();
} }


// TODO: Next step // TODO: Next step
// @Procedure( value = "db.schema.graph", mode = Mode.READ ) // TODO: Change to db.schema when ready to completly replace old one // @Procedure( value = "db.schema.graph", mode = Mode.READ ) // TODO: Change to db.schema when ready to completely replace old one (if possible)
// @Description( "Show the schema of the data." ) // old description // @Description( "Show the schema of the data." ) // old description
// public Stream<SchemaProcedure.GraphResult> schemaAsGraph() //TODO: Maybe extract inner result classes // public Stream<SchemaProcedure.GraphResult> schemaAsGraph()
// { // {
// return new SchemaCalculator( db, tx ).calculateGraphResultStream(); // return new SchemaCalculator( db, tx ).calculateGraphResultStream();
// } // }
Expand Down
Expand Up @@ -43,35 +43,34 @@
import org.neo4j.internal.kernel.api.TokenRead; import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.kernel.api.KernelTransaction; import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.Statement; import org.neo4j.kernel.api.Statement;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.values.storable.Value; import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueGroup; import org.neo4j.values.storable.ValueGroup;


import static org.neo4j.kernel.builtinprocs.SchemaCalculator.ValueStatus.ANY_VALUE;
import static org.neo4j.kernel.builtinprocs.SchemaCalculator.ValueStatus.VALUE;
import static org.neo4j.kernel.builtinprocs.SchemaCalculator.ValueStatus.VALUE_GROUP;

public class SchemaCalculator public class SchemaCalculator
{ {
private GraphDatabaseAPI db;
private KernelTransaction ktx; private KernelTransaction ktx;


private Map<LabelSet,Set<Integer>> labelSetToPropertyKeysMapping; private Map<LabelSet,Set<Integer>> labelSetToPropertyKeysMapping;
// TODO: make those different Set<whatever> etc. into more useful/understandable classes?! private Map<Pair<LabelSet,Integer>,ValueTypeDecider> labelSetANDNodePropertyKeyIdToValueTypeMapping;
private Map<Pair<LabelSet,Integer>,Object> labelSetANDNodePropertyKeyIdToValueTypeMapping;
// Dislike object here... see deriveValueType() for Info about ValueType
private Map<Integer,String> labelIdToLabelNameMapping; private Map<Integer,String> labelIdToLabelNameMapping;
private Map<Integer,String> propertyIdToPropertylNameMapping; private Map<Integer,String> propertyIdToPropertylNameMapping;
private Map<Integer,String> relationshipTypIdToRelationshipNameMapping; private Map<Integer,String> relationshipTypIdToRelationshipNameMapping;
private Map<Integer,Set<Integer>> relationshipTypeIdToPropertyKeysMapping; private Map<Integer,Set<Integer>> relationshipTypeIdToPropertyKeysMapping;
private Map<Pair<Integer,Integer>,Object> relationshipTypeIdANDPropertyTypeIdToValueTypeMapping; private Map<Pair<Integer,Integer>,ValueTypeDecider> relationshipTypeIdANDPropertyTypeIdToValueTypeMapping;


private final Set<Integer> emptyPropertyIdSet = Collections.unmodifiableSet( Collections.emptySet() ); private final Set<Integer> emptyPropertyIdSet = Collections.unmodifiableSet( Collections.emptySet() );
private final String ANYVALUE = "ANYVALUE"; private final String ANYVALUE = "ANY_VALUE";
private final String NODE = "Node"; private final String NODE = "Node";
private final String RELATIONSHIP = "Relationship"; private final String RELATIONSHIP = "Relationship";


//TODO: Extend this to support showing how relationships types connect to nodes //TODO: Extend this to support showing how relationships types connect to nodes


SchemaCalculator( GraphDatabaseAPI db, KernelTransaction ktx ) SchemaCalculator( KernelTransaction ktx )
{ {
this.db = db;
this.ktx = ktx; this.ktx = ktx;
} }


Expand Down Expand Up @@ -106,9 +105,9 @@ private List<BuiltInSchemaProcedures.SchemaInfoResult> produceResultsForRelation
{ {
// lookup propId name and valueGroup // lookup propId name and valueGroup
String propName = propertyIdToPropertylNameMapping.get( propId ); String propName = propertyIdToPropertylNameMapping.get( propId );
Object valueType = relationshipTypeIdANDPropertyTypeIdToValueTypeMapping.get( Pair.of( typeId, propId ) ); ValueTypeDecider valueTypeDecider = relationshipTypeIdANDPropertyTypeIdToValueTypeMapping.get( Pair.of( typeId, propId ) );
String cypherType = getCypherTypeString( valueType ); results.add( new BuiltInSchemaProcedures.SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), propName,
results.add( new BuiltInSchemaProcedures.SchemaInfoResult( RELATIONSHIP, Collections.singletonList( name ), propName, cypherType ) ); valueTypeDecider.getCypherTypeString() ) );
} }
} }
} }
Expand Down Expand Up @@ -140,9 +139,8 @@ private List<BuiltInSchemaProcedures.SchemaInfoResult> produceResultsForNodes()
{ {
// lookup propId name and valueGroup // lookup propId name and valueGroup
String propName = propertyIdToPropertylNameMapping.get( propId ); String propName = propertyIdToPropertylNameMapping.get( propId );
Object valueType = labelSetANDNodePropertyKeyIdToValueTypeMapping.get( Pair.of( labelSet, propId ) ); ValueTypeDecider valueTypeDecider = labelSetANDNodePropertyKeyIdToValueTypeMapping.get( Pair.of( labelSet, propId ) );
String cypherType = getCypherTypeString( valueType ); results.add( new BuiltInSchemaProcedures.SchemaInfoResult( NODE, labelNames, propName, valueTypeDecider.getCypherTypeString() ) );
results.add( new BuiltInSchemaProcedures.SchemaInfoResult( NODE, labelNames, propName, cypherType ) );
} }
} }
} }
Expand Down Expand Up @@ -204,12 +202,11 @@ private void scanEverythingBelongingToRelationships( Read dataRead, CursorFactor
while ( propertyCursor.next() ) while ( propertyCursor.next() )
{ {
int propertyKey = propertyCursor.propertyKey(); int propertyKey = propertyCursor.propertyKey();
Value currentValue = propertyCursor.propertyValue();


Value currentValue = propertyCursor.propertyValue();
Pair<Integer,Integer> key = Pair.of( typeId, propertyKey ); Pair<Integer,Integer> key = Pair.of( typeId, propertyKey );
Object typeExampleValue = relationshipTypeIdANDPropertyTypeIdToValueTypeMapping.get( key ); updateValueTypeInMapping( currentValue, key, relationshipTypeIdANDPropertyTypeIdToValueTypeMapping );
typeExampleValue = deriveValueType( currentValue, typeExampleValue );
relationshipTypeIdANDPropertyTypeIdToValueTypeMapping.put( key, typeExampleValue );
propertyIds.add( propertyKey ); propertyIds.add( propertyKey );
} }
propertyCursor.close(); propertyCursor.close();
Expand All @@ -235,15 +232,11 @@ private void scanEverythingBelongingToNodes( Read dataRead, CursorFactory cursor


while ( propertyCursor.next() ) while ( propertyCursor.next() )
{ {
// each property
Value currentValue = propertyCursor.propertyValue(); Value currentValue = propertyCursor.propertyValue();
int propertyKeyId = propertyCursor.propertyKey(); int propertyKeyId = propertyCursor.propertyKey();

Pair<LabelSet,Integer> key = Pair.of( labels, propertyKeyId ); Pair<LabelSet,Integer> key = Pair.of( labels, propertyKeyId );
Object typeExampleValue = labelSetANDNodePropertyKeyIdToValueTypeMapping.get( key ); updateValueTypeInMapping( currentValue, key, labelSetANDNodePropertyKeyIdToValueTypeMapping );
typeExampleValue = deriveValueType( currentValue, typeExampleValue );


labelSetANDNodePropertyKeyIdToValueTypeMapping.put( key, typeExampleValue );
propertyIds.add( propertyKeyId ); propertyIds.add( propertyKeyId );
} }
propertyCursor.close(); propertyCursor.close();
Expand All @@ -255,6 +248,20 @@ private void scanEverythingBelongingToNodes( Read dataRead, CursorFactory cursor
nodeCursor.close(); nodeCursor.close();
} }


private <X, Y> void updateValueTypeInMapping( Value currentValue, Pair<X,Y> key, Map<Pair<X,Y>,ValueTypeDecider> mappingToUpdate )
{
ValueTypeDecider decider = mappingToUpdate.get( key );
if ( decider == null )
{
decider = new ValueTypeDecider( currentValue );
mappingToUpdate.put( key, decider );
}
else
{
decider.compareAndPutValueType( currentValue );
}
}

private void addNamesToCollection( Iterator<NamedToken> labelIterator, Map<Integer,String> collection ) private void addNamesToCollection( Iterator<NamedToken> labelIterator, Map<Integer,String> collection )
{ {
while ( labelIterator.hasNext() ) while ( labelIterator.hasNext() )
Expand All @@ -264,71 +271,89 @@ private void addNamesToCollection( Iterator<NamedToken> labelIterator, Map<Integ
} }
} }


/* private class ValueTypeDecider
This method is needed to handle conflicting property values. It returns one of the following:
A) Some concrete value, if all previous values matched on class level
B) A ValueGroup, if at least the ValueGroups of the previous values match
C) A String, if nothing matched
*/
private Object deriveValueType( Value currentValue, Object typeExampleValue )
{ {
if ( typeExampleValue == null ) private Value concreteValue;
private ValueGroup valueGroup;
private ValueStatus valueStatus;

ValueTypeDecider( Value v )
{ {
typeExampleValue = currentValue; this.concreteValue = v;
this.valueGroup = v.valueGroup();
this.valueStatus = VALUE;
} }
else
/*
This method translates an ValueTypeDecider into the correct String
*/
String getCypherTypeString( )
{ {
// Are they the same value typ? -[no]-> Are they the same ValueGroup? -[no]-> AnyValue switch ( valueStatus )
// TODO: We could check for String first and could skip the other instanceof checks (has Pro/Cons) {
case VALUE:
return concreteValue.getTypeName().toUpperCase();
case VALUE_GROUP:
return valueGroup.name();
case ANY_VALUE:
return ANYVALUE;
default:
throw new IllegalStateException( "Did not recognize ValueStatus" );
}
}

/*
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_VALUE, if nothing matches
*/
void compareAndPutValueType( Value newValue )
{
if ( newValue == null )
{
throw new IllegalArgumentException();
}


if ( typeExampleValue instanceof Value ) switch ( valueStatus )
{ {
case VALUE:
// check if classes match // check if classes match
if ( !currentValue.getClass().equals( typeExampleValue.getClass() ) ) if ( !concreteValue.getClass().equals( newValue.getClass() ) )
{ {
// Clases don't match -> update needed // Clases don't match -> update needed
if ( currentValue.valueGroup().equals( ((Value) typeExampleValue).valueGroup() ) ) if ( valueGroup.equals( newValue.valueGroup() ) )
{ {
// same valueGroup -> set that // same valueGroup -> set that
typeExampleValue = currentValue.valueGroup(); valueStatus = VALUE_GROUP;
} }
else else
{ {
// Not same valuegroup -> set to AnyValue // Not same valueGroup -> update to AnyValue
typeExampleValue = ANYVALUE; valueStatus = ANY_VALUE;
} }
} }
} break;
else if ( typeExampleValue instanceof ValueGroup ) case VALUE_GROUP:
{ if ( !valueGroup.equals( newValue.valueGroup() ) )
if ( !currentValue.valueGroup().equals( typeExampleValue ) )
{ {
// not same valueGroup -> update to AnyValue // not same valueGroup -> update to AnyValue
typeExampleValue = ANYVALUE; valueStatus = ANY_VALUE;
} }
break;
case ANY_VALUE:
// DO nothing, cannot go higher
break;
default:
throw new IllegalStateException( "Did not recognize ValueStatus" );
} }
} }
return typeExampleValue;
} }


/* enum ValueStatus
This method translates an Object from deriveValueType() into a String
*/
private String getCypherTypeString( Object valueType )
{ {
String cypherType; VALUE,
if ( valueType instanceof Value ) VALUE_GROUP,
{ ANY_VALUE
cypherType = ((Value) valueType).getTypeName().toUpperCase();
}
else if ( valueType instanceof ValueGroup )
{
cypherType = ((ValueGroup) valueType).name();
}
else
{
cypherType = valueType.toString();
}
return cypherType;
} }
} }
Expand Up @@ -150,7 +150,7 @@ public void testSchemaTableWithSimilarNodesHavingDifferentPropertyValueTypes() t
assertThat( asList( stream ), containsInAnyOrder( assertThat( asList( stream ), containsInAnyOrder(
equalTo( new Object[]{"Node", Arrays.asList(), "prop1", "STRING"} ), equalTo( new Object[]{"Node", Arrays.asList(), "prop1", "STRING"} ),
equalTo( new Object[]{"Node", Arrays.asList(), "prop2", "NUMBER"} ), equalTo( new Object[]{"Node", Arrays.asList(), "prop2", "NUMBER"} ),
equalTo( new Object[]{"Node", Arrays.asList(), "prop3", "ANYVALUE"} ) ) ); equalTo( new Object[]{"Node", Arrays.asList(), "prop3", "ANY_VALUE"} ) ) );


// Just for printing out the result if needed // Just for printing out the result if needed
//printStream( stream ); //printStream( stream );
Expand Down Expand Up @@ -264,7 +264,7 @@ public void testSchemaTableWithSimilarRelationshipsHavingDifferentPropertyValueT
equalTo( new Object[]{"Node", Arrays.asList(), null, null} ), equalTo( new Object[]{"Node", Arrays.asList(), null, null} ),
equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop1", "STRING"} ), equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop1", "STRING"} ),
equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop2", "NUMBER"} ), equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop2", "NUMBER"} ),
equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop3", "ANYVALUE"} ) ) ); equalTo( new Object[]{"Relationship", Arrays.asList( "R" ), "prop3", "ANY_VALUE"} ) ) );


// Just for printing out the result if needed // Just for printing out the result if needed
//printStream( stream ); //printStream( stream );
Expand Down

0 comments on commit 7d4e24d

Please sign in to comment.