Skip to content

Commit

Permalink
Started working on adding new schema procedure
Browse files Browse the repository at this point in the history
Added method to get common type name to values.
Made LabelSet able to be used as key in HashMaps.
  • Loading branch information
SaschaPeukert committed Jul 26, 2018
1 parent af7bab5 commit 69ac745
Show file tree
Hide file tree
Showing 41 changed files with 763 additions and 19 deletions.
Expand Up @@ -19,6 +19,7 @@
*/
package org.neo4j.internal.kernel.api;

import java.util.Arrays;
import java.util.NoSuchElementException;

/**
Expand Down Expand Up @@ -64,5 +65,21 @@ public long[] all()
{
return EMPTY;
}

@Override
public int hashCode()
{
return Arrays.hashCode( EMPTY );
}

@Override
public boolean equals( Object obj )
{
if ( obj instanceof LabelSet )
{
return ((LabelSet) obj).all().length == 0;
}
return false;
}
};
}
Expand Up @@ -19,6 +19,7 @@
*/
package org.neo4j.internal.kernel.api.helpers;

import java.util.Arrays;
import java.util.Map;

import org.neo4j.internal.kernel.api.LabelSet;
Expand Down Expand Up @@ -71,6 +72,22 @@ public long[] all()
{
return labels;
}

@Override
public int hashCode()
{
return Arrays.hashCode( labels );
}

@Override
public boolean equals( Object obj )
{
if ( obj instanceof LabelSet )
{
return Arrays.equals( labels, ((LabelSet) obj).all() );
}
return false;
}
};
}
}
@@ -0,0 +1,83 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

package org.neo4j.kernel.builtinprocs;

import java.util.List;
import java.util.stream.Stream;

import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Procedure;

public class BuiltInSchemaProcedures
{
@Context
public GraphDatabaseAPI db;

@Context
public KernelTransaction tx;

@Procedure( name = "db.schemaAsTable", mode = Mode.READ )
@Description( "Show the schema of the data in tabular form." )
public Stream<SchemaInfoResult> schemaAsTable()
{
return new SchemaCalculator( db, tx ).calculateTabularResultStream();
}

// TODO: Next step
// @Procedure( value = "db.schema.graph", mode = Mode.READ ) // TODO: Change to db.schema when ready to completly replace old one
// @Description( "Show the schema of the data." ) // old description
// public Stream<SchemaProcedure.GraphResult> schemaAsGraph() //TODO: Maybe extract inner result classes
// {
// return new SchemaCalculator( db, tx ).calculateGraphResultStream();
// }

public static class SchemaInfoResult
{
/**
* Indicates whether the entry is a node or a relationship
*/
public final String type;
/**
* A combination of labels or a relationship
*/
public final List<String> nodeLabelsOrRelType;
/**
* A property that occurs on the given label combination / relationship type
*/
public final String property;
/**
* The CypherType of the given property on the given label combination / relationship type
*/
public final String cypherType;

public SchemaInfoResult( String type, List<String> nodeLabelsOrRelType, String property, String cypherType )
{
this.type = type;
this.nodeLabelsOrRelType = nodeLabelsOrRelType;
this.property = property;
this.cypherType = cypherType;
}
}
}
@@ -0,0 +1,240 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.builtinprocs;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

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.internal.kernel.api.CursorFactory;
import org.neo4j.internal.kernel.api.LabelSet;
import org.neo4j.internal.kernel.api.NamedToken;
import org.neo4j.internal.kernel.api.NodeCursor;
import org.neo4j.internal.kernel.api.PropertyCursor;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.SchemaRead;
import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.Statement;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueGroup;

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

private Map<LabelSet,Set<Integer>> labelSetToPropertyKeysMapping;
// TODO: make those different Set<whatever> etc. into more useful/understandable classes?!
private Table<LabelSet,Integer,Object> labelSetAndNodePropertyKeyIdToValueTypeMapping; // Dislike object here...
private Map<Integer,String> labelIdToLabelNameMapping;
private Map<Integer,String> propertyIdToPropertylNameMapping;

private final Set<Integer> emptyPropertyIdSet = Collections.unmodifiableSet( Collections.emptySet() );

private final String ANYVALUE = "ANYVALUE";
private final String NODE = "Node";
private final String RELATIONSHIP = "Relationship";

//TODO: Change Table to Pair!
//TODO: Remove Guava from pom

public SchemaCalculator( GraphDatabaseAPI db, KernelTransaction ktx )
{
this.db = db;
this.ktx = ktx;
labelSetAndNodePropertyKeyIdToValueTypeMapping = HashBasedTable.create();
}

public Stream<BuiltInSchemaProcedures.SchemaInfoResult> calculateTabularResultStream()
{
calculateSchema();

// Build up a stream of SchemaInfoResult objects from calculated mappings
List<BuiltInSchemaProcedures.SchemaInfoResult> results = new ArrayList<>();
for ( LabelSet labelSet : labelSetToPropertyKeysMapping.keySet() )
{
// lookup label names and produce list of names
List<String> labelNames = new ArrayList<>();
for ( int i = 0; i < labelSet.numberOfLabels(); i++ )
{
String name = labelIdToLabelNameMapping.get( labelSet.label( i ) );
labelNames.add( name );
}

Set<Integer> propertyIds = labelSetToPropertyKeysMapping.get( labelSet );

if ( propertyIds.size() == 0 )
{
results.add( new BuiltInSchemaProcedures.SchemaInfoResult( NODE, labelNames, null, null ) );
}
else
{
for ( Integer propId : propertyIds )
{
// lookup propId name and valueGroup
String propName = propertyIdToPropertylNameMapping.get( propId );
Object valueType = labelSetAndNodePropertyKeyIdToValueTypeMapping.get( labelSet, propId );

String cypherType;
if ( valueType instanceof Value )
{
cypherType = ((Value) valueType).getTypeName().toUpperCase();
}
else if ( valueType instanceof ValueGroup )
{
cypherType = ((ValueGroup) valueType).name();
}
else
{
cypherType = valueType.toString();
}

results.add( new BuiltInSchemaProcedures.SchemaInfoResult( NODE, labelNames, propName, cypherType ) );
}
}
}

return results.stream();
}

public Stream<SchemaProcedure.GraphResult> calculateGraphResultStream()
{
calculateSchema();
//TODO finish this
throw new NotImplementedException();
}

private void calculateSchema() //TODO: Parallelize this: (Nodes | Rels) and/or more
{
// this one does most of the work
//TODO: implement this for rels
try ( Statement ignore = ktx.acquireStatement() )
{
Read dataRead = ktx.dataRead();
TokenRead tokenRead = ktx.tokenRead();
SchemaRead schemaRead = ktx.schemaRead();
CursorFactory cursors = ktx.cursors();

// setup mappings
int labelCount = tokenRead.labelCount();
labelSetToPropertyKeysMapping = new HashMap<>( labelCount );
labelIdToLabelNameMapping = new HashMap<>( labelCount );
propertyIdToPropertylNameMapping = new HashMap<>( tokenRead.propertyKeyCount() );

NodeCursor nodeCursor = cursors.allocateNodeCursor();
dataRead.allNodesScan( nodeCursor );

while ( nodeCursor.next() )
{
// each node
LabelSet labels = nodeCursor.labels();
PropertyCursor propertyCursor = cursors.allocatePropertyCursor();
nodeCursor.properties( propertyCursor );
Set<Integer> propertyIds = new HashSet<>(); // is Set really the best fit here?

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

Object typeExampleValue = labelSetAndNodePropertyKeyIdToValueTypeMapping.get( labels, propertyKeyId );
if ( typeExampleValue == null )
{
typeExampleValue = currentValue;
}
else
{
// Object value in labelSetAndNodePropertyKeyIdToValueTypeMapping can be either:
// A) Value, if the Values match on class level
// B) ValueGroup, if the ValueGroups of the Values match
// C) String, if nothing matches

// Are they the same value typ? -[no]-> Are they the same ValueGroup? -[no]-> AnyValue

// TODO: We could check for String first and could skip the other instanceof checks (has Pro/Cons)

if ( typeExampleValue instanceof Value )
{
// check if classes match
if ( !currentValue.getClass().equals( typeExampleValue.getClass() ) )
{
// Clases don't match -> update needed
if ( currentValue.valueGroup().equals( ((Value) typeExampleValue).valueGroup() ) )
{
// same valueGroup -> set that
typeExampleValue = currentValue.valueGroup();
}
else
{
// Not same valuegroup -> set to AnyValue
typeExampleValue = ANYVALUE;
}
}
}
else if ( typeExampleValue instanceof ValueGroup )
{
if ( !currentValue.valueGroup().equals( typeExampleValue ) )
{
// not same valueGroup -> update to AnyValue
typeExampleValue = ANYVALUE;
}
}
}

labelSetAndNodePropertyKeyIdToValueTypeMapping.put( labels, propertyKeyId, typeExampleValue );
propertyIds.add( propertyKeyId );
}

Set<Integer> oldPropertyKeySet = labelSetToPropertyKeysMapping.getOrDefault( labels, emptyPropertyIdSet );
propertyIds.addAll( oldPropertyKeySet );
labelSetToPropertyKeysMapping.put( labels, propertyIds );
}

// go through all labels
Iterator<NamedToken> labelIterator = tokenRead.labelsGetAllTokens();
while ( labelIterator.hasNext() )
{
NamedToken label = labelIterator.next();
labelIdToLabelNameMapping.put( label.id(), label.name() );
}

// go through all propertyKeys
Iterator<NamedToken> propIterator = tokenRead.propertyKeyGetAllTokens();
while ( propIterator.hasNext() )
{
NamedToken prop = propIterator.next();
propertyIdToPropertylNameMapping.put( prop.id(), prop.name() );
}
}
}
}

0 comments on commit 69ac745

Please sign in to comment.